@zhongqian97-code/ecode 0.5.4 → 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 +65 -15
  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
@@ -149,14 +150,21 @@ function createOpenAIProvider(profile) {
149
150
  return {
150
151
  [Symbol.asyncIterator]: async function* () {
151
152
  let thinkPhase = "pre";
152
- let afterReasoningDetails = false;
153
+ let scanningForClose = false;
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
+ }
153
166
  function processContent(raw) {
154
167
  if (!raw) return { text: "", thinking: "" };
155
- if (afterReasoningDetails) {
156
- afterReasoningDetails = false;
157
- thinkPhase = "post";
158
- return { text: raw.startsWith("</think>") ? raw.slice(8) : raw, thinking: "" };
159
- }
160
168
  if (thinkPhase === "post") return { text: raw, thinking: "" };
161
169
  if (thinkPhase === "pre") {
162
170
  if (raw.startsWith("<think>")) {
@@ -167,11 +175,11 @@ function createOpenAIProvider(profile) {
167
175
  return { text: raw, thinking: "" };
168
176
  }
169
177
  }
170
- const endIdx = raw.indexOf("</think>");
178
+ const endIdx = raw.indexOf(THINK_END);
171
179
  if (endIdx === -1) return { text: "", thinking: raw };
172
180
  const thinking = raw.slice(0, endIdx);
173
181
  thinkPhase = "post";
174
- return { text: raw.slice(endIdx + 8), thinking };
182
+ return { text: raw.slice(endIdx + THINK_END.length), thinking };
175
183
  }
176
184
  const requestParams = {
177
185
  model: profile.model,
@@ -197,7 +205,7 @@ function createOpenAIProvider(profile) {
197
205
  reasoningAccumulator += delta.reasoning_content;
198
206
  }
199
207
  if (delta.reasoning_details && delta.reasoning_details.length > 0) {
200
- afterReasoningDetails = true;
208
+ scanningForClose = true;
201
209
  for (const rd of delta.reasoning_details) {
202
210
  const id = rd.id ?? "";
203
211
  const text = rd.text ?? "";
@@ -233,8 +241,36 @@ function createOpenAIProvider(profile) {
233
241
  }
234
242
  }
235
243
  const isLast = choice.finish_reason != null;
236
- const raw = delta.reasoning_details && delta.reasoning_details.length > 0 ? "" : delta.content ?? "";
237
- const { text: filteredText, thinking: thinkContent } = processContent(raw);
244
+ let rawToProcess;
245
+ if (delta.reasoning_details && delta.reasoning_details.length > 0) {
246
+ rawToProcess = "";
247
+ } else if (scanningForClose) {
248
+ closeSearchBuffer += delta.content ?? "";
249
+ const closeIdx = closeSearchBuffer.indexOf(THINK_END);
250
+ if (closeIdx !== -1) {
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);
255
+ scanningForClose = false;
256
+ closeSearchBuffer = "";
257
+ thinkPhase = "post";
258
+ } else if (isLast) {
259
+ rawToProcess = closeSearchBuffer;
260
+ scanningForClose = false;
261
+ closeSearchBuffer = "";
262
+ thinkPhase = "post";
263
+ } else {
264
+ rawToProcess = "";
265
+ }
266
+ } else {
267
+ rawToProcess = delta.content ?? "";
268
+ }
269
+ if (thinkPhase === "post" && rawToProcess.endsWith(THINK_END) && !rawToProcess.includes("<think>") && !hasNearbyVisibleOpenThink()) {
270
+ rawToProcess = rawToProcess.slice(0, -THINK_END.length);
271
+ }
272
+ const { text: filteredText, thinking: thinkContent } = processContent(rawToProcess);
273
+ rememberVisibleText(filteredText);
238
274
  if (thinkContent) {
239
275
  reasoningAccumulator += thinkContent;
240
276
  }
@@ -3522,6 +3558,7 @@ var SkillRegistry = class {
3522
3558
  };
3523
3559
 
3524
3560
  // src/pipe.ts
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.";
3525
3562
  var PIPE_TOOLS = [READ_TOOL, GLOB_TOOL, GREP_TOOL];
3526
3563
  function emit(out, event) {
3527
3564
  out.write(JSON.stringify(event) + "\n");
@@ -3541,15 +3578,21 @@ async function executeToolCall(name, args) {
3541
3578
  }
3542
3579
  return `Unknown tool: ${name}`;
3543
3580
  }
3544
- async function runPipe(prompt, llm, out = process.stdout) {
3545
- const messages = [{ role: "user", content: prompt }];
3581
+ async function runPipe(prompt, llm, out = process.stdout, systemPrompt) {
3582
+ const messages = [
3583
+ { role: "system", content: systemPrompt ?? PIPE_SYSTEM_PROMPT },
3584
+ { role: "user", content: prompt }
3585
+ ];
3546
3586
  while (true) {
3547
3587
  let assistantText = "";
3588
+ const pendingTextChunks = [];
3548
3589
  let lastUsage;
3590
+ let lastReasoning;
3591
+ let lastReasoningDetails;
3549
3592
  const toolCalls = [];
3550
3593
  for await (const chunk of llm.stream(messages, PIPE_TOOLS)) {
3551
3594
  if (chunk.text) {
3552
- emit(out, { type: "chunk", text: chunk.text });
3595
+ pendingTextChunks.push(chunk.text);
3553
3596
  assistantText += chunk.text;
3554
3597
  }
3555
3598
  if (chunk.reasoning) {
@@ -3558,6 +3601,8 @@ async function runPipe(prompt, llm, out = process.stdout) {
3558
3601
  if (chunk.done) {
3559
3602
  if (chunk.toolCalls) toolCalls.push(...chunk.toolCalls);
3560
3603
  if (chunk.usage) lastUsage = chunk.usage;
3604
+ if (chunk.reasoning) lastReasoning = chunk.reasoning;
3605
+ if (chunk.reasoningDetails) lastReasoningDetails = chunk.reasoningDetails;
3561
3606
  }
3562
3607
  }
3563
3608
  if (toolCalls.length > 0) {
@@ -3568,7 +3613,9 @@ async function runPipe(prompt, llm, out = process.stdout) {
3568
3613
  id: tc.id,
3569
3614
  type: "function",
3570
3615
  function: { name: tc.name, arguments: tc.arguments }
3571
- }))
3616
+ })),
3617
+ ...lastReasoning ? { reasoning_content: lastReasoning } : {},
3618
+ ...lastReasoningDetails ? { reasoning_details: lastReasoningDetails } : {}
3572
3619
  });
3573
3620
  for (const tc of toolCalls) {
3574
3621
  emit(out, { type: "tool_call", id: tc.id, name: tc.name, arguments: tc.arguments });
@@ -3577,6 +3624,9 @@ async function runPipe(prompt, llm, out = process.stdout) {
3577
3624
  messages.push({ role: "tool", tool_call_id: tc.id, content });
3578
3625
  }
3579
3626
  } else {
3627
+ for (const text of pendingTextChunks) {
3628
+ emit(out, { type: "chunk", text });
3629
+ }
3580
3630
  const doneEvent = lastUsage ? { type: "done", usage: lastUsage } : { type: "done" };
3581
3631
  emit(out, doneEvent);
3582
3632
  break;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zhongqian97-code/ecode",
3
- "version": "0.5.4",
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",