@magpie-community/opencode-trae-auth 0.2.22 → 0.2.24

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 +10 -1
  2. package/index.mjs +156 -7
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -134,6 +134,13 @@ agent's `POST https://trae-api-cn.mchost.guru/api/agent/v3/llm_utils_chat`:
134
134
  error (plugins#38). Trae takes no two messages of one role in a row,
135
135
  so an assistant turn's text is folded into the calls turn after it
136
136
  and consecutive calls turns join one `tool_calls` list.
137
+ - For a Kimi (Moonshot) model the tools' schemas are first rewritten for
138
+ Moonshot's "moonshot flavored json schema", which Trae's 4027 "tool call
139
+ failed" quotes when it refuses them: an `enum` or `const` with no `type`
140
+ gets the one its values have, a `$ref` beside a `type` and a `type` beside
141
+ an `anyOf` are written as Kimi holds them (the same rules as kimi-work's).
142
+ The request's own tools are not changed, and a schema Kimi takes as it is
143
+ goes byte for byte. Other models' tools go as they are.
137
144
  - A call with no name is dropped, and the `tool` result answering it
138
145
  with it: Trae requires the name, and an empty `tool_calls` list with a
139
146
  result naming no call is not a turn it takes.
@@ -231,7 +238,9 @@ which levels it takes — its `reasoning_effort_config`, with the options
231
238
  (`light`, `high`, `extra_high`) and the default. The request takes the same
232
239
  names as `reasoning_effort`, so a level an agent picks is sent as Trae
233
240
  spells it: the plugin offers each model's own levels as `variants`
234
- (`low`/`high`/`xhigh` for `light`/`high`/`extra_high`), and sends nothing
241
+ (`low`/`high`/`xhigh` for `light`/`high`/`extra_high`; `medium` and `max`
242
+ keep their names, offered only where the entry names them, as for DeepSeek
243
+ V4.1 Flash's `max` in Trae's app), and sends nothing
235
244
  for a level the model doesn't name, or for a model whose entry names none —
236
245
  Trae then serves it at its own default. An entry naming levels this map
237
246
  can't spell offers none, rather than being sent one Trae would reject. A
package/index.mjs CHANGED
@@ -1126,11 +1126,157 @@ function traeMessages(req, sees = false) {
1126
1126
  })
1127
1127
  }
1128
1128
 
