acpreact 1.0.6 → 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 +118 -180
- package/package.json +1 -1
package/core.js
CHANGED
|
@@ -12,25 +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
|
-
createJsonRpcResponse(id, result) {
|
|
27
|
-
return { jsonrpc: "2.0", id, result };
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
createJsonRpcError(id, error) {
|
|
31
|
-
return { jsonrpc: "2.0", id, error: { code: -32603, message: error } };
|
|
32
|
-
}
|
|
33
|
-
|
|
34
21
|
registerTool(name, description, inputSchema, handler) {
|
|
35
22
|
this.toolWhitelist.add(name);
|
|
36
23
|
this.tools[name] = handler;
|
|
@@ -51,14 +38,14 @@ class ACPProtocol extends EventEmitter {
|
|
|
51
38
|
const tools = this.getToolsList();
|
|
52
39
|
if (tools.length === 0) return '';
|
|
53
40
|
|
|
54
|
-
let prompt = '\n\nYou have access to the following tools. You MUST use these tools to
|
|
41
|
+
let prompt = '\n\nYou have access to the following tools. You MUST use these tools to interact:\n\n';
|
|
55
42
|
for (const tool of tools) {
|
|
56
43
|
prompt += `## Tool: ${tool.name}\n${tool.description}\n`;
|
|
57
44
|
prompt += `Parameters: ${JSON.stringify(tool.inputSchema, null, 2)}\n`;
|
|
58
|
-
prompt += `To call this tool, output a JSON-RPC request
|
|
59
|
-
prompt += `{"jsonrpc":"2.0","id":<
|
|
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`;
|
|
60
47
|
}
|
|
61
|
-
prompt += 'IMPORTANT:
|
|
48
|
+
prompt += 'IMPORTANT: When you need to use a tool, output ONLY the JSON-RPC request, nothing else.\n';
|
|
62
49
|
return prompt;
|
|
63
50
|
}
|
|
64
51
|
|
|
@@ -100,192 +87,143 @@ class ACPProtocol extends EventEmitter {
|
|
|
100
87
|
throw new Error(`Unknown tool: ${toolName}`);
|
|
101
88
|
}
|
|
102
89
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
this.cliProcess = spawn('script', ['-q', '-c', `${cli} acp`, '/dev/null'], {
|
|
108
|
-
stdio: ['pipe', 'pipe', 'pipe'],
|
|
109
|
-
cwd: process.cwd(),
|
|
110
|
-
env: { ...process.env, TERM: 'dumb' },
|
|
111
|
-
});
|
|
112
|
-
|
|
113
|
-
this.cliProcess.stdout.on('data', (data) => {
|
|
114
|
-
this.buffer += data.toString();
|
|
115
|
-
this.processBuffer();
|
|
116
|
-
});
|
|
117
|
-
|
|
118
|
-
this.cliProcess.stderr.on('data', (data) => {
|
|
119
|
-
this.emit('stderr', data.toString());
|
|
120
|
-
});
|
|
121
|
-
|
|
122
|
-
this.cliProcess.on('error', (error) => {
|
|
123
|
-
this.emit('error', error);
|
|
124
|
-
reject(error);
|
|
125
|
-
});
|
|
126
|
-
|
|
127
|
-
this.cliProcess.on('close', (code) => {
|
|
128
|
-
this.emit('close', code);
|
|
129
|
-
this.cliProcess = null;
|
|
130
|
-
this.initialized = false;
|
|
131
|
-
this.sessionId = null;
|
|
132
|
-
});
|
|
133
|
-
|
|
134
|
-
const timeout = setTimeout(() => {
|
|
135
|
-
reject(new Error('ACP initialization timeout'));
|
|
136
|
-
}, 30000);
|
|
137
|
-
|
|
138
|
-
this.once('ready', () => {
|
|
139
|
-
clearTimeout(timeout);
|
|
140
|
-
resolve(this.sessionId);
|
|
141
|
-
});
|
|
142
|
-
|
|
143
|
-
setTimeout(() => this.createSession(), 500);
|
|
144
|
-
});
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
processBuffer() {
|
|
148
|
-
const lines = this.buffer.split('\n');
|
|
149
|
-
this.buffer = lines.pop() || '';
|
|
150
|
-
|
|
90
|
+
parseTextOutput(output) {
|
|
91
|
+
let text = '';
|
|
92
|
+
const lines = output.split('\n');
|
|
93
|
+
|
|
151
94
|
for (const line of lines) {
|
|
152
95
|
const trimmed = line.trim();
|
|
153
96
|
if (!trimmed) continue;
|
|
154
|
-
|
|
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 {}
|
|
155
104
|
}
|
|
105
|
+
|
|
106
|
+
return text;
|
|
156
107
|
}
|
|
157
108
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
this.emit('ready');
|
|
178
|
-
}
|
|
179
|
-
} else if (msg.id !== undefined && msg.error !== undefined) {
|
|
180
|
-
const resolver = this.pendingRequests.get(msg.id);
|
|
181
|
-
if (resolver) {
|
|
182
|
-
this.pendingRequests.delete(msg.id);
|
|
183
|
-
resolver({ error: msg.error });
|
|
184
|
-
}
|
|
185
|
-
} else if (msg.method?.startsWith('tools/')) {
|
|
186
|
-
const toolName = msg.method.replace('tools/', '');
|
|
187
|
-
this.handleToolCall(msg.id, toolName, msg.params);
|
|
188
|
-
} else if (msg.method === 'session/update') {
|
|
189
|
-
this.emit('update', msg.params);
|
|
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 {}
|
|
190
128
|
}
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
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 {}
|
|
199
145
|
}
|
|
146
|
+
|
|
147
|
+
return calls;
|
|
200
148
|
}
|
|
201
149
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
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
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
protocolVersion: 1,
|
|
214
|
-
capabilities: { tools: this.getToolsList() },
|
|
215
|
-
serverInfo: { name: 'acpreact', version: '1.0.0' },
|
|
216
|
-
instruction: this.instruction,
|
|
217
|
-
}
|
|
218
|
-
};
|
|
219
|
-
}
|
|
158
|
+
const escapedPrompt = fullPrompt.replace(/"/g, '\\"').replace(/\n/g, ' ');
|
|
159
|
+
|
|
160
|
+
return new Promise((resolve, reject) => {
|
|
161
|
+
let output = '';
|
|
162
|
+
let errorOutput = '';
|
|
220
163
|
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
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'],
|
|
227
169
|
cwd: process.cwd(),
|
|
228
|
-
|
|
229
|
-
}
|
|
230
|
-
});
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
async sendPrompt(content) {
|
|
234
|
-
if (!this.sessionId) {
|
|
235
|
-
throw new Error('No session. Call start() first.');
|
|
236
|
-
}
|
|
170
|
+
env: { ...process.env, TERM: 'dumb' },
|
|
171
|
+
});
|
|
237
172
|
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
: `${this.getToolsPrompt()}\n\n---\n\n${content}`;
|
|
173
|
+
child.stdout.on('data', (data) => {
|
|
174
|
+
output += data.toString();
|
|
175
|
+
});
|
|
242
176
|
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
this.send({
|
|
246
|
-
jsonrpc: "2.0",
|
|
247
|
-
id: reqId,
|
|
248
|
-
method: "session/prompt",
|
|
249
|
-
params: {
|
|
250
|
-
sessionId: this.sessionId,
|
|
251
|
-
prompt: [{ type: "text", text: fullPrompt }],
|
|
252
|
-
},
|
|
177
|
+
child.stderr.on('data', (data) => {
|
|
178
|
+
errorOutput += data.toString();
|
|
253
179
|
});
|
|
254
180
|
|
|
255
|
-
|
|
256
|
-
if (
|
|
257
|
-
|
|
258
|
-
|
|
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;
|
|
259
185
|
}
|
|
260
|
-
}, 120000);
|
|
261
|
-
});
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
async process(text, options = {}) {
|
|
265
|
-
const cli = options.cli || 'kilo';
|
|
266
186
|
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
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
|
+
});
|
|
270
214
|
|
|
271
|
-
|
|
215
|
+
child.on('error', (error) => {
|
|
216
|
+
reject(new Error(`Failed to spawn ${cli}: ${error.message}`));
|
|
217
|
+
});
|
|
272
218
|
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
};
|
|
219
|
+
setTimeout(() => {
|
|
220
|
+
child.kill();
|
|
221
|
+
reject(new Error('Timeout'));
|
|
222
|
+
}, 120000);
|
|
223
|
+
});
|
|
279
224
|
}
|
|
280
225
|
|
|
281
|
-
stop() {
|
|
282
|
-
if (this.cliProcess) {
|
|
283
|
-
this.cliProcess.kill();
|
|
284
|
-
this.cliProcess = null;
|
|
285
|
-
}
|
|
286
|
-
this.initialized = false;
|
|
287
|
-
this.sessionId = null;
|
|
288
|
-
}
|
|
226
|
+
stop() {}
|
|
289
227
|
}
|
|
290
228
|
|
|
291
229
|
export { ACPProtocol };
|