acpreact 1.0.5 → 1.0.7
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 +128 -189
- package/package.json +1 -1
package/core.js
CHANGED
|
@@ -12,29 +12,12 @@ class ACPProtocol extends EventEmitter {
|
|
|
12
12
|
this.toolCallLog = [];
|
|
13
13
|
this.rejectedCallLog = [];
|
|
14
14
|
this.tools = {};
|
|
15
|
-
this.cliProcess = null;
|
|
16
|
-
this.pendingRequests = new Map();
|
|
17
|
-
this.buffer = '';
|
|
18
|
-
this.initialized = false;
|
|
19
|
-
this.sessionId = null;
|
|
20
15
|
}
|
|
21
16
|
|
|
22
17
|
generateRequestId() {
|
|
23
18
|
return ++this.messageId;
|
|
24
19
|
}
|
|
25
20
|
|
|
26
|
-
createJsonRpcRequest(method, params) {
|
|
27
|
-
return { jsonrpc: "2.0", id: this.generateRequestId(), method, params };
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
createJsonRpcResponse(id, result) {
|
|
31
|
-
return { jsonrpc: "2.0", id, result };
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
createJsonRpcError(id, error) {
|
|
35
|
-
return { jsonrpc: "2.0", id, error: { code: -32603, message: error } };
|
|
36
|
-
}
|
|
37
|
-
|
|
38
21
|
registerTool(name, description, inputSchema, handler) {
|
|
39
22
|
this.toolWhitelist.add(name);
|
|
40
23
|
this.tools[name] = handler;
|
|
@@ -51,6 +34,21 @@ class ACPProtocol extends EventEmitter {
|
|
|
51
34
|
}));
|
|
52
35
|
}
|
|
53
36
|
|
|
37
|
+
getToolsPrompt() {
|
|
38
|
+
const tools = this.getToolsList();
|
|
39
|
+
if (tools.length === 0) return '';
|
|
40
|
+
|
|
41
|
+
let prompt = '\n\nYou have access to the following tools. You MUST use these tools to interact:\n\n';
|
|
42
|
+
for (const tool of tools) {
|
|
43
|
+
prompt += `## Tool: ${tool.name}\n${tool.description}\n`;
|
|
44
|
+
prompt += `Parameters: ${JSON.stringify(tool.inputSchema, null, 2)}\n`;
|
|
45
|
+
prompt += `To call this tool, output a JSON-RPC request on a single line:\n`;
|
|
46
|
+
prompt += `{"jsonrpc":"2.0","id":<number>,"method":"tools/${tool.name}","params":{<parameters>}}\n\n`;
|
|
47
|
+
}
|
|
48
|
+
prompt += 'IMPORTANT: When you need to use a tool, output ONLY the JSON-RPC request, nothing else.\n';
|
|
49
|
+
return prompt;
|
|
50
|
+
}
|
|
51
|
+
|
|
54
52
|
validateToolCall(toolName) {
|
|
55
53
|
if (!this.toolWhitelist.has(toolName)) {
|
|
56
54
|
const availableTools = Array.from(this.toolWhitelist);
|
|
@@ -89,202 +87,143 @@ class ACPProtocol extends EventEmitter {
|
|
|
89
87
|
throw new Error(`Unknown tool: ${toolName}`);
|
|
90
88
|
}
|
|
91
89
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
this.cliProcess = spawn('script', ['-q', '-c', `${cli} acp`, '/dev/null'], {
|
|
97
|
-
stdio: ['pipe', 'pipe', 'pipe'],
|
|
98
|
-
cwd: process.cwd(),
|
|
99
|
-
env: { ...process.env, TERM: 'dumb' },
|
|
100
|
-
});
|
|
101
|
-
|
|
102
|
-
this.cliProcess.stdout.on('data', (data) => {
|
|
103
|
-
this.buffer += data.toString();
|
|
104
|
-
this.processBuffer();
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
this.cliProcess.stderr.on('data', (data) => {
|
|
108
|
-
this.emit('stderr', data.toString());
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
this.cliProcess.on('error', (error) => {
|
|
112
|
-
this.emit('error', error);
|
|
113
|
-
reject(error);
|
|
114
|
-
});
|
|
115
|
-
|
|
116
|
-
this.cliProcess.on('close', (code) => {
|
|
117
|
-
this.emit('close', code);
|
|
118
|
-
this.cliProcess = null;
|
|
119
|
-
this.initialized = false;
|
|
120
|
-
this.sessionId = null;
|
|
121
|
-
});
|
|
122
|
-
|
|
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);
|
|
130
|
-
});
|
|
131
|
-
|
|
132
|
-
setTimeout(() => this.createSession(), 500);
|
|
133
|
-
});
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
processBuffer() {
|
|
137
|
-
const lines = this.buffer.split('\n');
|
|
138
|
-
this.buffer = lines.pop() || '';
|
|
139
|
-
|
|
90
|
+
parseTextOutput(output) {
|
|
91
|
+
let text = '';
|
|
92
|
+
const lines = output.split('\n');
|
|
93
|
+
|
|
140
94
|
for (const line of lines) {
|
|
141
95
|
const trimmed = line.trim();
|
|
142
96
|
if (!trimmed) continue;
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
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 });
|
|
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);
|
|
97
|
+
|
|
98
|
+
try {
|
|
99
|
+
const json = JSON.parse(trimmed);
|
|
100
|
+
if (json.type === 'text' && json.part?.text) {
|
|
101
|
+
text += json.part.text;
|
|
102
|
+
}
|
|
103
|
+
} catch {}
|
|
179
104
|
}
|
|
105
|
+
|
|
106
|
+
return text;
|
|
180
107
|
}
|
|
181
108
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
109
|
+
parseToolCalls(output) {
|
|
110
|
+
const calls = [];
|
|
111
|
+
|
|
112
|
+
const textContent = this.parseTextOutput(output);
|
|
113
|
+
|
|
114
|
+
for (const line of textContent.split('\n')) {
|
|
115
|
+
const trimmed = line.trim();
|
|
116
|
+
if (!trimmed) continue;
|
|
117
|
+
|
|
118
|
+
try {
|
|
119
|
+
const json = JSON.parse(trimmed);
|
|
120
|
+
if (json.jsonrpc === '2.0' && json.method?.startsWith('tools/') && json.params) {
|
|
121
|
+
calls.push({
|
|
122
|
+
id: json.id,
|
|
123
|
+
method: json.method,
|
|
124
|
+
params: json.params
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
} catch {}
|
|
188
128
|
}
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
129
|
+
|
|
130
|
+
const lines = output.split('\n');
|
|
131
|
+
for (const line of lines) {
|
|
132
|
+
const trimmed = line.trim();
|
|
133
|
+
if (!trimmed) continue;
|
|
134
|
+
|
|
135
|
+
try {
|
|
136
|
+
const json = JSON.parse(trimmed);
|
|
137
|
+
if (json.jsonrpc === '2.0' && json.method?.startsWith('tools/') && json.params) {
|
|
138
|
+
calls.push({
|
|
139
|
+
id: json.id,
|
|
140
|
+
method: json.method,
|
|
141
|
+
params: json.params
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
} catch {}
|
|
194
145
|
}
|
|
146
|
+
|
|
147
|
+
return calls;
|
|
195
148
|
}
|
|
196
149
|
|
|
197
|
-
|
|
198
|
-
const
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
if (this.instruction) {
|
|
205
|
-
result.instruction = this.instruction;
|
|
206
|
-
}
|
|
150
|
+
async process(text, options = {}) {
|
|
151
|
+
const cli = options.cli || 'kilo';
|
|
152
|
+
const model = options.model || 'kilo/z-ai/glm-5:free';
|
|
153
|
+
|
|
154
|
+
const fullPrompt = this.instruction
|
|
155
|
+
? `${this.instruction}${this.getToolsPrompt()}\n\n---\n\n${text}`
|
|
156
|
+
: `${this.getToolsPrompt()}\n\n---\n\n${text}`;
|
|
207
157
|
|
|
208
|
-
|
|
209
|
-
|
|
158
|
+
const escapedPrompt = fullPrompt.replace(/"/g, '\\"').replace(/\n/g, ' ');
|
|
159
|
+
|
|
160
|
+
return new Promise((resolve, reject) => {
|
|
161
|
+
let output = '';
|
|
162
|
+
let errorOutput = '';
|
|
210
163
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
params: {
|
|
164
|
+
const child = spawn('script', ['-q', '-c',
|
|
165
|
+
`${cli} run --auto --model ${model} --format json "${escapedPrompt}"`,
|
|
166
|
+
'/dev/null'
|
|
167
|
+
], {
|
|
168
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
217
169
|
cwd: process.cwd(),
|
|
218
|
-
|
|
219
|
-
}
|
|
220
|
-
});
|
|
221
|
-
}
|
|
170
|
+
env: { ...process.env, TERM: 'dumb' },
|
|
171
|
+
});
|
|
222
172
|
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
return '\n\nAvailable tools:\n' + tools.map(t =>
|
|
228
|
-
`- ${t.name}: ${t.description}\n Parameters: ${JSON.stringify(t.inputSchema)}`
|
|
229
|
-
).join('\n');
|
|
230
|
-
}
|
|
173
|
+
child.stdout.on('data', (data) => {
|
|
174
|
+
output += data.toString();
|
|
175
|
+
});
|
|
231
176
|
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
177
|
+
child.stderr.on('data', (data) => {
|
|
178
|
+
errorOutput += data.toString();
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
child.on('close', async (code) => {
|
|
182
|
+
if (code !== 0 && code !== null && !output) {
|
|
183
|
+
reject(new Error(`CLI exited with code ${code}: ${errorOutput}`));
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
236
186
|
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
187
|
+
try {
|
|
188
|
+
const toolCalls = this.parseToolCalls(output);
|
|
189
|
+
const results = [];
|
|
190
|
+
|
|
191
|
+
for (const call of toolCalls) {
|
|
192
|
+
const toolName = call.method.replace('tools/', '');
|
|
193
|
+
if (this.toolWhitelist.has(toolName)) {
|
|
194
|
+
const result = await this.callTool(toolName, call.params);
|
|
195
|
+
results.push({ tool: toolName, result });
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
resolve({
|
|
200
|
+
text: this.parseTextOutput(output),
|
|
201
|
+
rawOutput: output,
|
|
202
|
+
toolCalls: results,
|
|
203
|
+
logs: this.toolCallLog
|
|
204
|
+
});
|
|
205
|
+
} catch (e) {
|
|
206
|
+
resolve({
|
|
207
|
+
text: this.parseTextOutput(output),
|
|
208
|
+
rawOutput: output,
|
|
209
|
+
error: e.message,
|
|
210
|
+
logs: this.toolCallLog
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
});
|
|
241
214
|
|
|
242
|
-
|
|
243
|
-
|
|
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
|
-
},
|
|
215
|
+
child.on('error', (error) => {
|
|
216
|
+
reject(new Error(`Failed to spawn ${cli}: ${error.message}`));
|
|
252
217
|
});
|
|
253
218
|
|
|
254
219
|
setTimeout(() => {
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
resolve({ timeout: true });
|
|
258
|
-
}
|
|
220
|
+
child.kill();
|
|
221
|
+
reject(new Error('Timeout'));
|
|
259
222
|
}, 120000);
|
|
260
223
|
});
|
|
261
224
|
}
|
|
262
225
|
|
|
263
|
-
|
|
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;
|
|
287
|
-
}
|
|
226
|
+
stop() {}
|
|
288
227
|
}
|
|
289
228
|
|
|
290
229
|
export { ACPProtocol };
|