acpreact 1.0.0 → 1.0.3

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.
@@ -21,6 +21,15 @@ jobs:
21
21
  - name: Install dependencies
22
22
  run: npm ci
23
23
 
24
+ - name: Auto-bump version
25
+ run: |
26
+ git config user.name "github-actions[bot]"
27
+ git config user.email "github-actions[bot]@users.noreply.github.com"
28
+ npm version patch --no-git-tag-version
29
+ git add package.json package-lock.json
30
+ git commit -m "Auto-bump version [skip ci]"
31
+ git push
32
+
24
33
  - name: Publish to npm
25
34
  run: npm publish
26
35
  env:
package/README.md CHANGED
@@ -1,13 +1,13 @@
1
1
  # acpreact - ACP SDK
2
2
 
3
- A clean, production-ready SDK for implementing ACP (AI Control Protocol) with function-based chat analysis.
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.
4
4
 
5
5
  ## Features
6
6
 
7
7
  - **ACPProtocol**: Core ACP protocol implementation with JSON-RPC 2.0 support
8
- - **processChat**: Analyze chat content with tool invocation and whitelist enforcement
9
- - **Tool Factory**: Create tool definitions for ACP integration
8
+ - **Tool Registration**: Register custom tools with descriptions and input schemas
10
9
  - **Tool Whitelist**: Built-in security model for controlling tool access
10
+ - **Tool Execution**: Execute whitelisted tools with validation
11
11
  - **ES Module**: Pure ES modules, no build step required
12
12
 
13
13
  ## Installation
@@ -18,86 +18,162 @@ npm install acpreact
18
18
 
19
19
  ## Quick Start
20
20
 
21
- ### Processing Chat Content
21
+ ### Creating an ACP Server with Custom Tools
22
22
 
23
23
  ```javascript
24
- import { processChat } from 'acpreact';
24
+ import { ACPProtocol } from 'acpreact';
25
+
26
+ const acp = new ACPProtocol();
25
27
 
26
- const chatContent = "[14:23] alice: Where is Taj Mahal?\n[14:24] bob: Main Street";
27
- const result = await processChat(chatContent, {
28
- onToolCall: (toolName, args) => console.log('Tool:', toolName, args)
29
- });
30
- console.log(result.answer);
28
+ // Register a custom tool
29
+ acp.registerTool(
30
+ 'weather',
31
+ 'Get weather information for a location',
32
+ {
33
+ type: 'object',
34
+ properties: {
35
+ location: { type: 'string', description: 'City name' }
36
+ },
37
+ required: ['location']
38
+ },
39
+ async (params) => {
40
+ return {
41
+ location: params.location,
42
+ temperature: 72,
43
+ condition: 'sunny'
44
+ };
45
+ }
46
+ );
47
+
48
+ // Initialize protocol response
49
+ const response = acp.createInitializeResponse();
50
+
51
+ // Execute tool
52
+ const result = await acp.callTool('weather', { location: 'San Francisco' });
53
+ console.log(result);
31
54
  ```
32
55
 
33
- ### Using ACPProtocol
56
+ ### Using System Instructions
57
+
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:
34
59
 
35
60
  ```javascript
36
61
  import { ACPProtocol } from 'acpreact';
37
62
 
38
- const acp = new ACPProtocol();
63
+ const instruction = 'You are a helpful weather assistant. Always provide temperature in Fahrenheit.';
64
+ const acp = new ACPProtocol(instruction);
65
+
66
+ // Register tools as usual
67
+ acp.registerTool(
68
+ 'weather',
69
+ 'Get weather information for a location',
70
+ {
71
+ type: 'object',
72
+ properties: {
73
+ location: { type: 'string', description: 'City name' }
74
+ },
75
+ required: ['location']
76
+ },
77
+ async (params) => {
78
+ return {
79
+ location: params.location,
80
+ temperature: 72,
81
+ condition: 'sunny'
82
+ };
83
+ }
84
+ );
85
+
86
+ // The instruction is included in the initialization response
39
87
  const response = acp.createInitializeResponse();
40
- const result = await acp.callTool('simulative_retriever', {
41
- query: 'Taj Mahal Main Street phone number'
42
- });
88
+ console.log(response.result.instruction);
89
+ // Output: "You are a helpful weather assistant. Always provide temperature in Fahrenheit."
43
90
  ```
