@zhongqian97-code/ecode 0.5.36 → 0.5.38

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.
@@ -202,9 +202,229 @@ function createOpenAIProvider(profile) {
202
202
  };
203
203
  }
204
204
 
205
+ // src/providers/anthropic.ts
206
+ import https from "https";
207
+ import { URL as URL2 } from "url";
208
+ function convertMessagesToAnthropic(messages) {
209
+ let system = "";
210
+ const anthropicMessages = [];
211
+ for (let i = 0; i < messages.length; i++) {
212
+ const msg = messages[i];
213
+ if (msg.role === "system") {
214
+ system = msg.content;
215
+ continue;
216
+ }
217
+ if (msg.role === "user") {
218
+ anthropicMessages.push({ role: "user", content: msg.content });
219
+ continue;
220
+ }
221
+ if (msg.role === "tool") {
222
+ const toolResults = [];
223
+ while (i < messages.length && messages[i].role === "tool") {
224
+ const toolMsg = messages[i];
225
+ toolResults.push({
226
+ type: "tool_result",
227
+ tool_use_id: toolMsg.tool_call_id,
228
+ content: toolMsg.content
229
+ });
230
+ i++;
231
+ }
232
+ i--;
233
+ anthropicMessages.push({ role: "user", content: toolResults });
234
+ continue;
235
+ }
236
+ if (msg.role === "assistant") {
237
+ if (msg.tool_calls && msg.tool_calls.length > 0) {
238
+ const content = [];
239
+ if (msg.content) content.push({ type: "text", text: msg.content });
240
+ for (const tc of msg.tool_calls) {
241
+ let input = {};
242
+ try {
243
+ input = JSON.parse(tc.function.arguments);
244
+ } catch {
245
+ input = {};
246
+ }
247
+ content.push({ type: "tool_use", id: tc.id, name: tc.function.name, input });
248
+ }
249
+ anthropicMessages.push({ role: "assistant", content });
250
+ } else {
251
+ anthropicMessages.push({ role: "assistant", content: msg.content ?? "" });
252
+ }
253
+ }
254
+ }
255
+ return { system, anthropicMessages };
256
+ }
257
+ function convertToolsToAnthropic(tools) {
258
+ return tools.map((tool) => ({
259
+ name: tool.function.name,
260
+ description: tool.function.description,
261
+ input_schema: tool.function.parameters
262
+ }));
263
+ }
264
+ function createAnthropicProvider(profile) {
265
+ const capabilities = Object.freeze(
266
+ Object.defineProperties({}, {
267
+ supportsTools: { value: true, writable: false, enumerable: true },
268
+ supportsReasoningStream: { value: false, writable: false, enumerable: true },
269
+ supportsImages: { value: true, writable: false, enumerable: true },
270
+ supportsJsonSchema: { value: true, writable: false, enumerable: true }
271
+ })
272
+ );
273
+ return {
274
+ capabilities,
275
+ stream(messages, tools, signal) {
276
+ return streamAnthropicResponse(profile, messages, tools, signal);
277
+ }
278
+ };
279
+ }
280
+ async function* streamAnthropicResponse(profile, messages, tools, signal) {
281
+ const { system, anthropicMessages } = convertMessagesToAnthropic(messages);
282
+ const endpointUrl = new URL2(`${profile.baseUrl.replace(/\/$/, "")}/v1/messages`);
283
+ const requestBody = {
284
+ model: profile.model,
285
+ max_tokens: 8192,
286
+ messages: anthropicMessages,
287
+ stream: true
288
+ };
289
+ if (system) requestBody.system = system;
290
+ if (tools && tools.length > 0) requestBody.tools = convertToolsToAnthropic(tools);
291
+ const bodyStr = JSON.stringify(requestBody);
292
+ yield* makeHttpsStream(endpointUrl, profile.apiKey, bodyStr, signal);
293
+ }
294
+ async function* makeHttpsStream(url, apiKey, body, signal) {
295
+ var _a;
296
+ const queue = [];
297
+ let notify = null;
298
+ function push(item) {
299
+ queue.push(item);
300
+ if (notify) {
301
+ const fn = notify;
302
+ notify = null;
303
+ fn();
304
+ }
305
+ }
306
+ const port = url.port ? parseInt(url.port, 10) : 443;
307
+ const req = https.request(
308
+ {
309
+ hostname: url.hostname,
310
+ port,
311
+ path: url.pathname + url.search,
312
+ method: "POST",
313
+ headers: {
314
+ "x-api-key": apiKey,
315
+ "anthropic-version": "2023-06-01",
316
+ "content-type": "application/json",
317
+ accept: "text/event-stream",
318
+ "content-length": String(Buffer.byteLength(body, "utf8"))
319
+ }
320
+ },
321
+ (res) => {
322
+ const statusCode = res.statusCode;
323
+ if (statusCode !== 200) {
324
+ push(new Error(`Anthropic API error: HTTP ${statusCode}`));
325
+ return;
326
+ }
327
+ const r = res;
328
+ r.on("data", (chunk) => push(chunk));
329
+ r.on("end", () => push(null));
330
+ r.on("error", (err) => push(err));
331
+ }
332
+ );
333
+ req.on("error", (err) => push(err));
334
+ if (signal) {
335
+ signal.addEventListener(
336
+ "abort",
337
+ () => {
338
+ req.destroy();
339
+ push(null);
340
+ },
341
+ { once: true }
342
+ );
343
+ }
344
+ req.write(body);
345
+ req.end();
346
+ let buffer = "";
347
+ const toolBlocks = /* @__PURE__ */ new Map();
348
+ let stopReason = null;
349
+ let inputTokens = 0;
350
+ let outputTokens = 0;
351
+ while (true) {
352
+ let item;
353
+ if (queue.length > 0) {
354
+ item = queue.shift();
355
+ } else {
356
+ await new Promise((resolve4) => {
357
+ notify = resolve4;
358
+ });
359
+ item = queue.shift();
360
+ }
361
+ if (item === null) break;
362
+ if (item instanceof Error) throw item;
363
+ buffer += item.toString("utf8");
364
+ const lines = buffer.split("\n");
365
+ buffer = lines.pop() ?? "";
366
+ for (const line of lines) {
367
+ if (!line.startsWith("data: ")) continue;
368
+ const data = line.slice(6).trim();
369
+ if (data === "[DONE]") break;
370
+ let event;
371
+ try {
372
+ event = JSON.parse(data);
373
+ } catch {
374
+ continue;
375
+ }
376
+ const eventType = event.type;
377
+ if (eventType === "message_start") {
378
+ const msg = event.message;
379
+ if ((_a = msg == null ? void 0 : msg.usage) == null ? void 0 : _a.input_tokens) inputTokens = msg.usage.input_tokens;
380
+ } else if (eventType === "content_block_start") {
381
+ const block = event.content_block;
382
+ const index = event.index;
383
+ if ((block == null ? void 0 : block.type) === "tool_use" && block.id && block.name) {
384
+ toolBlocks.set(index, { id: block.id, name: block.name, jsonAccum: "" });
385
+ }
386
+ } else if (eventType === "content_block_delta") {
387
+ const delta = event.delta;
388
+ const index = event.index;
389
+ if ((delta == null ? void 0 : delta.type) === "text_delta" && delta.text) {
390
+ yield { text: delta.text, done: false };
391
+ } else if ((delta == null ? void 0 : delta.type) === "input_json_delta" && delta.partial_json) {
392
+ const tool = toolBlocks.get(index);
393
+ if (tool) tool.jsonAccum += delta.partial_json;
394
+ }
395
+ } else if (eventType === "message_delta") {
396
+ const delta = event.delta;
397
+ stopReason = (delta == null ? void 0 : delta.stop_reason) ?? null;
398
+ const usage = event.usage;
399
+ if (usage == null ? void 0 : usage.output_tokens) outputTokens = usage.output_tokens;
400
+ } else if (eventType === "message_stop") {
401
+ const toolCalls = [...toolBlocks.values()].map((t) => ({
402
+ id: t.id,
403
+ name: t.name,
404
+ arguments: t.jsonAccum
405
+ }));
406
+ yield {
407
+ done: true,
408
+ text: "",
409
+ finishReason: stopReason,
410
+ toolCalls: toolCalls.length > 0 ? toolCalls : void 0,
411
+ usage: {
412
+ promptTokens: inputTokens,
413
+ completionTokens: outputTokens,
414
+ totalTokens: inputTokens + outputTokens
415
+ }
416
+ };
417
+ return;
418
+ }
419
+ }
420
+ }
421
+ yield { done: true, text: "", finishReason: stopReason };
422
+ }
423
+
205
424
  // src/providers/index.ts
