@magpie-community/opencode-workbuddy-auth 0.1.8 → 0.1.10

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 (2) hide show
  1. package/index.mjs +89 -12
  2. package/package.json +1 -1
package/index.mjs CHANGED
@@ -257,17 +257,75 @@ function sign(site, a, headers) {
257
257
  headers.set("X-Request-ID", msg)
258
258
  }
259
259
 
260
+ // FLAGGED are the words WorkBuddy refuses a chat for, "Illegal API
261
+ // invocation from an unapproved channel" (magpie #182), each with words
262
+ // that say the same and pass: tried one by one against both builds with
263
+ // the requests Claude Code 2.1.280 and Codex 0.159.2 send. WorkBuddy reads
264
+ // every message, not only the system prompt (Claude Code puts its main
265
+ // branch in the first user message), and pays no mind to case. Claude
266
+ // Code's billing header means nothing to WorkBuddy's models and goes.
267
+ const FLAGGED = [
268
+ [/x-anthropic-billing-header:[^\n]*\n?/gi, ""],
269
+ [/You are Claude Code, Anthropic['’]s official CLI for Claude/gi, "You are Claude Code, Anthropic's CLI for Claude"],
270
+ [/Main branch \(you will usually use this for PRs\)/gi, "Main branch (usually the base for PRs)"],
271
+ [/(?<!the )Codex CLI is an open source project led by OpenAI\./gi, "The Codex CLI is an open source project led by OpenAI."],
272
+ ]
273
+
274
+ function unflagged(text) {
275
+ for (const [re, to] of FLAGGED) text = text.replace(re, to)
276
+ return text
277
+ }
278
+
279
+ // unflag puts FLAGGED's words in place in every message of a chat, and
280
+ // leaves out a text that had nothing else (the billing header's system
281
+ // block). It says whether it changed anything.
282
+ function unflag(messages) {
283
+ let changed = false
284
+ for (let i = messages.length - 1; i >= 0; i--) {
285
+ const m = messages[i]
286
+ if (typeof m?.content === "string") {
287
+ const t = unflagged(m.content)
288
+ if (t === m.content) continue
289
+ changed = true
290
+ if (t.trim() || m.role !== "system") m.content = t
291
+ else messages.splice(i, 1)
292
+ } else if (Array.isArray(m?.content)) {
293
+ const parts = []
294
+ let here = false
295
+ for (const p of m.content) {
296
+ const t = typeof p?.text === "string" ? unflagged(p.text) : null
297
+ if (t === null || t === p.text) {
298
+ parts.push(p)
299
+ continue
300
+ }
301
+ here = true
302
+ if (t.trim()) parts.push({ ...p, text: t })
303
+ }
304
+ if (!here) continue
305
+ changed = true
306
+ if (parts.length || m.role !== "system") m.content = parts
307
+ else messages.splice(i, 1)
308
+ }
309
+ }
310
+ return changed
311
+ }
312
+
260
313
  // withSystem gives a chat that doesn't open with a system message
261
- // WorkBuddy's default one, as its app does.
314
+ // WorkBuddy's default one, as its app does, and takes FLAGGED's words out
315
+ // of it.
262
316
  function withSystem(body) {
263
317
  // a body magpie or OpenCode hands over as bytes is read as the text it is
264
318
  if (body instanceof ArrayBuffer || ArrayBuffer.isView(body)) body = new TextDecoder().decode(body)
265
319
  if (typeof body !== "string") return body
266
320
  try {
267
321
  const b = JSON.parse(body)
268
- if (!Array.isArray(b?.messages) || !b.messages.length || b.messages[0]?.role === "system") return body
269
- b.messages.unshift({ role: "system", content: "You are a helpful assistant." })
270
- return JSON.stringify(b)
322
+ if (!Array.isArray(b?.messages) || !b.messages.length) return body
323
+ let changed = unflag(b.messages)
324
+ if (b.messages[0]?.role !== "system") {
325
+ b.messages.unshift({ role: "system", content: "You are a helpful assistant." })
326
+ changed = true
327
+ }
328
+ return changed ? JSON.stringify(b) : body
271
329
  } catch {
272
330
  return body
273
331
  }
@@ -696,13 +754,22 @@ function makePlugin(site) {
696
754
 
697
755
  // REFUSED_HINT is what the user can do about WorkBuddy's "Illegal API
698
756
  // invocation from an unapproved channel": both builds answer it to a chat
699
- // whose system prompt is Codex's or Claude Code's own (magpie #182),
700
- // whatever the headers. It ends the error's message, as magpie's built-in
757
+ // holding words of Codex's or Claude Code's own (magpie #182), whatever the
758
+ // headers. FLAGGED rewrites the ones known; this is for one it doesn't know. It ends the error's message, as magpie's built-in
701
759
  // put it (provider.WBRefusedHint), so magpie's routing page says it apart.
702
760
  const REFUSED_HINT =
703
761
  "WorkBuddy refuses chats from Codex and Claude Code (their system prompt); use it from Hermes, OpenCode or Pi, or add another provider to this group"
704
762
  const REFUSED = /unapproved channel|illegal api invocation/i
705
763
 
764
+ // EXHAUSTED_HINT explains WorkBuddy's requirement of an active package
765
+ // even for 0-rate / free models. When an account has no packages or 0 credits,
766
+ // WorkBuddy returns code 14018 ("Credits exhausted...").
767
+ const EXHAUSTED_HINT =
768
+ "WorkBuddy requires an active credit package even for 0-rate free models; check your account package balance"
769
+ // The code is matched whole: a code that only begins with 14018 (140185) is
770
+ // another error, and insufficient_quota would rest the account as out of credit.
771
+ const EXHAUSTED_TEXT = /credits exhausted|额度已用尽|["']code["']\s*:\s*["']?14018(?![\d.])/i
772
+
706
773
  // kept is an answer that went through, saying the sign-in is kept: the
707
774
  // built-in never cleared a WorkBuddy account's mark, as it never set one.
708
775
  function kept(res) {
@@ -711,7 +778,7 @@ function kept(res) {
711
778
  return new Response(res.body, { status: res.status, statusText: res.statusText, headers })
712
779
  }
713
780
 
714
- // explained is res with REFUSED_HINT added to that refusal's message.
781
+ // explained is res with REFUSED_HINT or EXHAUSTED_HINT added to that refusal's message.
715
782
  // Each refusal says the sign-in is kept: the built-in passed WorkBuddy's
716
783
  // 401 on and never marked a WorkBuddy account lapsed.
717
784
  async function explained(res) {
@@ -723,27 +790,37 @@ async function explained(res) {
723
790
  headers.set("X-Magpie-Sign-In", "kept")
724
791
  return new Response(b, { status: res.status, statusText: res.statusText, headers })
725
792
  }
726
- if (!REFUSED.test(text)) return again(text)
727
- let msg = text.trim()
728
793
  let v
729
794
  try {
730
795
  v = JSON.parse(text)
731
796
  } catch {}
732
797
  const at = v?.error && typeof v.error === "object" ? v.error : v && typeof v === "object" ? v : null
798
+ const code = at?.data?.code ?? at?.code ?? v?.code ?? v?.data?.code
799
+ const isRefused = REFUSED.test(text)
800
+ const isExhausted = code === 14018 || Number(code) === 14018 || EXHAUSTED_TEXT.test(text)
801
+ if (!isRefused && !isExhausted) return again(text)
802
+
803
+ let msg = text.trim()
733
804
  if (at && typeof at.message === "string") msg = at.message
805
+ else if (typeof at?.data?.msg === "string") msg = at.data.msg
734
806
  else if (typeof v?.error === "string") msg = v.error
735
807
  else if (typeof v?.msg === "string" && v.msg) msg = v.msg // WorkBuddy's own {"code", "msg"}
736
- msg = `${msg} — ${REFUSED_HINT}`
808
+ else if (typeof v?.data?.msg === "string") msg = v.data.msg
809
+
810
+ const hint = isRefused ? REFUSED_HINT : EXHAUSTED_HINT
811
+ const type = isRefused ? "permission_error" : "insufficient_quota"
812
+ const errCode = isRefused ? null : 14018
813
+ msg = `${msg} — ${hint}`
737
814
  const headers = new Headers(res.headers)
738
815
  headers.set("content-type", "application/json")
739
816
  headers.delete("content-length")
740
817
  headers.delete("content-encoding")
741
818
  headers.set("X-Magpie-Sign-In", "kept")
742
- return new Response(JSON.stringify({ error: { message: msg, type: "permission_error", code: null } }), { status: res.status, statusText: res.statusText, headers })
819
+ return new Response(JSON.stringify({ error: { message: msg, type, code: errCode } }), { status: res.status, statusText: res.statusText, headers })
743
820
  }
744
821
 
745
822
  export const WorkBuddyAuthPlugin = makePlugin(SITES.workbuddy)
746
823
  export const WorkBuddyAIAuthPlugin = makePlugin(SITES["workbuddy-ai"])
747
824
 
748
825
  // for tests
749
- export const _internal = { withSystem, usageOf, desktopHeld, explained, REFUSED_HINT, freeCredits, creditsOf, fresh, SITES, renewing, renewed }
826
+ export const _internal = { withSystem, unflagged, usageOf, desktopHeld, explained, REFUSED_HINT, EXHAUSTED_HINT, freeCredits, creditsOf, fresh, SITES, renewing, renewed }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@magpie-community/opencode-workbuddy-auth",
3
- "version": "0.1.8",
3
+ "version": "0.1.10",
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",