@gencode/console 0.0.1
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/dist/client/assets/main-ACHOM3RY.css +1 -0
- package/dist/client/assets/main-BC36yw7y.js +131 -0
- package/dist/client/index.html +13 -0
- package/dist/server/callback-server.d.ts +56 -0
- package/dist/server/callback-server.d.ts.map +1 -0
- package/dist/server/callback-server.js +284 -0
- package/dist/server/callback-server.js.map +1 -0
- package/dist/server/cli-runner.d.ts +55 -0
- package/dist/server/cli-runner.d.ts.map +1 -0
- package/dist/server/cli-runner.js +191 -0
- package/dist/server/cli-runner.js.map +1 -0
- package/dist/server/config.d.ts +48 -0
- package/dist/server/config.d.ts.map +1 -0
- package/dist/server/config.js +82 -0
- package/dist/server/config.js.map +1 -0
- package/dist/server/index.d.ts +8 -0
- package/dist/server/index.d.ts.map +1 -0
- package/dist/server/index.js +45 -0
- package/dist/server/index.js.map +1 -0
- package/dist/server/server.d.ts +17 -0
- package/dist/server/server.d.ts.map +1 -0
- package/dist/server/server.js +168 -0
- package/dist/server/server.js.map +1 -0
- package/dist/server/ws-handler.d.ts +59 -0
- package/dist/server/ws-handler.d.ts.map +1 -0
- package/dist/server/ws-handler.js +262 -0
- package/dist/server/ws-handler.js.map +1 -0
- package/dist/shared/protocol.d.ts +287 -0
- package/dist/shared/protocol.d.ts.map +1 -0
- package/dist/shared/protocol.js +8 -0
- package/dist/shared/protocol.js.map +1 -0
- package/package.json +60 -0
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Server configuration management.
|
|
3
|
+
* Reads from environment variables with validation.
|
|
4
|
+
*/
|
|
5
|
+
export type ServerConfig = {
|
|
6
|
+
/** HTTP server port */
|
|
7
|
+
port: number;
|
|
8
|
+
/** User data directory (must be provided) */
|
|
9
|
+
dataDir: string;
|
|
10
|
+
/** Callback server port (for receiving CLI callbacks) */
|
|
11
|
+
callbackPort: number;
|
|
12
|
+
/** LLM configuration */
|
|
13
|
+
llm: {
|
|
14
|
+
baseUrl: string;
|
|
15
|
+
apiKey?: string;
|
|
16
|
+
authToken?: string;
|
|
17
|
+
model: string;
|
|
18
|
+
contextWindow?: number;
|
|
19
|
+
};
|
|
20
|
+
/** CLI binary to execute (default: aimax) */
|
|
21
|
+
cliBin: string;
|
|
22
|
+
/** Path to node executable (used when cliBin is node) */
|
|
23
|
+
nodeBinPath: string;
|
|
24
|
+
/** Path to aimax CLI script (used when cliBin is node) */
|
|
25
|
+
cliScriptPath: string;
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* Loads server configuration from environment variables.
|
|
29
|
+
*
|
|
30
|
+
* Required environment variables:
|
|
31
|
+
* - AIMAX_DATA_DIR: User data directory
|
|
32
|
+
* - AIMAX_BASE_URL: LLM API base URL
|
|
33
|
+
* - AIMAX_MODEL: LLM model name
|
|
34
|
+
* - AIMAX_API_KEY: LLM API key (required if AIMAX_AUTH_TOKEN is missing)
|
|
35
|
+
* - AIMAX_AUTH_TOKEN: Auth token for generating API key (optional)
|
|
36
|
+
*
|
|
37
|
+
* Optional environment variables:
|
|
38
|
+
* - AIMAX_CONSOLE_PORT: HTTP server port (default: 3000)
|
|
39
|
+
* - AIMAX_CALLBACK_PORT: Callback server port (default: 3001)
|
|
40
|
+
* - AIMAX_CONTEXT_WINDOW: LLM context window size
|
|
41
|
+
* - AIMAX_CLI_BIN: CLI binary to execute (default: 'aimax')
|
|
42
|
+
* - AIMAX_NODE_BIN: Path to node executable (default: 'node', only used when cliBin is node)
|
|
43
|
+
* - AIMAX_CLI_SCRIPT: Path to aimax CLI script (used when cliBin is node)
|
|
44
|
+
*
|
|
45
|
+
* @throws {Error} If required environment variables are missing
|
|
46
|
+
*/
|
|
47
|
+
export declare function loadConfig(): ServerConfig;
|
|
48
|
+
//# sourceMappingURL=config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/server/config.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH,MAAM,MAAM,YAAY,GAAG;IACzB,uBAAuB;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,6CAA6C;IAC7C,OAAO,EAAE,MAAM,CAAC;IAChB,yDAAyD;IACzD,YAAY,EAAE,MAAM,CAAC;IACrB,wBAAwB;IACxB,GAAG,EAAE;QACH,OAAO,EAAE,MAAM,CAAC;QAChB,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,KAAK,EAAE,MAAM,CAAC;QACd,aAAa,CAAC,EAAE,MAAM,CAAC;KACxB,CAAC;IACF,6CAA6C;IAC7C,MAAM,EAAE,MAAM,CAAC;IACf,yDAAyD;IACzD,WAAW,EAAE,MAAM,CAAC;IACpB,0DAA0D;IAC1D,aAAa,EAAE,MAAM,CAAC;CACvB,CAAC;AAEF;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,UAAU,IAAI,YAAY,CAkEzC"}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Server configuration management.
|
|
3
|
+
* Reads from environment variables with validation.
|
|
4
|
+
*/
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
/**
|
|
7
|
+
* Loads server configuration from environment variables.
|
|
8
|
+
*
|
|
9
|
+
* Required environment variables:
|
|
10
|
+
* - AIMAX_DATA_DIR: User data directory
|
|
11
|
+
* - AIMAX_BASE_URL: LLM API base URL
|
|
12
|
+
* - AIMAX_MODEL: LLM model name
|
|
13
|
+
* - AIMAX_API_KEY: LLM API key (required if AIMAX_AUTH_TOKEN is missing)
|
|
14
|
+
* - AIMAX_AUTH_TOKEN: Auth token for generating API key (optional)
|
|
15
|
+
*
|
|
16
|
+
* Optional environment variables:
|
|
17
|
+
* - AIMAX_CONSOLE_PORT: HTTP server port (default: 3000)
|
|
18
|
+
* - AIMAX_CALLBACK_PORT: Callback server port (default: 3001)
|
|
19
|
+
* - AIMAX_CONTEXT_WINDOW: LLM context window size
|
|
20
|
+
* - AIMAX_CLI_BIN: CLI binary to execute (default: 'aimax')
|
|
21
|
+
* - AIMAX_NODE_BIN: Path to node executable (default: 'node', only used when cliBin is node)
|
|
22
|
+
* - AIMAX_CLI_SCRIPT: Path to aimax CLI script (used when cliBin is node)
|
|
23
|
+
*
|
|
24
|
+
* @throws {Error} If required environment variables are missing
|
|
25
|
+
*/
|
|
26
|
+
export function loadConfig() {
|
|
27
|
+
const dataDir = process.env.AIMAX_DATA_DIR;
|
|
28
|
+
if (!dataDir) {
|
|
29
|
+
throw new Error('AIMAX_DATA_DIR environment variable is required');
|
|
30
|
+
}
|
|
31
|
+
const baseUrl = process.env.AIMAX_BASE_URL;
|
|
32
|
+
if (!baseUrl) {
|
|
33
|
+
throw new Error('AIMAX_BASE_URL environment variable is required');
|
|
34
|
+
}
|
|
35
|
+
const authToken = process.env.AIMAX_AUTH_TOKEN;
|
|
36
|
+
const apiKey = process.env.AIMAX_API_KEY;
|
|
37
|
+
if (!authToken && !apiKey) {
|
|
38
|
+
throw new Error('AIMAX_API_KEY or AIMAX_AUTH_TOKEN environment variable is required');
|
|
39
|
+
}
|
|
40
|
+
const model = process.env.AIMAX_MODEL;
|
|
41
|
+
if (!model) {
|
|
42
|
+
throw new Error('AIMAX_MODEL environment variable is required');
|
|
43
|
+
}
|
|
44
|
+
const port = process.env.AIMAX_CONSOLE_PORT
|
|
45
|
+
? Number(process.env.AIMAX_CONSOLE_PORT)
|
|
46
|
+
: 3000;
|
|
47
|
+
if (isNaN(port) || port <= 0 || port > 65535) {
|
|
48
|
+
throw new Error(`Invalid AIMAX_CONSOLE_PORT: ${process.env.AIMAX_CONSOLE_PORT}`);
|
|
49
|
+
}
|
|
50
|
+
const callbackPort = process.env.AIMAX_CALLBACK_PORT
|
|
51
|
+
? Number(process.env.AIMAX_CALLBACK_PORT)
|
|
52
|
+
: 3001;
|
|
53
|
+
if (isNaN(callbackPort) || callbackPort <= 0 || callbackPort > 65535) {
|
|
54
|
+
throw new Error(`Invalid AIMAX_CALLBACK_PORT: ${process.env.AIMAX_CALLBACK_PORT}`);
|
|
55
|
+
}
|
|
56
|
+
const contextWindow = process.env.AIMAX_CONTEXT_WINDOW
|
|
57
|
+
? Number(process.env.AIMAX_CONTEXT_WINDOW)
|
|
58
|
+
: undefined;
|
|
59
|
+
if (contextWindow !== undefined && (isNaN(contextWindow) || contextWindow <= 0)) {
|
|
60
|
+
throw new Error(`Invalid AIMAX_CONTEXT_WINDOW: ${process.env.AIMAX_CONTEXT_WINDOW}`);
|
|
61
|
+
}
|
|
62
|
+
const cliBin = process.env.AIMAX_CLI_BIN || 'aimax';
|
|
63
|
+
const nodeBinPath = process.env.AIMAX_NODE_BIN || 'node';
|
|
64
|
+
const cliScriptPath = process.env.AIMAX_CLI_SCRIPT ||
|
|
65
|
+
path.resolve(process.cwd(), 'source/packages/cli/dist/bin.js');
|
|
66
|
+
return {
|
|
67
|
+
port,
|
|
68
|
+
dataDir,
|
|
69
|
+
callbackPort,
|
|
70
|
+
llm: {
|
|
71
|
+
baseUrl,
|
|
72
|
+
apiKey,
|
|
73
|
+
authToken,
|
|
74
|
+
model,
|
|
75
|
+
contextWindow,
|
|
76
|
+
},
|
|
77
|
+
cliBin,
|
|
78
|
+
nodeBinPath,
|
|
79
|
+
cliScriptPath,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/server/config.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,IAAI,MAAM,WAAW,CAAC;AAyB7B;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,UAAU,UAAU;IACxB,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC;IAC3C,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,MAAM,IAAI,KAAK,CAAC,iDAAiD,CAAC,CAAC;IACrE,CAAC;IAED,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC;IAC3C,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,MAAM,IAAI,KAAK,CAAC,iDAAiD,CAAC,CAAC;IACrE,CAAC;IAED,MAAM,SAAS,GAAG,OAAO,CAAC,GAAG,CAAC,gBAAgB,CAAC;IAC/C,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,aAAa,CAAC;IACzC,IAAI,CAAC,SAAS,IAAI,CAAC,MAAM,EAAE,CAAC;QAC1B,MAAM,IAAI,KAAK,CAAC,oEAAoE,CAAC,CAAC;IACxF,CAAC;IAED,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,WAAW,CAAC;IACtC,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,MAAM,IAAI,KAAK,CAAC,8CAA8C,CAAC,CAAC;IAClE,CAAC;IAED,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,kBAAkB;QACzC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,kBAAkB,CAAC;QACxC,CAAC,CAAC,IAAI,CAAC;IAET,IAAI,KAAK,CAAC,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,IAAI,IAAI,GAAG,KAAK,EAAE,CAAC;QAC7C,MAAM,IAAI,KAAK,CAAC,+BAA+B,OAAO,CAAC,GAAG,CAAC,kBAAkB,EAAE,CAAC,CAAC;IACnF,CAAC;IAED,MAAM,YAAY,GAAG,OAAO,CAAC,GAAG,CAAC,mBAAmB;QAClD,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,mBAAmB,CAAC;QACzC,CAAC,CAAC,IAAI,CAAC;IAET,IAAI,KAAK,CAAC,YAAY,CAAC,IAAI,YAAY,IAAI,CAAC,IAAI,YAAY,GAAG,KAAK,EAAE,CAAC;QACrE,MAAM,IAAI,KAAK,CAAC,gCAAgC,OAAO,CAAC,GAAG,CAAC,mBAAmB,EAAE,CAAC,CAAC;IACrF,CAAC;IAED,MAAM,aAAa,GAAG,OAAO,CAAC,GAAG,CAAC,oBAAoB;QACpD,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,oBAAoB,CAAC;QAC1C,CAAC,CAAC,SAAS,CAAC;IAEd,IAAI,aAAa,KAAK,SAAS,IAAI,CAAC,KAAK,CAAC,aAAa,CAAC,IAAI,aAAa,IAAI,CAAC,CAAC,EAAE,CAAC;QAChF,MAAM,IAAI,KAAK,CAAC,iCAAiC,OAAO,CAAC,GAAG,CAAC,oBAAoB,EAAE,CAAC,CAAC;IACvF,CAAC;IAED,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,aAAa,IAAI,OAAO,CAAC;IACpD,MAAM,WAAW,GAAG,OAAO,CAAC,GAAG,CAAC,cAAc,IAAI,MAAM,CAAC;IACzD,MAAM,aAAa,GAAG,OAAO,CAAC,GAAG,CAAC,gBAAgB;QAChD,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,iCAAiC,CAAC,CAAC;IAEjE,OAAO;QACL,IAAI;QACJ,OAAO;QACP,YAAY;QACZ,GAAG,EAAE;YACH,OAAO;YACP,MAAM;YACN,SAAS;YACT,KAAK;YACL,aAAa;SACd;QACD,MAAM;QACN,WAAW;QACX,aAAa;KACd,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":";AAEA;;;;GAIG"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Server entry point for @gencode/console.
|
|
4
|
+
*
|
|
5
|
+
* Starts the HTTP + WebSocket server and callback server.
|
|
6
|
+
*/
|
|
7
|
+
import { loadConfig } from './config.js';
|
|
8
|
+
import { createServer } from './server.js';
|
|
9
|
+
async function main() {
|
|
10
|
+
try {
|
|
11
|
+
// Load configuration
|
|
12
|
+
const config = loadConfig();
|
|
13
|
+
console.log('Starting AIMax Web Server...');
|
|
14
|
+
console.log(`Data directory: ${config.dataDir}`);
|
|
15
|
+
console.log(`LLM: ${config.llm.model} @ ${config.llm.baseUrl}`);
|
|
16
|
+
// Create and start server
|
|
17
|
+
const { httpServer, callbackServer } = await createServer(config);
|
|
18
|
+
// Handle shutdown signals
|
|
19
|
+
const shutdown = async () => {
|
|
20
|
+
console.log('\nShutting down...');
|
|
21
|
+
await callbackServer.stop();
|
|
22
|
+
await new Promise((resolve, reject) => {
|
|
23
|
+
httpServer.close((err) => {
|
|
24
|
+
if (err)
|
|
25
|
+
reject(err);
|
|
26
|
+
else
|
|
27
|
+
resolve();
|
|
28
|
+
});
|
|
29
|
+
});
|
|
30
|
+
process.exit(0);
|
|
31
|
+
};
|
|
32
|
+
process.on('SIGTERM', shutdown);
|
|
33
|
+
process.on('SIGINT', shutdown);
|
|
34
|
+
console.log('\nServer ready!');
|
|
35
|
+
console.log(`Web UI: http://localhost:${config.port}`);
|
|
36
|
+
console.log(`WebSocket: ws://localhost:${config.port}/ws`);
|
|
37
|
+
console.log(`Callback: http://localhost:${config.callbackPort}/callback`);
|
|
38
|
+
}
|
|
39
|
+
catch (err) {
|
|
40
|
+
console.error('Failed to start server:', err);
|
|
41
|
+
process.exit(1);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
main();
|
|
45
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":";AAEA;;;;GAIG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE3C,KAAK,UAAU,IAAI;IACjB,IAAI,CAAC;QACH,qBAAqB;QACrB,MAAM,MAAM,GAAG,UAAU,EAAE,CAAC;QAE5B,OAAO,CAAC,GAAG,CAAC,8BAA8B,CAAC,CAAC;QAC5C,OAAO,CAAC,GAAG,CAAC,mBAAmB,MAAM,CAAC,OAAO,EAAE,CAAC,CAAC;QACjD,OAAO,CAAC,GAAG,CAAC,QAAQ,MAAM,CAAC,GAAG,CAAC,KAAK,MAAM,MAAM,CAAC,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC;QAEhE,0BAA0B;QAC1B,MAAM,EAAE,UAAU,EAAE,cAAc,EAAE,GAAG,MAAM,YAAY,CAAC,MAAM,CAAC,CAAC;QAElE,0BAA0B;QAC1B,MAAM,QAAQ,GAAG,KAAK,IAAI,EAAE;YAC1B,OAAO,CAAC,GAAG,CAAC,oBAAoB,CAAC,CAAC;YAClC,MAAM,cAAc,CAAC,IAAI,EAAE,CAAC;YAC5B,MAAM,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;gBAC1C,UAAU,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;oBACvB,IAAI,GAAG;wBAAE,MAAM,CAAC,GAAG,CAAC,CAAC;;wBAChB,OAAO,EAAE,CAAC;gBACjB,CAAC,CAAC,CAAC;YACL,CAAC,CAAC,CAAC;YACH,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC,CAAC;QAEF,OAAO,CAAC,EAAE,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAC;QAChC,OAAO,CAAC,EAAE,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;QAE/B,OAAO,CAAC,GAAG,CAAC,iBAAiB,CAAC,CAAC;QAC/B,OAAO,CAAC,GAAG,CAAC,4BAA4B,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC;QACvD,OAAO,CAAC,GAAG,CAAC,6BAA6B,MAAM,CAAC,IAAI,KAAK,CAAC,CAAC;QAC3D,OAAO,CAAC,GAAG,CAAC,8BAA8B,MAAM,CAAC,YAAY,WAAW,CAAC,CAAC;IAC5E,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO,CAAC,KAAK,CAAC,yBAAyB,EAAE,GAAG,CAAC,CAAC;QAC9C,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;AACH,CAAC;AAED,IAAI,EAAE,CAAC"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HTTP + WebSocket server.
|
|
3
|
+
*
|
|
4
|
+
* Serves static files, provides API endpoints, and handles WebSocket connections.
|
|
5
|
+
*/
|
|
6
|
+
import http from 'node:http';
|
|
7
|
+
import type { ServerConfig } from './config.js';
|
|
8
|
+
import { CallbackServer } from './callback-server.js';
|
|
9
|
+
/**
|
|
10
|
+
* Create and start the HTTP + WebSocket server.
|
|
11
|
+
*/
|
|
12
|
+
export declare function createServer(config: ServerConfig): Promise<{
|
|
13
|
+
httpServer: http.Server;
|
|
14
|
+
callbackServer: CallbackServer;
|
|
15
|
+
close: () => Promise<void>;
|
|
16
|
+
}>;
|
|
17
|
+
//# sourceMappingURL=server.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/server/server.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,IAAI,MAAM,WAAW,CAAC;AAE7B,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAStD;;GAEG;AACH,wBAAsB,YAAY,CAAC,MAAM,EAAE,YAAY,GAAG,OAAO,CAAC;IAChE,UAAU,EAAE,IAAI,CAAC,MAAM,CAAC;IACxB,cAAc,EAAE,cAAc,CAAC;IAC/B,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC5B,CAAC,CAsED"}
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HTTP + WebSocket server.
|
|
3
|
+
*
|
|
4
|
+
* Serves static files, provides API endpoints, and handles WebSocket connections.
|
|
5
|
+
*/
|
|
6
|
+
import http from 'node:http';
|
|
7
|
+
import { WebSocketServer } from 'ws';
|
|
8
|
+
import { CallbackServer } from './callback-server.js';
|
|
9
|
+
import { WsHandler } from './ws-handler.js';
|
|
10
|
+
import path from 'node:path';
|
|
11
|
+
import fs from 'node:fs';
|
|
12
|
+
import { fileURLToPath } from 'node:url';
|
|
13
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
14
|
+
const __dirname = path.dirname(__filename);
|
|
15
|
+
/**
|
|
16
|
+
* Create and start the HTTP + WebSocket server.
|
|
17
|
+
*/
|
|
18
|
+
export async function createServer(config) {
|
|
19
|
+
// Start callback server first
|
|
20
|
+
const callbackServer = new CallbackServer(config.callbackPort);
|
|
21
|
+
await callbackServer.start();
|
|
22
|
+
console.log(`Callback server listening on port ${config.callbackPort}`);
|
|
23
|
+
// Create HTTP server
|
|
24
|
+
const httpServer = http.createServer((req, res) => {
|
|
25
|
+
handleHttpRequest(req, res, config);
|
|
26
|
+
});
|
|
27
|
+
// Create WebSocket server
|
|
28
|
+
const wss = new WebSocketServer({ noServer: true });
|
|
29
|
+
// Handle WebSocket upgrade
|
|
30
|
+
httpServer.on('upgrade', (request, socket, head) => {
|
|
31
|
+
if (request.url === '/ws') {
|
|
32
|
+
wss.handleUpgrade(request, socket, head, (ws) => {
|
|
33
|
+
wss.emit('connection', ws, request);
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
socket.destroy();
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
// Create a single callback handler for all WebSocket connections
|
|
41
|
+
const broadcastToAll = (message) => {
|
|
42
|
+
wss.clients.forEach((client) => {
|
|
43
|
+
if (client.readyState === 1) { // WebSocket.OPEN
|
|
44
|
+
client.send(JSON.stringify(message));
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
};
|
|
48
|
+
// Handle WebSocket connections
|
|
49
|
+
wss.on('connection', (ws) => {
|
|
50
|
+
const handler = new WsHandler(config, callbackServer);
|
|
51
|
+
handler.registerCallbackHandler(broadcastToAll);
|
|
52
|
+
handler.handleConnection(ws);
|
|
53
|
+
});
|
|
54
|
+
// Start HTTP server
|
|
55
|
+
await new Promise((resolve, reject) => {
|
|
56
|
+
httpServer.once('error', reject);
|
|
57
|
+
httpServer.listen(config.port, () => {
|
|
58
|
+
httpServer.off('error', reject);
|
|
59
|
+
console.log(`HTTP server listening on port ${config.port}`);
|
|
60
|
+
resolve();
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
return {
|
|
64
|
+
httpServer,
|
|
65
|
+
callbackServer,
|
|
66
|
+
close: async () => {
|
|
67
|
+
await new Promise((resolve, reject) => {
|
|
68
|
+
wss.close((err) => {
|
|
69
|
+
if (err)
|
|
70
|
+
reject(err);
|
|
71
|
+
else
|
|
72
|
+
resolve();
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
await new Promise((resolve, reject) => {
|
|
76
|
+
httpServer.close((err) => {
|
|
77
|
+
if (err)
|
|
78
|
+
reject(err);
|
|
79
|
+
else
|
|
80
|
+
resolve();
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
await callbackServer.stop();
|
|
84
|
+
},
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Handle HTTP requests.
|
|
89
|
+
*/
|
|
90
|
+
function handleHttpRequest(req, res, config) {
|
|
91
|
+
const url = req.url ?? '/';
|
|
92
|
+
// API endpoint: /api/config
|
|
93
|
+
if (url === '/api/config') {
|
|
94
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
95
|
+
res.end(JSON.stringify({
|
|
96
|
+
dataDir: config.dataDir,
|
|
97
|
+
}));
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
// Serve static files
|
|
101
|
+
serveStaticFile(url, res);
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Serve static files from the dist/client directory.
|
|
105
|
+
*/
|
|
106
|
+
function serveStaticFile(url, res) {
|
|
107
|
+
// Resolve file path
|
|
108
|
+
let filePath = url === '/' ? '/index.html' : url;
|
|
109
|
+
// Security: prevent directory traversal
|
|
110
|
+
if (filePath.includes('..')) {
|
|
111
|
+
res.writeHead(400, { 'Content-Type': 'text/plain' });
|
|
112
|
+
res.end('Bad Request');
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
// Determine client dist directory
|
|
116
|
+
// __dirname points to dist/server/, so ../client points to dist/client/
|
|
117
|
+
const clientDistDir = path.join(__dirname, '../client');
|
|
118
|
+
const fullPath = path.join(clientDistDir, filePath);
|
|
119
|
+
// Check if file exists
|
|
120
|
+
if (!fs.existsSync(fullPath)) {
|
|
121
|
+
// For SPA, serve index.html for all non-existent routes
|
|
122
|
+
const indexPath = path.join(clientDistDir, 'index.html');
|
|
123
|
+
if (fs.existsSync(indexPath)) {
|
|
124
|
+
serveFile(indexPath, res, 'text/html');
|
|
125
|
+
}
|
|
126
|
+
else {
|
|
127
|
+
res.writeHead(404, { 'Content-Type': 'text/plain' });
|
|
128
|
+
res.end('Not Found');
|
|
129
|
+
}
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
// Determine content type
|
|
133
|
+
const ext = path.extname(fullPath);
|
|
134
|
+
const contentType = getContentType(ext);
|
|
135
|
+
serveFile(fullPath, res, contentType);
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Serve a file with the given content type.
|
|
139
|
+
*/
|
|
140
|
+
function serveFile(filePath, res, contentType) {
|
|
141
|
+
fs.readFile(filePath, (err, data) => {
|
|
142
|
+
if (err) {
|
|
143
|
+
res.writeHead(500, { 'Content-Type': 'text/plain' });
|
|
144
|
+
res.end('Internal Server Error');
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
res.writeHead(200, { 'Content-Type': contentType });
|
|
148
|
+
res.end(data);
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Get content type based on file extension.
|
|
153
|
+
*/
|
|
154
|
+
function getContentType(ext) {
|
|
155
|
+
const types = {
|
|
156
|
+
'.html': 'text/html',
|
|
157
|
+
'.js': 'application/javascript',
|
|
158
|
+
'.css': 'text/css',
|
|
159
|
+
'.json': 'application/json',
|
|
160
|
+
'.png': 'image/png',
|
|
161
|
+
'.jpg': 'image/jpeg',
|
|
162
|
+
'.gif': 'image/gif',
|
|
163
|
+
'.svg': 'image/svg+xml',
|
|
164
|
+
'.ico': 'image/x-icon',
|
|
165
|
+
};
|
|
166
|
+
return types[ext] || 'application/octet-stream';
|
|
167
|
+
}
|
|
168
|
+
//# sourceMappingURL=server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../../src/server/server.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WebSocket message handler.
|
|
3
|
+
*
|
|
4
|
+
* Routes incoming WebSocket messages and manages agent execution state.
|
|
5
|
+
*/
|
|
6
|
+
import type { WebSocket } from 'ws';
|
|
7
|
+
import type { S2CMessage } from '../shared/protocol.js';
|
|
8
|
+
import type { ServerConfig } from './config.js';
|
|
9
|
+
import type { CallbackServer } from './callback-server.js';
|
|
10
|
+
/**
|
|
11
|
+
* Manages WebSocket connections and agent executions.
|
|
12
|
+
*/
|
|
13
|
+
export declare class WsHandler {
|
|
14
|
+
private config;
|
|
15
|
+
private callbackServer;
|
|
16
|
+
private activeRuns;
|
|
17
|
+
constructor(config: ServerConfig, callbackServer: CallbackServer);
|
|
18
|
+
/**
|
|
19
|
+
* Handle a new WebSocket connection.
|
|
20
|
+
*/
|
|
21
|
+
handleConnection(ws: WebSocket): void;
|
|
22
|
+
/**
|
|
23
|
+
* Handle incoming C2S messages.
|
|
24
|
+
*/
|
|
25
|
+
private handleMessage;
|
|
26
|
+
/**
|
|
27
|
+
* Handle commands.list message - list available slash commands.
|
|
28
|
+
*/
|
|
29
|
+
private handleCommandsList;
|
|
30
|
+
/**
|
|
31
|
+
* Handle agent.send message - start a new agent execution.
|
|
32
|
+
*/
|
|
33
|
+
private handleAgentSend;
|
|
34
|
+
/**
|
|
35
|
+
* Handle agent.abort message - abort a running agent.
|
|
36
|
+
*/
|
|
37
|
+
private handleAgentAbort;
|
|
38
|
+
/**
|
|
39
|
+
* Handle sessions.list message - list all sessions.
|
|
40
|
+
*/
|
|
41
|
+
private handleSessionsList;
|
|
42
|
+
/**
|
|
43
|
+
* Handle session.get message - get session details.
|
|
44
|
+
*/
|
|
45
|
+
private handleSessionGet;
|
|
46
|
+
/**
|
|
47
|
+
* Send a message to the WebSocket client.
|
|
48
|
+
*/
|
|
49
|
+
private send;
|
|
50
|
+
/**
|
|
51
|
+
* Send an error message to the WebSocket client.
|
|
52
|
+
*/
|
|
53
|
+
private sendError;
|
|
54
|
+
/**
|
|
55
|
+
* Register a callback handler to forward messages from CLI to WebSocket.
|
|
56
|
+
*/
|
|
57
|
+
registerCallbackHandler(broadcast: (message: S2CMessage) => void): void;
|
|
58
|
+
}
|
|
59
|
+
//# sourceMappingURL=ws-handler.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ws-handler.d.ts","sourceRoot":"","sources":["../../src/server/ws-handler.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,IAAI,CAAC;AACpC,OAAO,KAAK,EAAc,UAAU,EAAE,MAAM,uBAAuB,CAAC;AACpE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAG3D;;GAEG;AACH,qBAAa,SAAS;IAIlB,OAAO,CAAC,MAAM;IACd,OAAO,CAAC,cAAc;IAJxB,OAAO,CAAC,UAAU,CAAgC;gBAGxC,MAAM,EAAE,YAAY,EACpB,cAAc,EAAE,cAAc;IAGxC;;OAEG;IACH,gBAAgB,CAAC,EAAE,EAAE,SAAS,GAAG,IAAI;IAuBrC;;OAEG;YACW,aAAa;IA6B3B;;OAEG;YACW,kBAAkB;IAqBhC;;OAEG;YACW,eAAe;IAyD7B;;OAEG;IACH,OAAO,CAAC,gBAAgB;IASxB;;OAEG;YACW,kBAAkB;IAqBhC;;OAEG;YACW,gBAAgB;IAuB9B;;OAEG;IACH,OAAO,CAAC,IAAI;IAMZ;;OAEG;IACH,OAAO,CAAC,SAAS;IAOjB;;OAEG;IACH,uBAAuB,CAAC,SAAS,EAAE,CAAC,OAAO,EAAE,UAAU,KAAK,IAAI,GAAG,IAAI;CAcxE"}
|