@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.
Files changed (3) hide show
  1. package/README.md +8 -0
  2. package/index.mjs +101 -7
  3. 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
- if (s >= 0 && [i, d, r].every((x) => x < 0 || s < x)) {
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.18",
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",