@magpie-community/opencode-qoder-auth 0.2.21 → 0.2.23
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 +7 -0
- package/index.mjs +60 -7
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -81,6 +81,10 @@ itself.
|
|
|
81
81
|
- Messages become text and image blocks.
|
|
82
82
|
- Tool calls and results become OpenAI tool turns. Images a tool returned
|
|
83
83
|
follow in a user turn.
|
|
84
|
+
- A result whose id the client lost answers its turn's call in order
|
|
85
|
+
(since 0.2.22, #277): Qoder refuses a whole request with a result that
|
|
86
|
+
answers no call before it. A result with no call left to answer goes
|
|
87
|
+
as text in the user's turn.
|
|
84
88
|
- Your tools become Qoder's native function tools.
|
|
85
89
|
- The model's own configuration from Qoder's list goes with it.
|
|
86
90
|
- It picks the reasoning effort from the model's own levels:
|
|
@@ -96,6 +100,9 @@ itself.
|
|
|
96
100
|
- the account, AES-encrypted, with the key wrapped by Qoder's RSA key;
|
|
97
101
|
- an MD5 signature;
|
|
98
102
|
- the machine id and the client's headers.
|
|
103
|
+
- `Cosy-Version`: the worker runtime version that Qoder's desktop app
|
|
104
|
+
ships (1.1.69 in app 0.4.8, since 0.2.23, #281), because
|
|
105
|
+
`Cosy-ClientType` says the desktop app.
|
|
99
106
|
|
|
100
107
|
**Reading the reply**
|
|
101
108
|
|
package/index.mjs
CHANGED
|
@@ -43,7 +43,14 @@ import { join, resolve } from "node:path"
|
|
|
43
43
|
|
|
44
44
|
const CHAT_PATH = "/algo/api/v2/service/pro/sse/agent_chat_generation?FetchKeys=llm_model_result&AgentId=agent_common&Encode=1"
|
|
45
45
|
const MODELS_PATH = "/algo/api/v2/model/list?Encode=1"
|
|
46
|
-
|
|
46
|
+
// The build the envelope claims. It has to be the one Qoder's desktop app
|
|
47
|
+
// talks through — the worker runtime bundled in the app — because the
|
|
48
|
+
// envelope also says Cosy-ClientType: 10 (desktop) and carries that app's
|
|
49
|
+
// machine identity; the CLI's channel publishes a different, later number
|
|
50
|
+
// that no desktop build runs. Taken from app 0.4.8 (build 79743374,
|
|
51
|
+
// 2026-10-14), whose transport config names 1.1.69. Raise it when the app
|
|
52
|
+
// moves, not when the CLI does.
|
|
53
|
+
const COSY_VERSION = "1.1.69"
|
|
47
54
|
const SIGN_IN_TIMEOUT = 15 * 60 * 1000
|
|
48
55
|
const REFRESH_LEAD = 5 * 60 * 1000 // a chat token this close to its end is refreshed before a request
|
|
49
56
|
const RENEW_LEAD = 10 * 60 * 1000 // and this close, magpie renews it ahead of time (auth.refresh)
|
|
@@ -687,6 +694,17 @@ const textOf = (c) => (typeof c === "string" ? c : blocksOf(c).filter((b) => b.t
|
|
|
687
694
|
// blocks, an assistant's tool calls as tool_calls, results as tool turns by
|
|
688
695
|
// tool_call_id. A tool turn holds text only, so the images a tool returned
|
|
689
696
|
// follow in a user turn — the start of the user's own when one comes next.
|
|
697
|
+
//
|
|
698
|
+
// Qoder turns the whole request away when a tool turn answers no call of the
|
|
699
|
+
// turn before it ("Messages with role 'tool' must be a response to a
|
|
700
|
+
// preceding message with 'tool_calls'"), where another API takes the same
|
|
701
|
+
// conversation. A client's history reaches here with such a turn because it
|
|
702
|
+
// dropped the id: either the call lost it, so the result names one no call
|
|
703
|
+
// has, or the result kept none. Both are filled in rather than dropped — a
|
|
704
|
+
// call without an id is given one, and the results after it take the ids it
|
|
705
|
+
// went out with in order, which is the pairing a dropped id means. Only a
|
|
706
|
+
// result with no call of its turn left to answer becomes text in the user's
|
|
707
|
+
// own turn, so the words the tool returned stay with the conversation.
|
|
690
708
|
function qoderMessages(msgs) {
|
|
691
709
|
const out = []
|
|
692
710
|
const names = {}
|
|
@@ -695,31 +713,64 @@ function qoderMessages(msgs) {
|
|
|
695
713
|
if (seen.length) out.push({ role: "user", content: seen })
|
|
696
714
|
seen = []
|
|
697
715
|
}
|
|
698
|
-
|
|
716
|
+
// turn is the assistant turn being answered: the calls no result has
|
|
717
|
+
// answered yet, in the order they were made, and the ones a result after
|
|
718
|
+
// it names (kept), which a result whose id was lost must not take.
|
|
719
|
+
let turn = null
|
|
720
|
+
const asText = (txt, ims) => {
|
|
721
|
+
const said = txt.trim()
|
|
722
|
+
if (!said && !ims.length) return
|
|
723
|
+
if (seen.length) seen.push({ type: "text", text: "\n\n" })
|
|
724
|
+
if (said) seen.push({ type: "text", text: said })
|
|
725
|
+
seen.push(...ims)
|
|
726
|
+
}
|
|
727
|
+
for (const [i, m] of msgs.entries()) {
|
|
699
728
|
if (m.role === "system" || m.role === "developer") continue
|
|
729
|
+
if (m.role === "user") turn = null
|
|
700
730
|
if (m.role !== "user" && m.role !== "tool") showSeen()
|
|
701
731
|
if (m.role === "tool") {
|
|
702
732
|
let txt = textOf(m.content)
|
|
703
733
|
const ims = Array.isArray(m.content) ? m.content.filter((p) => p?.type === "image_url").map(imageBlock).filter(Boolean) : []
|
|
734
|
+
const named = String(m.tool_call_id ?? "").trim()
|
|
735
|
+
// A result naming a call of this turn answers it; one whose id the
|
|
736
|
+
// client lost — none, or one no call here has — answers the earliest
|
|
737
|
+
// call still open that no other result names, which is the order the
|
|
738
|
+
// calls were made in.
|
|
739
|
+
const at = named ? turn?.open.findIndex((id) => id === named) ?? -1 : -1
|
|
740
|
+
const pick = at >= 0 ? at : turn ? turn.open.findIndex((id) => !turn.kept.has(id)) : -1
|
|
741
|
+
if (pick < 0) {
|
|
742
|
+
asText(txt, ims)
|
|
743
|
+
continue
|
|
744
|
+
}
|
|
745
|
+
const answered = turn.open.splice(pick, 1)[0]
|
|
704
746
|
if (ims.length) {
|
|
705
|
-
let of = "tool call " +
|
|
706
|
-
const name = names[
|
|
747
|
+
let of = "tool call " + answered
|
|
748
|
+
const name = names[answered] || m.name
|
|
707
749
|
if (name) of = `${name} (${of})`
|
|
708
750
|
seen.push({ type: "text", text: `[From the result of ${of}:]` }, ...ims)
|
|
709
751
|
const note = ims.length === 1 ? "[The tool returned an image; it follows in the next message.]" : `[The tool returned ${ims.length} images; they follow in the next message.]`
|
|
710
752
|
txt = txt.trim() ? txt + "\n\n" + note : txt + note
|
|
711
753
|
}
|
|
712
|
-
out.push({ role: "tool", tool_call_id:
|
|
754
|
+
out.push({ role: "tool", tool_call_id: answered, content: txt })
|
|
713
755
|
continue
|
|
714
756
|
}
|
|
715
757
|
if (m.role === "assistant" && m.tool_calls?.length) {
|
|
758
|
+
// a call the client sent without an id gets one, so its result can
|
|
759
|
+
// answer it instead of being turned into text
|
|
716
760
|
const calls = m.tool_calls.map((c) => {
|
|
717
|
-
const id = c.id || "call_" + hexID()
|
|
761
|
+
const id = String(c.id ?? "").trim() || "call_" + hexID()
|
|
718
762
|
names[id] = c.function?.name
|
|
719
763
|
const args = c.function?.arguments
|
|
720
764
|
return { id, type: "function", function: { name: c.function?.name, arguments: typeof args === "string" ? args : JSON.stringify(args ?? {}) } }
|
|
721
765
|
})
|
|
722
766
|
out.push({ role: "assistant", content: textOf(m.content), tool_calls: calls })
|
|
767
|
+
const open = calls.map((c) => c.id)
|
|
768
|
+
const kept = new Set()
|
|
769
|
+
for (let j = i + 1; j < msgs.length && msgs[j].role === "tool"; j++) {
|
|
770
|
+
const id = String(msgs[j].tool_call_id ?? "").trim()
|
|
771
|
+
if (open.includes(id)) kept.add(id)
|
|
772
|
+
}
|
|
773
|
+
turn = { open, kept }
|
|
723
774
|
continue
|
|
724
775
|
}
|
|
725
776
|
let blocks = blocksOf(m.content)
|
|
@@ -727,6 +778,8 @@ function qoderMessages(msgs) {
|
|
|
727
778
|
if (!blocks.length) continue
|
|
728
779
|
if (m.role === "user" && seen.length) [blocks, seen] = [[...seen, ...blocks], []]
|
|
729
780
|
out.push({ role: m.role, content: blocks })
|
|
781
|
+
// a result after the assistant's own words answers no call before it
|
|
782
|
+
turn = null
|
|
730
783
|
}
|
|
731
784
|
showSeen()
|
|
732
785
|
return out
|
|
@@ -1928,4 +1981,4 @@ export const QoderAuthPlugin = makePlugin(SITES.qoder)
|
|
|
1928
1981
|
export const QoderCNAuthPlugin = makePlugin(SITES["qoder-cn"])
|
|
1929
1982
|
|
|
1930
1983
|
// for tests
|
|
1931
|
-
export const _internal = { campaignRuntime, campaignPage, campaignTarget, vpcInstance, vpcSite, parseUsage, gfmt, when, failure, modelInfo, qoderBody, SITES, decodeBody, events }
|
|
1984
|
+
export const _internal = { campaignRuntime, campaignPage, campaignTarget, vpcInstance, vpcSite, parseUsage, gfmt, when, failure, modelInfo, qoderBody, qoderMessages, SITES, decodeBody, events }
|
package/package.json
CHANGED