@magpie-community/opencode-workbuddy-auth 0.1.39 → 0.1.41
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 +14 -2
- package/index.mjs +80 -11
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -124,7 +124,11 @@ estimate.
|
|
|
124
124
|
|
|
125
125
|
Each chat carries WorkBuddy's headers:
|
|
126
126
|
- `Authorization`, `X-User-Id`, `X-Domain`, `X-Product: SaaS`, `X-IDE-Type: WorkBuddy`;
|
|
127
|
-
- `User-Agent
|
|
127
|
+
- the desktop CLI's `User-Agent`, `WorkBuddy/<app> <appName>/<app> CLI/<cli>`:
|
|
128
|
+
`WorkBuddy/5.9.3 WorkBuddy/5.9.3 CLI/2.165.0` on the China build and
|
|
129
|
+
`WorkBuddy/5.8.4 workbuddy-ai/5.8.4 CLI/2.161.0` on WorkBuddy AI, each
|
|
130
|
+
build's current app on 2026-10-28; `X-IDE-Version` is the app's version.
|
|
131
|
+
A User-Agent not starting `WorkBuddy/<version>` is answered error 10085;
|
|
128
132
|
- the client's, which WorkBuddy's usage list shows as the client that asked
|
|
129
133
|
(使用端): `X-Agent-Purpose: conversation`, `X-IDE-Name: WorkBuddy`,
|
|
130
134
|
`X-IDE-Version`, `X-Agent-Intent: craft`, `X-Requested-With`. Both builds
|
|
@@ -139,7 +143,15 @@ Each chat carries WorkBuddy's headers:
|
|
|
139
143
|
else the chat's first user message; nothing is made up when neither names
|
|
140
144
|
one, a made-up conversation of the request's own being what splits one
|
|
141
145
|
conversation into many in the usage detail;
|
|
142
|
-
-
|
|
146
|
+
- what the CLI adds to a model request alone: `X-Agent-Type: main` on both
|
|
147
|
+
builds, `X-Agent-Runtime: local-agent` on the China build (new in 5.9),
|
|
148
|
+
`Accept: application/json`, and the `X-Stainless-*` headers of the OpenAI
|
|
149
|
+
SDK it sends through (openai 6.25.0 on Electron 37's Node v22.21.1, a
|
|
150
|
+
1200s timeout, no retries).
|
|
151
|
+
|
|
152
|
+
Not sent: `X-Device-Token`, which the desktop app gets from Tencent's
|
|
153
|
+
native device SDK before each prompt, and a `traceparent`, which the CLI
|
|
154
|
+
adds only while its server-side tracing is on.
|
|
143
155
|
|
|
144
156
|
A chat that doesn't open with a system message is given WorkBuddy's
|
|
145
157
|
default one, as the app does.
|
package/index.mjs
CHANGED
|
@@ -6,7 +6,8 @@
|
|
|
6
6
|
// the token as WorkBuddy's desktop app does) or is taken from WorkBuddy
|
|
7
7
|
// desktop's own sign-in. Its chats are chat completions at <endpoint>/v2,
|
|
8
8
|
// streamed only, with WorkBuddy's headers: a request whose User-Agent isn't
|
|
9
|
-
// WorkBuddy/<version> is refused (error 10085).
|
|
9
|
+
// WorkBuddy/<version> is refused (error 10085). Each build's headers are
|
|
10
|
+
// those of its current desktop app (see SITES' app, cli and appName).
|
|
10
11
|
|
|
11
12
|
import { createHash, randomBytes } from "node:crypto"
|
|
12
13
|
import { readFileSync } from "node:fs"
|
|
@@ -14,7 +15,6 @@ import { homedir } from "node:os"
|
|
|
14
15
|
import { join } from "node:path"
|
|
15
16
|
|
|
16
17
|
const APP_VERSION = "2.0.0" // the version WorkBuddy's sign-in page is opened with
|
|
17
|
-
const UA_VERSION = "5.5.6" // WorkBuddy/<this> is the User-Agent the API takes
|
|
18
18
|
const POLL_MS = 1000
|
|
19
19
|
const SIGN_IN_MS = 5 * 60 * 1000
|
|
20
20
|
const EARLY_MS = 60 * 1000 // a token this close to its end is refreshed before a request
|
|
@@ -67,6 +67,16 @@ const SITES = {
|
|
|
67
67
|
authID: "workbuddy-desktop",
|
|
68
68
|
platform: "workbuddy",
|
|
69
69
|
ai: false,
|
|
70
|
+
// the desktop app the requests are WorkBuddy's own client's of: its
|
|
71
|
+
// version, its bundled CLI's (@genie/agent-cli), and the CLI's
|
|
72
|
+
// product.json applicationName. Read from WorkBuddy 5.9.3.42398134,
|
|
73
|
+
// the update feed's current build on 2026-10-28.
|
|
74
|
+
app: "5.9.3",
|
|
75
|
+
cli: "2.165.0",
|
|
76
|
+
appName: "WorkBuddy",
|
|
77
|
+
// 5.9's CLI says on each chat where the agent runs; the desktop's own
|
|
78
|
+
// is "local-agent" (X-Agent-Runtime, absent in 5.8)
|
|
79
|
+
runtime: "local-agent",
|
|
70
80
|
// where this build's API keys are made (see keyAccount)
|
|
71
81
|
keys: "https://www.workbuddy.cn/profile/keys",
|
|
72
82
|
models: CN_MODELS,
|
|
@@ -81,6 +91,11 @@ const SITES = {
|
|
|
81
91
|
authID: "workbuddy-desktop-ai",
|
|
82
92
|
platform: "workbuddy-ai",
|
|
83
93
|
ai: true,
|
|
94
|
+
// WorkBuddy AI 5.8.4.41366630, its update feed's current build on
|
|
95
|
+
// 2026-10-28 (see the China build's)
|
|
96
|
+
app: "5.8.4",
|
|
97
|
+
cli: "2.161.0",
|
|
98
|
+
appName: "workbuddy-ai",
|
|
84
99
|
keys: "https://www.workbuddy.ai/profile/keys",
|
|
85
100
|
models: AI_MODELS,
|
|
86
101
|
// models whose images WorkBuddy AI drops though its config says they
|
|
@@ -155,7 +170,7 @@ async function call(site, method, path, headers = {}, body) {
|
|
|
155
170
|
headers: {
|
|
156
171
|
"Content-Type": "application/json",
|
|
157
172
|
Accept: "application/json",
|
|
158
|
-
"User-Agent": "WorkBuddy/" +
|
|
173
|
+
"User-Agent": "WorkBuddy/" + site.app,
|
|
159
174
|
...headers,
|
|
160
175
|
},
|
|
161
176
|
body: body === undefined ? undefined : JSON.stringify(body),
|
|
@@ -312,15 +327,51 @@ function sign(site, a, headers) {
|
|
|
312
327
|
}
|
|
313
328
|
headers.set("X-Product", "SaaS")
|
|
314
329
|
headers.set("X-IDE-Type", "WorkBuddy")
|
|
315
|
-
headers.set("User-Agent",
|
|
330
|
+
headers.set("User-Agent", cliAgent(site))
|
|
316
331
|
headers.set("X-Requested-With", "XMLHttpRequest")
|
|
317
332
|
headers.set("X-Agent-Intent", "craft")
|
|
318
333
|
headers.set("X-Agent-Purpose", "conversation")
|
|
319
334
|
headers.set("X-IDE-Name", "WorkBuddy")
|
|
320
|
-
headers.set("X-IDE-Version",
|
|
335
|
+
headers.set("X-IDE-Version", site.app)
|
|
321
336
|
for (const [k, v] of Object.entries(enterpriseHeaders(a))) headers.set(k, v)
|
|
322
|
-
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
// cliAgent is the User-Agent WorkBuddy's desktop CLI sends its API calls
|
|
340
|
+
// (a chat, the product config) with: UserAgentHttpInterceptor's
|
|
341
|
+
// buildUserAgent puts the platform (CLIENT_INFO_PLATFORM "WorkBuddy", at
|
|
342
|
+
// the app's version) first, then the CLI's applicationName at the same
|
|
343
|
+
// version, then CLIENT_INFO_USER_AGENT_EXTENSION, "CLI/<its version>".
|
|
344
|
+
function cliAgent(site) {
|
|
345
|
+
return `WorkBuddy/${site.app} ${site.appName}/${site.app} CLI/${site.cli}`
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
// OPENAI_SDK and NODE are what the CLI's chats go out through: the OpenAI
|
|
349
|
+
// SDK it bundles (openai 6.25.0) and the Node its Electron runs it on
|
|
350
|
+
// (Electron 37.10.3, ELECTRON_RUN_AS_NODE).
|
|
351
|
+
const OPENAI_SDK = "6.25.0"
|
|
352
|
+
const NODE = "v22.21.1"
|
|
353
|
+
const SDK_OS = { darwin: "MacOS", win32: "Windows", linux: "Linux", freebsd: "FreeBSD", openbsd: "OpenBSD", android: "Android" }
|
|
354
|
+
const SDK_ARCH = { x64: "x64", arm64: "arm64", arm: "arm", ia32: "x32", x32: "x32" }
|
|
355
|
+
|
|
356
|
+
// chatHeaders sets on a chat what WorkBuddy's CLI adds to its model
|
|
357
|
+
// requests alone (axiosToFetchAdapter, both builds): the agent type
|
|
358
|
+
// (resolveModelCallAgentType, "main" for the conversation's own agent),
|
|
359
|
+
// the runtime where a build sends one, and the OpenAI SDK's own headers,
|
|
360
|
+
// which the CLI's fetch passes on. Its client has a 1200s timeout and
|
|
361
|
+
// no retries of its own.
|
|
362
|
+
function chatHeaders(site, headers) {
|
|
323
363
|
headers.set("X-Agent-Type", "main")
|
|
364
|
+
if (site.runtime) headers.set("X-Agent-Runtime", site.runtime)
|
|
365
|
+
else headers.delete("X-Agent-Runtime")
|
|
366
|
+
headers.set("Accept", "application/json")
|
|
367
|
+
headers.set("X-Stainless-Retry-Count", "0")
|
|
368
|
+
headers.set("X-Stainless-Timeout", "1200")
|
|
369
|
+
headers.set("X-Stainless-Lang", "js")
|
|
370
|
+
headers.set("X-Stainless-Package-Version", OPENAI_SDK)
|
|
371
|
+
headers.set("X-Stainless-OS", SDK_OS[process.platform] ?? (process.platform ? `Other:${process.platform}` : "Unknown"))
|
|
372
|
+
headers.set("X-Stainless-Arch", SDK_ARCH[process.arch] ?? (process.arch ? `other:${process.arch}` : "unknown"))
|
|
373
|
+
headers.set("X-Stainless-Runtime", "node")
|
|
374
|
+
headers.set("X-Stainless-Runtime-Version", NODE)
|
|
324
375
|
}
|
|
325
376
|
|
|
326
377
|
// keyAccount is the account an API key (OpenCode's {type: "api", key}) is
|
|
@@ -1459,7 +1510,7 @@ const statusText = (n) => STATUS_TEXT[n] || `HTTP ${n}`
|
|
|
1459
1510
|
function meterAsked(site, a, path, body = {}, signal = AbortSignal.timeout(20000)) {
|
|
1460
1511
|
return fetch(site.endpoint + path, {
|
|
1461
1512
|
method: "POST",
|
|
1462
|
-
headers: { "Content-Type": "application/json", Accept: "application/json", "User-Agent": "WorkBuddy/" +
|
|
1513
|
+
headers: { "Content-Type": "application/json", Accept: "application/json", "User-Agent": "WorkBuddy/" + site.app,
|
|
1463
1514
|
Authorization: "Bearer " + a.access, "X-User-Id": a.uid ?? "", "X-Domain": domainOf(site, a), "X-Product": "SaaS",
|
|
1464
1515
|
"X-IDE-Type": "WorkBuddy", ...enterpriseHeaders(a) },
|
|
1465
1516
|
body: JSON.stringify(body),
|
|
@@ -1687,14 +1738,31 @@ const compact = (n) => (Number.isInteger(n) ? String(n) : n.toFixed(2).replace(/
|
|
|
1687
1738
|
// as used or left beside the share (magpie#659). The free plan is said as a
|
|
1688
1739
|
// plan: "Free" alone beside the account read as the model being free, and
|
|
1689
1740
|
// routing as preferring it for that (#337).
|
|
1741
|
+
//
|
|
1742
|
+
// A package with no CycleTotalCapacity has no denominator: it is sent as
|
|
1743
|
+
// magpie's balance-shaped window (#3950), {balance, unit} with no used,
|
|
1744
|
+
// limit or aside, and routing ranks it by what is left. A literal "0" total
|
|
1745
|
+
// is still a total, not a wallet: it stays on the old path, so a lapsed or
|
|
1746
|
+
// placeholder package adds nothing. A literal "0" remainder is a reading
|
|
1747
|
+
// of nothing left, not an absent field; only a package with no
|
|
1748
|
+
// CycleRemainCapacity at all leaves the window out. TotalCredits is not a
|
|
1749
|
+
// fallback: an account with no packages at all keeps its unknown allowance,
|
|
1750
|
+
// as before, so its free models are not skipped.
|
|
1690
1751
|
function usageOf(sum, plan) {
|
|
1691
1752
|
const out = { plan: plan || (sum?.IsPaidUser ? "Pro" : "Free plan"), windows: [] }
|
|
1692
|
-
let total = 0, used = 0
|
|
1753
|
+
let total = 0, used = 0, left = 0, bags = 0
|
|
1693
1754
|
for (const p of sum?.Packages ?? []) {
|
|
1694
|
-
|
|
1695
|
-
|
|
1755
|
+
const hasTotal = p?.CycleTotalCapacity != null && p.CycleTotalCapacity !== ""
|
|
1756
|
+
if (hasTotal) {
|
|
1757
|
+
total += capacity(p.CycleTotalCapacity)
|
|
1758
|
+
used += capacity(p?.CycleUsedCapacity)
|
|
1759
|
+
} else if (p?.CycleRemainCapacity != null && p.CycleRemainCapacity !== "") {
|
|
1760
|
+
left += capacity(p.CycleRemainCapacity)
|
|
1761
|
+
bags++
|
|
1762
|
+
}
|
|
1696
1763
|
}
|
|
1697
1764
|
if (total > 0) out.windows.push({ name: "Credits", used: (100 * used) / total, display: `${compact(used)} / ${compact(total)}`, amount: used, limit: total, unit: "credits" })
|
|
1765
|
+
if (bags > 0 && (total === 0 || left > 0)) out.windows.push({ name: "Prepaid credits", balance: left, display: `${compact(left)} credits`, unit: "credits" })
|
|
1698
1766
|
return out
|
|
1699
1767
|
}
|
|
1700
1768
|
|
|
@@ -1992,7 +2060,7 @@ function effortsOf(d, site, id) {
|
|
|
1992
2060
|
async function liveModels(site, a) {
|
|
1993
2061
|
const headers = new Headers()
|
|
1994
2062
|
sign(site, a, headers)
|
|
1995
|
-
headers.set("User-Agent",
|
|
2063
|
+
headers.set("User-Agent", cliAgent(site))
|
|
1996
2064
|
headers.set("X-Requested-With", "XMLHttpRequest")
|
|
1997
2065
|
headers.set("Accept", "application/json")
|
|
1998
2066
|
const res = await fetch(site.endpoint + "/v3/config", { headers, signal: AbortSignal.timeout(15000) })
|
|
@@ -2242,6 +2310,7 @@ function makePlugin(site) {
|
|
|
2242
2310
|
} else {
|
|
2243
2311
|
body = withoutUnsigned(body)
|
|
2244
2312
|
}
|
|
2313
|
+
chatHeaders(site, headers)
|
|
2245
2314
|
attend(headers, body, session)
|
|
2246
2315
|
const post = (b) =>
|
|
2247
2316
|
fetch(req ? req.url : input, { ...init, method: init?.method ?? req?.method, headers, body: b }).catch((e) => unanswered(site, a, b, e, init?.signal))
|
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.41",
|
|
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",
|