@kadi.build/core 0.0.1-alpha.2 → 0.0.1-alpha.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.
Files changed (39) hide show
  1. package/README.md +1145 -216
  2. package/examples/example-abilities/echo-js/README.md +131 -0
  3. package/examples/example-abilities/echo-js/agent.json +63 -0
  4. package/examples/example-abilities/echo-js/package.json +24 -0
  5. package/examples/example-abilities/echo-js/service.js +43 -0
  6. package/examples/example-abilities/hash-go/agent.json +53 -0
  7. package/examples/example-abilities/hash-go/cmd/hash_ability/main.go +340 -0
  8. package/examples/example-abilities/hash-go/go.mod +3 -0
  9. package/examples/example-agent/abilities/echo-js/0.0.1/README.md +131 -0
  10. package/examples/example-agent/abilities/echo-js/0.0.1/agent.json +63 -0
  11. package/examples/example-agent/abilities/echo-js/0.0.1/package-lock.json +93 -0
  12. package/examples/example-agent/abilities/echo-js/0.0.1/package.json +24 -0
  13. package/examples/example-agent/abilities/echo-js/0.0.1/service.js +41 -0
  14. package/examples/example-agent/abilities/hash-go/0.0.1/agent.json +53 -0
  15. package/examples/example-agent/abilities/hash-go/0.0.1/bin/hash_ability +0 -0
  16. package/examples/example-agent/abilities/hash-go/0.0.1/cmd/hash_ability/main.go +340 -0
  17. package/examples/example-agent/abilities/hash-go/0.0.1/go.mod +3 -0
  18. package/examples/example-agent/agent.json +39 -0
  19. package/examples/example-agent/index.js +102 -0
  20. package/examples/example-agent/package-lock.json +93 -0
  21. package/examples/example-agent/package.json +17 -0
  22. package/package.json +4 -2
  23. package/src/KadiAbility.js +478 -0
  24. package/src/index.js +65 -0
  25. package/src/loadAbility.js +1086 -0
  26. package/src/servers/BaseRpcServer.js +404 -0
  27. package/src/servers/BrokerRpcServer.js +776 -0
  28. package/src/servers/StdioRpcServer.js +360 -0
  29. package/src/transport/BrokerMessageBuilder.js +377 -0
  30. package/src/transport/IpcMessageBuilder.js +1229 -0
  31. package/src/utils/agentUtils.js +137 -0
  32. package/src/utils/commandUtils.js +64 -0
  33. package/src/utils/configUtils.js +72 -0
  34. package/src/utils/logger.js +161 -0
  35. package/src/utils/pathUtils.js +86 -0
  36. package/broker.js +0 -214
  37. package/index.js +0 -382
  38. package/ipc.js +0 -220
  39. package/ipcInterfaces/pythonAbilityIPC.py +0 -177
