@magpie-community/opencode-trae-auth 0.2.10 → 0.2.12

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 +4 -0
  2. package/index.mjs +82 -6
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -107,6 +107,10 @@ agent's `POST https://trae-api-cn.mchost.guru/api/agent/v3/llm_utils_chat`:
107
107
  Text under `message`, `delta` or an event with no name is the answer's
108
108
  too.
109
109
  - `token_usage`, `done` and `error` mark usage, the end and failures.
110
+ What `token_usage` says the prompt cache read and wrote
111
+ (`cache_read_input_tokens`, `cache_creation_input_tokens`) goes on under
112
+ those names, and `reasoning_tokens` as
113
+ `completion_tokens_details.reasoning_tokens`.
110
114
  `done`'s `finish_reason` `length`/`max_tokens` is OpenAI's `length`.
111
115
  - Queue events are ignored.
112
116
  - A `{"reasoning_content": …}` object written into the text is reasoning,
package/index.mjs CHANGED
@@ -992,13 +992,35 @@ const eventName = (s) =>
992
992
  .toLowerCase()
993
993
  .replace(/^_|_$/g, "")
994
994
 
995
- // usageOf maps Trae's token counts to OpenAI's
995
+ // tokensOf maps Trae's token counts to OpenAI's. Trae's token_usage event
996
+ // (TRAE SOLO CN's ai-agent, TokenUsageEvent: name, prompt_tokens,
997
+ // completion_tokens, total_tokens, reasoning_tokens,
998
+ // cache_creation_input_tokens, cache_read_input_tokens,
999
+ // prompt_tokens_total, completion_tokens_total) says what the prompt cache
1000
+ // read and wrote under Anthropic's names. They go on under those names,
1001
+ // which magpie reads beside prompt_tokens as it reads a relay's in front of
1002
+ // Claude (whether prompt_tokens counts them too is judged there, by
1003
+ // whether it is at least as big); without them every request read as no
1004
+ // cache hit (01huadalang on magpie's Discord: K3, 53 calls, all 0)
996
1005
  function tokensOf(u) {
997
1006
  if (!u || typeof u !== "object") return null
998
- const p = Number(u.prompt_tokens ?? u.input_tokens ?? u.promptTokens ?? u.inputTokens ?? 0) || 0
999
- const c = Number(u.completion_tokens ?? u.output_tokens ?? u.completionTokens ?? u.outputTokens ?? 0) || 0
1007
+ const n = (...v) => Number(v.find((x) => x != null) ?? 0) || 0
1008
+ const p = n(u.prompt_tokens, u.input_tokens, u.promptTokens, u.inputTokens)
1009
+ const c = n(u.completion_tokens, u.output_tokens, u.completionTokens, u.outputTokens)
1000
1010
  if (!p && !c) return null
1001
- return { prompt_tokens: p, completion_tokens: c, total_tokens: Number(u.total_tokens ?? u.totalTokens) || p + c }
1011
+ const out = { prompt_tokens: p, completion_tokens: c, total_tokens: n(u.total_tokens, u.totalTokens) || p + c }
1012
+ const read = n(u.prompt_tokens_details?.cached_tokens)
1013
+ const write = n(u.prompt_tokens_details?.cache_write_tokens)
1014
+ if (read || write) out.prompt_tokens_details = { cached_tokens: read, ...(write ? { cache_write_tokens: write } : {}) }
1015
+ else {
1016
+ const r = n(u.cache_read_input_tokens, u.cacheReadInputTokens)
1017
+ const w = n(u.cache_creation_input_tokens, u.cacheCreationInputTokens)
1018
+ if (r) out.cache_read_input_tokens = r
1019
+ if (w) out.cache_creation_input_tokens = w
1020
+ }
1021
+ const thought = n(u.reasoning_tokens, u.completion_tokens_details?.reasoning_tokens)
1022
+ if (thought) out.completion_tokens_details = { reasoning_tokens: thought }
1023
+ return out
1002
1024
  }
1003
1025
 
1004
1026
  let callSeq = 0
@@ -1022,6 +1044,7 @@ class TextTools {
1022
1044
  this.tools = tools
1023
1045
  this.sent = "" // the text given out so far, to tell a block in code from a call
1024
1046
  this.bad = new Set() // where the waiting block's JSON was tried and not read, not tried again
1047
+ this.mark = 0 // where in sent the text after the last call starts
1025
1048
  }
