@magpie-community/opencode-qoder-auth 0.2.13 → 0.2.14

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 +4 -2
  2. package/index.mjs +47 -6
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -83,8 +83,10 @@ itself.
83
83
 
84
84
  | What Qoder says | What the request returns |
85
85
  |---|---|
86
- | A refused sign-in | 401 |
87
- | A quota | 429 |
86
+ | A refused sign-in (no code, or code 105) | 401 |
87
+ | A quota (codes 110, 112–119, 122) | 429 |
88
+ | Busy or queued for the model (code 10605) | 503, with `Retry-After` from Qoder's `retryAfterSeconds` (30s when it gives none); the sign-in is untouched |
89
+ | A refusal of the request itself (codes 406, 416, 430, 80411, 80412, 100400–100403) | 400, with Qoder's words |
88
90
  | A failure before the answer starts | Qoder's status |
89
91
  | A failure after the answer has started | The stream ends with an error |
90
92
 
package/index.mjs CHANGED
@@ -720,19 +720,55 @@ function qoderBody(chat, m) {
720
720
  return body
721
721
  }
722
722
 
723
- // failure is the status and message for a Qoder failure: a refused sign-in
724
- // asks for another, a quota is a 429.
723
+ // failure is the status and message for a Qoder failure. Qoder's own code
724
+ // says what it is, read as Qoder's client reads it (QoderErrorCode in the
725
+ // app's qoder-agent-sdk worker): only 105 (loginExpired) is a lapsed
726
+ // sign-in there, so a 401/403 that carries another code isn't one here
727
+ // either (#98: a queued 10605 came as a 403 and read as "sign in again").
728
+ // A refused chat with no code keeps the built-in's 401, and a quota is a
729
+ // 429.
730
+ const QUEUED = "10605"
731
+ // the codes Qoder's client counts as a plan's usage or credits used up
732
+ const QUOTA_CODES = new Set(["110", "112", "113", "114", "115", "116", "117", "118", "119", "122"])
733
+ // refusals of this request, not of the account: sensitive content, not
734
+ // supported, input too long, too many media, a custom model's service
735
+ const REQUEST_CODES = new Set(["406", "416", "430", "80411", "80412", "100400", "100401", "100403"])
736
+ const BUSY_WAIT = 30
737
+
725
738
  function failure(status, text) {
726
739
  if (status < 400 || status > 599) status = 502
727
740
  let msg = String(text ?? "").trim()
741
+ let code = ""
742
+ let queue
728
743
  try {
729
744
  const j = JSON.parse(text)
730
- if (j?.message) msg = j.message
745
+ code = j?.code == null ? "" : String(j.code)
746
+ if (j?.message) msg = String(j.message)
731
747
  // details is a JSON string, or the object itself (gjson reads either)
732
748
  const d = typeof j?.details === "string" ? JSON.parse(j.details) : j?.details
733
749
  if (d?.error?.message) msg += ": " + d.error.message
750
+ // the queue's state: the message itself, as a JSON string, or a field
751
+ queue = j?.queue && typeof j.queue === "object" ? j.queue : undefined
752
+ if (!queue && typeof j?.message === "string" && j.message.trim().startsWith("{")) {
753
+ const q = JSON.parse(j.message)
754
+ if (q && typeof q === "object" && ("isQueued" in q || "serviceAvailable" in q || "retryAfterSeconds" in q)) queue = q
755
+ }
734
756
  } catch {}
735
757
  msg ||= STATUS_CODES[status] ?? `HTTP ${status}`
758
+ // Qoder's queue for the model: busy for now, with when to ask again. The
759
+ // sign-in is fine (the same token reads the models and the usage), so it
760
+ // is a 503 with Retry-After, which magpie asks again after, and never
761
+ // touches the account's sign-in.
762
+ if (code === QUEUED || queue?.isQueued === true || queue?.serviceAvailable === false) {
763
+ const n = Number(queue?.retryAfterSeconds)
764
+ const wait = Number.isFinite(n) && n > 0 ? Math.ceil(n) : BUSY_WAIT
765
+ const model = queue?.modelKey ? ` for ${queue.modelKey}` : ""
766
+ const tier = queue?.queueType ? ` (this plan's queue: ${queue.queueType})` : ""
767
+ const what = queue?.isQueued === true ? "queued the request" : "is busy"
768
+ return { status: 503, message: `Qoder ${what}${model}${tier}; try again in ${wait}s`, retryAfter: wait, signIn: "" }
769
+ }
770
+ if (QUOTA_CODES.has(code)) return { status: 429, message: "usage limit reached: " + msg, signIn: "" }
771
+ if (REQUEST_CODES.has(code)) return { status: 400, message: `Qoder refused the request (${code}): ${msg}`, signIn: "" }
736
772
  // a refused chat is the built-in's 401, which never marked the account
737
773
  // lapsed: only a refused refresh did (errorResponse says kept). Qoder's
738
774
  // own words go with it: a 401 right after signing in isn't a lapsed
@@ -747,11 +783,16 @@ function failure(status, text) {
747
783
 
748
784
  // errorResponse is a failure as OpenAI's API gives it. signIn is what it
749
785
  // means for the account (magpie's X-Magpie-Sign-In): a 401 keeps it unless
750
- // told expired, since the built-in marked only a refused refresh.
751
- const errorResponse = ({ status, message, signIn = status === 401 ? "kept" : undefined }) =>
786
+ // told expired, since the built-in marked only a refused refresh. retryAfter
787
+ // (seconds) is sent as Retry-After.
788
+ const errorResponse = ({ status, message, signIn = status === 401 ? "kept" : undefined, retryAfter }) =>
752
789
  new Response(JSON.stringify({ error: { message, type: "qoder_error", code: status } }), {
753
790
  status,
754
- headers: { "Content-Type": "application/json", ...(signIn ? { "X-Magpie-Sign-In": signIn } : {}) },
791
+ headers: {
792
+ "Content-Type": "application/json",
793
+ ...(signIn ? { "X-Magpie-Sign-In": signIn } : {}),
794
+ ...(retryAfter > 0 ? { "Retry-After": String(retryAfter) } : {}),
795
+ },
755
796
  })
756
797
 
757
798
  // ---- the reply ----------------------------------------------------------------
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@magpie-community/opencode-qoder-auth",
3
- "version": "0.2.13",
3
+ "version": "0.2.14",
4
4
  "description": "Qoder subscriptions in OpenCode and magpie: Qoder (qoder.com) and Qoder CN (qoder.cn)",
5
5
  "type": "module",
6
6
  "main": "./index.mjs",