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.
Files changed (2) hide show
  1. package/core.js +118 -180
  2. 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 respond:\n\n';
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 like:\n`;
59
- prompt += `{"jsonrpc":"2.0","id":<unique_id>,"method":"tools/${tool.name}","params":{<parameters>}}\n\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`;
60
47
  }
61
- prompt += 'IMPORTANT: Always respond by calling a tool with a JSON-RPC request. Do not just output text.\n';
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
- async start(cli = 'kilo') {
104
- if (this.cliProcess) return this.sessionId;
105
-
106
- return new Promise((resolve, reject) => {
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
- this.handleMessage(trimmed);
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
- handleMessage(line) {
159
- let msg;
160
- try {
161
- msg = JSON.parse(line);
162
- } catch {
163
- return;
164
- }
165
-
166
- if (msg.method === 'initialize') {
167
- this.send(this.createInitializeResponse(msg.id));
168
- this.initialized = true;
169
- } else if (msg.id !== undefined && msg.result !== undefined) {
170
- const resolver = this.pendingRequests.get(msg.id);
171
- if (resolver) {
172
- this.pendingRequests.delete(msg.id);
173
- resolver(msg.result);
174
- }
175
- if (msg.id === 1 && msg.result?.sessionId) {
176
- this.sessionId = msg.result.sessionId;
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
- async handleToolCall(id, toolName, params) {
194
- try {
195
- const result = await this.callTool(toolName, params);
196
- this.send(this.createJsonRpcResponse(id, result));
197
- } catch (e) {
198
- this.send(this.createJsonRpcError(id, e.message));
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
- send(msg) {
203
- if (this.cliProcess && this.cliProcess.stdin.writable) {
204
- this.cliProcess.stdin.write(JSON.stringify(msg) + '\n');
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
- createInitializeResponse(id) {
209
- return {
210
- jsonrpc: "2.0",
211
- id,
212
- result: {
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
- createSession() {
222
- this.send({
223
- jsonrpc: "2.0",
224
- id: 1,
225
- method: "session/new",
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
- mcpServers: [],
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
- const reqId = this.generateRequestId();
239
- const fullPrompt = this.instruction
240
- ? `${this.instruction}${this.getToolsPrompt()}\n\n---\n\n${content}`
241
- : `${this.getToolsPrompt()}\n\n---\n\n${content}`;
173
+ child.stdout.on('data', (data) => {
174
+ output += data.toString();
175
+ });
242
176
 
243
- return new Promise((resolve) => {
244
- this.pendingRequests.set(reqId, resolve);
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
- setTimeout(() => {
256
- if (this.pendingRequests.has(reqId)) {
257
- this.pendingRequests.delete(reqId);
258
- resolve({ timeout: true });
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
- if (!this.cliProcess) {
268
- await this.start(cli);
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
- const result = await this.sendPrompt(text);
215
+ child.on('error', (error) => {
216
+ reject(new Error(`Failed to spawn ${cli}: ${error.message}`));
217
+ });
272
218
 
273
- return {
274
- text,
275
- result,
276
- toolCalls: this.toolCallLog.slice(-10),
277
- logs: this.toolCallLog,
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 };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "acpreact",
3
- "version": "1.0.6",
3
+ "version": "1.0.7",
4
4
  "type": "module",
5
5
  "main": "index.js",
6
6
  "description": "ACP SDK for monitoring and reacting to chat conversations",