@gird/ccli 2.0.1 → 2.0.2

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/dist/ai.js ADDED
@@ -0,0 +1,44 @@
1
+ /**
2
+ * AI 客户端 —— 把配置变成「能对话的对象」。
3
+ *
4
+ * 本步只做最基本的一问一答(带对话历史 + 流式输出),还没有工具调用 / 代理循环。
5
+ * 目前只对接 OpenAI 兼容后端(通义千问 / DeepSeek / GLM…);Anthropic 留待后续步骤。
6
+ *
7
+ * 流式(stream)的意义:模型边生成边返回,我们边收边打印,
8
+ * 用户不用干等一整段生成完,交互感更接近「打字机」。
9
+ */
10
+ import OpenAI from 'openai';
11
+ export class AIClient {
12
+ client;
13
+ model;
14
+ constructor(config) {
15
+ this.client = new OpenAI({
16
+ apiKey: config.apiKey,
17
+ baseURL: config.baseURL,
18
+ });
19
+ this.model = config.model;
20
+ }
21
+ /**
22
+ * 发送整段对话历史,流式接收助手回复。
23
+ * @param messages 完整历史(含本轮 user 消息)
24
+ * @param onDelta 每收到一小段文本就回调(用于实时打印)
25
+ * @returns 助手本轮完整回复文本
26
+ */
27
+ async chat(messages, onDelta) {
28
+ const stream = await this.client.chat.completions.create({
29
+ model: this.model,
30
+ messages,
31
+ stream: true,
32
+ });
33
+ let full = '';
34
+ for await (const chunk of stream) {
35
+ const delta = chunk.choices[0]?.delta?.content ?? '';
36
+ if (delta) {
37
+ full += delta;
38
+ onDelta(delta);
39
+ }
40
+ }
41
+ return full;
42
+ }
43
+ }
44
+ //# sourceMappingURL=ai.js.map
package/dist/ai.js.map ADDED
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ai.js","sourceRoot":"","sources":["../src/ai.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,MAAM,MAAM,QAAQ,CAAC;AAS5B,MAAM,OAAO,QAAQ;IACT,MAAM,CAAS;IACf,KAAK,CAAS;IAEtB,YAAY,MAAc;QACtB,IAAI,CAAC,MAAM,GAAG,IAAI,MAAM,CAAC;YACrB,MAAM,EAAE,MAAM,CAAC,MAAM;YACrB,OAAO,EAAE,MAAM,CAAC,OAAO;SAC1B,CAAC,CAAC;QACH,IAAI,CAAC,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC;IAC9B,CAAC;IAED;;;;;OAKG;IACH,KAAK,CAAC,IAAI,CACN,QAAuB,EACvB,OAA+B;QAE/B,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,WAAW,CAAC,MAAM,CAAC;YACrD,KAAK,EAAE,IAAI,CAAC,KAAK;YACjB,QAAQ;YACR,MAAM,EAAE,IAAI;SACf,CAAC,CAAC;QAEH,IAAI,IAAI,GAAG,EAAE,CAAC;QACd,IAAI,KAAK,EAAE,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;YAC/B,MAAM,KAAK,GAAG,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,OAAO,IAAI,EAAE,CAAC;YACrD,IAAI,KAAK,EAAE,CAAC;gBACR,IAAI,IAAI,KAAK,CAAC;gBACd,OAAO,CAAC,KAAK,CAAC,CAAC;YACnB,CAAC;QACL,CAAC;QACD,OAAO,IAAI,CAAC;IAChB,CAAC;CACJ"}
package/dist/repl.js CHANGED
@@ -1,19 +1,18 @@
1
1
  /**
2
2
  * 交互式会话循环(REPL:Read-Eval-Print-Loop)。
3
3
  *
4
- * 本步只搭骨架,不接 AI
5
- * 读一行用户输入 → 处理(暂时只回显)→ 打印提示符,再读下一行。
4
+ * 读一行用户输入 → 调用 AI(流式打印回复)→ 打印提示符,再读下一行。
5
+ * 维护整段对话历史,所以模型能记住上文,形成真正的多轮对话。
6
6
  * 支持优雅退出:/exit、/quit,或按 Ctrl-D(EOF)、Ctrl-C。
7
- *
8
- * 用 Node 内置 readline,零依赖。等骨架稳定后,下一步把「处理」那一环
9
- * 换成「调用 AI」即可,循环结构不用动。
10
7
  */
11
8
  import { createInterface } from 'node:readline';
12
9
  import { NAME, VERSION } from './version.js';
13
10
  import { loadConfig, maskKey } from './config.js';
11
+ import { AIClient } from './ai.js';
14
12
  const PROMPT = '› ';
15
13
  /** 用户输入这些即退出。 */
16
14
  const EXIT_COMMANDS = new Set(['/exit', '/quit']);
