@kadi.build/core 0.0.1-alpha.1 → 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.
- package/README.md +1145 -216
- package/examples/example-abilities/echo-js/README.md +131 -0
- package/examples/example-abilities/echo-js/agent.json +63 -0
- package/examples/example-abilities/echo-js/package.json +24 -0
- package/examples/example-abilities/echo-js/service.js +43 -0
- package/examples/example-abilities/hash-go/agent.json +53 -0
- package/examples/example-abilities/hash-go/cmd/hash_ability/main.go +340 -0
- package/examples/example-abilities/hash-go/go.mod +3 -0
- package/examples/example-agent/abilities/echo-js/0.0.1/README.md +131 -0
- package/examples/example-agent/abilities/echo-js/0.0.1/agent.json +63 -0
- package/examples/example-agent/abilities/echo-js/0.0.1/package-lock.json +93 -0
- package/examples/example-agent/abilities/echo-js/0.0.1/package.json +24 -0
- package/examples/example-agent/abilities/echo-js/0.0.1/service.js +41 -0
- package/examples/example-agent/abilities/hash-go/0.0.1/agent.json +53 -0
- package/examples/example-agent/abilities/hash-go/0.0.1/bin/hash_ability +0 -0
- package/examples/example-agent/abilities/hash-go/0.0.1/cmd/hash_ability/main.go +340 -0
- package/examples/example-agent/abilities/hash-go/0.0.1/go.mod +3 -0
- package/examples/example-agent/agent.json +39 -0
- package/examples/example-agent/index.js +102 -0
- package/examples/example-agent/package-lock.json +93 -0
- package/examples/example-agent/package.json +17 -0
- package/package.json +4 -2
- package/src/KadiAbility.js +478 -0
- package/src/index.js +65 -0
- package/src/loadAbility.js +1086 -0
- package/src/servers/BaseRpcServer.js +404 -0
- package/src/servers/BrokerRpcServer.js +776 -0
- package/src/servers/StdioRpcServer.js +360 -0
- package/src/transport/BrokerMessageBuilder.js +377 -0
- package/src/transport/IpcMessageBuilder.js +1229 -0
- package/src/utils/agentUtils.js +137 -0
- package/src/utils/commandUtils.js +64 -0
- package/src/utils/configUtils.js +72 -0
- package/src/utils/logger.js +161 -0
- package/src/utils/pathUtils.js +86 -0
- package/broker.js +0 -214
- package/index.js +0 -370
- package/ipc.js +0 -220
- 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()
|