@jsbot-tellus/core 2.5.0 → 2.5.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/README.md +8 -5
- package/dist/app/cli.d.ts +10 -0
- package/dist/app/cli.d.ts.map +1 -0
- package/dist/app/cli.js +37 -0
- package/dist/app/cli.js.map +1 -0
- package/dist/app/runtime.d.ts +121 -0
- package/dist/app/runtime.d.ts.map +1 -0
- package/dist/app/runtime.js +296 -0
- package/dist/app/runtime.js.map +1 -0
- package/dist/app.js +63 -273
- package/dist/app.js.map +1 -1
- package/dist/core/plugin/command-params.d.ts +3 -37
- package/dist/core/plugin/command-params.d.ts.map +1 -1
- package/dist/core/plugin/command-params.js +2 -580
- package/dist/core/plugin/command-params.js.map +1 -1
- package/dist/core/plugin/install/paths.d.ts +7 -0
- package/dist/core/plugin/install/paths.d.ts.map +1 -0
- package/dist/core/plugin/install/paths.js +49 -0
- package/dist/core/plugin/install/paths.js.map +1 -0
- package/dist/core/plugin/install/state.d.ts +23 -0
- package/dist/core/plugin/install/state.d.ts.map +1 -0
- package/dist/core/plugin/install/state.js +129 -0
- package/dist/core/plugin/install/state.js.map +1 -0
- package/dist/core/plugin/install/validate-script.d.ts +3 -0
- package/dist/core/plugin/install/validate-script.d.ts.map +1 -0
- package/dist/core/plugin/install/validate-script.js +154 -0
- package/dist/core/plugin/install/validate-script.js.map +1 -0
- package/dist/core/plugin/install-dir.d.ts +3 -28
- package/dist/core/plugin/install-dir.d.ts.map +1 -1
- package/dist/core/plugin/install-dir.js +3 -326
- package/dist/core/plugin/install-dir.js.map +1 -1
- package/dist/core/plugin/installer/checks.d.ts +6 -0
- package/dist/core/plugin/installer/checks.d.ts.map +1 -0
- package/dist/core/plugin/installer/checks.js +99 -0
- package/dist/core/plugin/installer/checks.js.map +1 -0
- package/dist/core/plugin/installer/support.d.ts +37 -0
- package/dist/core/plugin/installer/support.d.ts.map +1 -0
- package/dist/core/plugin/installer/support.js +67 -0
- package/dist/core/plugin/installer/support.js.map +1 -0
- package/dist/core/plugin/installer/warnings.d.ts +5 -0
- package/dist/core/plugin/installer/warnings.d.ts.map +1 -0
- package/dist/core/plugin/installer/warnings.js +28 -0
- package/dist/core/plugin/installer/warnings.js.map +1 -0
- package/dist/core/plugin/installer.d.ts +4 -30
- package/dist/core/plugin/installer.d.ts.map +1 -1
- package/dist/core/plugin/installer.js +21 -202
- package/dist/core/plugin/installer.js.map +1 -1
- package/dist/core/plugin/params/compile.d.ts +3 -0
- package/dist/core/plugin/params/compile.d.ts.map +1 -0
- package/dist/core/plugin/params/compile.js +23 -0
- package/dist/core/plugin/params/compile.js.map +1 -0
- package/dist/core/plugin/params/declaration.d.ts +9 -0
- package/dist/core/plugin/params/declaration.d.ts.map +1 -0
- package/dist/core/plugin/params/declaration.js +223 -0
- package/dist/core/plugin/params/declaration.js.map +1 -0
- package/dist/core/plugin/params/input.d.ts +5 -0
- package/dist/core/plugin/params/input.d.ts.map +1 -0
- package/dist/core/plugin/params/input.js +181 -0
- package/dist/core/plugin/params/input.js.map +1 -0
- package/dist/core/plugin/params/reply.d.ts +3 -0
- package/dist/core/plugin/params/reply.d.ts.map +1 -0
- package/dist/core/plugin/params/reply.js +49 -0
- package/dist/core/plugin/params/reply.js.map +1 -0
- package/dist/core/plugin/params/type-level.d.ts +23 -0
- package/dist/core/plugin/params/type-level.d.ts.map +1 -0
- package/dist/core/plugin/params/type-level.js +2 -0
- package/dist/core/plugin/params/type-level.js.map +1 -0
- package/dist/core/plugin/params/types.d.ts +28 -0
- package/dist/core/plugin/params/types.d.ts.map +1 -0
- package/dist/core/plugin/params/types.js +12 -0
- package/dist/core/plugin/params/types.js.map +1 -0
- package/dist/core/plugin/params/values.d.ts +6 -0
- package/dist/core/plugin/params/values.d.ts.map +1 -0
- package/dist/core/plugin/params/values.js +108 -0
- package/dist/core/plugin/params/values.js.map +1 -0
- package/dist/core/web/api.d.ts +2 -14
- package/dist/core/web/api.d.ts.map +1 -1
- package/dist/core/web/api.js +8 -500
- package/dist/core/web/api.js.map +1 -1
- package/dist/core/web/routes/actions.d.ts +4 -0
- package/dist/core/web/routes/actions.d.ts.map +1 -0
- package/dist/core/web/routes/actions.js +33 -0
- package/dist/core/web/routes/actions.js.map +1 -0
- package/dist/core/web/routes/config.d.ts +5 -0
- package/dist/core/web/routes/config.d.ts.map +1 -0
- package/dist/core/web/routes/config.js +30 -0
- package/dist/core/web/routes/config.js.map +1 -0
- package/dist/core/web/routes/kv.d.ts +6 -0
- package/dist/core/web/routes/kv.d.ts.map +1 -0
- package/dist/core/web/routes/kv.js +80 -0
- package/dist/core/web/routes/kv.js.map +1 -0
- package/dist/core/web/routes/plugins.d.ts +5 -0
- package/dist/core/web/routes/plugins.d.ts.map +1 -0
- package/dist/core/web/routes/plugins.js +98 -0
- package/dist/core/web/routes/plugins.js.map +1 -0
- package/dist/core/web/routes/sessions.d.ts +8 -0
- package/dist/core/web/routes/sessions.d.ts.map +1 -0
- package/dist/core/web/routes/sessions.js +130 -0
- package/dist/core/web/routes/sessions.js.map +1 -0
- package/dist/core/web/routes/shared.d.ts +18 -0
- package/dist/core/web/routes/shared.d.ts.map +1 -0
- package/dist/core/web/routes/shared.js +19 -0
- package/dist/core/web/routes/shared.js.map +1 -0
- package/dist/core/web/routes/status.d.ts +6 -0
- package/dist/core/web/routes/status.d.ts.map +1 -0
- package/dist/core/web/routes/status.js +132 -0
- package/dist/core/web/routes/status.js.map +1 -0
- package/dist/core/web/server/auth.d.ts +17 -0
- package/dist/core/web/server/auth.d.ts.map +1 -0
- package/dist/core/web/server/auth.js +68 -0
- package/dist/core/web/server/auth.js.map +1 -0
- package/dist/core/web/server/csp.d.ts +11 -0
- package/dist/core/web/server/csp.d.ts.map +1 -0
- package/dist/core/web/server/csp.js +31 -0
- package/dist/core/web/server/csp.js.map +1 -0
- package/dist/core/web/server/request.d.ts +6 -0
- package/dist/core/web/server/request.d.ts.map +1 -0
- package/dist/core/web/server/request.js +33 -0
- package/dist/core/web/server/request.js.map +1 -0
- package/dist/core/web/server/security.d.ts +4 -0
- package/dist/core/web/server/security.d.ts.map +1 -0
- package/dist/core/web/server/security.js +12 -0
- package/dist/core/web/server/security.js.map +1 -0
- package/dist/core/web/server/sse.d.ts +18 -0
- package/dist/core/web/server/sse.d.ts.map +1 -0
- package/dist/core/web/server/sse.js +49 -0
- package/dist/core/web/server/sse.js.map +1 -0
- package/dist/core/web/server/static.d.ts +16 -0
- package/dist/core/web/server/static.d.ts.map +1 -0
- package/dist/core/web/server/static.js +80 -0
- package/dist/core/web/server/static.js.map +1 -0
- package/dist/core/web/server.d.ts +0 -7
- package/dist/core/web/server.d.ts.map +1 -1
- package/dist/core/web/server.js +41 -263
- package/dist/core/web/server.js.map +1 -1
- package/docs/architecture.md +28 -11
- package/docs/configuration.md +1 -1
- package/docs/migration.md +2 -2
- package/docs/plugin-api.md +1 -1
- package/docs/plugin-development.md +1 -1
- package/package.json +19 -17
- package/src/app/cli.ts +47 -0
- package/src/app/runtime.ts +522 -0
- package/src/app.ts +87 -395
- package/src/core/plugin/command-params.ts +8 -871
- package/src/core/plugin/install/paths.ts +72 -0
- package/src/core/plugin/install/state.ts +196 -0
- package/src/core/plugin/install/validate-script.ts +154 -0
- package/src/core/plugin/install-dir.ts +19 -427
- package/src/core/plugin/installer/checks.ts +137 -0
- package/src/core/plugin/installer/support.ts +120 -0
- package/src/core/plugin/installer/warnings.ts +52 -0
- package/src/core/plugin/installer.ts +52 -301
- package/src/core/plugin/params/compile.ts +41 -0
- package/src/core/plugin/params/declaration.ts +286 -0
- package/src/core/plugin/params/input.ts +254 -0
- package/src/core/plugin/params/reply.ts +59 -0
- package/src/core/plugin/params/type-level.ts +71 -0
- package/src/core/plugin/params/types.ts +49 -0
- package/src/core/plugin/params/values.ts +146 -0
- package/src/core/web/api.ts +33 -759
- package/src/core/web/routes/actions.ts +43 -0
- package/src/core/web/routes/config.ts +42 -0
- package/src/core/web/routes/kv.ts +122 -0
- package/src/core/web/routes/plugins.ts +151 -0
- package/src/core/web/routes/sessions.ts +188 -0
- package/src/core/web/routes/shared.ts +42 -0
- package/src/core/web/routes/status.ts +191 -0
- package/src/core/web/server/auth.ts +126 -0
- package/src/core/web/server/csp.ts +33 -0
- package/src/core/web/server/request.ts +38 -0
- package/src/core/web/server/security.ts +14 -0
- package/src/core/web/server/sse.ts +74 -0
- package/src/core/web/server/static.ts +112 -0
- package/src/core/web/server.ts +41 -383
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { parseSegments } from '@snowluma/sdk';
|
|
2
|
+
import { CommandParamsError, declarationError } from './types.js';
|
|
3
|
+
export function unquote(value) {
|
|
4
|
+
const trimmed = value.trim();
|
|
5
|
+
const quote = trimmed[0];
|
|
6
|
+
if (quote !== '"' && quote !== "'") {
|
|
7
|
+
return trimmed;
|
|
8
|
+
}
|
|
9
|
+
if (trimmed.length < 2 || trimmed[trimmed.length - 1] !== quote) {
|
|
10
|
+
throw declarationError('默认值引号未闭合');
|
|
11
|
+
}
|
|
12
|
+
let result = '';
|
|
13
|
+
for (let index = 1; index < trimmed.length - 1; index += 1) {
|
|
14
|
+
const char = trimmed[index];
|
|
15
|
+
if (char === undefined)
|
|
16
|
+
break;
|
|
17
|
+
if (char === '\\') {
|
|
18
|
+
const next = trimmed[index + 1];
|
|
19
|
+
if (next !== undefined) {
|
|
20
|
+
result += next;
|
|
21
|
+
index += 1;
|
|
22
|
+
}
|
|
23
|
+
continue;
|
|
24
|
+
}
|
|
25
|
+
result += char;
|
|
26
|
+
}
|
|
27
|
+
return result;
|
|
28
|
+
}
|
|
29
|
+
export function parseBoolean(value) {
|
|
30
|
+
const normalized = value.trim().toLowerCase();
|
|
31
|
+
if (['true', '1', 'yes', 'y', 'on'].includes(normalized)) {
|
|
32
|
+
return true;
|
|
33
|
+
}
|
|
34
|
+
if (['false', '0', 'no', 'n', 'off'].includes(normalized)) {
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
export function parseAt(value, name, usage) {
|
|
40
|
+
const raw = value.trim();
|
|
41
|
+
const segments = parseSegments(raw);
|
|
42
|
+
const segment = segments[0];
|
|
43
|
+
if (segments.length === 1 && segment?.type === 'at') {
|
|
44
|
+
const qq = segment.data.qq;
|
|
45
|
+
if (qq !== 'all') {
|
|
46
|
+
const parsed = Number(qq);
|
|
47
|
+
if (Number.isSafeInteger(parsed) && parsed > 0) {
|
|
48
|
+
return parsed;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
else if (/^\d+$/.test(raw)) {
|
|
53
|
+
const parsed = Number(raw);
|
|
54
|
+
if (Number.isSafeInteger(parsed) && parsed > 0) {
|
|
55
|
+
return parsed;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
throw new CommandParamsError(`参数 ${name} 需要 @ 一个用户或填写 QQ 号`, usage);
|
|
59
|
+
}
|
|
60
|
+
const TIME_UNITS = {
|
|
61
|
+
s: 1,
|
|
62
|
+
秒: 1,
|
|
63
|
+
m: 60,
|
|
64
|
+
分: 60,
|
|
65
|
+
分钟: 60,
|
|
66
|
+
h: 3600,
|
|
67
|
+
时: 3600,
|
|
68
|
+
小时: 3600,
|
|
69
|
+
d: 86400,
|
|
70
|
+
天: 86400,
|
|
71
|
+
w: 604800,
|
|
72
|
+
周: 604800,
|
|
73
|
+
};
|
|
74
|
+
const TIME_PART = /^(\d+(?:\.\d+)?)(分钟|小时|秒|分|时|天|周|[smhdw])/i;
|
|
75
|
+
export function parseTimeValue(value) {
|
|
76
|
+
const raw = value.trim();
|
|
77
|
+
if (/^\d+$/.test(raw)) {
|
|
78
|
+
const seconds = Number(raw);
|
|
79
|
+
return Number.isSafeInteger(seconds) ? seconds : null;
|
|
80
|
+
}
|
|
81
|
+
let index = 0;
|
|
82
|
+
let total = 0;
|
|
83
|
+
let parsed = false;
|
|
84
|
+
while (index < raw.length) {
|
|
85
|
+
const match = TIME_PART.exec(raw.slice(index));
|
|
86
|
+
if (!match)
|
|
87
|
+
return null;
|
|
88
|
+
const unit = TIME_UNITS[match[2]?.toLowerCase() ?? ''];
|
|
89
|
+
const amount = Number(match[1]);
|
|
90
|
+
if (unit === undefined)
|
|
91
|
+
return null;
|
|
92
|
+
total += amount * unit;
|
|
93
|
+
index += match[0].length;
|
|
94
|
+
parsed = true;
|
|
95
|
+
}
|
|
96
|
+
if (!parsed || !Number.isSafeInteger(total) || total < 0) {
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
99
|
+
return total;
|
|
100
|
+
}
|
|
101
|
+
export function parseTime(value, name, usage) {
|
|
102
|
+
const seconds = parseTimeValue(value);
|
|
103
|
+
if (seconds === null) {
|
|
104
|
+
throw new CommandParamsError(`参数 ${name} 需要是时间,例如 10s、5m、1h30m`, usage);
|
|
105
|
+
}
|
|
106
|
+
return seconds;
|
|
107
|
+
}
|
|
108
|
+
//# sourceMappingURL=values.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"values.js","sourceRoot":"","sources":["../../../../src/core/plugin/params/values.ts"],"names":[],"mappings":"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"}
|
package/dist/core/web/api.d.ts
CHANGED
|
@@ -1,16 +1,4 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
|
|
3
|
-
import type { WebUiDeps } from './types.js';
|
|
4
|
-
import { type URLSearchParamsLike } from './http.js';
|
|
5
|
-
export interface ApiContext {
|
|
6
|
-
deps: WebUiDeps;
|
|
7
|
-
req: IncomingMessage;
|
|
8
|
-
res: ServerResponse;
|
|
9
|
-
method: string;
|
|
10
|
-
pathname: string;
|
|
11
|
-
segments: string[];
|
|
12
|
-
search: URLSearchParamsLike;
|
|
13
|
-
session: AuthSession;
|
|
14
|
-
}
|
|
1
|
+
import type { ApiContext } from './routes/shared.js';
|
|
2
|
+
export type { ApiContext } from './routes/shared.js';
|
|
15
3
|
export declare function handleApiRoute(ctx: ApiContext): Promise<boolean>;
|
|
16
4
|
//# sourceMappingURL=api.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../../../src/core/web/api.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../../../src/core/web/api.ts"],"names":[],"mappings":"AAgCA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAA;AAEpD,YAAY,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAA;AAEpD,wBAAsB,cAAc,CAAC,GAAG,EAAE,UAAU,GAAG,OAAO,CAAC,OAAO,CAAC,CAyKtE"}
|
package/dist/core/web/api.js
CHANGED
|
@@ -1,502 +1,10 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
|
|
8
|
-
const loginCache = new WeakMap();
|
|
9
|
-
function requireRuntime(deps) {
|
|
10
|
-
const runtime = deps.getRuntime();
|
|
11
|
-
if (!runtime)
|
|
12
|
-
throw new HttpError(503, '运行时不可用,请稍后再试');
|
|
13
|
-
return runtime;
|
|
14
|
-
}
|
|
15
|
-
function countKvPlugins(database) {
|
|
16
|
-
const row = database.connection
|
|
17
|
-
.prepare('SELECT COUNT(DISTINCT plugin_id) AS count FROM plugin_kv')
|
|
18
|
-
.get();
|
|
19
|
-
return row?.count ?? 0;
|
|
20
|
-
}
|
|
21
|
-
async function loginInfo(runtime) {
|
|
22
|
-
if (!runtime.kernel.isConnected)
|
|
23
|
-
return null;
|
|
24
|
-
const cached = loginCache.get(runtime);
|
|
25
|
-
if (cached && Date.now() - cached.at < 10_000)
|
|
26
|
-
return cached.value;
|
|
27
|
-
let value = null;
|
|
28
|
-
try {
|
|
29
|
-
const info = await withTimeout(runtime.api.getLoginInfo(), 2_000);
|
|
30
|
-
if (typeof info.user_id === 'number') {
|
|
31
|
-
value = {
|
|
32
|
-
userId: info.user_id,
|
|
33
|
-
nickname: info.nickname || String(info.user_id),
|
|
34
|
-
};
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
catch {
|
|
38
|
-
value = null;
|
|
39
|
-
}
|
|
40
|
-
loginCache.set(runtime, { at: Date.now(), value });
|
|
41
|
-
return value;
|
|
42
|
-
}
|
|
43
|
-
async function runtimeStatus(runtime) {
|
|
44
|
-
const statuses = runtime.kernel.listPlugins();
|
|
45
|
-
const sessions = await new SqliteSessionStore(runtime.database).count();
|
|
46
|
-
return {
|
|
47
|
-
generation: runtime.generation,
|
|
48
|
-
state: runtime.kernel.state,
|
|
49
|
-
uptimeMs: runtime.kernel.uptimeMs,
|
|
50
|
-
connected: runtime.kernel.isConnected,
|
|
51
|
-
login: await loginInfo(runtime),
|
|
52
|
-
counts: {
|
|
53
|
-
plugins: statuses.length,
|
|
54
|
-
loadedPlugins: statuses.filter((entry) => entry.loaded).length,
|
|
55
|
-
enabledPlugins: statuses.filter((entry) => entry.enabled).length,
|
|
56
|
-
sessions,
|
|
57
|
-
kvPlugins: countKvPlugins(runtime.database),
|
|
58
|
-
},
|
|
59
|
-
};
|
|
60
|
-
}
|
|
61
|
-
function pathsInfo(deps) {
|
|
62
|
-
return {
|
|
63
|
-
config: deps.paths.configPath,
|
|
64
|
-
data: deps.paths.dataDir,
|
|
65
|
-
plugins: deps.paths.pluginDir,
|
|
66
|
-
};
|
|
67
|
-
}
|
|
68
|
-
function assertLevel(value) {
|
|
69
|
-
if (!logLevels.includes(value)) {
|
|
70
|
-
throw new HttpError(400, `invalid log level: ${value}`);
|
|
71
|
-
}
|
|
72
|
-
return value;
|
|
73
|
-
}
|
|
74
|
-
const FEED_TYPES = [
|
|
75
|
-
'message',
|
|
76
|
-
'notice',
|
|
77
|
-
'request',
|
|
78
|
-
'meta',
|
|
79
|
-
'other',
|
|
80
|
-
];
|
|
81
|
-
function assertFeedType(value) {
|
|
82
|
-
if (!FEED_TYPES.includes(value)) {
|
|
83
|
-
throw new HttpError(400, `invalid event type: ${value}`);
|
|
84
|
-
}
|
|
85
|
-
return value;
|
|
86
|
-
}
|
|
87
|
-
function clampInt(value, fallback, min, max) {
|
|
88
|
-
if (value === null)
|
|
89
|
-
return fallback;
|
|
90
|
-
const parsed = Number.parseInt(value, 10);
|
|
91
|
-
if (!Number.isFinite(parsed))
|
|
92
|
-
return fallback;
|
|
93
|
-
return Math.min(Math.max(parsed, min), max);
|
|
94
|
-
}
|
|
95
|
-
function asRecord(value) {
|
|
96
|
-
return isRecord(value) ? value : {};
|
|
97
|
-
}
|
|
98
|
-
function parseSessionMessages(value) {
|
|
99
|
-
if (!Array.isArray(value)) {
|
|
100
|
-
throw new HttpError(400, 'messages must be an array');
|
|
101
|
-
}
|
|
102
|
-
if (value.length > MAX_SESSION_MESSAGES) {
|
|
103
|
-
throw new HttpError(400, 'messages too long');
|
|
104
|
-
}
|
|
105
|
-
return value.map((item, index) => {
|
|
106
|
-
if (!isRecord(item)) {
|
|
107
|
-
throw new HttpError(400, `invalid message at index ${index}`);
|
|
108
|
-
}
|
|
109
|
-
const role = item['role'];
|
|
110
|
-
const content = item['content'];
|
|
111
|
-
const at = item['at'];
|
|
112
|
-
if (role !== 'system' && role !== 'user' && role !== 'assistant') {
|
|
113
|
-
throw new HttpError(400, `invalid message role at index ${index}`);
|
|
114
|
-
}
|
|
115
|
-
if (typeof content !== 'string') {
|
|
116
|
-
throw new HttpError(400, `invalid message content at index ${index}`);
|
|
117
|
-
}
|
|
118
|
-
if (typeof at !== 'number' || !Number.isFinite(at)) {
|
|
119
|
-
throw new HttpError(400, `invalid message time at index ${index}`);
|
|
120
|
-
}
|
|
121
|
-
return { role: role, content, at };
|
|
122
|
-
});
|
|
123
|
-
}
|
|
124
|
-
function clearableString(value, fallback) {
|
|
125
|
-
if (value === null)
|
|
126
|
-
return undefined;
|
|
127
|
-
if (value === undefined)
|
|
128
|
-
return fallback;
|
|
129
|
-
if (typeof value === 'string' && value.trim().length > 0) {
|
|
130
|
-
return value;
|
|
131
|
-
}
|
|
132
|
-
return undefined;
|
|
133
|
-
}
|
|
134
|
-
async function handleStatus(ctx) {
|
|
135
|
-
const { deps, res } = ctx;
|
|
136
|
-
const runtime = deps.getRuntime();
|
|
137
|
-
const webui = {
|
|
138
|
-
listening: deps.getListening(),
|
|
139
|
-
requiresProcessRestart: deps.getRequiresProcessRestart(),
|
|
140
|
-
};
|
|
141
|
-
if (!runtime) {
|
|
142
|
-
sendData(res, 200, {
|
|
143
|
-
restarting: deps.isRestarting(),
|
|
144
|
-
startupError: deps.getStartupError(),
|
|
145
|
-
runtime: null,
|
|
146
|
-
webui,
|
|
147
|
-
version: deps.version,
|
|
148
|
-
paths: pathsInfo(deps),
|
|
149
|
-
});
|
|
150
|
-
return;
|
|
151
|
-
}
|
|
152
|
-
sendData(res, 200, {
|
|
153
|
-
restarting: deps.isRestarting(),
|
|
154
|
-
startupError: deps.getStartupError(),
|
|
155
|
-
runtime: await runtimeStatus(runtime),
|
|
156
|
-
webui,
|
|
157
|
-
version: deps.version,
|
|
158
|
-
paths: pathsInfo(deps),
|
|
159
|
-
});
|
|
160
|
-
}
|
|
161
|
-
async function handleLogsList(ctx) {
|
|
162
|
-
const level = ctx.search.get('level');
|
|
163
|
-
if (level)
|
|
164
|
-
assertLevel(level);
|
|
165
|
-
const scope = ctx.search.get('scope') ?? undefined;
|
|
166
|
-
const query = ctx.search.get('query') ?? undefined;
|
|
167
|
-
const limit = clampInt(ctx.search.get('limit'), 200, 1, 1_000);
|
|
168
|
-
sendData(ctx.res, 200, ctx.deps.logs.list({
|
|
169
|
-
...(level ? { level: assertLevel(level) } : {}),
|
|
170
|
-
scope,
|
|
171
|
-
query,
|
|
172
|
-
limit,
|
|
173
|
-
}));
|
|
174
|
-
}
|
|
175
|
-
async function handleEventsList(ctx) {
|
|
176
|
-
const type = ctx.search.get('type');
|
|
177
|
-
if (type)
|
|
178
|
-
assertFeedType(type);
|
|
179
|
-
const limit = clampInt(ctx.search.get('limit'), 100, 1, 500);
|
|
180
|
-
sendData(ctx.res, 200, ctx.deps.events.list({
|
|
181
|
-
...(type ? { type: assertFeedType(type) } : {}),
|
|
182
|
-
limit,
|
|
183
|
-
}));
|
|
184
|
-
}
|
|
185
|
-
async function handleSessionList(ctx) {
|
|
186
|
-
const runtime = requireRuntime(ctx.deps);
|
|
187
|
-
const store = new SqliteSessionStore(runtime.database);
|
|
188
|
-
const limit = clampInt(ctx.search.get('limit'), 50, 1, 200);
|
|
189
|
-
const offset = clampInt(ctx.search.get('offset'), 0, 0, 10_000_000);
|
|
190
|
-
const query = ctx.search.get('query')?.slice(0, 200) || undefined;
|
|
191
|
-
const items = await store.list({ limit, offset, query });
|
|
192
|
-
const total = await store.count(query);
|
|
193
|
-
sendData(ctx.res, 200, { items, total, limit, offset });
|
|
194
|
-
}
|
|
195
|
-
function sessionIdOf(ctx) {
|
|
196
|
-
const id = ctx.segments[2];
|
|
197
|
-
if (!id)
|
|
198
|
-
throw new HttpError(404, 'session not found');
|
|
199
|
-
return id;
|
|
200
|
-
}
|
|
201
|
-
function requireSessionStore(ctx) {
|
|
202
|
-
const runtime = requireRuntime(ctx.deps);
|
|
203
|
-
return new SqliteSessionStore(runtime.database);
|
|
204
|
-
}
|
|
205
|
-
async function sessionDetail(ctx) {
|
|
206
|
-
const id = sessionIdOf(ctx);
|
|
207
|
-
const state = await requireSessionStore(ctx).get(id);
|
|
208
|
-
if (!state)
|
|
209
|
-
throw new HttpError(404, 'session not found');
|
|
210
|
-
return state;
|
|
211
|
-
}
|
|
212
|
-
async function handleSessionGet(ctx) {
|
|
213
|
-
sendData(ctx.res, 200, await sessionDetail(ctx));
|
|
214
|
-
}
|
|
215
|
-
async function handleSessionPut(ctx) {
|
|
216
|
-
const body = asRecord(await readJsonBody(ctx.req));
|
|
217
|
-
await sessionDetail(ctx);
|
|
218
|
-
const messages = body['messages'] === undefined
|
|
219
|
-
? undefined
|
|
220
|
-
: parseSessionMessages(body['messages']);
|
|
221
|
-
const baseline = await sessionDetail(ctx);
|
|
222
|
-
const state = {
|
|
223
|
-
sessionId: sessionIdOf(ctx),
|
|
224
|
-
provider: clearableString(body['provider'], baseline.provider),
|
|
225
|
-
model: clearableString(body['model'], baseline.model),
|
|
226
|
-
persona: clearableString(body['persona'], baseline.persona),
|
|
227
|
-
messages: messages ?? baseline.messages,
|
|
228
|
-
updatedAt: Date.now(),
|
|
229
|
-
};
|
|
230
|
-
await requireSessionStore(ctx).save(state);
|
|
231
|
-
sendData(ctx.res, 200, state);
|
|
232
|
-
}
|
|
233
|
-
async function handleSessionMessages(ctx) {
|
|
234
|
-
const body = asRecord(await readJsonBody(ctx.req));
|
|
235
|
-
const role = body['role'];
|
|
236
|
-
const content = body['content'];
|
|
237
|
-
if (role !== 'system' && role !== 'user' && role !== 'assistant') {
|
|
238
|
-
throw new HttpError(400, 'invalid message role');
|
|
239
|
-
}
|
|
240
|
-
if (typeof content !== 'string' ||
|
|
241
|
-
content.length === 0 ||
|
|
242
|
-
content.length > 100_000) {
|
|
243
|
-
throw new HttpError(400, 'invalid message content');
|
|
244
|
-
}
|
|
245
|
-
const state = await sessionDetail(ctx);
|
|
246
|
-
const message = { role: role, content, at: Date.now() };
|
|
247
|
-
const next = {
|
|
248
|
-
...state,
|
|
249
|
-
messages: [...state.messages, message],
|
|
250
|
-
updatedAt: Date.now(),
|
|
251
|
-
};
|
|
252
|
-
await requireSessionStore(ctx).save(next);
|
|
253
|
-
sendData(ctx.res, 200, next);
|
|
254
|
-
}
|
|
255
|
-
async function handleSessionSend(ctx) {
|
|
256
|
-
const body = asRecord(await readJsonBody(ctx.req));
|
|
257
|
-
const id = sessionIdOf(ctx);
|
|
258
|
-
const message = body['message'];
|
|
259
|
-
const autoEscape = body['autoEscape'];
|
|
260
|
-
if (typeof message !== 'string' ||
|
|
261
|
-
message.length === 0 ||
|
|
262
|
-
message.length > 20_000) {
|
|
263
|
-
throw new HttpError(400, 'invalid message');
|
|
264
|
-
}
|
|
265
|
-
const runtime = requireRuntime(ctx.deps);
|
|
266
|
-
await sessionDetail(ctx);
|
|
267
|
-
const result = await runtime.api.send(id, message, typeof autoEscape === 'boolean' ? { autoEscape } : undefined);
|
|
268
|
-
sendData(ctx.res, 200, { result: result ?? null });
|
|
269
|
-
}
|
|
270
|
-
async function handleSessionDelete(ctx) {
|
|
271
|
-
await requireSessionStore(ctx).clear(sessionIdOf(ctx));
|
|
272
|
-
sendData(ctx.res, 200, { cleared: true });
|
|
273
|
-
}
|
|
274
|
-
async function handlePluginsList(ctx) {
|
|
275
|
-
const runtime = requireRuntime(ctx.deps);
|
|
276
|
-
const statuses = new Map(runtime.kernel.listPlugins().map((entry) => [entry.id, entry]));
|
|
277
|
-
const plugins = runtime.plugins.map((definition) => {
|
|
278
|
-
const id = definition.manifest.id;
|
|
279
|
-
const status = statuses.get(id);
|
|
280
|
-
const envelope = asRecord(runtime.config.plugins[id]);
|
|
281
|
-
return {
|
|
282
|
-
id,
|
|
283
|
-
version: definition.manifest.version,
|
|
284
|
-
description: definition.manifest.description,
|
|
285
|
-
dependencies: definition.manifest.dependencies ?? [],
|
|
286
|
-
defaultEnabled: definition.manifest.defaultEnabled ?? false,
|
|
287
|
-
enabled: status?.enabled ?? false,
|
|
288
|
-
loaded: status?.loaded ?? false,
|
|
289
|
-
config: envelope,
|
|
290
|
-
options: stripPluginEnvelope(envelope),
|
|
291
|
-
};
|
|
292
|
-
});
|
|
293
|
-
const installed = runtime.installer.enabled
|
|
294
|
-
? await runtime.installer.list()
|
|
295
|
-
: [];
|
|
296
|
-
sendData(ctx.res, 200, {
|
|
297
|
-
plugins,
|
|
298
|
-
installed,
|
|
299
|
-
installerEnabled: runtime.installer.enabled,
|
|
300
|
-
configHash: (await ctx.deps.readConfig()).hash,
|
|
301
|
-
});
|
|
302
|
-
}
|
|
303
|
-
const ENVELOPE_KEYS = new Set(['enabled', 'private', 'groups']);
|
|
304
|
-
async function handlePluginConfigPut(ctx) {
|
|
305
|
-
const body = asRecord(await readJsonBody(ctx.req));
|
|
306
|
-
const id = ctx.segments[2];
|
|
307
|
-
const document = await ctx.deps.readConfig();
|
|
308
|
-
if (id === undefined)
|
|
309
|
-
throw new HttpError(404, 'unknown plugin');
|
|
310
|
-
if (!isRecord(document.config.plugins[id])) {
|
|
311
|
-
throw new HttpError(404, `unknown plugin: ${id}`);
|
|
312
|
-
}
|
|
313
|
-
const existing = asRecord(document.config.plugins[id]);
|
|
314
|
-
const next = { ...existing };
|
|
315
|
-
if (typeof body['enabled'] === 'boolean') {
|
|
316
|
-
next['enabled'] = body['enabled'];
|
|
317
|
-
}
|
|
318
|
-
if (typeof body['private'] === 'boolean') {
|
|
319
|
-
next['private'] = body['private'];
|
|
320
|
-
}
|
|
321
|
-
if (body['groups'] !== undefined)
|
|
322
|
-
next['groups'] = body['groups'];
|
|
323
|
-
if (body['options'] !== undefined) {
|
|
324
|
-
if (!isRecord(body['options'])) {
|
|
325
|
-
throw new HttpError(400, 'options must be an object');
|
|
326
|
-
}
|
|
327
|
-
for (const key of Object.keys(next)) {
|
|
328
|
-
if (!ENVELOPE_KEYS.has(key))
|
|
329
|
-
delete next[key];
|
|
330
|
-
}
|
|
331
|
-
for (const [key, value] of Object.entries(body['options'])) {
|
|
332
|
-
if (!ENVELOPE_KEYS.has(key))
|
|
333
|
-
next[key] = value;
|
|
334
|
-
}
|
|
335
|
-
}
|
|
336
|
-
const config = {
|
|
337
|
-
...document.config,
|
|
338
|
-
plugins: {
|
|
339
|
-
...document.config.plugins,
|
|
340
|
-
[id]: next,
|
|
341
|
-
},
|
|
342
|
-
};
|
|
343
|
-
const baseHash = body['baseHash'];
|
|
344
|
-
sendData(ctx.res, 200, await ctx.deps.applyConfig(config, typeof baseHash === 'string' ? baseHash : undefined));
|
|
345
|
-
}
|
|
346
|
-
async function handleInstallerAction(ctx) {
|
|
347
|
-
const runtime = requireRuntime(ctx.deps);
|
|
348
|
-
if (!runtime.installer.enabled) {
|
|
349
|
-
throw new HttpError(403, '插件安装器未启用');
|
|
350
|
-
}
|
|
351
|
-
const body = asRecord(await readJsonBody(ctx.req));
|
|
352
|
-
const target = typeof body['target'] === 'string' ? body['target'] : '';
|
|
353
|
-
const spec = typeof body['spec'] === 'string' ? body['spec'] : '';
|
|
354
|
-
if (ctx.pathname.endsWith('/install')) {
|
|
355
|
-
if (!spec)
|
|
356
|
-
throw new HttpError(400, 'spec is required');
|
|
357
|
-
sendData(ctx.res, 200, await ctx.deps.runExclusive(() => runtime.installer.install(spec)));
|
|
358
|
-
return;
|
|
359
|
-
}
|
|
360
|
-
if (ctx.pathname.endsWith('/update')) {
|
|
361
|
-
sendData(ctx.res, 200, await ctx.deps.runExclusive(() => runtime.installer.update(target || undefined)));
|
|
362
|
-
return;
|
|
363
|
-
}
|
|
364
|
-
sendData(ctx.res, 200, await ctx.deps.runExclusive(async () => runtime.installer.remove(target, undefined, {
|
|
365
|
-
force: body['force'] === true,
|
|
366
|
-
})));
|
|
367
|
-
}
|
|
368
|
-
async function handleConfigGet(ctx) {
|
|
369
|
-
const document = await ctx.deps.readConfig();
|
|
370
|
-
const masked = maskConfig(document.config);
|
|
371
|
-
sendData(ctx.res, 200, {
|
|
372
|
-
config: masked.config,
|
|
373
|
-
meta: {
|
|
374
|
-
path: document.path,
|
|
375
|
-
hash: document.hash,
|
|
376
|
-
accessTokenSet: masked.accessTokenSet,
|
|
377
|
-
webuiPasswordSet: masked.webuiPasswordSet,
|
|
378
|
-
},
|
|
379
|
-
});
|
|
380
|
-
}
|
|
381
|
-
async function handleConfigValidate(ctx) {
|
|
382
|
-
const body = asRecord(await readJsonBody(ctx.req));
|
|
383
|
-
const result = await ctx.deps.validateConfig(body['config']);
|
|
384
|
-
sendData(ctx.res, 200, {
|
|
385
|
-
ok: result.ok,
|
|
386
|
-
issues: result.issues,
|
|
387
|
-
});
|
|
388
|
-
}
|
|
389
|
-
async function handleConfigPut(ctx) {
|
|
390
|
-
const body = asRecord(await readJsonBody(ctx.req));
|
|
391
|
-
const baseHash = body['baseHash'];
|
|
392
|
-
sendData(ctx.res, 200, await ctx.deps.applyConfig(body['config'], typeof baseHash === 'string' ? baseHash : undefined));
|
|
393
|
-
}
|
|
394
|
-
function assertPluginId(value) {
|
|
395
|
-
if (!/^\w[\w.-]*$/.test(value)) {
|
|
396
|
-
throw new HttpError(400, `invalid plugin id: ${value}`);
|
|
397
|
-
}
|
|
398
|
-
return value;
|
|
399
|
-
}
|
|
400
|
-
function assertKey(value) {
|
|
401
|
-
if (value.length === 0 || value.length > 512) {
|
|
402
|
-
throw new HttpError(400, 'invalid key length');
|
|
403
|
-
}
|
|
404
|
-
return value;
|
|
405
|
-
}
|
|
406
|
-
function parseKvValue(value) {
|
|
407
|
-
try {
|
|
408
|
-
return JSON.parse(value);
|
|
409
|
-
}
|
|
410
|
-
catch {
|
|
411
|
-
return value;
|
|
412
|
-
}
|
|
413
|
-
}
|
|
414
|
-
async function handleKvPlugins(ctx) {
|
|
415
|
-
const runtime = requireRuntime(ctx.deps);
|
|
416
|
-
const rows = runtime.database.connection
|
|
417
|
-
.prepare('SELECT plugin_id, COUNT(*) AS count FROM plugin_kv GROUP BY plugin_id ORDER BY plugin_id')
|
|
418
|
-
.all();
|
|
419
|
-
sendData(ctx.res, 200, rows.map((row) => ({ pluginId: row.plugin_id, count: row.count })));
|
|
420
|
-
}
|
|
421
|
-
async function handleKvList(ctx) {
|
|
422
|
-
const runtime = requireRuntime(ctx.deps);
|
|
423
|
-
const pluginId = assertPluginId(ctx.search.get('pluginId') ?? '');
|
|
424
|
-
const prefix = ctx.search.get('prefix') ?? '';
|
|
425
|
-
const limit = clampInt(ctx.search.get('limit'), 100, 1, 500);
|
|
426
|
-
const offset = clampInt(ctx.search.get('offset'), 0, 0, 10_000_000);
|
|
427
|
-
const pattern = prefix === ''
|
|
428
|
-
? '%'
|
|
429
|
-
: `${prefix.replace(/[%_]/g, (match) => `\\${match}`)}%`;
|
|
430
|
-
const rows = runtime.database.connection
|
|
431
|
-
.prepare(`SELECT key, value, updated_at FROM plugin_kv
|
|
432
|
-
WHERE plugin_id = ? AND key LIKE ? ESCAPE '\\'
|
|
433
|
-
ORDER BY key
|
|
434
|
-
LIMIT ? OFFSET ?`)
|
|
435
|
-
.all(pluginId, pattern, limit, offset);
|
|
436
|
-
const totalRow = runtime.database.connection
|
|
437
|
-
.prepare(`SELECT COUNT(*) AS count FROM plugin_kv
|
|
438
|
-
WHERE plugin_id = ? AND key LIKE ? ESCAPE '\\'`)
|
|
439
|
-
.get(pluginId, pattern);
|
|
440
|
-
sendData(ctx.res, 200, {
|
|
441
|
-
items: rows.map((row) => ({
|
|
442
|
-
pluginId,
|
|
443
|
-
key: row.key,
|
|
444
|
-
value: parseKvValue(row.value),
|
|
445
|
-
updatedAt: row.updated_at,
|
|
446
|
-
})),
|
|
447
|
-
total: totalRow?.count ?? 0,
|
|
448
|
-
limit,
|
|
449
|
-
offset,
|
|
450
|
-
});
|
|
451
|
-
}
|
|
452
|
-
async function handleKvPut(ctx) {
|
|
453
|
-
const body = asRecord(await readJsonBody(ctx.req));
|
|
454
|
-
const runtime = requireRuntime(ctx.deps);
|
|
455
|
-
const pluginId = assertPluginId(typeof body['pluginId'] === 'string' ? body['pluginId'] : '');
|
|
456
|
-
const key = assertKey(typeof body['key'] === 'string' ? body['key'] : '');
|
|
457
|
-
if (typeof body['value'] === 'function') {
|
|
458
|
-
throw new HttpError(400, 'invalid value');
|
|
459
|
-
}
|
|
460
|
-
await new SqlitePluginKV(runtime.database, pluginId).set(key, body['value']);
|
|
461
|
-
sendData(ctx.res, 200, { ok: true });
|
|
462
|
-
}
|
|
463
|
-
async function handleKvDelete(ctx) {
|
|
464
|
-
const runtime = requireRuntime(ctx.deps);
|
|
465
|
-
const pluginId = assertPluginId(ctx.search.get('pluginId') ?? '');
|
|
466
|
-
const key = assertKey(ctx.search.get('key') ?? '');
|
|
467
|
-
const deleted = await new SqlitePluginKV(runtime.database, pluginId).delete(key);
|
|
468
|
-
sendData(ctx.res, 200, { ok: deleted });
|
|
469
|
-
}
|
|
470
|
-
async function handleActions(ctx) {
|
|
471
|
-
const body = asRecord(await readJsonBody(ctx.req));
|
|
472
|
-
const action = body['action'];
|
|
473
|
-
if (typeof action !== 'string' ||
|
|
474
|
-
action.length === 0 ||
|
|
475
|
-
action.length > 128) {
|
|
476
|
-
throw new HttpError(400, 'action is required');
|
|
477
|
-
}
|
|
478
|
-
const runtime = requireRuntime(ctx.deps);
|
|
479
|
-
const params = body['params'] ?? undefined;
|
|
480
|
-
const result = await runtime.api.call(action, params);
|
|
481
|
-
sendData(ctx.res, 200, { result: result ?? null });
|
|
482
|
-
}
|
|
483
|
-
async function handleRuntimeCommand(ctx) {
|
|
484
|
-
if (ctx.pathname.endsWith('/restart')) {
|
|
485
|
-
sendData(ctx.res, 200, {
|
|
486
|
-
ok: await ctx.deps.restart(),
|
|
487
|
-
});
|
|
488
|
-
return;
|
|
489
|
-
}
|
|
490
|
-
if (ctx.pathname.endsWith('/pause')) {
|
|
491
|
-
ctx.deps.pause();
|
|
492
|
-
sendData(ctx.res, 200, { ok: true });
|
|
493
|
-
return;
|
|
494
|
-
}
|
|
495
|
-
if (ctx.pathname.endsWith('/resume')) {
|
|
496
|
-
ctx.deps.resume();
|
|
497
|
-
sendData(ctx.res, 200, { ok: true });
|
|
498
|
-
}
|
|
499
|
-
}
|
|
1
|
+
import { HttpError } from './http.js';
|
|
2
|
+
import { handleStatus, handleCommands, handleLogsList, handleEventsList, } from './routes/status.js';
|
|
3
|
+
import { handleSessionDelete, handleSessionGet, handleSessionList, handleSessionMessages, handleSessionPut, handleSessionSend, } from './routes/sessions.js';
|
|
4
|
+
import { handleInstallerAction, handlePluginConfigPut, handlePluginsList, } from './routes/plugins.js';
|
|
5
|
+
import { handleConfigGet, handleConfigPut, handleConfigValidate, } from './routes/config.js';
|
|
6
|
+
import { handleKvDelete, handleKvList, handleKvPlugins, handleKvPut, } from './routes/kv.js';
|
|
7
|
+
import { handleActions, handleRuntimeCommand } from './routes/actions.js';
|
|
500
8
|
export async function handleApiRoute(ctx) {
|
|
501
9
|
const { method, pathname } = ctx;
|
|
502
10
|
if (method === 'GET' && pathname === '/api/status') {
|
|
@@ -504,7 +12,7 @@ export async function handleApiRoute(ctx) {
|
|
|
504
12
|
return true;
|
|
505
13
|
}
|
|
506
14
|
if (method === 'GET' && pathname === '/api/commands') {
|
|
507
|
-
|
|
15
|
+
await handleCommands(ctx);
|
|
508
16
|
return true;
|
|
509
17
|
}
|
|
510
18
|
if (method === 'POST' &&
|