snow-ai 0.1.12 → 0.2.1

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 (97) hide show
  1. package/dist/api/chat.d.ts +65 -2
  2. package/dist/api/chat.js +299 -16
  3. package/dist/api/responses.d.ts +52 -0
  4. package/dist/api/responses.js +541 -0
  5. package/dist/api/systemPrompt.d.ts +4 -0
  6. package/dist/api/systemPrompt.js +43 -0
  7. package/dist/app.js +15 -4
  8. package/dist/cli.js +17 -1
  9. package/dist/hooks/useConversation.d.ts +32 -0
  10. package/dist/hooks/useConversation.js +403 -0
  11. package/dist/hooks/useGlobalNavigation.d.ts +6 -0
  12. package/dist/hooks/useGlobalNavigation.js +15 -0
  13. package/dist/hooks/useSessionManagement.d.ts +10 -0
  14. package/dist/hooks/useSessionManagement.js +43 -0
  15. package/dist/hooks/useSessionSave.d.ts +8 -0
  16. package/dist/hooks/useSessionSave.js +52 -0
  17. package/dist/hooks/useToolConfirmation.d.ts +18 -0
  18. package/dist/hooks/useToolConfirmation.js +49 -0
  19. package/dist/mcp/bash.d.ts +57 -0
  20. package/dist/mcp/bash.js +138 -0
  21. package/dist/mcp/filesystem.d.ts +307 -0
  22. package/dist/mcp/filesystem.js +520 -0
  23. package/dist/mcp/todo.d.ts +55 -0
  24. package/dist/mcp/todo.js +329 -0
  25. package/dist/test/logger-test.d.ts +1 -0
  26. package/dist/test/logger-test.js +7 -0
  27. package/dist/types/index.d.ts +1 -1
  28. package/dist/ui/components/ChatInput.d.ts +15 -2
  29. package/dist/ui/components/ChatInput.js +445 -59
  30. package/dist/ui/components/CommandPanel.d.ts +2 -2
  31. package/dist/ui/components/CommandPanel.js +11 -7
  32. package/dist/ui/components/DiffViewer.d.ts +9 -0
  33. package/dist/ui/components/DiffViewer.js +93 -0
  34. package/dist/ui/components/FileList.d.ts +14 -0
  35. package/dist/ui/components/FileList.js +131 -0
  36. package/dist/ui/components/MCPInfoPanel.d.ts +2 -0
  37. package/dist/ui/components/MCPInfoPanel.js +74 -0
  38. package/dist/ui/components/MCPInfoScreen.d.ts +7 -0
  39. package/dist/ui/components/MCPInfoScreen.js +27 -0
  40. package/dist/ui/components/MarkdownRenderer.d.ts +7 -0
  41. package/dist/ui/components/MarkdownRenderer.js +110 -0
  42. package/dist/ui/components/Menu.d.ts +5 -2
  43. package/dist/ui/components/Menu.js +60 -9
  44. package/dist/ui/components/MessageList.d.ts +30 -2
  45. package/dist/ui/components/MessageList.js +64 -12
  46. package/dist/ui/components/PendingMessages.js +1 -1
  47. package/dist/ui/components/ScrollableSelectInput.d.ts +29 -0
  48. package/dist/ui/components/ScrollableSelectInput.js +157 -0
  49. package/dist/ui/components/SessionListScreen.d.ts +7 -0
  50. package/dist/ui/components/SessionListScreen.js +196 -0
  51. package/dist/ui/components/SessionListScreenWrapper.d.ts +7 -0
  52. package/dist/ui/components/SessionListScreenWrapper.js +14 -0
  53. package/dist/ui/components/TodoTree.d.ts +15 -0
  54. package/dist/ui/components/TodoTree.js +60 -0
  55. package/dist/ui/components/ToolConfirmation.d.ts +8 -0
  56. package/dist/ui/components/ToolConfirmation.js +38 -0
  57. package/dist/ui/components/ToolResultPreview.d.ts +12 -0
  58. package/dist/ui/components/ToolResultPreview.js +115 -0
  59. package/dist/ui/pages/ChatScreen.d.ts +4 -0
  60. package/dist/ui/pages/ChatScreen.js +385 -196
  61. package/dist/ui/pages/MCPConfigScreen.d.ts +6 -0
  62. package/dist/ui/pages/MCPConfigScreen.js +55 -0
  63. package/dist/ui/pages/ModelConfigScreen.js +73 -12
  64. package/dist/ui/pages/WelcomeScreen.js +17 -11
  65. package/dist/utils/apiConfig.d.ts +12 -0
  66. package/dist/utils/apiConfig.js +95 -9
  67. package/dist/utils/commandExecutor.d.ts +2 -1
  68. package/dist/utils/commands/init.d.ts +2 -0
  69. package/dist/utils/commands/init.js +93 -0
  70. package/dist/utils/commands/mcp.d.ts +2 -0
  71. package/dist/utils/commands/mcp.js +12 -0
  72. package/dist/utils/commands/resume.d.ts +2 -0
  73. package/dist/utils/commands/resume.js +12 -0
  74. package/dist/utils/commands/yolo.d.ts +2 -0
  75. package/dist/utils/commands/yolo.js +12 -0
  76. package/dist/utils/fileUtils.d.ts +44 -0
  77. package/dist/utils/fileUtils.js +222 -0
  78. package/dist/utils/index.d.ts +4 -0
  79. package/dist/utils/index.js +6 -0
  80. package/dist/utils/logger.d.ts +31 -0
  81. package/dist/utils/logger.js +97 -0
  82. package/dist/utils/mcpToolsManager.d.ts +47 -0
  83. package/dist/utils/mcpToolsManager.js +476 -0
  84. package/dist/utils/messageFormatter.d.ts +12 -0
  85. package/dist/utils/messageFormatter.js +32 -0
  86. package/dist/utils/sessionConverter.d.ts +6 -0
  87. package/dist/utils/sessionConverter.js +61 -0
  88. package/dist/utils/sessionManager.d.ts +39 -0
  89. package/dist/utils/sessionManager.js +141 -0
  90. package/dist/utils/textBuffer.d.ts +36 -7
  91. package/dist/utils/textBuffer.js +265 -179
  92. package/dist/utils/todoPreprocessor.d.ts +5 -0
  93. package/dist/utils/todoPreprocessor.js +19 -0
  94. package/dist/utils/toolExecutor.d.ts +21 -0
  95. package/dist/utils/toolExecutor.js +28 -0
  96. package/package.json +12 -3
  97. package/readme.md +2 -2