44
91
 
45
- ### Creating Tool Definitions
92
+ ## API
46
93
 
47
- ```javascript
48
- import { createSimulativeRetriever } from 'acpreact';
94
+ ### ACPProtocol
49
95
 
50
- const tool = createSimulativeRetriever();
51
- // Use in your ACP setup
52
- ```
96
+ Main class for setting up ACP protocol communication.
53
97
 
54
- ## API
98
+ **Constructor:**
55
99
 
56
- ### processChat(chatContent, options)
100
+ - `new ACPProtocol(instruction)`: Initialize the protocol
101
+ - `instruction` (optional): String - system instruction to communicate to opencode or kilo CLI
57
102
 
58
- Process chat content with tool invocation and analysis.
103
+ **Methods:**
59
104
 
60
- **Parameters:**
61
- - `chatContent`: String containing chat messages
62
- - `options`: Optional configuration object
63
- - `onToolCall(toolName, args)`: Callback when a tool is invoked
64
- - `systemPrompt`: Custom system prompt for analysis
105
+ - `registerTool(name, description, inputSchema, handler)`: Register a custom tool
106
+ - `name`: String - tool identifier
107
+ - `description`: String - tool description
108
+ - `inputSchema`: Object - JSON Schema for tool inputs
109
+ - `handler`: Async function - receives params object, returns result
110
+ - Returns: Tool definition object
65
111
 
66
- **Returns:** Promise resolving to object with:
67
- - `answer`: Processed chat analysis
68
- - `toolCalls`: Array of tool invocations
69
- - `logs`: Tool execution log
70
- - `rejectedLogs`: Failed tool call attempts
112
+ - `createInitializeResponse()`: Generate protocol initialization response
71
113
 
72
- **Example:**
73
- ```javascript
74
- const result = await processChat(chatContent, {
75
- onToolCall: (name, args) => console.log(`Called: ${name}`)
76
- });
77
- ```
114
+ - `createJsonRpcRequest(method, params)`: Create JSON-RPC request object
78
115
 
79
- ### ACPProtocol
116
+ - `createJsonRpcResponse(id, result)`: Create JSON-RPC response object
80
117
 
81
- Main protocol handler for ACP communication.
118
+ - `createJsonRpcError(id, error)`: Create JSON-RPC error object
82
119
 
83
- **Methods:**
84
- - `createInitializeResponse()`: Generate protocol initialization response
85
- - `createJsonRpcRequest(method, params)`: Create JSON-RPC request
86
- - `createJsonRpcResponse(id, result)`: Create JSON-RPC response
87
- - `createJsonRpcError(id, error)`: Create JSON-RPC error
88
120
  - `validateToolCall(toolName)`: Check if tool is whitelisted
89
- - `callTool(toolName, params)`: Execute a whitelisted tool
121
+
122
+ - `async callTool(toolName, params)`: Execute a registered tool
90
123
 
91
124
  **Properties:**
92
- - `toolWhitelist`: Set of allowed tool names
93
- - `toolCallLog`: Array of executed tool calls
125
+
126
+ - `instruction`: String (optional) - system instruction communicated to opencode or kilo CLI
127
+ - `toolWhitelist`: Set of registered tool names
128
+ - `toolCallLog`: Array of executed tool calls with timestamps
94
129
  - `rejectedCallLog`: Array of rejected tool attempts
95
130
 
