@jesonliu/lark-claudecode-bridge 0.10.0 → 0.13.0
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 +84 -46
- package/assets/web/index.html +1416 -0
- package/config.example.yaml +22 -2
- package/dist/auth-precheck.d.ts +5 -2
- package/dist/auth-precheck.js +8 -2
- package/dist/auth-precheck.js.map +1 -1
- package/dist/bin/lcb.js +34 -3
- package/dist/bin/lcb.js.map +1 -1
- package/dist/claude-config.d.ts +19 -0
- package/dist/claude-config.js +76 -0
- package/dist/claude-config.js.map +1 -0
- package/dist/cli/setup-wizard.d.ts +1 -0
- package/dist/cli/setup-wizard.js +4 -0
- package/dist/cli/setup-wizard.js.map +1 -1
- package/dist/config-defaults.d.ts +4 -0
- package/dist/config-defaults.js +15 -0
- package/dist/config-defaults.js.map +1 -0
- package/dist/config.d.ts +7 -0
- package/dist/config.js +190 -6
- package/dist/config.js.map +1 -1
- package/dist/executor/permission-gate.d.ts +8 -1
- package/dist/executor/permission-gate.js +21 -14
- package/dist/executor/permission-gate.js.map +1 -1
- package/dist/executor/plugin-discovery.d.ts +11 -0
- package/dist/executor/plugin-discovery.js +28 -0
- package/dist/executor/plugin-discovery.js.map +1 -1
- package/dist/executor/plugin-manager.d.ts +19 -0
- package/dist/executor/plugin-manager.js +84 -0
- package/dist/executor/plugin-manager.js.map +1 -0
- package/dist/feishu/slash-commands.d.ts +77 -0
- package/dist/feishu/slash-commands.js +188 -0
- package/dist/feishu/slash-commands.js.map +1 -0
- package/dist/gateway/card-builder.d.ts +2 -0
- package/dist/gateway/card-builder.js +3 -1
- package/dist/gateway/card-builder.js.map +1 -1
- package/dist/gateway/feishu-gateway.d.ts +37 -2
- package/dist/gateway/feishu-gateway.js +153 -8
- package/dist/gateway/feishu-gateway.js.map +1 -1
- package/dist/index.js +87 -20
- package/dist/index.js.map +1 -1
- package/dist/session/commands.d.ts +16 -1
- package/dist/session/commands.js +33 -1
- package/dist/session/commands.js.map +1 -1
- package/dist/session/plugin-command.d.ts +14 -0
- package/dist/session/plugin-command.js +110 -0
- package/dist/session/plugin-command.js.map +1 -0
- package/dist/types.d.ts +69 -0
- package/dist/util/open-browser.d.ts +1 -0
- package/dist/util/open-browser.js +20 -0
- package/dist/util/open-browser.js.map +1 -0
- package/dist/util/server-url.d.ts +3 -0
- package/dist/util/server-url.js +10 -0
- package/dist/util/server-url.js.map +1 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +6 -2
- package/dist/version.js.map +1 -1
- package/dist/web/config-api.d.ts +50 -0
- package/dist/web/config-api.js +184 -0
- package/dist/web/config-api.js.map +1 -0
- package/dist/web/lifecycle.d.ts +52 -0
- package/dist/web/lifecycle.js +167 -0
- package/dist/web/lifecycle.js.map +1 -0
- package/dist/web/model-list.d.ts +36 -0
- package/dist/web/model-list.js +101 -0
- package/dist/web/model-list.js.map +1 -0
- package/dist/web/server.d.ts +22 -0
- package/dist/web/server.js +543 -0
- package/dist/web/server.js.map +1 -0
- package/dist/web/update.d.ts +26 -0
- package/dist/web/update.js +64 -0
- package/dist/web/update.js.map +1 -0
- package/package.json +2 -1
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
// 桥接器进程生命周期:PID 文件探活 / 后台拉起 / 跨进程停止 / 旧进程退出后重启。
|
|
2
|
+
// 消费方:Web 配置页 /api/bridge/* 端点(页面按钮启停);startBridge 写/清 PID(src/index.ts)。
|
|
3
|
+
// 路径参数可注入(默认 CONFIG_DIR),便于测试用临时目录
|
|
4
|
+
import { closeSync, existsSync, openSync, readFileSync, rmSync, statSync, writeFileSync } from 'node:fs';
|
|
5
|
+
import { spawn } from 'node:child_process';
|
|
6
|
+
import { join } from 'node:path';
|
|
7
|
+
import { fileURLToPath } from 'node:url';
|
|
8
|
+
import { CONFIG_DIR } from '../config.js';
|
|
9
|
+
const PID_PATH = join(CONFIG_DIR, 'bridge.pid');
|
|
10
|
+
const LOG_PATH = join(CONFIG_DIR, 'bridge.log');
|
|
11
|
+
/** 后台运行日志超 5MB 先截断,避免常驻进程日志无限增长 */
|
|
12
|
+
const LOG_TRUNCATE_BYTES = 5 * 1024 * 1024;
|
|
13
|
+
/** 桥接器进程启动成功后自写 PID 文件(页面据此探活/跨进程停止) */
|
|
14
|
+
export function writePidFile(pidPath = PID_PATH) {
|
|
15
|
+
writeFileSync(pidPath, String(process.pid), 'utf8');
|
|
16
|
+
}
|
|
17
|
+
/** 优雅关闭时自清 PID 文件;文件已被其它进程覆写(重启竞态)时不误删 */
|
|
18
|
+
export function clearPidFile(pidPath = PID_PATH) {
|
|
19
|
+
try {
|
|
20
|
+
if (readPidFile(pidPath) === process.pid)
|
|
21
|
+
rmSync(pidPath, { force: true });
|
|
22
|
+
}
|
|
23
|
+
catch { /* 文件不存在等:无事 */ }
|
|
24
|
+
}
|
|
25
|
+
function readPidFile(pidPath) {
|
|
26
|
+
try {
|
|
27
|
+
const n = Number.parseInt(readFileSync(pidPath, 'utf8').trim(), 10);
|
|
28
|
+
return Number.isInteger(n) && n > 0 ? n : null;
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
/** 进程是否存活:signal 0 仅探测不发送;EPERM = 存在但属他人,仍视为存活 */
|
|
35
|
+
function isPidAlive(pid) {
|
|
36
|
+
try {
|
|
37
|
+
process.kill(pid, 0);
|
|
38
|
+
return true;
|
|
39
|
+
}
|
|
40
|
+
catch (e) {
|
|
41
|
+
return e.code === 'EPERM';
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
/** 探活桥接器;PID 文件指向已消亡进程(崩溃/被硬杀未及清理)时顺手清掉陈旧文件 */
|
|
45
|
+
export function bridgeStatus(pidPath = PID_PATH) {
|
|
46
|
+
const pid = readPidFile(pidPath);
|
|
47
|
+
if (pid === null)
|
|
48
|
+
return { running: false, pid: null };
|
|
49
|
+
if (isPidAlive(pid))
|
|
50
|
+
return { running: true, pid };
|
|
51
|
+
try {
|
|
52
|
+
rmSync(pidPath, { force: true });
|
|
53
|
+
}
|
|
54
|
+
catch { /* 并发清理竞态:忽略 */ }
|
|
55
|
+
return { running: false, pid: null };
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* 解析 lcb CLI 入口(发布包 dist/bin/lcb.js;模块位置相对定位,tsx 源码模式下该路径不存在)。
|
|
59
|
+
* 源码运行时返回 error——页面按钮禁用并提示手动启停
|
|
60
|
+
*/
|
|
61
|
+
export function resolveLcbEntry() {
|
|
62
|
+
// dist/web/lifecycle.js → ../bin/lcb.js = dist/bin/lcb.js;tsx 源码模式下对应 src/bin/lcb.js
|
|
63
|
+
// (实际是 lcb.ts,node 跑不了)→ 不存在即返回 error,页面按钮禁用提示手动启停
|
|
64
|
+
const entry = fileURLToPath(new URL('../bin/lcb.js', import.meta.url));
|
|
65
|
+
if (!existsSync(entry)) {
|
|
66
|
+
return { ok: false, error: '未找到 dist/bin/lcb.js:源码(tsx)运行模式不支持由页面启停桥接器,请手动运行 lcb start' };
|
|
67
|
+
}
|
|
68
|
+
return { ok: true, entry };
|
|
69
|
+
}
|
|
70
|
+
/** 打开后台日志 fd(append;超 5MB 先截断) */
|
|
71
|
+
function openLogFd() {
|
|
72
|
+
try {
|
|
73
|
+
if (existsSync(LOG_PATH) && statSync(LOG_PATH).size > LOG_TRUNCATE_BYTES)
|
|
74
|
+
rmSync(LOG_PATH, { force: true });
|
|
75
|
+
}
|
|
76
|
+
catch { /* 截断失败继续 append */ }
|
|
77
|
+
return openSync(LOG_PATH, 'a');
|
|
78
|
+
}
|
|
79
|
+
/** 后台拉起桥接器:detached 守护进程,stdio 落 bridge.log,父进程退出不影响其存活 */
|
|
80
|
+
export function spawnBridgeDetached() {
|
|
81
|
+
const r = resolveLcbEntry();
|
|
82
|
+
if (!r.ok)
|
|
83
|
+
return r;
|
|
84
|
+
const fd = openLogFd();
|
|
85
|
+
const child = spawn(process.execPath, [r.entry, 'start'], {
|
|
86
|
+
detached: true,
|
|
87
|
+
stdio: ['ignore', fd, fd],
|
|
88
|
+
windowsHide: true,
|
|
89
|
+
});
|
|
90
|
+
child.unref();
|
|
91
|
+
closeSync(fd); // 父进程关闭自有 fd 副本(子进程已继承)
|
|
92
|
+
return { ok: true, pid: child.pid ?? -1 };
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* 停止指定 PID 的桥接器。Windows 上 SIGTERM 等效 TerminateProcess(不走进程内优雅关闭
|
|
96
|
+
* handler);会话/配对状态均逐消息落盘,硬终止风险可控。同进程内(embedded)停止走
|
|
97
|
+
* server.ts 注入的 selfStop 优雅路径,不经本函数
|
|
98
|
+
*/
|
|
99
|
+
export function stopBridgeByPid(pid) {
|
|
100
|
+
try {
|
|
101
|
+
process.kill(pid, 'SIGTERM');
|
|
102
|
+
return true;
|
|
103
|
+
}
|
|
104
|
+
catch {
|
|
105
|
+
return false; // 已退出或无权限
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* 内嵌重启 helper(CJS,node -e 执行):轮询旧进程退出 → 拉起新桥接器 → 自退。
|
|
110
|
+
* 不能「先拉新再杀旧」:新进程的 Web 配置页会因端口仍被旧进程占用而启动失败,
|
|
111
|
+
* 导致重启后页面失联。超时 30s 放弃(保留旧进程运行态,页面可重试)
|
|
112
|
+
*/
|
|
113
|
+
const RESTART_HELPER = `
|
|
114
|
+
const { spawn } = require('node:child_process');
|
|
115
|
+
const { openSync, closeSync, existsSync, statSync, rmSync } = require('node:fs');
|
|
116
|
+
const [oldPid, entry, logPath] = process.argv.slice(1);
|
|
117
|
+
const t0 = Date.now();
|
|
118
|
+
(function wait() {
|
|
119
|
+
try { process.kill(+oldPid, 0); } catch {
|
|
120
|
+
try {
|
|
121
|
+
if (existsSync(logPath) && statSync(logPath).size > 5*1024*1024) rmSync(logPath, { force: true });
|
|
122
|
+
const fd = openSync(logPath, 'a');
|
|
123
|
+
const child = spawn(process.execPath, [entry, 'start'], { detached: true, stdio: ['ignore', fd, fd], windowsHide: true });
|
|
124
|
+
child.unref();
|
|
125
|
+
closeSync(fd);
|
|
126
|
+
} catch (e) { try { console.error('restart helper failed:', e); } catch {} }
|
|
127
|
+
process.exit(0);
|
|
128
|
+
}
|
|
129
|
+
if (Date.now() - t0 > 30000) process.exit(1);
|
|
130
|
+
setTimeout(wait, 300);
|
|
131
|
+
})();
|
|
132
|
+
`;
|
|
133
|
+
/** 重启桥接器:spawn detached helper 由其等待旧进程退出后拉起新进程(跨平台统一路径) */
|
|
134
|
+
export function restartBridgeWithHelper(oldPid) {
|
|
135
|
+
const r = resolveLcbEntry();
|
|
136
|
+
if (!r.ok)
|
|
137
|
+
return r;
|
|
138
|
+
const helper = spawn(process.execPath, ['-e', RESTART_HELPER, String(oldPid), r.entry, LOG_PATH], {
|
|
139
|
+
detached: true,
|
|
140
|
+
stdio: 'ignore',
|
|
141
|
+
windowsHide: true,
|
|
142
|
+
});
|
|
143
|
+
helper.unref();
|
|
144
|
+
return { ok: true };
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* lcb start 启动早期调用:探活并终止 pid 文件登记的旧实例,轮询等待真正退出。
|
|
148
|
+
* 旧实例占着 Web 配置页端口会让本进程配置页静默失联(浏览器仍打到旧进程)。
|
|
149
|
+
* Windows 上 SIGTERM=硬杀仍为异步,端口释放有延迟,故轮询(300ms 节奏同 RESTART_HELPER)。
|
|
150
|
+
* 纯逻辑不打日志(由调用方输出),便于测试
|
|
151
|
+
*/
|
|
152
|
+
export async function stopExistingBridgeAndWait(timeoutMs = 10_000, pidPath = PID_PATH) {
|
|
153
|
+
const st = bridgeStatus(pidPath); // 指向已消亡进程时顺手清理陈旧文件
|
|
154
|
+
if (!st.running || st.pid === null || st.pid === process.pid)
|
|
155
|
+
return { stopped: false, pid: null };
|
|
156
|
+
const pid = st.pid;
|
|
157
|
+
if (!stopBridgeByPid(pid))
|
|
158
|
+
return { stopped: false, pid }; // 无权限等:交给调用方提示
|
|
159
|
+
const t0 = Date.now();
|
|
160
|
+
while (isPidAlive(pid)) {
|
|
161
|
+
if (Date.now() - t0 > timeoutMs)
|
|
162
|
+
return { stopped: false, pid };
|
|
163
|
+
await new Promise((r) => setTimeout(r, 300));
|
|
164
|
+
}
|
|
165
|
+
return { stopped: true, pid };
|
|
166
|
+
}
|
|
167
|
+
//# sourceMappingURL=lifecycle.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lifecycle.js","sourceRoot":"","sources":["../../src/web/lifecycle.ts"],"names":[],"mappings":"AAAA,gDAAgD;AAChD,0EAA0E;AAC1E,mCAAmC;AACnC,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,EAAE,QAAQ,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AACzG,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAC3C,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAE1C,MAAM,QAAQ,GAAG,IAAI,CAAC,UAAU,EAAE,YAAY,CAAC,CAAC;AAChD,MAAM,QAAQ,GAAG,IAAI,CAAC,UAAU,EAAE,YAAY,CAAC,CAAC;AAChD,mCAAmC;AACnC,MAAM,kBAAkB,GAAG,CAAC,GAAG,IAAI,GAAG,IAAI,CAAC;AAE3C,wCAAwC;AACxC,MAAM,UAAU,YAAY,CAAC,OAAO,GAAW,QAAQ;IACrD,aAAa,CAAC,OAAO,EAAE,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC,CAAC;AACtD,CAAC;AAED,0CAA0C;AAC1C,MAAM,UAAU,YAAY,CAAC,OAAO,GAAW,QAAQ;IACrD,IAAI,CAAC;QACH,IAAI,WAAW,CAAC,OAAO,CAAC,KAAK,OAAO,CAAC,GAAG;YAAE,MAAM,CAAC,OAAO,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;IAC7E,CAAC;IAAC,MAAM,CAAC,CAAC,eAAe,CAAC,CAAC;AAC7B,CAAC;AAED,SAAS,WAAW,CAAC,OAAe;IAClC,IAAI,CAAC;QACH,MAAM,CAAC,GAAG,MAAM,CAAC,QAAQ,CAAC,YAAY,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,EAAE,CAAC,CAAC;QACpE,OAAO,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IACjD,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED,kDAAkD;AAClD,SAAS,UAAU,CAAC,GAAW;IAC7B,IAAI,CAAC;QACH,OAAO,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;QACrB,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,OAAQ,CAA2B,CAAC,IAAI,KAAK,OAAO,CAAC;IACvD,CAAC;AACH,CAAC;AAID,+CAA+C;AAC/C,MAAM,UAAU,YAAY,CAAC,OAAO,GAAW,QAAQ;IACrD,MAAM,GAAG,GAAG,WAAW,CAAC,OAAO,CAAC,CAAC;IACjC,IAAI,GAAG,KAAK,IAAI;QAAE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,GAAG,EAAE,IAAI,EAAE,CAAC;IACvD,IAAI,UAAU,CAAC,GAAG,CAAC;QAAE,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,GAAG,EAAE,CAAC;IACnD,IAAI,CAAC;QAAC,MAAM,CAAC,OAAO,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;IAAC,CAAC;IAAC,MAAM,CAAC,CAAC,eAAe,CAAC,CAAC;IACnE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,GAAG,EAAE,IAAI,EAAE,CAAC;AACvC,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,eAAe;IAC7B,qFAAqF;IACrF,mDAAmD;IACnD,MAAM,KAAK,GAAG,aAAa,CAAC,IAAI,GAAG,CAAC,eAAe,EAAE,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;IACvE,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC,EAAE,CAAC;QACvB,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,4DAA4D,EAAE,CAAC;IAC5F,CAAC;IACD,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC;AAC7B,CAAC;AAED,kCAAkC;AAClC,SAAS,SAAS;IAChB,IAAI,CAAC;QACH,IAAI,UAAU,CAAC,QAAQ,CAAC,IAAI,QAAQ,CAAC,QAAQ,CAAC,CAAC,IAAI,GAAG,kBAAkB;YAAE,MAAM,CAAC,QAAQ,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;IAC9G,CAAC;IAAC,MAAM,CAAC,CAAC,mBAAmB,CAAC,CAAC;IAC/B,OAAO,QAAQ,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC;AACjC,CAAC;AAED,2DAA2D;AAC3D,MAAM,UAAU,mBAAmB;IACjC,MAAM,CAAC,GAAG,eAAe,EAAE,CAAC;IAC5B,IAAI,CAAC,CAAC,CAAC,EAAE;QAAE,OAAO,CAAC,CAAC;IACpB,MAAM,EAAE,GAAG,SAAS,EAAE,CAAC;IACvB,MAAM,KAAK,GAAG,KAAK,CAAC,OAAO,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,KAAK,EAAE,OAAO,CAAC,EAAE;QACxD,QAAQ,EAAE,IAAI;QACd,KAAK,EAAE,CAAC,QAAQ,EAAE,EAAE,EAAE,EAAE,CAAC;QACzB,WAAW,EAAE,IAAI;KAClB,CAAC,CAAC;IACH,KAAK,CAAC,KAAK,EAAE,CAAC;IACd,SAAS,CAAC,EAAE,CAAC,CAAC,CAAC,wBAAwB;IACvC,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,GAAG,EAAE,KAAK,CAAC,GAAG,IAAI,CAAC,CAAC,EAAE,CAAC;AAC5C,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,eAAe,CAAC,GAAW;IACzC,IAAI,CAAC;QACH,OAAO,CAAC,IAAI,CAAC,GAAG,EAAE,SAAS,CAAC,CAAC;QAC7B,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC,CAAC,UAAU;IAC1B,CAAC;AACH,CAAC;AAED;;;;GAIG;AACH,MAAM,cAAc,GAAG;;;;;;;;;;;;;;;;;;;CAmBtB,CAAC;AAEF,2DAA2D;AAC3D,MAAM,UAAU,uBAAuB,CAAC,MAAc;IACpD,MAAM,CAAC,GAAG,eAAe,EAAE,CAAC;IAC5B,IAAI,CAAC,CAAC,CAAC,EAAE;QAAE,OAAO,CAAC,CAAC;IACpB,MAAM,MAAM,GAAG,KAAK,CAAC,OAAO,CAAC,QAAQ,EAAE,CAAC,IAAI,EAAE,cAAc,EAAE,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,KAAK,EAAE,QAAQ,CAAC,EAAE;QAChG,QAAQ,EAAE,IAAI;QACd,KAAK,EAAE,QAAQ;QACf,WAAW,EAAE,IAAI;KAClB,CAAC,CAAC;IACH,MAAM,CAAC,KAAK,EAAE,CAAC;IACf,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC;AACtB,CAAC;AAED;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,yBAAyB,CAC7C,SAAS,GAAG,MAAM,EAClB,OAAO,GAAW,QAAQ;IAE1B,MAAM,EAAE,GAAG,YAAY,CAAC,OAAO,CAAC,CAAC,CAAC,mBAAmB;IACrD,IAAI,CAAC,EAAE,CAAC,OAAO,IAAI,EAAE,CAAC,GAAG,KAAK,IAAI,IAAI,EAAE,CAAC,GAAG,KAAK,OAAO,CAAC,GAAG;QAAE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,GAAG,EAAE,IAAI,EAAE,CAAC;IACnG,MAAM,GAAG,GAAG,EAAE,CAAC,GAAG,CAAC;IACnB,IAAI,CAAC,eAAe,CAAC,GAAG,CAAC;QAAE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC,CAAC,eAAe;IAC1E,MAAM,EAAE,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IACtB,OAAO,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;QACvB,IAAI,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,SAAS;YAAE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC;QAChE,MAAM,IAAI,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,UAAU,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC;IAC/C,CAAC;IACD,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,GAAG,EAAE,CAAC;AAChC,CAAC"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
export type ModelCredType = 'auth_token' | 'api_key';
|
|
2
|
+
export interface ModelFetchParams {
|
|
3
|
+
baseUrl: string;
|
|
4
|
+
credType: ModelCredType;
|
|
5
|
+
credential: string;
|
|
6
|
+
}
|
|
7
|
+
export declare const DEFAULT_MODEL_BASE = "https://api.anthropic.com";
|
|
8
|
+
/** body 明文凭证优先;空/缺省回落磁盘 claude 段(带 profile_name 时回落该档案);两处皆无 → error(联合类型便于路由层直接 400) */
|
|
9
|
+
export declare function resolveModelFetchParams(body: {
|
|
10
|
+
base_url?: unknown;
|
|
11
|
+
auth_token?: unknown;
|
|
12
|
+
api_key?: unknown;
|
|
13
|
+
profile_name?: unknown;
|
|
14
|
+
}, disk: {
|
|
15
|
+
baseUrl?: string;
|
|
16
|
+
authToken?: string;
|
|
17
|
+
apiKey?: string;
|
|
18
|
+
profiles?: Array<{
|
|
19
|
+
name: string;
|
|
20
|
+
authToken?: string;
|
|
21
|
+
apiKey?: string;
|
|
22
|
+
baseUrl?: string;
|
|
23
|
+
}>;
|
|
24
|
+
} | undefined): ModelFetchParams | {
|
|
25
|
+
error: string;
|
|
26
|
+
};
|
|
27
|
+
/** GET 模型列表的 URL+请求头:双发认证头(OpenAI 风格认 Bearer、Anthropic 风格认 x-api-key),
|
|
28
|
+
* anthropic-version 对非 Anthropic 网关是未知头、按惯例忽略,无害 */
|
|
29
|
+
export declare function buildModelListRequest(p: ModelFetchParams): {
|
|
30
|
+
url: string;
|
|
31
|
+
headers: Record<string, string>;
|
|
32
|
+
};
|
|
33
|
+
/** 宽容解析多形状模型列表:{data:[{id}]}(OpenAI/Anthropic 通用)/ {data:["id"]} / {models:[...]} / {result:[...]} */
|
|
34
|
+
export declare function parseModelList(payload: unknown): string[];
|
|
35
|
+
/** 实际拉取:全局 fetch(Node≥20 自带),AbortController 超时;非 2xx 抛带状态码+截断 body 的 Error */
|
|
36
|
+
export declare function fetchModelList(p: ModelFetchParams, fetchFn?: typeof fetch, timeoutMs?: number): Promise<string[]>;
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
// 模型列表拉取(Web 配置页「拉取模型」按钮)。厂商无关:模型可能来自 Anthropic 官方、
|
|
2
|
+
// OpenAI 风格中转站或任意兼容网关,故 URL 拼接兼容带/不带 /v1 的 base、认证头双发、
|
|
3
|
+
// 响应形状宽容解析。纯函数与 fetch 分离(fetchFn 可注入,测试免网络)。
|
|
4
|
+
export const DEFAULT_MODEL_BASE = 'https://api.anthropic.com';
|
|
5
|
+
/** body 明文凭证优先;空/缺省回落磁盘 claude 段(带 profile_name 时回落该档案);两处皆无 → error(联合类型便于路由层直接 400) */
|
|
6
|
+
export function resolveModelFetchParams(body, disk) {
|
|
7
|
+
const pick = (v) => (typeof v === 'string' && v.trim() ? v.trim() : undefined);
|
|
8
|
+
// 档案编辑抽屉场景:body.profile_name 指定档案,磁盘回落取该档案(而非顶层)的凭证与 base_url
|
|
9
|
+
const pname = pick(body.profile_name);
|
|
10
|
+
const prof = pname ? disk?.profiles?.find((p) => p.name === pname) : undefined;
|
|
11
|
+
let baseUrl = pick(body.base_url);
|
|
12
|
+
if (!baseUrl)
|
|
13
|
+
baseUrl = (pname ? prof?.baseUrl : undefined) ?? disk?.baseUrl ?? DEFAULT_MODEL_BASE;
|
|
14
|
+
const bToken = pick(body.auth_token);
|
|
15
|
+
const bKey = pick(body.api_key);
|
|
16
|
+
// pname 命中档案时严格用档案凭证(档案选了哪种类型就按哪种,顶层不混入);否则回落顶层
|
|
17
|
+
const dToken = (pname && prof ? prof.authToken : disk?.authToken)?.trim() || undefined;
|
|
18
|
+
const dKey = (pname && prof ? prof.apiKey : disk?.apiKey)?.trim() || undefined;
|
|
19
|
+
// 优先级:body 明文(用户刚输入、尚未保存,按其选择的类型)> 磁盘(档案/顶层,token 型先于 key 型)
|
|
20
|
+
if (bToken)
|
|
21
|
+
return { baseUrl, credType: 'auth_token', credential: bToken };
|
|
22
|
+
if (bKey)
|
|
23
|
+
return { baseUrl, credType: 'api_key', credential: bKey };
|
|
24
|
+
if (dToken)
|
|
25
|
+
return { baseUrl, credType: 'auth_token', credential: dToken };
|
|
26
|
+
if (dKey)
|
|
27
|
+
return { baseUrl, credType: 'api_key', credential: dKey };
|
|
28
|
+
return {
|
|
29
|
+
error: '未找到可用凭证:请在表单填入 Auth Token / API Key 后重试(无须先保存),或保存 managed 模式凭证。'
|
|
30
|
+
+ 'inherit 模式使用本机 ~/.claude 登录,页面无法代取其凭证。',
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
/** GET 模型列表的 URL+请求头:双发认证头(OpenAI 风格认 Bearer、Anthropic 风格认 x-api-key),
|
|
34
|
+
* anthropic-version 对非 Anthropic 网关是未知头、按惯例忽略,无害 */
|
|
35
|
+
export function buildModelListRequest(p) {
|
|
36
|
+
const base = p.baseUrl.replace(/\/+$/, ''); // 剥尾部斜杠
|
|
37
|
+
const url = /\/v\d+$/.test(base) ? `${base}/models` : `${base}/v1/models`;
|
|
38
|
+
return {
|
|
39
|
+
url,
|
|
40
|
+
headers: {
|
|
41
|
+
Accept: 'application/json',
|
|
42
|
+
'anthropic-version': '2023-06-01',
|
|
43
|
+
Authorization: `Bearer ${p.credential}`,
|
|
44
|
+
'x-api-key': p.credential,
|
|
45
|
+
},
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
/** 宽容解析多形状模型列表:{data:[{id}]}(OpenAI/Anthropic 通用)/ {data:["id"]} / {models:[...]} / {result:[...]} */
|
|
49
|
+
export function parseModelList(payload) {
|
|
50
|
+
if (!payload || typeof payload !== 'object' || Array.isArray(payload)) {
|
|
51
|
+
throw new Error('无法识别的响应格式(期望 JSON 对象含 data/models 数组)');
|
|
52
|
+
}
|
|
53
|
+
const candidates = payload.data
|
|
54
|
+
?? payload.models
|
|
55
|
+
?? payload.result;
|
|
56
|
+
if (!Array.isArray(candidates)) {
|
|
57
|
+
throw new Error('无法识别的响应格式(期望 data/models 数组)');
|
|
58
|
+
}
|
|
59
|
+
const ids = new Set();
|
|
60
|
+
for (const item of candidates) {
|
|
61
|
+
const id = typeof item === 'string' ? item
|
|
62
|
+
: (item && typeof item === 'object' && typeof item.id === 'string')
|
|
63
|
+
? item.id
|
|
64
|
+
: undefined;
|
|
65
|
+
if (id?.trim())
|
|
66
|
+
ids.add(id.trim());
|
|
67
|
+
}
|
|
68
|
+
return [...ids].sort();
|
|
69
|
+
}
|
|
70
|
+
/** 实际拉取:全局 fetch(Node≥20 自带),AbortController 超时;非 2xx 抛带状态码+截断 body 的 Error */
|
|
71
|
+
export async function fetchModelList(p, fetchFn = fetch, timeoutMs = 15000) {
|
|
72
|
+
const { url, headers } = buildModelListRequest(p);
|
|
73
|
+
const controller = new AbortController();
|
|
74
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
75
|
+
let res;
|
|
76
|
+
try {
|
|
77
|
+
res = await fetchFn(url, { method: 'GET', headers, signal: controller.signal });
|
|
78
|
+
}
|
|
79
|
+
catch (e) {
|
|
80
|
+
throw new Error(`模型列表请求失败:${e instanceof Error ? e.message : String(e)}(请检查 BASE_URL 可达性;也可直接手动填写模型名)`);
|
|
81
|
+
}
|
|
82
|
+
finally {
|
|
83
|
+
clearTimeout(timer);
|
|
84
|
+
}
|
|
85
|
+
const text = await res.text().catch(() => '');
|
|
86
|
+
if (!res.ok) {
|
|
87
|
+
throw new Error(`模型列表拉取失败:HTTP ${res.status} ${text.slice(0, 200)}(也可直接手动填写模型名)`);
|
|
88
|
+
}
|
|
89
|
+
let payload;
|
|
90
|
+
try {
|
|
91
|
+
payload = JSON.parse(text);
|
|
92
|
+
}
|
|
93
|
+
catch {
|
|
94
|
+
throw new Error(`响应不是合法 JSON(前 200 字符:${text.slice(0, 200) || '(空)'});也可直接手动填写模型名`);
|
|
95
|
+
}
|
|
96
|
+
const models = parseModelList(payload);
|
|
97
|
+
if (!models.length)
|
|
98
|
+
throw new Error('响应中没有模型条目;也可直接手动填写模型名');
|
|
99
|
+
return models;
|
|
100
|
+
}
|
|
101
|
+
//# sourceMappingURL=model-list.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"model-list.js","sourceRoot":"","sources":["../../src/web/model-list.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { ServerConfig } from '../types.js';
|
|
2
|
+
export interface WebServerOptions {
|
|
3
|
+
/** server 段配置;缺省用默认(firstRun 无配置文件场景) */
|
|
4
|
+
server?: ServerConfig;
|
|
5
|
+
configPath?: string;
|
|
6
|
+
/** true = lcb start 内嵌(页面顶栏标识 + status.apps 显示启动态) */
|
|
7
|
+
embedded?: boolean;
|
|
8
|
+
/** 启动后自动开浏览器 */
|
|
9
|
+
autoOpen?: boolean;
|
|
10
|
+
appsStarted?: Array<{
|
|
11
|
+
name: string;
|
|
12
|
+
started: boolean;
|
|
13
|
+
}>;
|
|
14
|
+
/** embedded 模式下页面停止/重启自身进程的优雅关闭回调(lcb start 注入 shutdown) */
|
|
15
|
+
selfStop?: () => void;
|
|
16
|
+
}
|
|
17
|
+
export interface WebServerHandle {
|
|
18
|
+
url: string;
|
|
19
|
+
port: number;
|
|
20
|
+
close(): void;
|
|
21
|
+
}
|
|
22
|
+
export declare function startWebServer(opts?: WebServerOptions): Promise<WebServerHandle | null>;
|