acpreact 1.0.3 → 1.0.5
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/core.js +194 -132
- package/package.json +1 -1
package/core.js
CHANGED
|
@@ -1,18 +1,22 @@
|
|
|
1
1
|
import { spawn } from 'child_process';
|
|
2
|
-
import {
|
|
3
|
-
import { dirname, join } from 'path';
|
|
2
|
+
import { EventEmitter } from 'events';
|
|
4
3
|
|
|
5
|
-
|
|
6
|
-
const __dirname = dirname(__filename);
|
|
7
|
-
|
|
8
|
-
class ACPProtocol {
|
|
4
|
+
class ACPProtocol extends EventEmitter {
|
|
9
5
|
constructor(instruction) {
|
|
6
|
+
super();
|
|
10
7
|
this.messageId = 0;
|
|
11
8
|
this.instruction = instruction;
|
|
12
9
|
this.toolWhitelist = new Set();
|
|
10
|
+
this.toolSchemas = {};
|
|
11
|
+
this.toolDescriptions = {};
|
|
13
12
|
this.toolCallLog = [];
|
|
14
13
|
this.rejectedCallLog = [];
|
|
15
14
|
this.tools = {};
|
|
15
|
+
this.cliProcess = null;
|
|
16
|
+
this.pendingRequests = new Map();
|
|
17
|
+
this.buffer = '';
|
|
18
|
+
this.initialized = false;
|
|
19
|
+
this.sessionId = null;
|
|
16
20
|
}
|
|
17
21
|
|
|
18
22
|
generateRequestId() {
|
|
@@ -20,84 +24,42 @@ class ACPProtocol {
|
|
|
20
24
|
}
|
|
21
25
|
|
|
22
26
|
createJsonRpcRequest(method, params) {
|
|
23
|
-
return {
|
|
24
|
-
jsonrpc: "2.0",
|
|
25
|
-
id: this.generateRequestId(),
|
|
26
|
-
method,
|
|
27
|
-
params,
|
|
28
|
-
};
|
|
27
|
+
return { jsonrpc: "2.0", id: this.generateRequestId(), method, params };
|
|
29
28
|
}
|
|
30
29
|
|
|
31
30
|
createJsonRpcResponse(id, result) {
|
|
32
|
-
return {
|
|
33
|
-
jsonrpc: "2.0",
|
|
34
|
-
id,
|
|
35
|
-
result,
|
|
36
|
-
};
|
|
31
|
+
return { jsonrpc: "2.0", id, result };
|
|
37
32
|
}
|
|
38
33
|
|
|
39
34
|
createJsonRpcError(id, error) {
|
|
40
|
-
return {
|
|
41
|
-
jsonrpc: "2.0",
|
|
42
|
-
id,
|
|
43
|
-
error: {
|
|
44
|
-
code: -32603,
|
|
45
|
-
message: error,
|
|
46
|
-
},
|
|
47
|
-
};
|
|
35
|
+
return { jsonrpc: "2.0", id, error: { code: -32603, message: error } };
|
|
48
36
|
}
|
|
49
37
|
|
|
50
38
|
registerTool(name, description, inputSchema, handler) {
|
|
51
39
|
this.toolWhitelist.add(name);
|
|
52
40
|
this.tools[name] = handler;
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
inputSchema,
|
|
57
|
-
};
|
|
41
|
+
this.toolSchemas[name] = inputSchema;
|
|
42
|
+
this.toolDescriptions[name] = description;
|
|
43
|
+
return { name, description, inputSchema };
|
|
58
44
|
}
|
|
59
45
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
type: "tool",
|
|
46
|
+
getToolsList() {
|
|
47
|
+
return Array.from(this.toolWhitelist).map(toolName => ({
|
|
63
48
|
name: toolName,
|
|
64
|
-
|
|
49
|
+
description: this.toolDescriptions[toolName],
|
|
50
|
+
inputSchema: this.toolSchemas[toolName],
|
|
65
51
|
}));
|
|
66
|
-
|
|
67
|
-
const result = {
|
|
68
|
-
protocolVersion: "1.0",
|
|
69
|
-
serverInfo: {
|
|
70
|
-
name: "acpreact ACP Server",
|
|
71
|
-
version: "1.0.0",
|
|
72
|
-
},
|
|
73
|
-
securityConfiguration: {
|
|
74
|
-
toolWhitelistEnabled: true,
|
|
75
|
-
allowedTools: Array.from(this.toolWhitelist),
|
|
76
|
-
rejectionBehavior: "strict",
|
|
77
|
-
},
|
|
78
|
-
agentCapabilities,
|
|
79
|
-
};
|
|
80
|
-
|
|
81
|
-
if (this.instruction) {
|
|
82
|
-
result.instruction = this.instruction;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
return {
|
|
86
|
-
jsonrpc: "2.0",
|
|
87
|
-
id: 0,
|
|
88
|
-
result,
|
|
89
|
-
};
|
|
90
52
|
}
|
|
91
53
|
|
|
92
54
|
validateToolCall(toolName) {
|
|
93
55
|
if (!this.toolWhitelist.has(toolName)) {
|
|
94
56
|
const availableTools = Array.from(this.toolWhitelist);
|
|
95
|
-
const error = `Tool not available.
|
|
57
|
+
const error = `Tool not available. Available: ${availableTools.join(', ')}`;
|
|
96
58
|
this.rejectedCallLog.push({
|
|
97
59
|
timestamp: new Date().toISOString(),
|
|
98
60
|
attemptedTool: toolName,
|
|
99
61
|
reason: 'Not in whitelist',
|
|
100
|
-
availableTools
|
|
62
|
+
availableTools,
|
|
101
63
|
});
|
|
102
64
|
return { allowed: false, error };
|
|
103
65
|
}
|
|
@@ -127,101 +89,201 @@ class ACPProtocol {
|
|
|
127
89
|
throw new Error(`Unknown tool: ${toolName}`);
|
|
128
90
|
}
|
|
129
91
|
|
|
130
|
-
async
|
|
131
|
-
|
|
132
|
-
const instruction = options.instruction || this.instruction || '';
|
|
133
|
-
|
|
134
|
-
const toolsDesc = Array.from(this.toolWhitelist).map(name => {
|
|
135
|
-
const tool = this.tools[name];
|
|
136
|
-
return `- ${name}: Tool available`;
|
|
137
|
-
}).join('\n');
|
|
138
|
-
|
|
139
|
-
const prompt = `${instruction}
|
|
140
|
-
|
|
141
|
-
Available tools:
|
|
142
|
-
${toolsDesc}
|
|
143
|
-
|
|
144
|
-
Text to analyze:
|
|
145
|
-
${text}
|
|
146
|
-
|
|
147
|
-
Analyze the text and call appropriate tools using the ACP protocol. Respond with JSON-RPC tool calls.`;
|
|
92
|
+
async start(cli = 'kilo') {
|
|
93
|
+
if (this.cliProcess) return this.sessionId;
|
|
148
94
|
|
|
149
95
|
return new Promise((resolve, reject) => {
|
|
150
|
-
|
|
151
|
-
let errorOutput = '';
|
|
152
|
-
|
|
153
|
-
const child = spawn(cli, ['--stdin'], {
|
|
96
|
+
this.cliProcess = spawn('script', ['-q', '-c', `${cli} acp`, '/dev/null'], {
|
|
154
97
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
155
|
-
cwd: process.cwd()
|
|
98
|
+
cwd: process.cwd(),
|
|
99
|
+
env: { ...process.env, TERM: 'dumb' },
|
|
156
100
|
});
|
|
157
101
|
|
|
158
|
-
|
|
159
|
-
|
|
102
|
+
this.cliProcess.stdout.on('data', (data) => {
|
|
103
|
+
this.buffer += data.toString();
|
|
104
|
+
this.processBuffer();
|
|
160
105
|
});
|
|
161
106
|
|
|
162
|
-
|
|
163
|
-
|
|
107
|
+
this.cliProcess.stderr.on('data', (data) => {
|
|
108
|
+
this.emit('stderr', data.toString());
|
|
164
109
|
});
|
|
165
110
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
}
|
|
111
|
+
this.cliProcess.on('error', (error) => {
|
|
112
|
+
this.emit('error', error);
|
|
113
|
+
reject(error);
|
|
114
|
+
});
|
|
171
115
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
if (this.toolWhitelist.has(call.method)) {
|
|
178
|
-
const result = await this.callTool(call.method, call.params);
|
|
179
|
-
results.push({ tool: call.method, result });
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
resolve({
|
|
184
|
-
text: output,
|
|
185
|
-
toolCalls: results,
|
|
186
|
-
logs: this.toolCallLog
|
|
187
|
-
});
|
|
188
|
-
} catch (e) {
|
|
189
|
-
resolve({
|
|
190
|
-
text: output,
|
|
191
|
-
error: e.message,
|
|
192
|
-
logs: this.toolCallLog
|
|
193
|
-
});
|
|
194
|
-
}
|
|
116
|
+
this.cliProcess.on('close', (code) => {
|
|
117
|
+
this.emit('close', code);
|
|
118
|
+
this.cliProcess = null;
|
|
119
|
+
this.initialized = false;
|
|
120
|
+
this.sessionId = null;
|
|
195
121
|
});
|
|
196
122
|
|
|
197
|
-
|
|
198
|
-
reject(new Error(
|
|
123
|
+
const timeout = setTimeout(() => {
|
|
124
|
+
reject(new Error('ACP initialization timeout'));
|
|
125
|
+
}, 30000);
|
|
126
|
+
|
|
127
|
+
this.once('ready', () => {
|
|
128
|
+
clearTimeout(timeout);
|
|
129
|
+
resolve(this.sessionId);
|
|
199
130
|
});
|
|
200
131
|
|
|
201
|
-
|
|
202
|
-
child.stdin.end();
|
|
132
|
+
setTimeout(() => this.createSession(), 500);
|
|
203
133
|
});
|
|
204
134
|
}
|
|
205
135
|
|
|
206
|
-
|
|
207
|
-
const
|
|
208
|
-
|
|
209
|
-
|
|
136
|
+
processBuffer() {
|
|
137
|
+
const lines = this.buffer.split('\n');
|
|
138
|
+
this.buffer = lines.pop() || '';
|
|
139
|
+
|
|
210
140
|
for (const line of lines) {
|
|
211
141
|
const trimmed = line.trim();
|
|
212
142
|
if (!trimmed) continue;
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
143
|
+
this.handleMessage(trimmed);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
handleMessage(line) {
|
|
148
|
+
let msg;
|
|
149
|
+
try {
|
|
150
|
+
msg = JSON.parse(line);
|
|
151
|
+
} catch {
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if (msg.method === 'initialize') {
|
|
156
|
+
this.send(this.createInitializeResponse(msg.id));
|
|
157
|
+
this.initialized = true;
|
|
158
|
+
} else if (msg.id !== undefined && msg.result !== undefined) {
|
|
159
|
+
const resolver = this.pendingRequests.get(msg.id);
|
|
160
|
+
if (resolver) {
|
|
161
|
+
this.pendingRequests.delete(msg.id);
|
|
162
|
+
resolver(msg.result);
|
|
163
|
+
}
|
|
164
|
+
if (msg.id === 1 && msg.result?.sessionId) {
|
|
165
|
+
this.sessionId = msg.result.sessionId;
|
|
166
|
+
this.emit('ready');
|
|
167
|
+
}
|
|
168
|
+
} else if (msg.id !== undefined && msg.error !== undefined) {
|
|
169
|
+
const resolver = this.pendingRequests.get(msg.id);
|
|
170
|
+
if (resolver) {
|
|
171
|
+
this.pendingRequests.delete(msg.id);
|
|
172
|
+
resolver({ error: msg.error });
|
|
221
173
|
}
|
|
174
|
+
} else if (msg.method?.startsWith('tools/')) {
|
|
175
|
+
const toolName = msg.method.replace('tools/', '');
|
|
176
|
+
this.handleToolCall(msg.id, toolName, msg.params);
|
|
177
|
+
} else if (msg.method === 'session/update') {
|
|
178
|
+
this.emit('update', msg.params);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
async handleToolCall(id, toolName, params) {
|
|
183
|
+
try {
|
|
184
|
+
const result = await this.callTool(toolName, params);
|
|
185
|
+
this.send(this.createJsonRpcResponse(id, result));
|
|
186
|
+
} catch (e) {
|
|
187
|
+
this.send(this.createJsonRpcError(id, e.message));
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
send(msg) {
|
|
192
|
+
if (this.cliProcess && this.cliProcess.stdin.writable) {
|
|
193
|
+
this.cliProcess.stdin.write(JSON.stringify(msg) + '\n');
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
createInitializeResponse(id) {
|
|
198
|
+
const result = {
|
|
199
|
+
protocolVersion: 1,
|
|
200
|
+
capabilities: { tools: this.getToolsList() },
|
|
201
|
+
serverInfo: { name: 'acpreact', version: '1.0.0' },
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
if (this.instruction) {
|
|
205
|
+
result.instruction = this.instruction;
|
|
222
206
|
}
|
|
207
|
+
|
|
208
|
+
return { jsonrpc: "2.0", id, result };
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
createSession() {
|
|
212
|
+
this.send({
|
|
213
|
+
jsonrpc: "2.0",
|
|
214
|
+
id: 1,
|
|
215
|
+
method: "session/new",
|
|
216
|
+
params: {
|
|
217
|
+
cwd: process.cwd(),
|
|
218
|
+
mcpServers: [],
|
|
219
|
+
},
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
getToolsDescription() {
|
|
224
|
+
const tools = this.getToolsList();
|
|
225
|
+
if (tools.length === 0) return '';
|
|
223
226
|
|
|
224
|
-
return
|
|
227
|
+
return '\n\nAvailable tools:\n' + tools.map(t =>
|
|
228
|
+
`- ${t.name}: ${t.description}\n Parameters: ${JSON.stringify(t.inputSchema)}`
|
|
229
|
+
).join('\n');
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
async sendPrompt(content) {
|
|
233
|
+
if (!this.sessionId) {
|
|
234
|
+
throw new Error('No session. Call start() first.');
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
const reqId = this.generateRequestId();
|
|
238
|
+
const promptWithTools = this.instruction
|
|
239
|
+
? `${this.instruction}${this.getToolsDescription()}\n\nUser message: ${content}`
|
|
240
|
+
: content;
|
|
241
|
+
|
|
242
|
+
return new Promise((resolve) => {
|
|
243
|
+
this.pendingRequests.set(reqId, resolve);
|
|
244
|
+
this.send({
|
|
245
|
+
jsonrpc: "2.0",
|
|
246
|
+
id: reqId,
|
|
247
|
+
method: "session/prompt",
|
|
248
|
+
params: {
|
|
249
|
+
sessionId: this.sessionId,
|
|
250
|
+
prompt: [{ type: "text", text: promptWithTools }],
|
|
251
|
+
},
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
setTimeout(() => {
|
|
255
|
+
if (this.pendingRequests.has(reqId)) {
|
|
256
|
+
this.pendingRequests.delete(reqId);
|
|
257
|
+
resolve({ timeout: true });
|
|
258
|
+
}
|
|
259
|
+
}, 120000);
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
async process(text, options = {}) {
|
|
264
|
+
const cli = options.cli || 'kilo';
|
|
265
|
+
|
|
266
|
+
if (!this.cliProcess) {
|
|
267
|
+
await this.start(cli);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
const result = await this.sendPrompt(text);
|
|
271
|
+
|
|
272
|
+
return {
|
|
273
|
+
text,
|
|
274
|
+
result,
|
|
275
|
+
toolCalls: this.toolCallLog.slice(-10),
|
|
276
|
+
logs: this.toolCallLog,
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
stop() {
|
|
281
|
+
if (this.cliProcess) {
|
|
282
|
+
this.cliProcess.kill();
|
|
283
|
+
this.cliProcess = null;
|
|
284
|
+
}
|
|
285
|
+
this.initialized = false;
|
|
286
|
+
this.sessionId = null;
|
|
225
287
|
}
|
|
226
288
|
}
|
|
227
289
|
|