@zhongqian97-code/ecode 0.5.2 → 0.5.4

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 +75 -31
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -148,33 +148,30 @@ function createOpenAIProvider(profile) {
148
148
  stream(messages, tools, signal) {
149
149
  return {
150
150
  [Symbol.asyncIterator]: async function* () {
151
- let inThinkBlock = false;
152
- function stripThinkTags(raw) {
153
- let text = "";
154
- let thinking = "";
155
- let i = 0;
156
- while (i < raw.length) {
157
- if (!inThinkBlock) {
158
- const start = raw.indexOf("<think>", i);
159
- if (start === -1) {
160
- text += raw.slice(i);
161
- break;
162
- }
163
- text += raw.slice(i, start);
164
- inThinkBlock = true;
165
- i = start + 7;
151
+ let thinkPhase = "pre";
152
+ let afterReasoningDetails = false;
153
+ function processContent(raw) {
154
+ 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
+ if (thinkPhase === "post") return { text: raw, thinking: "" };
161
+ if (thinkPhase === "pre") {
162
+ if (raw.startsWith("<think>")) {
163
+ thinkPhase = "in";
164
+ raw = raw.slice(7);
166
165
  } else {
167
- const end = raw.indexOf("</think>", i);
168
- if (end === -1) {
169
- thinking += raw.slice(i);
170
- break;
171
- }
172
- thinking += raw.slice(i, end);
173
- inThinkBlock = false;
174
- i = end + 8;
166
+ thinkPhase = "post";
167
+ return { text: raw, thinking: "" };
175
168
  }
176
169
  }
177
- return { text, thinking };
170
+ const endIdx = raw.indexOf("</think>");
171
+ if (endIdx === -1) return { text: "", thinking: raw };
172
+ const thinking = raw.slice(0, endIdx);
173
+ thinkPhase = "post";
174
+ return { text: raw.slice(endIdx + 8), thinking };
178
175
  }
179
176
  const requestParams = {
180
177
  model: profile.model,
@@ -200,6 +197,7 @@ function createOpenAIProvider(profile) {
200
197
  reasoningAccumulator += delta.reasoning_content;
201
198
  }
202
199
  if (delta.reasoning_details && delta.reasoning_details.length > 0) {
200
+ afterReasoningDetails = true;
203
201
  for (const rd of delta.reasoning_details) {
204
202
  const id = rd.id ?? "";
205
203
  const text = rd.text ?? "";
@@ -235,8 +233,8 @@ function createOpenAIProvider(profile) {
235
233
  }
236
234
  }
237
235
  const isLast = choice.finish_reason != null;
238
- const raw = delta.content ?? "";
239
- const { text: filteredText, thinking: thinkContent } = stripThinkTags(raw);
236
+ const raw = delta.reasoning_details && delta.reasoning_details.length > 0 ? "" : delta.content ?? "";
237
+ const { text: filteredText, thinking: thinkContent } = processContent(raw);
240
238
  if (thinkContent) {
241
239
  reasoningAccumulator += thinkContent;
242
240
  }
@@ -3524,18 +3522,64 @@ var SkillRegistry = class {
3524
3522
  };
3525
3523
 
3526
3524
  // src/pipe.ts
3525
+ var PIPE_TOOLS = [READ_TOOL, GLOB_TOOL, GREP_TOOL];
3527
3526
  function emit(out, event) {
3528
3527
  out.write(JSON.stringify(event) + "\n");
3529
3528
  }
3529
+ async function executeToolCall(name, args) {
3530
+ if (name === "read") {
3531
+ const parsed = JSON.parse(args);
3532
+ return readFile2(parsed);
3533
+ }
3534
+ if (name === "glob") {
3535
+ const parsed = JSON.parse(args);
3536
+ return globFiles(parsed);
3537
+ }
3538
+ if (name === "grep") {
3539
+ const parsed = JSON.parse(args);
3540
+ return grepFiles(parsed);
3541
+ }
3542
+ return `Unknown tool: ${name}`;
3543
+ }
3530
3544
  async function runPipe(prompt, llm, out = process.stdout) {
3531
3545
  const messages = [{ role: "user", content: prompt }];
3532
- for await (const chunk of llm.stream(messages)) {
3533
- if (chunk.text) {
3534
- emit(out, { type: "chunk", text: chunk.text });
3546
+ while (true) {
3547
+ let assistantText = "";
3548
+ let lastUsage;
3549
+ const toolCalls = [];
3550
+ for await (const chunk of llm.stream(messages, PIPE_TOOLS)) {
3551
+ if (chunk.text) {
3552
+ emit(out, { type: "chunk", text: chunk.text });
3553
+ assistantText += chunk.text;
3554
+ }
3555
+ if (chunk.reasoning) {
3556
+ emit(out, { type: "reasoning", text: chunk.reasoning });
3557
+ }
3558
+ if (chunk.done) {
3559
+ if (chunk.toolCalls) toolCalls.push(...chunk.toolCalls);
3560
+ if (chunk.usage) lastUsage = chunk.usage;
3561
+ }
3535
3562
  }
3536
- if (chunk.done) {
3537
- const doneEvent = chunk.usage ? { type: "done", usage: chunk.usage } : { type: "done" };
3563
+ if (toolCalls.length > 0) {
3564
+ messages.push({
3565
+ role: "assistant",
3566
+ content: assistantText || null,
3567
+ tool_calls: toolCalls.map((tc) => ({
3568
+ id: tc.id,
3569
+ type: "function",
3570
+ function: { name: tc.name, arguments: tc.arguments }
3571
+ }))
3572
+ });
3573
+ for (const tc of toolCalls) {
3574
+ emit(out, { type: "tool_call", id: tc.id, name: tc.name, arguments: tc.arguments });
3575
+ const content = await executeToolCall(tc.name, tc.arguments);
3576
+ emit(out, { type: "tool_result", toolCallId: tc.id, name: tc.name, content });
3577
+ messages.push({ role: "tool", tool_call_id: tc.id, content });
3578
+ }
3579
+ } else {
3580
+ const doneEvent = lastUsage ? { type: "done", usage: lastUsage } : { type: "done" };
3538
3581
  emit(out, doneEvent);
3582
+ break;
3539
3583
  }
3540
3584
  }
3541
3585
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zhongqian97-code/ecode",
3
- "version": "0.5.2",
3
+ "version": "0.5.4",
4
4
  "description": "A minimal Claude Code clone with REPL interface and bash tool calling",
5
5
  "type": "module",
6
6
  "author": "zhongqian97-code",