acpreact 1.0.6 → 1.0.8

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 (4) hide show
  1. package/README.md +78 -66
  2. package/core.js +97 -211
  3. package/package.json +1 -1
  4. package/parser.js +58 -0
package/README.md CHANGED
@@ -1,15 +1,25 @@
1
1
  # acpreact - ACP SDK
2
2
 
3
- A lightweight SDK for setting up tools and managing ACP protocol communication. Allows opencode and kilo CLI to call registered tools via the ACP protocol.
3
+ A lightweight SDK for registering tools and running them via kilo CLI or opencode. Allows kilo and opencode to call registered tools via a custom JSON-RPC 2.0 protocol injected into the prompt.
4
4
 
5
5
  ## Features
6
6
 
7
7
  - **ACPProtocol**: Core ACP protocol implementation with JSON-RPC 2.0 support
8
8
  - **Tool Registration**: Register custom tools with descriptions and input schemas
9
9
  - **Tool Whitelist**: Built-in security model for controlling tool access
10
- - **Tool Execution**: Execute whitelisted tools with validation
10
+ - **Tool Execution**: Execute whitelisted tools with validation and logging
11
+ - **CLI Integration**: Works with kilo CLI and opencode via `process()` method
11
12
  - **ES Module**: Pure ES modules, no build step required
12
13
 
14
+ ## Prerequisites
15
+
16
+ Install kilo CLI and/or opencode before using `process()`:
17
+
18
+ ```bash
19
+ npm install -g @kilocode/cli # for kilo
20
+ npm install -g opencode-ai # for opencode
21
+ ```
22
+
13
23
  ## Installation
14
24
 
15
25
  ```bash
@@ -18,52 +28,46 @@ npm install acpreact
18
28
 
19
29
  ## Quick Start
20
30
 
21
- ### Creating an ACP Server with Custom Tools
31
+ ### Register Tools and Process with kilo CLI
22
32
 
23
33
  ```javascript
24
34
  import { ACPProtocol } from 'acpreact';
25
35
 
26
- const acp = new ACPProtocol();
36
+ const acp = new ACPProtocol('You are a calculator assistant. Use the add tool when asked to add numbers.');
27
37
 
28
- // Register a custom tool
29
38
  acp.registerTool(
30
- 'weather',
31
- 'Get weather information for a location',
39
+ 'add',
40
+ 'Add two numbers together',
32
41
  {
33
42
  type: 'object',
34
43
  properties: {
35
- location: { type: 'string', description: 'City name' }
44
+ a: { type: 'number', description: 'First number' },
45
+ b: { type: 'number', description: 'Second number' }
36
46
  },
37
- required: ['location']
47
+ required: ['a', 'b']
38
48
  },
39
- async (params) => {
40
- return {
41
- location: params.location,
42
- temperature: 72,
43
- condition: 'sunny'
44
- };
45
- }
49
+ async (params) => ({ sum: params.a + params.b })
46
50
  );
47
51
 
48
- // Initialize protocol response
49
- const response = acp.createInitializeResponse();
52
+ const result = await acp.process('What is 15 + 27? Use the add tool.', { cli: 'kilo' });
53
+ console.log(result.text); // human-readable text response
54
+ console.log(result.toolCalls); // [{ tool: 'add', result: { sum: 42 } }]
55
+ console.log(result.logs); // tool call audit log
56
+ ```
57
+
58
+ ### Using opencode
50
59
 
51
- // Execute tool
52
- const result = await acp.callTool('weather', { location: 'San Francisco' });
53
- console.log(result);
60
+ ```javascript
61
+ const result = await acp.process('What is 15 + 27? Use the add tool.', { cli: 'opencode' });
54
62
  ```
55
63
 
56
64
  ### Using System Instructions
57
65
 
58
- Pass a system instruction to the ACPProtocol constructor. The instruction will be included in the initialization response and communicated to opencode or kilo CLI:
59
-
60
66
  ```javascript
61
67
  import { ACPProtocol } from 'acpreact';
62
68
 
63
- const instruction = 'You are a helpful weather assistant. Always provide temperature in Fahrenheit.';
64
- const acp = new ACPProtocol(instruction);
69
+ const acp = new ACPProtocol('You are a helpful weather assistant. Always provide temperature in Fahrenheit.');
65
70
 
66
- // Register tools as usual
67
71
  acp.registerTool(
68
72
  'weather',
69
73
  'Get weather information for a location',
@@ -74,85 +78,96 @@ acp.registerTool(
74
78
  },
75
79
  required: ['location']
76
80
  },
