@gird/ccli 2.1.1 → 2.2.0

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
@@ -27,6 +27,11 @@ ccli --help # 打印帮助
27
27
  - 直接输入即与 AI 对话,模型会记住上文(多轮对话)。
28
28
  - 模型可调用的工具:读文件、列目录、搜索代码、写文件、局部编辑文件、执行 shell 命令(写入/编辑/执行操作会请你确认)。
29
29
  - `/help` 查看可用命令,`/clear` 清空历史,`/undo [n]` 撤销最近 n 轮对话。
30
+ - `/save [name]` 保存当前会话,`/load <name>` 加载会话(按 `Tab` 补全/列出候选),`/sessions` 列出已保存会话。
31
+ - `/stats` 查看当前会话累计 token 消耗与上下文占比(估算值)。
32
+ - 按 `↑` / `↓` recalled 之前输入过的 prompt / 命令,历史自动保存到 `~/.ccli/history`。
33
+ - 粘贴多行文本时会自动合并成一条消息发送,不会被截断。
34
+ - 退出时自动保存 `last` 恢复点;下次启动会询问是否恢复上次会话。
30
35
  - `/exit` 或 `/quit` 退出,也可按 Ctrl-C / Ctrl-D。
31
36
 
32
37
  ## 配置
@@ -42,6 +47,14 @@ export QWEN_MODEL=qwen-plus
42
47
  # 或通用:OPENAI_API_KEY / OPENAI_BASE_URL / OPENAI_MODEL
