ai-worktool 1.0.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/LICENSE.txt ADDED
@@ -0,0 +1 @@
1
+ NO LICENSE
package/README.md ADDED
@@ -0,0 +1,67 @@
1
+ # 🎮 吃豆豆:单测智能体
2
+
3
+ <p align="center">
4
+ <img src="https://aicoder.jianguoke.cn/assets/images/logo.png" alt="吃豆豆logo" width="120" height="120">
5
+ </p>
6
+
7
+ <p align="center">
8
+ <strong>自动生成高质量单元测试,让代码覆盖率轻松达到100% 🚀</strong>
9
+ </p>
10
+
11
+ <p align="center">
12
+ <em>你负责编写源代码,我负责编写测试代码,解放双手,专注核心业务逻辑开发 💻</em>
13
+ </p>
14
+
15
+ ---
16
+
17
+ ## 目录
18
+ - [🎮 吃豆豆:单测智能体](#-吃豆豆单测智能体)
19
+ - [目录](#目录)
20
+ - [🌟 特性亮点](#-特性亮点)
21
+ - [🚀 核心功能](#-核心功能)
22
+ - [💎 核心价值](#-核心价值)
23
+ - [📝 使用指南](#-使用指南)
24
+ - [🔧 适用范围](#-适用范围)
25
+ - [🚀 开始使用](#-开始使用)
26
+ - [💬 联系我们](#-联系我们)
27
+
28
+ ## 🌟 特性亮点
29
+ - 🔍 **智能代码分析**:深入理解代码结构和逻辑关系
30
+ - 📝 **精准测试生成**:针对不同代码场景生成最佳测试策略
31
+ - 🔄 **持续优化**:根据测试结果不断完善测试用例
32
+ - 💡 **AI驱动**:利用先进的大语言模型提升测试质量
33
+
34
+ ## 🚀 核心功能
35
+ - 🤖 **自动化测试生成**:自动识别未覆盖代码,生成完整测试用例,直到100%代码覆盖通过
36
+ - 🛠️ **智能错误修复**:检测并修复单元测试中的语法错误、逻辑错误及断言失败
37
+ - 📊 **覆盖率可视化**:实时展示行/语句/函数/分支覆盖率,通过颜色编码直观呈现测试状态
38
+ - 🔄 **全流程自动化**:从环境初始化、依赖安装到持续测试优化,全程无需人工干预
39
+
40
+ ## 💎 核心价值
41
+ - ⚡ **提升开发效率**:再也**不用手写**单测代码,专注核心业务逻辑开发
42
+ - 🛡️ **保障代码质量**:确保测试覆盖率达标,降低线上bug风险
43
+ - 🧩 **简化测试流程**:无需深入掌握测试框架细节,一键启动全自动化测试流程
44
+ - 👁️ **可视化反馈**:通过直观的覆盖率报表和进度展示,让测试状态一目了然
45
+
46
+ ## 📝 使用指南
47
+ 1. 📂 打开项目文件夹
48
+ 2. ▶️ 启动吃豆豆智能体
49
+ 3. 📈 查看实时测试进度和覆盖率报告
50
+ 4. 🧠 智能体自动编写代码
51
+
52
+ ## 🔧 适用范围
53
+ - 🔌 支持多种调用方式:VSCode插件、CLI命令
54
+ - 💻 支持多种编程语言:TypeScript、JavaScript等
55
+ - 🧪 支持主流测试框架:Jest、Vitest等
56
+
57
+ ---
58
+
59
+ ## 🚀 开始使用
60
+ 准备好提升你的开发效率了吗?立即安装吃豆豆单测智能体,让测试工作变得轻松简单!
61
+
62
+ ## 💬 联系我们
63
+ 如有任何问题或建议,可以给我发邮件 9160294@qq.com。
64
+
65
+ <p align="center">
66
+ <strong>© 2025 吃豆豆单测智能体 - 让单元测试自动化、智能化</strong>
67
+ </p>
@@ -0,0 +1,382 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.startAgent = exports.stopAgent = exports.setConversationId = exports.addMessage = void 0;
37
+ const tools = __importStar(require("../tools"));
38
+ const os = __importStar(require("os"));
39
+ const toolCall_1 = require("./toolCall");
40
+ // API配置 - 方便修改和更新
41
+ const API_CONFIG = {
42
+ // 聊天API地址
43
+ // eslint-disable-next-line @typescript-eslint/naming-convention
44
+ CHAT_ENDPOINT: 'https://inte-oneai.chanjet.com.cn/assistant/v1/chat',
45
+ // 函数调用结果上报地址前缀
46
+ // eslint-disable-next-line @typescript-eslint/naming-convention
47
+ FUNCTION_RESULT_ENDPOINT_PREFIX: 'https://inte-oneai.chanjet.com.cn/assistant/v1/function_call/update_result/',
48
+ // 认证信息
49
+ // eslint-disable-next-line @typescript-eslint/naming-convention
50
+ AUTH_TOKEN: 'Bearer DF#app-35EgnCA8VOlHFaRkR39pXvOw',
51
+ // 函数调用结果上报API密钥
52
+ // eslint-disable-next-line @typescript-eslint/naming-convention
53
+ API_KEY: 'your_api_key',
54
+ // 请求配置
55
+ // eslint-disable-next-line @typescript-eslint/naming-convention
56
+ REQUEST_CONFIG: {
57
+ channel: 'VSCode插件',
58
+ // eslint-disable-next-line @typescript-eslint/naming-convention
59
+ user_id: os.userInfo().username,
60
+ // eslint-disable-next-line @typescript-eslint/naming-convention
61
+ tenant_id: 'none'
62
+ }
63
+ };
64
+ let conversationId = ''; // 会话ID
65
+ // 添加日志
66
+ const addLog = (text, type = '') => {
67
+ console.log(text);
68
+ };
69
+ let lateLines = 0;
70
+ function wrapText(text, width = process.stdout.columns) {
71
+ // 按现有换行符分割文本
72
+ const paragraphs = text.split('\n');
73
+ let totalLines = 0;
74
+ let wrappedText = '';
75
+ paragraphs.forEach((paragraph, index) => {
76
+ let currentLine = '';
77
+ let currentLineWidth = 0;
78
+ let paragraphLines = 1; // 每个段落至少有一行
79
+ for (let i = 0; i < paragraph.length; i++) {
80
+ const char = paragraph[i];
81
+ const charWidth = getCharWidth(char);
82
+ if (currentLineWidth + charWidth > width) {
83
+ currentLine += '\n';
84
+ currentLine += char;
85
+ currentLineWidth = charWidth;
86
+ paragraphLines++;
87
+ }
88
+ else {
89
+ currentLine += char;
90
+ currentLineWidth += charWidth;
91
+ }
92
+ }
93
+ // 添加处理后的段落(最后一个段落不添加额外换行)
94
+ wrappedText += currentLine + (index < paragraphs.length - 1 ? '\n' : '');
95
+ totalLines += paragraphLines;
96
+ });
97
+ return {
98
+ wrappedText,
99
+ lines: totalLines
100
+ };
101
+ }
102
+ function getCharWidth(char) {
103
+ const code = char.codePointAt(0) ?? 0;
104
+ // 全角字符范围判断
105
+ return (code >= 0x1100 && code <= 0x11ff) || // Hangul Jamo
106
+ (code >= 0x2e80 && code <= 0xa4cf) || // CJK ... Yi
107
+ (code >= 0xac00 && code <= 0xd7af) || // Hangul Syllables
108
+ (code >= 0xf900 && code <= 0xfaff) || // CJK Compatibility Ideographs
109
+ (code >= 0xfe30 && code <= 0xfe4f) || // CJK Compatibility Forms
110
+ (code >= 0xff00 && code <= 0xffef) // Halfwidth and Fullwidth Forms
111
+ ? 2
112
+ : 1;
113
+ }
114
+ const addMessage = (text, type = 'bot') => {
115
+ if (!process.stdout.moveCursor) {
116
+ console.log(text);
117
+ return;
118
+ }
119
+ const { lines } = wrapText(text);
120
+ process.stdout.moveCursor(0, -lateLines);
121
+ process.stdout.write(text + '\n');
122
+ lateLines = lines;
123
+ };
124
+ exports.addMessage = addMessage;
125
+ const setConversationId = (id) => {
126
+ conversationId = id;
127
+ };
128
+ exports.setConversationId = setConversationId;
129
+ const invokeTool = async (toolName, params) => {
130
+ let data = null;
131
+ try {
132
+ const tool = tools[toolName];
133
+ if (!tool) {
134
+ throw new Error(`Tool ${toolName} not found`);
135
+ }
136
+ data = await tool.apply(null, params);
137
+ }
138
+ catch (err) {
139
+ data = err.message || '执行失败';
140
+ }
141
+ return data || '执行完成';
142
+ };
143
+ // 处理事件消息
144
+ const handleEventMessage = (contentObj) => {
145
+ switch (contentObj.type) {
146
+ case 'workflow_started':
147
+ addLog('工作流开始', 'workflow');
148
+ break;
149
+ case 'workflow_finished':
150
+ addLog('工作流结束', 'workflow');
151
+ break;
152
+ case 'node_started':
153
+ const startNodeInfo = contentObj.text || {};
154
+ addLog(`节点开始: ${startNodeInfo.title || ''} (${startNodeInfo.node_type || '未知类型'})`, 'node');
155
+ break;
156
+ case 'node_finished':
157
+ const finishNodeInfo = contentObj.text || {};
158
+ addLog(`节点结束: ${finishNodeInfo.title || ''} (${finishNodeInfo.node_type || '未知类型'})`, 'node');
159
+ break;
160
+ case 'message_end':
161
+ addLog('消息结束', 'workflow');
162
+ break;
163
+ default:
164
+ addLog(`事件: ${contentObj.type}`, 'workflow');
165
+ }
166
+ };
167
+ const reportFunctionResult = async (taskId, result) => {
168
+ addLog(`上报函数调用结果: ${taskId} ${result.success ? 'success' : 'failed'} ${typeof result.data === 'string' ? result.data : JSON.stringify(result.data, null, 2)}`);
169
+ try {
170
+ const response = await fetch(API_CONFIG.FUNCTION_RESULT_ENDPOINT_PREFIX + taskId, {
171
+ method: 'POST',
172
+ headers: {
173
+ // eslint-disable-next-line @typescript-eslint/naming-convention
174
+ 'Content-Type': 'application/json'
175
+ },
176
+ body: JSON.stringify({
177
+ result
178
+ })
179
+ });
180
+ // if (response.ok) {
181
+ // addLog('函数调用结果上报成功');
182
+ // } else {
183
+ // addLog(`函数调用结果上报失败: ${response.status}`);
184
+ // }
185
+ }
186
+ catch (error) {
187
+ console.error('上报函数调用结果错误:', error);
188
+ addLog(`上报函数调用结果错误: ${error.message}`, 'error');
189
+ }
190
+ };
191
+ const stopAgent = async (taskId) => {
192
+ try {
193
+ await fetch(`${API_CONFIG.CHAT_ENDPOINT}/${taskId}/stop`, {
194
+ method: 'POST',
195
+ headers: {
196
+ // eslint-disable-next-line @typescript-eslint/naming-convention
197
+ Authorization: API_CONFIG.AUTH_TOKEN,
198
+ // eslint-disable-next-line @typescript-eslint/naming-convention
199
+ 'Content-Type': 'application/json'
200
+ },
201
+ body: JSON.stringify(API_CONFIG.REQUEST_CONFIG)
202
+ });
203
+ }
204
+ catch (err) {
205
+ console.log(err);
206
+ }
207
+ };
208
+ exports.stopAgent = stopAgent;
209
+ // 获取机器人响应
210
+ const startAgent = async (name, userMessage, vars, withConversation, ctrl, mode, onMessage, onToolCall) => {
211
+ if (withConversation !== true) {
212
+ (0, exports.setConversationId)(''); // 每次重来
213
+ }
214
+ // console.log('conversationId:', conversationId);
215
+ const requestData = {
216
+ ...API_CONFIG.REQUEST_CONFIG,
217
+ // eslint-disable-next-line @typescript-eslint/naming-convention
218
+ conversation_id: conversationId,
219
+ variables: {
220
+ agent: name,
221
+ ...vars,
222
+ testFilePatterns: vars?.testFilePatterns?.join(','),
223
+ languages: vars?.languages?.join(',')
224
+ },
225
+ // eslint-disable-next-line @typescript-eslint/naming-convention
226
+ completion_option: {
227
+ stream: true,
228
+ append: false
229
+ },
230
+ messages: [
231
+ {
232
+ role: 'user',
233
+ // eslint-disable-next-line @typescript-eslint/naming-convention
234
+ content_type: 'text',
235
+ content: JSON.stringify({ type: 'text', text: userMessage }),
236
+ attachments: []
237
+ }
238
+ ]
239
+ };
240
+ console.log(userMessage);
241
+ let response;
242
+ try {
243
+ response = await fetch(API_CONFIG.CHAT_ENDPOINT, {
244
+ method: 'POST',
245
+ signal: ctrl?.signal,
246
+ headers: {
247
+ // eslint-disable-next-line @typescript-eslint/naming-convention
248
+ Authorization: API_CONFIG.AUTH_TOKEN,
249
+ // eslint-disable-next-line @typescript-eslint/naming-convention
250
+ 'Content-Type': 'application/json'
251
+ },
252
+ body: JSON.stringify(requestData)
253
+ });
254
+ }
255
+ catch (err) {
256
+ throw new Error('大模型调用失败,' + err.message);
257
+ }
258
+ if (!response.ok) {
259
+ throw new Error(`HTTP错误! 状态码: ${response.status}`);
260
+ }
261
+ const reader = response.body.getReader();
262
+ const decoder = new TextDecoder();
263
+ let botResponse = '';
264
+ let botId = Date.now();
265
+ const clearMessage = () => {
266
+ if (botResponse) {
267
+ addLog(botResponse, 'bot');
268
+ botResponse = '';
269
+ }
270
+ lateLines = 0;
271
+ botId = Date.now();
272
+ };
273
+ // 处理函数调用
274
+ const handleFunctionCall = async (contentObj) => {
275
+ if (contentObj.type !== 'function_call') {
276
+ return;
277
+ }
278
+ try {
279
+ const functionCallData = JSON.parse(contentObj.text);
280
+ if (!functionCallData) {
281
+ return;
282
+ }
283
+ const { function_name: functionName, params, task_id: taskId } = functionCallData;
284
+ // console.log(JSON.stringify(params))
285
+ addLog(`函数调用: ${functionName}(\n${params
286
+ .map((it) => (typeof it === 'string' ? it.replace(/\\n/g, '\n') : JSON.stringify(it, null, 2)))
287
+ .join(',\n')})`, 'function');
288
+ await onMessage?.((0, toolCall_1.formatToolCallInfo)(functionCallData, vars.projectRoot), taskId);
289
+ let result = null;
290
+ try {
291
+ const ret = await invokeTool(functionName, params);
292
+ result = { success: true, data: ret };
293
+ await onToolCall?.({
294
+ functionName,
295
+ params,
296
+ result
297
+ });
298
+ }
299
+ catch (e) {
300
+ addLog(`函数调用失败: ${functionName}`, 'error');
301
+ result = { success: false, message: e instanceof Error ? e.message : '未知错误' };
302
+ await onToolCall?.({
303
+ functionName,
304
+ params,
305
+ result
306
+ });
307
+ }
308
+ await reportFunctionResult(taskId, result);
309
+ await onMessage?.((0, toolCall_1.formatToolCallInfo)(functionCallData, vars.projectRoot, result), taskId);
310
+ }
311
+ catch (error) {
312
+ console.error('函数调用错误:', error);
313
+ addLog(`函数调用错误: ${error instanceof Error ? error.message : '未知错误'}`, 'error');
314
+ }
315
+ };
316
+ let taskId = '';
317
+ while (true) {
318
+ const { value, done } = await reader.read();
319
+ if (done) {
320
+ break;
321
+ }
322
+ const chunk = decoder.decode(value, { stream: true });
323
+ const sseChunks = chunk.split('\n\n').filter((line) => line.trim().startsWith('data: '));
324
+ for (const sseChunk of sseChunks) {
325
+ const jsonStr = sseChunk.substring(6);
326
+ const data = JSON.parse(jsonStr);
327
+ if (!data?.answer?.length) {
328
+ continue;
329
+ }
330
+ if (data.conversation_id && !conversationId) {
331
+ (0, exports.setConversationId)(data.conversation_id);
332
+ // addLog(`会话ID: ${data.conversation_id}`);
333
+ }
334
+ for (const item of data.answer) {
335
+ if (!item.message) {
336
+ continue;
337
+ }
338
+ const { role, content_type: contentType, content } = item.message;
339
+ if (role !== 'assistant') {
340
+ continue;
341
+ }
342
+ const contentObj = JSON.parse(content);
343
+ if (!contentObj) {
344
+ continue;
345
+ }
346
+ switch (contentType) {
347
+ case 'event':
348
+ clearMessage();
349
+ handleEventMessage(contentObj);
350
+ if (contentObj.type === 'workflow_started') {
351
+ taskId = contentObj.task_id;
352
+ }
353
+ break;
354
+ case 'text':
355
+ if (contentObj.type === 'text' && contentObj.text) {
356
+ if (mode === 'full') {
357
+ botResponse += contentObj.text;
358
+ }
359
+ else {
360
+ botResponse = contentObj.text;
361
+ }
362
+ await onMessage?.(botResponse, name + botId);
363
+ // addMessage(botResponse, 'bot');
364
+ }
365
+ break;
366
+ case 'function_call':
367
+ clearMessage();
368
+ await handleFunctionCall(contentObj);
369
+ break;
370
+ default:
371
+ clearMessage();
372
+ addLog(`未知的内容类型: ${contentType}`);
373
+ }
374
+ }
375
+ }
376
+ }
377
+ if (ctrl?.signal.aborted && taskId) {
378
+ await (0, exports.stopAgent)(taskId);
379
+ }
380
+ };
381
+ exports.startAgent = startAgent;
382
+ //# sourceMappingURL=chanjet.js.map
@@ -0,0 +1,73 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
36
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
37
+ };
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.setConversationId = void 0;
40
+ exports.getPlatform = getPlatform;
41
+ exports.setPlatform = setPlatform;
42
+ exports.startAgent = startAgent;
43
+ const jianguoke = __importStar(require("./jianguoke"));
44
+ const chanjet = __importStar(require("./chanjet"));
45
+ __exportStar(require("./prompt"), exports);
46
+ const platforms = {
47
+ chanjet,
48
+ jianguoke
49
+ };
50
+ let currentPlatform;
51
+ function getPlatform() {
52
+ return platforms[currentPlatform || 'jianguoke'];
53
+ }
54
+ function setPlatform(target) {
55
+ currentPlatform = target;
56
+ }
57
+ const setConversationId = (id) => {
58
+ const platform = getPlatform();
59
+ if (!platform) {
60
+ throw new Error(`智能体 "${currentPlatform}" 不支持`);
61
+ }
62
+ platform.setConversationId(id);
63
+ };
64
+ exports.setConversationId = setConversationId;
65
+ async function startAgent(currentPlatform, name, question, vars, withConversation, ctrl, mode, onMessage, onToolCall) {
66
+ const platform = platforms[currentPlatform] || getPlatform();
67
+ if (!platform) {
68
+ throw new Error(`智能体 "${currentPlatform}" 不支持`);
69
+ }
70
+ await platform.startAgent.apply(null, [name, question, vars, withConversation, ctrl, mode, onMessage, onToolCall]);
71
+ console.log(`智能体 "${currentPlatform}" 调用完成`);
72
+ }
73
+ //# sourceMappingURL=index.js.map