77
- async (params) => {
78
- return {
79
- location: params.location,
80
- temperature: 72,
81
- condition: 'sunny'
82
- };
83
- }
81
+ async (params) => ({
82
+ location: params.location,
83
+ temperature: 72,
84
+ condition: 'sunny'
85
+ })
84
86
  );
85
87
 
86
- // The instruction is included in the initialization response
87
- const response = acp.createInitializeResponse();
88
- console.log(response.result.instruction);
89
- // Output: "You are a helpful weather assistant. Always provide temperature in Fahrenheit."
88
+ const result = await acp.process('What is the weather in San Francisco?', { cli: 'kilo' });
89
+ console.log(result.text); // text response (tool call JSON filtered out)
90
+ console.log(result.toolCalls); // [{ tool: 'weather', result: { location: 'San Francisco', ... } }]
90
91
  ```
91
92
 
92
93
  ## API
93
94
 
94
95
  ### ACPProtocol
95
96
 
96
- Main class for setting up ACP protocol communication.
97
+ Main class for ACP protocol communication.
97
98
 
98
99
  **Constructor:**
99
100
 
100
- - `new ACPProtocol(instruction)`: Initialize the protocol
101
- - `instruction` (optional): String - system instruction to communicate to opencode or kilo CLI
101
+ - `new ACPProtocol(instruction?)`: Initialize the protocol
102
+ - `instruction` (optional): String - system instruction prepended to every prompt sent to the CLI
102
103
 
103
104
  **Methods:**
104
105
 
105
106
  - `registerTool(name, description, inputSchema, handler)`: Register a custom tool
106
107
  - `name`: String - tool identifier
107
- - `description`: String - tool description
108
+ - `description`: String - tool description shown to the model
108
109
  - `inputSchema`: Object - JSON Schema for tool inputs
109
110
  - `handler`: Async function - receives params object, returns result
110
111
  - Returns: Tool definition object
111
112
 
112
- - `createInitializeResponse()`: Generate protocol initialization response
113
+ - `async process(text, options?)`: Send a prompt to kilo or opencode and execute any tool calls
114
+ - `text`: String - the user prompt
115
+ - `options.cli`: `'kilo'` (default) or `'opencode'`
116
+ - `options.model`: String - model in `provider/model` format (uses CLI default if omitted)
117
+ - Returns: `{ text, rawOutput, toolCalls, logs }` or `{ text, rawOutput, error, logs }` on parse failure
118
+
119
+ - `createInitializeResponse()`: Generate ACP protocol initialization response with registered tools
120
+
121
+ - `createJsonRpcRequest(method, params)`: Create JSON-RPC 2.0 request object
113
122
 
114
- - `createJsonRpcRequest(method, params)`: Create JSON-RPC request object
123
+ - `createJsonRpcResponse(id, result)`: Create JSON-RPC 2.0 response object
115
124
 
116
- - `createJsonRpcResponse(id, result)`: Create JSON-RPC response object
125
+ - `createJsonRpcError(id, error)`: Create JSON-RPC 2.0 error object (accepts Error or string)
117
126
 
118
- - `createJsonRpcError(id, error)`: Create JSON-RPC error object
127
+ - `validateToolCall(toolName)`: Check if tool is whitelisted, returns `{ allowed, error? }`
119
128
 
120
- - `validateToolCall(toolName)`: Check if tool is whitelisted
129
+ - `async callTool(toolName, params)`: Execute a registered tool directly
121
130
 
122
- - `async callTool(toolName, params)`: Execute a registered tool
131
+ - `parseTextOutput(output)`: Parse human-readable text from CLI JSON output (filters tool call JSON)
132
+
133
+ - `parseToolCalls(output)`: Parse JSON-RPC tool calls from CLI output, deduplicated by id+method
123
134
 
124
135
  **Properties:**
125
136
 
126
- - `instruction`: String (optional) - system instruction communicated to opencode or kilo CLI
137
+ - `instruction`: String (optional) - system instruction prepended to prompts
127
138
  - `toolWhitelist`: Set of registered tool names
128
- - `toolCallLog`: Array of executed tool calls with timestamps
129
- - `rejectedCallLog`: Array of rejected tool attempts
139
+ - `toolCallLog`: Array of executed tool calls with timestamps and results
140
+ - `rejectedCallLog`: Array of rejected tool attempts with reasons
141
+
142
+ ## How It Works
143
+
144
+ `process()` injects the registered tool list and JSON-RPC call format into the prompt, invokes the CLI, and parses any JSON-RPC tool calls from the output. Matched tool calls are executed locally and their results returned.
145
+
146
+ The model outputs tool calls as JSON-RPC lines:
147
+ ```
148
+ {"jsonrpc":"2.0","id":1,"method":"tools/add","params":{"a":15,"b":27}}
149
+ ```
150
+
151
+ These are parsed, executed, and returned in `result.toolCalls`. The `text` field contains only human-readable model output with tool call JSON filtered out.
130
152
 
131
153
  ## Example: Multiple Tools
132
154
 
133
155
  ```javascript
