@magpie-community/opencode-trae-auth 0.2.18 → 0.2.19
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 +8 -0
- package/index.mjs +101 -7
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -165,6 +165,14 @@ agent's `POST https://trae-api-cn.mchost.guru/api/agent/v3/llm_utils_chat`:
|
|
|
165
165
|
or is cut short, is never sent on as the answer: the reply ends with an
|
|
166
166
|
error (a 502 without a stream), so the gateway can try again
|
|
167
167
|
(plugins#38). A `<tool_call>` in backticks or a code fence is text.
|
|
168
|
+
- Kimi and MiMo sometimes write a call in the tags they were trained on:
|
|
169
|
+
Qwen's `<function=name><parameter=k>v</parameter></function>` (bare or
|
|
170
|
+
inside `<tool_call>`), or Anthropic's `<function_calls>` around one or
|
|
171
|
+
more `<invoke name="…"><parameter name="k">v</parameter></invoke>`.
|
|
172
|
+
Each is read as a call, a parameter's value as JSON unless the tool's
|
|
173
|
+
schema says string, and one cut short is an error as above. These
|
|
174
|
+
tags are read only for a tool the request declared; for any other, or
|
|
175
|
+
in backticks or a code fence, they stay text.
|
|
168
176
|
- Calls from either source become OpenAI `tool_calls`. A name matching a
|
|
169
177
|
requested tool but for case or punctuation (`Read`) goes on as the
|
|
170
178
|
request's (`read`); any other goes on as written, so the agent can say
|
package/index.mjs
CHANGED
|
@@ -1332,7 +1332,8 @@ class TextTools {
|
|
|
1332
1332
|
const r = this.buf.search(REASON_AT)
|
|
1333
1333
|
const sm = this.buf.match(STRAY_AT)
|
|
1334
1334
|
const s = sm ? sm.index : -1
|
|
1335
|
-
|
|
1335
|
+
const x = this.buf.search(XML_AT)
|
|
1336
|
+
if (s >= 0 && [i, d, r, x].every((y) => y < 0 || s < y)) {
|
|
1336
1337
|
// a close with no call open, with what GLM left before it
|
|
1337
1338
|
// (}}</tool_call>, magpie#1945): not the answer, dropped; in code,
|
|
1338
1339
|
// shown
|
|
@@ -1351,7 +1352,7 @@ class TextTools {
|
|
|
1351
1352
|
this.after = true
|
|
1352
1353
|
continue
|
|
1353
1354
|
}
|
|
1354
|
-
if (r >= 0 && (i < 0 || r < i) && (d < 0 || r < d)) {
|
|
1355
|
+
if (r >= 0 && (i < 0 || r < i) && (d < 0 || r < d) && (x < 0 || r < x)) {
|
|
1355
1356
|
const o = objectEnd(this.buf, r)
|
|
1356
1357
|
if (o === -1 && !end) {
|
|
1357
1358
|
// not whole yet: held, the text before it sent
|
|
@@ -1371,7 +1372,39 @@ class TextTools {
|
|
|
1371
1372
|
this.buf = this.buf.slice(r + 1)
|
|
1372
1373
|
continue
|
|
1373
1374
|
}
|
|
1374
|
-
if (i < 0 && d < 0) break
|
|
1375
|
+
if (i < 0 && d < 0 && x < 0) break
|
|
1376
|
+
if (x >= 0 && (i < 0 || x < i) && (d < 0 || x < d)) {
|
|
1377
|
+
// Anthropic's <function_calls><invoke name="…"> or Qwen's
|
|
1378
|
+
// <function=…>, as Kimi and MiMo write a call (01huadalang on
|
|
1379
|
+
// magpie's Discord, dsh): a call only for a tool the request
|
|
1380
|
+
// declared, and never in a code fence or backticks
|
|
1381
|
+
const head = this.buf.slice(0, x)
|
|
1382
|
+
const at = this.buf.slice(x)
|
|
1383
|
+
const opener = at.match(/^<[^<>]*>?/)[0]
|
|
1384
|
+
if (quoted(this.sent + text + head)) {
|
|
1385
|
+
text += head + opener
|
|
1386
|
+
this.buf = at.slice(opener.length)
|
|
1387
|
+
continue
|
|
1388
|
+
}
|
|
1389
|
+
const b = this.xmlCall(at, end)
|
|
1390
|
+
if (b.wait) break
|
|
1391
|
+
if (b.text) {
|
|
1392
|
+
text += head + opener
|
|
1393
|
+
this.buf = at.slice(opener.length)
|
|
1394
|
+
continue
|
|
1395
|
+
}
|
|
1396
|
+
text += head
|
|
1397
|
+
if (b.error) {
|
|
1398
|
+
this.buf = ""
|
|
1399
|
+
this.sent += text
|
|
1400
|
+
return { text, calls, reasoning, error: b.error }
|
|
1401
|
+
}
|
|
1402
|
+
calls.push(b.call)
|
|
1403
|
+
this.mark = this.sent.length + text.length
|
|
1404
|
+
this.buf = b.rest
|
|
1405
|
+
this.after = true
|
|
1406
|
+
continue
|
|
1407
|
+
}
|
|
1375
1408
|
if (d >= 0 && (i < 0 || d < i)) {
|
|
1376
1409
|
text += this.buf.slice(0, d)
|
|
1377
1410
|
this.buf = this.buf.slice(d)
|
|
@@ -1462,7 +1495,7 @@ class TextTools {
|
|
|
1462
1495
|
this.buf = ""
|
|
1463
1496
|
} else {
|
|
1464
1497
|
// keep back an opened block, or what may be the start of one
|
|
1465
|
-
const at = [this.buf.search(OPEN_AT), this.buf.search(OPEN_HOLD), this.buf.search(DSML_AT), this.buf.search(REASON_AT)].filter((x) => x >= 0)
|
|
1498
|
+
const at = [this.buf.search(OPEN_AT), this.buf.search(OPEN_HOLD), this.buf.search(DSML_AT), this.buf.search(REASON_AT), this.buf.search(XML_AT), this.buf.search(XML_HOLD)].filter((x) => x >= 0)
|
|
1466
1499
|
let keep = at.length ? this.buf.length - Math.min(...at) : 0
|
|
1467
1500
|
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 }
|
|
1468
1501
|
// and braces, brackets, quotes or a > a close may still come after
|
|
@@ -1475,6 +1508,60 @@ class TextTools {
|
|
|
1475
1508
|
return { text, calls, reasoning }
|
|
1476
1509
|
}
|
|
1477
1510
|
|
|
1511
|
+
// xmlCall reads a call written in Anthropic's tags (<function_calls>
|
|
1512
|
+
// around one or more <invoke name="…"><parameter name="k">v</parameter>
|
|
1513
|
+
// </invoke>) or Qwen's (<function=…><parameter=k>v</parameter>
|
|
1514
|
+
// </function>), s starting at its first tag: {call, rest}, {wait}
|
|
1515
|
+
// while it may still become one, {text} when it is no call (a tool the
|
|
1516
|
+
// request didn't declare, a tag named in prose), {error} for one cut
|
|
1517
|
+
// short. Each invoke is a call of its own; the tags between them are
|
|
1518
|
+
// dropped as the leftovers of one.
|
|
1519
|
+
xmlCall(s, end) {
|
|
1520
|
+
const w = s.match(/^<(?:antml:)?function_calls>/)
|
|
1521
|
+
if (w) {
|
|
1522
|
+
const rest = s.slice(w[0].length).replace(/^\s+/, "")
|
|
1523
|
+
if (!end && (!rest || XML_STARTS.some((k) => k.startsWith(rest)))) return { wait: true }
|
|
1524
|
+
return this.xmlInvoke(rest, end)
|
|
1525
|
+
}
|
|
1526
|
+
return this.xmlInvoke(s, end)
|
|
1527
|
+
}
|
|
1528
|
+
|
|
1529
|
+
xmlInvoke(s, end) {
|
|
1530
|
+
let m = s.match(/^<(?:antml:)?invoke\s+name\s*=\s*"([^"<>]+)"\s*>/)
|
|
1531
|
+
let closeRe = /<\/(?:antml:)?invoke\s*>/
|
|
1532
|
+
if (!m) {
|
|
1533
|
+
m = s.match(/^<function=([^\s<>]+?)\s*>/)
|
|
1534
|
+
closeRe = /<\/function\s*>/
|
|
1535
|
+
}
|
|
1536
|
+
if (!m) {
|
|
1537
|
+
// an opener still being written
|
|
1538
|
+
if (!end && (XML_STARTS.some((k) => k.startsWith(s)) || /^<(?:antml:)?(?:invoke\s|function=)[^<>]{0,300}$/.test(s))) return { wait: true }
|
|
1539
|
+
return { text: true }
|
|
1540
|
+
}
|
|
1541
|
+
const declared = this.tools.map((t) => (t.function ?? t)?.name).filter(Boolean)
|
|
1542
|
+
const name = toolNamed(m[1].trim(), this.tools)
|
|
1543
|
+
if (!declared.includes(name)) return { text: true }
|
|
1544
|
+
const body = s.slice(m[0].length)
|
|
1545
|
+
const c = body.match(closeRe)
|
|
1546
|
+
const n = body.search(XML_NEXT)
|
|
1547
|
+
let stop = c ? c.index : -1
|
|
1548
|
+
if (n >= 0 && (stop < 0 || n < stop)) stop = n
|
|
1549
|
+
if (stop < 0) {
|
|
1550
|
+
if (!end) return { wait: true }
|
|
1551
|
+
stop = body.length
|
|
1552
|
+
}
|
|
1553
|
+
const whole = !!c && c.index === stop
|
|
1554
|
+
const region = body.slice(0, stop).replace(/<(\/?)antml:parameter\b/g, "<$1parameter")
|
|
1555
|
+
const p = paramArgs(region, name, this.tools)
|
|
1556
|
+
if (!whole && !p.closed) {
|
|
1557
|
+
// no parameter and no close: a tag named in passing; a parameter
|
|
1558
|
+
// left open: the call cut short (plugins#38)
|
|
1559
|
+
if (!/<parameter\b/.test(region)) return { text: true }
|
|
1560
|
+
return { error: CUT_SHORT }
|
|
1561
|
+
}
|
|
1562
|
+
return { call: { name, arguments: JSON.stringify(p.args) }, rest: whole ? body.slice(stop + c[0].length) : body.slice(stop) }
|
|
1563
|
+
}
|
|
1564
|
+
|
|
1478
1565
|
// block reads what follows a <tool_call> opener: {call, rest} for a
|
|
1479
1566
|
// call, {wait} while it may still become one, {text} when it is no call
|
|
1480
1567
|
// at all, {error} for one that can't be read: cut short, or not JSON
|
|
@@ -1960,11 +2047,18 @@ const DSML_INVOKE = new RegExp(String.raw`^<\s*[||][\s||]*DSML[\s||]*invok
|
|
|
1960
2047
|
const DSML_INVOKE_AT = new RegExp(String.raw`<\s*[||][\s||]*DSML[\s||]*invoke\s+name\s*=`)
|
|
1961
2048
|
const DSML_CLOSE = new RegExp(String.raw`<\s*\/\s*[||][\s||]*DSML[\s||]*invoke\s*>`)
|
|
1962
2049
|
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")
|
|
2050
|
+
// Anthropic's and Qwen's call tags (xmlCall): where one starts, an
|
|
2051
|
+
// opener still being written, the openers a wrapper is followed by, and
|
|
2052
|
+
// the next call's opener
|
|
2053
|
+
const XML_AT = /<(?:antml:)?(?:function_calls>|invoke\s+name\s*=|function=)/
|
|
2054
|
+
const XML_HOLD = /<(?:antml:)?(?:invoke\s[^<>]{0,300}|function=[^<>]{0,300})$/
|
|
2055
|
+
const XML_STARTS = ["<invoke ", "<antml:invoke ", "<function="]
|
|
2056
|
+
const XML_NEXT = /<(?:antml:)?invoke\s+name\s*=|<function=/
|
|
1963
2057
|
// what is left after a call: blanks, our close, DeepSeek's tags but the
|
|
1964
|
-
// next call's
|
|
1965
|
-
const LEFTOVER = new RegExp(String.raw`^(?:\s+|<\/tool_?calls?>|<tool_?calls>|` + DSML + String.raw`(?![\s||]*invoke\b)[^<>]*>)+`)
|
|
2058
|
+
// next call's, the closes of Anthropic's and Qwen's
|
|
2059
|
+
const LEFTOVER = new RegExp(String.raw`^(?:\s+|<\/tool_?calls?>|<tool_?calls>|<\/(?:antml:)?(?:function_calls|invoke|function)>|` + DSML + String.raw`(?![\s||]*invoke\b)[^<>]*>)+`)
|
|
1966
2060
|
// what text may be the start of, so held back
|
|
1967
|
-
const MARKS = [OPEN, CLOSE, "<toolcall>", "</toolcall>", "<|DSML|", "<|DSML|", "</|DSML|", "</|DSML|", '{"reasoning_content"']
|
|
2061
|
+
const MARKS = [OPEN, CLOSE, "<toolcall>", "</toolcall>", "<|DSML|", "<|DSML|", "</|DSML|", "</|DSML|", '{"reasoning_content"', "<function_calls>", "</function_calls>", "<antml:function_calls>", "</antml:function_calls>", "<invoke ", "<antml:invoke ", "</invoke>", "</antml:invoke>", "<function=", "</function>"]
|
|
1968
2062
|
// reasoning a model wrote into its text as JSON
|
|
1969
2063
|
const REASON_AT = /\{\s*"reasoning_content"\s*:/
|
|
1970
2064
|
const MARK_MAX = Math.max(...MARKS.map((m) => m.length))
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@magpie-community/opencode-trae-auth",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.19",
|
|
4
4
|
"description": "OpenCode provider plugins for Trae: Trae CN (trae.cn) and Trae Global (trae.ai), free tier included, with Trae's own browser sign-in",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./index.mjs",
|