package/ipc.js DELETED
@@ -1,220 +0,0 @@
1
- import { EventEmitter } from 'events';
2
- import { randomUUID } from 'crypto';
3
- import { spawn } from 'child_process';
4
- import fs from 'fs';
5
- import { log } from 'console';
6
-
7
- //IPC JSON Message Schema
8
- // {
9
- // type: 'launch' | 'shutdown' | 'message',
10
- // contents: any,
11
- // time: string
12
- // }
13
-
14
- export const IPCMessageBuilder = {
15
- createMessage: function (type, contents) {
16
- return JSON.stringify({
17
- type: type,
18
- contents: contents,
19
- time: new Date().toISOString()
20
- });
21
- },
22
-
23
- launch: function (commandString, logfilepath = null) {
24
- return IPCMessageBuilder.createMessage('launch', {
25
- command: commandString,
26
- logfilepath: logfilepath
27
- });
28
- },
29
-
30
- shutdown: function () {
31
- return IPCMessageBuilder.createMessage('shutdown', {});
32
- },
33
-
34
- message: function (content) {
35
- return IPCMessageBuilder.createMessage('message', content);
36
- },
37
-
38
- broker: function (content) {
39
- return IPCMessageBuilder.createMessage('broker', content);
40
- }
41
- };
42
-
43
- export const IPCManager = {
44
- instances: new Map(),
45
- IPCInterfaces: {},
46
- createInstance: function (language, commandString, name = 'default') {
47
- if (!IPCManager.IPCInterfaces[language]) {
48
- throw new Error('Configuration for this language is not provided');
49
- }
50
-
51
- const instance = new IAbilityIPC(
52
- language,
53
- commandString,
54
- IPCManager.IPCInterfaces,
55
- name
56
- );
57
- this.instances.set(name, instance);
58
-
59
- instance.on('shutdown', () => {
60
- this.instances.delete(name);
61
- console.log(`Instance ${name} removed after shutdown.`);
62
- });
63
-
64
- return instance;
65
- },
66
-
67
- getInstance: function (name) {
68
- return this.instances.get(name);
69
- },
70
-
71
- shutdownInstance: function (name) {
72
- const instance = this.instances.get(name);
73
- if (instance) {
74
- instance.shutdown();
75
- }
76
- },
77
-
78
- setIPCInterfaces: function (interfaces) {
79
- IPCManager.IPCInterfaces = interfaces;
80
- }
81
- };
82
-
83
- class IAbilityIPC extends EventEmitter {
84
- constructor(language, commandString, interfaces, name) {
85
- super();
86
- this.language = language;
87
- this.process = null; // Process is initially null
88
- this.commandString = commandString;
89
- this.name = name;
90
- this.input_buffer = '';
91
- this.error_buffer = '';
92
- this._broker = null; // Broker used for proxying messages
93
-
94
- // Read configurations from agent.json
95
- this.IPCInterfaces = interfaces;
96
- this.ipcStartupCommand = this.IPCInterfaces[language]; // Command to start the language IPC
97
-
98
- if (!this.ipcStartupCommand) {
99
- throw new Error('Unsupported language or missing command configuration');
100
- } else {
101
- console.log('IPC Startup Command: ', this.ipcStartupCommand);
102
- this.start();
103
- }
104
- }
105
-
106
- start() {
107
- // Starts the child process that handles IPC
108
- this.process = spawn(this.ipcStartupCommand, {
109
- shell: true
110
- });
111
-
112
- this.process.stdout.on('data', (data) => {
113
- this.input_buffer += data.toString();
114
- let boundary = this.input_buffer.indexOf('\n');
115
- while (boundary !== -1) {
116
- let message = this.input_buffer.substring(0, boundary).trim();
117
- this.input_buffer = this.input_buffer.substring(boundary + 1);
118
- if (message) {
119
- try {
120
- let msg = JSON.parse(message);
121
- if (msg.type === 'shutdown') {
122
- this.emit('shutdown');
123
- if (this._broker) {
124
- this._broker.ws.close();
125
- }
126
- } else if (msg.type === 'broker') {
127
- this.emit('broker', msg);
128
- } else {
129
- this.emit('message', msg);
130
- }
131
- // this.emit('message', JSON.parse(message));
132
- } catch (error) {
133
- console.error('Error parsing JSON:', error.message);
134
- console.error('Raw data received:', message);
135
- }
136
- }
137
- boundary = this.input_buffer.indexOf('\n');
138
- }
139
- });
140
-
141
- this.process.stderr.on('data', (data) => {
142
- this.error_buffer += data.toString();
143
- let boundary = this.error_buffer.indexOf('\n');
144
- while (boundary !== -1) {
145
- let message = this.error_buffer.substring(0, boundary).trim();
146
- this.error_buffer = this.error_buffer.substring(boundary + 1);
147
- if (message) {
148
- try {
149
- this.emit('error', JSON.parse(message));
150
- } catch (error) {
151
- console.error('Error parsing JSON:', error.message);
152
- console.error('Raw data received:', message);
153
- }
154
- }
155
- boundary = this.error_buffer.indexOf('\n');
156
- }
157
- });
158
-
159
- this.process.on('exit', (code) => {
160
- this.emit('exit', code);
161
- // this.emit('shutdown', this); // Emitting a shutdown event for the manager to catch
162
- });
163
-
164
- this.process.on('error', (error) => {
165
- this.emit('error', error);
166
- });
167
-
168
- this.on('broker', (msg) => {
169
- if (this._broker) {
170
- this._broker.send(JSON.stringify(msg.contents));
171
- } else {
172
- console.error('No broker available to forward message to.');
173
- }
174
- });
175
-
176
- this.on('message', (msg) => {
177
- // console.log('Message received:', msg);
178
- });
179
- }
180
-
181
- launch(logfilepath = null) {
182
- // Send the command string to the language-specific IPC to launch the actual tool
183
- let msg = IPCMessageBuilder.launch(this.commandString, logfilepath);
184
- console.log('Sending launch message: ', msg);
185
- this.sendMessage(msg);
186
- }
187
-
188
- sendMessage(message) {
189
- if (this.process && this.process.stdin.writable) {
190
- this.process.stdin.write(JSON.stringify(message) + '\n');
191
- } else {
192
- // throw new Error("Process is not running or stdin is not writable.");
193
- }
194
- }
195
-
196
- shutdown() {
197
- this.sendMessage(IPCMessageBuilder.shutdown());
198
- }
199
-
200
- attachBroker(broker) {
201
- this._broker = broker;
202
-
203
- this._broker.on('message', (data) => {
204
- let msg = JSON.parse(data);
205
- console.log('IPC Broker Message Recieved: ', msg);
206
- this.sendMessage(IPCMessageBuilder.broker(msg));
207
- });
208
-
209
- this._broker.on('error', (error) => {
210
- let msg = JSON.parse(error);
211
-
212
- this.sendMessage(IPCMessageBuilder.broker(msg));
213
- });
214
-
215
- this._broker.on('close', () => {
216
- this.shutdown();
217
- // process.exit(0); // Exit the process when the WebSocket connection is closed
218
- });
219
- }
220
- }
@@ -1,177 +0,0 @@
1
- import sys
2
- import json
3
- import subprocess
4
- import select
5
- import threading
6
- import os
7
- from datetime import datetime, timezone
8
-
9
- class MessageBuilder:
10
- @staticmethod
11
- def create_message(type, contents):
12
- return json.dumps({
13
- "type": type,
14
- "contents": contents,
15
- "time": datetime.now(timezone.utc).isoformat()
16
- })
17
-
18
- @staticmethod
19
- def shutdown():
20
- return MessageBuilder.create_message('shutdown', {})
21
-
22
- @staticmethod
23
- def message(contents):
24
- return MessageBuilder.create_message('message', contents)
25
-
26
- @staticmethod
27
- def broker(contents):
28
- return MessageBuilder.create_message('broker', contents)
29
-
30
- def handle_process_output(ipc_instance, output):
31
- """Developer-customizable function to handle stdout data."""
32
- # print("Process Output:", output.strip())
33
- # ipc_instance.send(MessageBuilder.message({"status": "stdout", "contents": output}))
34
-
35
- def handle_process_errors(ipc_instance, output):
36
- """Developer-customizable function to handle stderr data."""
37
- # print("Process Error:", output.strip())
38
- ipc_instance.send(MessageBuilder.message({"status": "stderr", "contents": output}))
39
-
40
- def handle_broker(ipc_instance, contents):
41
- """Developer-customizable function to handle custom Broker messages."""
42
- # ipc_instance.send(MessageBuilder.broker({"status": "message_received", "contents": contents}))
43
-
44
- def handle_message(ipc_instance, contents):
45
- """Developer-customizable function to handle custom IPC messages."""
46
- # Placeholder implementation that simply prints the message
47
- # print(f"Received custom message: {contents}")
48
- # ipc_instance.send(MessageBuilder.message({"status": "message_received", "contents": contents}))
49
- # Developers can add their own logic here depending on their requirements
50
-
51
- class PythonAbilityIPC:
52
- def __init__(self):
53
- self.running = True
54
- self.child_process = None
55
- self.logfile = False
56
-
57
- def process_stream(self, stream, handler):
58
- """Generic method to process stdout or stderr from child process."""
59
- for line in iter(stream.readline, ''):
60
- handler(line)
61
- stream.close()
62
-
63
- def read_input(self):
64
- """Read input lines from stdin and process each as a JSON message."""
65
- while self.running:
66
- try:
67
- readable, _, _ = select.select([sys.stdin], [], [], 0.1)
68
- if readable:
69
- line = sys.stdin.readline()
70
- if line == '':
71
- print("Node.js process has terminated or closed the pipe.")
72
- self.shutdown()
73
- break
74
- if line.strip():
75
- try:
76
- message = json.loads(line)
77
- # self.send(MessageBuilder.message({"debug": "Parsing Message", "message": message}))
78
- self.parse_message(message)
79
- except json.JSONDecodeError as e:
80
- self.send_error(f"JSON decoding failed: {str(e)}")
81
- except Exception as e:
82
- self.send_error(f"An unexpected error occurred: {str(e)}")
83
- self.shutdown()
84
- break
85
-
86
- def parse_message(self, message):
87
- """Parse incoming messages and take appropriate actions."""
88
- try:
89
- message = json.loads(message)
90
- message_type = message.get('type')
91
- if message_type == 'launch':
92
- command = message.get('contents').get('command')
93
- logfilepath = message.get('contents').get('logfilepath', None)
94
- self.launch_tool(command, logfilepath)
95
- elif message_type == 'shutdown':
96
- self.shutdown()
97
- elif message_type == 'broker':
98
- handle_broker(self, message.get('contents')) # Handle broker messages
99
- elif message_type == 'message':
100
- handle_message(self, message.get('contents')) # Handle ipc messages
101
- else:
102
- self.send(MessageBuilder.message({"status": "unknown_message_type", "contents": message}))
103
- except Exception as e:
104
- self.send_error(f"An error occurred: {e}")
105
-
106
- def handle_stdout(self, output):
107
- """Handles stdout from the child process."""
108
- if self.logfile and self.stdout_log_path:
109
- with open(self.stdout_log_path, 'a') as f:
110
- f.write(output)
111
- handle_process_output(self, output)
112
-
113
- def handle_stderr(self, output):
114
- """Handles stderr from the child process."""
115
- if self.logfile and self.stderr_log_path:
116
- with open(self.stderr_log_path, 'a') as f:
117
- f.write(output)
118
- handle_process_errors(self, output)
119
-
120
- def launch_tool(self, command_string, logfilepath=None):
121
- if self.child_process:
122
- self.child_process.terminate()
123
-
124
- if logfilepath:
125
- self.logfile = True
126
- timestamp = datetime.now().strftime("%Y-%m-%d_%H-%M-%S")
127
- self.stdout_log_path = os.path.join(logfilepath, f"{timestamp}-stdout.log")
128
- self.stderr_log_path = os.path.join(logfilepath, f"{timestamp}-stderr.log")
129
- os.makedirs(logfilepath, exist_ok=True)
130
-
131
- self.child_process = subprocess.Popen(
132
- command_string, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE,
133
- universal_newlines=True, bufsize=1
134
- )
135
- # Setup stdout and stderr handling threads
136
- threading.Thread(target=self.process_stream, args=(self.child_process.stdout, self.handle_stdout)).start()
137
- threading.Thread(target=self.process_stream, args=(self.child_process.stderr, self.handle_stderr)).start()
138
-
139
- # Send a message back to KADI using the message builder
140
- self.send(MessageBuilder.message({"status": "tool_launched", "command": command_string}))
141
-
142
- def shutdown(self):
143
- if self.child_process:
144
- self.child_process.terminate()
145
- self.child_process = None
146
- self.running = False
147
- self.send(MessageBuilder.shutdown())
148
- sys.exit(0)
149
-
150
- def send_error(self, message):
151
- sys.stderr.write(MessageBuilder.message({
152
- 'type': 'error',
153
- 'contents': {'message': message, 'type': 'exception'},
154
- 'time': datetime.now(timezone.utc).isoformat()
155
- }) + '\n')
156
- sys.stderr.flush()
157
-
158
- def send(self, serializedJSONmessage):
159
- """Send a JSON message back to Node.js using standardized format."""
160
- sys.stdout.write(serializedJSONmessage + '\n')
161
- sys.stdout.flush()
162
-
163
- def main():
164
- try:
165
- ipc = PythonAbilityIPC()
166
- ipc.read_input()
167
- except Exception as e:
168
- # Print exception information to stderr to catch it in Node.js
169
- print(json.dumps({'type': 'error', 'contents': {'message': str(e), 'type': 'exception'}, 'time': datetime.now(timezone.utc).isoformat()}), file=sys.stderr)
170
- # Here you could also include more sophisticated error handling mechanisms:
171
- # - Logging to a file
172
- # - Sending alerts to a monitoring system
173
- # - Cleanup resources or attempt recovery
174
- sys.exit(1) # Exit with a non-zero status to indicate an error
175
-
176
- if __name__ == "__main__":
177
- main()