@magpie-community/opencode-workbuddy-auth 0.1.22 → 0.1.24
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 +78 -8
- package/package.json +1 -1
package/index.mjs
CHANGED
|
@@ -1136,17 +1136,67 @@ const STATUS_TEXT = {
|
|
|
1136
1136
|
}
|
|
1137
1137
|
const statusText = (n) => STATUS_TEXT[n] || `HTTP ${n}`
|
|
1138
1138
|
|
|
1139
|
-
//
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
const res = await fetch(site.endpoint + path, {
|
|
1139
|
+
// meterAsked is the billing API's reply to path, asked with a's sign-in.
|
|
1140
|
+
function meterAsked(site, a, path, body = {}, signal = AbortSignal.timeout(20000)) {
|
|
1141
|
+
return fetch(site.endpoint + path, {
|
|
1143
1142
|
method: "POST",
|
|
1144
1143
|
headers: { "Content-Type": "application/json", Accept: "application/json", "User-Agent": "WorkBuddy/" + UA_VERSION,
|
|
1145
1144
|
Authorization: "Bearer " + a.access, "X-User-Id": a.uid ?? "", "X-Domain": domainOf(site, a), "X-Product": "SaaS",
|
|
1146
1145
|
"X-IDE-Type": "WorkBuddy", ...enterpriseHeaders(a) },
|
|
1147
1146
|
body: JSON.stringify(body),
|
|
1148
|
-
signal
|
|
1147
|
+
signal,
|
|
1148
|
+
})
|
|
1149
|
+
}
|
|
1150
|
+
|
|
1151
|
+
// EDGE_READS is how much of a request WorkBuddy's front door (APISIX, on
|
|
1152
|
+
// both sites) reads before it answers a sign-in it refuses. A refused
|
|
1153
|
+
// request with a longer body gets no answer at all: the connection is
|
|
1154
|
+
// reset, and the chat fails as "ECONNRESET: The socket connection was
|
|
1155
|
+
// closed unexpectedly", which says nothing of the sign-in (wyp on Discord,
|
|
1156
|
+
// 2026-10-15: a Claude Code session past 2 MiB failed on every turn).
|
|
1157
|
+
// Measured on 2026-10-15 on www.workbuddy.ai and copilot.tencent.com: a
|
|
1158
|
+
// body of 2097152 bytes with a refused token got 401, one of 2097153 a
|
|
1159
|
+
// reset; with an accepted token 20 MB went through.
|
|
1160
|
+
const EDGE_READS = 2 * 1024 * 1024
|
|
1161
|
+
|
|
1162
|
+
const byteLength = (b) =>
|
|
1163
|
+
typeof b === "string" ? Buffer.byteLength(b) : b instanceof ArrayBuffer || ArrayBuffer.isView(b) ? b.byteLength : 0
|
|
1164
|
+
|
|
1165
|
+
// unanswered is what a chat whose fetch failed with err comes to: a chat
|
|
1166
|
+
// past EDGE_READS that the connection was reset on is asked about at the
|
|
1167
|
+
// billing API, which answers a refused sign-in with its 401 whatever the
|
|
1168
|
+
// size. A 401 there is the chat's own answer, said as one; anything else
|
|
1169
|
+
// leaves err as it was, as does a smaller chat, which the front door
|
|
1170
|
+
// answers itself. The chat isn't sent again.
|
|
1171
|
+
async function unanswered(site, a, body, err, signal) {
|
|
1172
|
+
if (signal?.aborted || err?.name === "AbortError" || err?.name === "TimeoutError") throw err
|
|
1173
|
+
const size = byteLength(body)
|
|
1174
|
+
if (size <= EDGE_READS) throw err
|
|
1175
|
+
let status = 0
|
|
1176
|
+
try {
|
|
1177
|
+
const res = await meterAsked(site, a, "/billing/meter/get-user-resource-summary", {}, AbortSignal.timeout(20000))
|
|
1178
|
+
status = res.status
|
|
1179
|
+
await res.body?.cancel()
|
|
1180
|
+
} catch {
|
|
1181
|
+
throw err
|
|
1182
|
+
}
|
|
1183
|
+
if (status !== 401) throw err
|
|
1184
|
+
const mb = (size / (1024 * 1024)).toFixed(1)
|
|
1185
|
+
const message =
|
|
1186
|
+
`${site.name} refused this account's sign-in (401). It closes the connection on a refused request over 2 MiB ` +
|
|
1187
|
+
`(this one was ${mb} MiB) instead of answering, which is why it read as ECONNRESET. ` +
|
|
1188
|
+
(a.source === "desktop" ? `Sign in to ${site.name} desktop again.` : `Sign in to ${site.name} again in magpie.`)
|
|
1189
|
+
return new Response(JSON.stringify({ error: { message, type: "authentication_error", code: 401 } }), {
|
|
1190
|
+
status: 401,
|
|
1191
|
+
statusText: "Unauthorized",
|
|
1192
|
+
headers: { "content-type": "application/json" },
|
|
1149
1193
|
})
|
|
1194
|
+
}
|
|
1195
|
+
|
|
1196
|
+
// meter asks the billing API as magpie's built-in did, its errors said
|
|
1197
|
+
// the same: WorkBuddy's message, else its code, else the HTTP status.
|
|
1198
|
+
async function meter(site, a, path, body = {}) {
|
|
1199
|
+
const res = await meterAsked(site, a, path, body)
|
|
1150
1200
|
let env = {}
|
|
1151
1201
|
try {
|
|
1152
1202
|
env = JSON.parse(await res.text()) ?? {}
|
|
@@ -1209,6 +1259,24 @@ function enterpriseUsageOf(u) {
|
|
|
1209
1259
|
return w
|
|
1210
1260
|
}
|
|
1211
1261
|
|
|
1262
|
+
// spareFree has the credits windows not count the models that cost none
|
|
1263
|
+
// (notModels): a model at x0.00 draws no credits, and with the credits
|
|
1264
|
+
// used up WorkBuddy still serves it (deepseek-v4.1-flash at 560 / 560,
|
|
1265
|
+
// magpie-community/plugins#138, hheei). Counted, the window at 100% put
|
|
1266
|
+
// the account last, and behind every other, for its free models too. A
|
|
1267
|
+
// model free only some hours (a night promotion) is not free here, as in
|
|
1268
|
+
// the list (priceOf). A list that can't be read leaves the windows as they
|
|
1269
|
+
// are: every model counted, as before.
|
|
1270
|
+
async function spareFree(site, a, windows) {
|
|
1271
|
+
if (!windows.length) return windows
|
|
1272
|
+
let free = []
|
|
1273
|
+
try {
|
|
1274
|
+
free = (await liveModels(site, a)).filter((m) => m.free).map((m) => m.id)
|
|
1275
|
+
} catch {}
|
|
1276
|
+
if (free.length) for (const w of windows) w.notModels = free
|
|
1277
|
+
return windows
|
|
1278
|
+
}
|
|
1279
|
+
|
|
1212
1280
|
// The packages' codes, as WorkBuddy's own client lists them when it asks
|
|
1213
1281
|
// for each package's credits (its COMMODITY_CODES): the API refuses a
|
|
1214
1282
|
// detail list without them (10001 "PackageCodes required").
|
|
@@ -1537,7 +1605,8 @@ function makePlugin(site) {
|
|
|
1537
1605
|
const fit = fitted(body)
|
|
1538
1606
|
body = normalizeToolChoice(fit.body)
|
|
1539
1607
|
attend(headers, body, session)
|
|
1540
|
-
const send = (b) =>
|
|
1608
|
+
const send = (b) =>
|
|
1609
|
+
fetch(req ? req.url : input, { ...init, method: init?.method ?? req?.method, headers, body: b }).catch((e) => unanswered(site, a, b, e, init?.signal))
|
|
1541
1610
|
// a model known to turn tool results then the user's message away
|
|
1542
1611
|
// gets the empty reply between them up front (see endsAfterTools)
|
|
1543
1612
|
const model = chatOf(body)?.model
|
|
@@ -1577,7 +1646,7 @@ function makePlugin(site) {
|
|
|
1577
1646
|
try {
|
|
1578
1647
|
ent = enterpriseUsageOf(await meter(site, a, "/billing/meter/get-enterprise-user-usage"))
|
|
1579
1648
|
} catch {}
|
|
1580
|
-
if (ent) return { plan: auth.plan || "Enterprise", windows: [ent], signIn: "kept" }
|
|
1649
|
+
if (ent) return { plan: auth.plan || "Enterprise", windows: await spareFree(site, a, [ent]), signIn: "kept" }
|
|
1581
1650
|
}
|
|
1582
1651
|
// when the credits left run out, for routing to spend them first;
|
|
1583
1652
|
// a list that can't be read leaves it unknown, the card stands
|
|
@@ -1587,6 +1656,7 @@ function makePlugin(site) {
|
|
|
1587
1656
|
Object.assign(credits, runsOutOf(sum, await packagesOf(site, a)) ?? {})
|
|
1588
1657
|
} catch {}
|
|
1589
1658
|
}
|
|
1659
|
+
await spareFree(site, a, out.windows)
|
|
1590
1660
|
return { ...out, signIn: "kept" }
|
|
1591
1661
|
} catch (e) {
|
|
1592
1662
|
return { error: e?.message ?? String(e), signIn: "kept" }
|
|
@@ -1692,4 +1762,4 @@ export const WorkBuddyAuthPlugin = makePlugin(SITES.workbuddy)
|
|
|
1692
1762
|
export const WorkBuddyAIAuthPlugin = makePlugin(SITES["workbuddy-ai"])
|
|
1693
1763
|
|
|
1694
1764
|
// for tests
|
|
1695
|
-
export const _internal = { fitShape, replyAfterTools, AFTER_TOOLS_REFUSED, enterpriseUsageOf, notDesktop, textOnly, runsOutOf, packagesOf, endOf, enterpriseHeaders, enterpriseOf, liveModels, browserSignIn, withSystem, normalizeToolChoice, fitTools, fitted, renamed, unflagged, usageOf, desktopHeld, explained, REFUSED_HINT, RESTRICTED_HINT, EXHAUSTED_HINT, freeCredits, creditsOf, priceOf, fresh, SITES, renewing, renewed, SESSION, attend, turnKey, conversationKey, signatureOf }
|
|
1765
|
+
export const _internal = { EDGE_READS, fitShape, replyAfterTools, AFTER_TOOLS_REFUSED, enterpriseUsageOf, notDesktop, textOnly, runsOutOf, packagesOf, endOf, enterpriseHeaders, enterpriseOf, liveModels, browserSignIn, withSystem, normalizeToolChoice, fitTools, fitted, renamed, unflagged, usageOf, desktopHeld, explained, REFUSED_HINT, RESTRICTED_HINT, EXHAUSTED_HINT, freeCredits, creditsOf, priceOf, fresh, SITES, renewing, renewed, SESSION, attend, turnKey, conversationKey, signatureOf }
|
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.24",
|
|
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",
|