@magpie-community/opencode-trae-auth 0.2.7 → 0.2.9

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 (2) hide show
  1. package/index.mjs +60 -14
  2. package/package.json +1 -1
package/index.mjs CHANGED
@@ -1067,6 +1067,15 @@ class TextTools {
1067
1067
  this.after = true
1068
1068
  }
1069
1069
  if (end) {
1070
+ // an opener the stream ended inside (<tool_call name): a call cut
1071
+ // short, not the answer (plugins#38)
1072
+ const h = this.buf.search(OPEN_HOLD)
1073
+ if (h >= 0 && !quoted(this.sent + text + this.buf.slice(0, h))) {
1074
+ text += this.buf.slice(0, h)
1075
+ this.buf = ""
1076
+ this.sent += text
1077
+ return { text, calls, reasoning, error: CUT_SHORT }
1078
+ }
1070
1079
  text += this.buf
1071
1080
  this.buf = ""
1072
1081
  } else {
@@ -1088,11 +1097,11 @@ class TextTools {
1088
1097
  // gave as <tool_call name="…">.
1089
1098
  block(body, named, end) {
1090
1099
  if (!end && !body.trim()) return { wait: true }
1091
- const close = body.indexOf(CLOSE)
1100
+ const [close, closeLen] = closeAt(body)
1092
1101
  if (named) {
1093
1102
  if (close < 0 && !end) return { wait: true }
1094
1103
  const region = close >= 0 ? body.slice(0, close) : body
1095
- const rest = close >= 0 ? body.slice(close + CLOSE.length) : ""
1104
+ const rest = close >= 0 ? body.slice(close + closeLen) : ""
1096
1105
  if (region.trimStart().startsWith("{")) {
1097
1106
  const v = repairedJSON(region.trim())
1098
1107
  if (!v) return { error: close >= 0 ? UNREADABLE : CUT_SHORT }
@@ -1116,7 +1125,7 @@ class TextTools {
1116
1125
  const region = body.slice(ph[0].length, close >= 0 ? close : body.length)
1117
1126
  const p = paramArgs(region, ph[1], this.tools)
1118
1127
  if (close < 0 && !p.closed) return { error: CUT_SHORT }
1119
- return { call: { name: ph[1], arguments: JSON.stringify(p.args) }, rest: close >= 0 ? body.slice(close + CLOSE.length) : "" }
1128
+ return { call: { name: ph[1], arguments: JSON.stringify(p.args) }, rest: close >= 0 ? body.slice(close + closeLen) : "" }
1120
1129
  }
1121
1130
  const o = objectEnd(body, 0)
1122
1131
  if (o > 0 && !this.bad.has(":" + o)) {
@@ -1128,10 +1137,10 @@ class TextTools {
1128
1137
  }
1129
1138
  // not whole, or not JSON as it stands: read up to a close (a file's
1130
1139
  // content may hold one), or at the end all of it
1131
- for (let j = close; j >= 0; j = body.indexOf(CLOSE, j + 1)) {
1140
+ for (let [j, n] = [close, closeLen]; j >= 0; [j, n] = closeAt(body, j + 1)) {
1132
1141
  if (this.bad.has("<" + j)) continue
1133
1142
  const v = callJSON(body.slice(0, j))
1134
- if (v) return { call: { name: String(v.name), arguments: callArgs(v) }, rest: body.slice(j + CLOSE.length) }
1143
+ if (v) return { call: { name: String(v.name), arguments: callArgs(v) }, rest: body.slice(j + n) }
1135
1144
  this.bad.add("<" + j)
1136
1145
  }
1137
1146
  if (!end) return { wait: true }
@@ -1145,7 +1154,7 @@ class TextTools {
1145
1154
  const region = close >= 0 ? body.slice(0, close) : body
1146
1155
  const w = glmCall(region, this.tools)
1147
1156
  // GLM's call, cut short at the end, is still the call
1148
- if (w) return { call: { name: w.name, arguments: callArgs(w) }, rest: close >= 0 ? body.slice(close + CLOSE.length) : "" }
1157
+ if (w) return { call: { name: w.name, arguments: callArgs(w) }, rest: close >= 0 ? body.slice(close + closeLen) : "" }
1149
1158
  if (/^\s*[\w.:-]{1,128}\s*<arg_key>/.test(body)) return { error: close >= 0 ? UNREADABLE : CUT_SHORT }
1150
1159
  return { text: true }
1151
1160
  }
@@ -1155,9 +1164,18 @@ const CUT_SHORT = "the model's tool call, written as text, was cut short; it is
1155
1164
  const UNREADABLE = "the model's tool call, written as text, can't be read; it is not sent on as the answer"
1156
1165
 
1157
1166
  // an opener: <tool_call>, one with attributes (<tool_call name="bash">),
1158
- // or <tool_call right before its JSON; and one still being written
1159
- const OPEN_AT = /<tool_call(?:(\s[^<>{}]*)?>|(?=\s*\{))/
1160
- const OPEN_HOLD = /<tool_call(?:\s[^<>{}\n]{0,200})?$/
1167
+ // or <tool_call right before its JSON; and one still being written. GLM
1168
+ // also writes it without the underscore, <toolcall> (plugins#38)
1169
+ const OPEN_AT = /<tool_?call(?:(\s[^<>{}]*)?>|(?=\s*\{))/
1170
+ const OPEN_HOLD = /<tool_?call(?:\s[^<>{}\n]{0,200})?$/
1171
+ // closeAt is where the next close is from i, </tool_call> or </toolcall>,
1172
+ // and its length: [-1, 0] when there is none
1173
+ function closeAt(s, i = 0) {
1174
+ const re = /<\/tool_?call>/g
1175
+ re.lastIndex = i
1176
+ const m = re.exec(s)
1177
+ return m ? [m.index, m[0].length] : [-1, 0]
1178
+ }
1161
1179
  // DeepSeek's half-and-half call: {"name":"x"} (or its brace or > left
1162
1180
  // out), then <parameter name="k">v</parameter> or Qwen's <parameter=k>
1163
1181
  const PARAM_HEAD = /^\s*\{\s*"name"\s*:\s*"([^"]+)"\s*\}?\s*>?\s*(?=<parameter\b)/
@@ -1303,7 +1321,7 @@ const GLM_ARG = /<arg_key>\s*([\s\S]*?)\s*<\/arg_key>\s*<arg_value>([\s\S]*?)<\/
1303
1321
  // a value that isn't a string. A name alone is a call only to a tool the
1304
1322
  // request has.
1305
1323
  function glmCall(s, tools = []) {
1306
- s = String(s ?? "").replace(/<\/tool_call>\s*$/, "")
1324
+ s = String(s ?? "").replace(/<\/tool_?call>\s*$/, "")
1307
1325
  const k = s.indexOf("<arg_key>")
1308
1326
  const name = (k < 0 ? s : s.slice(0, k)).trim()
1309
1327
  if (!/^[\w.:-]{1,128}$/.test(name)) return null
@@ -1338,6 +1356,31 @@ function toolNamed(name, tools) {
1338
1356
  return names.find((n) => norm(n) === norm(name)) ?? name
1339
1357
  }
1340
1358
 
1359
+ // unwrapped is a call's arguments with the layer a model put around them
1360
+ // taken off: asked for {"name":…,"arguments":{…}}, some write the
1361
+ // arguments as {"arguments":{…}} again, natively or in the block, and the
1362
+ // agent got {"arguments":{"content":…,"path":…}} for its write (bee2an on
1363
+ // X). A lone arguments, input, parameters or args key holding an object is
1364
+ // that layer when the tool has no parameter of that name.
1365
+ const WRAPS = ["arguments", "input", "parameters", "args"]
1366
+ function unwrapped(args, name, tools) {
1367
+ const t = tools.map((x) => x.function ?? x).find((f) => f?.name === name)
1368
+ if (!t) return args
1369
+ const props = t.parameters?.properties ?? {}
1370
+ let v = looseJSON(args)
1371
+ let changed = false
1372
+ for (let k = 0; k < 3 && v && typeof v === "object" && !Array.isArray(v); k++) {
1373
+ const keys = Object.keys(v)
1374
+ if (keys.length !== 1 || !WRAPS.includes(keys[0]) || keys[0] in props) break
1375
+ let inner = v[keys[0]]
1376
+ if (typeof inner === "string") inner = looseJSON(inner)
1377
+ if (!inner || typeof inner !== "object" || Array.isArray(inner)) break
1378
+ v = inner
1379
+ changed = true
1380
+ }
1381
+ return changed ? JSON.stringify(v) : args
1382
+ }
1383
+
1341
1384
  // DeepSeek's tags, with its full-width bar or a plain one
1342
1385
  const DSML = String.raw`<\s*\/?\s*[||][\s||]*DSML[\s||]*`
1343
1386
  const DSML_AT = new RegExp(DSML)
@@ -1348,9 +1391,9 @@ const DSML_CLOSE = new RegExp(String.raw`<\s*\/\s*[||][\s||]*DSML[\s||]*in
1348
1391
  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")
1349
1392
  // what is left after a call: blanks, our close, DeepSeek's tags but the
1350
1393
  // next call's
1351
- const LEFTOVER = new RegExp(String.raw`^(?:\s+|<\/tool_calls?>|<tool_calls>|` + DSML + String.raw`(?![\s||]*invoke\b)[^<>]*>)+`)
1394
+ const LEFTOVER = new RegExp(String.raw`^(?:\s+|<\/tool_?calls?>|<tool_?calls>|` + DSML + String.raw`(?![\s||]*invoke\b)[^<>]*>)+`)
1352
1395
  // what text may be the start of, so held back
1353
- const MARKS = [OPEN, CLOSE, "<|DSML|", "<|DSML|", "</|DSML|", "</|DSML|", '{"reasoning_content"']
1396
+ const MARKS = [OPEN, CLOSE, "<toolcall>", "</toolcall>", "<|DSML|", "<|DSML|", "</|DSML|", "</|DSML|", '{"reasoning_content"']
1354
1397
  // reasoning a model wrote into its text as JSON
1355
1398
  const REASON_AT = /\{\s*"reasoning_content"\s*:/
1356
1399
  const MARK_MAX = Math.max(...MARKS.map((m) => m.length))
@@ -1569,7 +1612,10 @@ async function* parts(events, tools = []) {
1569
1612
  const tt = new TextTools(tools)
1570
1613
  const nc = new NativeCalls()
1571
1614
  const th = new Thoughts()
1572
- const call = (c) => ({ call: { ...c, name: toolNamed(c.name, tools) } })
1615
+ const call = (c) => {
1616
+ const name = toolNamed(c.name, tools)
1617
+ return { call: { ...c, name, arguments: unwrapped(c.arguments, name, tools) } }
1618
+ }
1573
1619
  let finish = ""
1574
1620
  for await (const { event, data } of events) {
1575
1621
  const name = eventName(event)
@@ -2301,4 +2347,4 @@ const makePlugin = (site) => async ({ client }) => {
2301
2347
  export const TraeCNAuthPlugin = makePlugin(SITES["trae-cn"])
2302
2348
  export const TraeGlobalAuthPlugin = makePlugin(SITES["trae-global"])
2303
2349
 
2304
- export const _internal = { HOSTS: SITES["trae-cn"].hosts, MODELS: SITES["trae-cn"].models, TextTools, NativeCalls, looseJSON, glmCall, toolNamed, traeMessages, chatBody, credits, dollarUsageOf, whenOf, newDevice, proofOf, SITES, SESSION, conversationID }
2350
+ export const _internal = { HOSTS: SITES["trae-cn"].hosts, MODELS: SITES["trae-cn"].models, TextTools, NativeCalls, looseJSON, glmCall, toolNamed, unwrapped, traeMessages, chatBody, credits, dollarUsageOf, whenOf, newDevice, proofOf, SITES, SESSION, conversationID }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@magpie-community/opencode-trae-auth",
3
- "version": "0.2.7",
3
+ "version": "0.2.9",
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",