@magpie-community/opencode-workbuddy-auth 0.1.19 → 0.1.20

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 +9 -0
  2. package/index.mjs +47 -2
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -99,6 +99,15 @@ models to each shape below and Kimi's, GPT's, Gemini's and GLM's to some:
99
99
  (`.`, `:`, `/`, a space, CJK) is sent with `_` there, and the model's
100
100
  calls come back under the tool's own name.
101
101
 
102
+ WorkBuddy AI's `gemini-3.8-flash` answers 400 "the conversation must end
103
+ with a user message" (11153) to a chat whose tool results are followed by
104
+ the user's message, as magpie sends Claude Code's reminders, words typed
105
+ mid-task and a tool's images. Such a chat goes again with an empty
106
+ assistant message between the results and the user's message, which it
107
+ serves; a model that refused it once gets it that way from then on, until
108
+ the plugin restarts. A chat ending with the assistant's message (a prefill)
109
+ is refused as before.
110
+
102
111
  ## Models
103
112
 
104
113
  When you are signed in, the models are the ones WorkBuddy's product config
package/index.mjs CHANGED
@@ -1262,7 +1262,37 @@ async function liveModels(site, a) {
1262
1262
 
1263
1263
  // ---- the plugins --------------------------------------------------------------
1264
1264
 
1265
+ // AFTER_TOOLS_REFUSED is WorkBuddy's 400 code 11153, "the conversation must
1266
+ // end with a user message", which WorkBuddy AI's gemini-3.8-flash answers to a
1267
+ // chat whose tool results are followed by the user's message (#113): Claude
1268
+ // Code's reminders, a user's words sent mid-task, a tool's images, which
1269
+ // magpie hands over in a user message after the results. It does end with a
1270
+ // user message; the same chat with any other message between them is served.
1271
+ // The code is matched whole, as with 14018.
1272
+ const AFTER_TOOLS_REFUSED = /must end with a user message|["']code["']\s*:\s*["']?11153(?![\d.])/i
1273
+
1274
+ // replyAfterTools is a chat ending with tool results then the user's
1275
+ // message(s), with an empty assistant message between them, as if the model
1276
+ // had said nothing after the results. Tried with a real WorkBuddy AI
1277
+ // account on 2026-10-14: gemini-3.8-flash serves it and follows the user's
1278
+ // message as a user's, where folded into the tool's result it was read as
1279
+ // the tool's output and its ask left undone. null for any other chat — one
1280
+ // ending with the assistant's message is a prefill, refused as it is.
1281
+ function replyAfterTools(body) {
1282
+ const b = chatOf(body)
1283
+ const msgs = b?.messages
1284
+ if (!Array.isArray(msgs)) return null
1285
+ let i = msgs.length - 1
1286
+ while (i >= 0 && msgs[i]?.role === "user") i--
1287
+ if (i === msgs.length - 1 || i < 0 || msgs[i]?.role !== "tool") return null
1288
+ msgs.splice(i + 1, 0, { role: "assistant", content: "" })
1289
+ return JSON.stringify(b)
1290
+ }
1291
+
1265
1292
  function makePlugin(site) {
1293
+ // the models of this site that refused tool results then the user's
1294
+ // message, for this run: their next chats go with replyAfterTools at once
1295
+ const endsAfterTools = new Set()
1266
1296
  return async ({ client }) => ({
1267
1297
  config: async (config) => {
1268
1298
  config.provider ??= {}
@@ -1328,7 +1358,22 @@ function makePlugin(site) {
1328
1358
  const fit = fitted(body)
1329
1359
  body = normalizeToolChoice(fit.body)
1330
1360
  attend(headers, body, session)
1331
- const res = await fetch(req ? req.url : input, { ...init, method: init?.method ?? req?.method, headers, body })
1361
+ const send = (b) => fetch(req ? req.url : input, { ...init, method: init?.method ?? req?.method, headers, body: b })
1362
+ // a model known to turn tool results then the user's message away
1363
+ // gets the empty reply between them up front (see endsAfterTools)
1364
+ const model = chatOf(body)?.model
1365
+ const quiet = endsAfterTools.has(model) ? replyAfterTools(body) : null
1366
+ let res = await send(quiet ?? body)
1367
+ if (res.status === 400 && !quiet) {
1368
+ const text = await res.text()
1369
+ const again = AFTER_TOOLS_REFUSED.test(text) ? replyAfterTools(body) : null
1370
+ if (again) {
1371
+ endsAfterTools.add(model)
1372
+ res = await send(again)
1373
+ } else {
1374
+ res = new Response(text, { status: res.status, statusText: res.statusText, headers: res.headers })
1375
+ }
1376
+ }
1332
1377
  return res.status >= 400 ? explained(res) : kept(await renamed(res, fit.names))
1333
1378
  },
1334
1379
  }
@@ -1468,4 +1513,4 @@ export const WorkBuddyAuthPlugin = makePlugin(SITES.workbuddy)
1468
1513
  export const WorkBuddyAIAuthPlugin = makePlugin(SITES["workbuddy-ai"])
1469
1514
 
1470
1515
  // for tests
1471
- export const _internal = { 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, fresh, SITES, renewing, renewed, SESSION, attend, turnKey, conversationKey, signatureOf }
1516
+ export const _internal = { 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, 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.19",
3
+ "version": "0.1.20",
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",