zhitalk 0.0.9 → 0.0.10

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.
package/README.md CHANGED
@@ -2,22 +2,30 @@
2
2
 
3
3
  [智语 Zhitalk](https://zhitalk.chat/) 是一个 AI Agent 个人助手,像 OpenClaw 小龙虾一样。 包含 tools skills memory hook subagent MCP-server 等 Agent 功能。你可以和它聊天,给它分配任务,让它操作文件...
4
4
 
5
- ## Usage
5
+ 📖 你可以免费围观项目进展,也可以加入学习 [跟双越老师从 0 开发一个 AI Agent 智能体](https://www.huashuiai.com/pub/ai-agent-camp-new)
6
6
 
7
- Docs https://zhitalk.chat/
7
+ ## Usage
8
8
 
9
- Install
9
+ 使用 npm 全局安装。注意,Windows 用户使用管理员打开 cmd 再执行。
10
10
 
11
11
  ```
12
12
  npm install zhitalk -g
13
13
  ```
14
14
 
15
- Run in terminal
15
+ 查看版本
16
+
17
+ ```
18
+ zhitalk --version
19
+ ```
20
+
21
+ 启动项目
16
22
 
17
23
  ```
18
24
  zhitalk
19
25
  ```
20
26
 
27
+ 第一次执行时,需要你修改配置文件,参考 https://zhitalk.chat/#config
28
+
21
29
  ## Dev
22
30
 
23
31
  ### Tech
@@ -1,6 +1,6 @@
1
1
  import { SqliteSaver } from '@langchain/langgraph-checkpoint-sqlite';
2
2
  import { SystemMessage, AIMessage, HumanMessage, ToolMessage, } from '@langchain/core/messages';
3
- import { StateGraph, Annotation, START, END, interrupt, Command, } from '@langchain/langgraph';
3
+ import { StateGraph, Annotation, START, END, interrupt, Command, GraphRecursionError, } from '@langchain/langgraph';
4
4
  import { messagesStateReducer } from '@langchain/langgraph';
5
5
  import { DB_PATH } from './db.js';
6
6
  import { tools, maybePersistedOutput, initTools } from './tools.js';
@@ -295,32 +295,43 @@ async function _runAgent(compiledAgent, userMessage, onToken, onToolConfirmation
295
295
  let fullResponse = '';
296
296
  let usageMetadata;
297
297
  let input = { messages: [new HumanMessage(userMessage)] };
298
+ const recursionLimit = 50;
298
299
  while (true) {
299
- const stream = await compiledAgent.stream(input, {
300
- ...config,
301
- streamMode: 'messages',
302
- signal,
303
- recursionLimit: 50,
304
- });
305
- for await (const chunk of stream) {
306
- if (signal?.aborted) {
307
- throw new Error('abort');
300
+ try {
301
+ const stream = await compiledAgent.stream(input, {
302
+ ...config,
303
+ streamMode: 'messages',
304
+ signal,
305
+ recursionLimit,
306
+ });
307
+ for await (const chunk of stream) {
308
+ if (signal?.aborted) {
309
+ throw new Error('abort');
310
+ }
311
+ const message = chunk[0];
312
+ const metadata = chunk[1];
313
+ if (metadata?.langgraph_node !== 'model_request')
314
+ continue;
315
+ const msgUsage = message.usage_metadata;
316
+ if (msgUsage) {
317
+ usageMetadata = msgUsage;
318
+ }
319
+ // AIMessageChunk 的 content 在 message.content 属性上,不在 kwargs.content
320
+ const content = message.content ?? message.kwargs?.content ?? '';
321
+ const toolCallChunks = message.tool_call_chunks ?? [];
322
+ if (!content || toolCallChunks.length > 0)
323
+ continue;
324
+ onToken(content);
325
+ fullResponse += content;
308
326
  }
309
- const message = chunk[0];
310
- const metadata = chunk[1];
311
- if (metadata?.langgraph_node !== 'model_request')
312
- continue;
313
- const msgUsage = message.usage_metadata;
314
- if (msgUsage) {
315
- usageMetadata = msgUsage;
327
+ }
328
+ catch (err) {
329
+ if (err instanceof GraphRecursionError) {
330
+ const hint = `我已经思考了 ${recursionLimit} 个步骤,仍未得到最终结果,我暂停歇会儿~ 你可以随时让我“继续”`;
331
+ onToken('\n\n' + hint);
332
+ return { response: fullResponse + '\n\n' + hint, usageMetadata };
316
333
  }
317
- // AIMessageChunk 的 content 在 message.content 属性上,不在 kwargs.content
318
- const content = message.content ?? message.kwargs?.content ?? '';
319
- const toolCallChunks = message.tool_call_chunks ?? [];
320
- if (!content || toolCallChunks.length > 0)
321
- continue;
322
- onToken(content);
323
- fullResponse += content;
334
+ throw err;
324
335
  }
325
336
  const state = await compiledAgent.getState(config);
326
337
  const interruptedTask = state.tasks?.find((t) => t.interrupts?.length > 0);
package/dist/agent/cli.js CHANGED
@@ -49,9 +49,43 @@ function prompt(question) {
49
49
  });
50
50
  });
51
51
  }
52
+ function createSpinner(text) {
53
+ const frames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
54
+ let i = 0;
55
+ let timer = null;
56
+ let running = false;
57
+ const tick = () => {
58
+ i = (i + 1) % frames.length;
59
+ readline.cursorTo(process.stdout, 0);
60
+ process.stdout.write(color.gray(`${text} ${frames[i]}`));
61
+ };
62
+ return {
63
+ start() {
64
+ if (running)
65
+ return;
66
+ running = true;
67
+ i = 0;
68
+ readline.cursorTo(process.stdout, 0);
69
+ process.stdout.write(color.gray(`${text} ${frames[0]}`));
70
+ timer = setInterval(tick, 80);
71
+ },
72
+ stop() {
73
+ if (!running)
74
+ return;
75
+ running = false;
76
+ if (timer) {
77
+ clearInterval(timer);
78
+ timer = null;
79
+ }
80
+ readline.cursorTo(process.stdout, 0);
81
+ readline.clearLine(process.stdout, 0);
82
+ },
83
+ };
84
+ }
52
85
  async function chat(userInput) {
53
86
  const rl = createInterface();
54
- process.stdout.write('\n' + color.aiPrefix());
87
+ // AI: 前缀延迟到第一个 token 输出,此前显示 spinner
88
+ // process.stdout.write('\n' + color.aiPrefix())
55
89
  const controller = new AbortController();
56
90
  // 监听 ESC 键,中断 AI 请求
57
91
  const escListener = (_str, key) => {
@@ -64,18 +98,31 @@ async function chat(userInput) {
64
98
  readline.emitKeypressEvents(process.stdin);
65
99
  process.stdin.on('keypress', escListener);
66
100
  let usageMetadata;
101
+ let isFirstToken = true;
102
+ const spinner = createSpinner('AI 思考中');
67
103
  try {
104
+ spinner.start();
68
105
  const result = await runAgentStream(userInput, (token) => {
106
+ if (isFirstToken) {
107
+ isFirstToken = false;
108
+ spinner.stop();
109
+ process.stdout.write('\n' + color.aiPrefix());
110
+ }
69
111
  process.stdout.write(token);
70
112
  }, async (toolCalls) => {
113
+ spinner.stop();
71
114
  for (const call of toolCalls) {
72
115
  console.log(formatToolLog(call.name, JSON.stringify(call.args)));
73
116
  }
74
117
  const answer = await new Promise((resolve) => {
75
118
  rl.question('确认执行以上工具? (y/n): ', resolve);
76
119
  });
77
- return (answer.trim().toLowerCase() === 'y' ||
78
- answer.trim().toLowerCase() === 'yes');
120
+ const confirmed = answer.trim().toLowerCase() === 'y' ||
121
+ answer.trim().toLowerCase() === 'yes';
122
+ if (confirmed && isFirstToken) {
123
+ spinner.start();
124
+ }
125
+ return confirmed;
79
126
  }, threadId, controller.signal);
80
127
  usageMetadata = result.usageMetadata;
81
128
  }
@@ -85,6 +132,9 @@ async function chat(userInput) {
85
132
  }
86
133
  }
87
134
  finally {
135
+ if (isFirstToken) {
136
+ spinner.stop();
137
+ }
88
138
  process.stdin.removeListener('keypress', escListener);
89
139
  rl.close();
90
140
  }
@@ -36,7 +36,10 @@ export function getModelConfig() {
36
36
  if (!config.model.baseURL) {
37
37
  throw new Error(`配置文件 ${CONFIG_PATH} 中缺少 model.baseURL 字段,请参考 https://zhitalk.chat/#config 修改配置`);
38
38
  }
39
- return config.model;
39
+ return {
40
+ ...config.model,
41
+ model: config.model.model.toLowerCase(),
42
+ };
40
43
  }
41
44
  export function getEnv(key) {
42
45
  const config = loadConfig();
@@ -3,11 +3,12 @@ import { getModelConfig } from './config.js';
3
3
  const modelConfig = getModelConfig();
4
4
  export function createModel(options) {
5
5
  const modelKwargs = {};
6
- if (modelConfig.model.startsWith('kimi')) {
6
+ if (modelConfig.model.startsWith('kimi') ||
7
+ modelConfig.model.startsWith('minimax')) {
7
8
  modelKwargs.thinking = { type: 'disabled' };
8
9
  }
9
10
  // GLM 模型的流式 function calling 存在兼容性问题,禁用流式输出
10
- const isGlm = modelConfig.model.toLowerCase().startsWith('glm');
11
+ const isGlm = modelConfig.model.startsWith('glm');
11
12
  return new ChatOpenAI({
12
13
  model: modelConfig.model,
13
14
  apiKey: modelConfig.apiKey,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zhitalk",
3
- "version": "0.0.9",
3
+ "version": "0.0.10",
4
4
  "description": "A personal AI Agent like OpenClaw, including tools, skills, memory, hook, sub-agent, MCP server, etc. Run in the terminal.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",