@zhongqian97-code/ecode 0.5.5 → 0.5.7

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 +50 -17
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -116,6 +116,7 @@ import { Box as Box6, useInput as useInput2, useStdout, useStdin } from "ink";
116
116
  // src/providers/openai.ts
117
117
  import OpenAI from "openai";
118
118
  function createOpenAIProvider(profile) {
119
+ const THINK_END = "</think>";
119
120
  const openai = new OpenAI({
120
121
  baseURL: profile.baseUrl,
121
122
  apiKey: profile.apiKey
@@ -151,6 +152,17 @@ function createOpenAIProvider(profile) {
151
152
  let thinkPhase = "pre";
152
153
  let scanningForClose = false;
153
154
  let closeSearchBuffer = "";
155
+ let visibleTextTail = "";
156
+ function rememberVisibleText(text) {
157
+ if (!text) return;
158
+ visibleTextTail = (visibleTextTail + text).slice(-256);
159
+ }
160
+ function hasNearbyVisibleOpenThink(maxDistance = 4) {
161
+ const openIdx = visibleTextTail.lastIndexOf("<think>");
162
+ const closeIdx = visibleTextTail.lastIndexOf(THINK_END);
163
+ if (openIdx === -1 || openIdx < closeIdx) return false;
164
+ return visibleTextTail.length - (openIdx + "<think>".length) <= maxDistance;
165
+ }
154
166
  function processContent(raw) {
155
167
  if (!raw) return { text: "", thinking: "" };
156
168
  if (thinkPhase === "post") return { text: raw, thinking: "" };
@@ -163,11 +175,11 @@ function createOpenAIProvider(profile) {
163
175
  return { text: raw, thinking: "" };
164
176
  }
165
177
  }
166
- const endIdx = raw.indexOf("</think>");
178
+ const endIdx = raw.indexOf(THINK_END);
167
179
  if (endIdx === -1) return { text: "", thinking: raw };
168
180
  const thinking = raw.slice(0, endIdx);
169
181
  thinkPhase = "post";
170
- return { text: raw.slice(endIdx + 8), thinking };
182
+ return { text: raw.slice(endIdx + THINK_END.length), thinking };
171
183
  }
172
184
  const requestParams = {
173
185
  model: profile.model,
@@ -234,9 +246,12 @@ function createOpenAIProvider(profile) {
234
246
  rawToProcess = "";
235
247
  } else if (scanningForClose) {
236
248
  closeSearchBuffer += delta.content ?? "";
237
- const closeIdx = closeSearchBuffer.indexOf("</think>");
249
+ const closeIdx = closeSearchBuffer.indexOf(THINK_END);
238
250
  if (closeIdx !== -1) {
239
- rawToProcess = closeSearchBuffer.slice(closeIdx + 8);
251
+ const openIdx = closeSearchBuffer.lastIndexOf("<think>", closeIdx);
252
+ const hasNearbyLiteralPair = openIdx !== -1 && closeIdx - (openIdx + "<think>".length) <= 8;
253
+ const afterClose = closeSearchBuffer.slice(closeIdx + THINK_END.length);
254
+ rawToProcess = hasNearbyLiteralPair ? closeSearchBuffer : afterClose.length > 0 ? afterClose : closeSearchBuffer.slice(0, closeIdx);
240
255
  scanningForClose = false;
241
256
  closeSearchBuffer = "";
242
257
  thinkPhase = "post";
@@ -251,7 +266,11 @@ function createOpenAIProvider(profile) {
251
266
  } else {
252
267
  rawToProcess = delta.content ?? "";
253
268
  }
269
+ if (thinkPhase === "post" && rawToProcess.endsWith(THINK_END) && !rawToProcess.includes("<think>") && !hasNearbyVisibleOpenThink()) {
270
+ rawToProcess = rawToProcess.slice(0, -THINK_END.length);
271
+ }
254
272
  const { text: filteredText, thinking: thinkContent } = processContent(rawToProcess);
273
+ rememberVisibleText(filteredText);
255
274
  if (thinkContent) {
256
275
  reasoningAccumulator += thinkContent;
257
276
  }
@@ -2115,12 +2134,13 @@ function confirmSelection(inputText, selectedPath) {
2115
2134
 
2116
2135
  // src/ui/mouseInput.ts
2117
2136
  var SGR_MOUSE_RE = /^\x1b\[<(\d+);\d+;\d+[Mm]/;
2118
- function isMouseEvent(data) {
2137
+ function mouseEventLength(data) {
2119
2138
  const s = typeof data === "string" ? data : data.toString("binary");
2120
- if (!s) return false;
2121
- if (SGR_MOUSE_RE.test(s)) return true;
2122
- if (s.length >= 6 && s.charCodeAt(0) === 27 && s[1] === "[" && s[2] === "M") return true;
2123
- return false;
2139
+ if (!s) return 0;
2140
+ const sgrMatch = SGR_MOUSE_RE.exec(s);
2141
+ if (sgrMatch) return sgrMatch[0].length;
2142
+ if (s.length >= 6 && s.charCodeAt(0) === 27 && s[1] === "[" && s[2] === "M") return 6;
2143
+ return 0;
2124
2144
  }
2125
2145
  function parseMouseScroll(data) {
2126
2146
  const s = typeof data === "string" ? data : data.toString("binary");
@@ -2965,8 +2985,11 @@ function App({ config: config2, version: version2, autoMode: autoMode2 = false,
2965
2985
  if (event === "readable") {
2966
2986
  const chunk = stdin.read();
2967
2987
  if (chunk !== null) {
2968
- if (isMouseEvent(chunk)) {
2969
- const scrollEvent = parseMouseScroll(chunk);
2988
+ const s = typeof chunk === "string" ? chunk : chunk.toString("binary");
2989
+ const mouseLen = mouseEventLength(s);
2990
+ if (mouseLen > 0) {
2991
+ const mouseStr = s.slice(0, mouseLen);
2992
+ const scrollEvent = parseMouseScroll(mouseStr);
2970
2993
  if (scrollEvent) {
2971
2994
  if (scrollEvent.direction === "up") {
2972
2995
  setScrollOffset((prev) => Math.min(prev + 3, Math.max(0, totalLinesRef.current - 1)));
@@ -2974,9 +2997,15 @@ function App({ config: config2, version: version2, autoMode: autoMode2 = false,
2974
2997
  setScrollOffset((prev) => Math.max(0, prev - 3));
2975
2998
  }
2976
2999
  }
2977
- return false;
3000
+ const remainder = s.slice(mouseLen);
3001
+ if (remainder.length > 0) {
3002
+ stdin.unshift(
3003
+ typeof chunk === "string" ? remainder : Buffer.from(remainder, "binary")
3004
+ );
3005
+ }
3006
+ } else {
3007
+ stdin.unshift(chunk);
2978
3008
  }
2979
- stdin.unshift(chunk);
2980
3009
  }
2981
3010
  }
2982
3011
  return origEmit.apply(stdin, [event, ...args]);
@@ -3539,7 +3568,7 @@ var SkillRegistry = class {
3539
3568
  };
3540
3569
 
3541
3570
  // src/pipe.ts
3542
- var PIPE_SYSTEM_PROMPT = "You are a helpful assistant running in headless pipe mode. You have access to read-only file tools (read, glob, grep). Answer concisely and accurately.";
3571
+ var PIPE_SYSTEM_PROMPT = "You are a helpful assistant running in headless pipe mode. You have access to read-only file tools (read, glob, grep). Answer concisely and accurately. Follow the user's requested language, format, and length exactly. Do not reveal chain-of-thought or emit raw <think> / </think> tags in the final answer; if you must refer to them, describe them in plain language instead.";
3543
3572
  var PIPE_TOOLS = [READ_TOOL, GLOB_TOOL, GREP_TOOL];
3544
3573
  function emit(out, event) {
3545
3574
  out.write(JSON.stringify(event) + "\n");
@@ -3559,20 +3588,21 @@ async function executeToolCall(name, args) {
3559
3588
  }
3560
3589
  return `Unknown tool: ${name}`;
3561
3590
  }
3562
- async function runPipe(prompt, llm, out = process.stdout) {
3591
+ async function runPipe(prompt, llm, out = process.stdout, systemPrompt) {
3563
3592
  const messages = [
3564
- { role: "system", content: PIPE_SYSTEM_PROMPT },
3593
+ { role: "system", content: systemPrompt ?? PIPE_SYSTEM_PROMPT },
3565
3594
  { role: "user", content: prompt }
3566
3595
  ];
3567
3596
  while (true) {
3568
3597
  let assistantText = "";
3598
+ const pendingTextChunks = [];
3569
3599
  let lastUsage;
3570
3600
  let lastReasoning;
3571
3601
  let lastReasoningDetails;
3572
3602
  const toolCalls = [];
3573
3603
  for await (const chunk of llm.stream(messages, PIPE_TOOLS)) {
3574
3604
  if (chunk.text) {
3575
- emit(out, { type: "chunk", text: chunk.text });
3605
+ pendingTextChunks.push(chunk.text);
3576
3606
  assistantText += chunk.text;
3577
3607
  }
3578
3608
  if (chunk.reasoning) {
@@ -3604,6 +3634,9 @@ async function runPipe(prompt, llm, out = process.stdout) {
3604
3634
  messages.push({ role: "tool", tool_call_id: tc.id, content });
3605
3635
  }
3606
3636
  } else {
3637
+ for (const text of pendingTextChunks) {
3638
+ emit(out, { type: "chunk", text });
3639
+ }
3607
3640
  const doneEvent = lastUsage ? { type: "done", usage: lastUsage } : { type: "done" };
3608
3641
  emit(out, doneEvent);
3609
3642
  break;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zhongqian97-code/ecode",
3
- "version": "0.5.5",
3
+ "version": "0.5.7",
4
4
  "description": "A minimal Claude Code clone with REPL interface and bash tool calling",
5
5
  "type": "module",
6
6
  "author": "zhongqian97-code",