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.
@@ -56,11 +56,17 @@ class AgentRuntime {
56
56
  this.httpEnabled = process.env.HTTP_ENABLED === "true";
57
57
  this.httpPort = parseInt(process.env.HTTP_PORT) || 3000;
58
58
  this.httpHost = process.env.HTTP_HOST || "0.0.0.0";
59
+
60
+ // Main agent port (used for health, prompting, and optionally HTTP API)
61
+ this.mainPort = parseInt(process.env.DOCKER_PORT) || 3000;
62
+ logger.info(`🔌 Main agent port configured: ${this.mainPort} (from DOCKER_PORT: ${process.env.DOCKER_PORT || 'default'})`);
59
63
 
60
- // Setup express servers
61
- this.healthApp = express(); // Health check server (always running)
62
- this.httpApp = null; // Main HTTP server (optional)
64
+ // Single Express app for health, prompting, and HTTP API
65
+ this.mainApp = express();
66
+ this.mainApp.use(express.json({ limit: "10mb" }));
67
+ this.mainApp.use(express.urlencoded({ extended: true, limit: "10mb" }));
63
68
 
69
+ // Setup health endpoints on main app
64
70
  this.setupHealthEndpoints();
65
71
  }
66
72
 
@@ -80,18 +86,25 @@ class AgentRuntime {
80
86
  // Setup agent handlers
81
87
  await this.setupHandlers();
82
88
 
83
- // Start health check server
84
- this.startHealthServer();
85
-
86
- // Start HTTP server if enabled
89
+ // Setup HTTP middleware (CORS, rate limiting) if HTTP is enabled
90
+ // This must be done BEFORE routes are added
87
91
  if (this.httpEnabled) {
88
- await this.setupHttpServer();
89
- this.startHttpServer();
92
+ await this.setupHttpMiddleware();
90
93
  }
91
94
 
92
- // Setup direct prompting server
95
+ // Setup user routes on main app (if any routes exist in agent code)
96
+ // Routes are always added to the main app (same server as health and prompting)
97
+ await this.setupAgentRoutes();
98
+
99
+ // Setup direct prompting server (adds /prompt endpoint to main app)
93
100
  await this.setupDirectPromptingServer();
94
101
 
102
+ // Set up 404 and error handlers AFTER all routes (including /prompt) are registered
103
+ this.setupDefaultRoutes();
104
+
105
+ // Start the main server (handles health, prompting, and user routes)
106
+ this.startMainServer();
107
+
95
108
  // Mark as running
96
109
  this.isRunning = true;
97
110
 
@@ -418,10 +431,10 @@ class AgentRuntime {
418
431
  }
419
432
 
420
433
  /**
421
- * Setup health check endpoints
434
+ * Setup health check endpoints on main app
422
435
  */
423
436
  setupHealthEndpoints() {
424
- this.healthApp.get("/health", (req, res) => {
437
+ this.mainApp.get("/health", (req, res) => {
425
438
  res.json({
426
439
  status: this.isRunning ? "healthy" : "starting",
427
440
  agent: {
@@ -439,7 +452,7 @@ class AgentRuntime {
439
452
  });
440
453
  });
441
454
 
442
- this.healthApp.get("/status", (req, res) => {
455
+ this.mainApp.get("/status", (req, res) => {
443
456
  res.json({
444
457
  agent: {
445
458
  name: this.agentName,
@@ -456,7 +469,7 @@ class AgentRuntime {
456
469
  http: {
457
470
  enabled: this.httpEnabled,
458
471
  port: this.httpEnabled ? this.httpPort : null,
459
- routes: this.httpEnabled && this.httpApp ? this.getRoutesList() : [],
472
+ routes: this.httpEnabled && this.mainApp ? this.getRoutesList() : [],
460
473
  },
461
474
  handlers: Array.from(this.handlers.keys()),
462
475
  environment: {
@@ -469,22 +482,15 @@ class AgentRuntime {
469
482
  }
470
483
 
471
484
  /**
472
- * Setup HTTP server with Express.js
485
+ * Setup HTTP middleware (CORS, rate limiting) on main app
486
+ * This is only called if HTTP is explicitly enabled via configuration
473
487
  */
474
- async setupHttpServer() {
475
- if (!this.httpEnabled) return;
476
-
477
- logger.info(`Setting up HTTP server on port ${this.httpPort}`);
478
-
479
- this.httpApp = express();
480
-
481
- // Basic middleware
482
- this.httpApp.use(express.json({ limit: "10mb" }));
483
- this.httpApp.use(express.urlencoded({ extended: true, limit: "10mb" }));
488
+ async setupHttpMiddleware() {
489
+ logger.info(`Setting up HTTP middleware on main server`);
484
490
 
485
491
  // CORS if enabled
486
492
  if (process.env.HTTP_CORS !== "false") {
487
- this.httpApp.use((req, res, next) => {
493
+ this.mainApp.use((req, res, next) => {
488
494
  res.header("Access-Control-Allow-Origin", "*");
489
495
  res.header(
490
496
  "Access-Control-Allow-Methods",
@@ -512,50 +518,31 @@ class AgentRuntime {
512
518
  max: parseInt(process.env.HTTP_RATE_LIMIT_MAX) || 100,
513
519
  message: process.env.HTTP_RATE_LIMIT_MESSAGE || "Too many requests",
514
520
  });
515
- this.httpApp.use(limiter);
521
+ this.mainApp.use(limiter);
516
522
  }
517
-
518
- // Setup routes from agent configuration
519
- await this.setupAgentRoutes();
520
-
521
- // Default routes
522
- this.httpApp.get("/", (req, res) => {
523
- res.json({
524
- message: `🤖 ${this.agentName} HTTP Server`,
525
- agent: this.agentName,
526
- version: "1.0.0",
527
- endpoints: this.getRoutesList(),
528
- timestamp: new Date().toISOString(),
529
- });
530
- });
531
-
532
- // 404 handler
533
- this.httpApp.use((req, res) => {
534
- res.status(404).json({
535
- error: "Not Found",
536
- message: `Cannot ${req.method} ${req.path}`,
537
- availableRoutes: this.getRoutesList(),
538
- });
539
- });
540
-
541
- // Error handler
542
- this.httpApp.use((err, req, res, next) => {
543
- logger.error("HTTP server error:", err);
544
- res.status(500).json({
545
- error: "Internal Server Error",
546
- message:
547
- process.env.NODE_ENV === "development"
548
- ? err.message
549
- : "Something went wrong",
550
- });
551
- });
552
523
  }
553
524
 
554
525
  /**
555
526
  * Setup routes defined in agent configuration
527
+ * Routes are always added to the main app (same server as health and prompting)
528
+ * NOTE: Does NOT set up 404 handler - that must be done AFTER all routes including /prompt
556
529
  */
557
530
  async setupAgentRoutes() {
558
- if (!this.agentCode || !this.agentCode.routes) return;
531
+ if (!this.agentCode || !this.agentCode.routes) {
532
+ // Set up default root route only (404 handler will be set up later)
533
+ this.mainApp.get("/", (req, res) => {
534
+ res.json({
535
+ message: `🤖 ${this.agentName} HTTP Server`,
536
+ agent: this.agentName,
537
+ version: "1.0.0",
538
+ endpoints: this.getRoutesList(),
539
+ timestamp: new Date().toISOString(),
540
+ });
541
+ });
542
+ return;
543
+ }
544
+
545
+ logger.info(`Setting up user routes on main server`);
559
546
 
560
547
  // Setup routes from agent code
561
548
  Object.entries(this.agentCode.routes).forEach(([path, handlers]) => {
@@ -563,14 +550,9 @@ class AgentRuntime {
563
550
  Object.entries(handlers).forEach(([method, handler]) => {
564
551
  if (typeof handler === "function") {
565
552
  const lowerMethod = method.toLowerCase();
566
- if (this.httpApp[lowerMethod]) {
567
- // Wrap handler to emit tool events
568
- const wrappedHandler = this.wrapHttpHandlerWithEvents(
569
- method,
570
- path,
571
- handler
572
- );
573
- this.httpApp[lowerMethod](path, wrappedHandler);
553
+ if (this.mainApp[lowerMethod]) {
554
+ // Register route handler directly on main app
555
+ this.mainApp[lowerMethod](path, handler);
574
556
  logger.info(`Registered route: ${method.toUpperCase()} ${path}`);
575
557
  }
576
558
  }
@@ -582,21 +564,59 @@ class AgentRuntime {
582
564
  if (this.agentCode.middleware && Array.isArray(this.agentCode.middleware)) {
583
565
  this.agentCode.middleware.forEach((middleware) => {
584
566
  if (typeof middleware === "function") {
585
- this.httpApp.use(middleware);
567
+ this.mainApp.use(middleware);
586
568
  logger.info("Registered custom middleware");
587
569
  }
588
570
  });
589
571
  }
572
+
573
+ // Set up default root route (404 handler will be set up later, after /prompt)
574
+ this.mainApp.get("/", (req, res) => {
575
+ res.json({
576
+ message: `🤖 ${this.agentName} HTTP Server`,
577
+ agent: this.agentName,
578
+ version: "1.0.0",
579
+ endpoints: this.getRoutesList(),
580
+ timestamp: new Date().toISOString(),
581
+ });
582
+ });
583
+ }
584
+
585
+ /**
586
+ * Setup 404 and error handlers on main app
587
+ * MUST be called AFTER all routes (including /prompt) are registered
588
+ */
589
+ setupDefaultRoutes() {
590
+ // 404 handler (must be after ALL routes, including /prompt)
591
+ this.mainApp.use((req, res) => {
592
+ res.status(404).json({
593
+ error: "Not Found",
594
+ message: `Cannot ${req.method} ${req.path}`,
595
+ availableRoutes: this.getRoutesList(),
596
+ });
597
+ });
598
+
599
+ // Error handler (must be last)
600
+ this.mainApp.use((err, req, res, next) => {
601
+ logger.error("HTTP server error:", err);
602
+ res.status(500).json({
603
+ error: "Internal Server Error",
604
+ message:
605
+ process.env.NODE_ENV === "development"
606
+ ? err.message
607
+ : "Something went wrong",
608
+ });
609
+ });
590
610
  }
591
611
 
592
612
  /**
593
613
  * Get list of registered routes
594
614
  */
595
615
  getRoutesList() {
596
- if (!this.httpApp) return [];
616
+ if (!this.mainApp) return [];
597
617
 
598
618
  const routes = [];
599
- this.httpApp._router.stack.forEach((middleware) => {
619
+ this.mainApp._router.stack.forEach((middleware) => {
600
620
  if (middleware.route) {
601
621
  const methods = Object.keys(middleware.route.methods);
602
622
  routes.push({
@@ -610,167 +630,66 @@ class AgentRuntime {
610
630
  }
611
631
 
612
632
  /**
613
- * Start health check server
633
+ * Start main server (handles health, prompting, and optionally HTTP API)
634
+ * The main server ALWAYS runs on the port specified by setPromptingServer (or default 3000)
614
635
  */
615
- startHealthServer() {
616
- const port = process.env.HEALTH_PORT || 3001;
617
-
618
- this.healthApp.listen(port, "0.0.0.0", () => {
619
- logger.info(`Health server listening on port ${port}`);
620
- });
621
- }
622
-
623
- /**
624
- * Start HTTP server
625
- */
626
- startHttpServer() {
627
- if (!this.httpEnabled || !this.httpApp) return;
628
-
629
- this.httpApp.listen(this.httpPort, this.httpHost, () => {
630
- logger.info(
631
- `🌐 HTTP server listening on ${this.httpHost}:${this.httpPort}`
632
- );
633
- logger.info(
634
- `🔗 Agent HTTP endpoint: http://${this.httpHost}:${this.httpPort}`
635
- );
636
+ startMainServer() {
637
+ const port = this.mainPort;
638
+ const host = "0.0.0.0";
639
+
640
+ this.mainApp.listen(port, host, () => {
641
+ logger.info(`🌐 Main HTTP server listening on ${host}:${port}`);
642
+ logger.info(`🔗 Health check: GET http://localhost:${port}/health`);
643
+ logger.info(`🔗 Status: GET http://localhost:${port}/status`);
644
+
645
+ const directPromptingEnabled =
646
+ process.env.DIRECT_PROMPTING_ENABLED !== "false";
647
+ if (directPromptingEnabled) {
648
+ logger.info(`📡 Direct prompting: POST http://localhost:${port}/prompt`);
649
+ }
650
+
651
+ if (this.httpEnabled) {
652
+ logger.info(`🔗 HTTP API routes: http://localhost:${port}`);
653
+ }
654
+
655
+ logger.info(`✅ Main server ready on port ${port}`);
636
656
  });
637
657
  }
638
658
 
639
659
  /**
640
- * Setup direct prompting server (WebSocket/HTTP)
660
+ * Setup direct prompting server (HTTP only)
661
+ * Always sets up /prompt endpoint - the main HTTP server always runs
641
662
  */
642
663
  async setupDirectPromptingServer() {
643
- const directPromptingEnabled =
644
- process.env.DIRECT_PROMPTING_ENABLED !== "false";
645
- logger.info(`🔍 Direct prompting enabled: ${directPromptingEnabled}`);
646
- if (!directPromptingEnabled) return;
647
-
648
- const protocol = process.env.DIRECT_PROMPTING_PROTOCOL || "websocket";
649
664
  const port = parseInt(process.env.DOCKER_PORT) || 3000;
650
-
651
- logger.info(
652
- `Setting up direct prompting server (${protocol}) on port ${port}`
653
- );
654
-
655
- if (protocol === "websocket") {
656
- logger.info("📡 Setting up WebSocket server...");
657
- await this.setupWebSocketServer(port);
658
- } else if (protocol === "http") {
659
- logger.info("🌐 Setting up HTTP prompting endpoints...");
665
+ const directPromptingEnabled = process.env.DIRECT_PROMPTING_ENABLED !== "false";
666
+
667
+ logger.info(`🔍 Checking direct prompting setup:`);
668
+ logger.info(` - DOCKER_PORT: ${process.env.DOCKER_PORT || 'not set (defaulting to 3000)'}`);
669
+ logger.info(` - DIRECT_PROMPTING_ENABLED: ${process.env.DIRECT_PROMPTING_ENABLED || 'not set'}`);
670
+ logger.info(` - Direct prompting enabled: ${directPromptingEnabled}`);
671
+
672
+ // Always set up the /prompt endpoint if DIRECT_PROMPTING_ENABLED is not explicitly "false"
673
+ // The main HTTP server always runs, so the endpoint should be available
674
+ if (directPromptingEnabled) {
675
+ logger.info(`🌐 Setting up HTTP prompting endpoints on port ${port}...`);
660
676
  await this.setupHttpPromptingEndpoints();
677
+ logger.info("✅ Direct prompting endpoint (/prompt) configured");
661
678
  } else {
662
- logger.warn(`⚠️ Unknown protocol: ${protocol}`);
679
+ logger.warn(`⚠️ Direct prompting is DISABLED - /prompt endpoint will not be available`);
680
+ logger.warn(` Set DIRECT_PROMPTING_ENABLED=true or call setPromptingServer() to enable`);
663
681
  }
664
-
665
- logger.info("✅ Direct prompting server setup completed");
666
- }
667
-
668
- /**
669
- * Setup WebSocket server for direct prompting
670
- */
671
- async setupWebSocketServer(port) {
672
- const WebSocket = require("ws");
673
- const maxConnections =
674
- parseInt(process.env.DIRECT_PROMPTING_MAX_CONNECTIONS) || 100;
675
- const authentication =
676
- process.env.DIRECT_PROMPTING_AUTHENTICATION === "true";
677
-
678
- this.wsServer = new WebSocket.Server({
679
- port: port,
680
- host: "0.0.0.0",
681
- maxClients: maxConnections,
682
- });
683
-
684
- this.activeConnections = 0;
685
-
686
- logger.info(
687
- `WebSocket server configured with max ${maxConnections} connections, auth: ${authentication}`
688
- );
689
-
690
- this.wsServer.on("connection", (ws, req) => {
691
- const clientId = require("uuid").v4();
692
- this.activeConnections++;
693
- logger.info(
694
- `WebSocket client connected: ${clientId} (${this.activeConnections}/${maxConnections})`
695
- );
696
-
697
- ws.on("message", async (message) => {
698
- try {
699
- const data = JSON.parse(message.toString());
700
- const { prompt, conversationId, metadata } = data;
701
-
702
- if (!prompt) {
703
- ws.send(
704
- JSON.stringify({
705
- error: "Prompt is required",
706
- timestamp: new Date().toISOString(),
707
- })
708
- );
709
- return;
710
- }
711
-
712
- // Process the prompt with LLM
713
- const response = await this.processDirectPrompt(prompt, {
714
- conversationId,
715
- metadata,
716
- clientId,
717
- protocol: "websocket",
718
- });
719
-
720
- // Send response back
721
- ws.send(
722
- JSON.stringify({
723
- response: response.content,
724
- conversationId: conversationId || response.conversationId,
725
- metadata: response.metadata,
726
- timestamp: new Date().toISOString(),
727
- })
728
- );
729
- } catch (error) {
730
- logger.error("WebSocket message processing error:", error);
731
- ws.send(
732
- JSON.stringify({
733
- error: "Failed to process prompt",
734
- message: error.message,
735
- timestamp: new Date().toISOString(),
736
- })
737
- );
738
- }
739
- });
740
-
741
- ws.on("close", () => {
742
- this.activeConnections--;
743
- logger.info(
744
- `WebSocket client disconnected: ${clientId} (${this.activeConnections}/${maxConnections})`
745
- );
746
- });
747
-
748
- ws.on("error", (error) => {
749
- logger.error(`WebSocket error for client ${clientId}:`, error);
750
- });
751
- });
752
-
753
- logger.info(`🔌 WebSocket server listening on port ${port}`);
754
- logger.info(`🔗 Direct prompting endpoint: ws://localhost:${port}`);
755
682
  }
756
683
 
757
684
  /**
758
- * Setup HTTP endpoints for direct prompting
685
+ * Setup HTTP endpoints for direct prompting (adds /prompt endpoint to main app)
759
686
  */
760
687
  async setupHttpPromptingEndpoints() {
761
- logger.info("🔧 Setting up HTTP prompting endpoints...");
762
- const port = parseInt(process.env.DOCKER_PORT) || 3000;
763
- logger.info(`Port: ${port}, httpApp exists: ${!!this.httpApp}`);
764
-
765
- if (!this.httpApp) {
766
- // Create a minimal HTTP app for prompting if main HTTP is disabled
767
- logger.info("Creating new Express app for HTTP prompting");
768
- this.httpApp = express();
769
- this.httpApp.use(express.json({ limit: "10mb" }));
770
- }
688
+ logger.info("🔧 Setting up HTTP prompting endpoints on main server...");
771
689
 
772
- // Direct prompting endpoint
773
- this.httpApp.post("/prompt", async (req, res) => {
690
+ // Direct prompting endpoint on main app
691
+ this.mainApp.post("/prompt", async (req, res) => {
692
+ logger.info(`📥 Received POST /prompt request from ${req.ip || 'unknown'}`);
774
693
  try {
775
694
  const { prompt, conversationId, metadata } = req.body;
776
695
 
@@ -804,34 +723,9 @@ class AgentRuntime {
804
723
  }
805
724
  });
806
725
 
807
- // Start the HTTP server if it's not already running
808
- logger.info(
809
- `HTTP server status: httpEnabled=${this.httpEnabled}, port=${port}`
810
- );
811
-
812
- if (!this.httpEnabled) {
813
- // Only start if main HTTP server is not enabled
814
- logger.info(`Starting HTTP direct prompting server on port ${port}...`);
815
-
816
- try {
817
- const server = this.httpApp.listen(port, "0.0.0.0", () => {
818
- logger.info(
819
- `🌐 HTTP direct prompting server listening on port ${port}`
820
- );
821
- logger.info(
822
- `📡 Direct prompting endpoint: POST http://localhost:${port}/prompt`
823
- );
824
- });
825
-
826
- server.on("error", (error) => {
827
- logger.error(`HTTP server error:`, error);
828
- });
829
- } catch (error) {
830
- logger.error(`Failed to start HTTP server:`, error);
831
- }
832
- } else {
833
- logger.info(`📡 HTTP prompting endpoint added: POST /prompt`);
834
- }
726
+ logger.info(`📡 HTTP prompting endpoint added: POST /prompt`);
727
+ logger.info(` Full URL: http://localhost:${this.mainPort}/prompt`);
728
+ logger.info(` Full URL: http://127.0.0.1:${this.mainPort}/prompt`);
835
729
  }
836
730
 
837
731
  /**
@@ -948,113 +842,6 @@ class AgentRuntime {
948
842
  }
949
843
  }
950
844
 
951
- /**
952
- * Wrap HTTP handler to emit tool events
953
- */
954
- wrapHttpHandlerWithEvents(method, path, originalHandler) {
955
- return async (req, res) => {
956
- const startTime = Date.now();
957
- const requestId = require("uuid").v4();
958
-
959
- try {
960
- // Emit call event
961
- this.emitEvent("tool:http-server:call", {
962
- requestId,
963
- method: method.toUpperCase(),
964
- path,
965
- headers: req.headers,
966
- body: req.body,
967
- query: req.query,
968
- params: req.params,
969
- timestamp: new Date().toISOString(),
970
- });
971
-
972
- // Emit specific method call event
973
- this.emitEvent(`tool:http-server:call:${method.toLowerCase()}`, {
974
- requestId,
975
- path,
976
- headers: req.headers,
977
- body: req.body,
978
- query: req.query,
979
- params: req.params,
980
- timestamp: new Date().toISOString(),
981
- });
982
-
983
- // Capture response data
984
- const originalSend = res.send;
985
- const originalJson = res.json;
986
- let responseData = null;
987
- let statusCode = 200;
988
-
989
- res.send = function (data) {
990
- responseData = data;
991
- statusCode = res.statusCode;
992
- return originalSend.call(this, data);
993
- };
994
-
995
- res.json = function (data) {
996
- responseData = data;
997
- statusCode = res.statusCode;
998
- return originalJson.call(this, data);
999
- };
1000
-
1001
- // Execute original handler
1002
- const result = await originalHandler(req, res);
1003
-
1004
- const processingTime = Date.now() - startTime;
1005
-
1006
- // Emit response event
1007
- this.emitEvent("tool:http-server:response", {
1008
- requestId,
1009
- method: method.toUpperCase(),
1010
- path,
1011
- statusCode,
1012
- responseData,
1013
- processingTime,
1014
- timestamp: new Date().toISOString(),
1015
- });
1016
-
1017
- // Emit specific method response event
1018
- this.emitEvent(`tool:http-server:response:${method.toLowerCase()}`, {
1019
- requestId,
1020
- path,
1021
- statusCode,
1022
- responseData,
1023
- processingTime,
1024
- timestamp: new Date().toISOString(),
1025
- });
1026
-
1027
- // Emit wildcard events
1028
- this.emitEvent("tool:http-server:*", {
1029
- type: "response",
1030
- requestId,
1031
- method: method.toUpperCase(),
1032
- path,
1033
- statusCode,
1034
- responseData,
1035
- processingTime,
1036
- timestamp: new Date().toISOString(),
1037
- });
1038
-
1039
- return result;
1040
- } catch (error) {
1041
- const processingTime = Date.now() - startTime;
1042
-
1043
- // Emit error event
1044
- this.emitEvent("tool:http-server:error", {
1045
- requestId,
1046
- method: method.toUpperCase(),
1047
- path,
1048
- error: error.message,
1049
- stack: error.stack,
1050
- processingTime,
1051
- timestamp: new Date().toISOString(),
1052
- });
1053
-
1054
- throw error;
1055
- }
1056
- };
1057
- }
1058
845
 
1059
846
  /**
1060
847
  * Create tools proxy for agent runtime