@magpie-community/opencode-trae-auth 0.1.9 → 0.1.11
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/index.mjs +180 -11
- package/package.json +1 -1
package/index.mjs
CHANGED
|
@@ -534,25 +534,79 @@ const callId = () => "call_" + randomBytes(12).toString("hex") + (callSeq++).toS
|
|
|
534
534
|
|
|
535
535
|
// TextTools splits the model's text into what the agent sees and the tool
|
|
536
536
|
// calls it wrote as blocks, holding back the start of a block until it
|
|
537
|
-
// is whole
|
|
537
|
+
// is whole. A block is ours (<tool_call>{…}</tool_call>), its JSON taken
|
|
538
|
+
// once whole even when DeepSeek closes it with its own tags in place of
|
|
539
|
+
// </tool_call>, or DeepSeek's (<|DSML|invoke name="…">, magpie#823); the
|
|
540
|
+
// tags left around a call are dropped.
|
|
538
541
|
class TextTools {
|
|
539
542
|
constructor() {
|
|
540
543
|
this.buf = ""
|
|
544
|
+
this.after = false
|
|
541
545
|
}
|
|
542
546
|
push(s, end = false) {
|
|
543
547
|
this.buf += s
|
|
544
548
|
let text = ""
|
|
545
549
|
const calls = []
|
|
546
550
|
for (;;) {
|
|
551
|
+
if (this.after) {
|
|
552
|
+
const m = this.buf.match(LEFTOVER)
|
|
553
|
+
if (m) this.buf = this.buf.slice(m[0].length)
|
|
554
|
+
if (!this.buf || (!end && MARKS.some((k) => k.startsWith(this.buf)))) break
|
|
555
|
+
this.after = false
|
|
556
|
+
}
|
|
547
557
|
const i = this.buf.indexOf(OPEN)
|
|
548
|
-
|
|
558
|
+
const d = this.buf.search(DSML_AT)
|
|
559
|
+
if (i < 0 && d < 0) break
|
|
560
|
+
if (d >= 0 && (i < 0 || d < i)) {
|
|
561
|
+
text += this.buf.slice(0, d)
|
|
562
|
+
this.buf = this.buf.slice(d)
|
|
563
|
+
const tag = this.buf.match(DSML_TAG)
|
|
564
|
+
if (!tag) {
|
|
565
|
+
// a tag not closed yet; at the end, or with another tag after it,
|
|
566
|
+
// a stray one dropped
|
|
567
|
+
const next = this.buf.indexOf("<", 1)
|
|
568
|
+
if (next < 0 && !end) break
|
|
569
|
+
this.buf = next < 0 ? "" : this.buf.slice(next)
|
|
570
|
+
continue
|
|
571
|
+
}
|
|
572
|
+
const inv = tag[0].match(DSML_INVOKE)
|
|
573
|
+
if (!inv) {
|
|
574
|
+
// function_calls, a parameter's close, any other tag: dropped,
|
|
575
|
+
// with the blanks after it
|
|
576
|
+
this.buf = this.buf.slice(tag[0].length)
|
|
577
|
+
this.after = true
|
|
578
|
+
continue
|
|
579
|
+
}
|
|
580
|
+
const body = this.buf.slice(tag[0].length)
|
|
581
|
+
const c = body.match(DSML_CLOSE)
|
|
582
|
+
const n = body.search(DSML_INVOKE_AT)
|
|
583
|
+
let stop = c ? c.index : n >= 0 ? n : -1
|
|
584
|
+
if (n >= 0 && n < stop) stop = n
|
|
585
|
+
if (stop < 0) {
|
|
586
|
+
if (!end) break
|
|
587
|
+
stop = body.length
|
|
588
|
+
}
|
|
589
|
+
calls.push({ name: inv[1], arguments: dsmlArgs(body.slice(0, stop)) })
|
|
590
|
+
this.buf = body.slice(c && c.index === stop ? stop + c[0].length : stop)
|
|
591
|
+
this.after = true
|
|
592
|
+
continue
|
|
593
|
+
}
|
|
594
|
+
const o = objectEnd(this.buf, i + OPEN.length)
|
|
595
|
+
const v = o > 0 ? looseJSON(this.buf.slice(i + OPEN.length, o)) : null
|
|
596
|
+
if (v?.name) {
|
|
597
|
+
text += this.buf.slice(0, i)
|
|
598
|
+
calls.push({ name: String(v.name), arguments: callArgs(v) })
|
|
599
|
+
this.buf = this.buf.slice(o)
|
|
600
|
+
this.after = true
|
|
601
|
+
continue
|
|
602
|
+
}
|
|
549
603
|
const j = this.buf.indexOf(CLOSE, i + OPEN.length)
|
|
550
604
|
if (j < 0) break
|
|
551
605
|
text += this.buf.slice(0, i)
|
|
552
606
|
const raw = this.buf.slice(i + OPEN.length, j).trim()
|
|
553
607
|
this.buf = this.buf.slice(j + CLOSE.length)
|
|
554
|
-
const
|
|
555
|
-
if (
|
|
608
|
+
const w = looseJSON(raw) ?? {}
|
|
609
|
+
if (w.name) calls.push({ name: String(w.name), arguments: callArgs(w) })
|
|
556
610
|
else text += OPEN + raw + CLOSE
|
|
557
611
|
}
|
|
558
612
|
if (end) {
|
|
@@ -560,9 +614,9 @@ class TextTools {
|
|
|
560
614
|
this.buf = ""
|
|
561
615
|
} else {
|
|
562
616
|
// keep back an opened block, or what may be the start of one
|
|
563
|
-
const
|
|
564
|
-
let keep =
|
|
565
|
-
if (!keep) for (let k = Math.min(
|
|
617
|
+
const at = [this.buf.indexOf(OPEN), this.buf.search(DSML_AT)].filter((x) => x >= 0)
|
|
618
|
+
let keep = at.length ? this.buf.length - Math.min(...at) : 0
|
|
619
|
+
if (!keep) for (let k = Math.min(MARK_MAX - 1, this.buf.length); k > 0; k--) if (MARKS.some((m) => m.startsWith(this.buf.slice(-k)))) { keep = k; break }
|
|
566
620
|
text += this.buf.slice(0, this.buf.length - keep)
|
|
567
621
|
this.buf = this.buf.slice(this.buf.length - keep)
|
|
568
622
|
}
|
|
@@ -570,14 +624,120 @@ class TextTools {
|
|
|
570
624
|
}
|
|
571
625
|
}
|
|
572
626
|
|
|
627
|
+
// DeepSeek's tags, with its full-width bar or a plain one
|
|
628
|
+
const DSML = String.raw`<\s*\/?\s*[||][\s||]*DSML[\s||]*`
|
|
629
|
+
const DSML_AT = new RegExp(DSML)
|
|
630
|
+
const DSML_TAG = new RegExp("^" + DSML + "[^<>]*>")
|
|
631
|
+
const DSML_INVOKE = new RegExp(String.raw`^<\s*[||][\s||]*DSML[\s||]*invoke\s+name\s*=\s*"([^"]+)"\s*>`)
|
|
632
|
+
const DSML_INVOKE_AT = new RegExp(String.raw`<\s*[||][\s||]*DSML[\s||]*invoke\s+name\s*=`)
|
|
633
|
+
const DSML_CLOSE = new RegExp(String.raw`<\s*\/\s*[||][\s||]*DSML[\s||]*invoke\s*>`)
|
|
634
|
+
const DSML_PARAM = new RegExp(String.raw`<\s*[||][\s||]*DSML[\s||]*parameter\s+name\s*=\s*"([^"]+)"((?:\s+\w+\s*=\s*"[^"]*")*)\s*>([\s\S]*?)` + DSML + String.raw`parameter\s*>`, "g")
|
|
635
|
+
// what is left after a call: blanks, our close, DeepSeek's tags but the
|
|
636
|
+
// next call's
|
|
637
|
+
const LEFTOVER = new RegExp(String.raw`^(?:\s+|<\/tool_calls?>|<tool_calls>|` + DSML + String.raw`(?![\s||]*invoke\b)[^<>]*>)+`)
|
|
638
|
+
// what text may be the start of, so held back
|
|
639
|
+
const MARKS = [OPEN, CLOSE, "<|DSML|", "<|DSML|", "</|DSML|", "</|DSML|"]
|
|
640
|
+
const MARK_MAX = Math.max(...MARKS.map((m) => m.length))
|
|
641
|
+
|
|
642
|
+
// dsmlArgs is the arguments of a DSML invoke as JSON: a parameter marked
|
|
643
|
+
// string="true" is its text, any other its JSON (its text when not JSON)
|
|
644
|
+
function dsmlArgs(body) {
|
|
645
|
+
const args = {}
|
|
646
|
+
for (const p of body.matchAll(DSML_PARAM)) {
|
|
647
|
+
if (/string\s*=\s*"true"/.test(p[2])) {
|
|
648
|
+
args[p[1]] = p[3]
|
|
649
|
+
continue
|
|
650
|
+
}
|
|
651
|
+
try {
|
|
652
|
+
args[p[1]] = JSON.parse(p[3].trim())
|
|
653
|
+
} catch {
|
|
654
|
+
args[p[1]] = p[3]
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
return JSON.stringify(args)
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
// objectEnd is where the JSON object starting at from (after blanks) ends:
|
|
661
|
+
// past its closing brace, -1 when it isn't whole yet, -2 when there's none
|
|
662
|
+
function objectEnd(s, from) {
|
|
663
|
+
let k = from
|
|
664
|
+
while (k < s.length && /\s/.test(s[k])) k++
|
|
665
|
+
if (k >= s.length) return -1
|
|
666
|
+
if (s[k] !== "{") return -2
|
|
667
|
+
let depth = 0
|
|
668
|
+
let str = false
|
|
669
|
+
let esc = false
|
|
670
|
+
for (; k < s.length; k++) {
|
|
671
|
+
const ch = s[k]
|
|
672
|
+
if (str) {
|
|
673
|
+
if (esc) esc = false
|
|
674
|
+
else if (ch === "\\") esc = true
|
|
675
|
+
else if (ch === '"') str = false
|
|
676
|
+
continue
|
|
677
|
+
}
|
|
678
|
+
if (ch === '"') str = true
|
|
679
|
+
else if (ch === "{") depth++
|
|
680
|
+
else if (ch === "}" && --depth === 0) return k + 1
|
|
681
|
+
}
|
|
682
|
+
return -1
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
// looseJSON reads a JSON object as a model writes one: a string holding a
|
|
686
|
+
// raw newline or tab (a file's content written out, #799) is read as if
|
|
687
|
+
// they were escaped. null when it isn't one even so.
|
|
688
|
+
function looseJSON(s) {
|
|
689
|
+
for (const t of [s, escapeRaw(s)]) {
|
|
690
|
+
try {
|
|
691
|
+
const v = JSON.parse(t)
|
|
692
|
+
if (v && typeof v === "object" && !Array.isArray(v)) return v
|
|
693
|
+
} catch {}
|
|
694
|
+
}
|
|
695
|
+
return null
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
// escapeRaw escapes the control characters inside JSON strings
|
|
699
|
+
function escapeRaw(s) {
|
|
700
|
+
let out = ""
|
|
701
|
+
let str = false
|
|
702
|
+
let esc = false
|
|
703
|
+
for (const ch of String(s ?? "")) {
|
|
704
|
+
if (str && !esc && ch < " ") {
|
|
705
|
+
out += ch === "\n" ? "\\n" : ch === "\r" ? "\\r" : ch === "\t" ? "\\t" : "\\u" + ch.charCodeAt(0).toString(16).padStart(4, "0")
|
|
706
|
+
continue
|
|
707
|
+
}
|
|
708
|
+
if (str && ch === "\\" && !esc) esc = true
|
|
709
|
+
else {
|
|
710
|
+
if (ch === '"' && !esc) str = !str
|
|
711
|
+
esc = false
|
|
712
|
+
}
|
|
713
|
+
out += ch
|
|
714
|
+
}
|
|
715
|
+
return out
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
// callArgs is a tagged block's arguments as JSON: under arguments (an
|
|
719
|
+
// object, or a string of one), input or parameters, else the block's other
|
|
720
|
+
// keys, as a model that writes them beside the name means them (#799: a
|
|
721
|
+
// write that went out as {})
|
|
722
|
+
function callArgs(v) {
|
|
723
|
+
let a = v.arguments ?? v.input ?? v.parameters
|
|
724
|
+
if (a === undefined) {
|
|
725
|
+
const { name, type, id, ...rest } = v
|
|
726
|
+
a = rest
|
|
727
|
+
}
|
|
728
|
+
if (typeof a === "string") return a.trim() ? (looseJSON(a) ? JSON.stringify(looseJSON(a)) : a) : "{}"
|
|
729
|
+
return JSON.stringify(a ?? {})
|
|
730
|
+
}
|
|
731
|
+
|
|
573
732
|
// nativeCall is a tool call as Trae's events carry one
|
|
574
733
|
function nativeCall(tc) {
|
|
575
734
|
const f = tc?.function ?? tc?.function_call ?? tc
|
|
576
735
|
const name = f?.name ?? tc?.tool_name ?? ""
|
|
577
736
|
const index = Number.isInteger(tc?.index) ? tc.index : null
|
|
578
|
-
if (!name && index === null && !tc?.id) return null
|
|
579
737
|
const a = f?.arguments ?? f?.args ?? tc?.params ?? tc?.input ?? tc?.parameters ?? ""
|
|
580
|
-
|
|
738
|
+
const args = typeof a === "string" ? a : JSON.stringify(a)
|
|
739
|
+
if (!name && index === null && !tc?.id && !args) return null
|
|
740
|
+
return { id: tc?.id || tc?.tool_call_id || "", index, name: String(name), arguments: args }
|
|
581
741
|
}
|
|
582
742
|
|
|
583
743
|
// NativeCalls puts together the tool calls Trae streams: a call comes in
|
|
@@ -594,6 +754,12 @@ class NativeCalls {
|
|
|
594
754
|
let o = this.calls.find((x) => (c.id && x.id === c.id) || (!c.id && c.index !== null && x.index === c.index))
|
|
595
755
|
if (!o && !c.id && c.index === null)
|
|
596
756
|
o = this.calls.findLast((x) => x.name === c.name && (x.arguments === c.arguments || (!done(x.arguments) && c.arguments.startsWith(x.arguments))))
|
|
757
|
+
// a piece with nothing to say whose it is (or an index its call's
|
|
758
|
+
// first event didn't give) belongs to the call still being written
|
|
759
|
+
if (!o && !c.name && !c.id) {
|
|
760
|
+
o = this.calls.findLast((x) => !done(x.arguments))
|
|
761
|
+
if (o && c.index !== null && o.index !== null && o.index !== c.index) o = null
|
|
762
|
+
}
|
|
597
763
|
if (!o) {
|
|
598
764
|
if (!c.name) return
|
|
599
765
|
this.calls.push({ ...c })
|
|
@@ -608,7 +774,10 @@ class NativeCalls {
|
|
|
608
774
|
else o.arguments = prev + next // a piece
|
|
609
775
|
}
|
|
610
776
|
take() {
|
|
611
|
-
return this.calls.filter((c) => c.name).map(({ id, name, arguments: a }) =>
|
|
777
|
+
return this.calls.filter((c) => c.name).map(({ id, name, arguments: a }) => {
|
|
778
|
+
const v = a && !done(a) ? looseJSON(a) : null
|
|
779
|
+
return { id: id || callId(), name, arguments: v ? JSON.stringify(v) : a || "{}" }
|
|
780
|
+
})
|
|
612
781
|
}
|
|
613
782
|
}
|
|
614
783
|
|
|
@@ -1154,4 +1323,4 @@ export const TraeCNAuthPlugin = async ({ client }) => {
|
|
|
1154
1323
|
}
|
|
1155
1324
|
|
|
1156
1325
|
// for tests
|
|
1157
|
-
export const _internal = { HOSTS, MODELS, TextTools, traeMessages, chatBody, credits, whenOf, newDevice }
|
|
1326
|
+
export const _internal = { HOSTS, MODELS, TextTools, NativeCalls, looseJSON, traeMessages, chatBody, credits, whenOf, newDevice }
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@magpie-community/opencode-trae-auth",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.11",
|
|
4
4
|
"description": "OpenCode provider plugin for Trae CN (trae.cn): your Trae CN account's models, free tier included, with Trae's own browser sign-in",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./index.mjs",
|