@magpie-community/opencode-qoder-auth 0.1.1 → 0.1.2

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 +23 -9
  2. package/package.json +1 -1
package/index.mjs CHANGED
@@ -252,6 +252,15 @@ const MODELS = [
252
252
  { id: "mmodel", name: "MiniMax-M3", context: 180_000 },
253
253
  ].map((m) => ({ images: true, ...m }))
254
254
 
255
+ // freeOf reads whether the listing marks a model free, as Qoder's client
256
+ // reads it (internal/qoder/models.go): is_free true, or a price_factor (the
257
+ // credits a request costs, as a multiple) of 0, either in snake or camel case.
258
+ function freeOf(raw) {
259
+ const is = raw.is_free ?? raw.isFree
260
+ const price = raw.price_factor ?? raw.priceFactor
261
+ return is === true || (typeof price === "number" && price === 0)
262
+ }
263
+
255
264
  // modelInfo reads one entry of the listing's "chat" array: its efforts and
256
265
  // default from thinking_config (is_reasoning alone when it has none).
257
266
  function modelInfo(raw) {
@@ -265,6 +274,7 @@ function modelInfo(raw) {
265
274
  alwaysThinks: false,
266
275
  efforts: Array.isArray(raw.reasoning_efforts) ? [...raw.reasoning_efforts] : [],
267
276
  defaultEffort: "",
277
+ free: freeOf(raw),
268
278
  config: raw,
269
279
  }
270
280
  const tc = raw.thinking_config
@@ -331,6 +341,8 @@ function runtimeModel(m) {
331
341
  },
332
342
  release_date: "",
333
343
  variants: Object.fromEntries((m.efforts ?? []).map((e) => [e, { reasoningEffort: e }])),
344
+ // magpie's own: served at no cost to the plan's credits
345
+ free: !!m.free,
334
346
  }
335
347
  }
336
348
 
@@ -478,9 +490,11 @@ function failure(status, text) {
478
490
  if (d?.error?.message) msg += ": " + d.error.message
479
491
  } catch {}
480
492
  msg ||= `HTTP ${status}`
481
- if (status === 401 || status === 403) return { status: 401, message: "Qoder: the sign-in lapsed — sign in again" }
493
+ // only Qoder's 401 says the job token itself is refused; a 403 is kept as
494
+ // what it is, as magpie marks the account lapsed on a 401
495
+ if (status === 401 || status === 403) return { status, message: "the sign-in lapsed — sign in again" }
482
496
  if (status === 429 || msg.toLowerCase().includes("quota")) return { status: 429, message: "usage limit reached: " + msg }
483
- return { status, message: "Qoder: " + msg }
497
+ return { status, message: msg }
484
498
  }
485
499
 
486
500
  const errorResponse = ({ status, message }) =>
@@ -900,7 +914,7 @@ export async function QoderAuthPlugin({ client }) {
900
914
  }
901
915
 
902
916
  const signedInError = (e) =>
903
- errorResponse(e instanceof SignInGone ? { status: 401, message: e.message } : { status: 502, message: "Qoder: " + (e?.message ?? e) })
917
+ errorResponse({ status: e instanceof SignInGone ? 401 : 502, message: String(e?.message ?? e).replace(/^Qoder: /, "") })
904
918
 
905
919
  return {
906
920
  auth: {
@@ -915,12 +929,12 @@ export async function QoderAuthPlugin({ client }) {
915
929
  async fetch(input, init = {}) {
916
930
  const url = typeof input === "string" ? input : input instanceof URL ? input.href : input.url
917
931
  if (!/\/chat\/completions$/.test(new URL(url).pathname))
918
- return errorResponse({ status: 404, message: "Qoder: only chat completions are served" })
932
+ return errorResponse({ status: 404, message: "only chat completions are served" })
919
933
  let chat
920
934
  try {
921
935
  chat = JSON.parse(await bodyText(input, init))
922
936
  } catch {
923
- return errorResponse({ status: 400, message: "Qoder: a request that isn't JSON" })
937
+ return errorResponse({ status: 400, message: "a request that isn't JSON" })
924
938
  }
925
939
  let cred
926
940
  try {
@@ -934,7 +948,7 @@ export async function QoderAuthPlugin({ client }) {
934
948
  } catch (e) {
935
949
  return errorResponse({ status: 502, message: e.message })
936
950
  }
937
- if (!m) return errorResponse({ status: 400, message: `Qoder: unknown or disabled model "${chat.model}"` })
951
+ if (!m) return errorResponse({ status: 400, message: `unknown or disabled model "${chat.model}"` })
938
952
  const wire = encodeBody(JSON.stringify(qoderBody(chat, m)))
939
953
  const headers = {
940
954
  ...cosyHeaders(CHAT_URL, cred, wire),
@@ -947,7 +961,7 @@ export async function QoderAuthPlugin({ client }) {
947
961
  try {
948
962
  res = await fetch(CHAT_URL, { method: "POST", headers, body: wire, signal: init.signal })
949
963
  } catch (e) {
950
- return errorResponse({ status: 502, message: "Qoder: " + e.message })
964
+ return errorResponse({ status: 502, message: e.message })
951
965
  }
952
966
  if (!res.ok) return errorResponse(failure(res.status, (await res.text()).slice(0, 1 << 20)))
953
967
  return answer(res, chat)
@@ -978,7 +992,7 @@ export async function QoderAuthPlugin({ client }) {
978
992
  const ms = await models(cred, true)
979
993
  if (!ms.length) return provider.models
980
994
  return Object.fromEntries(
981
- ms.map((m) => [m.key, runtimeModel({ id: m.key, name: m.name, context: m.context, images: m.images, efforts: m.thinks ? m.efforts : [] })]),
995
+ ms.map((m) => [m.key, runtimeModel({ id: m.key, name: m.name, context: m.context, images: m.images, efforts: m.thinks ? m.efforts : [], free: m.free })]),
982
996
  )
983
997
  } catch {
984
998
  return provider.models
@@ -989,4 +1003,4 @@ export async function QoderAuthPlugin({ client }) {
989
1003
  }
990
1004
 
991
1005
  // for tests
992
- export const _internal = { parseUsage, gfmt, when }
1006
+ export const _internal = { parseUsage, gfmt, when, failure, modelInfo }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@magpie-community/opencode-qoder-auth",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "description": "Qoder subscription in OpenCode and magpie",
5
5
  "type": "module",
6
6
  "main": "./index.mjs",