206
425
  function createProvider(profile) {
207
- return createOpenAIProvider(profile);
426
+ const useAnthropic = profile.apiFormat === "anthropic" || profile.baseUrl.includes("anthropic");
427
+ return useAnthropic ? createAnthropicProvider(profile) : createOpenAIProvider(profile);
208
428
  }
209
429
  function resolveActiveProfile(config, providerName) {
210
430
  var _a, _b;
@@ -646,7 +866,7 @@ async function grepFiles(params) {
646
866
 
647
867
  // src/tools/web_fetch.ts
648
868
  import * as http from "http";
649
- import * as https from "https";
869
+ import * as https2 from "https";
650
870
  var WEB_FETCH_TOOL = {
651
871
  type: "function",
652
872
  function: {
@@ -807,7 +1027,7 @@ async function nodeHttpFetch(url, signal) {
807
1027
  return new Promise((resolve4, reject) => {
808
1028
  const parsed = new URL(url);
809
1029
  const isHttps = parsed.protocol === "https:";
810
- const lib = isHttps ? https : http;
1030
+ const lib = isHttps ? https2 : http;
811
1031
  const onAbort = () => {
812
1032
  req.destroy();
813
1033
  const err = new Error("The user aborted a request.");
package/dist/index.js CHANGED
@@ -34,7 +34,7 @@ import {
34
34
  todo,
35
35
  webFetch,
36
36
  writeFile
37
- } from "./chunk-BRSZMOHF.js";
37
+ } from "./chunk-7DHMJ6NS.js";
38
38
  import {
39
39
  createSessionMetadata,
40
40
  generateTitle,
@@ -231,7 +231,10 @@ var EventBus = class {
231
231
  /** 发布事件,同步调用所有当前订阅者 */
232
232
  emit(event) {
233
233
  for (const listener of this.listeners) {
234
- listener(event);
234
+ try {
235
+ listener(event);
236
+ } catch {
237
+ }
235
238
  }
236
239
  }
237
240
  /** 添加事件监听器,返回取消订阅函数 */
@@ -807,7 +810,7 @@ if (rawArgs[0] === "web") {
807
810
  webAutoApprove = true;
808
811
  }
809
812
  }
810
- const { buildServer, generateAccessToken } = await import("./web-Y5CK2WBF.js");
813
+ const { buildServer, generateAccessToken } = await import("./web-7WWKTWTB.js");
811
814
  const token = generateAccessToken();
812
815
  const manager = new SessionManager(finalConfig);
813
816
  const server = await buildServer({
@@ -902,6 +905,6 @@ Node.js 16/18 \u8BF7\u4F7F\u7528 --web \u6216 --pipe \u6A21\u5F0F\u3002
902
905
  );
903
906
  process.exit(1);
904
907
  }
905
- const { App, React, render } = await import("./ui-QZG4SHLC.js");
908
+ const { App, React, render } = await import("./ui-XL5IDLH5.js");
906
909
  render(React.createElement(App, { config: finalConfig, version: VERSION, autoMode, registry, trustedSkillDirs, initialMessages }));
907
910
  }
@@ -26,7 +26,7 @@ import {
26
26
  todo,
27
27
  webFetch,
28
28
  writeFile
29
- } from "./chunk-BRSZMOHF.js";
29
+ } from "./chunk-7DHMJ6NS.js";
30
30
  import {
31
31
  loadJobs,
32
32
  removeJob,
@@ -592,6 +592,7 @@ function generateAdminHtml(version) {
592
592
  status: 'idle', // idle | thinking | tool_calling | awaiting_confirm
593
593
  wsRetries: 0,
594
594
  streamingMsgEl: null, // DOM element currently receiving delta tokens
595
+ streamingThinkEl: null, // DOM element currently receiving reasoning tokens
595
596
  showTools: true,
596
597
  skills: [], // cached skills from /api/skills
597
598
  skillsLoaded: false,
@@ -771,6 +772,7 @@ function generateAdminHtml(version) {
771
772
  '<div class="empty-chat">\u2190 \u52A0\u8F7D\u4F1A\u8BDD\u4E2D\u2026</div>';
772
773
  state.messages = [];
773
774
  state.streamingMsgEl = null;
775
+ state.streamingThinkEl = null;
774
776
  }
775
777
 
776
778
  async function loadMessages(sessionId) {
@@ -824,6 +826,7 @@ function generateAdminHtml(version) {
824
826
  if (body) body.classList.remove('cursor-blink');
825
827
  state.streamingMsgEl = null;
826
828
  }
829
+ state.streamingThinkEl = null;
827
830
  }
828
831
 
829
832
  // \u2500\u2500 WebSocket \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
@@ -935,14 +938,20 @@ function generateAdminHtml(version) {
935
938
  finalizeStreamingMsg();
936
939
  setStatus('idle');
937
940
  } else if (type === 'message.reasoning') {
941
+ const token = msg.reasoning || msg.text || '';
938
942
  const msgsEl = document.getElementById('messages');
939
- const placeholder = msgsEl.querySelector('.empty-chat');
940
- if (placeholder) placeholder.remove();
941
- const thinkEl = document.createElement('div');
942
- thinkEl.className = 'thinking-block';
943
- thinkEl.textContent = msg.reasoning || msg.text || '';
944
- if (!state.showTools) thinkEl.style.display = 'none';
945
- msgsEl.appendChild(thinkEl);
943
+ if (!state.streamingThinkEl) {
944
+ const placeholder = msgsEl.querySelector('.empty-chat');
945
+ if (placeholder) placeholder.remove();
946
+ const thinkEl = document.createElement('div');
947
+ thinkEl.className = 'thinking-block';
948
+ thinkEl.textContent = token;
949
+ if (!state.showTools) thinkEl.style.display = 'none';
950
+ msgsEl.appendChild(thinkEl);
951
+ state.streamingThinkEl = thinkEl;
952
+ } else {
953
+ state.streamingThinkEl.textContent += token;
954
+ }
946
955
  msgsEl.scrollTop = msgsEl.scrollHeight;
947
956
  } else if (type === 'session.error') {
948
957
  finalizeStreamingMsg();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zhongqian97-code/ecode",
3
- "version": "0.5.36",
3
+ "version": "0.5.38",
4
4
  "description": "A minimal Claude Code clone with REPL interface and bash tool calling",
5
5
  "type": "module",
6
6
  "author": "zhongqian97-code",