@magpie-community/opencode-trae-auth 0.2.3 → 0.2.5
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 +14 -1
- package/index.mjs +278 -26
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -96,6 +96,14 @@ agent's `POST https://trae-api-cn.mchost.guru/api/agent/v3/llm_utils_chat`:
|
|
|
96
96
|
<arg_value>v</arg_value></tool_call>` (a string kept as written, other
|
|
97
97
|
types read as JSON), and sometimes a whole call into a native call's
|
|
98
98
|
name; both are read as the call.
|
|
99
|
+
- A block is read as models get it wrong too: every quote escaped
|
|
100
|
+
(`{\"name\":…}`), quotes or backslashes in a string left unescaped, an
|
|
101
|
+
opener with the name as an attribute (`<tool_call name="bash">` with
|
|
102
|
+
`<parameter name="k">v</parameter>`), or JSON that is whole when the
|
|
103
|
+
stream ends without `</tool_call>`. A block that still can't be read,
|
|
104
|
+
or is cut short, is never sent on as the answer: the reply ends with an
|
|
105
|
+
error (a 502 without a stream), so the gateway can try again
|
|
106
|
+
(plugins#38). A `<tool_call>` in backticks or a code fence is text.
|
|
99
107
|
- Calls from either source become OpenAI `tool_calls`. A name matching a
|
|
100
108
|
requested tool but for case or punctuation (`Read`) goes on as the
|
|
101
109
|
request's (`read`); any other goes on as written, so the agent can say
|
|
@@ -122,7 +130,12 @@ it, as Max mode does.
|
|
|
122
130
|
|
|
123
131
|
Reasoning: the models reason, but Trae's request takes no effort or
|
|
124
132
|
thinking level, so the plugin lists no variants and a level an agent
|
|
125
|
-
picks does nothing.
|
|
133
|
+
picks does nothing. Every model it lists says so in its `capabilities`
|
|
134
|
+
(`reasoning: true`, with `toolcall`, `temperature`, `attachment`, the
|
|
135
|
+
input and output modalities and an empty `variants`), the fallback
|
|
136
|
+
list's own capabilities kept where it has an entry: magpie reads
|
|
137
|
+
`capabilities.reasoning` alone for a model a plugin lists, and a model
|
|
138
|
+
without it is shown as one that doesn't reason (0.2.4).
|
|
126
139
|
|
|
127
140
|
When no
|
|
128
141
|
list can be read, the plugin uses the realm's own fallback list. Trae CN's
|
package/index.mjs
CHANGED
|
@@ -889,6 +889,8 @@ class TextTools {
|
|
|
889
889
|
this.buf = ""
|
|
890
890
|
this.after = false
|
|
891
891
|
this.tools = tools
|
|
892
|
+
this.sent = "" // the text given out so far, to tell a block in code from a call
|
|
893
|
+
this.bad = new Set() // where the waiting block's JSON was tried and not read, not tried again
|
|
892
894
|
}
|
|
893
895
|
push(s, end = false) {
|
|
894
896
|
this.buf += s
|
|
@@ -907,7 +909,8 @@ class TextTools {
|
|
|
907
909
|
}
|
|
908
910
|
this.after = false
|
|
909
911
|
}
|
|
910
|
-
const
|
|
912
|
+
const om = this.buf.match(OPEN_AT)
|
|
913
|
+
const i = om ? om.index : -1
|
|
911
914
|
const d = this.buf.search(DSML_AT)
|
|
912
915
|
const r = this.buf.search(REASON_AT)
|
|
913
916
|
if (r >= 0 && (i < 0 || r < i) && (d < 0 || r < d)) {
|
|
@@ -965,53 +968,271 @@ class TextTools {
|
|
|
965
968
|
this.after = true
|
|
966
969
|
continue
|
|
967
970
|
}
|
|
968
|
-
const
|
|
969
|
-
const
|
|
970
|
-
if (
|
|
971
|
+
const head = this.buf.slice(0, i)
|
|
972
|
+
const body = this.buf.slice(i + om[0].length)
|
|
973
|
+
if (quoted(this.sent + text + head)) {
|
|
974
|
+
// a <tool_call> in backticks or a code fence is prose about one
|
|
975
|
+
text += head + om[0]
|
|
976
|
+
this.buf = body
|
|
977
|
+
continue
|
|
978
|
+
}
|
|
979
|
+
const named = om[1]?.match(/\bname\s*=\s*"([^"]+)"/)?.[1]
|
|
980
|
+
const inner = body.trimStart()
|
|
981
|
+
if (!named && (inner.startsWith("<") || (end && !inner))) {
|
|
971
982
|
// no JSON in it: DeepSeek's own invoke, or only the closes of one
|
|
972
983
|
// (magpie#823 again); the opener is dropped and what follows read
|
|
973
984
|
// as it is
|
|
974
|
-
text +=
|
|
985
|
+
text += head
|
|
975
986
|
this.buf = inner
|
|
976
987
|
this.after = true
|
|
977
988
|
continue
|
|
978
989
|
}
|
|
979
|
-
const
|
|
980
|
-
if (
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
990
|
+
const b = this.block(body, named, end)
|
|
991
|
+
if (b.wait) break
|
|
992
|
+
this.bad.clear()
|
|
993
|
+
if (b.text) {
|
|
994
|
+
// not a call: the opener is text, and what follows read on
|
|
995
|
+
text += head + om[0]
|
|
996
|
+
this.buf = body
|
|
985
997
|
continue
|
|
986
998
|
}
|
|
987
|
-
|
|
988
|
-
if (
|
|
989
|
-
//
|
|
990
|
-
|
|
991
|
-
|
|
999
|
+
text += head
|
|
1000
|
+
if (b.error) {
|
|
1001
|
+
// a call that can't be read is not the answer (plugins#38): the
|
|
1002
|
+
// turn ends as a failure, so the gateway can try it again
|
|
1003
|
+
this.buf = ""
|
|
1004
|
+
this.sent += text
|
|
1005
|
+
return { text, calls, reasoning, error: b.error }
|
|
992
1006
|
}
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
this.
|
|
996
|
-
const w = looseJSON(raw) ?? glmCall(raw, this.tools) ?? {}
|
|
997
|
-
if (w.name) {
|
|
998
|
-
calls.push({ name: String(w.name), arguments: callArgs(w) })
|
|
999
|
-
this.after = true
|
|
1000
|
-
} else text += OPEN + raw + CLOSE
|
|
1007
|
+
calls.push(b.call)
|
|
1008
|
+
this.buf = b.rest
|
|
1009
|
+
this.after = true
|
|
1001
1010
|
}
|
|
1002
1011
|
if (end) {
|
|
1003
1012
|
text += this.buf
|
|
1004
1013
|
this.buf = ""
|
|
1005
1014
|
} else {
|
|
1006
1015
|
// keep back an opened block, or what may be the start of one
|
|
1007
|
-
const at = [this.buf.
|
|
1016
|
+
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)
|
|
1008
1017
|
let keep = at.length ? this.buf.length - Math.min(...at) : 0
|
|
1009
1018
|
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 }
|
|
1010
1019
|
text += this.buf.slice(0, this.buf.length - keep)
|
|
1011
1020
|
this.buf = this.buf.slice(this.buf.length - keep)
|
|
1012
1021
|
}
|
|
1022
|
+
this.sent += text
|
|
1013
1023
|
return { text, calls, reasoning }
|
|
1014
1024
|
}
|
|
1025
|
+
|
|
1026
|
+
// block reads what follows a <tool_call> opener: {call, rest} for a
|
|
1027
|
+
// call, {wait} while it may still become one, {text} when it is no call
|
|
1028
|
+
// at all, {error} for one that can't be read: cut short, or not JSON
|
|
1029
|
+
// even as models get it wrong (plugins#38). named is the name an opener
|
|
1030
|
+
// gave as <tool_call name="…">.
|
|
1031
|
+
block(body, named, end) {
|
|
1032
|
+
if (!end && !body.trim()) return { wait: true }
|
|
1033
|
+
const close = body.indexOf(CLOSE)
|
|
1034
|
+
if (named) {
|
|
1035
|
+
if (close < 0 && !end) return { wait: true }
|
|
1036
|
+
const region = close >= 0 ? body.slice(0, close) : body
|
|
1037
|
+
const rest = close >= 0 ? body.slice(close + CLOSE.length) : ""
|
|
1038
|
+
if (region.trimStart().startsWith("{")) {
|
|
1039
|
+
const v = repairedJSON(region.trim())
|
|
1040
|
+
if (!v) return { error: close >= 0 ? UNREADABLE : CUT_SHORT }
|
|
1041
|
+
return { call: v.name ? { name: String(v.name), arguments: callArgs(v) } : { name: named, arguments: JSON.stringify(v) }, rest }
|
|
1042
|
+
}
|
|
1043
|
+
if (region.includes("<arg_key>")) {
|
|
1044
|
+
const w = glmCall(named + region, this.tools)
|
|
1045
|
+
return w ? { call: { name: w.name, arguments: callArgs(w) }, rest } : { error: close >= 0 ? UNREADABLE : CUT_SHORT }
|
|
1046
|
+
}
|
|
1047
|
+
const p = paramArgs(region, named, this.tools)
|
|
1048
|
+
// cut short at the end: whole only when each parameter was closed
|
|
1049
|
+
if (close < 0 && !p.closed) return { error: CUT_SHORT }
|
|
1050
|
+
return { call: { name: named, arguments: JSON.stringify(p.args) }, rest }
|
|
1051
|
+
}
|
|
1052
|
+
if (body.trimStart().startsWith("{")) {
|
|
1053
|
+
const ph = body.match(PARAM_HEAD)
|
|
1054
|
+
if (ph) {
|
|
1055
|
+
// DeepSeek's half-and-half: the name as JSON, then its parameters
|
|
1056
|
+
// (magpie#917)
|
|
1057
|
+
if (close < 0 && !end) return { wait: true }
|
|
1058
|
+
const region = body.slice(ph[0].length, close >= 0 ? close : body.length)
|
|
1059
|
+
const p = paramArgs(region, ph[1], this.tools)
|
|
1060
|
+
if (close < 0 && !p.closed) return { error: CUT_SHORT }
|
|
1061
|
+
return { call: { name: ph[1], arguments: JSON.stringify(p.args) }, rest: close >= 0 ? body.slice(close + CLOSE.length) : "" }
|
|
1062
|
+
}
|
|
1063
|
+
const o = objectEnd(body, 0)
|
|
1064
|
+
if (o > 0 && !this.bad.has(":" + o)) {
|
|
1065
|
+
const v = callJSON(body.slice(0, o))
|
|
1066
|
+
// a name alone may have its parameters still to come
|
|
1067
|
+
if (v && !end && Object.keys(v).length === 1 && "<parameter".startsWith(body.slice(o).replace(/^\s*>?\s*/, ""))) return { wait: true }
|
|
1068
|
+
if (v) return { call: { name: String(v.name), arguments: callArgs(v) }, rest: body.slice(o) }
|
|
1069
|
+
this.bad.add(":" + o)
|
|
1070
|
+
}
|
|
1071
|
+
// not whole, or not JSON as it stands: read up to a close (a file's
|
|
1072
|
+
// content may hold one), or at the end all of it
|
|
1073
|
+
for (let j = close; j >= 0; j = body.indexOf(CLOSE, j + 1)) {
|
|
1074
|
+
if (this.bad.has("<" + j)) continue
|
|
1075
|
+
const v = callJSON(body.slice(0, j))
|
|
1076
|
+
if (v) return { call: { name: String(v.name), arguments: callArgs(v) }, rest: body.slice(j + CLOSE.length) }
|
|
1077
|
+
this.bad.add("<" + j)
|
|
1078
|
+
}
|
|
1079
|
+
if (!end) return { wait: true }
|
|
1080
|
+
const v = callJSON(body)
|
|
1081
|
+
if (v) return { call: { name: String(v.name), arguments: callArgs(v) }, rest: "" }
|
|
1082
|
+
return { error: close >= 0 ? UNREADABLE : CUT_SHORT }
|
|
1083
|
+
}
|
|
1084
|
+
// GLM's own (a name, then <arg_key>/<arg_value> pairs), or text
|
|
1085
|
+
if (!/^\s*[\w.:-]{1,128}\s*(?:<|$)/.test(body)) return { text: true }
|
|
1086
|
+
if (close < 0 && !end) return { wait: true }
|
|
1087
|
+
const region = close >= 0 ? body.slice(0, close) : body
|
|
1088
|
+
const w = glmCall(region, this.tools)
|
|
1089
|
+
// GLM's call, cut short at the end, is still the call
|
|
1090
|
+
if (w) return { call: { name: w.name, arguments: callArgs(w) }, rest: close >= 0 ? body.slice(close + CLOSE.length) : "" }
|
|
1091
|
+
if (/^\s*[\w.:-]{1,128}\s*<arg_key>/.test(body)) return { error: close >= 0 ? UNREADABLE : CUT_SHORT }
|
|
1092
|
+
return { text: true }
|
|
1093
|
+
}
|
|
1094
|
+
}
|
|
1095
|
+
|
|
1096
|
+
const CUT_SHORT = "the model's tool call, written as text, was cut short; it is not sent on as the answer"
|
|
1097
|
+
const UNREADABLE = "the model's tool call, written as text, can't be read; it is not sent on as the answer"
|
|
1098
|
+
|
|
1099
|
+
// an opener: <tool_call>, one with attributes (<tool_call name="bash">),
|
|
1100
|
+
// or <tool_call right before its JSON; and one still being written
|
|
1101
|
+
const OPEN_AT = /<tool_call(?:(\s[^<>{}]*)?>|(?=\s*\{))/
|
|
1102
|
+
const OPEN_HOLD = /<tool_call(?:\s[^<>{}\n]{0,200})?$/
|
|
1103
|
+
// DeepSeek's half-and-half call: {"name":"x"} (or its brace or > left
|
|
1104
|
+
// out), then <parameter name="k">v</parameter> or Qwen's <parameter=k>
|
|
1105
|
+
const PARAM_HEAD = /^\s*\{\s*"name"\s*:\s*"([^"]+)"\s*\}?\s*>?\s*(?=<parameter\b)/
|
|
1106
|
+
const PARAM_OPEN = /<parameter(?:\s+name\s*=\s*"([^"]+)"|=([^\s>]+))\s*>/g
|
|
1107
|
+
|
|
1108
|
+
// quoted says text ends inside a code fence or inline code: what comes
|
|
1109
|
+
// next is shown, not done
|
|
1110
|
+
function quoted(text) {
|
|
1111
|
+
if ((text.match(/```/g)?.length ?? 0) % 2) return true
|
|
1112
|
+
const line = text.slice(text.lastIndexOf("\n") + 1).replace(/```/g, "")
|
|
1113
|
+
return (line.match(/`/g)?.length ?? 0) % 2 === 1
|
|
1114
|
+
}
|
|
1115
|
+
|
|
1116
|
+
// paramArgs reads <parameter name="k">v</parameter> (or <parameter=k>)
|
|
1117
|
+
// arguments; a value runs to its close, the next parameter or the end. A
|
|
1118
|
+
// value is its text where the tool's schema says string (or it isn't
|
|
1119
|
+
// JSON), else its JSON. closed says each one had its close.
|
|
1120
|
+
function paramArgs(s, name, tools = []) {
|
|
1121
|
+
const props = (tools.find((t) => (t.function ?? t)?.name === name)?.function ?? {}).parameters?.properties ?? {}
|
|
1122
|
+
const opens = [...s.matchAll(PARAM_OPEN)]
|
|
1123
|
+
const args = {}
|
|
1124
|
+
let closed = opens.length > 0
|
|
1125
|
+
opens.forEach((m, k) => {
|
|
1126
|
+
const from = m.index + m[0].length
|
|
1127
|
+
const to = k + 1 < opens.length ? opens[k + 1].index : s.length
|
|
1128
|
+
let v = s.slice(from, to)
|
|
1129
|
+
const c = v.indexOf("</parameter>")
|
|
1130
|
+
if (c >= 0) v = v.slice(0, c)
|
|
1131
|
+
else closed = false
|
|
1132
|
+
v = v.replace(/^\r?\n|\r?\n$/g, "")
|
|
1133
|
+
const key = m[1] ?? m[2]
|
|
1134
|
+
if (props[key]?.type === "string") {
|
|
1135
|
+
args[key] = v
|
|
1136
|
+
return
|
|
1137
|
+
}
|
|
1138
|
+
try {
|
|
1139
|
+
args[key] = JSON.parse(v.trim())
|
|
1140
|
+
} catch {
|
|
1141
|
+
args[key] = v
|
|
1142
|
+
}
|
|
1143
|
+
})
|
|
1144
|
+
return { args, closed }
|
|
1145
|
+
}
|
|
1146
|
+
|
|
1147
|
+
// callJSON is a block's call ({name, …}) as a model may get its JSON
|
|
1148
|
+
// wrong, null when it isn't one even so
|
|
1149
|
+
function callJSON(s) {
|
|
1150
|
+
const v = repairedJSON(s)
|
|
1151
|
+
return v && typeof v.name === "string" && v.name ? v : null
|
|
1152
|
+
}
|
|
1153
|
+
|
|
1154
|
+
// repairedJSON reads a JSON object as looseJSON does, and else as models
|
|
1155
|
+
// get it wrong (plugins#38): every quote escaped ({\"name\":…}), a
|
|
1156
|
+
// backslash that escapes nothing JSON knows (\_ \| C:\Users in a file's
|
|
1157
|
+
// content), or quotes inside a string left unescaped ("官方" in a write's
|
|
1158
|
+
// content)
|
|
1159
|
+
function repairedJSON(s) {
|
|
1160
|
+
const v = looseJSON(s)
|
|
1161
|
+
if (v) return v
|
|
1162
|
+
const t = escapeRaw(s)
|
|
1163
|
+
const tries = []
|
|
1164
|
+
if (/^\s*\{\s*\\"/.test(s)) {
|
|
1165
|
+
try {
|
|
1166
|
+
const u = JSON.parse('"' + s.trim().replace(/[\n\r\t]/g, (c) => (c === "\n" ? "\\n" : c === "\r" ? "\\r" : "\\t")) + '"')
|
|
1167
|
+
tries.push(u, escapeRaw(u))
|
|
1168
|
+
} catch {}
|
|
1169
|
+
tries.push(s.replace(/\\"/g, '"'))
|
|
1170
|
+
}
|
|
1171
|
+
tries.push(fixEscapes(t), quoteFix(fixEscapes(t)))
|
|
1172
|
+
for (const x of tries) {
|
|
1173
|
+
try {
|
|
1174
|
+
const v = JSON.parse(x)
|
|
1175
|
+
if (v && typeof v === "object" && !Array.isArray(v)) return v
|
|
1176
|
+
} catch {}
|
|
1177
|
+
}
|
|
1178
|
+
return null
|
|
1179
|
+
}
|
|
1180
|
+
|
|
1181
|
+
// fixEscapes makes a backslash that escapes nothing JSON knows a backslash
|
|
1182
|
+
// of its own (\' is the quote it meant)
|
|
1183
|
+
function fixEscapes(s) {
|
|
1184
|
+
return s.replace(/\\(u[0-9a-fA-F]{4}|["\\/bfnrt])|\\'|\\/g, (m, ok) => (ok ? m : m === "\\'" ? "'" : "\\\\"))
|
|
1185
|
+
}
|
|
1186
|
+
|
|
1187
|
+
// quoteFix escapes the quotes inside strings a model left unescaped: a
|
|
1188
|
+
// quote ends a key only before its colon, and a value only before what
|
|
1189
|
+
// may follow the last of one (closing braces or brackets, then the end or
|
|
1190
|
+
// a comma and the next key or value)
|
|
1191
|
+
function quoteFix(s) {
|
|
1192
|
+
let out = ""
|
|
1193
|
+
const stack = []
|
|
1194
|
+
let key = false
|
|
1195
|
+
for (let i = 0; i < s.length; ) {
|
|
1196
|
+
const ch = s[i]
|
|
1197
|
+
if (ch !== '"') {
|
|
1198
|
+
if (ch === "{") {
|
|
1199
|
+
stack.push("{")
|
|
1200
|
+
key = true
|
|
1201
|
+
} else if (ch === "[") stack.push("[")
|
|
1202
|
+
else if (ch === "}" || ch === "]") stack.pop()
|
|
1203
|
+
else if (ch === ",") key = stack.at(-1) === "{"
|
|
1204
|
+
else if (ch === ":") key = false
|
|
1205
|
+
out += ch
|
|
1206
|
+
i++
|
|
1207
|
+
continue
|
|
1208
|
+
}
|
|
1209
|
+
const isKey = key && stack.at(-1) === "{"
|
|
1210
|
+
const inArray = stack.at(-1) === "["
|
|
1211
|
+
out += ch
|
|
1212
|
+
i++
|
|
1213
|
+
for (; i < s.length; i++) {
|
|
1214
|
+
const c = s[i]
|
|
1215
|
+
if (c === "\\") {
|
|
1216
|
+
out += c + (s[i + 1] ?? "")
|
|
1217
|
+
i++
|
|
1218
|
+
continue
|
|
1219
|
+
}
|
|
1220
|
+
if (c !== '"') {
|
|
1221
|
+
out += c
|
|
1222
|
+
continue
|
|
1223
|
+
}
|
|
1224
|
+
const after = s.slice(i + 1)
|
|
1225
|
+
const ends = isKey ? /^\s*:/.test(after) : (inArray ? /^\s*(?:[}\]]\s*)*(?:$|,\s*["{[\d\-tfn])/ : /^\s*(?:[}\]]\s*)*(?:$|,\s*")/).test(after)
|
|
1226
|
+
if (ends) {
|
|
1227
|
+
out += c
|
|
1228
|
+
i++
|
|
1229
|
+
break
|
|
1230
|
+
}
|
|
1231
|
+
out += '\\"'
|
|
1232
|
+
}
|
|
1233
|
+
key = false
|
|
1234
|
+
}
|
|
1235
|
+
return out
|
|
1015
1236
|
}
|
|
1016
1237
|
|
|
1017
1238
|
// GLM's call template: the tool's name, then each argument as
|
|
@@ -1328,6 +1549,10 @@ async function* parts(events, tools = []) {
|
|
|
1328
1549
|
if (t) yield { reasoning: t }
|
|
1329
1550
|
if (r.text) yield { text: r.text }
|
|
1330
1551
|
for (const c of r.calls) yield call({ ...c, id: callId() })
|
|
1552
|
+
if (r.error) {
|
|
1553
|
+
yield { error: r.error, code: null }
|
|
1554
|
+
return
|
|
1555
|
+
}
|
|
1331
1556
|
}
|
|
1332
1557
|
for (const tc of Array.isArray(d.tool_calls) ? d.tool_calls : []) nc.push(tc)
|
|
1333
1558
|
const u = tokensOf(d.usage)
|
|
@@ -1339,6 +1564,10 @@ async function* parts(events, tools = []) {
|
|
|
1339
1564
|
if (t) yield { reasoning: t }
|
|
1340
1565
|
if (r.text) yield { text: r.text }
|
|
1341
1566
|
for (const c of r.calls) yield call({ ...c, id: callId() })
|
|
1567
|
+
if (r.error) {
|
|
1568
|
+
yield { error: r.error, code: null }
|
|
1569
|
+
return
|
|
1570
|
+
}
|
|
1342
1571
|
for (const c of nc.take(tools)) yield call(c)
|
|
1343
1572
|
if (finish) yield { finish }
|
|
1344
1573
|
}
|
|
@@ -1804,6 +2033,13 @@ const makePlugin = (site) => async ({ client }) => {
|
|
|
1804
2033
|
return [...out.values()].map((x) => x.m)
|
|
1805
2034
|
}
|
|
1806
2035
|
|
|
2036
|
+
// modelOf is a model of the account's list as OpenCode's provider
|
|
2037
|
+
// carries one. A model of the site's fallback list has capabilities
|
|
2038
|
+
// already (magpie's host spreads the config's into them); one the list
|
|
2039
|
+
// doesn't have gets them from MODEL's flat fields, so that magpie reads
|
|
2040
|
+
// it as a model that reasons rather than one that doesn't (its host
|
|
2041
|
+
// reads capabilities.reasoning alone, host.js). The fallback list stays
|
|
2042
|
+
// the authority where it has an entry.
|
|
1807
2043
|
const modelOf = (provider, m) => {
|
|
1808
2044
|
const id = String(m.config_name)
|
|
1809
2045
|
const was = provider.models?.[id] ?? {}
|
|
@@ -1811,7 +2047,23 @@ const makePlugin = (site) => async ({ client }) => {
|
|
|
1811
2047
|
const ctx = Number(m.context_window_tokens?.dev ?? m.context_window_size?.max?.[0] ?? m.context_window_size?.max ?? m.context_window_tokens?.max ?? m.prompt_max_tokens) || was.limit?.context || MODEL.limit.context
|
|
1812
2048
|
const out = Number(devModel(m)?.max_tokens) || was.limit?.output || 0
|
|
1813
2049
|
const name = m.display_config?.display_name || m.display_name || m.display_model_name || was.name || id
|
|
1814
|
-
|
|
2050
|
+
const input = (k) => (MODEL.modalities?.input ?? []).includes(k)
|
|
2051
|
+
return {
|
|
2052
|
+
...MODEL, ...was, id, providerID: site.id, name: String(name), limit: { context: ctx, output: out },
|
|
2053
|
+
api: was.api ?? { id, url: site.hosts.api, npm: "@ai-sdk/openai-compatible" },
|
|
2054
|
+
capabilities: {
|
|
2055
|
+
...(was.capabilities ?? {}),
|
|
2056
|
+
reasoning: was.capabilities?.reasoning ?? MODEL.reasoning,
|
|
2057
|
+
temperature: was.capabilities?.temperature ?? MODEL.temperature,
|
|
2058
|
+
attachment: was.capabilities?.attachment ?? MODEL.attachment,
|
|
2059
|
+
toolcall: was.capabilities?.toolcall ?? MODEL.tool_call,
|
|
2060
|
+
input: was.capabilities?.input ?? { text: true, image: input("image"), audio: input("audio"), video: input("video"), pdf: input("pdf") },
|
|
2061
|
+
output: was.capabilities?.output ?? { text: true, image: false, audio: false, video: false, pdf: false },
|
|
2062
|
+
},
|
|
2063
|
+
// Trae's request takes no effort or thinking level, so a model has
|
|
2064
|
+
// none to pick from (see the README's Reasoning)
|
|
2065
|
+
variants: was.variants ?? {},
|
|
2066
|
+
}
|
|
1815
2067
|
}
|
|
1816
2068
|
|
|
1817
2069
|
return {
|
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.5",
|
|
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",
|