@magpie-community/opencode-qoder-auth 0.2.3 → 0.2.5
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 +71 -10
- 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,22 @@ 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.
|
|
580
|
+
function windowFor(chat, m) {
|
|
581
|
+
const base = m.defaultWindow || (m.config?.max_input_tokens > 0 ? m.config.max_input_tokens : 0) || m.context
|
|
582
|
+
if (!m.windows?.length) return m.context
|
|
583
|
+
const need = Math.ceil(Buffer.byteLength(JSON.stringify({ m: chat.messages ?? [], t: chat.tools ?? [] })) / 3)
|
|
584
|
+
if (base > 0 && need <= base) return base
|
|
585
|
+
return m.windows.find((w) => w >= need) ?? m.windows[m.windows.length - 1]
|
|
586
|
+
}
|
|
587
|
+
|
|
536
588
|
// qoderBody is the plaintext agent_chat_generation takes for a chat
|
|
537
589
|
// completion request, for model m.
|
|
538
590
|
function qoderBody(chat, m) {
|
|
@@ -544,7 +596,8 @@ function qoderBody(chat, m) {
|
|
|
544
596
|
const [thinking, effort] = effortFor(chat.reasoning_effort, m)
|
|
545
597
|
const params = { enable_thinking: thinking, max_tokens: chat.max_completion_tokens || chat.max_tokens || 32000 }
|
|
546
598
|
if (effort) params.reasoning_effort = effort
|
|
547
|
-
|
|
599
|
+
const window = windowFor(chat, m)
|
|
600
|
+
if (window > 0) params.context_length = window
|
|
548
601
|
const body = {
|
|
549
602
|
parameters: params,
|
|
550
603
|
business: { product: "app", version: COSY_VERSION, type: "agent", id: hexID(), name: "magpie session", begin_at: Date.now(), stage: "start" },
|
|
@@ -726,6 +779,7 @@ async function* events(body) {
|
|
|
726
779
|
yield { error: failure(Number(env.statusCodeValue), inner || payload) }
|
|
727
780
|
return
|
|
728
781
|
}
|
|
782
|
+
if (inner.trim() === "[DONE]") break
|
|
729
783
|
let chunk
|
|
730
784
|
try {
|
|
731
785
|
chunk = JSON.parse(inner)
|
|
@@ -749,12 +803,19 @@ async function* events(body) {
|
|
|
749
803
|
yield { tool: { index: ++index, id: tc.id || "call_" + hexID(), name: tc.function?.name ?? "", args: tc.function?.arguments ?? "" } }
|
|
750
804
|
} else if (tc.function?.arguments) yield { args: { index, text: tc.function.arguments } }
|
|
751
805
|
}
|
|
752
|
-
fr = chunk?.choices?.[0]?.finish_reason
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
806
|
+
fr = chunk?.choices?.[0]?.finish_reason || fr
|
|
807
|
+
// the usage comes in a chunk of its own after the finish, with no
|
|
808
|
+
// choices, as the built-in read it (qoder.go): every request was
|
|
809
|
+
// counted as 0 tokens when the finish ended the reading
|
|
810
|
+
const u = chunk?.usage
|
|
811
|
+
if (u)
|
|
812
|
+
usage = {
|
|
813
|
+
prompt_tokens: u.prompt_tokens ?? 0,
|
|
814
|
+
completion_tokens: u.completion_tokens ?? 0,
|
|
815
|
+
total_tokens: (u.prompt_tokens ?? 0) + (u.completion_tokens ?? 0),
|
|
816
|
+
...(u.prompt_tokens_details?.cached_tokens ? { prompt_tokens_details: { cached_tokens: u.prompt_tokens_details.cached_tokens } } : {}),
|
|
817
|
+
...(u.completion_tokens_details?.reasoning_tokens ? { completion_tokens_details: { reasoning_tokens: u.completion_tokens_details.reasoning_tokens } } : {}),
|
|
818
|
+
}
|
|
758
819
|
}
|
|
759
820
|
yield* flushed()
|
|
760
821
|
yield { stop: a.sawTool ? "tool_calls" : fr === "length" ? "length" : "stop", usage }
|
|
@@ -1245,4 +1306,4 @@ export const QoderAuthPlugin = makePlugin(SITES.qoder)
|
|
|
1245
1306
|
export const QoderCNAuthPlugin = makePlugin(SITES["qoder-cn"])
|
|
1246
1307
|
|
|
1247
1308
|
// for tests
|
|
1248
|
-
export const _internal = { parseUsage, gfmt, when, failure, modelInfo, SITES, decodeBody }
|
|
1309
|
+
export const _internal = { parseUsage, gfmt, when, failure, modelInfo, qoderBody, SITES, decodeBody, events }
|
package/package.json
CHANGED