dank-ai 1.0.32 → 1.0.34
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 +49 -52
- package/bin/dank +1 -0
- package/docker/entrypoint.js +140 -353
- package/lib/agent.js +105 -30
- package/lib/cli/init.js +1 -14
- package/lib/cli/production-build.js +6 -8
- package/lib/config.js +34 -33
- package/lib/constants.js +23 -6
- package/lib/docker/manager.js +135 -26
- package/lib/project.js +1 -4
- package/package.json +1 -1
package/lib/docker/manager.js
CHANGED
|
@@ -1165,6 +1165,10 @@ class DockerManager {
|
|
|
1165
1165
|
const imageName = `dank-agent-${normalizedName}`;
|
|
1166
1166
|
this.logger.info(`Building image for agent: ${agent.name}`);
|
|
1167
1167
|
|
|
1168
|
+
// Finalize agent configuration before building
|
|
1169
|
+
// This ensures ports and other configs are properly set
|
|
1170
|
+
agent.finalize();
|
|
1171
|
+
|
|
1168
1172
|
try {
|
|
1169
1173
|
const buildContext = await this.createAgentBuildContext(agent);
|
|
1170
1174
|
const dockerCmd = await this.resolveDockerCommand();
|
|
@@ -1196,6 +1200,17 @@ class DockerManager {
|
|
|
1196
1200
|
|
|
1197
1201
|
/**
|
|
1198
1202
|
* Build production image with custom naming and tagging
|
|
1203
|
+
*
|
|
1204
|
+
* @param {Object} agent - The agent to build
|
|
1205
|
+
* @param {Object} options - Build options
|
|
1206
|
+
* @param {string} [options.tag="latest"] - Image tag
|
|
1207
|
+
* @param {string} [options.registry] - Docker registry
|
|
1208
|
+
* @param {string} [options.namespace] - Docker namespace
|
|
1209
|
+
* @param {boolean} [options.tagByAgent=false] - Use agent name as tag
|
|
1210
|
+
* @param {boolean} [options.force=false] - Force rebuild without cache
|
|
1211
|
+
* @param {boolean} [options.push=false] - Push to registry after build
|
|
1212
|
+
* @param {string} [options.baseImageOverride=null] - Production-only: Override base image for all agents (replaces agent's configured base image)
|
|
1213
|
+
* @returns {Promise<Object>} Build result with imageName and pushed status
|
|
1199
1214
|
*/
|
|
1200
1215
|
async buildProductionImage(agent, options = {}) {
|
|
1201
1216
|
const {
|
|
@@ -1205,6 +1220,7 @@ class DockerManager {
|
|
|
1205
1220
|
tagByAgent = false,
|
|
1206
1221
|
force = false,
|
|
1207
1222
|
push = false,
|
|
1223
|
+
baseImageOverride = null, // Production-only: override base image for all agents
|
|
1208
1224
|
} = options;
|
|
1209
1225
|
|
|
1210
1226
|
// Normalize all components
|
|
@@ -1231,8 +1247,20 @@ class DockerManager {
|
|
|
1231
1247
|
`Building production image for agent: ${agent.name} -> ${imageName}`
|
|
1232
1248
|
);
|
|
1233
1249
|
|
|
1250
|
+
// Finalize agent configuration before building
|
|
1251
|
+
// This ensures ports and other configs are properly set
|
|
1252
|
+
agent.finalize();
|
|
1253
|
+
|
|
1254
|
+
// Log base image override if provided
|
|
1255
|
+
if (baseImageOverride) {
|
|
1256
|
+
this.logger.info(`🔧 Production build: Using custom base image override: ${baseImageOverride}`);
|
|
1257
|
+
}
|
|
1258
|
+
|
|
1234
1259
|
try {
|
|
1235
|
-
const buildContext = await this.createAgentBuildContext(agent
|
|
1260
|
+
const buildContext = await this.createAgentBuildContext(agent, {
|
|
1261
|
+
isProductionBuild: true,
|
|
1262
|
+
baseImageOverride: baseImageOverride
|
|
1263
|
+
});
|
|
1236
1264
|
const dockerCmd = await this.resolveDockerCommand();
|
|
1237
1265
|
|
|
1238
1266
|
const buildCommand = [
|
|
@@ -1309,6 +1337,53 @@ class DockerManager {
|
|
|
1309
1337
|
return `{\n${handlersEntries}\n }`;
|
|
1310
1338
|
}
|
|
1311
1339
|
|
|
1340
|
+
/**
|
|
1341
|
+
* Generate routes code from agent configuration
|
|
1342
|
+
*/
|
|
1343
|
+
generateRoutesCode(agent) {
|
|
1344
|
+
const routes = {};
|
|
1345
|
+
|
|
1346
|
+
// Add routes from agent HTTP configuration
|
|
1347
|
+
if (agent.config?.http?.routes && agent.config.http.routes.size > 0) {
|
|
1348
|
+
for (const [routeKey, routeList] of agent.config.http.routes) {
|
|
1349
|
+
const [method, path] = routeKey.split(':');
|
|
1350
|
+
if (!routes[path]) {
|
|
1351
|
+
routes[path] = {};
|
|
1352
|
+
}
|
|
1353
|
+
|
|
1354
|
+
// Convert route handlers to string representations
|
|
1355
|
+
routeList.forEach((routeObj) => {
|
|
1356
|
+
if (routeObj && typeof routeObj.handler === "function") {
|
|
1357
|
+
const handlerStr = routeObj.handler.toString();
|
|
1358
|
+
routes[path][method.toLowerCase()] = handlerStr;
|
|
1359
|
+
}
|
|
1360
|
+
});
|
|
1361
|
+
}
|
|
1362
|
+
}
|
|
1363
|
+
|
|
1364
|
+
// Generate the JavaScript object code
|
|
1365
|
+
if (Object.keys(routes).length === 0) {
|
|
1366
|
+
return '{}';
|
|
1367
|
+
}
|
|
1368
|
+
|
|
1369
|
+
const routesEntries = Object.entries(routes)
|
|
1370
|
+
.map(([path, methods]) => {
|
|
1371
|
+
const methodsEntries = Object.entries(methods)
|
|
1372
|
+
.map(([method, handler]) => {
|
|
1373
|
+
return ` ${method}: ${handler}`;
|
|
1374
|
+
})
|
|
1375
|
+
.join(",\n");
|
|
1376
|
+
// Quote paths that contain special characters
|
|
1377
|
+
const quotedPath = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(path)
|
|
1378
|
+
? path
|
|
1379
|
+
: JSON.stringify(path);
|
|
1380
|
+
return ` ${quotedPath}: {\n${methodsEntries}\n }`;
|
|
1381
|
+
})
|
|
1382
|
+
.join(",\n");
|
|
1383
|
+
|
|
1384
|
+
return `{\n${routesEntries}\n }`;
|
|
1385
|
+
}
|
|
1386
|
+
|
|
1312
1387
|
/**
|
|
1313
1388
|
* Start agent container
|
|
1314
1389
|
*/
|
|
@@ -1351,12 +1426,12 @@ class DockerManager {
|
|
|
1351
1426
|
name: containerName,
|
|
1352
1427
|
Env: this.prepareEnvironmentVariables(agent),
|
|
1353
1428
|
HostConfig: {
|
|
1354
|
-
Memory: AgentConfig.parseMemory(agent.config.
|
|
1355
|
-
CpuQuota: Math.floor(agent.config.
|
|
1429
|
+
Memory: AgentConfig.parseMemory(AgentConfig.getResourcesFromInstanceType(agent.config.instanceType).memory),
|
|
1430
|
+
CpuQuota: Math.floor(AgentConfig.getResourcesFromInstanceType(agent.config.instanceType).cpu * 100000),
|
|
1356
1431
|
CpuPeriod: 100000,
|
|
1357
1432
|
RestartPolicy: {
|
|
1358
1433
|
Name: "on-failure",
|
|
1359
|
-
MaximumRetryCount:
|
|
1434
|
+
MaximumRetryCount: 3, // Default max restarts
|
|
1360
1435
|
},
|
|
1361
1436
|
NetworkMode: this.networkName,
|
|
1362
1437
|
...this.preparePortConfiguration(agent),
|
|
@@ -1485,10 +1560,7 @@ class DockerManager {
|
|
|
1485
1560
|
uptime: Date.now() - startTime.getTime(),
|
|
1486
1561
|
health: containerData.State.Health?.Status || "unknown",
|
|
1487
1562
|
restartCount: containerData.RestartCount,
|
|
1488
|
-
resources:
|
|
1489
|
-
memory: agent.config.resources.memory,
|
|
1490
|
-
cpu: agent.config.resources.cpu,
|
|
1491
|
-
},
|
|
1563
|
+
resources: AgentConfig.getResourcesFromInstanceType(agent.config.instanceType),
|
|
1492
1564
|
};
|
|
1493
1565
|
} catch (error) {
|
|
1494
1566
|
return { status: "error", error: error.message };
|
|
@@ -1548,7 +1620,7 @@ class DockerManager {
|
|
|
1548
1620
|
/**
|
|
1549
1621
|
* Create build context for agent
|
|
1550
1622
|
*/
|
|
1551
|
-
async createAgentBuildContext(agent) {
|
|
1623
|
+
async createAgentBuildContext(agent, options = {}) {
|
|
1552
1624
|
const contextDir = path.join(
|
|
1553
1625
|
__dirname,
|
|
1554
1626
|
`../../.build-context-${agent.name}`
|
|
@@ -1556,12 +1628,44 @@ class DockerManager {
|
|
|
1556
1628
|
await fs.ensureDir(contextDir);
|
|
1557
1629
|
|
|
1558
1630
|
// Get the base image for this agent
|
|
1559
|
-
|
|
1560
|
-
|
|
1631
|
+
// Production builds can override the base image via baseImageOverride option
|
|
1632
|
+
let baseImageName;
|
|
1633
|
+
if (options.isProductionBuild && options.baseImageOverride) {
|
|
1634
|
+
baseImageName = options.baseImageOverride;
|
|
1635
|
+
this.logger.info(`🔧 Using production base image override: ${baseImageName} (instead of ${agent.config.docker?.baseImage || this.defaultBaseImageName})`);
|
|
1636
|
+
} else {
|
|
1637
|
+
baseImageName = agent.config.docker?.baseImage || this.defaultBaseImageName;
|
|
1638
|
+
}
|
|
1639
|
+
|
|
1640
|
+
// Generate environment variables
|
|
1641
|
+
// For production builds, these need to be embedded in the image
|
|
1642
|
+
// For normal builds, they're injected at container creation time via startAgent()
|
|
1643
|
+
const env = AgentConfig.generateContainerEnv(agent);
|
|
1644
|
+
|
|
1645
|
+
// Embed env vars in Dockerfile for production builds
|
|
1646
|
+
// For normal builds, env vars are injected at container creation time
|
|
1647
|
+
const isProductionBuild = options.isProductionBuild || false;
|
|
1648
|
+
|
|
1649
|
+
let envStatements = '';
|
|
1650
|
+
if (isProductionBuild) {
|
|
1651
|
+
// Embed environment variables in Dockerfile for production builds
|
|
1652
|
+
// This ensures they're available when the image is deployed elsewhere
|
|
1653
|
+
envStatements = Object.entries(env)
|
|
1654
|
+
.map(([key, value]) => {
|
|
1655
|
+
// Escape special characters in values for Dockerfile ENV
|
|
1656
|
+
const escapedValue = String(value).replace(/\$/g, '$$$$').replace(/"/g, '\\"');
|
|
1657
|
+
return `ENV ${key}="${escapedValue}"`;
|
|
1658
|
+
})
|
|
1659
|
+
.join('\n');
|
|
1660
|
+
|
|
1661
|
+
this.logger.info(`🔌 Production build: Embedding environment variables in Dockerfile`);
|
|
1662
|
+
this.logger.info(` - DOCKER_PORT: ${env.DOCKER_PORT || 'not set'}`);
|
|
1663
|
+
}
|
|
1561
1664
|
|
|
1562
1665
|
// Create Dockerfile for agent
|
|
1563
1666
|
const dockerfile = `FROM ${baseImageName}
|
|
1564
1667
|
COPY agent-code/ /app/agent-code/
|
|
1668
|
+
${envStatements}
|
|
1565
1669
|
USER dankuser
|
|
1566
1670
|
`;
|
|
1567
1671
|
|
|
@@ -1574,6 +1678,7 @@ USER dankuser
|
|
|
1574
1678
|
// Create basic agent code structure
|
|
1575
1679
|
// Generate handlers from agent configuration
|
|
1576
1680
|
const handlersCode = this.generateHandlersCode(agent);
|
|
1681
|
+
const routesCode = this.generateRoutesCode(agent);
|
|
1577
1682
|
|
|
1578
1683
|
const agentCode = `
|
|
1579
1684
|
// Agent: ${agent.name}
|
|
@@ -1615,7 +1720,8 @@ module.exports = {
|
|
|
1615
1720
|
}, 10000);
|
|
1616
1721
|
},
|
|
1617
1722
|
|
|
1618
|
-
handlers: ${handlersCode}
|
|
1723
|
+
handlers: ${handlersCode},
|
|
1724
|
+
routes: ${routesCode}
|
|
1619
1725
|
};
|
|
1620
1726
|
`;
|
|
1621
1727
|
|
|
@@ -1642,19 +1748,21 @@ module.exports = {
|
|
|
1642
1748
|
|
|
1643
1749
|
// Always bind the main agent port
|
|
1644
1750
|
const mainPort = agent.config.docker?.port || DOCKER_CONFIG.defaultPort;
|
|
1751
|
+
this.logger.info(`🔌 Binding main agent port: ${mainPort} (from docker.port: ${agent.config.docker?.port || 'default'})`);
|
|
1645
1752
|
portBindings[`${mainPort}/tcp`] = [{ HostPort: mainPort.toString() }];
|
|
1646
1753
|
|
|
1647
|
-
//
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1754
|
+
// Only bind HTTP port if HTTP is explicitly enabled AND different from main port
|
|
1755
|
+
// Check both http.enabled and communication.httpApi.enabled to be safe
|
|
1756
|
+
const httpEnabled = agent.config.http?.enabled === true;
|
|
1757
|
+
const httpApiEnabled = agent.config.communication?.httpApi?.enabled === true;
|
|
1758
|
+
if (httpEnabled || httpApiEnabled) {
|
|
1759
|
+
const httpPort = agent.config.http?.port;
|
|
1760
|
+
if (httpPort && httpPort !== mainPort) {
|
|
1651
1761
|
portBindings[`${httpPort}/tcp`] = [{ HostPort: httpPort.toString() }];
|
|
1652
1762
|
}
|
|
1653
1763
|
}
|
|
1654
1764
|
|
|
1655
|
-
//
|
|
1656
|
-
const healthPort = DOCKER_CONFIG.healthCheckPort;
|
|
1657
|
-
portBindings[`${healthPort}/tcp`] = [{ HostPort: healthPort.toString() }];
|
|
1765
|
+
// Health check uses the same port as the main agent port, so no separate binding needed
|
|
1658
1766
|
|
|
1659
1767
|
portConfig.PortBindings = portBindings;
|
|
1660
1768
|
return portConfig;
|
|
@@ -1670,17 +1778,18 @@ module.exports = {
|
|
|
1670
1778
|
const mainPort = agent.config.docker?.port || DOCKER_CONFIG.defaultPort;
|
|
1671
1779
|
exposedPorts[`${mainPort}/tcp`] = {};
|
|
1672
1780
|
|
|
1673
|
-
//
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1781
|
+
// Only expose HTTP port if HTTP is explicitly enabled AND different from main port
|
|
1782
|
+
// Check both http.enabled and communication.httpApi.enabled to be safe
|
|
1783
|
+
const httpEnabled = agent.config.http?.enabled === true;
|
|
1784
|
+
const httpApiEnabled = agent.config.communication?.httpApi?.enabled === true;
|
|
1785
|
+
if (httpEnabled || httpApiEnabled) {
|
|
1786
|
+
const httpPort = agent.config.http?.port;
|
|
1787
|
+
if (httpPort && httpPort !== mainPort) {
|
|
1677
1788
|
exposedPorts[`${httpPort}/tcp`] = {};
|
|
1678
1789
|
}
|
|
1679
1790
|
}
|
|
1680
1791
|
|
|
1681
|
-
//
|
|
1682
|
-
const healthPort = DOCKER_CONFIG.healthCheckPort;
|
|
1683
|
-
exposedPorts[`${healthPort}/tcp`] = {};
|
|
1792
|
+
// Health check uses the same port as the main agent port, so no separate exposure needed
|
|
1684
1793
|
|
|
1685
1794
|
return { ExposedPorts: exposedPorts };
|
|
1686
1795
|
}
|
package/lib/project.js
CHANGED
|
@@ -85,10 +85,7 @@ module.exports = {
|
|
|
85
85
|
protocol: 'http',
|
|
86
86
|
port: 3000
|
|
87
87
|
})
|
|
88
|
-
.
|
|
89
|
-
memory: '512m',
|
|
90
|
-
cpu: 1
|
|
91
|
-
})
|
|
88
|
+
.setInstanceType('small')
|
|
92
89
|
// Event handlers for prompt modification and response enhancement
|
|
93
90
|
.addHandler('request_output:start', (data) => {
|
|
94
91
|
console.log('[Prompt Agent] Processing prompt:', data.conversationId);
|