dank-ai 1.0.41 → 1.0.45
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 +490 -1365
- package/docker/entrypoint.js +48 -32
- package/lib/agent.js +80 -0
- package/lib/cli/init.js +25 -1
- package/lib/cli/production-build.js +3 -1
- package/lib/cli/run.js +3 -1
- package/lib/docker/manager.js +193 -11
- package/lib/index.js +8 -0
- package/lib/plugins/base.js +324 -0
- package/lib/plugins/config.js +171 -0
- package/lib/plugins/events.js +186 -0
- package/lib/plugins/index.js +29 -0
- package/lib/plugins/manager.js +258 -0
- package/lib/plugins/registry.js +268 -0
- package/lib/project.js +15 -8
- package/package.json +1 -1
package/docker/entrypoint.js
CHANGED
|
@@ -494,10 +494,11 @@ class AgentRuntime {
|
|
|
494
494
|
}
|
|
495
495
|
|
|
496
496
|
/**
|
|
497
|
-
* Emit an event to all matching handlers
|
|
497
|
+
* Emit an event to all matching handlers (fire-and-forget)
|
|
498
|
+
* Supports both sync and async handlers
|
|
498
499
|
* Supports pattern matching for tool events
|
|
499
500
|
*/
|
|
500
|
-
emitEvent(eventName, data = null) {
|
|
501
|
+
async emitEvent(eventName, data = null) {
|
|
501
502
|
// Find all matching handlers (exact match and pattern match)
|
|
502
503
|
const matchingHandlers = [];
|
|
503
504
|
|
|
@@ -507,19 +508,27 @@ class AgentRuntime {
|
|
|
507
508
|
}
|
|
508
509
|
}
|
|
509
510
|
|
|
510
|
-
// Execute all matching handlers
|
|
511
|
-
matchingHandlers.
|
|
511
|
+
// Execute all matching handlers (in parallel for fire-and-forget)
|
|
512
|
+
const promises = matchingHandlers.map(async (handler) => {
|
|
512
513
|
try {
|
|
514
|
+
let result;
|
|
513
515
|
if (typeof handler === "function") {
|
|
514
|
-
handler(data);
|
|
516
|
+
result = handler(data);
|
|
515
517
|
} else if (handler.handler && typeof handler.handler === "function") {
|
|
516
|
-
handler.handler(data);
|
|
518
|
+
result = handler.handler(data);
|
|
519
|
+
}
|
|
520
|
+
// Await if handler returns a promise
|
|
521
|
+
if (result && typeof result.then === "function") {
|
|
522
|
+
await result;
|
|
517
523
|
}
|
|
518
524
|
} catch (error) {
|
|
519
525
|
logger.error(`Error in event handler for '${eventName}':`, error);
|
|
520
526
|
}
|
|
521
527
|
});
|
|
522
528
|
|
|
529
|
+
// Wait for all handlers to complete
|
|
530
|
+
await Promise.all(promises);
|
|
531
|
+
|
|
523
532
|
if (matchingHandlers.length > 0) {
|
|
524
533
|
logger.debug(
|
|
525
534
|
`Emitted event '${eventName}' to ${matchingHandlers.length} handlers`
|
|
@@ -529,8 +538,10 @@ class AgentRuntime {
|
|
|
529
538
|
|
|
530
539
|
/**
|
|
531
540
|
* Emit an event and collect responses from handlers that return modified data
|
|
541
|
+
* Supports both sync and async handlers
|
|
542
|
+
* Handlers are executed sequentially to allow proper chaining of modifications
|
|
532
543
|
*/
|
|
533
|
-
emitEventWithResponse(eventName, data) {
|
|
544
|
+
async emitEventWithResponse(eventName, data) {
|
|
534
545
|
// Find all matching handlers (exact match and pattern match)
|
|
535
546
|
const matchingHandlers = [];
|
|
536
547
|
|
|
@@ -542,8 +553,8 @@ class AgentRuntime {
|
|
|
542
553
|
|
|
543
554
|
let modifiedData = { ...data };
|
|
544
555
|
|
|
545
|
-
// Execute
|
|
546
|
-
|
|
556
|
+
// Execute handlers sequentially to allow chaining of modifications
|
|
557
|
+
for (const handler of matchingHandlers) {
|
|
547
558
|
try {
|
|
548
559
|
let result;
|
|
549
560
|
if (typeof handler === "function") {
|
|
@@ -552,6 +563,11 @@ class AgentRuntime {
|
|
|
552
563
|
result = handler.handler(modifiedData);
|
|
553
564
|
}
|
|
554
565
|
|
|
566
|
+
// Await if handler returns a promise
|
|
567
|
+
if (result && typeof result.then === "function") {
|
|
568
|
+
result = await result;
|
|
569
|
+
}
|
|
570
|
+
|
|
555
571
|
// If handler returns an object, merge it with the current data
|
|
556
572
|
if (result && typeof result === "object" && !Array.isArray(result)) {
|
|
557
573
|
modifiedData = { ...modifiedData, ...result };
|
|
@@ -559,7 +575,7 @@ class AgentRuntime {
|
|
|
559
575
|
} catch (error) {
|
|
560
576
|
logger.error(`Handler error for event '${eventName}':`, error);
|
|
561
577
|
}
|
|
562
|
-
}
|
|
578
|
+
}
|
|
563
579
|
|
|
564
580
|
if (matchingHandlers.length > 0) {
|
|
565
581
|
logger.debug(
|
|
@@ -1193,7 +1209,7 @@ class AgentRuntime {
|
|
|
1193
1209
|
this.mainApp.post("/prompt", async (req, res) => {
|
|
1194
1210
|
logger.info(`📥 Received POST /prompt request from ${req.ip || 'unknown'}`);
|
|
1195
1211
|
try {
|
|
1196
|
-
const { prompt,
|
|
1212
|
+
const { prompt, ...requestBodyFields } = req.body;
|
|
1197
1213
|
|
|
1198
1214
|
if (!prompt) {
|
|
1199
1215
|
return res.status(400).json({
|
|
@@ -1202,16 +1218,18 @@ class AgentRuntime {
|
|
|
1202
1218
|
});
|
|
1203
1219
|
}
|
|
1204
1220
|
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1221
|
+
// Build metadata object with all user-provided fields from request body (except prompt)
|
|
1222
|
+
const metadata = {
|
|
1223
|
+
...requestBodyFields,
|
|
1224
|
+
};
|
|
1225
|
+
|
|
1226
|
+
const response = await this.processDirectPrompt(prompt, metadata, {
|
|
1208
1227
|
protocol: "http",
|
|
1209
1228
|
clientIp: req.ip,
|
|
1210
1229
|
});
|
|
1211
1230
|
|
|
1212
1231
|
res.json({
|
|
1213
1232
|
response: response.content,
|
|
1214
|
-
conversationId: conversationId || response.conversationId,
|
|
1215
1233
|
metadata: response.metadata,
|
|
1216
1234
|
timestamp: new Date().toISOString(),
|
|
1217
1235
|
});
|
|
@@ -1233,24 +1251,23 @@ class AgentRuntime {
|
|
|
1233
1251
|
/**
|
|
1234
1252
|
* Process a direct prompt and emit events
|
|
1235
1253
|
*/
|
|
1236
|
-
async processDirectPrompt(prompt,
|
|
1254
|
+
async processDirectPrompt(prompt, metadata = {}, systemFields = {}) {
|
|
1237
1255
|
const startTime = Date.now();
|
|
1238
|
-
|
|
1256
|
+
let finalPrompt = prompt; // Declare outside try block so it's available in catch
|
|
1239
1257
|
|
|
1240
1258
|
try {
|
|
1241
1259
|
// Emit request start event and allow handlers to modify the prompt
|
|
1242
1260
|
const startEventData = {
|
|
1243
1261
|
prompt,
|
|
1244
|
-
|
|
1245
|
-
|
|
1262
|
+
metadata,
|
|
1263
|
+
...systemFields, // protocol, clientIp, etc.
|
|
1246
1264
|
timestamp: new Date().toISOString(),
|
|
1247
1265
|
};
|
|
1248
1266
|
|
|
1249
|
-
const modifiedData = this.emitEventWithResponse("request_output:start", startEventData);
|
|
1267
|
+
const modifiedData = await this.emitEventWithResponse("request_output:start", startEventData);
|
|
1250
1268
|
|
|
1251
1269
|
// Use modified prompt if handlers returned one, otherwise use original
|
|
1252
|
-
|
|
1253
|
-
|
|
1270
|
+
finalPrompt = modifiedData?.prompt || prompt;
|
|
1254
1271
|
// Process with LLM
|
|
1255
1272
|
let response;
|
|
1256
1273
|
if (this.llmProvider === "openai") {
|
|
@@ -1281,12 +1298,12 @@ class AgentRuntime {
|
|
|
1281
1298
|
const processingTime = Date.now() - startTime;
|
|
1282
1299
|
|
|
1283
1300
|
// Emit request output event
|
|
1284
|
-
this.emitEvent("request_output", {
|
|
1301
|
+
await this.emitEvent("request_output", {
|
|
1285
1302
|
prompt,
|
|
1286
1303
|
finalPrompt, // Include the final prompt that was sent to LLM
|
|
1287
1304
|
response: response.content,
|
|
1288
|
-
|
|
1289
|
-
|
|
1305
|
+
metadata,
|
|
1306
|
+
...systemFields, // protocol, clientIp, etc.
|
|
1290
1307
|
usage: response.usage,
|
|
1291
1308
|
model: response.model,
|
|
1292
1309
|
processingTime,
|
|
@@ -1296,11 +1313,11 @@ class AgentRuntime {
|
|
|
1296
1313
|
|
|
1297
1314
|
// Emit end event and allow handlers to modify the response before returning
|
|
1298
1315
|
const endEventData = {
|
|
1299
|
-
conversationId,
|
|
1300
1316
|
prompt,
|
|
1301
1317
|
finalPrompt,
|
|
1302
1318
|
response: response.content,
|
|
1303
|
-
|
|
1319
|
+
metadata,
|
|
1320
|
+
...systemFields, // protocol, clientIp, etc.
|
|
1304
1321
|
usage: response.usage,
|
|
1305
1322
|
model: response.model,
|
|
1306
1323
|
processingTime,
|
|
@@ -1309,14 +1326,13 @@ class AgentRuntime {
|
|
|
1309
1326
|
timestamp: new Date().toISOString(),
|
|
1310
1327
|
};
|
|
1311
1328
|
|
|
1312
|
-
const modifiedEndData = this.emitEventWithResponse("request_output:end", endEventData);
|
|
1329
|
+
const modifiedEndData = await this.emitEventWithResponse("request_output:end", endEventData);
|
|
1313
1330
|
|
|
1314
1331
|
// Use the final response (potentially modified by handlers)
|
|
1315
1332
|
const finalResponse = modifiedEndData.response || response.content;
|
|
1316
1333
|
|
|
1317
1334
|
return {
|
|
1318
1335
|
content: finalResponse,
|
|
1319
|
-
conversationId,
|
|
1320
1336
|
metadata: {
|
|
1321
1337
|
usage: response.usage,
|
|
1322
1338
|
model: response.model,
|
|
@@ -1328,11 +1344,11 @@ class AgentRuntime {
|
|
|
1328
1344
|
const processingTime = Date.now() - startTime;
|
|
1329
1345
|
|
|
1330
1346
|
// Emit error event
|
|
1331
|
-
this.emitEvent("request_output:error", {
|
|
1347
|
+
await this.emitEvent("request_output:error", {
|
|
1332
1348
|
prompt,
|
|
1333
1349
|
finalPrompt,
|
|
1334
|
-
|
|
1335
|
-
|
|
1350
|
+
metadata,
|
|
1351
|
+
...systemFields, // protocol, clientIp, etc.
|
|
1336
1352
|
error: error.message,
|
|
1337
1353
|
processingTime,
|
|
1338
1354
|
promptModified: finalPrompt !== prompt,
|
package/lib/agent.js
CHANGED
|
@@ -10,6 +10,7 @@ const { validate: validateUUID, v4: uuidv4 } = require('uuid');
|
|
|
10
10
|
const { DEFAULT_CONFIG, SUPPORTED_LLMS, DOCKER_CONFIG, INSTANCE_TYPES } = require('./constants');
|
|
11
11
|
const { ToolRegistry, ToolExecutor } = require('./tools');
|
|
12
12
|
const builtinTools = require('./tools/builtin');
|
|
13
|
+
const { PluginManager } = require('./plugins');
|
|
13
14
|
|
|
14
15
|
class DankAgent {
|
|
15
16
|
constructor(name, config = {}) {
|
|
@@ -26,6 +27,10 @@ class DankAgent {
|
|
|
26
27
|
this.toolRegistry = new ToolRegistry();
|
|
27
28
|
this.toolExecutor = new ToolExecutor(this.toolRegistry);
|
|
28
29
|
|
|
30
|
+
// Initialize plugin system
|
|
31
|
+
this.pluginManager = new PluginManager(this);
|
|
32
|
+
this.pendingPlugins = new Map(); // Store plugins to be loaded during finalization
|
|
33
|
+
|
|
29
34
|
// Register built-in tools if enabled
|
|
30
35
|
if (config.enableBuiltinTools !== false) {
|
|
31
36
|
this.registerBuiltinTools();
|
|
@@ -420,6 +425,68 @@ class DankAgent {
|
|
|
420
425
|
return this.toolRegistry.toOpenAISchema();
|
|
421
426
|
}
|
|
422
427
|
|
|
428
|
+
/**
|
|
429
|
+
* Add a plugin to this agent
|
|
430
|
+
* Plugin will be loaded and initialized during finalization
|
|
431
|
+
* @param {string|PluginBase} name - Plugin name, npm package, local path, or PluginBase instance
|
|
432
|
+
* @param {object} config - Plugin configuration
|
|
433
|
+
*/
|
|
434
|
+
addPlugin(name, config = {}) {
|
|
435
|
+
this.pendingPlugins.set(name, config);
|
|
436
|
+
return this;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
/**
|
|
440
|
+
* Add multiple plugins at once
|
|
441
|
+
* Plugins will be loaded and initialized during finalization
|
|
442
|
+
* @param {object} plugins - Object mapping plugin names to configs
|
|
443
|
+
*/
|
|
444
|
+
addPlugins(plugins) {
|
|
445
|
+
Object.entries(plugins).forEach(([name, config]) => {
|
|
446
|
+
this.pendingPlugins.set(name, config);
|
|
447
|
+
});
|
|
448
|
+
return this;
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
/**
|
|
452
|
+
* Get a plugin by name
|
|
453
|
+
* @param {string} name - Plugin name
|
|
454
|
+
*/
|
|
455
|
+
getPlugin(name) {
|
|
456
|
+
return this.pluginManager.getPlugin(name);
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
/**
|
|
460
|
+
* Get all plugins
|
|
461
|
+
*/
|
|
462
|
+
getPlugins() {
|
|
463
|
+
return this.pluginManager.getAllPlugins();
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
/**
|
|
467
|
+
* Check if a plugin is loaded
|
|
468
|
+
* @param {string} name - Plugin name
|
|
469
|
+
*/
|
|
470
|
+
hasPlugin(name) {
|
|
471
|
+
return this.pluginManager.hasPlugin(name);
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
/**
|
|
475
|
+
* Remove a plugin
|
|
476
|
+
* @param {string} name - Plugin name
|
|
477
|
+
*/
|
|
478
|
+
async removePlugin(name) {
|
|
479
|
+
await this.pluginManager.removePlugin(name);
|
|
480
|
+
return this;
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
/**
|
|
484
|
+
* Get plugin manager metadata
|
|
485
|
+
*/
|
|
486
|
+
getPluginMetadata() {
|
|
487
|
+
return this.pluginManager.getMetadata();
|
|
488
|
+
}
|
|
489
|
+
|
|
423
490
|
/**
|
|
424
491
|
* Register built-in tools
|
|
425
492
|
*/
|
|
@@ -590,6 +657,7 @@ class DankAgent {
|
|
|
590
657
|
/**
|
|
591
658
|
* Finalize agent configuration by auto-detecting features
|
|
592
659
|
* This should be called before the agent is deployed
|
|
660
|
+
* Plugins will be loaded when the agent is started
|
|
593
661
|
*/
|
|
594
662
|
finalize() {
|
|
595
663
|
// Validate that ID is set before finalization (REQUIRED)
|
|
@@ -599,6 +667,18 @@ class DankAgent {
|
|
|
599
667
|
return this;
|
|
600
668
|
}
|
|
601
669
|
|
|
670
|
+
/**
|
|
671
|
+
* Initialize plugins (called internally when agent starts)
|
|
672
|
+
* @private
|
|
673
|
+
*/
|
|
674
|
+
async _initializePlugins() {
|
|
675
|
+
if (this.pendingPlugins.size > 0) {
|
|
676
|
+
const plugins = Object.fromEntries(this.pendingPlugins);
|
|
677
|
+
await this.pluginManager.addPlugins(plugins);
|
|
678
|
+
this.pendingPlugins.clear();
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
|
|
602
682
|
/**
|
|
603
683
|
* Set custom configuration
|
|
604
684
|
*/
|
package/lib/cli/init.js
CHANGED
|
@@ -126,7 +126,9 @@ async function createPackageJson(npmProjectName, projectPath) {
|
|
|
126
126
|
clean: 'dank clean'
|
|
127
127
|
},
|
|
128
128
|
dependencies: {
|
|
129
|
-
'dank-ai': '^1.0.0'
|
|
129
|
+
'dank-ai': '^1.0.0',
|
|
130
|
+
'axios': '^1.6.0',
|
|
131
|
+
'date-fns': '^3.0.0'
|
|
130
132
|
},
|
|
131
133
|
keywords: ['dank', 'ai', 'agents', 'automation', 'llm'],
|
|
132
134
|
author: '',
|
|
@@ -270,6 +272,7 @@ A Dank AI agent project with modern event handling and Docker orchestration.
|
|
|
270
272
|
|
|
271
273
|
- 🤖 **AI Agents**: Powered by multiple LLM providers (OpenAI, Anthropic, Google AI)
|
|
272
274
|
- 🐳 **Docker Integration**: Containerized agents with automatic management
|
|
275
|
+
- 📦 **NPM Packages**: Use any npm package in your handlers with top-level imports
|
|
273
276
|
- 📡 **Event System**: Real-time event handling for prompts, responses, and tools
|
|
274
277
|
- 🔧 **Auto-Detection**: Automatically enables features based on usage
|
|
275
278
|
- 📊 **Monitoring**: Built-in logging and status monitoring
|
|
@@ -349,6 +352,27 @@ Each agent in your project has a unique UUIDv4 identifier that is automatically
|
|
|
349
352
|
- Once agents register with Dank Cloud services, these IDs become locked to your account
|
|
350
353
|
- You can generate new UUIDs if needed: \`require('uuid').v4()\`
|
|
351
354
|
|
|
355
|
+
## Using NPM Packages
|
|
356
|
+
|
|
357
|
+
You can use any npm package in your handlers by importing them at the top of \`dank.config.js\`:
|
|
358
|
+
|
|
359
|
+
\`\`\`javascript
|
|
360
|
+
// Import packages at the top
|
|
361
|
+
const axios = require('axios');
|
|
362
|
+
const { format } = require('date-fns');
|
|
363
|
+
|
|
364
|
+
// Use them in your handlers
|
|
365
|
+
.addHandler('request_output', async (data) => {
|
|
366
|
+
const timestamp = format(new Date(), 'yyyy-MM-dd HH:mm');
|
|
367
|
+
await axios.post('https://api.example.com/log', {
|
|
368
|
+
response: data.response,
|
|
369
|
+
timestamp
|
|
370
|
+
});
|
|
371
|
+
})
|
|
372
|
+
\`\`\`
|
|
373
|
+
|
|
374
|
+
Just add any packages you need to your \`package.json\` and run \`npm install\`.
|
|
375
|
+
|
|
352
376
|
## Configuration
|
|
353
377
|
|
|
354
378
|
Edit \`dank.config.js\` to:
|
|
@@ -173,7 +173,9 @@ async function productionBuildCommand(options) {
|
|
|
173
173
|
force: options.force || false,
|
|
174
174
|
push: options.push || false,
|
|
175
175
|
baseImageOverride: options.baseImageOverride || null,
|
|
176
|
-
projectDir: projectDir // Pass project directory so external files can be copied
|
|
176
|
+
projectDir: projectDir, // Pass project directory so external files can be copied
|
|
177
|
+
configPath: configPath, // Pass config path for extracting top-level requires
|
|
178
|
+
projectRoot: process.cwd() // Pass project root for package.json location
|
|
177
179
|
};
|
|
178
180
|
|
|
179
181
|
const result = await dockerManager.buildProductionImage(agent, buildOptions);
|
package/lib/cli/run.js
CHANGED
|
@@ -71,7 +71,9 @@ async function runCommand(options) {
|
|
|
71
71
|
|
|
72
72
|
const container = await dockerManager.startAgent(agent, {
|
|
73
73
|
rebuild: !options.noBuild, // Rebuild by default unless --no-build is specified
|
|
74
|
-
projectDir: projectDir // Pass project directory so external files can be copied
|
|
74
|
+
projectDir: projectDir, // Pass project directory so external files can be copied
|
|
75
|
+
configPath: configPath, // Pass config path for extracting top-level requires
|
|
76
|
+
projectRoot: process.cwd() // Pass project root for package.json location
|
|
75
77
|
});
|
|
76
78
|
|
|
77
79
|
console.log(chalk.green(` ✅ ${agent.name} started (${container.id.substring(0, 12)})`));
|