96
- ### createSimulativeRetriever()
131
+ ## Example: Multiple Tools
132
+
133
+ ```javascript
134
+ import { ACPProtocol } from 'acpreact';
97
135
 
98
- Factory function that returns a tool definition for simulative_retriever.
136
+ const acp = new ACPProtocol();
99
137
 
100
- **Returns:** Tool object with name, description, and inputSchema
138
+ // Register database tool
139
+ acp.registerTool(
140
+ 'query_database',
141
+ 'Query the application database',
142
+ {
143
+ type: 'object',
144
+ properties: {
145
+ query: { type: 'string' }
146
+ },
147
+ required: ['query']
148
+ },
149
+ async (params) => {
150
+ // Your database logic here
151
+ return { data: [] };
152
+ }
153
+ );
154
+
155
+ // Register API tool
156
+ acp.registerTool(
157
+ 'call_api',
158
+ 'Call an external API',
159
+ {
160
+ type: 'object',
161
+ properties: {
162
+ endpoint: { type: 'string' },
163
+ method: { type: 'string', enum: ['GET', 'POST'] }
164
+ },
165
+ required: ['endpoint', 'method']
166
+ },
167
+ async (params) => {
168
+ // Your API logic here
169
+ return { response: {} };
170
+ }
171
+ );
172
+
173
+ // Initialize and use
174
+ const initResponse = acp.createInitializeResponse();
175
+ console.log(initResponse.result.agentCapabilities);
176
+ ```
101
177
 
102
178
  ## License
103
179
 