@@ -0,0 +1,541 @@
1
+ import OpenAI from 'openai';
2
+ import { getOpenAiConfig } from '../utils/apiConfig.js';
3
+ import { executeMCPTool } from '../utils/mcpToolsManager.js';
4
+ import { SYSTEM_PROMPT } from './systemPrompt.js';
5
+ /**
6
+ * 确保 schema 符合 Responses API 的要求:
7
+ * 1. additionalProperties: false
8
+ * 2. 保持原有的 required 数组(不修改)
9
+ */
10
+ function ensureStrictSchema(schema) {
11
+ if (!schema) {
12
+ return undefined;
13
+ }
14
+ // 深拷贝 schema
15
+ const strictSchema = JSON.parse(JSON.stringify(schema));
16
+ if (strictSchema.type === 'object') {
17
+ // 添加 additionalProperties: false
18
+ strictSchema.additionalProperties = false;
19
+ // 递归处理嵌套的 object 属性
20
+ if (strictSchema.properties) {
21
+ for (const key of Object.keys(strictSchema.properties)) {
22
+ const prop = strictSchema.properties[key];
23
+ // 递归处理嵌套的 object
24
+ if (prop.type === 'object' || (Array.isArray(prop.type) && prop.type.includes('object'))) {
25
+ if (!('additionalProperties' in prop)) {
26
+ prop.additionalProperties = false;
27
+ }
28
+ }
29
+ }
30
+ }
31
+ // 如果 properties 为空且有 required 字段,删除它
32
+ if (strictSchema.properties && Object.keys(strictSchema.properties).length === 0 && strictSchema.required) {
33
+ delete strictSchema.required;
34
+ }
35
+ }
36
+ return strictSchema;
37
+ }
38
+ /**
39
+ * 转换 Chat Completions 格式的工具为 Responses API 格式
40
+ * Chat Completions: {type: 'function', function: {name, description, parameters}}
41
+ * Responses API: {type: 'function', name, description, parameters, strict}
42
+ */
43
+ function convertToolsForResponses(tools) {
44
+ if (!tools || tools.length === 0) {
45
+ return undefined;
46
+ }
47
+ return tools.map(tool => ({
48
+ type: 'function',
49
+ name: tool.function.name,
50
+ description: tool.function.description,
51
+ parameters: ensureStrictSchema(tool.function.parameters),
52
+ strict: false
53
+ }));
54
+ }
55
+ let openaiClient = null;
56
+ function getOpenAIClient() {
57
+ if (!openaiClient) {
58
+ const config = getOpenAiConfig();
59
+ if (!config.apiKey || !config.baseUrl) {
60
+ throw new Error('OpenAI API configuration is incomplete. Please configure API settings first.');
61
+ }
62
+ openaiClient = new OpenAI({
63
+ apiKey: config.apiKey,
64
+ baseURL: config.baseUrl,
65
+ });
66
+ }
67
+ return openaiClient;
68
+ }
69
+ export function resetOpenAIClient() {
70
+ openaiClient = null;
71
+ }
72
+ /**
73
+ * 转换消息格式为 Responses API 的 input 格式
74
+ * Responses API 的 input 格式:
75
+ * 1. 支持 user, assistant 角色消息,使用 type: "message" 包裹
76
+ * 2. 工具调用在 assistant 中表示为 function_call 类型的 item
77
+ * 3. 工具结果使用 function_call_output 类型
78
+ *
79
+ * 注意:Responses API 使用 instructions 字段代替 system 消息
80
+ * 优化:使用 type: "message" 包裹以提高缓存命中率
81
+ */
82
+ function convertToResponseInput(messages) {
83
+ const result = [];
84
+ for (const msg of messages) {
85
+ if (!msg)
86
+ continue;
87
+ // 跳过 system 消息(在 createResponse 中使用 instructions 字段)
88
+ if (msg.role === 'system') {
89
+ continue;
90
+ }
91
+ // 用户消息:content 必须是数组格式,使用 type: "message" 包裹
92
+ if (msg.role === 'user') {
93
+ const contentParts = [];
94
+ // 添加文本内容
95
+ if (msg.content) {
96
+ contentParts.push({
97
+ type: 'input_text',
98
+ text: msg.content
99
+ });
100
+ }
101
+ // 添加图片内容
102
+ if (msg.images && msg.images.length > 0) {
103
+ for (const image of msg.images) {
104
+ contentParts.push({
105
+ type: 'input_image',
106
+ image_url: image.data
107
+ });
108
+ }
109
+ }
110
+ result.push({
111
+ type: 'message',
112
+ role: 'user',
113
+ content: contentParts
114
+ });
115
+ continue;
116
+ }
117
+ // Assistant 消息(带工具调用)
118
+ // 在 Responses API 中,需要将工具调用转换为 function_call 类型的独立项
119
+ if (msg.role === 'assistant' && msg.tool_calls && msg.tool_calls.length > 0) {
120
+ // 添加 assistant 文本内容(如果有)
121
+ if (msg.content) {
122
+ result.push({
123
+ type: 'message',
124
+ role: 'assistant',
125
+ content: [{
126
+ type: 'output_text',
127
+ text: msg.content
128
+ }]
129
+ });
130
+ }
131
+ // 为每个工具调用添加 function_call 项
132
+ for (const toolCall of msg.tool_calls) {
133
+ result.push({
134
+ type: 'function_call',
135
+ call_id: toolCall.id,
136
+ name: toolCall.function.name,
137
+ arguments: toolCall.function.arguments
138
+ });
139
+ }
140
+ continue;
141
+ }
142
+ // Assistant 消息(纯文本)
143
+ if (msg.role === 'assistant') {
144
+ result.push({
145
+ type: 'message',
146
+ role: 'assistant',
147
+ content: [{
148
+ type: 'output_text',
149
+ text: msg.content || ''
150
+ }]
151
+ });
152
+ continue;
153
+ }
154
+ // Tool 消息:转换为 function_call_output
155
+ if (msg.role === 'tool' && msg.tool_call_id) {
156
+ result.push({
157
+ type: 'function_call_output',
158
+ call_id: msg.tool_call_id,
159
+ output: msg.content
160
+ });
161
+ continue;
162
+ }
163
+ }
164
+ return result;
165
+ }
166
+ /**
167
+ * 使用 Responses API 创建响应(非流式,带自动工具调用)
168
+ */
169
+ export async function createResponse(options) {
170
+ const client = getOpenAIClient();
171
+ let messages = [...options.messages];
172
+ // 提取系统提示词(如果存在)
173
+ let systemInstructions = SYSTEM_PROMPT;
174
+ const firstMessage = messages[0];
175
+ if (firstMessage?.role === 'system') {
176
+ systemInstructions = firstMessage.content;
177
+ messages = messages.slice(1); // 移除系统消息
178
+ }
179
+ try {
180
+ // 使用 Responses API
181
+ while (true) {
182
+ const requestPayload = {
183
+ model: options.model,
184
+ instructions: systemInstructions, // Responses API 使用 instructions 代替 system 消息
185
+ input: convertToResponseInput(messages),
186
+ tools: convertToolsForResponses(options.tools),
187
+ tool_choice: options.tool_choice,
188
+ reasoning: options.reasoning || { summary: 'auto', effort: 'high' },
189
+ store: options.store ?? false, // 默认不存储对话历史,提高缓存命中
190
+ include: options.include || ['reasoning.encrypted_content'], // 包含加密推理内容
191
+ prompt_cache_key: options.prompt_cache_key, // 缓存键(可选)
192
+ };
193
+ const response = await client.responses.create(requestPayload);
194
+ // 提取响应 - Responses API 返回 output 数组
195
+ const output = response.output;
196
+ if (!output || output.length === 0) {
197
+ throw new Error('No output from AI');
198
+ }
199
+ // 获取最后一条消息(通常是 assistant 的响应)
200
+ const lastMessage = output[output.length - 1];
201
+ // 添加 assistant 消息到对话
202
+ messages.push({
203
+ role: 'assistant',
204
+ content: lastMessage.content || '',
205
+ tool_calls: lastMessage.tool_calls
206
+ });
207
+ // 检查是否有工具调用
208
+ if (lastMessage.tool_calls && lastMessage.tool_calls.length > 0) {
209
+ // 执行每个工具调用
210
+ for (const toolCall of lastMessage.tool_calls) {
211
+ if (toolCall.type === 'function') {
212
+ try {
213
+ const args = JSON.parse(toolCall.function.arguments);
214
+ const result = await executeMCPTool(toolCall.function.name, args);
215
+ // 添加工具结果到对话
216
+ messages.push({
217
+ role: 'tool',
218
+ content: JSON.stringify(result),
219
+ tool_call_id: toolCall.id
220
+ });
221
+ }
222
+ catch (error) {
223
+ // 添加错误结果到对话
224
+ messages.push({
225
+ role: 'tool',
226
+ content: `Error: ${error instanceof Error ? error.message : 'Tool execution failed'}`,
227
+ tool_call_id: toolCall.id
228
+ });
229
+ }
230
+ }
231
+ }
232
+ // 继续对话获取工具结果后的响应
233
+ continue;
234
+ }
235
+ // 没有工具调用,返回内容
236
+ return lastMessage.content || '';
237
+ }
238
+ }
239
+ catch (error) {
240
+ if (error instanceof Error) {
241
+ // 检查是否是 API 网关不支持 Responses API
242
+ if (error.message.includes('Panic detected') ||
243
+ error.message.includes('nil pointer') ||
244
+ error.message.includes('404') ||
245
+ error.message.includes('not found')) {
246
+ throw new Error('Response creation failed: Your API endpoint does not support the Responses API. ' +
247
+ 'Please switch to "Chat Completions" method in API settings, or use an OpenAI-compatible endpoint that supports Responses API.');
248
+ }
249
+ throw new Error(`Response creation failed: ${error.message}`);
250
+ }
251
+ throw new Error('Response creation failed: Unknown error');
252
+ }
253
+ }
254
+ /**
255
+ * 使用 Responses API 创建流式响应(带自动工具调用)
256
+ */
257
+ export async function* createStreamingResponse(options, abortSignal) {
258
+ const client = getOpenAIClient();
259
+ // 提取系统提示词(如果存在)
260
+ let systemInstructions = SYSTEM_PROMPT;
261
+ let messages = [...options.messages];
262
+ const firstMessage = messages[0];
263
+ if (firstMessage?.role === 'system') {
264
+ systemInstructions = firstMessage.content;
265
+ messages = messages.slice(1); // 移除系统消息
266
+ }
267
+ try {
268
+ const requestInput = convertToResponseInput(messages);
269
+ const requestPayload = {
270
+ model: options.model,
271
+ instructions: systemInstructions, // Responses API 使用 instructions 代替 system 消息
272
+ input: requestInput,
273
+ stream: true,
274
+ tools: convertToolsForResponses(options.tools),
275
+ tool_choice: options.tool_choice,
276
+ reasoning: options.reasoning || { summary: 'auto', effort: 'high' },
277
+ store: options.store ?? false, // 默认不存储对话历史,提高缓存命中
278
+ include: options.include || ['reasoning.encrypted_content'], // 包含加密推理内容
279
+ prompt_cache_key: options.prompt_cache_key, // 缓存键(可选)
280
+ };
281
+ const stream = await client.responses.create(requestPayload, {
282
+ signal: abortSignal,
283
+ });
284
+ let contentBuffer = '';
285
+ let toolCallsBuffer = {};
286
+ let hasToolCalls = false;
287
+ let currentFunctionCallId = null;
288
+ let usageData;
289
+ for await (const chunk of stream) {
290
+ if (abortSignal?.aborted) {
291
+ return;
292
+ }
293
+ // Responses API 使用 SSE 事件格式
294
+ const eventType = chunk.type;
295
+ // 根据事件类型处理
296
+ if (eventType === 'response.created' || eventType === 'response.in_progress') {
297
+ // 响应创建/进行中 - 忽略
298
+ continue;
299
+ }
300
+ else if (eventType === 'response.output_item.added') {
301
+ // 新输出项添加
302
+ const item = chunk.item;
303
+ if (item?.type === 'reasoning') {
304
+ // 推理摘要开始 - 忽略
305
+ continue;
306
+ }
307
+ else if (item?.type === 'message') {
308
+ // 消息开始 - 忽略
309
+ continue;
310
+ }
311
+ else if (item?.type === 'function_call') {
312
+ // 工具调用开始
313
+ hasToolCalls = true;
314
+ const callId = item.call_id || item.id;
315
+ currentFunctionCallId = callId;
316
+ toolCallsBuffer[callId] = {
317
+ id: callId,
318
+ type: 'function',
319
+ function: {
320
+ name: item.name || '',
321
+ arguments: ''
322
+ }
323
+ };
324
+ continue;
325
+ }
326
+ continue;
327
+ }
328
+ else if (eventType === 'response.function_call_arguments.delta') {
329
+ // 工具调用参数增量
330
+ const delta = chunk.delta;
331
+ if (delta && currentFunctionCallId) {
332
+ toolCallsBuffer[currentFunctionCallId].function.arguments += delta;
333
+ // 发送 delta 用于 token 计数
334
+ yield {
335
+ type: 'tool_call_delta',
336
+ delta: delta
337
+ };
338
+ }
339
+ }
340
+ else if (eventType === 'response.function_call_arguments.done') {
341
+ // 工具调用参数完成
342
+ const itemId = chunk.item_id;
343
+ const args = chunk.arguments;
344
+ if (itemId && toolCallsBuffer[itemId]) {
345
+ toolCallsBuffer[itemId].function.arguments = args;
346
+ }
347
+ currentFunctionCallId = null;
348
+ continue;
349
+ }
350
+ else if (eventType === 'response.output_item.done') {
351
+ // 输出项完成
352
+ const item = chunk.item;
353
+ if (item?.type === 'function_call') {
354
+ // 确保工具调用信息完整
355
+ const callId = item.call_id || item.id;
356
+ if (toolCallsBuffer[callId]) {
357
+ toolCallsBuffer[callId].function.name = item.name;
358
+ toolCallsBuffer[callId].function.arguments = item.arguments;
359
+ }
360
+ }
361
+ continue;
362
+ }
363
+ else if (eventType === 'response.content_part.added') {
364
+ // 内容部分添加 - 忽略
365
+ continue;
366
+ }
367
+ else if (eventType === 'response.reasoning_summary_text.delta') {
368
+ // 推理摘要增量更新(仅用于 token 计数,不包含在响应内容中)
369
+ const delta = chunk.delta;
370
+ if (delta) {
371
+ yield {
372
+ type: 'reasoning_delta',
373
+ delta: delta
374
+ };
375
+ }
376
+ }
377
+ else if (eventType === 'response.output_text.delta') {
378
+ // 文本增量更新
379
+ const delta = chunk.delta;
380
+ if (delta) {
381
+ contentBuffer += delta;
382
+ yield {
383
+ type: 'content',
384
+ content: delta
385
+ };
386
+ }
387
+ }
388
+ else if (eventType === 'response.output_text.done') {
389
+ // 文本输出完成 - 忽略
390
+ continue;
391
+ }
392
+ else if (eventType === 'response.content_part.done') {
393
+ // 内容部分完成 - 忽略
394
+ continue;
395
+ }
396
+ else if (eventType === 'response.completed') {
397
+ // 响应完全完成 - 从 response 对象中提取 usage
398
+ if (chunk.response && chunk.response.usage) {
399
+ usageData = {
400
+ prompt_tokens: chunk.response.usage.input_tokens || 0,
401
+ completion_tokens: chunk.response.usage.output_tokens || 0,
402
+ total_tokens: chunk.response.usage.total_tokens || 0
403
+ };
404
+ }
405
+ break;
406
+ }
407
+ else if (eventType === 'response.failed' || eventType === 'response.cancelled') {
408
+ // 响应失败或取消
409
+ const error = chunk.error;
410
+ if (error) {
411
+ throw new Error(`Response failed: ${error.message || 'Unknown error'}`);
412
+ }
413
+ break;
414
+ }
415
+ }
416
+ // 如果有工具调用,返回它们
417
+ if (hasToolCalls) {
418
+ yield {
419
+ type: 'tool_calls',
420
+ tool_calls: Object.values(toolCallsBuffer)
421
+ };
422
+ }
423
+ // Yield usage information if available
424
+ if (usageData) {
425
+ yield {
426
+ type: 'usage',
427
+ usage: usageData
428
+ };
429
+ }
430
+ // 发送完成信号
431
+ yield {
432
+ type: 'done'
433
+ };
434
+ }
435
+ catch (error) {
436
+ if (error instanceof Error && error.name === 'AbortError') {
437
+ return;
438
+ }
439
+ if (error instanceof Error) {
440
+ // 检查是否是 API 网关不支持 Responses API
441
+ if (error.message.includes('Panic detected') ||
442
+ error.message.includes('nil pointer') ||
443
+ error.message.includes('404') ||
444
+ error.message.includes('not found')) {
445
+ throw new Error('Streaming response creation failed: Your API endpoint does not support the Responses API. ' +
446
+ 'Please switch to "Chat Completions" method in API settings, or use an OpenAI-compatible endpoint that supports Responses API (OpenAI official API, or compatible providers).');
447
+ }
448
+ throw new Error(`Streaming response creation failed: ${error.message}`);
449
+ }
450
+ throw new Error('Streaming response creation failed: Unknown error');
451
+ }
452
+ }
453
+ /**
454
+ * 使用 Responses API 创建响应(限制工具调用轮数)
455
+ */
456
+ export async function createResponseWithTools(options, maxToolRounds = 5) {
457
+ const client = getOpenAIClient();
458
+ let messages = [...options.messages];
459
+ let allToolCalls = [];
460
+ let rounds = 0;
461
+ // 提取系统提示词(如果存在)
462
+ let systemInstructions = SYSTEM_PROMPT;
463
+ const firstMessage = messages[0];
464
+ if (firstMessage?.role === 'system') {
465
+ systemInstructions = firstMessage.content;
466
+ messages = messages.slice(1); // 移除系统消息
467
+ }
468
+ try {
469
+ while (rounds < maxToolRounds) {
470
+ const response = await client.responses.create({
471
+ model: options.model,
472
+ instructions: systemInstructions, // Responses API 使用 instructions 代替 system 消息
473
+ input: convertToResponseInput(messages),
474
+ tools: convertToolsForResponses(options.tools),
475
+ tool_choice: options.tool_choice,
476
+ reasoning: options.reasoning || { summary: 'auto', effort: 'high' },
477
+ store: options.store ?? false, // 默认不存储对话历史,提高缓存命中
478
+ include: options.include || ['reasoning.encrypted_content'], // 包含加密推理内容
479
+ prompt_cache_key: options.prompt_cache_key, // 缓存键(可选)
480
+ });
481
+ const output = response.output;
482
+ if (!output || output.length === 0) {
483
+ throw new Error('No output from AI');
484
+ }
485
+ const lastMessage = output[output.length - 1];
486
+ // 添加 assistant 消息
487
+ messages.push({
488
+ role: 'assistant',
489
+ content: lastMessage.content || '',
490
+ tool_calls: lastMessage.tool_calls
491
+ });
492
+ // 检查工具调用
493
+ if (lastMessage.tool_calls && lastMessage.tool_calls.length > 0) {
494
+ allToolCalls.push(...lastMessage.tool_calls);
495
+ // 执行工具调用
496
+ for (const toolCall of lastMessage.tool_calls) {
497
+ if (toolCall.type === 'function') {
498
+ try {
499
+ const args = JSON.parse(toolCall.function.arguments);
500
+ const result = await executeMCPTool(toolCall.function.name, args);
501
+ messages.push({
502
+ role: 'tool',
503
+ content: JSON.stringify(result),
504
+ tool_call_id: toolCall.id
505
+ });
506
+ }
507
+ catch (error) {
508
+ messages.push({
509
+ role: 'tool',
510
+ content: `Error: ${error instanceof Error ? error.message : 'Tool execution failed'}`,
511
+ tool_call_id: toolCall.id
512
+ });
513
+ }
514
+ }
515
+ }
516
+ rounds++;
517
+ continue;
518
+ }
519
+ // 没有工具调用,返回结果
520
+ return {
521
+ content: lastMessage.content || '',
522
+ toolCalls: allToolCalls
523
+ };
524
+ }
525
+ throw new Error(`Maximum tool calling rounds (${maxToolRounds}) exceeded`);
526
+ }
527
+ catch (error) {
528
+ if (error instanceof Error) {
529
+ // 检查是否是 API 网关不支持 Responses API
530
+ if (error.message.includes('Panic detected') ||
531
+ error.message.includes('nil pointer') ||
532
+ error.message.includes('404') ||
533
+ error.message.includes('not found')) {
534
+ throw new Error('Response creation with tools failed: Your API endpoint does not support the Responses API. ' +
535
+ 'Please switch to "Chat Completions" method in API settings.');
536
+ }
537
+ throw new Error(`Response creation with tools failed: ${error.message}`);
538
+ }
539
+ throw new Error('Response creation with tools failed: Unknown error');
540
+ }
541
+ }
@@ -0,0 +1,4 @@
1
+ /**
2
+ * System prompt configuration for Snow AI CLI
3
+ */
4
+ export declare const SYSTEM_PROMPT = "You are Snow AI CLI, an intelligent command-line assistant designed to help users with their tasks efficiently.\n\nYour capabilities:\n- Answer technical questions and provide programming guidance\n- Execute MCP (Model Context Protocol) tools for file operations and system tasks\n- Run terminal commands using terminal-execute tool\n- Understand file references (using @ symbol)\n- Provide clear, accurate, and well-structured responses\n\n**Project Documentation:**\n- The current project may have a SNOW.md file in the root directory\n- SNOW.md contains project overview, architecture, tech stack, and development guidelines\n- You should read SNOW.md (if it exists) to understand the project context before making changes\n- If SNOW.md doesn't exist, you can still complete user requests without it - it's an optional helper document\n- You can generate or update SNOW.md using the /init command\n\nAvailable built-in tools:\n1. **Filesystem tools** (filesystem-*):\n - filesystem-read: Read file contents with line range\n - filesystem-create: Create new files\n - filesystem-edit: Edit existing files with diff preview\n - filesystem-delete: Delete files\n - filesystem-list: List directory contents\n - filesystem-search: Search for code patterns across files\n - filesystem-exists: Check if file/directory exists\n - filesystem-info: Get file metadata\n\n2. **Terminal execution** (terminal-execute):\n - Run commands exactly as typed in terminal\n - Examples: \"npm -v\", \"git status\", \"node index.js\"\n\n3. **TODO tools** (todo-*):\n - Track task progress with todo-create, todo-update, todo-add\n - Mark tasks completed immediately after finishing\n\nJust type the command as you would in terminal. That's it.\n\nError handling:\n- If command fails, check the error and try alternatives\n- Use filesystem tools when terminal commands don't work";
@@ -0,0 +1,43 @@
1
+ /**
2
+ * System prompt configuration for Snow AI CLI
3
+ */
4
+ export const SYSTEM_PROMPT = `You are Snow AI CLI, an intelligent command-line assistant designed to help users with their tasks efficiently.
5
+
6
+ Your capabilities:
7
+ - Answer technical questions and provide programming guidance
8
+ - Execute MCP (Model Context Protocol) tools for file operations and system tasks
9
+ - Run terminal commands using terminal-execute tool
10
+ - Understand file references (using @ symbol)
11
+ - Provide clear, accurate, and well-structured responses
12
+
13
+ **Project Documentation:**
14
+ - The current project may have a SNOW.md file in the root directory
15
+ - SNOW.md contains project overview, architecture, tech stack, and development guidelines
16
+ - You should read SNOW.md (if it exists) to understand the project context before making changes
17
+ - If SNOW.md doesn't exist, you can still complete user requests without it - it's an optional helper document
18
+ - You can generate or update SNOW.md using the /init command
19
+
20
+ Available built-in tools:
21
+ 1. **Filesystem tools** (filesystem-*):
22
+ - filesystem-read: Read file contents with line range
23
+ - filesystem-create: Create new files
24
+ - filesystem-edit: Edit existing files with diff preview
25
+ - filesystem-delete: Delete files
26
+ - filesystem-list: List directory contents
27
+ - filesystem-search: Search for code patterns across files
28
+ - filesystem-exists: Check if file/directory exists
29
+ - filesystem-info: Get file metadata
30
+
31
+ 2. **Terminal execution** (terminal-execute):
32
+ - Run commands exactly as typed in terminal
33
+ - Examples: "npm -v", "git status", "node index.js"
34
+
35
+ 3. **TODO tools** (todo-*):
36
+ - Track task progress with todo-create, todo-update, todo-add
37
+ - Mark tasks completed immediately after finishing
38
+
39
+ Just type the command as you would in terminal. That's it.
40
+
41
+ Error handling:
42
+ - If command fails, check the error and try alternatives
43
+ - Use filesystem tools when terminal commands don't work`;
package/dist/app.js CHANGED
@@ -1,11 +1,13 @@
1
- import React, { useState } from 'react';
1
+ import React, { useState, useEffect } from 'react';
2
2
  import { Box, Text } from 'ink';