15
+ const SYSTEM_PROMPT = '你是 CCLI,一个运行在终端里的 AI 助手。回答简洁、准确,使用中文。';
17
16
  export function startRepl() {
18
17
  // 启动即解析配置:缺失就明确报错退出,不进入循环
19
18
  const result = loadConfig();
@@ -22,6 +21,9 @@ export function startRepl() {
22
21
  process.exit(1);
23
22
  }
24
23
  const { config } = result;
24
+ const ai = new AIClient(config);
25
+ // 对话历史:以 system 提示词开场,之后累积 user / assistant 轮次
26
+ const history = [{ role: 'system', content: SYSTEM_PROMPT }];
25
27
  const rl = createInterface({
26
28
  input: process.stdin,
27
29
  output: process.stdout,
@@ -31,20 +33,36 @@ export function startRepl() {
31
33
  console.log(`后端: ${config.provider} 模型: ${config.model} 密钥: ${maskKey(config.apiKey)}`);
32
34
  console.log('进入交互式会话。输入 /exit 退出,或按 Ctrl-C。\n');
33
35
  rl.prompt();
34
- rl.on('line', (line) => {
36
+ // 正在等待 AI 回复时置 true,避免这期间的输入被重复处理
37
+ let busy = false;
38
+ rl.on('line', async (line) => {
35
39
  const input = line.trim();
36
- if (input.length === 0) {
37
- // 空行:不处理,直接再次提示
38
- rl.prompt();
40
+ if (busy || input.length === 0) {
41
+ if (!busy)
42
+ rl.prompt();
39
43
  return;
40
44
  }
41
45
  if (EXIT_COMMANDS.has(input)) {
42
46
  rl.close();
43
47
  return;
44
48
  }
45
- // 暂时的「处理」:回显。下一步换成调用 AI。
46
- console.log(`你说:${input}`);
47
- rl.prompt();
49
+ busy = true;
50
+ history.push({ role: 'user', content: input });
51
+ try {
52
+ // 流式打印助手回复:边收边写到终端
53
+ const reply = await ai.chat(history, (delta) => process.stdout.write(delta));
54
+ process.stdout.write('\n'); // 回复末尾补个换行
55
+ history.push({ role: 'assistant', content: reply });
56
+ }
57
+ catch (e) {
58
+ console.error(`\n[请求失败] ${e?.message ?? e}`);
59
+ // 失败则回滚本轮 user 消息,避免污染后续上下文
60
+ history.pop();
61
+ }
62
+ finally {
63
+ busy = false;
64
+ rl.prompt();
65
+ }
48
66
  });
49
67
  // Ctrl-D(EOF)或 rl.close() 触发
50
68
  rl.on('close', () => {
package/dist/repl.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"repl.js","sourceRoot":"","sources":["../src/repl.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAChD,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAC7C,OAAO,EAAE,UAAU,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAElD,MAAM,MAAM,GAAG,IAAI,CAAC;AAEpB,iBAAiB;AACjB,MAAM,aAAa,GAAG,IAAI,GAAG,CAAC,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC;AAElD,MAAM,UAAU,SAAS;IACrB,0BAA0B;IAC1B,MAAM,MAAM,GAAG,UAAU,EAAE,CAAC;IAC5B,IAAI,CAAC,MAAM,CAAC,EAAE,EAAE,CAAC;QACb,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;QAC9B,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACpB,CAAC;IACD,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,CAAC;IAE1B,MAAM,EAAE,GAAG,eAAe,CAAC;QACvB,KAAK,EAAE,OAAO,CAAC,KAAK;QACpB,MAAM,EAAE,OAAO,CAAC,MAAM;QACtB,MAAM,EAAE,MAAM;KACjB,CAAC,CAAC;IAEH,OAAO,CAAC,GAAG,CAAC,GAAG,IAAI,KAAK,OAAO,EAAE,CAAC,CAAC;IACnC,OAAO,CAAC,GAAG,CACP,OAAO,MAAM,CAAC,QAAQ,SAAS,MAAM,CAAC,KAAK,SAAS,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,CAC/E,CAAC;IACF,OAAO,CAAC,GAAG,CAAC,kCAAkC,CAAC,CAAC;IAChD,EAAE,CAAC,MAAM,EAAE,CAAC;IAEZ,EAAE,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE;QACnB,MAAM,KAAK,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;QAE1B,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACrB,gBAAgB;YAChB,EAAE,CAAC,MAAM,EAAE,CAAC;YACZ,OAAO;QACX,CAAC;QAED,IAAI,aAAa,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC;YAC3B,EAAE,CAAC,KAAK,EAAE,CAAC;YACX,OAAO;QACX,CAAC;QAED,yBAAyB;QACzB,OAAO,CAAC,GAAG,CAAC,MAAM,KAAK,EAAE,CAAC,CAAC;QAE3B,EAAE,CAAC,MAAM,EAAE,CAAC;IAChB,CAAC,CAAC,CAAC;IAEH,6BAA6B;IAC7B,EAAE,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE;QAChB,OAAO,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;QACvB,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACpB,CAAC,CAAC,CAAC;IAEH,0BAA0B;IAC1B,EAAE,CAAC,EAAE,CAAC,QAAQ,EAAE,GAAG,EAAE;QACjB,EAAE,CAAC,KAAK,EAAE,CAAC;IACf,CAAC,CAAC,CAAC;AACP,CAAC"}
1
+ {"version":3,"file":"repl.js","sourceRoot":"","sources":["../src/repl.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gird/ccli",
3
- "version": "2.0.1",
3
+ "version": "2.0.2",
4
4
  "description": "CCLI —— 一步一步从零实现的代理型(Agentic)终端 AI 编程助手",
5
5
  "type": "module",
6
6
  "bin": {
@@ -43,5 +43,8 @@
43
43
  "@types/node": "^25.9.3",
44
44
  "tsx": "^4.22.4",
45
45
  "typescript": "^6.0.3"
46
+ },
47
+ "dependencies": {
48
+ "openai": "^6.45.0"
46
49
  }
47
50
  }