@magpie-community/opencode-workbuddy-auth 0.1.38 → 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.
- package/README.md +20 -4
- package/index.mjs +92 -9
- 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.
|
|
@@ -164,8 +176,12 @@ refusal rests the account.
|
|
|
164
176
|
|
|
165
177
|
The loader also translates OpenAI-compatible request spellings WorkBuddy
|
|
166
178
|
doesn't take: `max_completion_tokens` becomes `max_tokens`, `developer`
|
|
167
|
-
messages become `system`,
|
|
168
|
-
string form
|
|
179
|
+
messages become `system`, an object `tool_choice` becomes WorkBuddy's
|
|
180
|
+
string form, and an earlier assistant turn's `reasoning_content` (or
|
|
181
|
+
`reasoning_details` text) goes as `reasoning`, the field WorkBuddy's own
|
|
182
|
+
client sends. WorkBuddy drops `reasoning_content`, so a client such as Grok
|
|
183
|
+
Build that sends it lost its earlier thinking; nothing is folded into the
|
|
184
|
+
message's content.
|
|
169
185
|
|
|
170
186
|
A chat's tools are given shapes WorkBuddy's models take. Without them
|
|
171
187
|
WorkBuddy AI answers 400 "Invalid request parameters" (11133), DeepSeek's
|
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
|
|
@@ -705,6 +756,36 @@ function unflag(messages) {
|
|
|
705
756
|
// OpenAI-compatible client normally uses max_completion_tokens and may call a
|
|
706
757
|
// reasoning prompt developer; WorkBuddy's endpoint takes max_tokens and
|
|
707
758
|
// system instead.
|
|
759
|
+
// An earlier turn's thinking goes back as `reasoning`, the field WorkBuddy's
|
|
760
|
+
// own client (CodeBuddy's agents converter) sets on an assistant message.
|
|
761
|
+
// WorkBuddy drops `reasoning_content`, which Grok Build, DeepSeek-style
|
|
762
|
+
// clients and magpie's own replies use: measured 2026-10-27 on
|
|
763
|
+
// www.workbuddy.ai, a codeword in reasoning_content reached none of
|
|
764
|
+
// kimi-k2.6, kimi-k3, glm-5.3, deepseek-v4.1-flash and minimax-m3, while
|
|
765
|
+
// the same words as `reasoning` reached kimi-k2.6 and minimax-m3.
|
|
766
|
+
// OpenRouter-style reasoning_details are read for their text the same way.
|
|
767
|
+
function reasoningOf(m) {
|
|
768
|
+
if (typeof m.reasoning_content === "string" && m.reasoning_content) return m.reasoning_content
|
|
769
|
+
if (Array.isArray(m.reasoning_details)) {
|
|
770
|
+
const text = m.reasoning_details.map((d) => (typeof d?.text === "string" ? d.text : typeof d?.summary === "string" ? d.summary : "")).join("")
|
|
771
|
+
if (text) return text
|
|
772
|
+
}
|
|
773
|
+
return ""
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
function asReasoning(messages) {
|
|
777
|
+
let changed = false
|
|
778
|
+
for (const m of messages) {
|
|
779
|
+
if (m?.role !== "assistant" || (m.reasoning_content === undefined && m.reasoning_details === undefined)) continue
|
|
780
|
+
const text = reasoningOf(m)
|
|
781
|
+
if (text && !(typeof m.reasoning === "string" && m.reasoning)) m.reasoning = text
|
|
782
|
+
delete m.reasoning_content
|
|
783
|
+
delete m.reasoning_details
|
|
784
|
+
changed = true
|
|
785
|
+
}
|
|
786
|
+
return changed
|
|
787
|
+
}
|
|
788
|
+
|
|
708
789
|
function withSystem(body) {
|
|
709
790
|
// a body magpie or OpenCode hands over as bytes is read as the text it is
|
|
710
791
|
if (body instanceof ArrayBuffer || ArrayBuffer.isView(body)) body = new TextDecoder().decode(body)
|
|
@@ -723,6 +804,7 @@ function withSystem(body) {
|
|
|
723
804
|
m.role = "system"
|
|
724
805
|
changed = true
|
|
725
806
|
}
|
|
807
|
+
if (asReasoning(b.messages)) changed = true
|
|
726
808
|
if (b.messages[0]?.role !== "system") {
|
|
727
809
|
b.messages.unshift({ role: "system", content: "You are a helpful assistant." })
|
|
728
810
|
changed = true
|
|
@@ -1428,7 +1510,7 @@ const statusText = (n) => STATUS_TEXT[n] || `HTTP ${n}`
|
|
|
1428
1510
|
function meterAsked(site, a, path, body = {}, signal = AbortSignal.timeout(20000)) {
|
|
1429
1511
|
return fetch(site.endpoint + path, {
|
|
1430
1512
|
method: "POST",
|
|
1431
|
-
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,
|
|
1432
1514
|
Authorization: "Bearer " + a.access, "X-User-Id": a.uid ?? "", "X-Domain": domainOf(site, a), "X-Product": "SaaS",
|
|
1433
1515
|
"X-IDE-Type": "WorkBuddy", ...enterpriseHeaders(a) },
|
|
1434
1516
|
body: JSON.stringify(body),
|
|
@@ -1961,7 +2043,7 @@ function effortsOf(d, site, id) {
|
|
|
1961
2043
|
async function liveModels(site, a) {
|
|
1962
2044
|
const headers = new Headers()
|
|
1963
2045
|
sign(site, a, headers)
|
|
1964
|
-
headers.set("User-Agent",
|
|
2046
|
+
headers.set("User-Agent", cliAgent(site))
|
|
1965
2047
|
headers.set("X-Requested-With", "XMLHttpRequest")
|
|
1966
2048
|
headers.set("Accept", "application/json")
|
|
1967
2049
|
const res = await fetch(site.endpoint + "/v3/config", { headers, signal: AbortSignal.timeout(15000) })
|
|
@@ -2211,6 +2293,7 @@ function makePlugin(site) {
|
|
|
2211
2293
|
} else {
|
|
2212
2294
|
body = withoutUnsigned(body)
|
|
2213
2295
|
}
|
|
2296
|
+
chatHeaders(site, headers)
|
|
2214
2297
|
attend(headers, body, session)
|
|
2215
2298
|
const post = (b) =>
|
|
2216
2299
|
fetch(req ? req.url : input, { ...init, method: init?.method ?? req?.method, headers, body: b }).catch((e) => unanswered(site, a, b, e, init?.signal))
|
|
@@ -2492,4 +2575,4 @@ export const WorkBuddyAuthPlugin = makePlugin(SITES.workbuddy)
|
|
|
2492
2575
|
export const WorkBuddyAIAuthPlugin = makePlugin(SITES["workbuddy-ai"])
|
|
2493
2576
|
|
|
2494
2577
|
// for tests
|
|
2495
|
-
export const _internal = { FREE_ONLY, PRICE_TTL, keyAccount, remoteConfig, cycleOf, typeUnions, systemAsUser, systemKey, DEFAULT_SYSTEM, 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, isMessages, withoutUnsigned, withoutSignedThinking, answeredBefore, nativeOf, modelOf, configModels, API_MESSAGES: NPM_MESSAGES, API_CHAT: NPM }
|
|
2578
|
+
export const _internal = { FREE_ONLY, PRICE_TTL, keyAccount, remoteConfig, cycleOf, typeUnions, systemAsUser, systemKey, DEFAULT_SYSTEM, EDGE_READS, fitShape, replyAfterTools, AFTER_TOOLS_REFUSED, enterpriseUsageOf, notDesktop, textOnly, runsOutOf, packagesOf, endOf, enterpriseHeaders, enterpriseOf, liveModels, browserSignIn, withSystem, asReasoning, 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, isMessages, withoutUnsigned, withoutSignedThinking, answeredBefore, nativeOf, modelOf, configModels, API_MESSAGES: NPM_MESSAGES, API_CHAT: NPM }
|
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.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",
|