3
3
  import { Alert } from '@inkjs/ui';
4
4
  import WelcomeScreen from './ui/pages/WelcomeScreen.js';
5
5
  import ApiConfigScreen from './ui/pages/ApiConfigScreen.js';
6
6
  import ModelConfigScreen from './ui/pages/ModelConfigScreen.js';
7
+ import MCPConfigScreen from './ui/pages/MCPConfigScreen.js';
7
8
  import ChatScreen from './ui/pages/ChatScreen.js';
8
9
  import { useGlobalExit } from './hooks/useGlobalExit.js';
10
+ import { onNavigate } from './hooks/useGlobalNavigation.js';
9
11
  export default function App({ version }) {
10
12
  const [currentView, setCurrentView] = useState('welcome');
11
13
  const [exitNotification, setExitNotification] = useState({
@@ -14,8 +16,15 @@ export default function App({ version }) {
14
16
  });
15
17
  // Global exit handler
16
18
  useGlobalExit(setExitNotification);
19
+ // Global navigation handler
20
+ useEffect(() => {
21
+ const unsubscribe = onNavigate((event) => {
22
+ setCurrentView(event.destination);
23
+ });
24
+ return unsubscribe;
25
+ }, []);
17
26
  const handleMenuSelect = (value) => {
18
- if (value === 'chat' || value === 'settings' || value === 'config' || value === 'models') {
27
+ if (value === 'chat' || value === 'settings' || value === 'config' || value === 'models' || value === 'mcp') {
19
28
  setCurrentView(value);
20
29
  }
21
30
  else if (value === 'exit') {
@@ -36,12 +45,14 @@ export default function App({ version }) {
36
45
  return (React.createElement(ApiConfigScreen, { onBack: () => setCurrentView('welcome'), onSave: () => setCurrentView('welcome') }));
37
46
  case 'models':
38
47
  return (React.createElement(ModelConfigScreen, { onBack: () => setCurrentView('welcome'), onSave: () => setCurrentView('welcome') }));
48
+ case 'mcp':
49
+ return (React.createElement(MCPConfigScreen, { onBack: () => setCurrentView('welcome'), onSave: () => setCurrentView('welcome') }));
39
50
  default:
40
51
  return (React.createElement(WelcomeScreen, { version: version, onMenuSelect: handleMenuSelect }));
41
52
  }
42
53
  };
43
- return (React.createElement(Box, { flexDirection: "column", padding: 1 },
54
+ return (React.createElement(Box, { flexDirection: "column" },
44
55
  renderView(),
45
- exitNotification.show && (React.createElement(Box, { padding: 1 },
56
+ exitNotification.show && (React.createElement(Box, { paddingX: 1 },
46
57
  React.createElement(Alert, { variant: "warning" }, exitNotification.message)))));
47
58
  }
package/dist/cli.js CHANGED
@@ -5,7 +5,7 @@ import meow from 'meow';
5
5
  import App from './app.js';
6
6
  const cli = meow(`
7
7
  Usage
8
- $ aibotpro
8
+ $ snow
9
9
 
10
10
  Options
11
11
  --help Show help
@@ -14,6 +14,22 @@ const cli = meow(`
14
14
  importMeta: import.meta,
15
15
  flags: {},
16
16
  });
17
+ // Disable bracketed paste mode on startup
18
+ process.stdout.write('\x1b[?2004l');
19
+ // Re-enable on exit to avoid polluting parent shell
20
+ const cleanup = () => {
21
+ process.stdout.write('\x1b[?2004l');
22
+ };
23
+ process.on('exit', cleanup);
24
+ process.on('SIGINT', () => {
25
+ cleanup();
26
+ process.exit(0);
27
+ });
28
+ process.on('SIGTERM', () => {
29
+ cleanup();
30
+ process.exit(0);
31
+ });
17
32
  render(React.createElement(App, { version: cli.pkg.version }), {
18
33
  exitOnCtrlC: false,
34
+ patchConsole: true,
19
35
  });