@zhongqian97-code/ecode 0.5.5 → 0.5.6

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 +31 -8
  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
  }
@@ -3539,7 +3558,7 @@ var SkillRegistry = class {
3539
3558
  };
3540
3559
 
3541
3560
  // 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.";
3561
+ 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
3562
  var PIPE_TOOLS = [READ_TOOL, GLOB_TOOL, GREP_TOOL];
3544
3563
  function emit(out, event) {
3545
3564
  out.write(JSON.stringify(event) + "\n");
@@ -3559,20 +3578,21 @@ async function executeToolCall(name, args) {
3559
3578
  }
3560
3579
  return `Unknown tool: ${name}`;
3561
3580
  }
3562
- async function runPipe(prompt, llm, out = process.stdout) {
3581
+ async function runPipe(prompt, llm, out = process.stdout, systemPrompt) {
3563
3582
  const messages = [
3564
- { role: "system", content: PIPE_SYSTEM_PROMPT },
3583
+ { role: "system", content: systemPrompt ?? PIPE_SYSTEM_PROMPT },
3565
3584
  { role: "user", content: prompt }
3566
3585
  ];
3567
3586
  while (true) {
3568
3587
  let assistantText = "";
3588
+ const pendingTextChunks = [];
3569
3589
  let lastUsage;
3570
3590
  let lastReasoning;
3571
3591
  let lastReasoningDetails;
3572
3592
  const toolCalls = [];
3573
3593
  for await (const chunk of llm.stream(messages, PIPE_TOOLS)) {
3574
3594
  if (chunk.text) {
3575
- emit(out, { type: "chunk", text: chunk.text });
3595
+ pendingTextChunks.push(chunk.text);
3576
3596
  assistantText += chunk.text;
3577
3597
  }
3578
3598
  if (chunk.reasoning) {
@@ -3604,6 +3624,9 @@ async function runPipe(prompt, llm, out = process.stdout) {
3604
3624
  messages.push({ role: "tool", tool_call_id: tc.id, content });
3605
3625
  }
3606
3626
  } else {
3627
+ for (const text of pendingTextChunks) {
3628
+ emit(out, { type: "chunk", text });
3629
+ }
3607
3630
  const doneEvent = lastUsage ? { type: "done", usage: lastUsage } : { type: "done" };
3608
3631
  emit(out, doneEvent);
3609
3632
  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.6",
4
4
  "description": "A minimal Claude Code clone with REPL interface and bash tool calling",
5
5
  "type": "module",
6
6
  "author": "zhongqian97-code",