package/core.js CHANGED
@@ -1,12 +1,18 @@
1
+ import { spawn } from 'child_process';
2
+ import { fileURLToPath } from 'url';
3
+ import { dirname, join } from 'path';
4
+
5
+ const __filename = fileURLToPath(import.meta.url);
6
+ const __dirname = dirname(__filename);
7
+
1
8
  class ACPProtocol {
2
- constructor() {
9
+ constructor(instruction) {
3
10
  this.messageId = 0;
4
- this.toolWhitelist = new Set(['simulative_retriever']);
11
+ this.instruction = instruction;
12
+ this.toolWhitelist = new Set();
5
13
  this.toolCallLog = [];
6
14
  this.rejectedCallLog = [];
7
- this.tools = {
8
- simulative_retriever: this.simulativeRetriever.bind(this),
9
- };
15
+ this.tools = {};
10
16
  }
11
17
 
12
18
  generateRequestId() {
@@ -41,71 +47,45 @@ class ACPProtocol {
41
47
  };
42
48
  }
43
49
 
44
- createInitializeResponse() {
50
+ registerTool(name, description, inputSchema, handler) {
51
+ this.toolWhitelist.add(name);
52
+ this.tools[name] = handler;
45
53
  return {
46
- jsonrpc: "2.0",
47
- id: 0,
48
- result: {
49
- protocolVersion: "1.0",
50
- serverInfo: {
51
- name: "OpenCode ACP Server",
52
- version: "1.0.0",
53
- },
54
- securityConfiguration: {
55
- toolWhitelistEnabled: true,
56
- allowedTools: Array.from(this.toolWhitelist),
57
- rejectionBehavior: "strict",
58
- },
59
- agentCapabilities: [
60
- {
61
- type: "tool",
62
- name: "simulative_retriever",
63
- description: "Retrieve business information from a simulated database",
64
- whitelisted: true,
65
- inputSchema: {
66
- type: "object",
67
- properties: {
68
- query: {
69
- type: "string",
70
- description: "The search query",
71
- },
72
- },
73
- required: ["query"],
74
- },
75
- outputSchema: {
76
- type: "object",
77
- properties: {
78
- success: { type: "boolean" },
79
- result: { type: "string" },
80
- details: { type: "string" },
81
- },
82
- required: ["success", "result", "details"],
83
- },
84
- },
85
- ],
86
- },
54
+ name,
55
+ description,
56
+ inputSchema,
87
57
  };
88
58
  }
89
59
 
90
- simulativeRetriever(query) {
91
- const lowerQuery = query.toLowerCase();
92
- if (
93
- (lowerQuery.includes("taj mahal") &&
94
- lowerQuery.includes("main street")) &&
95
- (lowerQuery.includes("phone") ||
96
- lowerQuery.includes("number") ||
97
- lowerQuery.includes("contact"))
98
- ) {
99
- return {
100
- success: true,
101
- result: "555-0142",
102
- details: "Found phone number for Taj Mahal on Main Street: 555-0142",
103
- };
60
+ createInitializeResponse() {
61
+ const agentCapabilities = Array.from(this.toolWhitelist).map(toolName => ({
62
+ type: "tool",
63
+ name: toolName,
64
+ whitelisted: true,
65
+ }));
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;
104
83
  }
84
+
105
85
  return {
106
- success: false,
107
- result: null,
108
- details: `Information not found in database for query: "${query}"`,
86
+ jsonrpc: "2.0",
87
+ id: 0,
88
+ result,
109
89
  };
110
90
  }
111
91
 
@@ -133,12 +113,12 @@ class ACPProtocol {
133
113
  this.toolCallLog.push({
134
114
  timestamp: new Date().toISOString(),
135
115
  toolName,
136
- query: params.query,
116
+ params,
137
117
  status: 'executing',
138
118
  });
139
119
 
140
120
  if (this.tools[toolName]) {
141
- const result = this.tools[toolName](params.query);
121
+ const result = await this.tools[toolName](params);
142
122
  const lastLog = this.toolCallLog[this.toolCallLog.length - 1];
143
123
  lastLog.status = 'completed';
144
124
  lastLog.result = result;
@@ -146,46 +126,103 @@ class ACPProtocol {
146
126
  }
147
127
  throw new Error(`Unknown tool: ${toolName}`);
148
128
  }
149
- }
150
129
 
151
- function createSimulativeRetriever() {
152
- return {
153
- name: "simulative_retriever",
154
- description: "Retrieve business information from a simulated database",
155
- inputSchema: {
156
- type: "object",
157
- properties: {
158
- query: {
159
- type: "string",
160
- description: "The search query (e.g., 'Taj Mahal phone number')",
161
- },
162
- },
163
- required: ["query"],
164
- },
165
- };
166
- }
130
+ async process(text, options = {}) {
131
+ const cli = options.cli || 'opencode';
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');
167
138
 
168
- async function processChat(chatContent, options = {}) {
169
- const acp = new ACPProtocol();
170
- const toolCalls = [];
171
- const answer = chatContent;
139
+ const prompt = `${instruction}
172
140
 
173
- const onToolCall = options.onToolCall || (() => {});
141
+ Available tools:
142
+ ${toolsDesc}
174
143
 
175
- if (options.onToolCall) {
176
- toolCalls.push({
177
- timestamp: new Date().toISOString(),
178
- content: chatContent.substring(0, 100),
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.`;
148
+
149
+ return new Promise((resolve, reject) => {
150
+ let output = '';
151
+ let errorOutput = '';
152
+
153
+ const child = spawn(cli, ['--stdin'], {
154
+ stdio: ['pipe', 'pipe', 'pipe'],
155
+ cwd: process.cwd()
156
+ });
157
+
158
+ child.stdout.on('data', (data) => {
159
+ output += data.toString();
160
+ });
161
+
162
+ child.stderr.on('data', (data) => {
163
+ errorOutput += data.toString();
164
+ });
165
+
166
+ child.on('close', async (code) => {
167
+ if (code !== 0 && code !== null) {
168
+ reject(new Error(`CLI exited with code ${code}: ${errorOutput}`));
169
+ return;
170
+ }
171
+
172
+ try {
173
+ const toolCalls = this.parseToolCalls(output);
174
+ const results = [];
175
+
176
+ for (const call of toolCalls) {
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
+ }
195
+ });
196
+
197
+ child.on('error', (error) => {
198
+ reject(new Error(`Failed to spawn ${cli}: ${error.message}`));
199
+ });
200
+
201
+ child.stdin.write(prompt);
202
+ child.stdin.end();
179
203
  });
180
- onToolCall('analyze', { content: chatContent });
181
204
  }
182
205
 
183
- return {
184
- answer,
185
- toolCalls,
186
- logs: acp.toolCallLog,
187
- rejectedLogs: acp.rejectedCallLog,
188
- };
206
+ parseToolCalls(output) {
207
+ const calls = [];
208
+ const lines = output.split('\n');
209
+
210
+ for (const line of lines) {
211
+ const trimmed = line.trim();
212
+ if (!trimmed) continue;
213
+
214
+ try {
215
+ const json = JSON.parse(trimmed);
216
+ if (json.jsonrpc === '2.0' && json.method && json.params) {
217
+ calls.push({ method: json.method, params: json.params });
218
+ }
219
+ } catch (e) {
220
+ // Not JSON, skip
221
+ }
222
+ }
223
+
224
+ return calls;
225
+ }
189
226
  }
190
227
 
191
- export { ACPProtocol, createSimulativeRetriever, processChat };
228
+ export { ACPProtocol };
package/index.js CHANGED
@@ -1,25 +1,28 @@
1
- import { ACPProtocol, createSimulativeRetriever, processChat } from './core.js';
1
+ import { ACPProtocol } from './core.js';
2
2
 
3
- export { ACPProtocol, createSimulativeRetriever, processChat };
3
+ export { ACPProtocol };
4
4
 
5
5
  /*
6
- * ACP SDK Usage Examples
6
+ * acpreact - ACP SDK for registering tools
7
7
  *
8
8
  * Basic usage:
9
- * import { ACPProtocol, createSimulativeRetriever, processChat } from 'acpreact';
9
+ * import { ACPProtocol } from 'acpreact';
10
10
  *
11
- * Create and use ACP Protocol:
11
+ * Create ACP Protocol instance:
12
12
  * const acp = new ACPProtocol();
13
- * const response = acp.createInitializeResponse();
14
- * const result = await acp.callTool('simulative_retriever', { query: 'test' });
15
- *
16
- * Create tool definition:
17
- * const tool = createSimulativeRetriever();
18
13
  *
19
- * Process chat content:
20
- * const chatContent = "[14:23] alice: Where is Taj Mahal?\n[14:24] bob: Main Street";
21
- * const result = await processChat(chatContent, {
22
- * onToolCall: (toolName, args) => console.log(toolName, args)
14
+ * Register custom tools:
15
+ * acp.registerTool('my_tool', 'Tool description', {
16
+ * type: 'object',
17
+ * properties: { query: { type: 'string' } },
18
+ * required: ['query']
19
+ * }, async (params) => {
20
+ * return { result: 'processed' };
23
21
  * });
24
- * console.log(result.answer);
22
+ *
23
+ * Initialize protocol:
24
+ * const response = acp.createInitializeResponse();
25
+ *
26
+ * Execute tool:
27
+ * const result = await acp.callTool('my_tool', { query: 'test' });
25
28
  */
package/package.json CHANGED
@@ -1,15 +1,21 @@
1
1
  {
2
2
  "name": "acpreact",
3
- "version": "1.0.0",
3
+ "version": "1.0.3",
4
4
  "type": "module",
5
5
  "main": "index.js",
6
6
  "description": "ACP SDK for monitoring and reacting to chat conversations",
7
- "keywords": ["acp", "protocol", "chat", "monitoring"],
7
+ "keywords": [
8
+ "acp",
9
+ "protocol",
10
+ "chat",
11
+ "monitoring"
12
+ ],
8
13
  "author": "",
9
14
  "license": "ISC",
10
15
  "dependencies": {
11
16
  "@anthropic-ai/sdk": "^0.74.0",
12
17
  "@opencode-ai/plugin": "^1.2.5",
13
- "@opencode-ai/sdk": "^1.2.5"
18
+ "@opencode-ai/sdk": "^1.2.5",
19
+ "@kilocode/cli": "^1.0.0"
14
20
  }
15
21
  }