panteao-ts 1.0.0
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/dist/cjs/index.d.ts +34 -0
- package/dist/cjs/index.js +323 -0
- package/dist/cjs/package.json +1 -0
- package/dist/esm/index.d.ts +34 -0
- package/dist/esm/index.js +323 -0
- package/dist/esm/package.json +1 -0
- package/package.json +35 -0
- package/panteaots-1.0.0.tgz +0 -0
- package/src/index.ts +306 -0
- package/tsconfig.cjs.json +8 -0
- package/tsconfig.json +13 -0
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { EventEmitter } from 'events';
|
|
2
|
+
export interface BdiClientOptions {
|
|
3
|
+
host?: string;
|
|
4
|
+
port?: number;
|
|
5
|
+
autoReconnect?: boolean;
|
|
6
|
+
reconnectInterval?: number;
|
|
7
|
+
project?: string;
|
|
8
|
+
binPath?: string;
|
|
9
|
+
}
|
|
10
|
+
export type ActionCallback = (args: string[], respond: (success: boolean) => void) => void;
|
|
11
|
+
export declare class BdiClient extends EventEmitter {
|
|
12
|
+
private socket;
|
|
13
|
+
private host;
|
|
14
|
+
private port;
|
|
15
|
+
private buffer;
|
|
16
|
+
private autoReconnect;
|
|
17
|
+
private reconnectInterval;
|
|
18
|
+
private actionHandlers;
|
|
19
|
+
private project?;
|
|
20
|
+
private binPath;
|
|
21
|
+
private process;
|
|
22
|
+
constructor(options?: BdiClientOptions);
|
|
23
|
+
connect(): Promise<void>;
|
|
24
|
+
private setupSocketEvents;
|
|
25
|
+
private handleIncomingLine;
|
|
26
|
+
private parseAction;
|
|
27
|
+
private cleanArg;
|
|
28
|
+
sendMsg(performative: string, sender: string, receiver: string, content: string): void;
|
|
29
|
+
sendPerception(action: 'add' | 'remove', perception: string): void;
|
|
30
|
+
registerAction(actionName: string, callback: ActionCallback): void;
|
|
31
|
+
private sendActionResult;
|
|
32
|
+
close(): void;
|
|
33
|
+
}
|
|
34
|
+
export { BdiClient as Panteao, BdiClient as Panteão };
|
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.Panteão = exports.Panteao = exports.BdiClient = void 0;
|
|
37
|
+
const net = __importStar(require("net"));
|
|
38
|
+
const events_1 = require("events");
|
|
39
|
+
const child_process = __importStar(require("child_process"));
|
|
40
|
+
const path = __importStar(require("path"));
|
|
41
|
+
const fs = __importStar(require("fs"));
|
|
42
|
+
function getFreePort() {
|
|
43
|
+
return new Promise((resolve, reject) => {
|
|
44
|
+
const server = net.createServer();
|
|
45
|
+
server.listen(0, '127.0.0.1', () => {
|
|
46
|
+
const address = server.address();
|
|
47
|
+
const port = typeof address === 'string' ? 0 : address?.port;
|
|
48
|
+
server.close(() => {
|
|
49
|
+
if (port)
|
|
50
|
+
resolve(port);
|
|
51
|
+
else
|
|
52
|
+
reject(new Error('Failed to allocate port'));
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
server.on('error', reject);
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
class BdiClient extends events_1.EventEmitter {
|
|
59
|
+
socket;
|
|
60
|
+
host;
|
|
61
|
+
port;
|
|
62
|
+
buffer = '';
|
|
63
|
+
autoReconnect;
|
|
64
|
+
reconnectInterval;
|
|
65
|
+
actionHandlers = new Map();
|
|
66
|
+
project;
|
|
67
|
+
binPath;
|
|
68
|
+
process = null;
|
|
69
|
+
constructor(options = {}) {
|
|
70
|
+
super();
|
|
71
|
+
this.host = options.host || '127.0.0.1';
|
|
72
|
+
this.port = options.port || 0;
|
|
73
|
+
this.project = options.project;
|
|
74
|
+
this.autoReconnect = options.project ? false : (options.autoReconnect ?? true);
|
|
75
|
+
this.reconnectInterval = options.reconnectInterval || 2000;
|
|
76
|
+
this.socket = new net.Socket();
|
|
77
|
+
let binPath = options.binPath;
|
|
78
|
+
if (!binPath) {
|
|
79
|
+
const isWin = process.platform === 'win32';
|
|
80
|
+
const binName = isWin ? 'panteao-engine.exe' : 'panteao-engine';
|
|
81
|
+
// Try to resolve from platform-specific package first
|
|
82
|
+
const platformPkg = `@panteao/engine-${process.platform}-${process.arch}`;
|
|
83
|
+
let resolvedPath = null;
|
|
84
|
+
try {
|
|
85
|
+
const pkgPath = require.resolve(path.join(platformPkg, 'package.json'));
|
|
86
|
+
const pkgDir = path.dirname(pkgPath);
|
|
87
|
+
const candidate = path.join(pkgDir, 'bin', binName);
|
|
88
|
+
const candidateFallback = path.join(pkgDir, binName);
|
|
89
|
+
if (fs.existsSync(candidate)) {
|
|
90
|
+
resolvedPath = candidate;
|
|
91
|
+
}
|
|
92
|
+
else if (fs.existsSync(candidateFallback)) {
|
|
93
|
+
resolvedPath = candidateFallback;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
catch (e) {
|
|
97
|
+
// Platform package not installed or resolved
|
|
98
|
+
}
|
|
99
|
+
if (resolvedPath) {
|
|
100
|
+
binPath = resolvedPath;
|
|
101
|
+
}
|
|
102
|
+
else {
|
|
103
|
+
// Fallback to local paths
|
|
104
|
+
const candidate1 = path.join(__dirname, binName);
|
|
105
|
+
const candidate2 = path.join(__dirname, 'bin', binName);
|
|
106
|
+
if (fs.existsSync(candidate1)) {
|
|
107
|
+
binPath = candidate1;
|
|
108
|
+
}
|
|
109
|
+
else if (fs.existsSync(candidate2)) {
|
|
110
|
+
binPath = candidate2;
|
|
111
|
+
}
|
|
112
|
+
else {
|
|
113
|
+
binPath = binName;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
this.binPath = binPath;
|
|
118
|
+
}
|
|
119
|
+
async connect() {
|
|
120
|
+
if (this.project) {
|
|
121
|
+
if (this.port === 0) {
|
|
122
|
+
this.port = await getFreePort();
|
|
123
|
+
}
|
|
124
|
+
const args = [this.project, '--port', String(this.port)];
|
|
125
|
+
this.process = child_process.spawn(this.binPath, args, { stdio: 'ignore' });
|
|
126
|
+
await new Promise((resolve) => setTimeout(resolve, 800));
|
|
127
|
+
}
|
|
128
|
+
else if (this.port === 0) {
|
|
129
|
+
this.port = 44444;
|
|
130
|
+
}
|
|
131
|
+
return new Promise((resolve, reject) => {
|
|
132
|
+
let handshakeBuffer = '';
|
|
133
|
+
const onConnect = () => {
|
|
134
|
+
const onData = (data) => {
|
|
135
|
+
handshakeBuffer += data.toString('utf8');
|
|
136
|
+
const lines = handshakeBuffer.split('\n');
|
|
137
|
+
handshakeBuffer = lines.pop() || '';
|
|
138
|
+
for (const line of lines) {
|
|
139
|
+
try {
|
|
140
|
+
if (!line.trim())
|
|
141
|
+
continue;
|
|
142
|
+
const msg = JSON.parse(line);
|
|
143
|
+
if (msg.type === 'mas_ready') {
|
|
144
|
+
this.socket.off('data', onData);
|
|
145
|
+
this.socket.off('error', onError);
|
|
146
|
+
this.setupSocketEvents();
|
|
147
|
+
this.emit('connect');
|
|
148
|
+
resolve();
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
catch (e) { }
|
|
153
|
+
}
|
|
154
|
+
};
|
|
155
|
+
const onError = (err) => {
|
|
156
|
+
this.socket.off('data', onData);
|
|
157
|
+
if (this.process) {
|
|
158
|
+
this.process.kill();
|
|
159
|
+
this.process = null;
|
|
160
|
+
}
|
|
161
|
+
reject(err);
|
|
162
|
+
};
|
|
163
|
+
this.socket.on('data', onData);
|
|
164
|
+
this.socket.once('error', onError);
|
|
165
|
+
};
|
|
166
|
+
const onInitialError = (err) => {
|
|
167
|
+
if (this.process) {
|
|
168
|
+
this.process.kill();
|
|
169
|
+
this.process = null;
|
|
170
|
+
}
|
|
171
|
+
reject(err);
|
|
172
|
+
};
|
|
173
|
+
this.socket.once('error', onInitialError);
|
|
174
|
+
this.socket.connect(this.port, this.host, () => {
|
|
175
|
+
this.socket.off('error', onInitialError);
|
|
176
|
+
onConnect();
|
|
177
|
+
});
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
setupSocketEvents() {
|
|
181
|
+
this.socket.on('data', (data) => {
|
|
182
|
+
this.buffer += data.toString('utf8');
|
|
183
|
+
const lines = this.buffer.split('\n');
|
|
184
|
+
this.buffer = lines.pop() || '';
|
|
185
|
+
for (const line of lines) {
|
|
186
|
+
if (line.trim().length === 0)
|
|
187
|
+
continue;
|
|
188
|
+
this.handleIncomingLine(line);
|
|
189
|
+
}
|
|
190
|
+
});
|
|
191
|
+
this.socket.on('close', () => {
|
|
192
|
+
this.emit('disconnect');
|
|
193
|
+
if (this.autoReconnect) {
|
|
194
|
+
setTimeout(() => {
|
|
195
|
+
this.connect().catch(() => { });
|
|
196
|
+
}, this.reconnectInterval);
|
|
197
|
+
}
|
|
198
|
+
});
|
|
199
|
+
this.socket.on('error', (err) => {
|
|
200
|
+
this.emit('error', err);
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
handleIncomingLine(line) {
|
|
204
|
+
try {
|
|
205
|
+
const msg = JSON.parse(line);
|
|
206
|
+
if (msg.type === 'action') {
|
|
207
|
+
const rawAction = msg.action;
|
|
208
|
+
const { name, args } = this.parseAction(rawAction);
|
|
209
|
+
const handler = this.actionHandlers.get(name);
|
|
210
|
+
if (handler) {
|
|
211
|
+
const respond = (success) => {
|
|
212
|
+
this.sendActionResult(msg.id, success);
|
|
213
|
+
};
|
|
214
|
+
handler(args, respond);
|
|
215
|
+
}
|
|
216
|
+
else {
|
|
217
|
+
this.sendActionResult(msg.id, true);
|
|
218
|
+
}
|
|
219
|
+
this.emit('action', { name, args, agent: msg.agent, id: msg.id });
|
|
220
|
+
}
|
|
221
|
+
else if (msg.type === 'message') {
|
|
222
|
+
const { performative, sender, receiver, content } = msg;
|
|
223
|
+
this.emit('message', performative, sender, receiver, content);
|
|
224
|
+
this.emit(performative, sender, receiver, content);
|
|
225
|
+
// Compatibility: parse message content as an action
|
|
226
|
+
if (content && typeof content === 'string') {
|
|
227
|
+
const parsed = this.parseAction(content);
|
|
228
|
+
if (parsed && parsed.name) {
|
|
229
|
+
const handler = this.actionHandlers.get(parsed.name);
|
|
230
|
+
if (handler) {
|
|
231
|
+
const dummyRespond = () => { };
|
|
232
|
+
handler(parsed.args, dummyRespond);
|
|
233
|
+
}
|
|
234
|
+
this.emit('action', { name: parsed.name, args: parsed.args, agent: sender, id: null });
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
catch (e) {
|
|
240
|
+
this.emit('error', e);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
parseAction(actionStr) {
|
|
244
|
+
const parenIdx = actionStr.indexOf('(');
|
|
245
|
+
if (parenIdx === -1) {
|
|
246
|
+
return { name: actionStr.trim(), args: [] };
|
|
247
|
+
}
|
|
248
|
+
const name = actionStr.substring(0, parenIdx).trim();
|
|
249
|
+
const argsStr = actionStr.substring(parenIdx + 1, actionStr.lastIndexOf(')'));
|
|
250
|
+
const args = [];
|
|
251
|
+
let current = '';
|
|
252
|
+
let insideQuotes = false;
|
|
253
|
+
let depthBrackets = 0;
|
|
254
|
+
let depthParens = 0;
|
|
255
|
+
for (let i = 0; i < argsStr.length; i++) {
|
|
256
|
+
const char = argsStr[i];
|
|
257
|
+
if (char === '"') {
|
|
258
|
+
insideQuotes = !insideQuotes;
|
|
259
|
+
current += char;
|
|
260
|
+
}
|
|
261
|
+
else if (!insideQuotes && char === '[') {
|
|
262
|
+
depthBrackets++;
|
|
263
|
+
current += char;
|
|
264
|
+
}
|
|
265
|
+
else if (!insideQuotes && char === ']') {
|
|
266
|
+
depthBrackets--;
|
|
267
|
+
current += char;
|
|
268
|
+
}
|
|
269
|
+
else if (!insideQuotes && char === '(') {
|
|
270
|
+
depthParens++;
|
|
271
|
+
current += char;
|
|
272
|
+
}
|
|
273
|
+
else if (!insideQuotes && char === ')') {
|
|
274
|
+
depthParens--;
|
|
275
|
+
current += char;
|
|
276
|
+
}
|
|
277
|
+
else if (char === ',' && !insideQuotes && depthBrackets === 0 && depthParens === 0) {
|
|
278
|
+
args.push(this.cleanArg(current));
|
|
279
|
+
current = '';
|
|
280
|
+
}
|
|
281
|
+
else {
|
|
282
|
+
current += char;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
if (current.trim().length > 0) {
|
|
286
|
+
args.push(this.cleanArg(current));
|
|
287
|
+
}
|
|
288
|
+
return { name, args };
|
|
289
|
+
}
|
|
290
|
+
cleanArg(arg) {
|
|
291
|
+
const s = arg.trim();
|
|
292
|
+
if (s.startsWith('"') && s.endsWith('"') && s.length >= 2) {
|
|
293
|
+
return s.substring(1, s.length - 1);
|
|
294
|
+
}
|
|
295
|
+
return s;
|
|
296
|
+
}
|
|
297
|
+
sendMsg(performative, sender, receiver, content) {
|
|
298
|
+
const payload = JSON.stringify({ type: 'message', performative, sender, receiver, content }) + '\n';
|
|
299
|
+
this.socket.write(payload);
|
|
300
|
+
}
|
|
301
|
+
sendPerception(action, perception) {
|
|
302
|
+
const payload = JSON.stringify({ type: 'perception', action, perception }) + '\n';
|
|
303
|
+
this.socket.write(payload);
|
|
304
|
+
}
|
|
305
|
+
registerAction(actionName, callback) {
|
|
306
|
+
this.actionHandlers.set(actionName, callback);
|
|
307
|
+
}
|
|
308
|
+
sendActionResult(id, success) {
|
|
309
|
+
const payload = JSON.stringify({ type: 'action_result', id, success }) + '\n';
|
|
310
|
+
this.socket.write(payload);
|
|
311
|
+
}
|
|
312
|
+
close() {
|
|
313
|
+
this.autoReconnect = false;
|
|
314
|
+
this.socket.destroy();
|
|
315
|
+
if (this.process) {
|
|
316
|
+
this.process.kill();
|
|
317
|
+
this.process = null;
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
exports.BdiClient = BdiClient;
|
|
322
|
+
exports.Panteao = BdiClient;
|
|
323
|
+
exports.Panteão = BdiClient;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"type": "commonjs"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { EventEmitter } from 'events';
|
|
2
|
+
export interface BdiClientOptions {
|
|
3
|
+
host?: string;
|
|
4
|
+
port?: number;
|
|
5
|
+
autoReconnect?: boolean;
|
|
6
|
+
reconnectInterval?: number;
|
|
7
|
+
project?: string;
|
|
8
|
+
binPath?: string;
|
|
9
|
+
}
|
|
10
|
+
export type ActionCallback = (args: string[], respond: (success: boolean) => void) => void;
|
|
11
|
+
export declare class BdiClient extends EventEmitter {
|
|
12
|
+
private socket;
|
|
13
|
+
private host;
|
|
14
|
+
private port;
|
|
15
|
+
private buffer;
|
|
16
|
+
private autoReconnect;
|
|
17
|
+
private reconnectInterval;
|
|
18
|
+
private actionHandlers;
|
|
19
|
+
private project?;
|
|
20
|
+
private binPath;
|
|
21
|
+
private process;
|
|
22
|
+
constructor(options?: BdiClientOptions);
|
|
23
|
+
connect(): Promise<void>;
|
|
24
|
+
private setupSocketEvents;
|
|
25
|
+
private handleIncomingLine;
|
|
26
|
+
private parseAction;
|
|
27
|
+
private cleanArg;
|
|
28
|
+
sendMsg(performative: string, sender: string, receiver: string, content: string): void;
|
|
29
|
+
sendPerception(action: 'add' | 'remove', perception: string): void;
|
|
30
|
+
registerAction(actionName: string, callback: ActionCallback): void;
|
|
31
|
+
private sendActionResult;
|
|
32
|
+
close(): void;
|
|
33
|
+
}
|
|
34
|
+
export { BdiClient as Panteao, BdiClient as Panteão };
|
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.Panteão = exports.Panteao = exports.BdiClient = void 0;
|
|
37
|
+
const net = __importStar(require("net"));
|
|
38
|
+
const events_1 = require("events");
|
|
39
|
+
const child_process = __importStar(require("child_process"));
|
|
40
|
+
const path = __importStar(require("path"));
|
|
41
|
+
const fs = __importStar(require("fs"));
|
|
42
|
+
function getFreePort() {
|
|
43
|
+
return new Promise((resolve, reject) => {
|
|
44
|
+
const server = net.createServer();
|
|
45
|
+
server.listen(0, '127.0.0.1', () => {
|
|
46
|
+
const address = server.address();
|
|
47
|
+
const port = typeof address === 'string' ? 0 : address?.port;
|
|
48
|
+
server.close(() => {
|
|
49
|
+
if (port)
|
|
50
|
+
resolve(port);
|
|
51
|
+
else
|
|
52
|
+
reject(new Error('Failed to allocate port'));
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
server.on('error', reject);
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
class BdiClient extends events_1.EventEmitter {
|
|
59
|
+
socket;
|
|
60
|
+
host;
|
|
61
|
+
port;
|
|
62
|
+
buffer = '';
|
|
63
|
+
autoReconnect;
|
|
64
|
+
reconnectInterval;
|
|
65
|
+
actionHandlers = new Map();
|
|
66
|
+
project;
|
|
67
|
+
binPath;
|
|
68
|
+
process = null;
|
|
69
|
+
constructor(options = {}) {
|
|
70
|
+
super();
|
|
71
|
+
this.host = options.host || '127.0.0.1';
|
|
72
|
+
this.port = options.port || 0;
|
|
73
|
+
this.project = options.project;
|
|
74
|
+
this.autoReconnect = options.project ? false : (options.autoReconnect ?? true);
|
|
75
|
+
this.reconnectInterval = options.reconnectInterval || 2000;
|
|
76
|
+
this.socket = new net.Socket();
|
|
77
|
+
let binPath = options.binPath;
|
|
78
|
+
if (!binPath) {
|
|
79
|
+
const isWin = process.platform === 'win32';
|
|
80
|
+
const binName = isWin ? 'panteao-engine.exe' : 'panteao-engine';
|
|
81
|
+
// Try to resolve from platform-specific package first
|
|
82
|
+
const platformPkg = `@panteao/engine-${process.platform}-${process.arch}`;
|
|
83
|
+
let resolvedPath = null;
|
|
84
|
+
try {
|
|
85
|
+
const pkgPath = require.resolve(path.join(platformPkg, 'package.json'));
|
|
86
|
+
const pkgDir = path.dirname(pkgPath);
|
|
87
|
+
const candidate = path.join(pkgDir, 'bin', binName);
|
|
88
|
+
const candidateFallback = path.join(pkgDir, binName);
|
|
89
|
+
if (fs.existsSync(candidate)) {
|
|
90
|
+
resolvedPath = candidate;
|
|
91
|
+
}
|
|
92
|
+
else if (fs.existsSync(candidateFallback)) {
|
|
93
|
+
resolvedPath = candidateFallback;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
catch (e) {
|
|
97
|
+
// Platform package not installed or resolved
|
|
98
|
+
}
|
|
99
|
+
if (resolvedPath) {
|
|
100
|
+
binPath = resolvedPath;
|
|
101
|
+
}
|
|
102
|
+
else {
|
|
103
|
+
// Fallback to local paths
|
|
104
|
+
const candidate1 = path.join(__dirname, binName);
|
|
105
|
+
const candidate2 = path.join(__dirname, 'bin', binName);
|
|
106
|
+
if (fs.existsSync(candidate1)) {
|
|
107
|
+
binPath = candidate1;
|
|
108
|
+
}
|
|
109
|
+
else if (fs.existsSync(candidate2)) {
|
|
110
|
+
binPath = candidate2;
|
|
111
|
+
}
|
|
112
|
+
else {
|
|
113
|
+
binPath = binName;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
this.binPath = binPath;
|
|
118
|
+
}
|
|
119
|
+
async connect() {
|
|
120
|
+
if (this.project) {
|
|
121
|
+
if (this.port === 0) {
|
|
122
|
+
this.port = await getFreePort();
|
|
123
|
+
}
|
|
124
|
+
const args = [this.project, '--port', String(this.port)];
|
|
125
|
+
this.process = child_process.spawn(this.binPath, args, { stdio: 'ignore' });
|
|
126
|
+
await new Promise((resolve) => setTimeout(resolve, 800));
|
|
127
|
+
}
|
|
128
|
+
else if (this.port === 0) {
|
|
129
|
+
this.port = 44444;
|
|
130
|
+
}
|
|
131
|
+
return new Promise((resolve, reject) => {
|
|
132
|
+
let handshakeBuffer = '';
|
|
133
|
+
const onConnect = () => {
|
|
134
|
+
const onData = (data) => {
|
|
135
|
+
handshakeBuffer += data.toString('utf8');
|
|
136
|
+
const lines = handshakeBuffer.split('\n');
|
|
137
|
+
handshakeBuffer = lines.pop() || '';
|
|
138
|
+
for (const line of lines) {
|
|
139
|
+
try {
|
|
140
|
+
if (!line.trim())
|
|
141
|
+
continue;
|
|
142
|
+
const msg = JSON.parse(line);
|
|
143
|
+
if (msg.type === 'mas_ready') {
|
|
144
|
+
this.socket.off('data', onData);
|
|
145
|
+
this.socket.off('error', onError);
|
|
146
|
+
this.setupSocketEvents();
|
|
147
|
+
this.emit('connect');
|
|
148
|
+
resolve();
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
catch (e) { }
|
|
153
|
+
}
|
|
154
|
+
};
|
|
155
|
+
const onError = (err) => {
|
|
156
|
+
this.socket.off('data', onData);
|
|
157
|
+
if (this.process) {
|
|
158
|
+
this.process.kill();
|
|
159
|
+
this.process = null;
|
|
160
|
+
}
|
|
161
|
+
reject(err);
|
|
162
|
+
};
|
|
163
|
+
this.socket.on('data', onData);
|
|
164
|
+
this.socket.once('error', onError);
|
|
165
|
+
};
|
|
166
|
+
const onInitialError = (err) => {
|
|
167
|
+
if (this.process) {
|
|
168
|
+
this.process.kill();
|
|
169
|
+
this.process = null;
|
|
170
|
+
}
|
|
171
|
+
reject(err);
|
|
172
|
+
};
|
|
173
|
+
this.socket.once('error', onInitialError);
|
|
174
|
+
this.socket.connect(this.port, this.host, () => {
|
|
175
|
+
this.socket.off('error', onInitialError);
|
|
176
|
+
onConnect();
|
|
177
|
+
});
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
setupSocketEvents() {
|
|
181
|
+
this.socket.on('data', (data) => {
|
|
182
|
+
this.buffer += data.toString('utf8');
|
|
183
|
+
const lines = this.buffer.split('\n');
|
|
184
|
+
this.buffer = lines.pop() || '';
|
|
185
|
+
for (const line of lines) {
|
|
186
|
+
if (line.trim().length === 0)
|
|
187
|
+
continue;
|
|
188
|
+
this.handleIncomingLine(line);
|
|
189
|
+
}
|
|
190
|
+
});
|
|
191
|
+
this.socket.on('close', () => {
|
|
192
|
+
this.emit('disconnect');
|
|
193
|
+
if (this.autoReconnect) {
|
|
194
|
+
setTimeout(() => {
|
|
195
|
+
this.connect().catch(() => { });
|
|
196
|
+
}, this.reconnectInterval);
|
|
197
|
+
}
|
|
198
|
+
});
|
|
199
|
+
this.socket.on('error', (err) => {
|
|
200
|
+
this.emit('error', err);
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
handleIncomingLine(line) {
|
|
204
|
+
try {
|
|
205
|
+
const msg = JSON.parse(line);
|
|
206
|
+
if (msg.type === 'action') {
|
|
207
|
+
const rawAction = msg.action;
|
|
208
|
+
const { name, args } = this.parseAction(rawAction);
|
|
209
|
+
const handler = this.actionHandlers.get(name);
|
|
210
|
+
if (handler) {
|
|
211
|
+
const respond = (success) => {
|
|
212
|
+
this.sendActionResult(msg.id, success);
|
|
213
|
+
};
|
|
214
|
+
handler(args, respond);
|
|
215
|
+
}
|
|
216
|
+
else {
|
|
217
|
+
this.sendActionResult(msg.id, true);
|
|
218
|
+
}
|
|
219
|
+
this.emit('action', { name, args, agent: msg.agent, id: msg.id });
|
|
220
|
+
}
|
|
221
|
+
else if (msg.type === 'message') {
|
|
222
|
+
const { performative, sender, receiver, content } = msg;
|
|
223
|
+
this.emit('message', performative, sender, receiver, content);
|
|
224
|
+
this.emit(performative, sender, receiver, content);
|
|
225
|
+
// Compatibility: parse message content as an action
|
|
226
|
+
if (content && typeof content === 'string') {
|
|
227
|
+
const parsed = this.parseAction(content);
|
|
228
|
+
if (parsed && parsed.name) {
|
|
229
|
+
const handler = this.actionHandlers.get(parsed.name);
|
|
230
|
+
if (handler) {
|
|
231
|
+
const dummyRespond = () => { };
|
|
232
|
+
handler(parsed.args, dummyRespond);
|
|
233
|
+
}
|
|
234
|
+
this.emit('action', { name: parsed.name, args: parsed.args, agent: sender, id: null });
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
catch (e) {
|
|
240
|
+
this.emit('error', e);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
parseAction(actionStr) {
|
|
244
|
+
const parenIdx = actionStr.indexOf('(');
|
|
245
|
+
if (parenIdx === -1) {
|
|
246
|
+
return { name: actionStr.trim(), args: [] };
|
|
247
|
+
}
|
|
248
|
+
const name = actionStr.substring(0, parenIdx).trim();
|
|
249
|
+
const argsStr = actionStr.substring(parenIdx + 1, actionStr.lastIndexOf(')'));
|
|
250
|
+
const args = [];
|
|
251
|
+
let current = '';
|
|
252
|
+
let insideQuotes = false;
|
|
253
|
+
let depthBrackets = 0;
|
|
254
|
+
let depthParens = 0;
|
|
255
|
+
for (let i = 0; i < argsStr.length; i++) {
|
|
256
|
+
const char = argsStr[i];
|
|
257
|
+
if (char === '"') {
|
|
258
|
+
insideQuotes = !insideQuotes;
|
|
259
|
+
current += char;
|
|
260
|
+
}
|
|
261
|
+
else if (!insideQuotes && char === '[') {
|
|
262
|
+
depthBrackets++;
|
|
263
|
+
current += char;
|
|
264
|
+
}
|
|
265
|
+
else if (!insideQuotes && char === ']') {
|
|
266
|
+
depthBrackets--;
|
|
267
|
+
current += char;
|
|
268
|
+
}
|
|
269
|
+
else if (!insideQuotes && char === '(') {
|
|
270
|
+
depthParens++;
|
|
271
|
+
current += char;
|
|
272
|
+
}
|
|
273
|
+
else if (!insideQuotes && char === ')') {
|
|
274
|
+
depthParens--;
|
|
275
|
+
current += char;
|
|
276
|
+
}
|
|
277
|
+
else if (char === ',' && !insideQuotes && depthBrackets === 0 && depthParens === 0) {
|
|
278
|
+
args.push(this.cleanArg(current));
|
|
279
|
+
current = '';
|
|
280
|
+
}
|
|
281
|
+
else {
|
|
282
|
+
current += char;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
if (current.trim().length > 0) {
|
|
286
|
+
args.push(this.cleanArg(current));
|
|
287
|
+
}
|
|
288
|
+
return { name, args };
|
|
289
|
+
}
|
|
290
|
+
cleanArg(arg) {
|
|
291
|
+
const s = arg.trim();
|
|
292
|
+
if (s.startsWith('"') && s.endsWith('"') && s.length >= 2) {
|
|
293
|
+
return s.substring(1, s.length - 1);
|
|
294
|
+
}
|
|
295
|
+
return s;
|
|
296
|
+
}
|
|
297
|
+
sendMsg(performative, sender, receiver, content) {
|
|
298
|
+
const payload = JSON.stringify({ type: 'message', performative, sender, receiver, content }) + '\n';
|
|
299
|
+
this.socket.write(payload);
|
|
300
|
+
}
|
|
301
|
+
sendPerception(action, perception) {
|
|
302
|
+
const payload = JSON.stringify({ type: 'perception', action, perception }) + '\n';
|
|
303
|
+
this.socket.write(payload);
|
|
304
|
+
}
|
|
305
|
+
registerAction(actionName, callback) {
|
|
306
|
+
this.actionHandlers.set(actionName, callback);
|
|
307
|
+
}
|
|
308
|
+
sendActionResult(id, success) {
|
|
309
|
+
const payload = JSON.stringify({ type: 'action_result', id, success }) + '\n';
|
|
310
|
+
this.socket.write(payload);
|
|
311
|
+
}
|
|
312
|
+
close() {
|
|
313
|
+
this.autoReconnect = false;
|
|
314
|
+
this.socket.destroy();
|
|
315
|
+
if (this.process) {
|
|
316
|
+
this.process.kill();
|
|
317
|
+
this.process = null;
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
exports.BdiClient = BdiClient;
|
|
322
|
+
exports.Panteao = BdiClient;
|
|
323
|
+
exports.Panteão = BdiClient;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"type": "module"}
|
package/package.json
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "panteao-ts",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "TypeScript client for the Panteao BDI agent engine",
|
|
5
|
+
"main": "./dist/cjs/index.js",
|
|
6
|
+
"module": "./dist/esm/index.js",
|
|
7
|
+
"types": "./dist/cjs/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/cjs/index.d.ts",
|
|
11
|
+
"import": "./dist/esm/index.js",
|
|
12
|
+
"require": "./dist/cjs/index.js"
|
|
13
|
+
},
|
|
14
|
+
"./types": {
|
|
15
|
+
"types": "./dist/cjs/index.d.ts",
|
|
16
|
+
"import": "./dist/esm/index.js",
|
|
17
|
+
"require": "./dist/cjs/index.js"
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
"scripts": {
|
|
21
|
+
"build": "tsc && tsc -p tsconfig.cjs.json && echo '{\"type\": \"module\"}' > dist/esm/package.json && echo '{\"type\": \"commonjs\"}' > dist/cjs/package.json"
|
|
22
|
+
},
|
|
23
|
+
"keywords": ["bdi", "agents", "jason", "ai", "multi-agent", "typescript"],
|
|
24
|
+
"license": "MIT",
|
|
25
|
+
"devDependencies": {
|
|
26
|
+
"typescript": "^5.0.0",
|
|
27
|
+
"@types/node": "^20.0.0"
|
|
28
|
+
},
|
|
29
|
+
"optionalDependencies": {
|
|
30
|
+
"@panteao/engine-win32-x64": "^1.0.0",
|
|
31
|
+
"@panteao/engine-linux-x64": "^1.0.0",
|
|
32
|
+
"@panteao/engine-darwin-arm64": "^1.0.0",
|
|
33
|
+
"@panteao/engine-darwin-x64": "^1.0.0"
|
|
34
|
+
}
|
|
35
|
+
}
|
|
Binary file
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
import * as net from 'net';
|
|
2
|
+
import { EventEmitter } from 'events';
|
|
3
|
+
import * as child_process from 'child_process';
|
|
4
|
+
import * as path from 'path';
|
|
5
|
+
import * as fs from 'fs';
|
|
6
|
+
|
|
7
|
+
export interface BdiClientOptions {
|
|
8
|
+
host?: string;
|
|
9
|
+
port?: number;
|
|
10
|
+
autoReconnect?: boolean;
|
|
11
|
+
reconnectInterval?: number;
|
|
12
|
+
project?: string;
|
|
13
|
+
binPath?: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export type ActionCallback = (args: string[], respond: (success: boolean) => void) => void;
|
|
17
|
+
|
|
18
|
+
function getFreePort(): Promise<number> {
|
|
19
|
+
return new Promise((resolve, reject) => {
|
|
20
|
+
const server = net.createServer();
|
|
21
|
+
server.listen(0, '127.0.0.1', () => {
|
|
22
|
+
const address = server.address();
|
|
23
|
+
const port = typeof address === 'string' ? 0 : address?.port;
|
|
24
|
+
server.close(() => {
|
|
25
|
+
if (port) resolve(port);
|
|
26
|
+
else reject(new Error('Failed to allocate port'));
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
server.on('error', reject);
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export class BdiClient extends EventEmitter {
|
|
34
|
+
private socket: net.Socket;
|
|
35
|
+
private host: string;
|
|
36
|
+
private port: number;
|
|
37
|
+
private buffer: string = '';
|
|
38
|
+
private autoReconnect: boolean;
|
|
39
|
+
private reconnectInterval: number;
|
|
40
|
+
private actionHandlers: Map<string, ActionCallback> = new Map();
|
|
41
|
+
private project?: string;
|
|
42
|
+
private binPath: string;
|
|
43
|
+
private process: child_process.ChildProcess | null = null;
|
|
44
|
+
|
|
45
|
+
constructor(options: BdiClientOptions = {}) {
|
|
46
|
+
super();
|
|
47
|
+
this.host = options.host || '127.0.0.1';
|
|
48
|
+
this.port = options.port || 0;
|
|
49
|
+
this.project = options.project;
|
|
50
|
+
this.autoReconnect = options.project ? false : (options.autoReconnect ?? true);
|
|
51
|
+
this.reconnectInterval = options.reconnectInterval || 2000;
|
|
52
|
+
this.socket = new net.Socket();
|
|
53
|
+
|
|
54
|
+
let binPath = options.binPath;
|
|
55
|
+
if (!binPath) {
|
|
56
|
+
const isWin = process.platform === 'win32';
|
|
57
|
+
const binName = isWin ? 'panteao-engine.exe' : 'panteao-engine';
|
|
58
|
+
|
|
59
|
+
// Try to resolve from platform-specific package first
|
|
60
|
+
const platformPkg = `@panteao/engine-${process.platform}-${process.arch}`;
|
|
61
|
+
let resolvedPath: string | null = null;
|
|
62
|
+
try {
|
|
63
|
+
const pkgPath = require.resolve(path.join(platformPkg, 'package.json'));
|
|
64
|
+
const pkgDir = path.dirname(pkgPath);
|
|
65
|
+
const candidate = path.join(pkgDir, 'bin', binName);
|
|
66
|
+
const candidateFallback = path.join(pkgDir, binName);
|
|
67
|
+
if (fs.existsSync(candidate)) {
|
|
68
|
+
resolvedPath = candidate;
|
|
69
|
+
} else if (fs.existsSync(candidateFallback)) {
|
|
70
|
+
resolvedPath = candidateFallback;
|
|
71
|
+
}
|
|
72
|
+
} catch (e) {
|
|
73
|
+
// Platform package not installed or resolved
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (resolvedPath) {
|
|
77
|
+
binPath = resolvedPath;
|
|
78
|
+
} else {
|
|
79
|
+
// Fallback to local paths
|
|
80
|
+
const candidate1 = path.join(__dirname, binName);
|
|
81
|
+
const candidate2 = path.join(__dirname, 'bin', binName);
|
|
82
|
+
if (fs.existsSync(candidate1)) {
|
|
83
|
+
binPath = candidate1;
|
|
84
|
+
} else if (fs.existsSync(candidate2)) {
|
|
85
|
+
binPath = candidate2;
|
|
86
|
+
} else {
|
|
87
|
+
binPath = binName;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
this.binPath = binPath;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
public async connect(): Promise<void> {
|
|
95
|
+
if (this.project) {
|
|
96
|
+
if (this.port === 0) {
|
|
97
|
+
this.port = await getFreePort();
|
|
98
|
+
}
|
|
99
|
+
const args = [this.project, '--port', String(this.port)];
|
|
100
|
+
this.process = child_process.spawn(this.binPath, args, { stdio: 'ignore' });
|
|
101
|
+
await new Promise((resolve) => setTimeout(resolve, 800));
|
|
102
|
+
} else if (this.port === 0) {
|
|
103
|
+
this.port = 44444;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return new Promise((resolve, reject) => {
|
|
107
|
+
let handshakeBuffer = '';
|
|
108
|
+
const onConnect = () => {
|
|
109
|
+
const onData = (data: Buffer) => {
|
|
110
|
+
handshakeBuffer += data.toString('utf8');
|
|
111
|
+
const lines = handshakeBuffer.split('\n');
|
|
112
|
+
handshakeBuffer = lines.pop() || '';
|
|
113
|
+
for (const line of lines) {
|
|
114
|
+
try {
|
|
115
|
+
if (!line.trim()) continue;
|
|
116
|
+
const msg = JSON.parse(line);
|
|
117
|
+
if (msg.type === 'mas_ready') {
|
|
118
|
+
this.socket.off('data', onData);
|
|
119
|
+
this.socket.off('error', onError);
|
|
120
|
+
this.setupSocketEvents();
|
|
121
|
+
this.emit('connect');
|
|
122
|
+
resolve();
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
} catch (e) {}
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
const onError = (err: Error) => {
|
|
129
|
+
this.socket.off('data', onData);
|
|
130
|
+
if (this.process) {
|
|
131
|
+
this.process.kill();
|
|
132
|
+
this.process = null;
|
|
133
|
+
}
|
|
134
|
+
reject(err);
|
|
135
|
+
};
|
|
136
|
+
this.socket.on('data', onData);
|
|
137
|
+
this.socket.once('error', onError);
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
const onInitialError = (err: Error) => {
|
|
141
|
+
if (this.process) {
|
|
142
|
+
this.process.kill();
|
|
143
|
+
this.process = null;
|
|
144
|
+
}
|
|
145
|
+
reject(err);
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
this.socket.once('error', onInitialError);
|
|
149
|
+
this.socket.connect(this.port, this.host, () => {
|
|
150
|
+
this.socket.off('error', onInitialError);
|
|
151
|
+
onConnect();
|
|
152
|
+
});
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
private setupSocketEvents(): void {
|
|
157
|
+
this.socket.on('data', (data: Buffer) => {
|
|
158
|
+
this.buffer += data.toString('utf8');
|
|
159
|
+
const lines = this.buffer.split('\n');
|
|
160
|
+
this.buffer = lines.pop() || '';
|
|
161
|
+
|
|
162
|
+
for (const line of lines) {
|
|
163
|
+
if (line.trim().length === 0) continue;
|
|
164
|
+
this.handleIncomingLine(line);
|
|
165
|
+
}
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
this.socket.on('close', () => {
|
|
169
|
+
this.emit('disconnect');
|
|
170
|
+
if (this.autoReconnect) {
|
|
171
|
+
setTimeout(() => {
|
|
172
|
+
this.connect().catch(() => {});
|
|
173
|
+
}, this.reconnectInterval);
|
|
174
|
+
}
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
this.socket.on('error', (err: Error) => {
|
|
178
|
+
this.emit('error', err);
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
private handleIncomingLine(line: string): void {
|
|
183
|
+
try {
|
|
184
|
+
const msg = JSON.parse(line);
|
|
185
|
+
if (msg.type === 'action') {
|
|
186
|
+
const rawAction: string = msg.action;
|
|
187
|
+
const { name, args } = this.parseAction(rawAction);
|
|
188
|
+
const handler = this.actionHandlers.get(name);
|
|
189
|
+
|
|
190
|
+
if (handler) {
|
|
191
|
+
const respond = (success: boolean) => {
|
|
192
|
+
this.sendActionResult(msg.id, success);
|
|
193
|
+
};
|
|
194
|
+
handler(args, respond);
|
|
195
|
+
} else {
|
|
196
|
+
this.sendActionResult(msg.id, true);
|
|
197
|
+
}
|
|
198
|
+
this.emit('action', { name, args, agent: msg.agent, id: msg.id });
|
|
199
|
+
} else if (msg.type === 'message') {
|
|
200
|
+
const { performative, sender, receiver, content } = msg;
|
|
201
|
+
this.emit('message', performative, sender, receiver, content);
|
|
202
|
+
this.emit(performative, sender, receiver, content);
|
|
203
|
+
|
|
204
|
+
// Compatibility: parse message content as an action
|
|
205
|
+
if (content && typeof content === 'string') {
|
|
206
|
+
const parsed = this.parseAction(content);
|
|
207
|
+
if (parsed && parsed.name) {
|
|
208
|
+
const handler = this.actionHandlers.get(parsed.name);
|
|
209
|
+
if (handler) {
|
|
210
|
+
const dummyRespond = () => {};
|
|
211
|
+
handler(parsed.args, dummyRespond);
|
|
212
|
+
}
|
|
213
|
+
this.emit('action', { name: parsed.name, args: parsed.args, agent: sender, id: null });
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
} catch (e) {
|
|
218
|
+
this.emit('error', e);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
private parseAction(actionStr: string): { name: string; args: string[] } {
|
|
223
|
+
const parenIdx = actionStr.indexOf('(');
|
|
224
|
+
if (parenIdx === -1) {
|
|
225
|
+
return { name: actionStr.trim(), args: [] };
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
const name = actionStr.substring(0, parenIdx).trim();
|
|
229
|
+
const argsStr = actionStr.substring(parenIdx + 1, actionStr.lastIndexOf(')'));
|
|
230
|
+
|
|
231
|
+
const args: string[] = [];
|
|
232
|
+
let current = '';
|
|
233
|
+
let insideQuotes = false;
|
|
234
|
+
let depthBrackets = 0;
|
|
235
|
+
let depthParens = 0;
|
|
236
|
+
|
|
237
|
+
for (let i = 0; i < argsStr.length; i++) {
|
|
238
|
+
const char = argsStr[i];
|
|
239
|
+
if (char === '"') {
|
|
240
|
+
insideQuotes = !insideQuotes;
|
|
241
|
+
current += char;
|
|
242
|
+
} else if (!insideQuotes && char === '[') {
|
|
243
|
+
depthBrackets++;
|
|
244
|
+
current += char;
|
|
245
|
+
} else if (!insideQuotes && char === ']') {
|
|
246
|
+
depthBrackets--;
|
|
247
|
+
current += char;
|
|
248
|
+
} else if (!insideQuotes && char === '(') {
|
|
249
|
+
depthParens++;
|
|
250
|
+
current += char;
|
|
251
|
+
} else if (!insideQuotes && char === ')') {
|
|
252
|
+
depthParens--;
|
|
253
|
+
current += char;
|
|
254
|
+
} else if (char === ',' && !insideQuotes && depthBrackets === 0 && depthParens === 0) {
|
|
255
|
+
args.push(this.cleanArg(current));
|
|
256
|
+
current = '';
|
|
257
|
+
} else {
|
|
258
|
+
current += char;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
if (current.trim().length > 0) {
|
|
262
|
+
args.push(this.cleanArg(current));
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
return { name, args };
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
private cleanArg(arg: string): string {
|
|
269
|
+
const s = arg.trim();
|
|
270
|
+
if (s.startsWith('"') && s.endsWith('"') && s.length >= 2) {
|
|
271
|
+
return s.substring(1, s.length - 1);
|
|
272
|
+
}
|
|
273
|
+
return s;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
public sendMsg(performative: string, sender: string, receiver: string, content: string): void {
|
|
277
|
+
const payload = JSON.stringify({ type: 'message', performative, sender, receiver, content }) + '\n';
|
|
278
|
+
this.socket.write(payload);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
public sendPerception(action: 'add' | 'remove', perception: string): void {
|
|
282
|
+
const payload = JSON.stringify({ type: 'perception', action, perception }) + '\n';
|
|
283
|
+
this.socket.write(payload);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
public registerAction(actionName: string, callback: ActionCallback): void {
|
|
287
|
+
this.actionHandlers.set(actionName, callback);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
private sendActionResult(id: string, success: boolean): void {
|
|
291
|
+
const payload = JSON.stringify({ type: 'action_result', id, success }) + '\n';
|
|
292
|
+
this.socket.write(payload);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
public close(): void {
|
|
296
|
+
this.autoReconnect = false;
|
|
297
|
+
this.socket.destroy();
|
|
298
|
+
if (this.process) {
|
|
299
|
+
this.process.kill();
|
|
300
|
+
this.process = null;
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
export { BdiClient as Panteao, BdiClient as Panteão };
|
|
306
|
+
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "NodeNext",
|
|
5
|
+
"moduleResolution": "NodeNext",
|
|
6
|
+
"declaration": true,
|
|
7
|
+
"outDir": "./dist/esm",
|
|
8
|
+
"strict": true,
|
|
9
|
+
"esModuleInterop": true,
|
|
10
|
+
"skipLibCheck": true
|
|
11
|
+
},
|
|
12
|
+
"include": ["src/**/*"]
|
|
13
|
+
}
|