1129
- // nativeTools are the tools as Trae names them: parameters a JSON string
1130
- const nativeTools = (tools) =>
1129
+ // Moonshot (Kimi) validates tools against its own "moonshot flavored json
1130
+ // schema" and refuses the whole request (Trae answers code 4027, "tool call
1131
+ // failed: Invalid request: tools.function.parameters is not a valid moonshot
1132
+ // flavored json schema, details: <At path 'properties.ops.items.properties.op':
1133
+ // type is not defined>") for shapes a JSON Schema allows and agents send: an
1134
+ // enum or const with no type (Claude Code's tools carry them), a $ref beside a
1135
+ // type, a type beside an anyOf. Trae passes the tools to Kimi as they are, so
1136
+ // they are rewritten here for the rules Kimi has been seen to hold, for a
1137
+ // Kimi/Moonshot model only (moonshotModel). This is kimi-work's moonshotSchema,
1138
+ // which carries the reasons for each rule, ported. The tools sent are
1139
+ // a copy: the request's own stay as they were, and a schema Kimi takes as it is
1140
+ // goes byte for byte.
1141
+ const jsIsObj=x=>x!==null&&typeof x==='object'&&!Array.isArray(x);
1142
+ const msMAP_KEYS=['properties','$defs','definitions','dependentSchemas','patternProperties'];
1143
+ const msSUB_KEYS=['items','additionalProperties','contains','propertyNames','not','if','then','else','allOf','anyOf','oneOf','prefixItems','unevaluatedProperties','unevaluatedItems','contentSchema'];
1144
+ const msALL_TYPES=['string','number','integer','boolean','object','array','null'];
1145
+
1146
+ function msEachSchema(node,f) {
1147
+ if(Array.isArray(node)) {for(const v of node)msEachSchema(v,f);return;}
1148
+ if(!jsIsObj(node))return;
1149
+ f(node);
1150
+ for(const k of msMAP_KEYS) if(jsIsObj(node[k])) for(const v of Object.values(node[k])) msEachSchema(v,f);
1151
+ for(const k of msSUB_KEYS) if(k in node) msEachSchema(node[k],f);
1152
+ }
1153
+
1154
+ function msRefTarget(root,ref) {
1155
+ if(typeof ref!=='string'||(ref!=='#'&&!ref.startsWith('#/')))return undefined;
1156
+ let cur=root;
1157
+ for(let tok of ref.split('/').slice(1)) {
1158
+ tok=tok.replace(/~1/g,'/').replace(/~0/g,'~');
1159
+ try{tok=decodeURIComponent(tok);}catch{}
1160
+ if(!jsIsObj(cur)&&!Array.isArray(cur))return undefined;
1161
+ if(!Object.hasOwn(cur,tok))return undefined;
1162
+ cur=cur[tok];
1163
+ }
1164
+ return cur;
1165
+ }
1166
+
1167
+ const msHasRefInside=s=>JSON.stringify(s).includes('"$ref"');
1168
+
1169
+ function msValueType(v) {
1170
+ if(v===null)return 'null';
1171
+ switch(typeof v){
1172
+ case 'string':return 'string';
1173
+ case 'boolean':return 'boolean';
1174
+ case 'number':return Number.isInteger(v)?'integer':'number';
1175
+ }
1176
+ return Array.isArray(v)?'array':'object';
1177
+ }
1178
+
1179
+ // the type an enum of vals is given: the one all non-null values have, with
1180
+ // "null" when a null is among them; undefined when there are none or they
1181
+ // are of more than one kind
1182
+ function msEnumType(vals) {
1183
+ let t,nul=false;
1184
+ for(const v of vals) {
1185
+ const vt=msValueType(v);
1186
+ if(vt==='null'){nul=true;continue;}
1187
+ if(vt==='array'||vt==='object')return undefined;
1188
+ if(t===undefined||t===vt)t=vt;
1189
+ else if((t==='integer'&&vt==='number')||(t==='number'&&vt==='integer'))t='number';
1190
+ else return undefined;
1191
+ }
1192
+ if(t===undefined)return undefined;
1193
+ return nul?[t,'null']:t;
1194
+ }
1195
+
1196
+ // the types s can have, as its type, keywords, values or branches say;
1197
+ // undefined when they don't say
1198
+ function msSchemaTypes(root,s,depth=0) {
1199
+ if(depth>16||!jsIsObj(s))return undefined;
1200
+ if('type' in s)return new Set([].concat(s.type).map(String));
1201
+ if('$ref' in s){const t=msRefTarget(root,s.$ref);return t===undefined?undefined:msSchemaTypes(root,t,depth+1);}
1202
+ if(Array.isArray(s.enum)||'const' in s)return new Set(('const' in s?[s.const]:s.enum).map(msValueType));
1203
+ for(const k of ['oneOf','anyOf','allOf']) {
1204
+ if(!Array.isArray(s[k]))continue;
1205
+ const ts=new Set();
1206
+ for(const b of s[k]) {
1207
+ const bt=msSchemaTypes(root,b,depth+1);
1208
+ if(!bt){if(k!=='allOf')return undefined;continue;}
1209
+ for(const t of bt)ts.add(t);
1210
+ }
1211
+ if(ts.size)return ts;
1212
+ }
1213
+ const has=(...ks)=>ks.some(k=>k in s);
1214
+ if(has('properties','required','additionalProperties','patternProperties','minProperties','maxProperties','propertyNames','dependentRequired','dependentSchemas','unevaluatedProperties'))return new Set(['object']);
1215
+ if(has('items','prefixItems','minItems','maxItems','uniqueItems','contains','unevaluatedItems'))return new Set(['array']);
1216
+ if(has('minLength','maxLength','pattern','format','contentEncoding','contentMediaType'))return new Set(['string']);
1217
+ if(has('minimum','maximum','exclusiveMinimum','exclusiveMaximum','multipleOf'))return new Set(['number']);
1218
+ return undefined;
1219
+ }
1220
+
1221
+ function msTypeFor(root,s) {
1222
+ if(Array.isArray(s.enum))return msEnumType(s.enum);
1223
+ if('const' in s)return msEnumType([s.const]);
1224
+ const ts=msSchemaTypes(root,s);
1225
+ if(!ts||!ts.size)return [...msALL_TYPES];
1226
+ if(ts.has('integer')&&ts.has('number'))ts.delete('integer');
1227
+ const names=msALL_TYPES.filter(t=>ts.has(t));
1228
+ return names.length===1?names[0]:names;
1229
+ }
1230
+
1231
+ // the same object with type written first, as an agent would have
1232
+ const msWithType=(s,type)=>{const rest={...s};delete rest.type;for(const k of Object.keys(s))delete s[k];Object.assign(s,{type},rest);};
1233
+
1234
+ function moonshotSchema(root) {
1235
+ let changed=false;
1236
+ // a $ref beside a type, and a type beside an anyOf
1237
+ msEachSchema(root,s=>{
1238
+ if('$ref' in s&&'type' in s) {
1239
+ const t=msRefTarget(root,s.$ref);
1240
+ if(jsIsObj(t)&&!msHasRefInside(t)) {
1241
+ const own={...s};delete own.$ref;
1242
+ for(const k of Object.keys(s))delete s[k];
1243
+ Object.assign(s,structuredClone(t),own);
1244
+ } else delete s.type;
1245
+ changed=true;
1246
+ }
1247
+ if(Array.isArray(s.anyOf)&&'type' in s){delete s.type;changed=true;}
1248
+ });
1249
+ // an enum or const with no type
1250
+ msEachSchema(root,s=>{
1251
+ if('type' in s||!(Array.isArray(s.enum)||'const' in s))return;
1252
+ const t=msEnumType(Array.isArray(s.enum)?s.enum:[s.const]);
1253
+ if(t!==undefined){msWithType(s,t);changed=true;}
1254
+ });
1255
+ // a $ref's target with no type
1256
+ msEachSchema(root,s=>{
1257
+ if(typeof s.$ref!=='string')return;
1258
+ const t=msRefTarget(root,s.$ref);
1259
+ if(!jsIsObj(t)||'type' in t||'anyOf' in t||'$ref' in t||!Object.keys(t).length)return;
1260
+ const typ=msTypeFor(root,t);
1261
+ if(typ!==undefined){msWithType(t,typ);changed=true;}
1262
+ });
1263
+ return changed;
1264
+ }
1265
+
1266
+
1267
+ const moonshotModel = (...names) => names.some((n) => /kimi|moonshot/i.test(String(n ?? "")))
1268
+
1269
+ // nativeTools are the tools as Trae names them: parameters a JSON string.
1270
+ // moonshot is whether a Kimi model takes them (moonshotSchema)
1271
+ const nativeTools = (tools, moonshot = false) =>
1131
1272
  tools.map((t) => {
1132
1273
  const f = t.function ?? t
1133
- return { type: "function", function: { name: f.name, description: f.description ?? "", parameters: typeof f.parameters === "string" ? f.parameters : JSON.stringify(f.parameters ?? {}) } }
1274
+ let p = f.parameters
1275
+ if (moonshot && jsIsObj(p)) {
1276
+ p = structuredClone(p)
1277
+ moonshotSchema(p)
1278
+ }
1279
+ return { type: "function", function: { name: f.name, description: f.description ?? "", parameters: typeof p === "string" ? p : JSON.stringify(p ?? {}) } }
1134
1280
  })
