@magpie-community/opencode-workbuddy-auth 0.1.14 → 0.1.16
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 +5 -0
- package/index.mjs +79 -5
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -82,6 +82,11 @@ Each chat carries WorkBuddy's headers:
|
|
|
82
82
|
A chat that doesn't open with a system message is given WorkBuddy's
|
|
83
83
|
default one, as the app does.
|
|
84
84
|
|
|
85
|
+
The loader also translates OpenAI-compatible request spellings WorkBuddy
|
|
86
|
+
doesn't take: `max_completion_tokens` becomes `max_tokens`, `developer`
|
|
87
|
+
messages become `system`, and an object `tool_choice` becomes WorkBuddy's
|
|
88
|
+
string form.
|
|
89
|
+
|
|
85
90
|
A chat's tools are given shapes WorkBuddy's models take. Without them
|
|
86
91
|
WorkBuddy AI answers 400 "Invalid request parameters" (11133), DeepSeek's
|
|
87
92
|
models to each shape below and Kimi's, GPT's, Gemini's and GLM's to some:
|
package/index.mjs
CHANGED
|
@@ -77,9 +77,27 @@ const SITES = {
|
|
|
77
77
|
platform: "workbuddy-ai",
|
|
78
78
|
ai: true,
|
|
79
79
|
models: AI_MODELS,
|
|
80
|
+
// models whose images WorkBuddy AI drops though its config says they
|
|
81
|
+
// take them (see textOnly)
|
|
82
|
+
noImages: /^glm-/,
|
|
80
83
|
},
|
|
81
84
|
}
|
|
82
85
|
|
|
86
|
+
// textOnly says whether site drops the images sent to model, whatever its
|
|
87
|
+
// config says. On 2026-10-13, with a real WorkBuddy AI account (Yirasumi on
|
|
88
|
+
// Discord), www.workbuddy.ai's /v3/config marked every model
|
|
89
|
+
// supportsImages: true, but a chat to glm-5.3-flash, glm-5.3 or glm-5.2
|
|
90
|
+
// came back saying "[System reminder: the current model does not support
|
|
91
|
+
// images. Content filtered. ...]": the server had taken the image out.
|
|
92
|
+
// Its other models (deepseek-v4.1-flash, kimi, gemini, grok, gpt) read
|
|
93
|
+
// them, and WorkBuddy (China)'s glm-5.3-flash does too, so this is WorkBuddy
|
|
94
|
+
// AI's GLM models alone. Said text-only, magpie's image-describing model
|
|
95
|
+
// reads the image for them; a user who said the model takes images keeps
|
|
96
|
+
// their own answer (magpie's settings, over what this list says).
|
|
97
|
+
function textOnly(site, model) {
|
|
98
|
+
return !!site.noImages?.test(model)
|
|
99
|
+
}
|
|
100
|
+
|
|
83
101
|
const NPM = "@ai-sdk/openai-compatible"
|
|
84
102
|
const hex = () => randomBytes(16).toString("hex")
|
|
85
103
|
const sleep = (ms) => new Promise((r) => setTimeout(r, ms))
|
|
@@ -487,8 +505,11 @@ function unflag(messages) {
|
|
|
487
505
|
}
|
|
488
506
|
|
|
489
507
|
// withSystem gives a chat that doesn't open with a system message
|
|
490
|
-
// WorkBuddy's default one, as its app does,
|
|
491
|
-
//
|
|
508
|
+
// WorkBuddy's default one, as its app does, takes FLAGGED's words out of it,
|
|
509
|
+
// and applies the request spellings WorkBuddy's chat endpoint accepts. The
|
|
510
|
+
// OpenAI-compatible client normally uses max_completion_tokens and may call a
|
|
511
|
+
// reasoning prompt developer; WorkBuddy's endpoint takes max_tokens and
|
|
512
|
+
// system instead.
|
|
492
513
|
function withSystem(body) {
|
|
493
514
|
// a body magpie or OpenCode hands over as bytes is read as the text it is
|
|
494
515
|
if (body instanceof ArrayBuffer || ArrayBuffer.isView(body)) body = new TextDecoder().decode(body)
|
|
@@ -497,6 +518,16 @@ function withSystem(body) {
|
|
|
497
518
|
const b = JSON.parse(body)
|
|
498
519
|
if (!Array.isArray(b?.messages) || !b.messages.length) return body
|
|
499
520
|
let changed = unflag(b.messages)
|
|
521
|
+
if (b.max_completion_tokens !== undefined) {
|
|
522
|
+
if (b.max_tokens === undefined) b.max_tokens = b.max_completion_tokens
|
|
523
|
+
delete b.max_completion_tokens
|
|
524
|
+
changed = true
|
|
525
|
+
}
|
|
526
|
+
for (const m of b.messages) {
|
|
527
|
+
if (m?.role !== "developer") continue
|
|
528
|
+
m.role = "system"
|
|
529
|
+
changed = true
|
|
530
|
+
}
|
|
500
531
|
if (b.messages[0]?.role !== "system") {
|
|
501
532
|
b.messages.unshift({ role: "system", content: "You are a helpful assistant." })
|
|
502
533
|
changed = true
|
|
@@ -507,6 +538,49 @@ function withSystem(body) {
|
|
|
507
538
|
}
|
|
508
539
|
}
|
|
509
540
|
|
|
541
|
+
// normalizeToolChoice converts the object form emitted by OpenAI clients to
|
|
542
|
+
// WorkBuddy's string form. It runs after fitted(), so a named choice follows a
|
|
543
|
+
// tool name that was made safe for WorkBuddy.
|
|
544
|
+
function normalizeToolChoice(body) {
|
|
545
|
+
if (typeof body !== "string") return body
|
|
546
|
+
try {
|
|
547
|
+
const b = JSON.parse(body)
|
|
548
|
+
if (!b || typeof b !== "object" || Array.isArray(b) || !("tool_choice" in b)) return body
|
|
549
|
+
const choice = b.tool_choice
|
|
550
|
+
if (typeof choice === "string") {
|
|
551
|
+
if (choice.trim().toLowerCase() === "none") {
|
|
552
|
+
delete b.tool_choice
|
|
553
|
+
delete b.tools
|
|
554
|
+
delete b.functions
|
|
555
|
+
}
|
|
556
|
+
return JSON.stringify(b)
|
|
557
|
+
}
|
|
558
|
+
if (!choice || typeof choice !== "object" || Array.isArray(choice)) {
|
|
559
|
+
delete b.tool_choice
|
|
560
|
+
return JSON.stringify(b)
|
|
561
|
+
}
|
|
562
|
+
const type = typeof choice.type === "string" ? choice.type.trim().toLowerCase() : ""
|
|
563
|
+
if (type === "none") {
|
|
564
|
+
delete b.tool_choice
|
|
565
|
+
delete b.tools
|
|
566
|
+
delete b.functions
|
|
567
|
+
} else if (type === "auto" || type === "required") {
|
|
568
|
+
b.tool_choice = type
|
|
569
|
+
} else if (type === "function") {
|
|
570
|
+
const fn = choice.function && typeof choice.function === "object" && !Array.isArray(choice.function)
|
|
571
|
+
? choice.function
|
|
572
|
+
: undefined
|
|
573
|
+
const name = typeof fn?.name === "string" ? fn.name.trim() : ""
|
|
574
|
+
b.tool_choice = name || "auto"
|
|
575
|
+
} else {
|
|
576
|
+
delete b.tool_choice
|
|
577
|
+
}
|
|
578
|
+
return JSON.stringify(b)
|
|
579
|
+
} catch {
|
|
580
|
+
return body
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
|
|
510
584
|
// ---- tools WorkBuddy takes ----------------------------------------------------
|
|
511
585
|
|
|
512
586
|
// WorkBuddy AI answers 400 code 11133 "Invalid request parameters" (the
|
|
@@ -1124,7 +1198,7 @@ async function liveModels(site, a) {
|
|
|
1124
1198
|
if (d) {
|
|
1125
1199
|
m.context = d.maxInputTokens
|
|
1126
1200
|
m.output = d.maxOutputTokens
|
|
1127
|
-
m.images = d.supportsImages === true
|
|
1201
|
+
m.images = d.supportsImages === true && !textOnly(site, id)
|
|
1128
1202
|
const r = creditsOf(d.credits)
|
|
1129
1203
|
if (r !== undefined) {
|
|
1130
1204
|
m.free = r === 0
|
|
@@ -1220,7 +1294,7 @@ function makePlugin(site) {
|
|
|
1220
1294
|
if (body === undefined && req) body = await req.clone().text()
|
|
1221
1295
|
body = withSystem(body)
|
|
1222
1296
|
const fit = fitted(body)
|
|
1223
|
-
body = fit.body
|
|
1297
|
+
body = normalizeToolChoice(fit.body)
|
|
1224
1298
|
attend(headers, body, session)
|
|
1225
1299
|
const res = await fetch(req ? req.url : input, { ...init, method: init?.method ?? req?.method, headers, body })
|
|
1226
1300
|
return res.status >= 400 ? explained(res) : kept(await renamed(res, fit.names))
|
|
@@ -1338,4 +1412,4 @@ export const WorkBuddyAuthPlugin = makePlugin(SITES.workbuddy)
|
|
|
1338
1412
|
export const WorkBuddyAIAuthPlugin = makePlugin(SITES["workbuddy-ai"])
|
|
1339
1413
|
|
|
1340
1414
|
// for tests
|
|
1341
|
-
export const _internal = { runsOutOf, packagesOf, endOf, enterpriseHeaders, enterpriseOf, liveModels, browserSignIn, withSystem, fitTools, fitted, renamed, unflagged, usageOf, desktopHeld, explained, REFUSED_HINT, EXHAUSTED_HINT, freeCredits, creditsOf, fresh, SITES, renewing, renewed, SESSION, attend, turnKey, conversationKey, signatureOf }
|
|
1415
|
+
export const _internal = { textOnly, runsOutOf, packagesOf, endOf, enterpriseHeaders, enterpriseOf, liveModels, browserSignIn, withSystem, normalizeToolChoice, fitTools, fitted, renamed, unflagged, usageOf, desktopHeld, explained, REFUSED_HINT, EXHAUSTED_HINT, freeCredits, creditsOf, 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.16",
|
|
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",
|