@probelabs/probe
Version:
Node.js wrapper for the probe code search tool
230 lines (186 loc) ⢠6.55 kB
JavaScript
/**
* ACP Tool Lifecycle Example
*
* This example demonstrates how ACP tracks tool execution lifecycle
* with status notifications for search, query, and extract operations.
*
* Usage:
* node tool-lifecycle.js
*/
import { spawn } from 'child_process';
class ACPToolLifecycleDemo {
constructor() {
this.messageId = 1;
this.pendingRequests = new Map();
this.sessionId = null;
this.server = null;
this.toolCalls = new Map();
}
async start() {
console.log('š ļø ACP Tool Lifecycle Demo');
console.log('=' .repeat(40));
console.log('This demo shows how ACP tracks tool execution with status updates\n');
// Start server
this.server = spawn('node', ['../../../index.js', '--acp', '--verbose'], {
stdio: ['pipe', 'pipe', 'pipe']
});
this.server.stdout.on('data', (data) => {
const lines = data.toString().split('\n').filter(line => line.trim());
for (const line of lines) {
try {
const message = JSON.parse(line);
this.handleMessage(message);
} catch (error) {
// Ignore parse errors for non-JSON output
}
}
});
this.server.stderr.on('data', (data) => {
// Log server debug output
const output = data.toString().trim();
if (output && !output.includes('Token usage')) {
console.log(`š ${output}`);
}
});
await new Promise(resolve => setTimeout(resolve, 1000));
}
handleMessage(message) {
if (message.id && this.pendingRequests.has(message.id)) {
// Response to our request
const { resolve, reject } = this.pendingRequests.get(message.id);
this.pendingRequests.delete(message.id);
if (message.error) {
reject(new Error(`${message.error.code}: ${message.error.message}`));
} else {
resolve(message.result);
}
} else if (message.method === 'toolCallProgress') {
// Tool lifecycle notification
this.handleToolProgress(message.params);
}
}
handleToolProgress(params) {
const { toolCallId, status, sessionId, result, error } = params;
// Track tool call
if (!this.toolCalls.has(toolCallId)) {
this.toolCalls.set(toolCallId, {
id: toolCallId,
sessionId,
startTime: Date.now(),
status: 'pending'
});
}
const toolCall = this.toolCalls.get(toolCallId);
toolCall.status = status;
toolCall.endTime = Date.now();
toolCall.duration = toolCall.endTime - toolCall.startTime;
// Display status update
const statusIcon = {
pending: 'ā³',
in_progress: 'š',
completed: 'ā
',
failed: 'ā'
}[status] || 'ā';
console.log(`${statusIcon} Tool ${toolCallId.slice(0, 8)}: ${status.toUpperCase()}`);
if (status === 'completed' && result) {
const preview = typeof result === 'string'
? result.slice(0, 100) + (result.length > 100 ? '...' : '')
: 'Object result';
console.log(` š Result: ${preview}`);
}
if (status === 'failed' && error) {
console.log(` š„ Error: ${error}`);
}
if (status === 'completed' || status === 'failed') {
console.log(` ā±ļø Duration: ${toolCall.duration}ms`);
}
console.log();
}
async sendRequest(method, params = null) {
const id = this.messageId++;
const message = { jsonrpc: '2.0', method, id };
if (params) message.params = params;
return new Promise((resolve, reject) => {
this.pendingRequests.set(id, { resolve, reject });
this.server.stdin.write(JSON.stringify(message) + '\n');
setTimeout(() => {
if (this.pendingRequests.has(id)) {
this.pendingRequests.delete(id);
reject(new Error('Timeout'));
}
}, 30000);
});
}
async setup() {
await this.sendRequest('initialize', { protocolVersion: '1' });
const session = await this.sendRequest('newSession', {});
this.sessionId = session.sessionId;
console.log(`š Session created: ${this.sessionId}\n`);
}
async demonstrateToolLifecycle() {
console.log('š Demonstrating Search Tool Lifecycle');
console.log('-'.repeat(40));
await this.sendRequest('prompt', {
sessionId: this.sessionId,
message: 'Search for functions named "search" in the codebase'
});
await new Promise(resolve => setTimeout(resolve, 2000));
console.log('šÆ Demonstrating Query Tool Lifecycle');
console.log('-'.repeat(40));
await this.sendRequest('prompt', {
sessionId: this.sessionId,
message: 'Find all function definitions using structural patterns'
});
await new Promise(resolve => setTimeout(resolve, 2000));
console.log('š¤ Demonstrating Extract Tool Lifecycle');
console.log('-'.repeat(40));
await this.sendRequest('prompt', {
sessionId: this.sessionId,
message: 'Extract the main function from the entry point file'
});
await new Promise(resolve => setTimeout(resolve, 3000));
}
async showStatistics() {
console.log('\nš Tool Execution Statistics');
console.log('='.repeat(40));
const completedCalls = Array.from(this.toolCalls.values())
.filter(call => call.status === 'completed');
const failedCalls = Array.from(this.toolCalls.values())
.filter(call => call.status === 'failed');
console.log(`Total tool calls: ${this.toolCalls.size}`);
console.log(`Completed: ${completedCalls.length}`);
console.log(`Failed: ${failedCalls.length}`);
if (completedCalls.length > 0) {
const avgDuration = completedCalls
.reduce((sum, call) => sum + call.duration, 0) / completedCalls.length;
console.log(`Average duration: ${Math.round(avgDuration)}ms`);
}
console.log();
}
async close() {
this.server?.kill();
}
}
async function main() {
const demo = new ACPToolLifecycleDemo();
try {
await demo.start();
await demo.setup();
await demo.demonstrateToolLifecycle();
await demo.showStatistics();
} catch (error) {
console.error('ā Demo failed:', error.message);
} finally {
await demo.close();
console.log('š Demo completed!');
process.exit(0);
}
}
process.on('SIGINT', () => {
console.log('\nā¹ļø Demo stopped');
process.exit(0);
});
if (import.meta.url === `file://${process.argv[1]}`) {
main().catch(console.error);
}