@probelabs/probe-chat 0.6.0-rc100

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.
@@ -0,0 +1,471 @@
1
+ import { describe, it, before, after, beforeEach, afterEach } from 'node:test';
2
+ import assert from 'node:assert';
3
+ import path from 'path';
4
+ import {
5
+ setupTestEnvironment,
6
+ createTestProbeChat,
7
+ captureConsoleOutput,
8
+ runChatInteraction,
9
+ createTempTestFiles,
10
+ createMockProbeResults,
11
+ testData
12
+ } from '../testUtils.js';
13
+ import { mockResponses } from '../mocks/mockLLMProvider.js';
14
+
15
+ describe('Tool Calling Integration Tests', () => {
16
+ let testEnv;
17
+ let tempFiles;
18
+
19
+ before(() => {
20
+ testEnv = setupTestEnvironment();
21
+ });
22
+
23
+ after(() => {
24
+ testEnv.restore();
25
+ if (tempFiles) {
26
+ tempFiles.cleanup();
27
+ }
28
+ });
29
+
30
+ beforeEach(() => {
31
+ // Create temporary test files
32
+ tempFiles = createTempTestFiles({
33
+ 'src/math.js': testData.sampleCode.javascript,
34
+ 'src/math.py': testData.sampleCode.python,
35
+ 'src/math.rs': testData.sampleCode.rust,
36
+ 'test/math.test.js': 'describe("math tests", () => {});'
37
+ });
38
+ });
39
+
40
+ afterEach(() => {
41
+ if (tempFiles) {
42
+ tempFiles.cleanup();
43
+ tempFiles = null;
44
+ }
45
+ });
46
+
47
+ describe('Probe Search Tool', () => {
48
+ it('should handle search tool calls', async () => {
49
+ const { probeChat, mockProvider } = await createTestProbeChat({
50
+ responses: [
51
+ mockResponses.withToolCall,
52
+ { text: 'I found the search results for your query.' }
53
+ ]
54
+ });
55
+
56
+ // Mock the probe search tool
57
+ probeChat.tools.probe_search.execute = async (args) => {
58
+ return createMockProbeResults({
59
+ path: path.join(tempFiles.tempDir, 'src/math.js'),
60
+ match: 'fibonacci',
61
+ context: testData.sampleCode.javascript
62
+ });
63
+ };
64
+
65
+ const results = await runChatInteraction(probeChat,
66
+ [{ role: 'user', content: 'Search for fibonacci functions' }]
67
+ );
68
+
69
+ assert.strictEqual(results.toolCalls.length, 1);
70
+ assert.strictEqual(results.toolCalls[0].toolName, 'probe_search');
71
+ assert.deepStrictEqual(results.toolCalls[0].args, {
72
+ query: 'test query',
73
+ path: './src'
74
+ });
75
+ assert.strictEqual(results.errors.length, 0);
76
+ });
77
+
78
+ it('should handle multiple search tool calls', async () => {
79
+ const { probeChat } = await createTestProbeChat({
80
+ responses: [
81
+ mockResponses.multipleToolCalls,
82
+ { text: 'I completed both search and extract operations.' }
83
+ ]
84
+ });
85
+
86
+ // Mock the tools
87
+ let toolExecutions = [];
88
+ probeChat.tools.probe_search.execute = async (args) => {
89
+ toolExecutions.push({ tool: 'search', args });
90
+ return createMockProbeResults();
91
+ };
92
+
93
+ probeChat.tools.probe_extract.execute = async (args) => {
94
+ toolExecutions.push({ tool: 'extract', args });
95
+ return {
96
+ content: testData.sampleCode.javascript,
97
+ language: 'javascript',
98
+ symbols: []
99
+ };
100
+ };
101
+
102
+ await runChatInteraction(probeChat,
103
+ [{ role: 'user', content: 'Search and extract code' }]
104
+ );
105
+
106
+ assert.strictEqual(toolExecutions.length, 2);
107
+ assert.strictEqual(toolExecutions[0].tool, 'search');
108
+ assert.strictEqual(toolExecutions[1].tool, 'extract');
109
+ });
110
+
111
+ it('should handle search with advanced options', async () => {
112
+ const { probeChat } = await createTestProbeChat({
113
+ responses: [{
114
+ text: 'Let me search with those specific parameters.',
115
+ toolCalls: [{
116
+ toolName: 'probe_search',
117
+ args: {
118
+ query: 'async function',
119
+ path: './src',
120
+ maxResults: 10,
121
+ maxTokens: 2000
122
+ }
123
+ }]
124
+ }, { text: 'Found results with your criteria.' }]
125
+ });
126
+
127
+ let capturedArgs;
128
+ probeChat.tools.probe_search.execute = async (args) => {
129
+ capturedArgs = args;
130
+ return createMockProbeResults();
131
+ };
132
+
133
+ await runChatInteraction(probeChat,
134
+ [{ role: 'user', content: 'Search for async functions in src, limit to 10 results' }]
135
+ );
136
+
137
+ assert.deepStrictEqual(capturedArgs, {
138
+ query: 'async function',
139
+ path: './src',
140
+ maxResults: 10,
141
+ maxTokens: 2000
142
+ });
143
+ });
144
+ });
145
+
146
+ describe('Implement Tool', () => {
147
+ it('should handle implement tool calls', async () => {
148
+ const { probeChat, mockBackend } = await createTestProbeChat({
149
+ responses: [
150
+ mockResponses.implementToolCall,
151
+ { text: 'The implementation is complete!' }
152
+ ],
153
+ useMockBackend: true,
154
+ mockBackendResponses: [{
155
+ filesModified: ['src/math.js'],
156
+ summary: 'Added fibonacci function'
157
+ }]
158
+ });
159
+
160
+ const results = await runChatInteraction(probeChat,
161
+ [{ role: 'user', content: 'Add a fibonacci function to math.js' }]
162
+ );
163
+
164
+ assert.strictEqual(results.toolCalls.length, 1);
165
+ assert.strictEqual(results.toolCalls[0].toolName, 'implement');
166
+ assert.strictEqual(mockBackend.capturedRequests.length, 1);
167
+
168
+ const request = mockBackend.getLastRequest();
169
+ assert.strictEqual(request.request.request, 'Add a new function to calculate fibonacci numbers');
170
+ assert.deepStrictEqual(request.request.files, ['src/math.js']);
171
+ assert.strictEqual(request.request.backend, 'mock');
172
+ });
173
+
174
+ it('should handle implement tool with streaming', async () => {
175
+ const { probeChat, mockBackend } = await createTestProbeChat({
176
+ responses: [
177
+ mockResponses.implementToolCall,
178
+ { text: 'Implementation completed with streaming!' }
179
+ ],
180
+ useMockBackend: true,
181
+ mockBackendResponses: [{
182
+ stream: [
183
+ { type: 'start', message: 'Starting implementation' },
184
+ { type: 'progress', message: 'Generating code' },
185
+ { type: 'file_update', file: 'src/math.js', action: 'modified' },
186
+ { type: 'complete', message: 'Done' }
187
+ ],
188
+ filesModified: ['src/math.js']
189
+ }]
190
+ });
191
+
192
+ mockBackend.setResponseDelay(50); // Fast streaming for tests
193
+
194
+ let streamEvents = [];
195
+ const originalImplementExecute = probeChat.tools.implement.execute;
196
+ probeChat.tools.implement.execute = async function(args) {
197
+ return originalImplementExecute.call(this, args, {
198
+ onProgress: (event) => {
199
+ streamEvents.push(event);
200
+ }
201
+ });
202
+ };
203
+
204
+ await runChatInteraction(probeChat,
205
+ [{ role: 'user', content: 'Implement fibonacci with streaming' }]
206
+ );
207
+
208
+ assert.ok(streamEvents.length >= 4, 'Should receive multiple stream events');
209
+ assert.strictEqual(streamEvents[0].type, 'start');
210
+ assert.strictEqual(streamEvents[streamEvents.length - 1].type, 'complete');
211
+ });
212
+
213
+ it('should handle backend selection', async () => {
214
+ const { probeChat, mockBackend } = await createTestProbeChat({
215
+ responses: [{
216
+ text: 'I will use the mock backend.',
217
+ toolCalls: [{
218
+ toolName: 'implement',
219
+ args: {
220
+ request: 'Test backend selection',
221
+ backend: 'mock'
222
+ }
223
+ }]
224
+ }, { text: 'Done with mock backend.' }],
225
+ useMockBackend: true
226
+ });
227
+
228
+ await runChatInteraction(probeChat,
229
+ [{ role: 'user', content: 'Implement something using mock backend' }]
230
+ );
231
+
232
+ const request = mockBackend.getLastRequest();
233
+ assert.ok(request, 'Should capture the request');
234
+ assert.strictEqual(request.request.backend, 'mock');
235
+ });
236
+
237
+ it('should handle backend errors', async () => {
238
+ const { probeChat, mockBackend } = await createTestProbeChat({
239
+ responses: [
240
+ mockResponses.implementToolCall,
241
+ { text: 'The implementation failed, but I handled it gracefully.' }
242
+ ],
243
+ useMockBackend: true
244
+ });
245
+
246
+ mockBackend.setErrorMode(true);
247
+
248
+ let toolError;
249
+ probeChat.tools.implement.handleError = (error) => {
250
+ toolError = error;
251
+ };
252
+
253
+ const results = await runChatInteraction(probeChat,
254
+ [{ role: 'user', content: 'Try to implement with error' }]
255
+ );
256
+
257
+ // The tool should handle the error gracefully
258
+ assert.strictEqual(results.errors.length, 0, 'Chat should not error');
259
+ assert.ok(toolError, 'Tool should capture the error');
260
+ });
261
+ });
262
+
263
+ describe('Query Tool', () => {
264
+ it('should handle semantic query tool calls', async () => {
265
+ const { probeChat } = await createTestProbeChat({
266
+ responses: [{
267
+ text: 'Let me query for that pattern.',
268
+ toolCalls: [{
269
+ toolName: 'probe_query',
270
+ args: {
271
+ pattern: 'function $name($args) { $body }',
272
+ path: './src'
273
+ }
274
+ }]
275
+ }, { text: 'Found matching patterns.' }]
276
+ });
277
+
278
+ let queryArgs;
279
+ probeChat.tools.probe_query.execute = async (args) => {
280
+ queryArgs = args;
281
+ return {
282
+ matches: [{
283
+ file: 'src/math.js',
284
+ matches: [{ text: 'function fibonacci(n) { ... }' }]
285
+ }]
286
+ };
287
+ };
288
+
289
+ await runChatInteraction(probeChat,
290
+ [{ role: 'user', content: 'Find all function definitions' }]
291
+ );
292
+
293
+ assert.deepStrictEqual(queryArgs, {
294
+ pattern: 'function $name($args) { $body }',
295
+ path: './src'
296
+ });
297
+ });
298
+ });
299
+
300
+ describe('Extract Tool', () => {
301
+ it('should handle code extraction', async () => {
302
+ const { probeChat } = await createTestProbeChat({
303
+ responses: [{
304
+ text: 'Let me extract that code section.',
305
+ toolCalls: [{
306
+ toolName: 'probe_extract',
307
+ args: {
308
+ location: 'src/math.js:1-5'
309
+ }
310
+ }]
311
+ }, { text: 'Here is the extracted code.' }]
312
+ });
313
+
314
+ let extractArgs;
315
+ probeChat.tools.probe_extract.execute = async (args) => {
316
+ extractArgs = args;
317
+ return {
318
+ content: testData.sampleCode.javascript,
319
+ language: 'javascript',
320
+ start_line: 1,
321
+ end_line: 5
322
+ };
323
+ };
324
+
325
+ await runChatInteraction(probeChat,
326
+ [{ role: 'user', content: 'Extract lines 1-5 from math.js' }]
327
+ );
328
+
329
+ assert.deepStrictEqual(extractArgs, {
330
+ location: 'src/math.js:1-5'
331
+ });
332
+ });
333
+
334
+ it('should handle symbol extraction', async () => {
335
+ const { probeChat } = await createTestProbeChat({
336
+ responses: [{
337
+ text: 'Extracting the fibonacci function.',
338
+ toolCalls: [{
339
+ toolName: 'probe_extract',
340
+ args: {
341
+ location: 'src/math.js:fibonacci'
342
+ }
343
+ }]
344
+ }, { text: 'Extracted the function successfully.' }]
345
+ });
346
+
347
+ probeChat.tools.probe_extract.execute = async (args) => {
348
+ assert.strictEqual(args.location, 'src/math.js:fibonacci');
349
+ return {
350
+ content: testData.sampleCode.javascript,
351
+ language: 'javascript',
352
+ symbol: 'fibonacci'
353
+ };
354
+ };
355
+
356
+ await runChatInteraction(probeChat,
357
+ [{ role: 'user', content: 'Extract the fibonacci function from math.js' }]
358
+ );
359
+ });
360
+ });
361
+
362
+ describe('Tool Error Handling', () => {
363
+ it('should handle tool execution errors', async () => {
364
+ const { probeChat } = await createTestProbeChat({
365
+ responses: [
366
+ mockResponses.withToolCall,
367
+ { text: 'I encountered an error but will continue.' }
368
+ ]
369
+ });
370
+
371
+ // Make the tool throw an error
372
+ probeChat.tools.probe_search.execute = async () => {
373
+ throw new Error('Search tool error');
374
+ };
375
+
376
+ let capturedToolError;
377
+ probeChat.handleToolError = (error, toolName) => {
378
+ capturedToolError = { error, toolName };
379
+ return 'Tool error handled';
380
+ };
381
+
382
+ const results = await runChatInteraction(probeChat,
383
+ [{ role: 'user', content: 'Search for something' }]
384
+ );
385
+
386
+ assert.ok(capturedToolError);
387
+ assert.strictEqual(capturedToolError.toolName, 'probe_search');
388
+ assert.ok(capturedToolError.error.message.includes('Search tool error'));
389
+ });
390
+
391
+ it('should handle invalid tool arguments', async () => {
392
+ const { probeChat } = await createTestProbeChat({
393
+ responses: [{
394
+ text: 'Calling tool with invalid args.',
395
+ toolCalls: [{
396
+ toolName: 'probe_search',
397
+ args: {} // Missing required 'query' field
398
+ }]
399
+ }, { text: 'Handling the validation error.' }]
400
+ });
401
+
402
+ let validationError;
403
+ probeChat.tools.probe_search.validate = (args) => {
404
+ if (!args.query) {
405
+ validationError = new Error('Missing required field: query');
406
+ throw validationError;
407
+ }
408
+ };
409
+
410
+ await runChatInteraction(probeChat,
411
+ [{ role: 'user', content: 'Search without query' }]
412
+ );
413
+
414
+ assert.ok(validationError);
415
+ assert.ok(validationError.message.includes('Missing required field'));
416
+ });
417
+ });
418
+
419
+ describe('Complex Tool Sequences', () => {
420
+ it('should handle search → extract → implement workflow', async () => {
421
+ const { probeChat, mockBackend } = await createTestProbeChat({
422
+ responses: [
423
+ {
424
+ text: 'Let me search for existing implementations.',
425
+ toolCalls: [{
426
+ toolName: 'probe_search',
427
+ args: { query: 'fibonacci', path: './src' }
428
+ }]
429
+ },
430
+ {
431
+ text: 'Now extracting the current implementation.',
432
+ toolCalls: [{
433
+ toolName: 'probe_extract',
434
+ args: { location: 'src/math.js:fibonacci' }
435
+ }]
436
+ },
437
+ {
438
+ text: 'Implementing the optimized version.',
439
+ toolCalls: [{
440
+ toolName: 'implement',
441
+ args: {
442
+ request: 'Optimize fibonacci with memoization',
443
+ files: ['src/math.js'],
444
+ backend: 'mock'
445
+ }
446
+ }]
447
+ },
448
+ { text: 'Successfully optimized the fibonacci function!' }
449
+ ],
450
+ useMockBackend: true
451
+ });
452
+
453
+ // Mock tool implementations
454
+ probeChat.tools.probe_search.execute = async () => createMockProbeResults();
455
+ probeChat.tools.probe_extract.execute = async () => ({
456
+ content: testData.sampleCode.javascript,
457
+ language: 'javascript'
458
+ });
459
+
460
+ const results = await runChatInteraction(probeChat,
461
+ [{ role: 'user', content: 'Optimize the fibonacci function' }]
462
+ );
463
+
464
+ assert.strictEqual(results.toolCalls.length, 3);
465
+ assert.strictEqual(results.toolCalls[0].toolName, 'probe_search');
466
+ assert.strictEqual(results.toolCalls[1].toolName, 'probe_extract');
467
+ assert.strictEqual(results.toolCalls[2].toolName, 'implement');
468
+ assert.strictEqual(mockBackend.capturedRequests.length, 1);
469
+ });
470
+ });
471
+ });