134
156
  import { ACPProtocol } from 'acpreact';
135
157
 
136
- const acp = new ACPProtocol();
158
+ const acp = new ACPProtocol('You are a data assistant.');
137
159
 
138
- // Register database tool
139
160
  acp.registerTool(
140
161
  'query_database',
141
162
  'Query the application database',
142
163
  {
143
164
  type: 'object',
144
- properties: {
145
- query: { type: 'string' }
146
- },
165
+ properties: { query: { type: 'string' } },
147
166
  required: ['query']
148
167
  },
149
- async (params) => {
150
- // Your database logic here
151
- return { data: [] };
152
- }
168
+ async (params) => ({ data: [] })
153
169
  );
154
170
 
155
- // Register API tool
156
171
  acp.registerTool(
157
172
  'call_api',
158
173
  'Call an external API',
@@ -164,15 +179,12 @@ acp.registerTool(
164
179
  },
165
180
  required: ['endpoint', 'method']
166
181
  },
167
- async (params) => {
168
- // Your API logic here
169
- return { response: {} };
170
- }
182
+ async (params) => ({ response: {} })
171
183
  );
172
184
 
173
- // Initialize and use
174
185
  const initResponse = acp.createInitializeResponse();
175
- console.log(initResponse.result.agentCapabilities);
186
+ console.log(initResponse.result.tools.length); // 2
187
+ console.log(initResponse.result.agentCapabilities); // { toolCalling: true, streaming: false }
176
188
  ```
177
189
 
178
190
  ## License
package/core.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import { spawn } from 'child_process';
2
2
  import { EventEmitter } from 'events';
3
+ import { parseTextOutput, parseToolCalls } from './parser.js';
3
4
 
4
5
  class ACPProtocol extends EventEmitter {
5
6
  constructor(instruction) {
@@ -12,25 +13,12 @@ class ACPProtocol extends EventEmitter {
12
13
  this.toolCallLog = [];
13
14
  this.rejectedCallLog = [];
14
15
  this.tools = {};
15
- this.cliProcess = null;
16
- this.pendingRequests = new Map();
17
- this.buffer = '';
18
- this.initialized = false;
19
- this.sessionId = null;
20
16
  }
21
17
 
22
18
  generateRequestId() {
23
19
  return ++this.messageId;
24
20
  }
25
21
 
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
22
  registerTool(name, description, inputSchema, handler) {
35
23
  this.toolWhitelist.add(name);
36
24
  this.tools[name] = handler;
@@ -40,252 +28,150 @@ class ACPProtocol extends EventEmitter {
40
28
  }
41
29
 
42
30
  getToolsList() {
43
- return Array.from(this.toolWhitelist).map(toolName => ({
44
- name: toolName,
45
- description: this.toolDescriptions[toolName],
46
- inputSchema: this.toolSchemas[toolName],
31
+ return Array.from(this.toolWhitelist).map(name => ({
32
+ name,
33
+ description: this.toolDescriptions[name],
34
+ inputSchema: this.toolSchemas[name],
47
35
  }));
48
36
  }
49
37
 
50
38
  getToolsPrompt() {
51
39
  const tools = this.getToolsList();
52
40
  if (tools.length === 0) return '';
53
-
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
 
52
+ createInitializeResponse() {
53
+ return {
54
+ jsonrpc: '2.0',
55
+ id: 1,
56
+ result: {
57
+ tools: this.getToolsList(),
58
+ instruction: this.instruction,
59
+ agentCapabilities: { toolCalling: true, streaming: false },
60
+ },
61
+ };
62
+ }
63
+
64
+ createJsonRpcRequest(method, params) {
65
+ return { jsonrpc: '2.0', id: this.generateRequestId(), method, params };
66
+ }
67
+
68
+ createJsonRpcResponse(id, result) {
69
+ return { jsonrpc: '2.0', id, result };
70
+ }
71
+
72
+ createJsonRpcError(id, error) {
73
+ return {
74
+ jsonrpc: '2.0',
75
+ id,
76
+ error: {
77
+ code: error?.code ?? -32000,
78
+ message: error instanceof Error ? error.message : String(error),
79
+ },
80
+ };
81
+ }
82
+
65
83
  validateToolCall(toolName) {
66
84
  if (!this.toolWhitelist.has(toolName)) {
67
85
  const availableTools = Array.from(this.toolWhitelist);
68
- const error = `Tool not available. Available: ${availableTools.join(', ')}`;
69
86
  this.rejectedCallLog.push({
70
87
  timestamp: new Date().toISOString(),
71
88
  attemptedTool: toolName,
72
89
  reason: 'Not in whitelist',
73
90
  availableTools,
74
91
  });
75
- return { allowed: false, error };
92
+ return { allowed: false, error: `Tool not available. Available: ${availableTools.join(', ')}` };
76
93
  }
77
94
  return { allowed: true };
78
95
  }
79
96
 
80
97
  async callTool(toolName, params) {
81
98
  const validation = this.validateToolCall(toolName);
82
- if (!validation.allowed) {
83
- throw new Error(validation.error);
84
- }
85
-
86
- this.toolCallLog.push({
87
- timestamp: new Date().toISOString(),
88
- toolName,
89
- params,
90
- status: 'executing',
91
- });
92
-
93
- if (this.tools[toolName]) {
94
- const result = await this.tools[toolName](params);
95
- const lastLog = this.toolCallLog[this.toolCallLog.length - 1];
96
- lastLog.status = 'completed';
97
- lastLog.result = result;
98
- return result;
99
- }
100
- throw new Error(`Unknown tool: ${toolName}`);
101
- }
102
-
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);
99
+ if (!validation.allowed) throw new Error(validation.error);
137
100
 
138
- this.once('ready', () => {
139
- clearTimeout(timeout);
140
- resolve(this.sessionId);
141
- });
101
+ const entry = { timestamp: new Date().toISOString(), toolName, params, status: 'executing' };
102
+ this.toolCallLog.push(entry);
142
103
 
143
- setTimeout(() => this.createSession(), 500);
144
- });
104
+ if (!this.tools[toolName]) throw new Error(`Unknown tool: ${toolName}`);
105
+ const result = await this.tools[toolName](params);
106
+ entry.status = 'completed';
107
+ entry.result = result;
108
+ return result;
145
109
  }
146
110
 
147
- processBuffer() {
148
- const lines = this.buffer.split('\n');
149
- this.buffer = lines.pop() || '';
150
-
151
- for (const line of lines) {
152
- const trimmed = line.trim();
153
- if (!trimmed) continue;
154
- this.handleMessage(trimmed);
155
- }
111
+ parseTextOutput(output) {
112
+ return parseTextOutput(output);
156
113
  }
157
114
 
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);
190
- }
115
+ parseToolCalls(output) {
116
+ return parseToolCalls(output);
191
117
  }
192
118
 
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));
199
- }
200
- }
119
+ async process(text, options = {}) {
120
+ const cli = options.cli || 'kilo';
121
+ const model = options.model;
122
+ const fullPrompt = this.instruction
123
+ ? `${this.instruction}${this.getToolsPrompt()}\n\n---\n\n${text}`
124
+ : `${this.getToolsPrompt()}\n\n---\n\n${text}`;
201
125
 
202
- send(msg) {
203
- if (this.cliProcess && this.cliProcess.stdin.writable) {
204
- this.cliProcess.stdin.write(JSON.stringify(msg) + '\n');
205
- }
206
- }
126
+ const args = ['run', '--format', 'json'];
127
+ if (cli === 'kilo') args.push('--auto');
128
+ if (model) args.push('--model', model);
129
+ args.push(fullPrompt);
207
130
 
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
- }
131
+ return new Promise((resolve, reject) => {
132
+ let output = '';
133
+ let errorOutput = '';
220
134
 
221
- createSession() {
222
- this.send({
223
- jsonrpc: "2.0",
224
- id: 1,
225
- method: "session/new",
226
- params: {
135
+ const child = spawn(cli, args, {
136
+ stdio: ['pipe', 'pipe', 'pipe'],
227
137
  cwd: process.cwd(),
228
- mcpServers: [],
229
- },
230
- });
231
- }
138
+ env: { ...process.env },
139
+ });
232
140
 
233
- async sendPrompt(content) {
234
- if (!this.sessionId) {
235
- throw new Error('No session. Call start() first.');
236
- }
141
+ child.stdin.end();
142
+ child.stdout.on('data', (data) => { output += data.toString(); });
143
+ child.stderr.on('data', (data) => { errorOutput += data.toString(); });
237
144
 
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}`;
145
+ child.on('close', async (code) => {
146
+ if (code !== 0 && code !== null && !output) {
147
+ reject(new Error(`CLI exited with code ${code}: ${errorOutput}`));
148
+ return;
149
+ }
150
+ try {
151
+ const toolCalls = parseToolCalls(output);
152
+ const results = [];
153
+ for (const call of toolCalls) {
154
+ const toolName = call.method.replace('tools/', '');
155
+ if (this.toolWhitelist.has(toolName)) {
156
+ const result = await this.callTool(toolName, call.params);
157
+ results.push({ tool: toolName, result });
158
+ }
159
+ }
160
+ resolve({ text: parseTextOutput(output), rawOutput: output, toolCalls: results, logs: this.toolCallLog });
161
+ } catch (e) {
162
+ resolve({ text: parseTextOutput(output), rawOutput: output, error: e.message, logs: this.toolCallLog });
163
+ }
164
+ });
242
165
 
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
- },
166
+ child.on('error', (error) => {
167
+ reject(new Error(`Failed to spawn ${cli}: ${error.message}`));
253
168
  });