43
48
  ```
44
49
 
50
+ **Anthropic Claude**
51
+
52
+ ```bash
53
+ export ANTHROPIC_API_KEY=sk-ant-...
54
+ export ANTHROPIC_MODEL=claude-sonnet-4-5
55
+ # 可选:ANTHROPIC_BASE_URL
56
+ ```
57
+
45
58
  ## 许可
46
59
 
47
60
  [MIT](./LICENSE)
package/dist/agent/ai.js CHANGED
@@ -1,15 +1,27 @@
1
1
  /**
2
2
  * AI 客户端 —— 把配置变成「能对话的对象」。
3
3
  *
4
- * 支持流式输出与工具调用(function calling)。
5
- * 目前只对接 OpenAI 兼容后端(通义千问 / DeepSeek / GLM…);Anthropic 留待后续步骤。
4
+ * 支持流式输出与工具调用(function calling / tool use)。
5
+ * 目前对接两种后端:
6
+ * - OpenAI 兼容后端(通义千问 / DeepSeek / GLM…)
7
+ * - Anthropic Claude
8
+ *
9
+ * 对外统一为 AIClient 接口;内部由 createAIClient 根据 provider 选择实现。
6
10
  *
7
11
  * 流式(stream)的意义:模型边生成边返回,我们边收边打印,
8
12
  * 用户不用干等一整段生成完,交互感更接近「打字机」。
9
13
  */
10
14
  import OpenAI from 'openai';
15
+ import Anthropic from '@anthropic-ai/sdk';
11
16
  import { toolDefs } from '../tools/index.js';
12
- export class AIClient {
17
+ /** 根据配置创建对应后端的客户端。 */
18
+ export function createAIClient(config) {
19
+ if (config.provider === 'anthropic') {
20
+ return new AnthropicClient(config);
21
+ }
22
+ return new OpenAIClient(config);
23
+ }
24
+ class OpenAIClient {
13
25
  client;
14
26
  model;
15
27
  constructor(config) {
@@ -41,19 +53,31 @@ export class AIClient {
41
53
  * @returns 助手本回合的文本 + 它想调用的工具
42
54
  */
43
55
  async chat(messages, onDelta) {
44
- const stream = await this.client.chat.completions.create({
56
+ const stream = (await this.client.chat.completions.create({
45
57
  model: this.model,
46
58
  messages,
47
59
  tools: toolDefs(),
48
60
  stream: true,
49
- });
61
+ stream_options: { include_usage: true },
62
+ }));
50
63
  let content = '';
51
64
  // 工具调用在流里是分片到达的,按 index 累加拼回完整的 id/name/arguments
52
65
  const acc = {};
66
+ let usage;
53
67
  for await (const chunk of stream) {
54
68
  const delta = chunk.choices[0]?.delta;
55
- if (!delta)
69
+ if (!delta) {
70
+ // OpenAI 流式 usage 通常在 choices 为空的最终 chunk 里
71
+ const u = chunk.usage;
72
+ if (u) {
73
+ usage = {
74
+ promptTokens: u.prompt_tokens ?? 0,
75
+ completionTokens: u.completion_tokens ?? 0,
76
+ totalTokens: u.total_tokens ?? 0,
77
+ };
78
+ }
56
79
  continue;
80
+ }
57
81
  if (delta.content) {
58
82
  content += delta.content;
59
83
  onDelta(delta.content);
@@ -76,7 +100,170 @@ export class AIClient {
76
100
  .map(Number)
77
101
  .sort((a, b) => a - b)
78
102
  .map((i) => acc[i]);
79
- return { content, toolCalls };
103
+ return { content, toolCalls, usage };
104
+ }
105
+ }
106
+ class AnthropicClient {
107
+ client;
108
+ model;
109
+ constructor(config) {
110
+ this.client = new Anthropic({
111
+ apiKey: config.apiKey,
112
+ baseURL: config.baseURL,
113
+ });
114
+ this.model = config.model;
115
+ }
116
+ getModel() {
117
+ return this.model;
118
+ }
119
+ setModel(model) {
120
+ this.model = model;
121
+ }
122
+ /**
123
+ * Anthropic SDK 没有稳定的模型列表接口,返回常用模型。
124
+ * 用户仍可用 /model <完整名> 切换到列表外的模型。
125
+ */
126
+ async listModels() {
127
+ return [
128
+ 'claude-opus-4-5-20251101',
129
+ 'claude-sonnet-4-5-20251101',
130
+ 'claude-sonnet-4-5-20241022',
131
+ 'claude-3-5-sonnet-20241022',
132
+ 'claude-3-opus-20240229',
133
+ 'claude-3-sonnet-20240229',
134
+ 'claude-3-haiku-20240307',
135
+ ].sort();
136
+ }
137
+ async chat(messages, onDelta) {
138
+ const { system, messages: anthropicMessages } = toAnthropicMessages(messages);
139
+ const tools = toAnthropicTools();
140
+ const stream = await this.client.messages.create({
141
+ model: this.model,
142
+ max_tokens: 4096,
143
+ system,
144
+ messages: anthropicMessages,
145
+ tools,
146
+ stream: true,
147
+ });
148
+ let content = '';
149
+ const acc = {};
150
+ let usage;
151
+ for await (const event of stream) {
152
+ if (event.type === 'content_block_delta') {
153
+ if (event.delta.type === 'text_delta') {
154
+ content += event.delta.text;
155
+ onDelta(event.delta.text);
156
+ }
157
+ else if (event.delta.type === 'input_json_delta') {
158
+ const tc = acc[event.index];
159
+ if (tc)
160
+ tc.arguments += event.delta.partial_json;
161
+ }
162
+ }
163
+ else if (event.type === 'content_block_start') {
164
+ if (event.content_block.type === 'tool_use') {
165
+ acc[event.index] = {
166
+ id: event.content_block.id,
167
+ name: event.content_block.name,
168
+ arguments: '',
169
+ };
170
+ }
171
+ }
172
+ else if (event.type === 'message_delta') {
173
+ const u = event.usage;
174
+ if (u) {
175
+ usage = {
176
+ promptTokens: u.input_tokens ?? 0,
177
+ completionTokens: u.output_tokens ?? 0,
178
+ totalTokens: (u.input_tokens ?? 0) + (u.output_tokens ?? 0),
179
+ };
180
+ }
181
+ }
182
+ }
183
+ const toolCalls = Object.keys(acc)
184
+ .map(Number)
185
+ .sort((a, b) => a - b)
186
+ .map((i) => acc[i]);
187
+ return { content, toolCalls, usage };
188
+ }
189
+ }
190
+ /** 把 OpenAI 风格的消息列表转成 Anthropic 的 system + messages。 */
191
+ export function toAnthropicMessages(messages) {
192
+ const systemParts = [];
193
+ const anthropicMessages = [];
194
+ for (const m of messages) {
195
+ if (m.role === 'system') {
196
+ if (typeof m.content === 'string') {
197
+ systemParts.push(m.content);
198
+ }
199
+ continue;
200
+ }
201
+ if (m.role === 'tool') {
202
+ // Anthropic 的 tool_result 必须放在 user 消息里
203
+ const toolCallId = m.tool_call_id ?? '';
204
+ const resultText = typeof m.content === 'string' ? m.content : JSON.stringify(m.content);
205
+ anthropicMessages.push({
206
+ role: 'user',
207
+ content: [
208
+ {
209
+ type: 'tool_result',
210
+ tool_use_id: toolCallId,
211
+ content: resultText,
212
+ },
213
+ ],
214
+ });
215
+ continue;
216
+ }
217
+ if (m.role === 'assistant') {
218
+ const content = [];
219
+ if (typeof m.content === 'string' && m.content) {
220
+ content.push({ type: 'text', text: m.content });
221
+ }
222
+ const toolCalls = m.tool_calls;
223
+ if (Array.isArray(toolCalls)) {
224
+ for (const tc of toolCalls) {
225
+ content.push({
226
+ type: 'tool_use',
227
+ id: tc.id,
228
+ name: tc.function?.name ?? '',
229
+ input: safeParse(tc.function?.arguments ?? '{}'),
230
+ });
231
+ }
232
+ }
233
+ anthropicMessages.push({ role: 'assistant', content });
234
+ continue;
235
+ }
236
+ if (m.role === 'user') {
237
+ if (typeof m.content === 'string') {
238
+ anthropicMessages.push({ role: 'user', content: m.content });
239
+ }
240
+ else {
241
+ anthropicMessages.push({
242
+ role: 'user',
243
+ content: JSON.stringify(m.content),
244
+ });
245
+ }
246
+ }
247
+ }
248
+ return { system: systemParts.join('\n\n'), messages: anthropicMessages };
249
+ }
250
+ /** 把内部工具定义转成 Anthropic 的 tool 格式。 */
251
+ export function toAnthropicTools() {
252
+ return toolDefs().map((t) => ({
253
+ name: t.function.name,
254
+ description: t.function.description,
255
+ input_schema: {
256
+ type: 'object',
257
+ ...t.function.parameters,
258
+ },
259
+ }));
260
+ }
261
+ function safeParse(json) {
262
+ try {
263
+ return JSON.parse(json);
264
+ }
265
+ catch {
266
+ return {};
80
267
  }
81
268
  }
82
269
  //# sourceMappingURL=ai.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"ai.js","sourceRoot":"","sources":["../../src/agent/ai.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"ai.js","sourceRoot":"","sources":["../../src/agent/ai.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,MAAM,MAAM,QAAQ,CAAC;AAC5B,OAAO,SAAS,MAAM,mBAAmB,CAAC;AAE1C,OAAO,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAgD7C,sBAAsB;AACtB,MAAM,UAAU,cAAc,CAAC,MAAc;IACzC,IAAI,MAAM,CAAC,QAAQ,KAAK,WAAW,EAAE,CAAC;QAClC,OAAO,IAAI,eAAe,CAAC,MAAM,CAAC,CAAC;IACvC,CAAC;IACD,OAAO,IAAI,YAAY,CAAC,MAAM,CAAC,CAAC;AACpC,CAAC;AAED,MAAM,YAAY;IACN,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,QAAQ;QACJ,OAAO,IAAI,CAAC,KAAK,CAAC;IACtB,CAAC;IAED,QAAQ,CAAC,KAAa;QAClB,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;IACvB,CAAC;IAED,+BAA+B;IACvB,UAAU,CAAY;IAC9B,KAAK,CAAC,UAAU;QACZ,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,CAAC;YACnB,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC;YAC7C,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;QACxD,CAAC;QACD,OAAO,IAAI,CAAC,UAAU,CAAC;IAC3B,CAAC;IAED;;;;;OAKG;IACH,KAAK,CAAC,IAAI,CACN,QAAuB,EACvB,OAA+B;QAE/B,MAAM,MAAM,GAAG,CAAC,MAAO,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,WAAW,CAAC,MAAc,CAAC;YAC/D,KAAK,EAAE,IAAI,CAAC,KAAK;YACjB,QAAQ;YACR,KAAK,EAAE,QAAQ,EAAE;YACjB,MAAM,EAAE,IAAI;YACZ,cAAc,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE;SAC1C,CAAC,CAA+D,CAAC;QAElE,IAAI,OAAO,GAAG,EAAE,CAAC;QACjB,kDAAkD;QAClD,MAAM,GAAG,GAA6B,EAAE,CAAC;QACzC,IAAI,KAAwB,CAAC;QAE7B,IAAI,KAAK,EAAE,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;YAC/B,MAAM,KAAK,GAAG,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC;YACtC,IAAI,CAAC,KAAK,EAAE,CAAC;gBACT,4CAA4C;gBAC5C,MAAM,CAAC,GAAI,KAAa,CAAC,KAAK,CAAC;gBAC/B,IAAI,CAAC,EAAE,CAAC;oBACJ,KAAK,GAAG;wBACJ,YAAY,EAAE,CAAC,CAAC,aAAa,IAAI,CAAC;wBAClC,gBAAgB,EAAE,CAAC,CAAC,iBAAiB,IAAI,CAAC;wBAC1C,WAAW,EAAE,CAAC,CAAC,YAAY,IAAI,CAAC;qBACnC,CAAC;gBACN,CAAC;gBACD,SAAS;YACb,CAAC;YAED,IAAI,KAAK,CAAC,OAAO,EAAE,CAAC;gBAChB,OAAO,IAAI,KAAK,CAAC,OAAO,CAAC;gBACzB,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;YAC3B,CAAC;YAED,KAAK,MAAM,EAAE,IAAI,KAAK,CAAC,UAAU,IAAI,EAAE,EAAE,CAAC;gBACtC,MAAM,GAAG,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,KAAK,CAAC,KAAK;oBAC3B,EAAE,EAAE,EAAE;oBACN,IAAI,EAAE,EAAE;oBACR,SAAS,EAAE,EAAE;iBAChB,CAAC,CAAC;gBACH,IAAI,EAAE,CAAC,EAAE;oBAAE,GAAG,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,CAAC;gBAC1B,IAAI,EAAE,CAAC,QAAQ,EAAE,IAAI;oBAAE,GAAG,CAAC,IAAI,GAAG,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC;gBACnD,IAAI,EAAE,CAAC,QAAQ,EAAE,SAAS;oBAAE,GAAG,CAAC,SAAS,IAAI,EAAE,CAAC,QAAQ,CAAC,SAAS,CAAC;YACvE,CAAC;QACL,CAAC;QAED,MAAM,SAAS,GAAG,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC;aAC7B,GAAG,CAAC,MAAM,CAAC;aACX,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC;aACrB,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;QAExB,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC;IACzC,CAAC;CACJ;AAED,MAAM,eAAe;IACT,MAAM,CAAY;IAClB,KAAK,CAAS;IAEtB,YAAY,MAAc;QACtB,IAAI,CAAC,MAAM,GAAG,IAAI,SAAS,CAAC;YACxB,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,QAAQ;QACJ,OAAO,IAAI,CAAC,KAAK,CAAC;IACtB,CAAC;IAED,QAAQ,CAAC,KAAa;QAClB,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;IACvB,CAAC;IAED;;;OAGG;IACH,KAAK,CAAC,UAAU;QACZ,OAAO;YACH,0BAA0B;YAC1B,4BAA4B;YAC5B,4BAA4B;YAC5B,4BAA4B;YAC5B,wBAAwB;YACxB,0BAA0B;YAC1B,yBAAyB;SAC5B,CAAC,IAAI,EAAE,CAAC;IACb,CAAC;IAED,KAAK,CAAC,IAAI,CACN,QAAuB,EACvB,OAA+B;QAE/B,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,iBAAiB,EAAE,GAAG,mBAAmB,CAAC,QAAQ,CAAC,CAAC;QAC9E,MAAM,KAAK,GAAG,gBAAgB,EAAE,CAAC;QAEjC,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC;YAC7C,KAAK,EAAE,IAAI,CAAC,KAAK;YACjB,UAAU,EAAE,IAAI;YAChB,MAAM;YACN,QAAQ,EAAE,iBAAiB;YAC3B,KAAK;YACL,MAAM,EAAE,IAAI;SACf,CAAC,CAAC;QAEH,IAAI,OAAO,GAAG,EAAE,CAAC;QACjB,MAAM,GAAG,GAA6B,EAAE,CAAC;QACzC,IAAI,KAAwB,CAAC;QAE7B,IAAI,KAAK,EAAE,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;YAC/B,IAAI,KAAK,CAAC,IAAI,KAAK,qBAAqB,EAAE,CAAC;gBACvC,IAAI,KAAK,CAAC,KAAK,CAAC,IAAI,KAAK,YAAY,EAAE,CAAC;oBACpC,OAAO,IAAI,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC;oBAC5B,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;gBAC9B,CAAC;qBAAM,IAAI,KAAK,CAAC,KAAK,CAAC,IAAI,KAAK,kBAAkB,EAAE,CAAC;oBACjD,MAAM,EAAE,GAAG,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;oBAC5B,IAAI,EAAE;wBAAE,EAAE,CAAC,SAAS,IAAI,KAAK,CAAC,KAAK,CAAC,YAAY,CAAC;gBACrD,CAAC;YACL,CAAC;iBAAM,IAAI,KAAK,CAAC,IAAI,KAAK,qBAAqB,EAAE,CAAC;gBAC9C,IAAI,KAAK,CAAC,aAAa,CAAC,IAAI,KAAK,UAAU,EAAE,CAAC;oBAC1C,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG;wBACf,EAAE,EAAE,KAAK,CAAC,aAAa,CAAC,EAAE;wBAC1B,IAAI,EAAE,KAAK,CAAC,aAAa,CAAC,IAAI;wBAC9B,SAAS,EAAE,EAAE;qBAChB,CAAC;gBACN,CAAC;YACL,CAAC;iBAAM,IAAI,KAAK,CAAC,IAAI,KAAK,eAAe,EAAE,CAAC;gBACxC,MAAM,CAAC,GAAI,KAAa,CAAC,KAAK,CAAC;gBAC/B,IAAI,CAAC,EAAE,CAAC;oBACJ,KAAK,GAAG;wBACJ,YAAY,EAAE,CAAC,CAAC,YAAY,IAAI,CAAC;wBACjC,gBAAgB,EAAE,CAAC,CAAC,aAAa,IAAI,CAAC;wBACtC,WAAW,EACP,CAAC,CAAC,CAAC,YAAY,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,aAAa,IAAI,CAAC,CAAC;qBACrD,CAAC;gBACN,CAAC;YACL,CAAC;QACL,CAAC;QAED,MAAM,SAAS,GAAG,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC;aAC7B,GAAG,CAAC,MAAM,CAAC;aACX,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC;aACrB,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;QACxB,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC;IACzC,CAAC;CACJ;AAED,wDAAwD;AACxD,MAAM,UAAU,mBAAmB,CAAC,QAAuB;IAIvD,MAAM,WAAW,GAAa,EAAE,CAAC;IACjC,MAAM,iBAAiB,GAAsC,EAAE,CAAC;IAEhE,KAAK,MAAM,CAAC,IAAI,QAAQ,EAAE,CAAC;QACvB,IAAI,CAAC,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;YACtB,IAAI,OAAO,CAAC,CAAC,OAAO,KAAK,QAAQ,EAAE,CAAC;gBAChC,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC;YAChC,CAAC;YACD,SAAS;QACb,CAAC;QAED,IAAI,CAAC,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC;YACpB,wCAAwC;YACxC,MAAM,UAAU,GAAI,CAAS,CAAC,YAAY,IAAI,EAAE,CAAC;YACjD,MAAM,UAAU,GACZ,OAAO,CAAC,CAAC,OAAO,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC;YAC1E,iBAAiB,CAAC,IAAI,CAAC;gBACnB,IAAI,EAAE,MAAM;gBACZ,OAAO,EAAE;oBACL;wBACI,IAAI,EAAE,aAAa;wBACnB,WAAW,EAAE,UAAU;wBACvB,OAAO,EAAE,UAAU;qBACqB;iBAC/C;aACJ,CAAC,CAAC;YACH,SAAS;QACb,CAAC;QAED,IAAI,CAAC,CAAC,IAAI,KAAK,WAAW,EAAE,CAAC;YACzB,MAAM,OAAO,GAA2C,EAAE,CAAC;YAC3D,IAAI,OAAO,CAAC,CAAC,OAAO,KAAK,QAAQ,IAAI,CAAC,CAAC,OAAO,EAAE,CAAC;gBAC7C,OAAO,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC;YACpD,CAAC;YAED,MAAM,SAAS,GAAI,CAAS,CAAC,UAAU,CAAC;YACxC,IAAI,KAAK,CAAC,OAAO,CAAC,SAAS,CAAC,EAAE,CAAC;gBAC3B,KAAK,MAAM,EAAE,IAAI,SAAS,EAAE,CAAC;oBACzB,OAAO,CAAC,IAAI,CAAC;wBACT,IAAI,EAAE,UAAU;wBAChB,EAAE,EAAE,EAAE,CAAC,EAAE;wBACT,IAAI,EAAE,EAAE,CAAC,QAAQ,EAAE,IAAI,IAAI,EAAE;wBAC7B,KAAK,EAAE,SAAS,CAAC,EAAE,CAAC,QAAQ,EAAE,SAAS,IAAI,IAAI,CAAC;qBACX,CAAC,CAAC;gBAC/C,CAAC;YACL,CAAC;YAED,iBAAiB,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,WAAW,EAAE,OAAO,EAAE,CAAC,CAAC;YACvD,SAAS;QACb,CAAC;QAED,IAAI,CAAC,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC;YACpB,IAAI,OAAO,CAAC,CAAC,OAAO,KAAK,QAAQ,EAAE,CAAC;gBAChC,iBAAiB,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC;YACjE,CAAC;iBAAM,CAAC;gBACJ,iBAAiB,CAAC,IAAI,CAAC;oBACnB,IAAI,EAAE,MAAM;oBACZ,OAAO,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,OAAO,CAAC;iBACrC,CAAC,CAAC;YACP,CAAC;QACL,CAAC;IACL,CAAC;IAED,OAAO,EAAE,MAAM,EAAE,WAAW,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,QAAQ,EAAE,iBAAiB,EAAE,CAAC;AAC7E,CAAC;AAED,qCAAqC;AACrC,MAAM,UAAU,gBAAgB;IAC5B,OAAO,QAAQ,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QAC1B,IAAI,EAAE,CAAC,CAAC,QAAQ,CAAC,IAAI;QACrB,WAAW,EAAE,CAAC,CAAC,QAAQ,CAAC,WAAW;QACnC,YAAY,EAAE;YACV,IAAI,EAAE,QAAiB;YACvB,GAAG,CAAC,CAAC,QAAQ,CAAC,UAAU;SAC3B;KACJ,CAAC,CAAC,CAAC;AACR,CAAC;AAED,SAAS,SAAS,CAAC,IAAY;IAC3B,IAAI,CAAC;QACD,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAC5B,CAAC;IAAC,MAAM,CAAC;QACL,OAAO,EAAE,CAAC;IACd,CAAC;AACL,CAAC"}
@@ -0,0 +1,97 @@
1
+ /**
2
+ * REPL 输入历史持久化。
3
+ *
4
+ * 把用户在交互式会话里输入过的非空文本按行保存到 ~/.ccli/history,
5
+ * 下次启动时加载到 readline 的 history 数组,支持上下箭头 recalled。
6
+ */
7
+ import { homedir } from 'node:os';
8
+ import { dirname, join } from 'node:path';
9
+ import { appendFileSync, existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync, } from 'node:fs';
10
+ const DEFAULT_HISTORY_PATH = join(homedir(), '.ccli', 'history');
11
+ /** 当前使用的历史文件路径(可被测试覆盖)。 */
12
+ let historyPath = DEFAULT_HISTORY_PATH;
13
+ /** 仅供测试:临时切换历史文件路径。 */
14
+ export function setHistoryPathForTest(path) {
15
+ historyPath = path;
16
+ }
17
+ /** 仅供测试:恢复默认历史文件路径。 */
18
+ export function resetHistoryPathForTest() {
19
+ historyPath = DEFAULT_HISTORY_PATH;
20
+ }
21
+ /** 确保历史文件所在目录存在。 */
22
+ function ensureDir() {
23
+ const dir = dirname(historyPath);
24
+ if (!existsSync(dir)) {
25
+ mkdirSync(dir, { recursive: true });
26
+ }
27
+ }
28
+ /**
29
+ * 加载历史输入列表。
30
+ * 文件不存在或读取失败时返回空数组。
31
+ */
32
+ export function loadHistory() {
33
+ if (!existsSync(historyPath)) {
34
+ return [];
35
+ }
36
+ try {
37
+ const raw = readFileSync(historyPath, 'utf8');
38
+ const lines = raw
39
+ .split('\n')
40
+ .map((line) => line.trimEnd())
41
+ .filter((line) => line.length > 0);
42
+ // readline 把最新输入放在 history[0],所以文件按时间顺序加载后要反转
43
+ return lines.reverse();
44
+ }
45
+ catch {
46
+ return [];
47
+ }
48
+ }
49
+ /**
50
+ * 追加一条输入到历史文件。
51
+ * 空字符串或仅空白字符的输入会被忽略。
52
+ */
53
+ export function appendHistory(line) {
54
+ const trimmed = line.trim();
55
+ if (trimmed.length === 0) {
56
+ return;
57
+ }
58
+ try {
59
+ ensureDir();
60
+ appendFileSync(historyPath, `${trimmed}\n`, 'utf8');
61
+ }
62
+ catch {
63
+ // 历史保存失败不打扰用户
64
+ }
65
+ }
66
+ /**
67
+ * 清空历史文件。
68
+ * 用于测试或后续可能的 /clear-history 命令。
69
+ */
70
+ export function clearHistory() {
71
+ try {
72
+ if (existsSync(historyPath)) {
73
+ unlinkSync(historyPath);
74
+ }
75
+ }
76
+ catch {
77
+ // 忽略清理失败
78
+ }
79
+ }
80
+ /**
81
+ * 用完整列表覆盖历史文件。
82
+ * 主要用于测试或批量导入场景。
83
+ */
84
+ export function saveHistory(lines) {
85
+ try {
86
+ ensureDir();
87
+ const content = lines
88
+ .map((line) => line.trim())
89
+ .filter((line) => line.length > 0)
90
+ .join('\n');
91
+ writeFileSync(historyPath, content ? `${content}\n` : '', 'utf8');
92
+ }
93
+ catch {
94
+ // 忽略保存失败
95
+ }
96
+ }
97
+ //# sourceMappingURL=history.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"history.js","sourceRoot":"","sources":["../../src/cli/history.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,50 @@
1
+ /**
2
+ * REPL 多行输入缓冲。
3
+ *
4
+ * 把短时间内连续到达的多行文本合并成一条输入,
5
+ * 解决用户粘贴多行内容时被 readline 拆散的问题。
6
+ */
7
+ /**
8
+ * 输入缓冲区。
9
+ *
10
+ * 每次收到一行先累积;在 debounceMs 内没有新行到达时,
11
+ * 把累积的多行按 \n 合并后触发回调。
12
+ */
13
+ export class InputBuffer {
14
+ options;
15
+ lines = [];
16
+ timer = null;
17
+ constructor(options) {
18
+ this.options = options;
19
+ }
20
+ /** 放入一行新输入。 */
21
+ push(line) {
22
+ this.lines.push(line);
23
+ if (this.timer) {
24
+ clearTimeout(this.timer);
25
+ }
26
+ this.timer = setTimeout(() => this.flush(), this.options.debounceMs);
27
+ }
28
+ /** 立即 flush 当前缓冲区。 */
29
+ flush() {
30
+ this.timer = null;
31
+ const raw = this.lines.join('\n');
32
+ this.lines = [];
33
+ const input = raw.trim();
34
+ if (input.length === 0) {
35
+ this.options.onEmpty();
36
+ }
37
+ else {
38
+ this.options.onInput(input);
39
+ }
40
+ }
41
+ /** 清空缓冲区和未触发的定时器。 */
42
+ clear() {
43
+ if (this.timer) {
44
+ clearTimeout(this.timer);
45
+ this.timer = null;
46
+ }
47
+ this.lines = [];
48
+ }
49
+ }
50
+ //# sourceMappingURL=input.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"input.js","sourceRoot":"","sources":["../../src/cli/input.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAWH;;;;;GAKG;AACH,MAAM,OAAO,WAAW;IAIA;IAHZ,KAAK,GAAa,EAAE,CAAC;IACrB,KAAK,GAA0B,IAAI,CAAC;IAE5C,YAAoB,OAA2B;QAA3B,YAAO,GAAP,OAAO,CAAoB;IAAG,CAAC;IAEnD,eAAe;IACf,IAAI,CAAC,IAAY;QACb,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACtB,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;YACb,YAAY,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAC7B,CAAC;QACD,IAAI,CAAC,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,KAAK,EAAE,EAAE,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;IACzE,CAAC;IAED,sBAAsB;IACtB,KAAK;QACD,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC;QAClB,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAClC,IAAI,CAAC,KAAK,GAAG,EAAE,CAAC;QAChB,MAAM,KAAK,GAAG,GAAG,CAAC,IAAI,EAAE,CAAC;QACzB,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACrB,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC;QAC3B,CAAC;aAAM,CAAC;YACJ,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;QAChC,CAAC;IACL,CAAC;IAED,qBAAqB;IACrB,KAAK;QACD,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;YACb,YAAY,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YACzB,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC;QACtB,CAAC;QACD,IAAI,CAAC,KAAK,GAAG,EAAE,CAAC;IACpB,CAAC;CACJ"}
package/dist/cli/repl.js CHANGED
@@ -8,9 +8,13 @@
8
8
  import { createInterface } from 'node:readline';
9
9
  import { NAME, VERSION } from '../version.js';
10
10
  import { loadConfig, maskKey } from '../config/config.js';
11
- import { AIClient } from '../agent/ai.js';
12
- import { handleSlash } from './slash.js';
13
- import { executeTool, getTool } from '../tools/index.js';
11
+ import { createAIClient } from '../agent/ai.js';
12
+ import { handleSlash, slashCompleter } from './slash.js';
13
+ import { executeTool, getTool, } from '../tools/index.js';
14
+ import { hasLastSession, loadLastSession, saveLastSession, clearLastSession, } from './session.js';
15
+ import { appendHistory, loadHistory } from './history.js';
16
+ import { InputBuffer } from './input.js';
17
+ import { estimatePromptTokens, estimateTokens, emptyStats, formatStats, } from './stats.js';
14
18
  const PROMPT = '› ';
15
19
  const SYSTEM_PROMPT = '你是 CCLI,一个运行在终端里的 AI 助手。回答简洁、准确,使用中文。' +
16
20
  '你可以调用工具读文件、列目录、搜索(read_file / list_dir / grep),' +
@@ -18,6 +22,12 @@ const SYSTEM_PROMPT = '你是 CCLI,一个运行在终端里的 AI 助手。回
18
22
  '需要查看或修改文件、运行测试/构建/命令时请调用工具,不要凭空猜测。';
19
23
  /** 单个用户回合内,最多允许模型连续调用工具的轮数,防止失控循环。 */
20
24
  const MAX_TOOL_ROUNDS = 10;
25
+ /** 用 readline 提一个问题,返回用户输入。 */
26
+ function question(rl, prompt) {
27
+ return new Promise((resolve) => {
28
+ rl.question(prompt, (ans) => resolve(ans));
29
+ });
30
+ }
21
31
  /**
22
32
  * 解析工具参数用于预览;失败则返回空对象(真正执行时 executeTool 会再报错)。
23
33
  */
@@ -33,9 +43,22 @@ function safeParse(argsJson) {
33
43
  * 驱动一个用户回合:请求模型 → 若它要调用工具则(必要时确认后)执行并回传 → 再请求 …
34
44
  * 直到模型不再调用工具(给出最终回答)或达到轮数上限。
35
45
  */
36
- async function runAgentTurn(ai, history, confirm) {
46
+ async function runAgentTurn(ai, history, confirm, stats) {
37
47
  for (let round = 0; round < MAX_TOOL_ROUNDS; round++) {
38
48
  const turn = await ai.chat(history, (delta) => process.stdout.write(delta));
49
+ // 优先使用后端返回的真实 usage;不支持时降级为字符估算
50
+ if (turn.usage) {
51
+ stats.promptTokens += turn.usage.promptTokens;
52
+ stats.completionTokens += turn.usage.completionTokens;
53
+ stats.totalTokens += turn.usage.totalTokens;
54
+ }
55
+ else {
56
+ const promptTokens = estimatePromptTokens(history);
57
+ const completionTokens = estimateTokens(turn.content);
58
+ stats.promptTokens += promptTokens;
59
+ stats.completionTokens += completionTokens;
60
+ stats.totalTokens += promptTokens + completionTokens;
61
+ }
39
62
  // 没有工具调用 → 这是最终回答,收尾退出
40
63
  if (turn.toolCalls.length === 0) {
41
64
  process.stdout.write('\n');
@@ -83,7 +106,7 @@ async function runAgentTurn(ai, history, confirm) {
83
106
  }
84
107
  console.log('\n(已达到工具调用轮数上限)');
85
108
  }
86
- export function startRepl() {
109
+ export async function startRepl() {
87
110
  // 启动即解析配置:缺失就明确报错退出,不进入循环
88
111
  const result = loadConfig();
89
112
  if (!result.ok) {
@@ -91,16 +114,39 @@ export function startRepl() {
91
114
  process.exit(1);
92
115
  }
93
116
  const { config } = result;
94
- const ai = new AIClient(config);
117
+ const ai = createAIClient(config);
95
118
  // 对话历史:以 system 提示词开场,之后累积 user / assistant 轮次
96
119
  const history = [{ role: 'system', content: SYSTEM_PROMPT }];
120
+ let stats = emptyStats();
97
121
  const rl = createInterface({
98
122
  input: process.stdin,
99
123
  output: process.stdout,
100
124
  prompt: PROMPT,
125
+ completer: slashCompleter,
126
+ history: loadHistory(),
127
+ historySize: 1000,
101
128
  });
102
129
  console.log(`${NAME} v${VERSION}`);
103
130
  console.log(`后端: ${config.provider} 模型: ${config.model} 密钥: ${maskKey(config.apiKey)}`);
131
+ // 如果有上次会话,询问是否恢复
132
+ if (hasLastSession()) {
133
+ const restore = await question(rl, '发现上次会话,是否恢复?(y/N) ');
134
+ if (restore.trim().toLowerCase() === 'y' || restore.trim() === 'yes') {
135
+ const loaded = loadLastSession();
136
+ if (loaded.ok) {
137
+ history.length = 0;
138
+ history.push(...loaded.history);
139
+ console.log(`已恢复上次会话(${loaded.history.length} 条消息)。`);
140
+ }
141
+ else {
142
+ console.log(`恢复失败:${loaded.message}`);
143
+ }
144
+ }
145
+ else {
146
+ clearLastSession();
147
+ console.log('已清空上次会话,开始新对话。');
148
+ }
149
+ }
104
150
  console.log('进入交互式会话。输入 /help 查看命令,/exit 退出。\n');
105
151
  rl.prompt();
106
152
  // 请用户确认破坏性操作:展示预览 + 读一行 y/N(默认 N,更安全)
@@ -113,13 +159,11 @@ export function startRepl() {
113
159
  });
114
160
  // 正在等待 AI 回复时置 true,避免这期间的输入被重复处理
115
161
  let busy = false;
116
- rl.on('line', async (line) => {
117
- const input = line.trim();
118
- if (busy || input.length === 0) {
119
- if (!busy)
120
- rl.prompt();
121
- return;
122
- }
162
+ const INPUT_DEBOUNCE_MS = 80;
163
+ /** 处理一条已合并好的用户输入(斜杠命令或普通消息)。 */
164
+ async function processUserInput(input) {
165
+ // 非空输入追加到持久化历史(readline 内存历史已自动维护)
166
+ appendHistory(input);
123
167
  // 斜杠命令:交给 handleSlash,不发给 AI
124
168
  // (/model 会联网列模型,故也置 busy 防止期间重复处理)
125
169
  if (input.startsWith('/')) {
@@ -128,6 +172,7 @@ export function startRepl() {
128
172
  await handleSlash(input, {
129
173
  history,
130
174
  ai,
175
+ stats,
131
176
  requestExit: () => {
132
177
  exiting = true;
133
178
  rl.close();
@@ -142,7 +187,9 @@ export function startRepl() {
142
187
  const mark = history.length; // 出错时回滚到这里,避免半截上下文
143
188
  history.push({ role: 'user', content: input });
144
189
  try {
145
- await runAgentTurn(ai, history, confirm);
190
+ await runAgentTurn(ai, history, confirm, stats);
191
+ // 本回合结束,显示本次 token 消耗
192
+ console.log(`\n${formatStats(stats, ai.getModel())}`);
146
193
  }
147
194
  catch (e) {
148
195
  console.error(`\n[请求失败] ${e?.message ?? e}`);
@@ -152,9 +199,22 @@ export function startRepl() {
152
199
  busy = false;
153
200
  rl.prompt();
154
201
  }
202
+ }
203
+ const inputBuffer = new InputBuffer({
204
+ debounceMs: INPUT_DEBOUNCE_MS,
205
+ onInput: (input) => void processUserInput(input),
206
+ onEmpty: () => rl.prompt(),
207
+ });
208
+ rl.on('line', (line) => {
209
+ // AI 正忙时忽略新输入
210
+ if (busy) {
211
+ return;
212
+ }
213
+ inputBuffer.push(line);
155
214
  });
156
215
  // Ctrl-D(EOF)或 rl.close() 触发
157
216
  rl.on('close', () => {
217
+ saveLastSession(history);
158
218
  console.log('\n再见 👋');
159
219
  process.exit(0);
160
220
  });
@@ -1 +1 @@
1
- {"version":3,"file":"repl.js","sourceRoot":"","sources":["../../src/cli/repl.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"repl.js","sourceRoot":"","sources":["../../src/cli/repl.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,169 @@
1
+ /**
2
+ * 会话持久化。
3
+ *
4
+ * 把对话历史(ChatMessage 数组)以 JSON 形式保存到 ~/.ccli/sessions/,
5
+ * 方便用户跨次启动后恢复上下文。
6
+ */
7
+ import { homedir } from 'node:os';
8
+ import { join } from 'node:path';
9
+ import { existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync, statSync, unlinkSync, } from 'node:fs';
10
+ const DEFAULT_SESSIONS_DIR = join(homedir(), '.ccli', 'sessions');
11
+ /** 当前使用的会话目录(可被测试覆盖)。 */
12
+ let sessionsDir = DEFAULT_SESSIONS_DIR;
13
+ /** 仅供测试:临时切换会话目录。 */
14
+ export function setSessionsDirForTest(dir) {
15
+ sessionsDir = dir;
16
+ }
17
+ /** 仅供测试:恢复默认会话目录。 */
18
+ export function resetSessionsDirForTest() {
19
+ sessionsDir = DEFAULT_SESSIONS_DIR;
20
+ }
21
+ /** 确保会话目录存在。 */
22
+ function ensureDir() {
23
+ if (!existsSync(sessionsDir)) {
24
+ mkdirSync(sessionsDir, { recursive: true });
25
+ }
26
+ }
27
+ /** 给没有扩展名的会话名补 .json。 */
28
+ function sessionPath(name) {
29
+ const safe = name.replace(/\\/g, '/').replace(/\.{2,}/g, '.');
30
+ const fileName = safe.endsWith('.json') ? safe : `${safe}.json`;
31
+ return join(sessionsDir, fileName);
32
+ }
33
+ /** 生成默认会话名(带时间戳)。 */
34
+ function defaultName() {
35
+ const now = new Date();
36
+ const pad = (n) => String(n).padStart(2, '0');
37
+ return `${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())}_${pad(now.getHours())}-${pad(now.getMinutes())}-${pad(now.getSeconds())}`;
38
+ }
39
+ /**
40
+ * 保存会话。
41
+ * @param history 完整对话历史(保留 system 提示)
42
+ * @param name 会话名称;为空则自动生成
43
+ * @returns 保存后的会话名
44
+ */
45
+ export function saveSession(history, name) {
46
+ if (history.length === 0) {
47
+ return { ok: false, message: '没有可保存的对话历史。' };
48
+ }
49
+ const finalName = name?.trim() || defaultName();
50
+ if (finalName.length === 0) {
51
+ return { ok: false, message: '会话名不能为空。' };
52
+ }
53
+ try {
54
+ ensureDir();
55
+ const path = sessionPath(finalName);
56
+ writeFileSync(path, JSON.stringify(history, null, 2), 'utf8');
57
+ return { ok: true, name: finalName, path };
58
+ }
59
+ catch (e) {
60
+ return { ok: false, message: `保存失败:${e?.message ?? String(e)}` };
61
+ }
62
+ }
63
+ /**
64
+ * 加载会话。
65
+ * @returns 解析后的消息列表;失败返回错误信息
66
+ */
67
+ export function loadSession(name) {
68
+ const trimmed = name.trim();
69
+ if (trimmed.length === 0) {
70
+ return { ok: false, message: '用法:/load <会话名>' };
71
+ }
72
+ const path = sessionPath(trimmed);
73
+ if (!existsSync(path)) {
74
+ return { ok: false, message: `找不到会话:${trimmed}` };
75
+ }
76
+ try {
77
+ const raw = readFileSync(path, 'utf8');
78
+ const history = JSON.parse(raw);
79
+ if (!Array.isArray(history)) {
80
+ return { ok: false, message: `会话文件格式错误:${trimmed}` };
81
+ }
82
+ return { ok: true, history, name: trimmed };
83
+ }
84
+ catch (e) {
85
+ return { ok: false, message: `加载失败:${e?.message ?? String(e)}` };
86
+ }
87
+ }
88
+ /** 按关键词搜索会话(大小写不敏感)。 */
89
+ export function searchSessions(keyword) {
90
+ const kw = keyword.toLowerCase().trim();
91
+ if (!kw)
92
+ return listSessions();
93
+ return listSessions().filter((s) => s.name.toLowerCase().includes(kw));
94
+ }
95
+ /** 列出所有已保存的会话,按修改时间倒序。 */
96
+ export function listSessions() {
97
+ if (!existsSync(sessionsDir))
98
+ return [];
99
+ const entries = readdirSync(sessionsDir)
100
+ .filter((f) => f.endsWith('.json'))
101
+ .map((f) => {
102
+ const path = join(sessionsDir, f);
103
+ try {
104
+ const stat = statSync(path);
105
+ const raw = readFileSync(path, 'utf8');
106
+ const history = JSON.parse(raw);
107
+ return {
108
+ name: f.replace(/\.json$/, ''),
109
+ updatedAt: stat.mtime,
110
+ messageCount: Array.isArray(history) ? history.length : 0,
111
+ };
112
+ }
113
+ catch {
114
+ return null;
115
+ }
116
+ })
117
+ .filter((x) => x !== null);
118
+ return entries.sort((a, b) => b.updatedAt.getTime() - a.updatedAt.getTime());
119
+ }
120
+ const LAST_SESSION_NAME = 'last';
121
+ /** 是否存在可恢复的 last 会话。 */
122
+ export function hasLastSession() {
123
+ return existsSync(sessionPath(LAST_SESSION_NAME));
124
+ }
125
+ /**
126
+ * 保存「上次会话」自动恢复点。
127
+ * 失败时静默忽略(自动保存不应影响退出体验)。
128
+ */
129
+ export function saveLastSession(history) {
130
+ if (history.length === 0)
131
+ return;
132
+ try {
133
+ ensureDir();
134
+ writeFileSync(sessionPath(LAST_SESSION_NAME), JSON.stringify(history, null, 2), 'utf8');
135
+ }
136
+ catch {
137
+ // 自动保存失败不打扰用户
138
+ }
139
+ }
140
+ /** 加载 last 会话;失败返回错误信息。 */
141
+ export function loadLastSession() {
142
+ if (!hasLastSession()) {
143
+ return { ok: false, message: '没有可恢复的 last 会话。' };
144
+ }
145
+ try {
146
+ const raw = readFileSync(sessionPath(LAST_SESSION_NAME), 'utf8');
147
+ const history = JSON.parse(raw);
148
+ if (!Array.isArray(history)) {
149
+ return { ok: false, message: 'last 会话文件格式错误。' };
150
+ }
151
+ return { ok: true, history };
152
+ }
153
+ catch (e) {
154
+ return { ok: false, message: `加载失败:${e?.message ?? String(e)}` };
155
+ }
156
+ }
157
+ /** 删除 last 恢复点。 */
158
+ export function clearLastSession() {
159
+ try {
160
+ const path = sessionPath(LAST_SESSION_NAME);
161
+ if (existsSync(path)) {
162
+ unlinkSync(path);
163
+ }
164
+ }
165
+ catch {
166
+ // 忽略清理失败
167
+ }
168
+ }
169
+ //# sourceMappingURL=session.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session.js","sourceRoot":"","sources":["../../src/cli/session.ts"],"names":[],"mappings":"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"}
package/dist/cli/slash.js CHANGED
@@ -1,13 +1,45 @@
1
+ import { saveSession, loadSession, listSessions, searchSessions } from './session.js';
2
+ import { formatStats, resetStats } from './stats.js';
1
3
  /** 命令清单:名称 → 说明,供 /help 展示,单一数据源。 */
2
4
  const COMMANDS = [
3
5
  ['/help', '列出所有命令'],
4
6
  ['/clear', '清空对话历史(保留系统提示)'],
5
7
  ['/undo [n]', '撤销最近 n 轮对话(默认 1)'],
6
8
  ['/model [id]', '查看或切换模型'],
9
+ ['/save [name]', '保存当前对话(省略名称自动生成)'],
10
+ ['/load <name>', '加载会话;不完整时列出候选'],
11
+ ['/sessions', '列出已保存的会话'],
12
+ ['/stats', '查看本次会话 token 统计'],
7
13
  ['/exit, /quit', '退出会话'],
8
14
  ];
9
15
  /** /model 搜索时最多列出多少个候选,避免刷屏。 */
10
16
  const MAX_MODEL_LIST = 30;
17
+ /**
18
+ * readline 的 Tab 补全函数。
19
+ * 对斜杠命令和 /load 的会话名提供补全提示。
20
+ */
21
+ export function slashCompleter(line) {
22
+ // /load <name> 的会话名补全
23
+ const loadPrefix = '/load ';
24
+ if (line.startsWith(loadPrefix)) {
25
+ const keyword = line.slice(loadPrefix.length).toLowerCase();
26
+ const sessions = listSessions()
27
+ .filter((s) => s.name.toLowerCase().startsWith(keyword))
28
+ .map((s) => `${loadPrefix}${s.name}`);
29
+ return [sessions, line];
30
+ }
31
+ // 其他斜杠命令补全
32
+ if (line.startsWith('/')) {
33
+ const hits = COMMANDS.filter(([cmd]) => cmd.startsWith(line))
34
+ .flatMap(([cmd]) => cmd
35
+ .split(' ')[0]
36
+ .split(',')
37
+ .map((c) => c.trim()))
38
+ .filter((c) => c.startsWith('/'));
39
+ return [hits, line];
40
+ }
41
+ return [[], line];
42
+ }
11
43
  /**
12
44
  * 处理一条斜杠命令。
13
45
  * @returns true 表示已作为命令处理(不应再发给 AI)
@@ -23,7 +55,8 @@ export async function handleSlash(input, ctx) {
23
55
  case 'clear':
24
56
  // 保留第 0 条 system 提示,清掉其余历史
25
57
  ctx.history.length = 1;
26
- console.log('(已清空对话历史)');
58
+ resetStats(ctx.stats);
59
+ console.log('(已清空对话历史和 token 统计)');
27
60
  return true;
28
61
  case 'undo':
29
62
  handleUndo(arg, ctx);
@@ -31,6 +64,18 @@ export async function handleSlash(input, ctx) {
31
64
  case 'model':
32
65
  await handleModel(arg, ctx);
33
66
  return true;
67
+ case 'save':
68
+ handleSave(arg, ctx);
69
+ return true;
70
+ case 'load':
71
+ handleLoad(arg, ctx);
72
+ return true;
73
+ case 'sessions':
74
+ handleSessions();
75
+ return true;
76
+ case 'stats':
77
+ handleStats(ctx);
78
+ return true;
34
79
  case 'exit':
35
80
  case 'quit':
36
81
  ctx.requestExit();
@@ -126,6 +171,79 @@ function handleUndo(arg, ctx) {
126
171
  console.log(`已撤销最近 ${removed} 轮对话。`);
127
172
  }
128
173
  }
174
+ /** /save:把当前历史落盘。 */
175
+ function handleSave(arg, ctx) {
176
+ const result = saveSession(ctx.history, arg);
177
+ if (result.ok) {
178
+ console.log(`已保存会话:${result.name}`);
179
+ }
180
+ else {
181
+ console.log(result.message);
182
+ }
183
+ }
184
+ /** /load:精确匹配时直接加载;否则按关键词列出候选。 */
185
+ function handleLoad(arg, ctx) {
186
+ if (!arg) {
187
+ console.log('用法:/load <会话名>');
188
+ return;
189
+ }
190
+ // 精确匹配 → 直接加载
191
+ const exact = listSessions().find((s) => s.name === arg);
192
+ if (exact) {
193
+ const result = loadSession(arg);
194
+ if (result.ok) {
195
+ ctx.history.length = 0;
196
+ ctx.history.push(...result.history);
197
+ console.log(`已加载会话:${result.name}(${result.history.length} 条消息)`);
198
+ }
199
+ else {
200
+ console.log(result.message);
201
+ }
202
+ return;
203
+ }
204
+ // 否则按关键词搜索
205
+ const matches = searchSessions(arg);
206
+ if (matches.length === 0) {
207
+ console.log(`找不到包含 "${arg}" 的会话。`);
208
+ return;
209
+ }
210
+ // 只有一个候选 → 直接加载,不用输全名
211
+ if (matches.length === 1) {
212
+ const name = matches[0].name;
213
+ const result = loadSession(name);
214
+ if (result.ok) {
215
+ ctx.history.length = 0;
216
+ ctx.history.push(...result.history);
217
+ console.log(`已加载会话:${result.name}(${result.history.length} 条消息)`);
218
+ }
219
+ else {
220
+ console.log(result.message);
221
+ }
222
+ return;
223
+ }
224
+ // 多个候选 → 列出让用户选
225
+ console.log(`未精确匹配 "${arg}",以下 ${matches.length} 个可选(输入完整名加载):`);
226
+ for (const s of matches) {
227
+ console.log(` ${s.name}`);
228
+ }
229
+ }
230
+ /** /sessions:列出所有保存的会话。 */
231
+ function handleSessions() {
232
+ const sessions = listSessions();
233
+ if (sessions.length === 0) {
234
+ console.log('没有已保存的会话。');
235
+ return;
236
+ }
237
+ console.log('已保存的会话:');
238
+ for (const s of sessions) {
239
+ const time = s.updatedAt.toLocaleString();
240
+ console.log(` ${s.name.padEnd(30)} ${s.messageCount} 条 ${time}`);
241
+ }
242
+ }
243
+ /** /stats:打印当前累计 token 统计。 */
244
+ function handleStats(ctx) {
245
+ console.log(formatStats(ctx.stats, ctx.ai.getModel()));
246
+ }
129
247
  function printHelp() {
130
248
  console.log('可用命令:');
131
249
  for (const [cmd, desc] of COMMANDS) {
@@ -1 +1 @@
1
- {"version":3,"file":"slash.js","sourceRoot":"","sources":["../../src/cli/slash.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"slash.js","sourceRoot":"","sources":["../../src/cli/slash.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,98 @@
1
+ /**
2
+ * Token 统计与上下文占比估算。
3
+ *
4
+ * 精确 token 数需要后端返回 usage 或引入 tokenizer(如 tiktoken)。
5
+ * 这里提供一个轻量级估算:按字符数折算,方便用户感知上下文压力。
6
+ */
7
+ /** 模型最大上下文长度(供占比计算)。数值为 token 数,注释为常见标称值。 */
8
+ const CONTEXT_LIMIT = {
9
+ // Anthropic —— 200K
10
+ 'claude-opus-4-5-20251101': 200000, // 200K
11
+ 'claude-sonnet-4-5-20251101': 200000, // 200K
12
+ 'claude-sonnet-4-5-20241022': 200000, // 200K
13
+ 'claude-3-5-sonnet-20241022': 200000, // 200K
14
+ 'claude-3-opus-20240229': 200000, // 200K
15
+ 'claude-3-sonnet-20240229': 200000, // 200K
16
+ 'claude-3-haiku-20240307': 200000, // 200K
17
+ // OpenAI / 通义千问 / DeepSeek 常见值
18
+ 'qwen-plus': 131072, // 128K
19
+ 'qwen-max': 32768, // 32K
20
+ 'deepseek-chat': 65536, // 64K
21
+ 'deepseek-coder': 65536, // 64K
22
+ 'gpt-4o': 128000, // 128K
23
+ 'gpt-4o-mini': 128000, // 128K
24
+ 'gpt-4': 8192, // 8K
25
+ 'gpt-3.5-turbo': 16385, // 16K
26
+ };
27
+ const DEFAULT_CONTEXT_LIMIT = 128000; // 128K
28
+ /**
29
+ * 粗略估算文本对应的 token 数。
30
+ * 混合文本按 1 token ≈ 3.5 个字符估算(中文更费 token,英文更省,取平均)。
31
+ */
32
+ export function estimateTokens(text) {
33
+ if (text.length === 0)
34
+ return 0;
35
+ return Math.max(1, Math.round(text.length / 3.5));
36
+ }
37
+ /** 获取模型上下文上限;未知模型返回默认值。 */
38
+ export function getContextLimit(model) {
39
+ if (model in CONTEXT_LIMIT)
40
+ return CONTEXT_LIMIT[model];
41
+ // 尝试前缀匹配,如 qwen-plus-0919 → qwen-plus
42
+ for (const key of Object.keys(CONTEXT_LIMIT)) {
43
+ if (model.startsWith(key))
44
+ return CONTEXT_LIMIT[key];
45
+ }
46
+ return DEFAULT_CONTEXT_LIMIT;
47
+ }
48
+ /** 把历史消息数组转成纯文本估算 prompt token。 */
49
+ export function estimatePromptTokens(history) {
50
+ return history.reduce((sum, m) => {
51
+ const text = typeof m.content === 'string'
52
+ ? m.content
53
+ : JSON.stringify(m.content ?? '');
54
+ return sum + estimateTokens(text);
55
+ }, 0);
56
+ }
57
+ /** 创建空统计对象。 */
58
+ export function emptyStats() {
59
+ return { promptTokens: 0, completionTokens: 0, totalTokens: 0 };
60
+ }
61
+ /** 原地清空统计对象(保持引用不变)。 */
62
+ export function resetStats(stats) {
63
+ stats.promptTokens = 0;
64
+ stats.completionTokens = 0;
65
+ stats.totalTokens = 0;
66
+ }
67
+ /** 合并两次统计。 */
68
+ export function addStats(a, b) {
69
+ return {
70
+ promptTokens: a.promptTokens + b.promptTokens,
71
+ completionTokens: a.completionTokens + b.completionTokens,
72
+ totalTokens: a.totalTokens + b.totalTokens,
73
+ };
74
+ }
75
+ /**
76
+ * 格式化统计为可读字符串。
77
+ * 示例:上下文: 20 / 131,072 tokens (0.0%) 输入: 12 输出: 8
78
+ */
79
+ export function formatStats(stats, model) {
80
+ const ctx = model ? getContextLimit(model) : DEFAULT_CONTEXT_LIMIT;
81
+ const ratio = ctx > 0 ? (stats.totalTokens / ctx) * 100 : 0;
82
+ return `上下文: ${stats.totalTokens.toLocaleString()} / ${ctx.toLocaleString()} tokens (${ratio.toFixed(1)}%) 输入: ${stats.promptTokens.toLocaleString()} 输出: ${stats.completionTokens.toLocaleString()}`;
83
+ }
84
+ /** 把大数字格式化为 k/M,便于阅读。 */
85
+ function fmtCompact(n) {
86
+ if (n >= 1_000_000)
87
+ return `${(n / 1_000_000).toFixed(1)}M`;
88
+ if (n >= 1_000)
89
+ return `${(n / 1_000).toFixed(1)}k`;
90
+ return n.toLocaleString();
91
+ }
92
+ /** 更紧凑的统计格式,适合放在提示符附近。 */
93
+ export function formatStatsCompact(stats, model) {
94
+ const ctx = model ? getContextLimit(model) : DEFAULT_CONTEXT_LIMIT;
95
+ const ratio = ctx > 0 ? (stats.totalTokens / ctx) * 100 : 0;
96
+ return `上下文: ${fmtCompact(stats.totalTokens)} / ${fmtCompact(ctx)} (${ratio.toFixed(1)}%)`;
97
+ }
98
+ //# sourceMappingURL=stats.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stats.js","sourceRoot":"","sources":["../../src/cli/stats.ts"],"names":[],"mappings":"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"}
package/dist/index.js CHANGED
@@ -29,7 +29,7 @@ const HELP = `${NAME} v${VERSION}
29
29
  ccli config 查看当前生效的后端 / 模型 / 密钥来源
