dank-ai 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/README.md +1331 -0
- package/bin/dank +118 -0
- package/docker/Dockerfile +57 -0
- package/docker/entrypoint.js +1227 -0
- package/docker/package.json +19 -0
- package/lib/agent.js +644 -0
- package/lib/cli/build.js +43 -0
- package/lib/cli/clean.js +30 -0
- package/lib/cli/init.js +38 -0
- package/lib/cli/logs.js +122 -0
- package/lib/cli/run.js +176 -0
- package/lib/cli/status.js +125 -0
- package/lib/cli/stop.js +87 -0
- package/lib/config.js +180 -0
- package/lib/constants.js +58 -0
- package/lib/docker/manager.js +968 -0
- package/lib/index.js +26 -0
- package/lib/project.js +280 -0
- package/lib/tools/builtin.js +445 -0
- package/lib/tools/index.js +335 -0
- package/package.json +52 -0
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "dank-agent-runtime",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Runtime dependencies for Dank agents",
|
|
5
|
+
"private": true,
|
|
6
|
+
"dependencies": {
|
|
7
|
+
"axios": "^1.5.0",
|
|
8
|
+
"express": "^4.18.2",
|
|
9
|
+
"winston": "^3.10.0",
|
|
10
|
+
"dotenv": "^16.3.1",
|
|
11
|
+
"lodash": "^4.17.21",
|
|
12
|
+
"uuid": "^9.0.0",
|
|
13
|
+
"moment": "^2.29.4",
|
|
14
|
+
"openai": "^4.0.0",
|
|
15
|
+
"@anthropic-ai/sdk": "^0.6.0",
|
|
16
|
+
"cohere-ai": "^7.0.0",
|
|
17
|
+
"express-rate-limit": "^6.8.1"
|
|
18
|
+
}
|
|
19
|
+
}
|
package/lib/agent.js
ADDED
|
@@ -0,0 +1,644 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DankAgent - Agent Definition Class
|
|
3
|
+
*
|
|
4
|
+
* This class represents a single AI agent with its configuration,
|
|
5
|
+
* handlers, and runtime requirements.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const Joi = require('joi');
|
|
9
|
+
const { DEFAULT_CONFIG, SUPPORTED_LLMS, DOCKER_CONFIG } = require('./constants');
|
|
10
|
+
const { ToolRegistry, ToolExecutor } = require('./tools');
|
|
11
|
+
const builtinTools = require('./tools/builtin');
|
|
12
|
+
|
|
13
|
+
class DankAgent {
|
|
14
|
+
constructor(name, config = {}) {
|
|
15
|
+
this.name = name;
|
|
16
|
+
this.config = this._validateConfig(config);
|
|
17
|
+
this.handlers = new Map();
|
|
18
|
+
this.id = this._generateId();
|
|
19
|
+
this.status = 'defined'; // defined, building, running, stopped, error
|
|
20
|
+
this.containerId = null;
|
|
21
|
+
this.createdAt = new Date().toISOString();
|
|
22
|
+
|
|
23
|
+
// Initialize tool system
|
|
24
|
+
this.toolRegistry = new ToolRegistry();
|
|
25
|
+
this.toolExecutor = new ToolExecutor(this.toolRegistry);
|
|
26
|
+
|
|
27
|
+
// Register built-in tools if enabled
|
|
28
|
+
if (config.enableBuiltinTools !== false) {
|
|
29
|
+
this.registerBuiltinTools();
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Set the LLM configuration for this agent
|
|
35
|
+
*/
|
|
36
|
+
setLLM(provider, config) {
|
|
37
|
+
if (!SUPPORTED_LLMS.includes(provider)) {
|
|
38
|
+
throw new Error(`Unsupported LLM provider: ${provider}. Supported: ${SUPPORTED_LLMS.join(', ')}`);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
this.config.llm = {
|
|
42
|
+
provider,
|
|
43
|
+
...config
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
return this;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Set the system prompt for the agent
|
|
51
|
+
*/
|
|
52
|
+
setPrompt(prompt) {
|
|
53
|
+
if (typeof prompt !== 'string' || prompt.trim().length === 0) {
|
|
54
|
+
throw new Error('Prompt must be a non-empty string');
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
this.config.prompt = prompt.trim();
|
|
58
|
+
return this;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Set resource limits for the container
|
|
63
|
+
*/
|
|
64
|
+
setResources(resources) {
|
|
65
|
+
const schema = Joi.object({
|
|
66
|
+
memory: Joi.string().pattern(/^\d+[mMgG]$/).default('512m'),
|
|
67
|
+
cpu: Joi.number().min(0.1).max(8).default(1),
|
|
68
|
+
timeout: Joi.number().min(1000).default(30000)
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
const { error, value } = schema.validate(resources);
|
|
72
|
+
if (error) {
|
|
73
|
+
throw new Error(`Invalid resource configuration: ${error.message}`);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
this.config.resources = { ...this.config.resources, ...value };
|
|
77
|
+
return this;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Set Docker configuration including base image
|
|
82
|
+
*/
|
|
83
|
+
setDocker(dockerConfig) {
|
|
84
|
+
const schema = Joi.object({
|
|
85
|
+
baseImage: Joi.string().required()
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
const { error, value } = schema.validate(dockerConfig);
|
|
89
|
+
if (error) {
|
|
90
|
+
throw new Error(`Invalid Docker configuration: ${error.message}`);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
this.config.docker = { ...this.config.docker, ...value };
|
|
94
|
+
return this;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Set the base Docker image tag for this agent
|
|
99
|
+
* The tag will be appended to the base image prefix (e.g., "nodejs-20" becomes "dank-agent-base:nodejs-20")
|
|
100
|
+
*/
|
|
101
|
+
setBaseImage(tag) {
|
|
102
|
+
if (typeof tag !== 'string' || tag.trim().length === 0) {
|
|
103
|
+
throw new Error('Base image tag must be a non-empty string');
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const cleanTag = tag.trim();
|
|
107
|
+
const fullImageName = `${DOCKER_CONFIG.baseImagePrefix}:${cleanTag}`;
|
|
108
|
+
|
|
109
|
+
this.config.docker = { ...this.config.docker, baseImage: fullImageName };
|
|
110
|
+
return this;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Set the main port that the agent will expose
|
|
115
|
+
*/
|
|
116
|
+
setPort(port) {
|
|
117
|
+
if (typeof port !== 'number' || port < 1000 || port > 65535) {
|
|
118
|
+
throw new Error('Port must be a number between 1000 and 65535');
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
this.config.docker = { ...this.config.docker, port };
|
|
122
|
+
return this;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Enable or configure direct prompting communication
|
|
127
|
+
*/
|
|
128
|
+
enableDirectPrompting(options = {}) {
|
|
129
|
+
const schema = Joi.object({
|
|
130
|
+
protocol: Joi.string().valid('websocket', 'tcp', 'http').default('websocket'),
|
|
131
|
+
authentication: Joi.boolean().default(false),
|
|
132
|
+
maxConnections: Joi.number().min(1).max(1000).default(100),
|
|
133
|
+
timeout: Joi.number().min(1000).default(30000)
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
const { error, value } = schema.validate(options);
|
|
137
|
+
if (error) {
|
|
138
|
+
throw new Error(`Invalid direct prompting configuration: ${error.message}`);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
this.config.communication = {
|
|
142
|
+
...this.config.communication,
|
|
143
|
+
directPrompting: {
|
|
144
|
+
enabled: true,
|
|
145
|
+
...value
|
|
146
|
+
}
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
return this;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Disable direct prompting communication
|
|
154
|
+
*/
|
|
155
|
+
disableDirectPrompting() {
|
|
156
|
+
this.config.communication = {
|
|
157
|
+
...this.config.communication,
|
|
158
|
+
directPrompting: {
|
|
159
|
+
...this.config.communication.directPrompting,
|
|
160
|
+
enabled: false
|
|
161
|
+
}
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
return this;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Enable HTTP server with Express.js
|
|
170
|
+
*/
|
|
171
|
+
enableHttp(options = {}) {
|
|
172
|
+
const schema = Joi.object({
|
|
173
|
+
port: Joi.number().min(1000).max(65535).default(3000),
|
|
174
|
+
host: Joi.string().default('0.0.0.0'),
|
|
175
|
+
cors: Joi.boolean().default(true),
|
|
176
|
+
rateLimit: Joi.object({
|
|
177
|
+
windowMs: Joi.number().default(15 * 60 * 1000), // 15 minutes
|
|
178
|
+
max: Joi.number().default(100), // limit each IP to 100 requests per windowMs
|
|
179
|
+
message: Joi.string().default('Too many requests')
|
|
180
|
+
}).default({}),
|
|
181
|
+
middleware: Joi.array().items(Joi.string()).default([]),
|
|
182
|
+
static: Joi.object({
|
|
183
|
+
enabled: Joi.boolean().default(false),
|
|
184
|
+
path: Joi.string().default('/public'),
|
|
185
|
+
directory: Joi.string().default('./public')
|
|
186
|
+
}).default({})
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
const { error, value } = schema.validate(options);
|
|
190
|
+
if (error) {
|
|
191
|
+
throw new Error(`Invalid HTTP configuration: ${error.message}`);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
this.config.http = {
|
|
195
|
+
enabled: true,
|
|
196
|
+
...value,
|
|
197
|
+
routes: new Map(),
|
|
198
|
+
middleware: []
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
return this;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Add HTTP route handler
|
|
206
|
+
*/
|
|
207
|
+
addRoute(method, path, handler, options = {}) {
|
|
208
|
+
if (!this.config.http || !this.config.http.enabled) {
|
|
209
|
+
throw new Error('HTTP server must be enabled before adding routes. Call enableHttp() first.');
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
if (typeof handler !== 'function') {
|
|
213
|
+
throw new Error('Route handler must be a function');
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
const validMethods = ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'HEAD', 'OPTIONS'];
|
|
217
|
+
const upperMethod = method.toUpperCase();
|
|
218
|
+
|
|
219
|
+
if (!validMethods.includes(upperMethod)) {
|
|
220
|
+
throw new Error(`Invalid HTTP method: ${method}. Valid methods: ${validMethods.join(', ')}`);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
const routeKey = `${upperMethod}:${path}`;
|
|
224
|
+
|
|
225
|
+
if (!this.config.http.routes.has(routeKey)) {
|
|
226
|
+
this.config.http.routes.set(routeKey, []);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
this.config.http.routes.get(routeKey).push({
|
|
230
|
+
method: upperMethod,
|
|
231
|
+
path,
|
|
232
|
+
handler,
|
|
233
|
+
options: {
|
|
234
|
+
auth: options.auth || false,
|
|
235
|
+
rateLimit: options.rateLimit || null,
|
|
236
|
+
validation: options.validation || null,
|
|
237
|
+
description: options.description || `${upperMethod} ${path}`
|
|
238
|
+
},
|
|
239
|
+
createdAt: new Date().toISOString()
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
return this;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Add HTTP middleware
|
|
247
|
+
*/
|
|
248
|
+
addMiddleware(middleware, options = {}) {
|
|
249
|
+
if (!this.config.http || !this.config.http.enabled) {
|
|
250
|
+
throw new Error('HTTP server must be enabled before adding middleware. Call enableHttp() first.');
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
if (typeof middleware !== 'function') {
|
|
254
|
+
throw new Error('Middleware must be a function');
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
this.config.http.middleware.push({
|
|
258
|
+
middleware,
|
|
259
|
+
options: {
|
|
260
|
+
path: options.path || '*',
|
|
261
|
+
priority: options.priority || 0,
|
|
262
|
+
description: options.description || 'Custom middleware'
|
|
263
|
+
},
|
|
264
|
+
createdAt: new Date().toISOString()
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
// Sort middleware by priority (higher priority first)
|
|
268
|
+
this.config.http.middleware.sort((a, b) => b.options.priority - a.options.priority);
|
|
269
|
+
|
|
270
|
+
return this;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Add common HTTP routes (convenience methods)
|
|
275
|
+
*/
|
|
276
|
+
get(path, handler, options) { return this.addRoute('GET', path, handler, options); }
|
|
277
|
+
post(path, handler, options) { return this.addRoute('POST', path, handler, options); }
|
|
278
|
+
put(path, handler, options) { return this.addRoute('PUT', path, handler, options); }
|
|
279
|
+
delete(path, handler, options) { return this.addRoute('DELETE', path, handler, options); }
|
|
280
|
+
patch(path, handler, options) { return this.addRoute('PATCH', path, handler, options); }
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* Add a tool that the agent can use
|
|
284
|
+
*/
|
|
285
|
+
addTool(name, definition) {
|
|
286
|
+
if (typeof name !== 'string' || name.trim().length === 0) {
|
|
287
|
+
throw new Error('Tool name must be a non-empty string');
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
this.toolRegistry.register(name, definition);
|
|
291
|
+
return this;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* Add multiple tools at once
|
|
296
|
+
*/
|
|
297
|
+
addTools(tools) {
|
|
298
|
+
Object.entries(tools).forEach(([name, definition]) => {
|
|
299
|
+
this.addTool(name, definition);
|
|
300
|
+
});
|
|
301
|
+
return this;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
/**
|
|
305
|
+
* Execute a tool with given parameters
|
|
306
|
+
*/
|
|
307
|
+
async useTool(toolName, parameters = {}, context = {}) {
|
|
308
|
+
const agentContext = {
|
|
309
|
+
...context,
|
|
310
|
+
agentId: this.id,
|
|
311
|
+
agentName: this.name,
|
|
312
|
+
timestamp: new Date().toISOString()
|
|
313
|
+
};
|
|
314
|
+
|
|
315
|
+
return await this.toolExecutor.execute(toolName, parameters, agentContext);
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/**
|
|
319
|
+
* Get available tools
|
|
320
|
+
*/
|
|
321
|
+
getTools() {
|
|
322
|
+
return this.toolRegistry.getAll();
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* Get tools by category
|
|
327
|
+
*/
|
|
328
|
+
getToolsByCategory(category) {
|
|
329
|
+
return this.toolRegistry.getByCategory(category);
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
/**
|
|
333
|
+
* Get tool execution statistics
|
|
334
|
+
*/
|
|
335
|
+
getToolStats() {
|
|
336
|
+
return this.toolExecutor.getStats();
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
/**
|
|
340
|
+
* Generate OpenAI function calling schema
|
|
341
|
+
*/
|
|
342
|
+
getOpenAIToolSchema() {
|
|
343
|
+
return this.toolRegistry.toOpenAISchema();
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
/**
|
|
347
|
+
* Register built-in tools
|
|
348
|
+
*/
|
|
349
|
+
registerBuiltinTools() {
|
|
350
|
+
Object.entries(builtinTools).forEach(([name, definition]) => {
|
|
351
|
+
this.toolRegistry.register(name, definition);
|
|
352
|
+
});
|
|
353
|
+
return this;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
/**
|
|
357
|
+
* Enable/disable specific built-in tools
|
|
358
|
+
*/
|
|
359
|
+
configureBuiltinTools(config) {
|
|
360
|
+
// Clear existing built-in tools
|
|
361
|
+
const allTools = this.toolRegistry.getAll();
|
|
362
|
+
const builtinToolNames = Object.keys(builtinTools);
|
|
363
|
+
|
|
364
|
+
builtinToolNames.forEach(name => {
|
|
365
|
+
if (this.toolRegistry.tools.has(name)) {
|
|
366
|
+
this.toolRegistry.tools.delete(name);
|
|
367
|
+
}
|
|
368
|
+
});
|
|
369
|
+
|
|
370
|
+
// Register only enabled tools
|
|
371
|
+
Object.entries(config).forEach(([toolName, enabled]) => {
|
|
372
|
+
if (enabled && builtinTools[toolName]) {
|
|
373
|
+
this.toolRegistry.register(toolName, builtinTools[toolName]);
|
|
374
|
+
}
|
|
375
|
+
});
|
|
376
|
+
|
|
377
|
+
return this;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
/**
|
|
381
|
+
* Add a handler function for specific events/outputs
|
|
382
|
+
*/
|
|
383
|
+
addHandler(eventType, handler) {
|
|
384
|
+
if (typeof handler !== 'function') {
|
|
385
|
+
throw new Error('Handler must be a function');
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
if (!this.handlers.has(eventType)) {
|
|
389
|
+
this.handlers.set(eventType, []);
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
this.handlers.get(eventType).push({
|
|
393
|
+
id: this._generateId(),
|
|
394
|
+
handler,
|
|
395
|
+
createdAt: new Date().toISOString()
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
return this;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
/**
|
|
402
|
+
* Add multiple handlers at once
|
|
403
|
+
*/
|
|
404
|
+
addHandlers(handlers) {
|
|
405
|
+
Object.entries(handlers).forEach(([eventType, handler]) => {
|
|
406
|
+
this.addHandler(eventType, handler);
|
|
407
|
+
});
|
|
408
|
+
return this;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
/**
|
|
412
|
+
* Auto-detect which communication features should be enabled based on actual usage
|
|
413
|
+
*/
|
|
414
|
+
_autoDetectFeatures() {
|
|
415
|
+
// Auto-detect event handlers
|
|
416
|
+
const hasHandlers = this.handlers.size > 0;
|
|
417
|
+
this.config.communication = {
|
|
418
|
+
...this.config.communication,
|
|
419
|
+
eventHandlers: {
|
|
420
|
+
enabled: hasHandlers
|
|
421
|
+
}
|
|
422
|
+
};
|
|
423
|
+
|
|
424
|
+
// Auto-detect direct prompting (requires both prompt and LLM)
|
|
425
|
+
const hasPrompt = this.config.prompt && this.config.prompt.trim().length > 0;
|
|
426
|
+
const hasLLM = this.config.llm && this.config.llm.provider;
|
|
427
|
+
const shouldEnableDirectPrompting = hasPrompt && hasLLM;
|
|
428
|
+
|
|
429
|
+
// Check if direct prompting was explicitly enabled
|
|
430
|
+
const currentDirectPrompting = this.config.communication?.directPrompting;
|
|
431
|
+
const explicitlyEnabled = currentDirectPrompting?.enabled === true;
|
|
432
|
+
|
|
433
|
+
if (explicitlyEnabled || shouldEnableDirectPrompting) {
|
|
434
|
+
// If explicitly enabled, keep it enabled regardless of prompt/LLM status
|
|
435
|
+
// If auto-detected, only enable if both prompt and LLM are present
|
|
436
|
+
const finalEnabled = explicitlyEnabled || shouldEnableDirectPrompting;
|
|
437
|
+
|
|
438
|
+
this.config.communication = {
|
|
439
|
+
...this.config.communication,
|
|
440
|
+
directPrompting: {
|
|
441
|
+
enabled: finalEnabled,
|
|
442
|
+
protocol: currentDirectPrompting?.protocol || 'websocket',
|
|
443
|
+
authentication: currentDirectPrompting?.authentication || false,
|
|
444
|
+
maxConnections: currentDirectPrompting?.maxConnections || 100,
|
|
445
|
+
timeout: currentDirectPrompting?.timeout || 30000
|
|
446
|
+
}
|
|
447
|
+
};
|
|
448
|
+
} else {
|
|
449
|
+
this.config.communication = {
|
|
450
|
+
...this.config.communication,
|
|
451
|
+
directPrompting: {
|
|
452
|
+
enabled: false
|
|
453
|
+
}
|
|
454
|
+
};
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
// Auto-detect HTTP API (check if routes were added)
|
|
458
|
+
const hasHttpRoutes = this.config.http && this.config.http.routes && this.config.http.routes.size > 0;
|
|
459
|
+
if (hasHttpRoutes) {
|
|
460
|
+
// HTTP was used, make sure it's enabled
|
|
461
|
+
this.config.http.enabled = true;
|
|
462
|
+
this.config.communication = {
|
|
463
|
+
...this.config.communication,
|
|
464
|
+
httpApi: {
|
|
465
|
+
enabled: true
|
|
466
|
+
}
|
|
467
|
+
};
|
|
468
|
+
} else {
|
|
469
|
+
// No HTTP routes, disable HTTP
|
|
470
|
+
if (this.config.http) {
|
|
471
|
+
this.config.http.enabled = false;
|
|
472
|
+
}
|
|
473
|
+
this.config.communication = {
|
|
474
|
+
...this.config.communication,
|
|
475
|
+
httpApi: {
|
|
476
|
+
enabled: false
|
|
477
|
+
}
|
|
478
|
+
};
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
/**
|
|
483
|
+
* Set environment variables for the agent
|
|
484
|
+
*/
|
|
485
|
+
setEnvironment(env) {
|
|
486
|
+
this.config.environment = { ...this.config.environment, ...env };
|
|
487
|
+
return this;
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
/**
|
|
491
|
+
* Finalize agent configuration by auto-detecting features
|
|
492
|
+
* This should be called before the agent is deployed
|
|
493
|
+
*/
|
|
494
|
+
finalize() {
|
|
495
|
+
this._autoDetectFeatures();
|
|
496
|
+
return this;
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
/**
|
|
500
|
+
* Set custom configuration
|
|
501
|
+
*/
|
|
502
|
+
setConfig(key, value) {
|
|
503
|
+
this.config.custom = this.config.custom || {};
|
|
504
|
+
this.config.custom[key] = value;
|
|
505
|
+
return this;
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
/**
|
|
509
|
+
* Get the complete agent configuration for serialization
|
|
510
|
+
*/
|
|
511
|
+
toConfig() {
|
|
512
|
+
return {
|
|
513
|
+
name: this.name,
|
|
514
|
+
id: this.id,
|
|
515
|
+
config: this.config,
|
|
516
|
+
handlers: this._serializeHandlers(),
|
|
517
|
+
status: this.status,
|
|
518
|
+
createdAt: this.createdAt
|
|
519
|
+
};
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
/**
|
|
523
|
+
* Create an agent from serialized configuration
|
|
524
|
+
*/
|
|
525
|
+
static fromConfig(config) {
|
|
526
|
+
const agent = new DankAgent(config.name, config.config);
|
|
527
|
+
agent.id = config.id;
|
|
528
|
+
agent.status = config.status;
|
|
529
|
+
agent.createdAt = config.createdAt;
|
|
530
|
+
|
|
531
|
+
// Restore handlers (note: actual functions will need to be re-registered)
|
|
532
|
+
if (config.handlers) {
|
|
533
|
+
Object.entries(config.handlers).forEach(([eventType, handlerList]) => {
|
|
534
|
+
agent.handlers.set(eventType, handlerList);
|
|
535
|
+
});
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
return agent;
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
/**
|
|
542
|
+
* Validate agent configuration
|
|
543
|
+
*/
|
|
544
|
+
_validateConfig(config) {
|
|
545
|
+
const schema = Joi.object({
|
|
546
|
+
llm: Joi.object({
|
|
547
|
+
provider: Joi.string().valid(...SUPPORTED_LLMS).required(),
|
|
548
|
+
apiKey: Joi.string().allow('').default(''),
|
|
549
|
+
model: Joi.string().default('gpt-3.5-turbo'),
|
|
550
|
+
baseURL: Joi.string().uri().optional(),
|
|
551
|
+
temperature: Joi.number().min(0).max(2).default(0.7),
|
|
552
|
+
maxTokens: Joi.number().min(1).default(1000)
|
|
553
|
+
}).optional(),
|
|
554
|
+
|
|
555
|
+
prompt: Joi.string().optional(),
|
|
556
|
+
|
|
557
|
+
resources: Joi.object({
|
|
558
|
+
memory: Joi.string().pattern(/^\d+[mMgG]$/).default('512m'),
|
|
559
|
+
cpu: Joi.number().min(0.1).max(8).default(1),
|
|
560
|
+
timeout: Joi.number().min(1000).default(30000)
|
|
561
|
+
}).default({}),
|
|
562
|
+
|
|
563
|
+
docker: Joi.object({
|
|
564
|
+
baseImage: Joi.string().default(`${DOCKER_CONFIG.baseImagePrefix}:${DOCKER_CONFIG.defaultTag}`),
|
|
565
|
+
port: Joi.number().min(1000).max(65535).default(DOCKER_CONFIG.defaultPort)
|
|
566
|
+
}).default({}),
|
|
567
|
+
|
|
568
|
+
communication: Joi.object({
|
|
569
|
+
directPrompting: Joi.object({
|
|
570
|
+
enabled: Joi.boolean().default(false),
|
|
571
|
+
protocol: Joi.string().valid('websocket', 'tcp', 'http').default('websocket'),
|
|
572
|
+
authentication: Joi.boolean().default(false),
|
|
573
|
+
maxConnections: Joi.number().min(1).max(1000).default(100),
|
|
574
|
+
timeout: Joi.number().min(1000).default(30000)
|
|
575
|
+
}).default({ enabled: false }),
|
|
576
|
+
httpApi: Joi.object({
|
|
577
|
+
enabled: Joi.boolean().default(false)
|
|
578
|
+
}).default({ enabled: false }),
|
|
579
|
+
eventHandlers: Joi.object({
|
|
580
|
+
enabled: Joi.boolean().default(false)
|
|
581
|
+
}).default({ enabled: false })
|
|
582
|
+
}).default({}),
|
|
583
|
+
|
|
584
|
+
http: Joi.object({
|
|
585
|
+
enabled: Joi.boolean().default(false),
|
|
586
|
+
port: Joi.number().min(1000).max(65535).default(3000),
|
|
587
|
+
host: Joi.string().default('0.0.0.0'),
|
|
588
|
+
cors: Joi.boolean().default(true),
|
|
589
|
+
rateLimit: Joi.object().default({}),
|
|
590
|
+
middleware: Joi.array().default([]),
|
|
591
|
+
static: Joi.object().default({}),
|
|
592
|
+
routes: Joi.any().default(new Map())
|
|
593
|
+
}).default({ enabled: false }),
|
|
594
|
+
|
|
595
|
+
environment: Joi.object().pattern(Joi.string(), Joi.string()).default({}),
|
|
596
|
+
|
|
597
|
+
custom: Joi.object().default({}),
|
|
598
|
+
|
|
599
|
+
tools: Joi.object({
|
|
600
|
+
enableBuiltinTools: Joi.boolean().default(true),
|
|
601
|
+
builtinToolConfig: Joi.object().default({}),
|
|
602
|
+
customTools: Joi.array().default([])
|
|
603
|
+
}).default({})
|
|
604
|
+
});
|
|
605
|
+
|
|
606
|
+
const { error, value } = schema.validate({
|
|
607
|
+
...DEFAULT_CONFIG,
|
|
608
|
+
...config
|
|
609
|
+
});
|
|
610
|
+
|
|
611
|
+
if (error) {
|
|
612
|
+
throw new Error(`Invalid agent configuration: ${error.message}`);
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
return value;
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
/**
|
|
619
|
+
* Generate a unique ID
|
|
620
|
+
*/
|
|
621
|
+
_generateId() {
|
|
622
|
+
return `agent_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
/**
|
|
626
|
+
* Serialize handlers for storage (functions can't be serialized)
|
|
627
|
+
*/
|
|
628
|
+
_serializeHandlers() {
|
|
629
|
+
const serialized = {};
|
|
630
|
+
|
|
631
|
+
this.handlers.forEach((handlerList, eventType) => {
|
|
632
|
+
serialized[eventType] = handlerList.map(h => ({
|
|
633
|
+
id: h.id,
|
|
634
|
+
createdAt: h.createdAt,
|
|
635
|
+
// Note: actual function is not serialized
|
|
636
|
+
hasFunction: typeof h.handler === 'function'
|
|
637
|
+
}));
|
|
638
|
+
});
|
|
639
|
+
|
|
640
|
+
return serialized;
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
module.exports = { DankAgent };
|
package/lib/cli/build.js
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI Build Command - Pull Docker images
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const chalk = require('chalk');
|
|
6
|
+
const { DockerManager } = require('../docker/manager');
|
|
7
|
+
|
|
8
|
+
async function buildCommand(options) {
|
|
9
|
+
try {
|
|
10
|
+
const dockerManager = new DockerManager();
|
|
11
|
+
await dockerManager.initialize();
|
|
12
|
+
|
|
13
|
+
if (options.base) {
|
|
14
|
+
await pullBaseImage(dockerManager, options);
|
|
15
|
+
} else {
|
|
16
|
+
console.log(chalk.yellow('š„ Pulling Docker images...\\n'));
|
|
17
|
+
|
|
18
|
+
// Pull base image
|
|
19
|
+
await pullBaseImage(dockerManager, options);
|
|
20
|
+
|
|
21
|
+
console.log(chalk.green('\\nā
Pull completed successfully!'));
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
} catch (error) {
|
|
25
|
+
console.error(chalk.red('ā Pull failed:'), error.message);
|
|
26
|
+
process.exit(1);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async function pullBaseImage(dockerManager, options) {
|
|
31
|
+
console.log(chalk.blue('š„ Pulling base image...'));
|
|
32
|
+
|
|
33
|
+
try {
|
|
34
|
+
await dockerManager.pullBaseImage();
|
|
35
|
+
|
|
36
|
+
console.log(chalk.green('ā
Base image pulled successfully'));
|
|
37
|
+
|
|
38
|
+
} catch (error) {
|
|
39
|
+
throw new Error(`Base image pull failed: ${error.message}`);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
module.exports = { buildCommand };
|