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,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
+ }