@zhongqian97-code/ecode 0.3.6 → 0.3.8

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/dist/index.js +94 -14
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -38,7 +38,11 @@ var MODEL_CONTEXT_LIMITS = {
38
38
  "claude-haiku-4-5-20251001": 2e5,
39
39
  // DeepSeek 系列(上下文较小,截断策略需更激进)
40
40
  "deepseek-chat": 65536,
41
- "deepseek-reasoner": 65536
41
+ "deepseek-reasoner": 65536,
42
+ // MiniMax 系列(baseUrl: https://api.minimax.chat/v1)
43
+ "MiniMax-M2.5": 1e6,
44
+ "MiniMax-M2.5-highspeed": 192e3,
45
+ "MiniMax-Text-01": 1e6
42
46
  };
43
47
  var DEFAULT_CONTEXT_LIMIT = 128e3;
44
48
  function getContextLimit(model, override) {
@@ -159,6 +163,7 @@ function createOpenAIProvider(profile) {
159
163
  );
160
164
  const tcAccumulator = /* @__PURE__ */ new Map();
161
165
  let reasoningAccumulator = "";
166
+ const reasoningDetailsAcc = /* @__PURE__ */ new Map();
162
167
  for await (const chunk of response) {
163
168
  const choice = chunk.choices[0];
164
169
  if (!choice) continue;
@@ -166,6 +171,26 @@ function createOpenAIProvider(profile) {
166
171
  if (delta.reasoning_content) {
167
172
  reasoningAccumulator += delta.reasoning_content;
168
173
  }
174
+ if (delta.reasoning_details && delta.reasoning_details.length > 0) {
175
+ for (const rd of delta.reasoning_details) {
176
+ const id = rd.id ?? "";
177
+ const text = rd.text ?? "";
178
+ if (!reasoningDetailsAcc.has(id)) {
179
+ reasoningDetailsAcc.set(id, {
180
+ type: rd.type ?? "reasoning.text",
181
+ id,
182
+ format: rd.format ?? "",
183
+ index: rd.index ?? 0,
184
+ text: ""
185
+ });
186
+ }
187
+ const existing = reasoningDetailsAcc.get(id);
188
+ existing.text += text;
189
+ if (!delta.reasoning_content && text) {
190
+ reasoningAccumulator += text;
191
+ }
192
+ }
193
+ }
169
194
  if (delta.tool_calls) {
170
195
  for (const tc of delta.tool_calls) {
171
196
  if (!tcAccumulator.has(tc.index)) {
@@ -192,6 +217,8 @@ function createOpenAIProvider(profile) {
192
217
  // 让调用方用 if (chunk.toolCalls) 做简洁判断
193
218
  toolCalls: tcAccumulator.size > 0 ? Array.from(tcAccumulator.values()) : void 0,
194
219
  reasoning: reasoningAccumulator || void 0,
220
+ // reasoningDetails 仅在流中出现过结构化推理时返回(MiniMax 兼容)
221
+ reasoningDetails: reasoningDetailsAcc.size > 0 ? Array.from(reasoningDetailsAcc.values()) : void 0,
195
222
  // 将 snake_case 的原始字段映射为 camelCase,对外接口保持一致
196
223
  usage: rawUsage ? {
197
224
  promptTokens: rawUsage.prompt_tokens,
@@ -200,9 +227,10 @@ function createOpenAIProvider(profile) {
200
227
  } : void 0
201
228
  };
202
229
  } else {
230
+ const incrementalReasoning = delta.reasoning_content ?? (delta.reasoning_details && delta.reasoning_details.length > 0 ? delta.reasoning_details.map((rd) => rd.text ?? "").join("") : void 0);
203
231
  yield {
204
232
  text: delta.content ?? "",
205
- reasoning: delta.reasoning_content,
233
+ reasoning: incrementalReasoning,
206
234
  done: false
207
235
  };
208
236
  }
@@ -258,8 +286,9 @@ var ALLOWLIST = [
258
286
  "date",
259
287
  "whoami",
260
288
  "which",
261
- "env",
262
289
  "printenv"
290
+ // "env" 故意排除:env <CMD> 会执行任意子命令,不适合 allow 级别。
291
+ // 仅显示环境变量的 printenv 保留在白名单。
263
292
  ];
264
293
  var DEFAULT_DANGER_LIST = [
265
294
  "rm -rf",
@@ -284,7 +313,10 @@ var INDIRECT_EXEC_LIST = [
284
313
  "python3 -c",
285
314
  "node -e",
286
315
  "perl -e",
287
- "ruby -e"
316
+ "ruby -e",
317
+ // eval 执行任意字符串作为 shell 命令,无论 payload 内容如何都视为危险。
318
+ // 与 xargs/python -c 的性质相同:执行能力本身就是风险。
319
+ "eval"
288
320
  ];
289
321
  function hasFindExec(cmd) {
290
322
  return /^find(\s|$)/.test(cmd) && /\s-exec(\s|$)/.test(cmd);
@@ -348,22 +380,70 @@ function splitByControlOps(cmd) {
348
380
  }
349
381
  function stripShellWrapper(cmd) {
350
382
  const trimmed = cmd.trim();
351
- const dq = trimmed.match(/^(?:bash|sh)\s+-c\s+"((?:[^"\\]|\\.)*)"/);
352
- if (dq) return dq[1];
353
- const sq = trimmed.match(/^(?:bash|sh)\s+-c\s+'([^']*)'/);
354
- if (sq) return sq[1];
355
- const nq = trimmed.match(/^(?:bash|sh)\s+-c\s+(\S+)/);
356
- if (nq) return nq[1];
383
+ const shellMatch = trimmed.match(/^(bash|sh)\s+([\s\S]*)/);
384
+ if (!shellMatch) return trimmed;
385
+ let rest = shellMatch[2];
386
+ let hasCFlag = false;
387
+ while (rest.length > 0) {
388
+ const flagMatch = rest.match(/^(-\w+)\s*([\s\S]*)/);
389
+ if (!flagMatch) break;
390
+ const flag = flagMatch[1];
391
+ rest = flagMatch[2];
392
+ if (flag.includes("c")) {
393
+ hasCFlag = true;
394
+ rest = rest.trimStart();
395
+ break;
396
+ }
397
+ rest = rest.trimStart();
398
+ }
399
+ if (!hasCFlag || !rest.length) return trimmed;
400
+ if (rest.startsWith('"')) {
401
+ const m = rest.match(/^"((?:[^"\\]|\\.)*)"/);
402
+ if (m) return m[1];
403
+ } else if (rest.startsWith("'")) {
404
+ const m = rest.match(/^'([^']*)'/);
405
+ if (m) return m[1];
406
+ } else {
407
+ const m = rest.match(/^(\S+)/);
408
+ if (m) return m[1];
409
+ }
410
+ return trimmed;
411
+ }
412
+ function stripEnvWrapper(cmd) {
413
+ const trimmed = cmd.trim();
414
+ if (!trimmed.startsWith("env ") && trimmed !== "env") return trimmed;
415
+ let rest = trimmed.slice(3).trimStart();
416
+ while (rest.length > 0) {
417
+ const assignMatch = rest.match(/^(\w+=\S*)\s*([\s\S]*)/);
418
+ if (!assignMatch) break;
419
+ rest = assignMatch[2].trimStart();
420
+ }
421
+ if (!rest.length) return trimmed;
422
+ return rest;
423
+ }
424
+ function stripCommandWrapper(cmd) {
425
+ const trimmed = cmd.trim();
426
+ const m = trimmed.match(/^command\s+([\s\S]+)/);
427
+ if (m) return m[1].trimStart();
357
428
  return trimmed;
358
429
  }
359
430
  function classifyCommand(cmd, dangerPatterns, _depth = 0) {
360
431
  const trimmed = cmd.trim();
361
432
  const patterns = dangerPatterns ?? DEFAULT_DANGER_LIST;
362
433
  if (_depth < 5) {
363
- const inner = stripShellWrapper(trimmed);
364
- if (inner !== trimmed) {
365
- const innerClass = classifyCommand(inner.trim(), dangerPatterns, _depth + 1);
366
- if (innerClass === "danger") return "danger";
434
+ const shellInner = stripShellWrapper(trimmed);
435
+ if (shellInner !== trimmed) {
436
+ if (classifyCommand(shellInner.trim(), dangerPatterns, _depth + 1) === "danger") return "danger";
437
+ return "normal";
438
+ }
439
+ const envInner = stripEnvWrapper(trimmed);
440
+ if (envInner !== trimmed) {
441
+ if (classifyCommand(envInner.trim(), dangerPatterns, _depth + 1) === "danger") return "danger";
442
+ return "normal";
443
+ }
444
+ const commandInner = stripCommandWrapper(trimmed);
445
+ if (commandInner !== trimmed) {
446
+ if (classifyCommand(commandInner.trim(), dangerPatterns, _depth + 1) === "danger") return "danger";
367
447
  return "normal";
368
448
  }
369
449
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zhongqian97-code/ecode",
3
- "version": "0.3.6",
3
+ "version": "0.3.8",
4
4
  "description": "A minimal Claude Code clone with REPL interface and bash tool calling",
5
5
  "type": "module",
6
6
  "author": "zhongqian97-code",