1026
1049
  push(s, end = false) {
1027
1050
  this.buf += s
@@ -1044,6 +1067,27 @@ class TextTools {
1044
1067
  const i = om ? om.index : -1
1045
1068
  const d = this.buf.search(DSML_AT)
1046
1069
  const r = this.buf.search(REASON_AT)
1070
+ const sm = this.buf.match(STRAY_AT)
1071
+ const s = sm ? sm.index : -1
1072
+ if (s >= 0 && [i, d, r].every((x) => x < 0 || s < x)) {
1073
+ // a close with no call open, with what GLM left before it
1074
+ // (}}</tool_call>, magpie#1945): not the answer, dropped; in code,
1075
+ // shown
1076
+ const head = this.buf.slice(0, s)
1077
+ // only a tail: a close with nothing before it since the last call
1078
+ // (or the start), or with GLM's braces, quotes or > right before
1079
+ // it; a close a sentence names in passing stays
1080
+ const tail = /^[}\]"'>]/.test(sm[0]) || !(this.sent + text + head).slice(this.mark).trim()
1081
+ if (!tail || quoted(this.sent + text + head)) {
1082
+ text += this.buf.slice(0, s + sm[0].length)
1083
+ this.buf = this.buf.slice(s + sm[0].length)
1084
+ continue
1085
+ }
1086
+ text += head
1087
+ this.buf = this.buf.slice(s + sm[0].length)
1088
+ this.after = true
1089
+ continue
1090
+ }
1047
1091
  if (r >= 0 && (i < 0 || r < i) && (d < 0 || r < d)) {
1048
1092
  const o = objectEnd(this.buf, r)
1049
1093
  if (o === -1 && !end) {
@@ -1095,6 +1139,7 @@ class TextTools {
1095
1139
  stop = body.length
1096
1140
  }
1097
1141
  calls.push({ name: inv[1], arguments: dsmlArgs(body.slice(0, stop)) })
1142
+ this.mark = this.sent.length + text.length
1098
1143
  this.buf = body.slice(c && c.index === stop ? stop + c[0].length : stop)
1099
1144
  this.after = true
1100
1145
  continue
@@ -1136,6 +1181,7 @@ class TextTools {
1136
1181
  return { text, calls, reasoning, error: b.error }
1137
1182
  }
1138
1183
  calls.push(b.call)
1184
+ this.mark = this.sent.length + text.length
1139
1185
  this.buf = b.rest
1140
1186
  this.after = true
1141
1187
  }
@@ -1156,6 +1202,9 @@ class TextTools {
1156
1202
  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)
1157
1203
  let keep = at.length ? this.buf.length - Math.min(...at) : 0
1158
1204
  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 }
1205
+ // and braces, brackets, quotes or a > a close may still come after
1206
+ const sh = this.buf.slice(0, this.buf.length - keep).match(STRAY_HOLD)
1207
+ if (sh) keep = this.buf.length - sh.index
1159
1208
  text += this.buf.slice(0, this.buf.length - keep)
1160
1209
  this.buf = this.buf.slice(this.buf.length - keep)
1161
1210
  }
@@ -1205,8 +1254,27 @@ class TextTools {
1205
1254
  const v = callJSON(body.slice(0, o))
1206
1255
  // a name alone may have its parameters still to come
1207
1256
  if (v && !end && Object.keys(v).length === 1 && "<parameter".startsWith(body.slice(o).replace(/^\s*>?\s*/, ""))) return { wait: true }
1208
- if (v) return { call: { name: String(v.name), arguments: callArgs(v) }, rest: body.slice(o) }
1209
- this.bad.add(":" + o)
1257
+ if (v) {
1258
+ // what follows the object up to the close: GLM leaves braces,
1259
+ // brackets, quotes or a > of its own there (}}</tool_call>,
1260
+ // "></tool_call>, magpie#1945), dropped with the close; held
1261
+ // while only those have come
1262
+ const tail = body.slice(o)
1263
+ const junk = tail.match(TAIL_JUNK)[0]
1264
+ const next = tail.slice(junk.length)
1265
+ const call = { name: String(v.name), arguments: callArgs(v) }
1266
+ if (!end && (!next || ["</tool_call>", "</toolcall>"].some((k) => k.startsWith(next)))) return { wait: true }
1267
+ const [c, n] = closeAt(next)
1268
+ if (c === 0) return { call, rest: next.slice(n) }
1269
+ // after blanks alone, or with no close to come: the call, and
1270
+ // what follows read on as before. Other text before the close
1271
+ // right after the object means the object ended early, at a
1272
+ // quote inside a string left unescaped: that cut is wrong (a
1273
+ // heredoc's command cut short, magpie#1945), so it is read up to
1274
+ // the close below, or not at all
1275
+ if (!/\S/.test(junk) && !/^[^\s<]/.test(next)) return { call, rest: tail }
1276
+ if (close < 0 && end) return { call, rest: tail }
1277
+ } else this.bad.add(":" + o)
1210
1278
  }
1211
1279
  // not whole, or not JSON as it stands: read up to a close (a file's
1212
1280
  // content may hold one), or at the end all of it
@@ -1241,6 +1309,14 @@ const UNREADABLE = "the model's tool call, written as text, can't be read; it is
1241
1309
  // also writes it without the underscore, <toolcall> (plugins#38)
1242
1310
  const OPEN_AT = /<tool_?call(?:(\s[^<>{}]*)?>|(?=\s*\{))/
1243
1311
  const OPEN_HOLD = /<tool_?call(?:\s[^<>{}\n]{0,200})?$/
1312
+ // what GLM leaves between a call's JSON and its close: blanks, closing
1313
+ // braces and brackets, quotes, a > (magpie#1945)
1314
+ const TAIL_JUNK = /^[\s}\]"'>]*/
1315
+ // a close with no call open, and that leftover before it: the tail of a
1316
+ // call Trae's service read itself, or of one already read (magpie#1945)
1317
+ const STRAY_AT = /[}\]"'>]*\s*<\/tool_?calls?>/
1318
+ // the end of text that may be such a tail still being written
1319
+ const STRAY_HOLD = /[}\]"'>][\s}\]"'>]*$/
1244
1320
  // closeAt is where the next close is from i, </tool_call> or </toolcall>,
1245
1321
  // and its length: [-1, 0] when there is none
1246
1322
  function closeAt(s, i = 0) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@magpie-community/opencode-trae-auth",
3
- "version": "0.2.10",
3
+ "version": "0.2.12",
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",