@magpie-community/opencode-workbuddy-auth 0.1.39 → 0.1.40

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 +14 -2
  2. package/index.mjs +60 -8
  3. 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: WorkBuddy/5.5.6`. Without it, WorkBuddy answers error 10085;
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
- - WorkBuddy AI also takes `X-Agent-Type: main`.
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/" + UA_VERSION,
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", "WorkBuddy/" + UA_VERSION)
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", UA_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
- if (!site.ai) return
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/" + UA_VERSION,
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),
@@ -1992,7 +2043,7 @@ function effortsOf(d, site, id) {
1992
2043
  async function liveModels(site, a) {
1993
2044
  const headers = new Headers()
1994
2045
  sign(site, a, headers)
1995
- headers.set("User-Agent", `CLI/${APP_VERSION} WorkBuddy/${UA_VERSION}`)
2046
+ headers.set("User-Agent", cliAgent(site))
1996
2047
  headers.set("X-Requested-With", "XMLHttpRequest")
1997
2048
  headers.set("Accept", "application/json")
1998
2049
  const res = await fetch(site.endpoint + "/v3/config", { headers, signal: AbortSignal.timeout(15000) })
@@ -2242,6 +2293,7 @@ function makePlugin(site) {
2242
2293
  } else {
2243
2294
  body = withoutUnsigned(body)
2244
2295
  }
2296
+ chatHeaders(site, headers)
2245
2297
  attend(headers, body, session)
2246
2298
  const post = (b) =>
2247
2299
  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.39",
3
+ "version": "0.1.40",
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",