30
30
  ccli --version 查看版本
31
31
  `;
32
- function main() {
32
+ async function main() {
33
33
  // argv 前两个是 node 路径和脚本路径,从第 3 个起才是用户输入
34
34
  const args = parseArgs(process.argv.slice(2));
35
35
  if (args.unknown.length > 0) {
@@ -56,7 +56,7 @@ function main() {
56
56
  process.exit(1);
57
57
  }
58
58
  // 无参数:进入交互式会话循环。
59
- startRepl();
59
+ await startRepl();
60
60
  }
61
61
  main();
62
62
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA;;;;;;;;GAQG;AACH,OAAO,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAC1C,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAC7C,OAAO,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAC1C,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAEjD,MAAM,IAAI,GAAG,GAAG,IAAI,KAAK,OAAO;;;;;;;;;;;;;;;;CAgB/B,CAAC;AAEF,SAAS,IAAI;IACT,uCAAuC;IACvC,MAAM,IAAI,GAAG,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;IAE9C,IAAI,IAAI,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC1B,OAAO,CAAC,KAAK,CAAC,SAAS,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QAClD,OAAO,CAAC,KAAK,CAAC,0BAA0B,CAAC,CAAC;QAC1C,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACpB,CAAC;IAED,IAAI,IAAI,CAAC,IAAI,EAAE,CAAC;QACZ,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QAClB,OAAO;IACX,CAAC;IAED,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;QACf,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;QACrB,OAAO;IACX,CAAC;IAED,kBAAkB;IAClB,MAAM,OAAO,GAAG,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC;IACpC,IAAI,OAAO,KAAK,QAAQ,EAAE,CAAC;QACvB,OAAO,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,CAAC;IAChC,CAAC;IACD,IAAI,OAAO,EAAE,CAAC;QACV,OAAO,CAAC,KAAK,CAAC,SAAS,OAAO,EAAE,CAAC,CAAC;QAClC,OAAO,CAAC,KAAK,CAAC,0BAA0B,CAAC,CAAC;QAC1C,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACpB,CAAC;IAED,iBAAiB;IACjB,SAAS,EAAE,CAAC;AAChB,CAAC;AAED,IAAI,EAAE,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA;;;;;;;;GAQG;AACH,OAAO,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAC1C,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAC7C,OAAO,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAC1C,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAEjD,MAAM,IAAI,GAAG,GAAG,IAAI,KAAK,OAAO;;;;;;;;;;;;;;;;CAgB/B,CAAC;AAEF,KAAK,UAAU,IAAI;IACf,uCAAuC;IACvC,MAAM,IAAI,GAAG,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;IAE9C,IAAI,IAAI,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC1B,OAAO,CAAC,KAAK,CAAC,SAAS,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QAClD,OAAO,CAAC,KAAK,CAAC,0BAA0B,CAAC,CAAC;QAC1C,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACpB,CAAC;IAED,IAAI,IAAI,CAAC,IAAI,EAAE,CAAC;QACZ,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QAClB,OAAO;IACX,CAAC;IAED,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;QACf,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;QACrB,OAAO;IACX,CAAC;IAED,kBAAkB;IAClB,MAAM,OAAO,GAAG,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC;IACpC,IAAI,OAAO,KAAK,QAAQ,EAAE,CAAC;QACvB,OAAO,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,CAAC;IAChC,CAAC;IACD,IAAI,OAAO,EAAE,CAAC;QACV,OAAO,CAAC,KAAK,CAAC,SAAS,OAAO,EAAE,CAAC,CAAC;QAClC,OAAO,CAAC,KAAK,CAAC,0BAA0B,CAAC,CAAC;QAC1C,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACpB,CAAC;IAED,iBAAiB;IACjB,MAAM,SAAS,EAAE,CAAC;AACtB,CAAC;AAED,IAAI,EAAE,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gird/ccli",
3
- "version": "2.1.1",
3
+ "version": "2.2.0",
4
4
  "description": "CCLI —— 一步一步从零实现的代理型(Agentic)终端 AI 编程助手",
5
5
  "type": "module",
6
6
  "bin": {
@@ -12,13 +12,14 @@
12
12
  "README.md"
13
13
  ],
14
14
  "scripts": {
15
+ "prebuild": "rm -rf dist",
15
16
  "build": "tsc -p tsconfig.json && chmod +x dist/index.js",
16
17
  "dev": "tsx src/index.ts",
17
18
  "start": "node dist/index.js",
18
19
  "typecheck": "tsc --noEmit -p tsconfig.json",
19
20
  "test": "node --import tsx --test test/*.test.ts",
20
21
  "smoke": "tsx scripts/smoke.ts",
21
- "prepare": "npm run build"
22
+ "prepublishOnly": "npm run build"
22
23
  },
23
24
  "engines": {
24
25
  "node": ">=18"
@@ -47,6 +48,7 @@
47
48
  "typescript": "^6.0.3"
48
49
  },
49
50
  "dependencies": {
51
+ "@anthropic-ai/sdk": "^0.110.0",
50
52
  "openai": "^6.45.0"
51
53
  }
52
54
  }