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,335 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dank Agent Tool System
|
|
3
|
+
*
|
|
4
|
+
* Enables agents to use external tools and function calling capabilities.
|
|
5
|
+
* This is the foundation for autonomous agent behavior.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const Joi = require('joi');
|
|
9
|
+
const { v4: uuidv4 } = require('uuid');
|
|
10
|
+
|
|
11
|
+
class ToolRegistry {
|
|
12
|
+
constructor() {
|
|
13
|
+
this.tools = new Map();
|
|
14
|
+
this.categories = new Map();
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Register a new tool
|
|
19
|
+
*/
|
|
20
|
+
register(name, definition) {
|
|
21
|
+
const validated = this.validateTool(name, definition);
|
|
22
|
+
this.tools.set(name, {
|
|
23
|
+
...validated,
|
|
24
|
+
id: uuidv4(),
|
|
25
|
+
registeredAt: new Date().toISOString()
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
// Add to category
|
|
29
|
+
const category = validated.category || 'general';
|
|
30
|
+
if (!this.categories.has(category)) {
|
|
31
|
+
this.categories.set(category, []);
|
|
32
|
+
}
|
|
33
|
+
this.categories.get(category).push(name);
|
|
34
|
+
|
|
35
|
+
return this;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Get a tool by name
|
|
40
|
+
*/
|
|
41
|
+
get(name) {
|
|
42
|
+
return this.tools.get(name);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Get all tools
|
|
47
|
+
*/
|
|
48
|
+
getAll() {
|
|
49
|
+
return Array.from(this.tools.values());
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Get tools by category
|
|
54
|
+
*/
|
|
55
|
+
getByCategory(category) {
|
|
56
|
+
const toolNames = this.categories.get(category) || [];
|
|
57
|
+
return toolNames.map(name => this.tools.get(name));
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Validate tool definition
|
|
62
|
+
*/
|
|
63
|
+
validateTool(name, definition) {
|
|
64
|
+
const schema = Joi.object({
|
|
65
|
+
description: Joi.string().required().min(10).max(500),
|
|
66
|
+
parameters: Joi.object().pattern(
|
|
67
|
+
Joi.string(),
|
|
68
|
+
Joi.object({
|
|
69
|
+
type: Joi.string().valid('string', 'number', 'boolean', 'array', 'object').required(),
|
|
70
|
+
description: Joi.string().optional(),
|
|
71
|
+
required: Joi.boolean().default(false),
|
|
72
|
+
default: Joi.any().optional(),
|
|
73
|
+
enum: Joi.array().optional(),
|
|
74
|
+
min: Joi.number().optional(),
|
|
75
|
+
max: Joi.number().optional(),
|
|
76
|
+
pattern: Joi.string().optional()
|
|
77
|
+
})
|
|
78
|
+
).default({}),
|
|
79
|
+
handler: Joi.function().required(),
|
|
80
|
+
category: Joi.string().default('general'),
|
|
81
|
+
version: Joi.string().default('1.0.0'),
|
|
82
|
+
timeout: Joi.number().min(1000).max(300000).default(30000), // 30 seconds default
|
|
83
|
+
retries: Joi.number().min(0).max(5).default(1),
|
|
84
|
+
async: Joi.boolean().default(true),
|
|
85
|
+
cacheable: Joi.boolean().default(false),
|
|
86
|
+
cacheTime: Joi.number().min(0).default(0),
|
|
87
|
+
metadata: Joi.object().default({})
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
const { error, value } = schema.validate(definition);
|
|
91
|
+
if (error) {
|
|
92
|
+
throw new Error(`Invalid tool definition for '${name}': ${error.message}`);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return value;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Generate OpenAI function calling schema for all tools
|
|
100
|
+
*/
|
|
101
|
+
toOpenAISchema() {
|
|
102
|
+
return this.getAll().map(tool => ({
|
|
103
|
+
type: 'function',
|
|
104
|
+
function: {
|
|
105
|
+
name: tool.name,
|
|
106
|
+
description: tool.description,
|
|
107
|
+
parameters: {
|
|
108
|
+
type: 'object',
|
|
109
|
+
properties: Object.fromEntries(
|
|
110
|
+
Object.entries(tool.parameters).map(([key, param]) => [
|
|
111
|
+
key,
|
|
112
|
+
{
|
|
113
|
+
type: param.type,
|
|
114
|
+
description: param.description,
|
|
115
|
+
...(param.enum && { enum: param.enum }),
|
|
116
|
+
...(param.min !== undefined && { minimum: param.min }),
|
|
117
|
+
...(param.max !== undefined && { maximum: param.max }),
|
|
118
|
+
...(param.pattern && { pattern: param.pattern })
|
|
119
|
+
}
|
|
120
|
+
])
|
|
121
|
+
),
|
|
122
|
+
required: Object.entries(tool.parameters)
|
|
123
|
+
.filter(([, param]) => param.required)
|
|
124
|
+
.map(([key]) => key)
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}));
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
class ToolExecutor {
|
|
132
|
+
constructor(registry) {
|
|
133
|
+
this.registry = registry;
|
|
134
|
+
this.executionHistory = [];
|
|
135
|
+
this.cache = new Map();
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Execute a tool with given parameters
|
|
140
|
+
*/
|
|
141
|
+
async execute(toolName, parameters = {}, context = {}) {
|
|
142
|
+
const tool = this.registry.get(toolName);
|
|
143
|
+
if (!tool) {
|
|
144
|
+
throw new Error(`Tool '${toolName}' not found`);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const executionId = uuidv4();
|
|
148
|
+
const startTime = Date.now();
|
|
149
|
+
|
|
150
|
+
try {
|
|
151
|
+
// Check cache if tool is cacheable
|
|
152
|
+
if (tool.cacheable && tool.cacheTime > 0) {
|
|
153
|
+
const cacheKey = this.getCacheKey(toolName, parameters);
|
|
154
|
+
const cached = this.cache.get(cacheKey);
|
|
155
|
+
if (cached && (Date.now() - cached.timestamp) < tool.cacheTime) {
|
|
156
|
+
return cached.result;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Validate parameters
|
|
161
|
+
this.validateParameters(tool, parameters);
|
|
162
|
+
|
|
163
|
+
// Execute with timeout and retries
|
|
164
|
+
const result = await this.executeWithRetries(tool, parameters, context);
|
|
165
|
+
|
|
166
|
+
// Cache result if cacheable
|
|
167
|
+
if (tool.cacheable && tool.cacheTime > 0) {
|
|
168
|
+
const cacheKey = this.getCacheKey(toolName, parameters);
|
|
169
|
+
this.cache.set(cacheKey, {
|
|
170
|
+
result,
|
|
171
|
+
timestamp: Date.now()
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Record execution
|
|
176
|
+
this.recordExecution(executionId, toolName, parameters, result, Date.now() - startTime, 'success');
|
|
177
|
+
|
|
178
|
+
return result;
|
|
179
|
+
|
|
180
|
+
} catch (error) {
|
|
181
|
+
this.recordExecution(executionId, toolName, parameters, null, Date.now() - startTime, 'error', error);
|
|
182
|
+
throw error;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Execute tool with retries and timeout
|
|
188
|
+
*/
|
|
189
|
+
async executeWithRetries(tool, parameters, context) {
|
|
190
|
+
let lastError;
|
|
191
|
+
|
|
192
|
+
for (let attempt = 0; attempt <= tool.retries; attempt++) {
|
|
193
|
+
try {
|
|
194
|
+
return await this.executeWithTimeout(tool, parameters, context);
|
|
195
|
+
} catch (error) {
|
|
196
|
+
lastError = error;
|
|
197
|
+
if (attempt < tool.retries) {
|
|
198
|
+
// Exponential backoff
|
|
199
|
+
await new Promise(resolve => setTimeout(resolve, Math.pow(2, attempt) * 1000));
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
throw lastError;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Execute tool with timeout
|
|
209
|
+
*/
|
|
210
|
+
async executeWithTimeout(tool, parameters, context) {
|
|
211
|
+
return new Promise((resolve, reject) => {
|
|
212
|
+
const timeoutId = setTimeout(() => {
|
|
213
|
+
reject(new Error(`Tool '${tool.name}' execution timed out after ${tool.timeout}ms`));
|
|
214
|
+
}, tool.timeout);
|
|
215
|
+
|
|
216
|
+
try {
|
|
217
|
+
const result = tool.handler(parameters, context);
|
|
218
|
+
|
|
219
|
+
if (result && typeof result.then === 'function') {
|
|
220
|
+
// Handle async function
|
|
221
|
+
result
|
|
222
|
+
.then(res => {
|
|
223
|
+
clearTimeout(timeoutId);
|
|
224
|
+
resolve(res);
|
|
225
|
+
})
|
|
226
|
+
.catch(err => {
|
|
227
|
+
clearTimeout(timeoutId);
|
|
228
|
+
reject(err);
|
|
229
|
+
});
|
|
230
|
+
} else {
|
|
231
|
+
// Handle sync function
|
|
232
|
+
clearTimeout(timeoutId);
|
|
233
|
+
resolve(result);
|
|
234
|
+
}
|
|
235
|
+
} catch (error) {
|
|
236
|
+
clearTimeout(timeoutId);
|
|
237
|
+
reject(error);
|
|
238
|
+
}
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Validate tool parameters
|
|
244
|
+
*/
|
|
245
|
+
validateParameters(tool, parameters) {
|
|
246
|
+
const schema = Joi.object(
|
|
247
|
+
Object.fromEntries(
|
|
248
|
+
Object.entries(tool.parameters).map(([key, param]) => {
|
|
249
|
+
let validator = Joi[param.type]();
|
|
250
|
+
|
|
251
|
+
if (param.required) validator = validator.required();
|
|
252
|
+
if (param.default !== undefined) validator = validator.default(param.default);
|
|
253
|
+
if (param.enum) validator = validator.valid(...param.enum);
|
|
254
|
+
if (param.min !== undefined) validator = validator.min(param.min);
|
|
255
|
+
if (param.max !== undefined) validator = validator.max(param.max);
|
|
256
|
+
if (param.pattern) validator = validator.pattern(new RegExp(param.pattern));
|
|
257
|
+
|
|
258
|
+
return [key, validator];
|
|
259
|
+
})
|
|
260
|
+
)
|
|
261
|
+
);
|
|
262
|
+
|
|
263
|
+
const { error } = schema.validate(parameters);
|
|
264
|
+
if (error) {
|
|
265
|
+
throw new Error(`Invalid parameters for tool '${tool.name}': ${error.message}`);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* Generate cache key
|
|
271
|
+
*/
|
|
272
|
+
getCacheKey(toolName, parameters) {
|
|
273
|
+
return `${toolName}:${JSON.stringify(parameters)}`;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* Record tool execution
|
|
278
|
+
*/
|
|
279
|
+
recordExecution(id, toolName, parameters, result, duration, status, error = null) {
|
|
280
|
+
this.executionHistory.push({
|
|
281
|
+
id,
|
|
282
|
+
toolName,
|
|
283
|
+
parameters,
|
|
284
|
+
result: status === 'success' ? result : null,
|
|
285
|
+
duration,
|
|
286
|
+
status,
|
|
287
|
+
error: error ? error.message : null,
|
|
288
|
+
timestamp: new Date().toISOString()
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
// Keep only last 1000 executions
|
|
292
|
+
if (this.executionHistory.length > 1000) {
|
|
293
|
+
this.executionHistory.shift();
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* Get execution statistics
|
|
299
|
+
*/
|
|
300
|
+
getStats() {
|
|
301
|
+
const total = this.executionHistory.length;
|
|
302
|
+
const successful = this.executionHistory.filter(e => e.status === 'success').length;
|
|
303
|
+
const failed = total - successful;
|
|
304
|
+
const avgDuration = this.executionHistory.reduce((sum, e) => sum + e.duration, 0) / total || 0;
|
|
305
|
+
|
|
306
|
+
const toolStats = {};
|
|
307
|
+
this.executionHistory.forEach(execution => {
|
|
308
|
+
if (!toolStats[execution.toolName]) {
|
|
309
|
+
toolStats[execution.toolName] = { total: 0, successful: 0, avgDuration: 0 };
|
|
310
|
+
}
|
|
311
|
+
toolStats[execution.toolName].total++;
|
|
312
|
+
if (execution.status === 'success') {
|
|
313
|
+
toolStats[execution.toolName].successful++;
|
|
314
|
+
}
|
|
315
|
+
toolStats[execution.toolName].avgDuration =
|
|
316
|
+
(toolStats[execution.toolName].avgDuration + execution.duration) / 2;
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
return {
|
|
320
|
+
overall: {
|
|
321
|
+
total,
|
|
322
|
+
successful,
|
|
323
|
+
failed,
|
|
324
|
+
successRate: total > 0 ? (successful / total) * 100 : 0,
|
|
325
|
+
avgDuration: Math.round(avgDuration)
|
|
326
|
+
},
|
|
327
|
+
byTool: toolStats
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
module.exports = {
|
|
333
|
+
ToolRegistry,
|
|
334
|
+
ToolExecutor
|
|
335
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "dank-ai",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Dank Agent Service - Docker-based AI agent orchestration platform",
|
|
5
|
+
"main": "lib/index.js",
|
|
6
|
+
"exports": {
|
|
7
|
+
".": "./lib/index.js",
|
|
8
|
+
"./lib": "./lib/index.js",
|
|
9
|
+
"./lib/*": "./lib/*"
|
|
10
|
+
},
|
|
11
|
+
"bin": {
|
|
12
|
+
"dank": "./bin/dank"
|
|
13
|
+
},
|
|
14
|
+
"scripts": {
|
|
15
|
+
"test": "echo \"Error: no test specified\" && exit 1",
|
|
16
|
+
"build": "echo \"Build completed\"",
|
|
17
|
+
"dev": "node bin/dank",
|
|
18
|
+
"install-global": "npm install -g .",
|
|
19
|
+
"docker:build-base": "docker build -t dank-agent-base -f docker/Dockerfile.base ."
|
|
20
|
+
},
|
|
21
|
+
"dependencies": {
|
|
22
|
+
"commander": "^11.0.0",
|
|
23
|
+
"chalk": "^4.1.2",
|
|
24
|
+
"dockerode": "^4.0.0",
|
|
25
|
+
"js-yaml": "^4.1.0",
|
|
26
|
+
"joi": "^17.9.2",
|
|
27
|
+
"winston": "^3.10.0",
|
|
28
|
+
"fs-extra": "^11.1.1",
|
|
29
|
+
"tar": "^6.1.15",
|
|
30
|
+
"uuid": "^9.0.0"
|
|
31
|
+
},
|
|
32
|
+
"devDependencies": {
|
|
33
|
+
"nodemon": "^3.0.1"
|
|
34
|
+
},
|
|
35
|
+
"keywords": ["agent", "ai", "docker", "orchestration", "llm", "automation"],
|
|
36
|
+
"author": "",
|
|
37
|
+
"license": "ISC",
|
|
38
|
+
"files": [
|
|
39
|
+
"lib/",
|
|
40
|
+
"bin/",
|
|
41
|
+
"docker/",
|
|
42
|
+
"templates/",
|
|
43
|
+
"README.md"
|
|
44
|
+
],
|
|
45
|
+
"engines": {
|
|
46
|
+
"node": ">=16.0.0",
|
|
47
|
+
"npm": ">=8.0.0"
|
|
48
|
+
},
|
|
49
|
+
"publishConfig": {
|
|
50
|
+
"access": "public"
|
|
51
|
+
}
|
|
52
|
+
}
|