@jesonliu/lark-claudecode-bridge 0.11.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 +81 -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 +32 -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/index.js +80 -17
- 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 +58 -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/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
package/dist/types.d.ts
CHANGED
|
@@ -44,6 +44,58 @@ export interface PermissionsConfig {
|
|
|
44
44
|
allowTools?: string[];
|
|
45
45
|
dangerousCommands?: RegExp[];
|
|
46
46
|
}
|
|
47
|
+
/** Web 配置页服务器(lcb start / lcb ui 内嵌,node:http 零依赖):enabled/host/port 均为启动时读取,改动需重启 */
|
|
48
|
+
export interface ServerConfig {
|
|
49
|
+
enabled?: boolean;
|
|
50
|
+
host?: string;
|
|
51
|
+
port?: number;
|
|
52
|
+
}
|
|
53
|
+
export type ClaudeAuthMode = 'inherit' | 'managed';
|
|
54
|
+
/**
|
|
55
|
+
* 多厂商认证档案(Web 配置页「Claude 认证」管理,0.13 起)。档案仅是配置库:
|
|
56
|
+
* 当前生效配置始终是 ClaudeConfig 顶层四字段(写入 settings.json 的就是它),
|
|
57
|
+
* 「设为当前」= 把档案字段拷贝到顶层(互斥凭证同步清理),与 cc-switch 同款切换语义。
|
|
58
|
+
*/
|
|
59
|
+
export interface ClaudeProfile {
|
|
60
|
+
/** 档案名(显示用,如「官方」「中转站A」「GLM」) */
|
|
61
|
+
name: string;
|
|
62
|
+
/** 与 apiKey 二选一 */
|
|
63
|
+
authToken?: string;
|
|
64
|
+
/** 与 authToken 二选一 */
|
|
65
|
+
apiKey?: string;
|
|
66
|
+
baseUrl?: string;
|
|
67
|
+
model?: string;
|
|
68
|
+
/** 候选模型集(fable/opus/sonnet/haiku 等各存一条,页面点选即切换当前模型);model 仍为「设为当前」时的默认模型 */
|
|
69
|
+
models?: string[];
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Claude 认证与模型配置。inherit = 共享本机 ~/.claude(0.4 起默认行为,登录态/settings 全继承);
|
|
73
|
+
* managed = bridge 自管目录(~/.lark-claudecode-bridge/claude/),authToken/apiKey/baseUrl/model
|
|
74
|
+
* 写入该目录 settings.json 的 env 块——完全摆脱对本机 claude login 的依赖
|
|
75
|
+
*/
|
|
76
|
+
export interface ClaudeConfig {
|
|
77
|
+
mode?: ClaudeAuthMode;
|
|
78
|
+
/** ANTHROPIC_AUTH_TOKEN(Bearer,中转站常用);与 apiKey 二选一 */
|
|
79
|
+
authToken?: string;
|
|
80
|
+
/** ANTHROPIC_API_KEY(x-api-key);与 authToken 二选一 */
|
|
81
|
+
apiKey?: string;
|
|
82
|
+
/** ANTHROPIC_BASE_URL(第三方中转端点;官方 API 留空) */
|
|
83
|
+
baseUrl?: string;
|
|
84
|
+
/** 写 settings.json 顶层 model + env.ANTHROPIC_MODEL */
|
|
85
|
+
model?: string;
|
|
86
|
+
/** 多厂商档案库(不直接生效;Web 页「设为当前」拷贝到顶层) */
|
|
87
|
+
profiles?: ClaudeProfile[];
|
|
88
|
+
}
|
|
89
|
+
/** 飞书斜杠命令注册定义(同步到开放平台 app_slash_commands;command 不含 / 前缀) */
|
|
90
|
+
export interface SlashCommandDef {
|
|
91
|
+
command: string;
|
|
92
|
+
description: string;
|
|
93
|
+
icon?: string;
|
|
94
|
+
}
|
|
95
|
+
/** 斜杠命令同步配置:extra 为用户自定义透传命令(内置命令集见 commands.SLASH_COMMAND_META,始终参与同步) */
|
|
96
|
+
export interface SlashCommandsConfig {
|
|
97
|
+
extra?: SlashCommandDef[];
|
|
98
|
+
}
|
|
47
99
|
export interface BridgeConfig {
|
|
48
100
|
/** 多飞书应用(多机器人):每 app 一条独立 WS 长连接 + 独立会话池(sessions.<appId>.json)。
|
|
49
101
|
* 全部 app 共享本机 ~/.claude:模型设置/登录态/user MCP/skills/插件统一继承,仅会话池隔离 */
|
|
@@ -59,6 +111,12 @@ export interface BridgeConfig {
|
|
|
59
111
|
};
|
|
60
112
|
/** 权限白名单;缺省用 permission-gate 的内置默认(读工具 + Bash + 危险命令表) */
|
|
61
113
|
permissions?: PermissionsConfig;
|
|
114
|
+
/** Web 配置页服务器;缺省 enabled=true / 127.0.0.1:17317(随 lcb start 常驻) */
|
|
115
|
+
server?: ServerConfig;
|
|
116
|
+
/** Claude 认证模式;缺省 inherit(共享 ~/.claude) */
|
|
117
|
+
claude?: ClaudeConfig;
|
|
118
|
+
/** 飞书斜杠命令同步;缺省仅内置命令集 */
|
|
119
|
+
slashCommands?: SlashCommandsConfig;
|
|
62
120
|
}
|
|
63
121
|
export interface IncomingMessage {
|
|
64
122
|
chatId: string;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function openBrowser(url: string): void;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// 跨平台开浏览器(best-effort:失败静默,用户可手动复制 URL)
|
|
2
|
+
import { spawn } from 'node:child_process';
|
|
3
|
+
export function openBrowser(url) {
|
|
4
|
+
try {
|
|
5
|
+
if (process.platform === 'win32') {
|
|
6
|
+
// cmd /c start:Windows 默认浏览器;空标题占位防 URL 被当成标题
|
|
7
|
+
spawn('cmd', ['/c', 'start', '', url], { detached: true, stdio: 'ignore', windowsHide: true }).unref();
|
|
8
|
+
}
|
|
9
|
+
else if (process.platform === 'darwin') {
|
|
10
|
+
spawn('open', [url], { detached: true, stdio: 'ignore' }).unref();
|
|
11
|
+
}
|
|
12
|
+
else {
|
|
13
|
+
spawn('xdg-open', [url], { detached: true, stdio: 'ignore' }).unref();
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
// 开不了浏览器不影响功能:URL 已打印在终端
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
//# sourceMappingURL=open-browser.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"open-browser.js","sourceRoot":"","sources":["../../src/util/open-browser.ts"],"names":[],"mappings":"AAAA,wCAAwC;AACxC,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAE3C,MAAM,UAAU,WAAW,CAAC,GAAW;IACrC,IAAI,CAAC;QACH,IAAI,OAAO,CAAC,QAAQ,KAAK,OAAO,EAAE,CAAC;YACjC,8CAA8C;YAC9C,KAAK,CAAC,KAAK,EAAE,CAAC,IAAI,EAAE,OAAO,EAAE,EAAE,EAAE,GAAG,CAAC,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,KAAK,EAAE,QAAQ,EAAE,WAAW,EAAE,IAAI,EAAE,CAAC,CAAC,KAAK,EAAE,CAAC;QACzG,CAAC;aAAM,IAAI,OAAO,CAAC,QAAQ,KAAK,QAAQ,EAAE,CAAC;YACzC,KAAK,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC,CAAC,KAAK,EAAE,CAAC;QACpE,CAAC;aAAM,CAAC;YACN,KAAK,CAAC,UAAU,EAAE,CAAC,GAAG,CAAC,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC,CAAC,KAAK,EAAE,CAAC;QACxE,CAAC;IACH,CAAC;IAAC,MAAM,CAAC;QACP,yBAAyB;IAC3B,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/** 配置页 URL(host/port 回退默认值);server.enabled=false 时返回 null(无页面可指) */
|
|
2
|
+
export function serverUrl(config) {
|
|
3
|
+
const s = config.server;
|
|
4
|
+
if (s?.enabled === false)
|
|
5
|
+
return null;
|
|
6
|
+
const host = s?.host ?? '127.0.0.1';
|
|
7
|
+
const port = s?.port ?? 17317;
|
|
8
|
+
return `http://${host}:${port}`;
|
|
9
|
+
}
|
|
10
|
+
//# sourceMappingURL=server-url.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server-url.js","sourceRoot":"","sources":["../../src/util/server-url.ts"],"names":[],"mappings":"AAGA,oEAAoE;AACpE,MAAM,UAAU,SAAS,CAAC,MAAoB;IAC5C,MAAM,CAAC,GAAG,MAAM,CAAC,MAAM,CAAC;IACxB,IAAI,CAAC,EAAE,OAAO,KAAK,KAAK;QAAE,OAAO,IAAI,CAAC;IACtC,MAAM,IAAI,GAAG,CAAC,EAAE,IAAI,IAAI,WAAW,CAAC;IACpC,MAAM,IAAI,GAAG,CAAC,EAAE,IAAI,IAAI,KAAK,CAAC;IAC9B,OAAO,UAAU,IAAI,IAAI,IAAI,EAAE,CAAC;AAClC,CAAC"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type { BridgeConfig, FeishuAppConfig } from '../types.js';
|
|
2
|
+
/** secret 字段的脱敏回显形状(真实值永不离开本机进程) */
|
|
3
|
+
export interface SecretHint {
|
|
4
|
+
secretSet: boolean;
|
|
5
|
+
secretHint?: string;
|
|
6
|
+
}
|
|
7
|
+
export declare function maskSecret(value: string | undefined): SecretHint;
|
|
8
|
+
/** 客户端提交的 secret 字段是否应视为「未修改」(空串/缺省/仍是脱敏对象都算) */
|
|
9
|
+
export declare function isUnchangedSecret(v: unknown): boolean;
|
|
10
|
+
/**
|
|
11
|
+
* secret 回填:客户端 doc 中的 secret 字段(apps[].app_secret、claude.auth_token/api_key)
|
|
12
|
+
* 未修改时回填磁盘现值。非 secret 字段不动;其余 secret 位置(未来新增)按同模式扩展。
|
|
13
|
+
*/
|
|
14
|
+
export declare function applySecrets(newDoc: Record<string, unknown>, oldDoc: Record<string, unknown>): Record<string, unknown>;
|
|
15
|
+
/** GET /api/config 响应构建:深拷贝 doc 并把 secret 字段替换为脱敏提示 */
|
|
16
|
+
export declare function docForClient(doc: Record<string, unknown>): Record<string, unknown>;
|
|
17
|
+
/**
|
|
18
|
+
* Host header 校验(防 DNS rebinding):host 部分(去端口)必须是回环名或配置的 host。
|
|
19
|
+
* 端口不校验(localhost:port 与 127.0.0.1:port 等价放行,用户从哪个名字进来无从假设)。
|
|
20
|
+
*/
|
|
21
|
+
export declare function isAllowedHost(hostHeader: string | undefined, cfgHost: string): boolean;
|
|
22
|
+
/** Origin header 校验(存在才查;同源 fetch 一定带且 host 合法,跨站请求携带外域 Origin) */
|
|
23
|
+
export declare function isAllowedOrigin(originHeader: string | undefined, cfgHost: string, cfgPort: number): boolean;
|
|
24
|
+
/** PUT 前后比较,列出需重启的段落(其余段落经热重载器下一条消息生效) */
|
|
25
|
+
export declare function computeRestartRequired(before: BridgeConfig, after: BridgeConfig): string[];
|
|
26
|
+
/** apps 启动态摘要(GET /api/status 用;lcb ui 独立进程无法得知,标 unknown) */
|
|
27
|
+
export declare function appStatusSummary(apps: FeishuAppConfig[], started?: Array<{
|
|
28
|
+
name: string;
|
|
29
|
+
started: boolean;
|
|
30
|
+
}>): Array<{
|
|
31
|
+
name: string;
|
|
32
|
+
appId: string;
|
|
33
|
+
started: boolean | 'unknown';
|
|
34
|
+
}>;
|
|
35
|
+
/**
|
|
36
|
+
* GET /api/config 显示层权限默认回填:permissions 键缺失(undefined/null,多见于老版本升级配置)时
|
|
37
|
+
* 回显运行时默认,让用户「打开页面就能看到/编辑默认值」;键存在但为空数组 = 用户有意清空,原样返回。
|
|
38
|
+
* PUT 整段替换语义下「回显 → 保存 → 固化写盘」为预期行为(老配置保存会把该段固化为默认并重写,
|
|
39
|
+
* 丢段内手写注释;bootstrap 出身的配置磁盘已有相同默认,走「值未变跳过重写」不受影响)。
|
|
40
|
+
*/
|
|
41
|
+
export declare function applyPermissionDisplayDefaults(doc: Record<string, unknown>): Record<string, unknown>;
|
|
42
|
+
/** 当前生效 Claude 配置摘要(GET /api/status 展示用):settings.json 内容 → 凭证脱敏。inherit/managed 均适用 */
|
|
43
|
+
export interface ClaudeCurrentSummary {
|
|
44
|
+
baseUrl?: string;
|
|
45
|
+
model?: string;
|
|
46
|
+
authToken: SecretHint;
|
|
47
|
+
apiKey: SecretHint;
|
|
48
|
+
}
|
|
49
|
+
/** 纯函数:解析 settings.json(顶层 model + env 块 ANTHROPIC_*),凭证过 maskSecret,明文永不离开本机 */
|
|
50
|
+
export declare function claudeSettingsSummary(settings: unknown): ClaudeCurrentSummary;
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import { sameApps } from '../config.js';
|
|
2
|
+
import { DEFAULT_ALLOW_TOOLS_LIST, DEFAULT_DANGEROUS_COMMAND_SOURCES } from '../executor/permission-gate.js';
|
|
3
|
+
export function maskSecret(value) {
|
|
4
|
+
if (!value)
|
|
5
|
+
return { secretSet: false };
|
|
6
|
+
return { secretSet: true, secretHint: `••••${value.slice(-4)}` };
|
|
7
|
+
}
|
|
8
|
+
/** 客户端提交的 secret 字段是否应视为「未修改」(空串/缺省/仍是脱敏对象都算) */
|
|
9
|
+
export function isUnchangedSecret(v) {
|
|
10
|
+
if (v === undefined || v === null)
|
|
11
|
+
return true;
|
|
12
|
+
if (typeof v === 'object')
|
|
13
|
+
return true; // 仍是 GET 回显的 {secretSet...} 对象
|
|
14
|
+
if (typeof v === 'string')
|
|
15
|
+
return v.trim() === '';
|
|
16
|
+
return true;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* secret 回填:客户端 doc 中的 secret 字段(apps[].app_secret、claude.auth_token/api_key)
|
|
20
|
+
* 未修改时回填磁盘现值。非 secret 字段不动;其余 secret 位置(未来新增)按同模式扩展。
|
|
21
|
+
*/
|
|
22
|
+
export function applySecrets(newDoc, oldDoc) {
|
|
23
|
+
const doc = { ...newDoc };
|
|
24
|
+
const newApps = Array.isArray(doc.apps) ? doc.apps : [];
|
|
25
|
+
const oldApps = Array.isArray(oldDoc.apps) ? oldDoc.apps : [];
|
|
26
|
+
// 按 app_id 对齐回填(而非索引):页面可能增删/重排应用,索引对齐会把别的应用 secret 错配
|
|
27
|
+
const oldSecretById = new Map(oldApps.map((a) => [String(a?.app_id ?? ''), a?.app_secret]));
|
|
28
|
+
doc.apps = newApps.map((app) => (isUnchangedSecret(app?.app_secret)
|
|
29
|
+
? { ...app, app_secret: oldSecretById.get(String(app?.app_id ?? '')) } // 新应用查不到 → undefined,loadConfig 拦截
|
|
30
|
+
: app));
|
|
31
|
+
if (doc.claude && typeof doc.claude === 'object' && !Array.isArray(doc.claude)) {
|
|
32
|
+
const newClaude = { ...doc.claude };
|
|
33
|
+
const oldClaude = oldDoc.claude && typeof oldDoc.claude === 'object' && !Array.isArray(oldDoc.claude)
|
|
34
|
+
? oldDoc.claude
|
|
35
|
+
: {};
|
|
36
|
+
for (const key of ['auth_token', 'api_key']) {
|
|
37
|
+
if (isUnchangedSecret(newClaude[key]))
|
|
38
|
+
newClaude[key] = oldClaude[key];
|
|
39
|
+
}
|
|
40
|
+
// 多厂商档案(0.13):按 name 对齐回填未修改的凭证(同 apps 按 app_id 对齐的考虑)
|
|
41
|
+
if (Array.isArray(newClaude.profiles)) {
|
|
42
|
+
const oldByName = new Map((Array.isArray(oldClaude.profiles) ? oldClaude.profiles : [])
|
|
43
|
+
.map((p) => [String(p?.name ?? ''), p]));
|
|
44
|
+
newClaude.profiles = newClaude.profiles.map((p) => {
|
|
45
|
+
const old = oldByName.get(String(p?.name ?? ''));
|
|
46
|
+
if (!old)
|
|
47
|
+
return p; // 新档案:提交值即最终值(无凭证无内容会被 normalizeClaude 剔除)
|
|
48
|
+
const merged = { ...p };
|
|
49
|
+
for (const key of ['auth_token', 'api_key']) {
|
|
50
|
+
if (isUnchangedSecret(merged[key]))
|
|
51
|
+
merged[key] = old[key];
|
|
52
|
+
}
|
|
53
|
+
return merged;
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
doc.claude = newClaude;
|
|
57
|
+
}
|
|
58
|
+
return doc;
|
|
59
|
+
}
|
|
60
|
+
/** GET /api/config 响应构建:深拷贝 doc 并把 secret 字段替换为脱敏提示 */
|
|
61
|
+
export function docForClient(doc) {
|
|
62
|
+
const out = JSON.parse(JSON.stringify(doc ?? {}));
|
|
63
|
+
if (Array.isArray(out.apps)) {
|
|
64
|
+
out.apps = out.apps.map((app) => ({
|
|
65
|
+
...app,
|
|
66
|
+
app_secret: maskSecret(typeof app?.app_secret === 'string' ? app.app_secret : undefined),
|
|
67
|
+
}));
|
|
68
|
+
}
|
|
69
|
+
if (out.claude && typeof out.claude === 'object') {
|
|
70
|
+
const c = { ...out.claude };
|
|
71
|
+
for (const key of ['auth_token', 'api_key']) {
|
|
72
|
+
c[key] = maskSecret(typeof c[key] === 'string' ? c[key] : undefined);
|
|
73
|
+
}
|
|
74
|
+
if (Array.isArray(c.profiles)) {
|
|
75
|
+
c.profiles = c.profiles.map((p) => ({
|
|
76
|
+
...p,
|
|
77
|
+
auth_token: maskSecret(typeof p?.auth_token === 'string' ? p.auth_token : undefined),
|
|
78
|
+
api_key: maskSecret(typeof p?.api_key === 'string' ? p.api_key : undefined),
|
|
79
|
+
}));
|
|
80
|
+
}
|
|
81
|
+
out.claude = c;
|
|
82
|
+
}
|
|
83
|
+
return out;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Host header 校验(防 DNS rebinding):host 部分(去端口)必须是回环名或配置的 host。
|
|
87
|
+
* 端口不校验(localhost:port 与 127.0.0.1:port 等价放行,用户从哪个名字进来无从假设)。
|
|
88
|
+
*/
|
|
89
|
+
export function isAllowedHost(hostHeader, cfgHost) {
|
|
90
|
+
if (!hostHeader)
|
|
91
|
+
return false;
|
|
92
|
+
let hostPart = hostHeader;
|
|
93
|
+
const bracket = hostPart.match(/^\[([^\]]+)\]/); // 规范 IPv6 host:[::1]:8080
|
|
94
|
+
if (bracket) {
|
|
95
|
+
hostPart = bracket[1];
|
|
96
|
+
}
|
|
97
|
+
else {
|
|
98
|
+
// 仅单一冒号视为 host:port(裸 IPv6 多冒号整体保留;多冒号场景只可能命中 '::1' 白名单)
|
|
99
|
+
const last = hostPart.lastIndexOf(':');
|
|
100
|
+
if (last !== -1 && hostPart.indexOf(':') === last)
|
|
101
|
+
hostPart = hostPart.slice(0, last);
|
|
102
|
+
}
|
|
103
|
+
return hostPart === '127.0.0.1' || hostPart === 'localhost' || hostPart === '::1' || hostPart === cfgHost;
|
|
104
|
+
}
|
|
105
|
+
/** Origin header 校验(存在才查;同源 fetch 一定带且 host 合法,跨站请求携带外域 Origin) */
|
|
106
|
+
export function isAllowedOrigin(originHeader, cfgHost, cfgPort) {
|
|
107
|
+
if (!originHeader)
|
|
108
|
+
return true; // 非 CORS 场景(curl / 同源部分实现)无 Origin,放行给 Host 校验兜底
|
|
109
|
+
try {
|
|
110
|
+
const u = new URL(originHeader);
|
|
111
|
+
return u.origin === `http://${cfgHost}:${cfgPort}`
|
|
112
|
+
|| u.origin === `http://127.0.0.1:${cfgPort}`
|
|
113
|
+
|| u.origin === `http://localhost:${cfgPort}`;
|
|
114
|
+
}
|
|
115
|
+
catch {
|
|
116
|
+
return false;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
/** PUT 前后比较,列出需重启的段落(其余段落经热重载器下一条消息生效) */
|
|
120
|
+
export function computeRestartRequired(before, after) {
|
|
121
|
+
const sections = [];
|
|
122
|
+
if (!sameApps(before.apps, after.apps))
|
|
123
|
+
sections.push('apps');
|
|
124
|
+
if (before.concurrency !== after.concurrency)
|
|
125
|
+
sections.push('concurrency');
|
|
126
|
+
if (JSON.stringify(before.claude ?? {}) !== JSON.stringify(after.claude ?? {}))
|
|
127
|
+
sections.push('claude');
|
|
128
|
+
if (JSON.stringify(before.server ?? {}) !== JSON.stringify(after.server ?? {}))
|
|
129
|
+
sections.push('server');
|
|
130
|
+
return sections;
|
|
131
|
+
}
|
|
132
|
+
/** apps 启动态摘要(GET /api/status 用;lcb ui 独立进程无法得知,标 unknown) */
|
|
133
|
+
export function appStatusSummary(apps, started) {
|
|
134
|
+
return apps.map((a) => ({
|
|
135
|
+
name: a.name,
|
|
136
|
+
appId: a.appId,
|
|
137
|
+
started: started?.find((s) => s.name === a.name)?.started ?? 'unknown',
|
|
138
|
+
}));
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* GET /api/config 显示层权限默认回填:permissions 键缺失(undefined/null,多见于老版本升级配置)时
|
|
142
|
+
* 回显运行时默认,让用户「打开页面就能看到/编辑默认值」;键存在但为空数组 = 用户有意清空,原样返回。
|
|
143
|
+
* PUT 整段替换语义下「回显 → 保存 → 固化写盘」为预期行为(老配置保存会把该段固化为默认并重写,
|
|
144
|
+
* 丢段内手写注释;bootstrap 出身的配置磁盘已有相同默认,走「值未变跳过重写」不受影响)。
|
|
145
|
+
*/
|
|
146
|
+
export function applyPermissionDisplayDefaults(doc) {
|
|
147
|
+
const p = doc.permissions;
|
|
148
|
+
if (p === undefined || p === null) {
|
|
149
|
+
return { ...doc, permissions: { allow_tools: [...DEFAULT_ALLOW_TOOLS_LIST], dangerous_commands: [...DEFAULT_DANGEROUS_COMMAND_SOURCES] } };
|
|
150
|
+
}
|
|
151
|
+
if (typeof p !== 'object' || Array.isArray(p))
|
|
152
|
+
return doc; // 脏形状:loadConfig/parseConfigText 会拦,GET 原样透出
|
|
153
|
+
const np = { ...p };
|
|
154
|
+
let changed = false;
|
|
155
|
+
if (np.allow_tools === undefined || np.allow_tools === null) {
|
|
156
|
+
np.allow_tools = [...DEFAULT_ALLOW_TOOLS_LIST];
|
|
157
|
+
changed = true;
|
|
158
|
+
}
|
|
159
|
+
if (np.dangerous_commands === undefined || np.dangerous_commands === null) {
|
|
160
|
+
np.dangerous_commands = [...DEFAULT_DANGEROUS_COMMAND_SOURCES];
|
|
161
|
+
changed = true;
|
|
162
|
+
}
|
|
163
|
+
return changed ? { ...doc, permissions: np } : doc;
|
|
164
|
+
}
|
|
165
|
+
/** 纯函数:解析 settings.json(顶层 model + env 块 ANTHROPIC_*),凭证过 maskSecret,明文永不离开本机 */
|
|
166
|
+
export function claudeSettingsSummary(settings) {
|
|
167
|
+
if (!settings || typeof settings !== 'object' || Array.isArray(settings)) {
|
|
168
|
+
return { authToken: maskSecret(undefined), apiKey: maskSecret(undefined) };
|
|
169
|
+
}
|
|
170
|
+
const doc = settings;
|
|
171
|
+
const env = doc.env && typeof doc.env === 'object' && !Array.isArray(doc.env)
|
|
172
|
+
? doc.env
|
|
173
|
+
: {};
|
|
174
|
+
const str = (v) => (typeof v === 'string' && v.trim() ? v : undefined);
|
|
175
|
+
const baseUrl = str(env.ANTHROPIC_BASE_URL);
|
|
176
|
+
const model = str(env.ANTHROPIC_MODEL) ?? str(doc.model);
|
|
177
|
+
return {
|
|
178
|
+
...(baseUrl ? { baseUrl } : {}),
|
|
179
|
+
...(model ? { model } : {}),
|
|
180
|
+
authToken: maskSecret(str(env.ANTHROPIC_AUTH_TOKEN)),
|
|
181
|
+
apiKey: maskSecret(str(env.ANTHROPIC_API_KEY)),
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
//# sourceMappingURL=config-api.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config-api.js","sourceRoot":"","sources":["../../src/web/config-api.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/** 桥接器进程启动成功后自写 PID 文件(页面据此探活/跨进程停止) */
|
|
2
|
+
export declare function writePidFile(pidPath?: string): void;
|
|
3
|
+
/** 优雅关闭时自清 PID 文件;文件已被其它进程覆写(重启竞态)时不误删 */
|
|
4
|
+
export declare function clearPidFile(pidPath?: string): void;
|
|
5
|
+
export interface BridgeStatus {
|
|
6
|
+
running: boolean;
|
|
7
|
+
pid: number | null;
|
|
8
|
+
}
|
|
9
|
+
/** 探活桥接器;PID 文件指向已消亡进程(崩溃/被硬杀未及清理)时顺手清掉陈旧文件 */
|
|
10
|
+
export declare function bridgeStatus(pidPath?: string): BridgeStatus;
|
|
11
|
+
/**
|
|
12
|
+
* 解析 lcb CLI 入口(发布包 dist/bin/lcb.js;模块位置相对定位,tsx 源码模式下该路径不存在)。
|
|
13
|
+
* 源码运行时返回 error——页面按钮禁用并提示手动启停
|
|
14
|
+
*/
|
|
15
|
+
export declare function resolveLcbEntry(): {
|
|
16
|
+
ok: true;
|
|
17
|
+
entry: string;
|
|
18
|
+
} | {
|
|
19
|
+
ok: false;
|
|
20
|
+
error: string;
|
|
21
|
+
};
|
|
22
|
+
/** 后台拉起桥接器:detached 守护进程,stdio 落 bridge.log,父进程退出不影响其存活 */
|
|
23
|
+
export declare function spawnBridgeDetached(): {
|
|
24
|
+
ok: true;
|
|
25
|
+
pid: number;
|
|
26
|
+
} | {
|
|
27
|
+
ok: false;
|
|
28
|
+
error: string;
|
|
29
|
+
};
|
|
30
|
+
/**
|
|
31
|
+
* 停止指定 PID 的桥接器。Windows 上 SIGTERM 等效 TerminateProcess(不走进程内优雅关闭
|
|
32
|
+
* handler);会话/配对状态均逐消息落盘,硬终止风险可控。同进程内(embedded)停止走
|
|
33
|
+
* server.ts 注入的 selfStop 优雅路径,不经本函数
|
|
34
|
+
*/
|
|
35
|
+
export declare function stopBridgeByPid(pid: number): boolean;
|
|
36
|
+
/** 重启桥接器:spawn detached helper 由其等待旧进程退出后拉起新进程(跨平台统一路径) */
|
|
37
|
+
export declare function restartBridgeWithHelper(oldPid: number): {
|
|
38
|
+
ok: true;
|
|
39
|
+
} | {
|
|
40
|
+
ok: false;
|
|
41
|
+
error: string;
|
|
42
|
+
};
|
|
43
|
+
/**
|
|
44
|
+
* lcb start 启动早期调用:探活并终止 pid 文件登记的旧实例,轮询等待真正退出。
|
|
45
|
+
* 旧实例占着 Web 配置页端口会让本进程配置页静默失联(浏览器仍打到旧进程)。
|
|
46
|
+
* Windows 上 SIGTERM=硬杀仍为异步,端口释放有延迟,故轮询(300ms 节奏同 RESTART_HELPER)。
|
|
47
|
+
* 纯逻辑不打日志(由调用方输出),便于测试
|
|
48
|
+
*/
|
|
49
|
+
export declare function stopExistingBridgeAndWait(timeoutMs?: number, pidPath?: string): Promise<{
|
|
50
|
+
stopped: boolean;
|
|
51
|
+
pid: number | null;
|
|
52
|
+
}>;
|
|
@@ -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[]>;
|