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.
@@ -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 };
@@ -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 };