1135
1281
 
1136
1282
  // SESSION carries the conversation magpie (or OpenCode) names a request by,
@@ -1225,7 +1371,7 @@ function chatBody(req, fn, modelName, most = 0, max = null, session = "", sees =
1225
1371
  }
1226
1372
  if (typeof req.temperature === "number") body.temperature = req.temperature
1227
1373
  if (Array.isArray(req.tools) && req.tools.length && req.tool_choice !== "none") {
1228
- body.tools = nativeTools(req.tools)
1374
+ body.tools = nativeTools(req.tools, moonshotModel(config, modelName, req.model))
1229
1375
  const c = req.tool_choice
1230
1376
  if (c && typeof c === "object") body.tool_choice = c.function?.name || c.name || "auto"
1231
1377
  else if (typeof c === "string") body.tool_choice = c
@@ -2593,13 +2739,16 @@ const MAX = "-max"
2593
2739
  // (light, high, extra_high) and the one it defaults to. The request takes the
2594
2740
  // same names, as reasoning_effort, so a level an agent picks is sent as Trae
2595
2741
  // spells it. OpenCode knows the three as low/high/xhigh, so the map goes both
2596
- // ways.
2742
+ // ways. Trae's client names two more, medium and max (its bundle's
2743
+ // reasoning_effort.{low,medium,high,extra_high,max}), handed out per model:
2744
+ // its app offers DeepSeek V4.1 Flash max (#434), so they are spelled here too,
2745
+ // and still offered only for a model whose entry names them.
2597
2746
  //
2598
2747
  // An entry without the config takes no level: Trae serves it at its own
2599
2748
  // default and nothing is sent. The plugin used to send none for every model,
2600
2749
  // which read as "Trae takes no level" — it takes them, per model, and an
2601
2750
  // entry that names none is the exception rather than the rule.
2602
- const LEVELS = { light: "low", high: "high", extra_high: "xhigh" }
2751
+ const LEVELS = { light: "low", medium: "medium", high: "high", extra_high: "xhigh", max: "max" }
2603
2752
 
2604
2753
  // levelsOf is an entry's levels in OpenCode's names, in Trae's order: [] when
2605
2754
  // the entry names none, null when it names ones this map can't spell (so a
@@ -3393,4 +3542,4 @@ const makePlugin = (site) => async ({ client }) => {
3393
3542
  export const TraeCNAuthPlugin = makePlugin(SITES["trae-cn"])
3394
3543
  export const TraeGlobalAuthPlugin = makePlugin(SITES["trae-global"])
3395
3544
 
3396
- export const _internal = { ownClient, clientStoragePaths, clientDevice, clientDecode, thisMachine, traeHost, accountOf, HOSTS: SITES["trae-cn"].hosts, MODELS: SITES["trae-cn"].models, TextTools, NativeCalls, looseJSON, glmCall, nameCall, toolNamed, unwrapped, traeMessages, chatBody, credits, dollarUsageOf, whenOf, newDevice, ugDevice, proofOf, SITES, SESSION, conversationID, LEVELS, levelsOf, variantsOf, effortOf }
3545
+ export const _internal = { ownClient, clientStoragePaths, clientDevice, clientDecode, thisMachine, traeHost, accountOf, HOSTS: SITES["trae-cn"].hosts, MODELS: SITES["trae-cn"].models, TextTools, NativeCalls, looseJSON, glmCall, nameCall, toolNamed, unwrapped, traeMessages, chatBody, nativeTools, moonshotSchema, credits, dollarUsageOf, whenOf, newDevice, ugDevice, proofOf, SITES, SESSION, conversationID, LEVELS, levelsOf, variantsOf, effortOf }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@magpie-community/opencode-trae-auth",
3
- "version": "0.2.22",
3
+ "version": "0.2.24",
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",