254
169
 
255
- setTimeout(() => {
256
- if (this.pendingRequests.has(reqId)) {
257
- this.pendingRequests.delete(reqId);
258
- resolve({ timeout: true });
259
- }
260
- }, 120000);
170
+ setTimeout(() => { child.kill(); reject(new Error('Timeout')); }, 120000);
261
171
  });
262
172
  }
263
173
 
264
- async process(text, options = {}) {
265
- const cli = options.cli || 'kilo';
266
-
267
- if (!this.cliProcess) {
268
- await this.start(cli);
269
- }
270
-
271
- const result = await this.sendPrompt(text);
272
-
273
- return {
274
- text,
275
- result,
276
- toolCalls: this.toolCallLog.slice(-10),
277
- logs: this.toolCallLog,
278
- };
279
- }
280
-
281
- stop() {
282
- if (this.cliProcess) {
283
- this.cliProcess.kill();
284
- this.cliProcess = null;
285
- }
286
- this.initialized = false;
287
- this.sessionId = null;
288
- }
174
+ stop() {}
289
175
  }
290
176
 
291
177
  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.8",
4
4
  "type": "module",
5
5
  "main": "index.js",
6
6
  "description": "ACP SDK for monitoring and reacting to chat conversations",
package/parser.js ADDED
@@ -0,0 +1,58 @@
1
+ function parseTextOutput(output) {
2
+ let text = '';
3
+ for (const line of output.split('\n')) {
4
+ const trimmed = line.trim();
5
+ if (!trimmed) continue;
6
+ try {
7
+ const json = JSON.parse(trimmed);
8
+ if (json.type === 'text' && json.part?.text) {
9
+ const partText = json.part.text;
10
+ try {
11
+ const inner = JSON.parse(partText.trim());
12
+ if (inner.jsonrpc === '2.0' && inner.method?.startsWith('tools/')) continue;
13
+ } catch {}
14
+ text += partText;
15
+ }
16
+ } catch {}
17
+ }
18
+ return text;
19
+ }
20
+
21
+ function parseToolCalls(output) {
22
+ const seen = new Set();
23
+ const calls = [];
24
+
25
+ const tryAdd = (candidate) => {
26
+ const trimmed = candidate.trim();
27
+ if (!trimmed) return;
28
+ try {
29
+ const json = JSON.parse(trimmed);
30
+ if (json.jsonrpc === '2.0' && json.method?.startsWith('tools/') && json.params) {
31
+ const key = `${json.id}:${json.method}`;
32
+ if (!seen.has(key)) {
33
+ seen.add(key);
34
+ calls.push({ id: json.id, method: json.method, params: json.params });
35
+ }
36
+ }
37
+ } catch {}
38
+ };
39
+
40
+ for (const line of output.split('\n')) {
41
+ const trimmed = line.trim();
42
+ if (!trimmed) continue;
43
+ try {
44
+ const json = JSON.parse(trimmed);
45
+ if (json.type === 'text' && json.part?.text) {
46
+ for (const inner of json.part.text.split('\n')) tryAdd(inner);
47
+ } else {
48
+ tryAdd(trimmed);
49
+ }
50
+ } catch {
51
+ tryAdd(trimmed);
52
+ }
53
+ }
54
+
55
+ return calls;
56
+ }
57
+
58
+ export { parseTextOutput, parseToolCalls };