@magpie-community/opencode-trae-auth 0.2.23 → 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 +7 -0
  2. package/index.mjs +151 -5
  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.
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
@@ -3396,4 +3542,4 @@ const makePlugin = (site) => async ({ client }) => {
3396
3542
  export const TraeCNAuthPlugin = makePlugin(SITES["trae-cn"])
3397
3543
  export const TraeGlobalAuthPlugin = makePlugin(SITES["trae-global"])
3398
3544
 
3399
- 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.23",
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",