@magpie-community/opencode-qoder-auth 0.2.21 → 0.2.22
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 +4 -0
- package/index.mjs +52 -6
- 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:
|
package/index.mjs
CHANGED
|
@@ -687,6 +687,17 @@ const textOf = (c) => (typeof c === "string" ? c : blocksOf(c).filter((b) => b.t
|
|
|
687
687
|
// blocks, an assistant's tool calls as tool_calls, results as tool turns by
|
|
688
688
|
// tool_call_id. A tool turn holds text only, so the images a tool returned
|
|
689
689
|
// follow in a user turn — the start of the user's own when one comes next.
|
|
690
|
+
//
|
|
691
|
+
// Qoder turns the whole request away when a tool turn answers no call of the
|
|
692
|
+
// turn before it ("Messages with role 'tool' must be a response to a
|
|
693
|
+
// preceding message with 'tool_calls'"), where another API takes the same
|
|
694
|
+
// conversation. A client's history reaches here with such a turn because it
|
|
695
|
+
// dropped the id: either the call lost it, so the result names one no call
|
|
696
|
+
// has, or the result kept none. Both are filled in rather than dropped — a
|
|
697
|
+
// call without an id is given one, and the results after it take the ids it
|
|
698
|
+
// went out with in order, which is the pairing a dropped id means. Only a
|
|
699
|
+
// result with no call of its turn left to answer becomes text in the user's
|
|
700
|
+
// own turn, so the words the tool returned stay with the conversation.
|
|
690
701
|
function qoderMessages(msgs) {
|
|
691
702
|
const out = []
|
|
692
703
|
const names = {}
|
|
@@ -695,31 +706,64 @@ function qoderMessages(msgs) {
|
|
|
695
706
|
if (seen.length) out.push({ role: "user", content: seen })
|
|
696
707
|
seen = []
|
|
697
708
|
}
|
|
698
|
-
|
|
709
|
+
// turn is the assistant turn being answered: the calls no result has
|
|
710
|
+
// answered yet, in the order they were made, and the ones a result after
|
|
711
|
+
// it names (kept), which a result whose id was lost must not take.
|
|
712
|
+
let turn = null
|
|
713
|
+
const asText = (txt, ims) => {
|
|
714
|
+
const said = txt.trim()
|
|
715
|
+
if (!said && !ims.length) return
|
|
716
|
+
if (seen.length) seen.push({ type: "text", text: "\n\n" })
|
|
717
|
+
if (said) seen.push({ type: "text", text: said })
|
|
718
|
+
seen.push(...ims)
|
|
719
|
+
}
|
|
720
|
+
for (const [i, m] of msgs.entries()) {
|
|
699
721
|
if (m.role === "system" || m.role === "developer") continue
|
|
722
|
+
if (m.role === "user") turn = null
|
|
700
723
|
if (m.role !== "user" && m.role !== "tool") showSeen()
|
|
701
724
|
if (m.role === "tool") {
|
|
702
725
|
let txt = textOf(m.content)
|
|
703
726
|
const ims = Array.isArray(m.content) ? m.content.filter((p) => p?.type === "image_url").map(imageBlock).filter(Boolean) : []
|
|
727
|
+
const named = String(m.tool_call_id ?? "").trim()
|
|
728
|
+
// A result naming a call of this turn answers it; one whose id the
|
|
729
|
+
// client lost — none, or one no call here has — answers the earliest
|
|
730
|
+
// call still open that no other result names, which is the order the
|
|
731
|
+
// calls were made in.
|
|
732
|
+
const at = named ? turn?.open.findIndex((id) => id === named) ?? -1 : -1
|
|
733
|
+
const pick = at >= 0 ? at : turn ? turn.open.findIndex((id) => !turn.kept.has(id)) : -1
|
|
734
|
+
if (pick < 0) {
|
|
735
|
+
asText(txt, ims)
|
|
736
|
+
continue
|
|
737
|
+
}
|
|
738
|
+
const answered = turn.open.splice(pick, 1)[0]
|
|
704
739
|
if (ims.length) {
|
|
705
|
-
let of = "tool call " +
|
|
706
|
-
const name = names[
|
|
740
|
+
let of = "tool call " + answered
|
|
741
|
+
const name = names[answered] || m.name
|
|
707
742
|
if (name) of = `${name} (${of})`
|
|
708
743
|
seen.push({ type: "text", text: `[From the result of ${of}:]` }, ...ims)
|
|
709
744
|
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
745
|
txt = txt.trim() ? txt + "\n\n" + note : txt + note
|
|
711
746
|
}
|
|
712
|
-
out.push({ role: "tool", tool_call_id:
|
|
747
|
+
out.push({ role: "tool", tool_call_id: answered, content: txt })
|
|
713
748
|
continue
|
|
714
749
|
}
|
|
715
750
|
if (m.role === "assistant" && m.tool_calls?.length) {
|
|
751
|
+
// a call the client sent without an id gets one, so its result can
|
|
752
|
+
// answer it instead of being turned into text
|
|
716
753
|
const calls = m.tool_calls.map((c) => {
|
|
717
|
-
const id = c.id || "call_" + hexID()
|
|
754
|
+
const id = String(c.id ?? "").trim() || "call_" + hexID()
|
|
718
755
|
names[id] = c.function?.name
|
|
719
756
|
const args = c.function?.arguments
|
|
720
757
|
return { id, type: "function", function: { name: c.function?.name, arguments: typeof args === "string" ? args : JSON.stringify(args ?? {}) } }
|
|
721
758
|
})
|
|
722
759
|
out.push({ role: "assistant", content: textOf(m.content), tool_calls: calls })
|
|
760
|
+
const open = calls.map((c) => c.id)
|
|
761
|
+
const kept = new Set()
|
|
762
|
+
for (let j = i + 1; j < msgs.length && msgs[j].role === "tool"; j++) {
|
|
763
|
+
const id = String(msgs[j].tool_call_id ?? "").trim()
|
|
764
|
+
if (open.includes(id)) kept.add(id)
|
|
765
|
+
}
|
|
766
|
+
turn = { open, kept }
|
|
723
767
|
continue
|
|
724
768
|
}
|
|
725
769
|
let blocks = blocksOf(m.content)
|
|
@@ -727,6 +771,8 @@ function qoderMessages(msgs) {
|
|
|
727
771
|
if (!blocks.length) continue
|
|
728
772
|
if (m.role === "user" && seen.length) [blocks, seen] = [[...seen, ...blocks], []]
|
|
729
773
|
out.push({ role: m.role, content: blocks })
|
|
774
|
+
// a result after the assistant's own words answers no call before it
|
|
775
|
+
turn = null
|
|
730
776
|
}
|
|
731
777
|
showSeen()
|
|
732
778
|
return out
|
|
@@ -1928,4 +1974,4 @@ export const QoderAuthPlugin = makePlugin(SITES.qoder)
|
|
|
1928
1974
|
export const QoderCNAuthPlugin = makePlugin(SITES["qoder-cn"])
|
|
1929
1975
|
|
|
1930
1976
|
// for tests
|
|
1931
|
-
export const _internal = { campaignRuntime, campaignPage, campaignTarget, vpcInstance, vpcSite, parseUsage, gfmt, when, failure, modelInfo, qoderBody, SITES, decodeBody, events }
|
|
1977
|
+
export const _internal = { campaignRuntime, campaignPage, campaignTarget, vpcInstance, vpcSite, parseUsage, gfmt, when, failure, modelInfo, qoderBody, qoderMessages, SITES, decodeBody, events }
|
package/package.json
CHANGED