@bundy-lmw/hive-server 1.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/LICENSE +21 -0
- package/README.md +211 -0
- package/dist/bootstrap.d.ts +36 -0
- package/dist/bootstrap.d.ts.map +1 -0
- package/dist/bootstrap.js +86 -0
- package/dist/bootstrap.js.map +1 -0
- package/dist/cli/index.d.ts +8 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +125 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/config.d.ts +93 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +156 -0
- package/dist/config.js.map +1 -0
- package/dist/gateway/auth.d.ts +16 -0
- package/dist/gateway/auth.d.ts.map +1 -0
- package/dist/gateway/auth.js +50 -0
- package/dist/gateway/auth.js.map +1 -0
- package/dist/gateway/http.d.ts +9 -0
- package/dist/gateway/http.d.ts.map +1 -0
- package/dist/gateway/http.js +178 -0
- package/dist/gateway/http.js.map +1 -0
- package/dist/gateway/websocket.d.ts +18 -0
- package/dist/gateway/websocket.d.ts.map +1 -0
- package/dist/gateway/websocket.js +197 -0
- package/dist/gateway/websocket.js.map +1 -0
- package/dist/gateway/ws/admin-handler.d.ts +68 -0
- package/dist/gateway/ws/admin-handler.d.ts.map +1 -0
- package/dist/gateway/ws/admin-handler.js +573 -0
- package/dist/gateway/ws/admin-handler.js.map +1 -0
- package/dist/gateway/ws/data-types.d.ts +130 -0
- package/dist/gateway/ws/data-types.d.ts.map +1 -0
- package/dist/gateway/ws/data-types.js +7 -0
- package/dist/gateway/ws/data-types.js.map +1 -0
- package/dist/gateway/ws/log-buffer.d.ts +29 -0
- package/dist/gateway/ws/log-buffer.d.ts.map +1 -0
- package/dist/gateway/ws/log-buffer.js +58 -0
- package/dist/gateway/ws/log-buffer.js.map +1 -0
- package/dist/gateway/ws/types.d.ts +67 -0
- package/dist/gateway/ws/types.d.ts.map +1 -0
- package/dist/gateway/ws/types.js +68 -0
- package/dist/gateway/ws/types.js.map +1 -0
- package/dist/heartbeat-scheduler.d.ts +43 -0
- package/dist/heartbeat-scheduler.d.ts.map +1 -0
- package/dist/heartbeat-scheduler.js +118 -0
- package/dist/heartbeat-scheduler.js.map +1 -0
- package/dist/main.d.ts +23 -0
- package/dist/main.d.ts.map +1 -0
- package/dist/main.js +120 -0
- package/dist/main.js.map +1 -0
- package/dist/plugin-manager/cli.d.ts +6 -0
- package/dist/plugin-manager/cli.d.ts.map +1 -0
- package/dist/plugin-manager/cli.js +115 -0
- package/dist/plugin-manager/cli.js.map +1 -0
- package/dist/plugin-manager/constants.d.ts +24 -0
- package/dist/plugin-manager/constants.d.ts.map +1 -0
- package/dist/plugin-manager/constants.js +58 -0
- package/dist/plugin-manager/constants.js.map +1 -0
- package/dist/plugin-manager/index.d.ts +11 -0
- package/dist/plugin-manager/index.d.ts.map +1 -0
- package/dist/plugin-manager/index.js +10 -0
- package/dist/plugin-manager/index.js.map +1 -0
- package/dist/plugin-manager/installer.d.ts +19 -0
- package/dist/plugin-manager/installer.d.ts.map +1 -0
- package/dist/plugin-manager/installer.js +256 -0
- package/dist/plugin-manager/installer.js.map +1 -0
- package/dist/plugin-manager/manager.d.ts +39 -0
- package/dist/plugin-manager/manager.d.ts.map +1 -0
- package/dist/plugin-manager/manager.js +171 -0
- package/dist/plugin-manager/manager.js.map +1 -0
- package/dist/plugin-manager/registry.d.ts +31 -0
- package/dist/plugin-manager/registry.d.ts.map +1 -0
- package/dist/plugin-manager/registry.js +63 -0
- package/dist/plugin-manager/registry.js.map +1 -0
- package/dist/plugin-manager/searcher.d.ts +18 -0
- package/dist/plugin-manager/searcher.d.ts.map +1 -0
- package/dist/plugin-manager/searcher.js +50 -0
- package/dist/plugin-manager/searcher.js.map +1 -0
- package/dist/plugin-manager/types.d.ts +71 -0
- package/dist/plugin-manager/types.d.ts.map +1 -0
- package/dist/plugin-manager/types.js +5 -0
- package/dist/plugin-manager/types.js.map +1 -0
- package/dist/plugins.d.ts +18 -0
- package/dist/plugins.d.ts.map +1 -0
- package/dist/plugins.js +198 -0
- package/dist/plugins.js.map +1 -0
- package/package.json +54 -0
package/dist/config.js
ADDED
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hive Server Configuration
|
|
3
|
+
*
|
|
4
|
+
* Loads configuration from hive.config.json with environment variable interpolation.
|
|
5
|
+
* Falls back to .env for backwards compatibility.
|
|
6
|
+
*/
|
|
7
|
+
import { config as dotenvConfig } from 'dotenv';
|
|
8
|
+
import { existsSync, readFileSync, mkdirSync } from 'fs';
|
|
9
|
+
import { resolve, join } from 'path';
|
|
10
|
+
import AjvModule from 'ajv';
|
|
11
|
+
const Ajv = AjvModule.default;
|
|
12
|
+
/**
|
|
13
|
+
* Hive 工作空间根目录
|
|
14
|
+
*
|
|
15
|
+
* 优先级: HIVE_HOME 环境变量 > ~/.hive
|
|
16
|
+
*/
|
|
17
|
+
function getHiveHome() {
|
|
18
|
+
if (process.env.HIVE_HOME) {
|
|
19
|
+
return process.env.HIVE_HOME;
|
|
20
|
+
}
|
|
21
|
+
return resolve(process.env.HOME || '~', '.hive');
|
|
22
|
+
}
|
|
23
|
+
export const HIVE_HOME = getHiveHome();
|
|
24
|
+
// 确保工作空间目录存在
|
|
25
|
+
if (!existsSync(HIVE_HOME)) {
|
|
26
|
+
mkdirSync(HIVE_HOME, { recursive: true });
|
|
27
|
+
}
|
|
28
|
+
// Load .env file if exists (for env var interpolation)
|
|
29
|
+
if (existsSync(join(HIVE_HOME, '.env'))) {
|
|
30
|
+
dotenvConfig({ path: join(HIVE_HOME, '.env') });
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Interpolate ${ENV_VAR} placeholders in a string
|
|
34
|
+
* E.g., "${GLM_API_KEY}" → "actual-api-key"
|
|
35
|
+
*/
|
|
36
|
+
function interpolateEnvVars(value) {
|
|
37
|
+
return value.replace(/\$\{([^}]+)\}/g, (_, envVar) => {
|
|
38
|
+
return process.env[envVar] || '';
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Recursively interpolate ${ENV_VAR} in configuration object
|
|
43
|
+
*/
|
|
44
|
+
function interpolateConfig(obj) {
|
|
45
|
+
if (typeof obj === 'string') {
|
|
46
|
+
return interpolateEnvVars(obj);
|
|
47
|
+
}
|
|
48
|
+
if (Array.isArray(obj)) {
|
|
49
|
+
return obj.map(interpolateConfig);
|
|
50
|
+
}
|
|
51
|
+
if (obj && typeof obj === 'object') {
|
|
52
|
+
const result = {};
|
|
53
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
54
|
+
result[key] = interpolateConfig(value);
|
|
55
|
+
}
|
|
56
|
+
return result;
|
|
57
|
+
}
|
|
58
|
+
return obj;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Load and parse hive.config.json
|
|
62
|
+
*/
|
|
63
|
+
function loadJsonConfig() {
|
|
64
|
+
const configPath = join(HIVE_HOME, 'hive.config.json');
|
|
65
|
+
if (!existsSync(configPath)) {
|
|
66
|
+
return {};
|
|
67
|
+
}
|
|
68
|
+
try {
|
|
69
|
+
const content = readFileSync(configPath, 'utf-8');
|
|
70
|
+
const raw = JSON.parse(content);
|
|
71
|
+
const interpolated = interpolateConfig(raw);
|
|
72
|
+
// Validate against JSON Schema
|
|
73
|
+
const schemaPath = join(HIVE_HOME, 'hive.config.schema.json');
|
|
74
|
+
if (existsSync(schemaPath)) {
|
|
75
|
+
const schemaContent = readFileSync(schemaPath, 'utf-8');
|
|
76
|
+
const schema = JSON.parse(schemaContent);
|
|
77
|
+
const ajv = new Ajv({ useDefaults: true });
|
|
78
|
+
const validate = ajv.compile(schema);
|
|
79
|
+
if (!validate(interpolated)) {
|
|
80
|
+
console.error('[config] Validation errors:');
|
|
81
|
+
for (const error of validate.errors || []) {
|
|
82
|
+
console.error(` - ${error.instancePath}: ${error.message}`);
|
|
83
|
+
}
|
|
84
|
+
throw new Error('Configuration validation failed');
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return interpolated;
|
|
88
|
+
}
|
|
89
|
+
catch (error) {
|
|
90
|
+
console.error('[config] Failed to load hive.config.json:', error);
|
|
91
|
+
throw error;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Load configuration from hive.config.json with fallbacks
|
|
96
|
+
*/
|
|
97
|
+
export function loadConfig() {
|
|
98
|
+
const jsonConfig = loadJsonConfig();
|
|
99
|
+
// Server config with defaults
|
|
100
|
+
const server = jsonConfig.server || {};
|
|
101
|
+
// Auth config
|
|
102
|
+
const auth = jsonConfig.auth || {};
|
|
103
|
+
// Provider config
|
|
104
|
+
const provider = jsonConfig.provider || { id: 'glm' };
|
|
105
|
+
// Heartbeat config
|
|
106
|
+
const heartbeat = jsonConfig.heartbeat || {};
|
|
107
|
+
// Plugin list (keys of plugins object)
|
|
108
|
+
const plugins = jsonConfig.plugins ? Object.keys(jsonConfig.plugins) : [];
|
|
109
|
+
return {
|
|
110
|
+
port: server.port ?? parseInt(process.env.PORT || '4450', 10),
|
|
111
|
+
host: server.host ?? process.env.HOST ?? '127.0.0.1',
|
|
112
|
+
logLevel: server.logLevel ?? process.env.LOG_LEVEL ?? 'info',
|
|
113
|
+
auth: {
|
|
114
|
+
enabled: auth.enabled ?? process.env.AUTH_ENABLED === 'true',
|
|
115
|
+
apiKey: auth.apiKey || process.env.AUTH_API_KEY || '',
|
|
116
|
+
},
|
|
117
|
+
plugins,
|
|
118
|
+
provider: {
|
|
119
|
+
id: provider.id || process.env.PROVIDER_ID || 'glm',
|
|
120
|
+
apiKey: provider.apiKey || process.env.API_KEY || process.env.GLM_API_KEY || '',
|
|
121
|
+
model: provider.model || process.env.MODEL,
|
|
122
|
+
baseUrl: provider.baseUrl || process.env.BASE_URL,
|
|
123
|
+
},
|
|
124
|
+
heartbeat: {
|
|
125
|
+
enabled: heartbeat.enabled ?? process.env.HEARTBEAT_ENABLED === 'true',
|
|
126
|
+
intervalMs: heartbeat.intervalMs ?? parseInt(process.env.HEARTBEAT_INTERVAL_MS || '300000', 10),
|
|
127
|
+
model: heartbeat.model || process.env.HEARTBEAT_MODEL,
|
|
128
|
+
prompt: heartbeat.prompt || process.env.HEARTBEAT_PROMPT,
|
|
129
|
+
},
|
|
130
|
+
pluginConfigs: jsonConfig.plugins || {},
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Get configuration for a specific plugin
|
|
135
|
+
*/
|
|
136
|
+
export function getPluginConfig(config, pluginName) {
|
|
137
|
+
return config.pluginConfigs[pluginName] || {};
|
|
138
|
+
}
|
|
139
|
+
/** Cached config instance */
|
|
140
|
+
let _config = null;
|
|
141
|
+
/**
|
|
142
|
+
* Get configuration (lazy-loaded singleton)
|
|
143
|
+
*/
|
|
144
|
+
export function getConfig() {
|
|
145
|
+
if (!_config) {
|
|
146
|
+
_config = loadConfig();
|
|
147
|
+
}
|
|
148
|
+
return _config;
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Reset cached config (for testing)
|
|
152
|
+
*/
|
|
153
|
+
export function resetConfig() {
|
|
154
|
+
_config = null;
|
|
155
|
+
}
|
|
156
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* API Key Authentication Middleware (Hono)
|
|
3
|
+
*
|
|
4
|
+
* Validates requests via Authorization: Bearer <key> header or ?apiKey=<key> query param.
|
|
5
|
+
*/
|
|
6
|
+
import type { MiddlewareHandler } from 'hono';
|
|
7
|
+
import type { ServerConfig } from '../config.js';
|
|
8
|
+
/**
|
|
9
|
+
* Create authentication middleware for Hono
|
|
10
|
+
*/
|
|
11
|
+
export declare function createAuthMiddleware(config: ServerConfig): MiddlewareHandler;
|
|
12
|
+
/**
|
|
13
|
+
* Validate API key for WebSocket upgrade (query param only)
|
|
14
|
+
*/
|
|
15
|
+
export declare function validateWsApiKey(config: ServerConfig, apiKey: string | null | undefined): boolean;
|
|
16
|
+
//# sourceMappingURL=auth.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../src/gateway/auth.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,MAAM,CAAA;AAC7C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,cAAc,CAAA;AAEhD;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,YAAY,GAAG,iBAAiB,CAwB5E;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,OAAO,CAQjG"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* API Key Authentication Middleware (Hono)
|
|
3
|
+
*
|
|
4
|
+
* Validates requests via Authorization: Bearer <key> header or ?apiKey=<key> query param.
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Create authentication middleware for Hono
|
|
8
|
+
*/
|
|
9
|
+
export function createAuthMiddleware(config) {
|
|
10
|
+
const { enabled, apiKey } = config.auth;
|
|
11
|
+
return async (c, next) => {
|
|
12
|
+
if (!enabled || !apiKey) {
|
|
13
|
+
await next();
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
const authHeader = c.req.header('Authorization');
|
|
17
|
+
const headerKey = extractBearerToken(authHeader);
|
|
18
|
+
const queryKey = c.req.query('apiKey');
|
|
19
|
+
const providedKey = headerKey || queryKey;
|
|
20
|
+
if (!providedKey) {
|
|
21
|
+
return c.json({ error: 'Missing API key' }, 401);
|
|
22
|
+
}
|
|
23
|
+
if (providedKey !== apiKey) {
|
|
24
|
+
return c.json({ error: 'Invalid API key' }, 401);
|
|
25
|
+
}
|
|
26
|
+
await next();
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Validate API key for WebSocket upgrade (query param only)
|
|
31
|
+
*/
|
|
32
|
+
export function validateWsApiKey(config, apiKey) {
|
|
33
|
+
if (!config.auth.enabled || !config.auth.apiKey) {
|
|
34
|
+
return true;
|
|
35
|
+
}
|
|
36
|
+
if (!apiKey) {
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
return apiKey === config.auth.apiKey;
|
|
40
|
+
}
|
|
41
|
+
function extractBearerToken(authHeader) {
|
|
42
|
+
if (!authHeader)
|
|
43
|
+
return undefined;
|
|
44
|
+
const parts = authHeader.split(' ');
|
|
45
|
+
if (parts.length === 2 && parts[0] === 'Bearer') {
|
|
46
|
+
return parts[1];
|
|
47
|
+
}
|
|
48
|
+
return undefined;
|
|
49
|
+
}
|
|
50
|
+
//# sourceMappingURL=auth.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../../src/gateway/auth.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAKH;;GAEG;AACH,MAAM,UAAU,oBAAoB,CAAC,MAAoB;IACvD,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,MAAM,CAAC,IAAI,CAAA;IAEvC,OAAO,KAAK,EAAE,CAAC,EAAE,IAAI,EAAE,EAAE;QACvB,IAAI,CAAC,OAAO,IAAI,CAAC,MAAM,EAAE,CAAC;YACxB,MAAM,IAAI,EAAE,CAAA;YACZ,OAAM;QACR,CAAC;QAED,MAAM,UAAU,GAAG,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,eAAe,CAAC,CAAA;QAChD,MAAM,SAAS,GAAG,kBAAkB,CAAC,UAAU,CAAC,CAAA;QAChD,MAAM,QAAQ,GAAG,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAA;QACtC,MAAM,WAAW,GAAG,SAAS,IAAI,QAAQ,CAAA;QAEzC,IAAI,CAAC,WAAW,EAAE,CAAC;YACjB,OAAO,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,iBAAiB,EAAE,EAAE,GAAG,CAAC,CAAA;QAClD,CAAC;QAED,IAAI,WAAW,KAAK,MAAM,EAAE,CAAC;YAC3B,OAAO,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,iBAAiB,EAAE,EAAE,GAAG,CAAC,CAAA;QAClD,CAAC;QAED,MAAM,IAAI,EAAE,CAAA;IACd,CAAC,CAAA;AACH,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,gBAAgB,CAAC,MAAoB,EAAE,MAAiC;IACtF,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC;QAChD,OAAO,IAAI,CAAA;IACb,CAAC;IACD,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,OAAO,KAAK,CAAA;IACd,CAAC;IACD,OAAO,MAAM,KAAK,MAAM,CAAC,IAAI,CAAC,MAAM,CAAA;AACtC,CAAC;AAED,SAAS,kBAAkB,CAAC,UAA8B;IACxD,IAAI,CAAC,UAAU;QAAE,OAAO,SAAS,CAAA;IACjC,MAAM,KAAK,GAAG,UAAU,CAAC,KAAK,CAAC,GAAG,CAAC,CAAA;IACnC,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,IAAI,KAAK,CAAC,CAAC,CAAC,KAAK,QAAQ,EAAE,CAAC;QAChD,OAAO,KAAK,CAAC,CAAC,CAAC,CAAA;IACjB,CAAC;IACD,OAAO,SAAS,CAAA;AAClB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../../src/gateway/http.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAA;AAG3B,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAA;AAqBlD,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,WAAW,GAAG,IAAI,CAgMxD"}
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HTTP Gateway Factory
|
|
3
|
+
*
|
|
4
|
+
* Creates Hono app for HTTP API.
|
|
5
|
+
*/
|
|
6
|
+
import { Hono } from 'hono';
|
|
7
|
+
import { cors } from 'hono/cors';
|
|
8
|
+
import { logger } from 'hono/logger';
|
|
9
|
+
import { createAuthMiddleware } from './auth.js';
|
|
10
|
+
/** Maximum message length (100KB) */
|
|
11
|
+
const MAX_MESSAGE_LENGTH = 100_000;
|
|
12
|
+
/** Maximum in-memory sessions (LRU eviction) */
|
|
13
|
+
const MAX_SESSIONS = 10_000;
|
|
14
|
+
// Simple in-memory session store with LRU eviction
|
|
15
|
+
const sessions = new Map();
|
|
16
|
+
function setSession(key, value) {
|
|
17
|
+
if (sessions.size >= MAX_SESSIONS) {
|
|
18
|
+
const firstKey = sessions.keys().next().value;
|
|
19
|
+
if (firstKey !== undefined)
|
|
20
|
+
sessions.delete(firstKey);
|
|
21
|
+
}
|
|
22
|
+
sessions.set(key, value);
|
|
23
|
+
}
|
|
24
|
+
export function createHttpGateway(ctx) {
|
|
25
|
+
const app = new Hono();
|
|
26
|
+
// Middleware
|
|
27
|
+
app.use('*', logger());
|
|
28
|
+
const allowedOrigins = process.env.CORS_ORIGINS
|
|
29
|
+
? process.env.CORS_ORIGINS.split(',').map(s => s.trim())
|
|
30
|
+
: ['http://localhost:3000'];
|
|
31
|
+
app.use('*', cors({
|
|
32
|
+
origin: allowedOrigins,
|
|
33
|
+
allowMethods: ['GET', 'POST', 'DELETE', 'OPTIONS'],
|
|
34
|
+
allowHeaders: ['Content-Type', 'Authorization'],
|
|
35
|
+
}));
|
|
36
|
+
// API authentication
|
|
37
|
+
app.use('/api/*', createAuthMiddleware(ctx.config));
|
|
38
|
+
// Health check (no auth required)
|
|
39
|
+
app.get('/health', (c) => {
|
|
40
|
+
return c.json({ status: 'ok', timestamp: new Date().toISOString() });
|
|
41
|
+
});
|
|
42
|
+
// Chat endpoint
|
|
43
|
+
app.post('/api/chat', async (c) => {
|
|
44
|
+
try {
|
|
45
|
+
const body = await c.req.json();
|
|
46
|
+
const { message, sessionId } = body;
|
|
47
|
+
if (!message) {
|
|
48
|
+
return c.json({ error: 'Missing message' }, 400);
|
|
49
|
+
}
|
|
50
|
+
if (typeof message !== 'string' || message.length > MAX_MESSAGE_LENGTH) {
|
|
51
|
+
return c.json({ error: `Message too long (max ${MAX_MESSAGE_LENGTH} chars)` }, 413);
|
|
52
|
+
}
|
|
53
|
+
const sid = sessionId || 'default';
|
|
54
|
+
// Get or create session
|
|
55
|
+
let session = sessions.get(sid);
|
|
56
|
+
if (!session) {
|
|
57
|
+
session = { id: sid, messages: [] };
|
|
58
|
+
setSession(sid, session);
|
|
59
|
+
}
|
|
60
|
+
// Add user message
|
|
61
|
+
session.messages.push({ role: 'user', content: message });
|
|
62
|
+
// Emit message received event
|
|
63
|
+
ctx.bus?.emit('message:received', {
|
|
64
|
+
content: message,
|
|
65
|
+
sessionId: sid,
|
|
66
|
+
timestamp: Date.now(),
|
|
67
|
+
});
|
|
68
|
+
// Send to agent (dispatch for smart routing)
|
|
69
|
+
const result = await ctx.agent.dispatch(message);
|
|
70
|
+
const response = result.text;
|
|
71
|
+
// Add assistant message
|
|
72
|
+
session.messages.push({ role: 'assistant', content: response });
|
|
73
|
+
// Emit message sent event
|
|
74
|
+
ctx.bus?.emit('message:sent', {
|
|
75
|
+
content: response,
|
|
76
|
+
sessionId: sid,
|
|
77
|
+
timestamp: Date.now(),
|
|
78
|
+
});
|
|
79
|
+
return c.json({
|
|
80
|
+
response,
|
|
81
|
+
sessionId: sid,
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
catch (error) {
|
|
85
|
+
const isDev = process.env.NODE_ENV !== 'production';
|
|
86
|
+
return c.json({
|
|
87
|
+
error: 'Internal server error',
|
|
88
|
+
...(isDev ? { message: error instanceof Error ? error.message : 'Unknown error' } : {}),
|
|
89
|
+
}, 500);
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
// List plugins
|
|
93
|
+
app.get('/api/plugins', (c) => {
|
|
94
|
+
return c.json({
|
|
95
|
+
plugins: ctx.plugins.map((p) => ({
|
|
96
|
+
id: p.metadata.id,
|
|
97
|
+
name: p.metadata.name,
|
|
98
|
+
version: p.metadata.version,
|
|
99
|
+
})),
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
// List sessions
|
|
103
|
+
app.get('/api/sessions', (c) => {
|
|
104
|
+
const sessionList = Array.from(sessions.values()).map((s) => ({
|
|
105
|
+
id: s.id,
|
|
106
|
+
messageCount: s.messages.length,
|
|
107
|
+
}));
|
|
108
|
+
return c.json({ sessions: sessionList });
|
|
109
|
+
});
|
|
110
|
+
// Get session by ID
|
|
111
|
+
app.get('/api/sessions/:id', (c) => {
|
|
112
|
+
const sessionId = c.req.param('id');
|
|
113
|
+
const session = sessions.get(sessionId);
|
|
114
|
+
if (!session) {
|
|
115
|
+
return c.json({ error: 'Session not found' }, 404);
|
|
116
|
+
}
|
|
117
|
+
return c.json({ session });
|
|
118
|
+
});
|
|
119
|
+
// Delete session
|
|
120
|
+
app.delete('/api/sessions/:id', (c) => {
|
|
121
|
+
const sessionId = c.req.param('id');
|
|
122
|
+
const deleted = sessions.delete(sessionId);
|
|
123
|
+
return c.json({ success: deleted });
|
|
124
|
+
});
|
|
125
|
+
// Webhook endpoint for plugins (e.g., Feishu)
|
|
126
|
+
app.post('/webhook/:plugin/:appId', async (c) => {
|
|
127
|
+
try {
|
|
128
|
+
const pluginName = c.req.param('plugin');
|
|
129
|
+
const appId = c.req.param('appId');
|
|
130
|
+
// Get headers
|
|
131
|
+
const signature = c.req.header('X-Lark-Signature') || c.req.header('X-Feishu-Signature') || '';
|
|
132
|
+
const timestamp = c.req.header('X-Lark-Request-Timestamp') || c.req.header('X-Feishu-Timestamp') || '';
|
|
133
|
+
const nonce = c.req.header('X-Lark-Request-Nonce') || c.req.header('X-Feishu-Nonce') || '';
|
|
134
|
+
// Get body
|
|
135
|
+
const body = await c.req.json();
|
|
136
|
+
// Find the plugin and get its channel
|
|
137
|
+
const plugin = ctx.plugins.find((p) => p.metadata.id === pluginName);
|
|
138
|
+
if (!plugin) {
|
|
139
|
+
return c.json({ error: 'Plugin not found' }, 404);
|
|
140
|
+
}
|
|
141
|
+
// Get channel from plugin
|
|
142
|
+
const channels = plugin.getChannels();
|
|
143
|
+
const channel = channels.find((ch) => {
|
|
144
|
+
// Check channel id format: "feishu:appId"
|
|
145
|
+
if (ch.id === `${pluginName}:${appId}`)
|
|
146
|
+
return true;
|
|
147
|
+
// Check if channel has appId property (for Feishu channels)
|
|
148
|
+
if ('appId' in ch && ch.appId === appId)
|
|
149
|
+
return true;
|
|
150
|
+
return false;
|
|
151
|
+
});
|
|
152
|
+
const webhookChannel = channel;
|
|
153
|
+
if (!channel || typeof webhookChannel.handleWebhook !== 'function') {
|
|
154
|
+
return c.json({ error: 'Channel not found or does not support webhooks' }, 404);
|
|
155
|
+
}
|
|
156
|
+
// Handle webhook
|
|
157
|
+
const result = await webhookChannel.handleWebhook(body, signature, timestamp, nonce);
|
|
158
|
+
return c.json(result);
|
|
159
|
+
}
|
|
160
|
+
catch (error) {
|
|
161
|
+
const isDev = process.env.NODE_ENV !== 'production';
|
|
162
|
+
return c.json({
|
|
163
|
+
error: 'Webhook processing failed',
|
|
164
|
+
...(isDev ? { message: error instanceof Error ? error.message : 'Unknown error' } : {}),
|
|
165
|
+
}, 500);
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
// Error handling
|
|
169
|
+
app.notFound((c) => {
|
|
170
|
+
return c.json({ error: 'Not found' }, 404);
|
|
171
|
+
});
|
|
172
|
+
app.onError((err, c) => {
|
|
173
|
+
const isDev = process.env.NODE_ENV !== 'production';
|
|
174
|
+
return c.json({ error: 'Internal server error', ...(isDev ? { message: err.message } : {}) }, 500);
|
|
175
|
+
});
|
|
176
|
+
return app;
|
|
177
|
+
}
|
|
178
|
+
//# sourceMappingURL=http.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"http.js","sourceRoot":"","sources":["../../src/gateway/http.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WebSocket Gateway
|
|
3
|
+
*
|
|
4
|
+
* Real-time bidirectional communication for chat and events.
|
|
5
|
+
*/
|
|
6
|
+
import type { Server as HttpServer } from 'http';
|
|
7
|
+
import type { HiveContext } from '../bootstrap.js';
|
|
8
|
+
interface OutgoingMessage {
|
|
9
|
+
type: string;
|
|
10
|
+
[key: string]: unknown;
|
|
11
|
+
}
|
|
12
|
+
export interface WebSocketGateway {
|
|
13
|
+
broadcast: (message: OutgoingMessage) => void;
|
|
14
|
+
close: () => void;
|
|
15
|
+
}
|
|
16
|
+
export declare function createWebSocketGateway(server: HttpServer, ctx: HiveContext): WebSocketGateway;
|
|
17
|
+
export {};
|
|
18
|
+
//# sourceMappingURL=websocket.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"websocket.d.ts","sourceRoot":"","sources":["../../src/gateway/websocket.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,MAAM,IAAI,UAAU,EAAE,MAAM,MAAM,CAAA;AAChD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAA;AAiBlD,UAAU,eAAe;IACvB,IAAI,EAAE,MAAM,CAAA;IACZ,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;CACvB;AAED,MAAM,WAAW,gBAAgB;IAC/B,SAAS,EAAE,CAAC,OAAO,EAAE,eAAe,KAAK,IAAI,CAAA;IAC7C,KAAK,EAAE,MAAM,IAAI,CAAA;CAClB;AAED,wBAAgB,sBAAsB,CACpC,MAAM,EAAE,UAAU,EAClB,GAAG,EAAE,WAAW,GACf,gBAAgB,CA6NlB"}
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WebSocket Gateway
|
|
3
|
+
*
|
|
4
|
+
* Real-time bidirectional communication for chat and events.
|
|
5
|
+
*/
|
|
6
|
+
import { validateWsApiKey } from './auth.js';
|
|
7
|
+
/** Maximum message length (100KB) — shared with HTTP gateway */
|
|
8
|
+
const MAX_MESSAGE_LENGTH = 100_000;
|
|
9
|
+
export function createWebSocketGateway(server, ctx) {
|
|
10
|
+
const clients = new Map();
|
|
11
|
+
let clientIdCounter = 0;
|
|
12
|
+
let busSubscriptionId = null;
|
|
13
|
+
// Handle WebSocket upgrade
|
|
14
|
+
server.on('upgrade', (request, socket, head) => {
|
|
15
|
+
const url = request.url;
|
|
16
|
+
if (!url)
|
|
17
|
+
return;
|
|
18
|
+
try {
|
|
19
|
+
const parsedUrl = new URL(url, `http://${request.headers.host || 'localhost'}`);
|
|
20
|
+
const pathname = parsedUrl.pathname;
|
|
21
|
+
if (pathname !== '/ws')
|
|
22
|
+
return;
|
|
23
|
+
// Validate API key from query param
|
|
24
|
+
const apiKey = parsedUrl.searchParams.get('apiKey');
|
|
25
|
+
if (!validateWsApiKey(ctx.config, apiKey)) {
|
|
26
|
+
socket.write('HTTP/1.1 401 Unauthorized\r\n\r\n');
|
|
27
|
+
socket.end();
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
// Invalid URL, ignore
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
/**
|
|
36
|
+
* Handle incoming WebSocket message
|
|
37
|
+
*/
|
|
38
|
+
async function handleMessage(client, data, ctx) {
|
|
39
|
+
try {
|
|
40
|
+
const message = JSON.parse(data);
|
|
41
|
+
switch (message.type) {
|
|
42
|
+
case 'chat': {
|
|
43
|
+
await handleChat(client, message, ctx);
|
|
44
|
+
break;
|
|
45
|
+
}
|
|
46
|
+
case 'session:create': {
|
|
47
|
+
handleSessionCreate(client);
|
|
48
|
+
break;
|
|
49
|
+
}
|
|
50
|
+
case 'session:join': {
|
|
51
|
+
handleSessionJoin(client, message);
|
|
52
|
+
break;
|
|
53
|
+
}
|
|
54
|
+
default:
|
|
55
|
+
send(client, {
|
|
56
|
+
type: 'error',
|
|
57
|
+
message: `Unknown message type: ${message.type}`,
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
catch (error) {
|
|
62
|
+
send(client, {
|
|
63
|
+
type: 'error',
|
|
64
|
+
message: 'Invalid message format',
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Handle chat message
|
|
70
|
+
*/
|
|
71
|
+
async function handleChat(client, message, ctx) {
|
|
72
|
+
const text = message.message;
|
|
73
|
+
const sessionId = message.sessionId;
|
|
74
|
+
if (!text) {
|
|
75
|
+
send(client, {
|
|
76
|
+
type: 'error',
|
|
77
|
+
message: 'Missing message content',
|
|
78
|
+
});
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
if (typeof text !== 'string' || text.length > MAX_MESSAGE_LENGTH) {
|
|
82
|
+
send(client, {
|
|
83
|
+
type: 'error',
|
|
84
|
+
message: `Message too long (max ${MAX_MESSAGE_LENGTH} chars)`,
|
|
85
|
+
});
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
try {
|
|
89
|
+
// Emit message received event
|
|
90
|
+
ctx.bus?.emit('message:received', {
|
|
91
|
+
content: text,
|
|
92
|
+
sessionId: sessionId || client.sessionId,
|
|
93
|
+
clientId: client.id,
|
|
94
|
+
timestamp: Date.now(),
|
|
95
|
+
});
|
|
96
|
+
// Send to agent (dispatch for smart routing)
|
|
97
|
+
const result = await ctx.agent.dispatch(text);
|
|
98
|
+
const response = result.text;
|
|
99
|
+
// Emit message sent event
|
|
100
|
+
ctx.bus?.emit('message:sent', {
|
|
101
|
+
content: response,
|
|
102
|
+
sessionId: sessionId || client.sessionId,
|
|
103
|
+
clientId: client.id,
|
|
104
|
+
timestamp: Date.now(),
|
|
105
|
+
});
|
|
106
|
+
send(client, {
|
|
107
|
+
type: 'response',
|
|
108
|
+
message: response,
|
|
109
|
+
sessionId: sessionId || client.sessionId,
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
catch (error) {
|
|
113
|
+
const isDev = process.env.NODE_ENV !== 'production';
|
|
114
|
+
send(client, {
|
|
115
|
+
type: 'error',
|
|
116
|
+
message: isDev && error instanceof Error ? error.message : 'Internal error',
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Handle session create
|
|
122
|
+
*/
|
|
123
|
+
function handleSessionCreate(client) {
|
|
124
|
+
const sessionId = `session-${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
125
|
+
client.sessionId = sessionId;
|
|
126
|
+
send(client, {
|
|
127
|
+
type: 'session:created',
|
|
128
|
+
sessionId,
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Handle session join
|
|
133
|
+
*/
|
|
134
|
+
function handleSessionJoin(client, message) {
|
|
135
|
+
const sessionId = message.sessionId;
|
|
136
|
+
if (!sessionId) {
|
|
137
|
+
send(client, {
|
|
138
|
+
type: 'error',
|
|
139
|
+
message: 'Missing sessionId',
|
|
140
|
+
});
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
client.sessionId = sessionId;
|
|
144
|
+
send(client, {
|
|
145
|
+
type: 'session:joined',
|
|
146
|
+
sessionId,
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Send message to client
|
|
151
|
+
*/
|
|
152
|
+
function send(client, message) {
|
|
153
|
+
if (client.ws.readyState === 1) { // WebSocket.OPEN
|
|
154
|
+
client.ws.send(JSON.stringify(message));
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Broadcast message to all clients
|
|
159
|
+
*/
|
|
160
|
+
function broadcast(message) {
|
|
161
|
+
const data = JSON.stringify(message);
|
|
162
|
+
for (const client of clients.values()) {
|
|
163
|
+
if (client.ws.readyState === 1) { // WebSocket.OPEN
|
|
164
|
+
client.ws.send(data);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Close all connections
|
|
170
|
+
*/
|
|
171
|
+
function close() {
|
|
172
|
+
// Unsubscribe from MessageBus
|
|
173
|
+
if (busSubscriptionId && ctx.bus) {
|
|
174
|
+
ctx.bus.unsubscribe(busSubscriptionId);
|
|
175
|
+
}
|
|
176
|
+
// Close all client connections
|
|
177
|
+
for (const client of clients.values()) {
|
|
178
|
+
client.ws.close();
|
|
179
|
+
}
|
|
180
|
+
clients.clear();
|
|
181
|
+
}
|
|
182
|
+
// Subscribe to MessageBus events and broadcast
|
|
183
|
+
if (ctx.bus) {
|
|
184
|
+
busSubscriptionId = ctx.bus.subscribe('plugin:event', (event) => {
|
|
185
|
+
broadcast({
|
|
186
|
+
type: 'event',
|
|
187
|
+
event: 'plugin:event',
|
|
188
|
+
data: event,
|
|
189
|
+
});
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
return {
|
|
193
|
+
broadcast,
|
|
194
|
+
close,
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
//# sourceMappingURL=websocket.js.map
|