@magpie-community/opencode-qoder-auth 0.2.4 → 0.2.6
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 +9 -1
- package/index.mjs +60 -4
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -62,6 +62,10 @@ itself.
|
|
|
62
62
|
- The nearest level to the one asked, the higher one on a tie.
|
|
63
63
|
- The model's default when none is asked.
|
|
64
64
|
- The lowest level for "none", when the model can't turn thinking off.
|
|
65
|
+
- It picks the context window (`context_length`) from the model's own
|
|
66
|
+
windows (Qoder's Context setting: 200K, 400K, 1M): the default while the
|
|
67
|
+
request fits it, else the smallest that holds it, else the largest. A
|
|
68
|
+
larger window may cost more, so it is asked for only when needed.
|
|
65
69
|
- It encodes the body with the client's codec and signs the call with the
|
|
66
70
|
client's COSY envelope. The envelope carries:
|
|
67
71
|
- the account, AES-encrypted, with the key wrapped by Qoder's RSA key;
|
|
@@ -89,7 +93,11 @@ itself.
|
|
|
89
93
|
The `provider.models` hook reads the account's own list, as Qoder's client
|
|
90
94
|
asks for it (`/algo/api/v2/model/list`). It keeps the enabled chat models
|
|
91
95
|
and leaves out "auto" and "default", which route inside Qoder. It takes
|
|
92
|
-
each model's reasoning levels from its `thinking_config
|
|
96
|
+
each model's reasoning levels from its `thinking_config`, and its context
|
|
97
|
+
from its `context_config`: the largest window it offers (Qoder's docs
|
|
98
|
+
give Qwen3.8-Flash 200K, 400K and 1M), not `max_input_tokens` (180K
|
|
99
|
+
there), which is only what a request naming no window gets. A model listed
|
|
100
|
+
with no windows keeps `max_input_tokens`.
|
|
93
101
|
|
|
94
102
|
The `config` hook declares the list as it was on 2026-09-30:
|
|
95
103
|
|
package/index.mjs
CHANGED
|
@@ -340,15 +340,51 @@ function rateOf(raw) {
|
|
|
340
340
|
return { rate, rateWas }
|
|
341
341
|
}
|
|
342
342
|
|
|
343
|
+
// windowsOf reads the context windows a listing entry offers, smallest
|
|
344
|
+
// first, and the one Qoder's client picks unless asked for another:
|
|
345
|
+
// context_config's entries ({"200k": {token_count: 200000, is_default:
|
|
346
|
+
// true}, "1m": {token_count: 1000000}, …}), else available_context_windows,
|
|
347
|
+
// as Qoder's client reads them (qodercli's catalog). Qoder's Context
|
|
348
|
+
// setting (200K / 400K / 1M) offers them on Qwen3.8-Flash and the rest;
|
|
349
|
+
// max_input_tokens (180K there) is only what a request that names none
|
|
350
|
+
// gets.
|
|
351
|
+
function windowsOf(raw) {
|
|
352
|
+
const n = (v) => (Number.isInteger(v) && v > 0 ? v : 0)
|
|
353
|
+
const windows = []
|
|
354
|
+
let def = 0
|
|
355
|
+
const cc = raw?.context_config ?? raw?.contextConfig
|
|
356
|
+
if (cc && typeof cc === "object" && !Array.isArray(cc)) {
|
|
357
|
+
for (const e of Object.values(cc)) {
|
|
358
|
+
const t = n(e?.token_count)
|
|
359
|
+
if (!t) continue
|
|
360
|
+
if (!windows.includes(t)) windows.push(t)
|
|
361
|
+
if (e.is_default === true || e.is_default === 1 || e.is_default === "1") def = t
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
if (!windows.length) {
|
|
365
|
+
const list = raw?.available_context_windows ?? raw?.availableContextWindows
|
|
366
|
+
for (const v of Array.isArray(list) ? list : []) if (n(v) && !windows.includes(v)) windows.push(v)
|
|
367
|
+
def = n(raw?.default_context_window ?? raw?.defaultContextWindow)
|
|
368
|
+
if (!windows.includes(def)) def = 0
|
|
369
|
+
}
|
|
370
|
+
windows.sort((a, b) => a - b)
|
|
371
|
+
return { windows, defaultWindow: def }
|
|
372
|
+
}
|
|
373
|
+
|
|
343
374
|
// modelInfo reads one entry of the listing's "chat" array: its efforts and
|
|
344
|
-
// default from thinking_config (is_reasoning alone when it has none)
|
|
375
|
+
// default from thinking_config (is_reasoning alone when it has none), and
|
|
376
|
+
// its context: the largest window it offers (windowsOf), since a request
|
|
377
|
+
// that needs more than the default is sent a larger one (windowFor).
|
|
345
378
|
function modelInfo(raw) {
|
|
379
|
+
const { windows, defaultWindow } = windowsOf(raw)
|
|
346
380
|
const m = {
|
|
347
381
|
key: raw.key,
|
|
348
382
|
source: raw.source ?? "",
|
|
349
383
|
name: raw.display_name || raw.key,
|
|
350
384
|
images: !!raw.is_vl,
|
|
351
|
-
context: raw.max_input_tokens || 0,
|
|
385
|
+
context: windows.length ? windows[windows.length - 1] : raw.max_input_tokens || 0,
|
|
386
|
+
windows,
|
|
387
|
+
defaultWindow,
|
|
352
388
|
thinks: !!raw.is_reasoning,
|
|
353
389
|
alwaysThinks: false,
|
|
354
390
|
efforts: Array.isArray(raw.reasoning_efforts) ? [...raw.reasoning_efforts] : [],
|
|
@@ -533,6 +569,25 @@ function qoderMessages(msgs) {
|
|
|
533
569
|
return out
|
|
534
570
|
}
|
|
535
571
|
|
|
572
|
+
// windowFor is the context window a request is sent (context_length) on a
|
|
573
|
+
// model that offers several: the one Qoder's client picks by default
|
|
574
|
+
// (max_input_tokens without one) while the request fits it, else the
|
|
575
|
+
// smallest that holds it, else the largest. A larger window may cost more
|
|
576
|
+
// (Qoder: changing the Context "may change the rate"), so it is asked for
|
|
577
|
+
// only when the conversation needs it. The request's size is counted
|
|
578
|
+
// generously, a token for every 3 bytes of its UTF-8 JSON, so a window is
|
|
579
|
+
// raised a little early rather than late, and the reply's max_tokens is
|
|
580
|
+
// counted in it: a window holds the prompt and the reply, so a
|
|
581
|
+
// conversation of 180K asking 32K for the reply is sent 400K, not the 200K
|
|
582
|
+
// it would overflow while magpie says the model takes 1M (yetone/magpie#700).
|
|
583
|
+
function windowFor(chat, m, reply = 0) {
|
|
584
|
+
const base = m.defaultWindow || (m.config?.max_input_tokens > 0 ? m.config.max_input_tokens : 0) || m.context
|
|
585
|
+
if (!m.windows?.length) return m.context
|
|
586
|
+
const need = Math.ceil(Buffer.byteLength(JSON.stringify({ m: chat.messages ?? [], t: chat.tools ?? [] })) / 3) + Math.max(0, reply)
|
|
587
|
+
if (base > 0 && need <= base) return base
|
|
588
|
+
return m.windows.find((w) => w >= need) ?? m.windows[m.windows.length - 1]
|
|
589
|
+
}
|
|
590
|
+
|
|
536
591
|
// qoderBody is the plaintext agent_chat_generation takes for a chat
|
|
537
592
|
// completion request, for model m.
|
|
538
593
|
function qoderBody(chat, m) {
|
|
@@ -544,7 +599,8 @@ function qoderBody(chat, m) {
|
|
|
544
599
|
const [thinking, effort] = effortFor(chat.reasoning_effort, m)
|
|
545
600
|
const params = { enable_thinking: thinking, max_tokens: chat.max_completion_tokens || chat.max_tokens || 32000 }
|
|
546
601
|
if (effort) params.reasoning_effort = effort
|
|
547
|
-
|
|
602
|
+
const window = windowFor(chat, m, params.max_tokens)
|
|
603
|
+
if (window > 0) params.context_length = window
|
|
548
604
|
const body = {
|
|
549
605
|
parameters: params,
|
|
550
606
|
business: { product: "app", version: COSY_VERSION, type: "agent", id: hexID(), name: "magpie session", begin_at: Date.now(), stage: "start" },
|
|
@@ -1253,4 +1309,4 @@ export const QoderAuthPlugin = makePlugin(SITES.qoder)
|
|
|
1253
1309
|
export const QoderCNAuthPlugin = makePlugin(SITES["qoder-cn"])
|
|
1254
1310
|
|
|
1255
1311
|
// for tests
|
|
1256
|
-
export const _internal = { parseUsage, gfmt, when, failure, modelInfo, SITES, decodeBody, events }
|
|
1312
|
+
export const _internal = { parseUsage, gfmt, when, failure, modelInfo, qoderBody, SITES, decodeBody, events }
|
package/package.json
CHANGED