vite-plugin-aipanel 1.2.0-beta.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 +72 -0
- package/es/client/vue-devtools-bridge.d.ts +58 -0
- package/es/client/vue-devtools-bridge.mjs +150 -0
- package/es/core/injector.d.ts +2 -0
- package/es/core/injector.mjs +15 -0
- package/es/core/mcp-chrome.d.ts +56 -0
- package/es/core/mcp-chrome.mjs +133 -0
- package/es/core/mcp-proxy.d.ts +23 -0
- package/es/core/mcp-proxy.mjs +212 -0
- package/es/core/mcp-tools.d.ts +15 -0
- package/es/core/mcp-tools.mjs +671 -0
- package/es/core/provider-loader.d.ts +19 -0
- package/es/core/provider-loader.mjs +30 -0
- package/es/core/proxy-server.d.ts +12 -0
- package/es/core/proxy-server.mjs +148 -0
- package/es/core/service.d.ts +39 -0
- package/es/core/service.mjs +267 -0
- package/es/endpoints/context.d.ts +3 -0
- package/es/endpoints/context.mjs +106 -0
- package/es/endpoints/index.d.ts +9 -0
- package/es/endpoints/index.mjs +30 -0
- package/es/endpoints/logs.d.ts +9 -0
- package/es/endpoints/logs.mjs +86 -0
- package/es/endpoints/mcp.d.ts +6 -0
- package/es/endpoints/mcp.mjs +610 -0
- package/es/endpoints/sessions.d.ts +3 -0
- package/es/endpoints/sessions.mjs +62 -0
- package/es/endpoints/sse.d.ts +3 -0
- package/es/endpoints/sse.mjs +44 -0
- package/es/endpoints/start.d.ts +3 -0
- package/es/endpoints/start.mjs +21 -0
- package/es/endpoints/types.d.ts +34 -0
- package/es/endpoints/types.mjs +0 -0
- package/es/endpoints/vue-devtools.d.ts +9 -0
- package/es/endpoints/vue-devtools.mjs +107 -0
- package/es/endpoints/warmup.d.ts +3 -0
- package/es/endpoints/warmup.mjs +42 -0
- package/es/endpoints/widget.d.ts +3 -0
- package/es/endpoints/widget.mjs +36 -0
- package/es/env.d.ts +1 -0
- package/es/index.d.ts +22 -0
- package/es/index.mjs +342 -0
- package/es/utils/format-bridge.d.ts +9 -0
- package/es/utils/format-bridge.mjs +80 -0
- package/es/utils/paths.d.ts +3 -0
- package/es/utils/paths.mjs +45 -0
- package/es/utils/system.d.ts +3 -0
- package/es/utils/system.mjs +76 -0
- package/lib/client/vue-devtools-bridge.cjs +150 -0
- package/lib/client/vue-devtools-bridge.d.ts +58 -0
- package/lib/client.css +2 -0
- package/lib/client.js +6950 -0
- package/lib/core/injector.cjs +34 -0
- package/lib/core/injector.d.ts +2 -0
- package/lib/core/mcp-chrome.cjs +161 -0
- package/lib/core/mcp-chrome.d.ts +56 -0
- package/lib/core/mcp-proxy.cjs +244 -0
- package/lib/core/mcp-proxy.d.ts +23 -0
- package/lib/core/mcp-tools.cjs +694 -0
- package/lib/core/mcp-tools.d.ts +15 -0
- package/lib/core/provider-loader.cjs +53 -0
- package/lib/core/provider-loader.d.ts +19 -0
- package/lib/core/proxy-server.cjs +181 -0
- package/lib/core/proxy-server.d.ts +12 -0
- package/lib/core/service.cjs +289 -0
- package/lib/core/service.d.ts +39 -0
- package/lib/endpoints/context.cjs +129 -0
- package/lib/endpoints/context.d.ts +3 -0
- package/lib/endpoints/index.cjs +58 -0
- package/lib/endpoints/index.d.ts +9 -0
- package/lib/endpoints/logs.cjs +107 -0
- package/lib/endpoints/logs.d.ts +9 -0
- package/lib/endpoints/mcp.cjs +624 -0
- package/lib/endpoints/mcp.d.ts +6 -0
- package/lib/endpoints/sessions.cjs +85 -0
- package/lib/endpoints/sessions.d.ts +3 -0
- package/lib/endpoints/sse.cjs +67 -0
- package/lib/endpoints/sse.d.ts +3 -0
- package/lib/endpoints/start.cjs +44 -0
- package/lib/endpoints/start.d.ts +3 -0
- package/lib/endpoints/types.cjs +15 -0
- package/lib/endpoints/types.d.ts +34 -0
- package/lib/endpoints/vue-devtools.cjs +132 -0
- package/lib/endpoints/vue-devtools.d.ts +9 -0
- package/lib/endpoints/warmup.cjs +65 -0
- package/lib/endpoints/warmup.d.ts +3 -0
- package/lib/endpoints/widget.cjs +69 -0
- package/lib/endpoints/widget.d.ts +3 -0
- package/lib/env.d.ts +1 -0
- package/lib/index.cjs +361 -0
- package/lib/index.d.ts +22 -0
- package/lib/utils/format-bridge.cjs +102 -0
- package/lib/utils/format-bridge.d.ts +9 -0
- package/lib/utils/paths.cjs +80 -0
- package/lib/utils/paths.d.ts +3 -0
- package/lib/utils/system.cjs +110 -0
- package/lib/utils/system.d.ts +3 -0
- package/package.json +68 -0
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 自定义 DevTools 工具定义
|
|
3
|
+
* 所有工具(除 chrome-devtools_list_pages)必须传入 pageId 参数,
|
|
4
|
+
* 代理层校验 pageId 是否为项目页面后方可调用 chrome-devtools-mcp。
|
|
5
|
+
*/
|
|
6
|
+
export interface CustomTool {
|
|
7
|
+
name: string;
|
|
8
|
+
description: string;
|
|
9
|
+
inputSchema: {
|
|
10
|
+
type: "object";
|
|
11
|
+
properties: Record<string, unknown>;
|
|
12
|
+
required?: string[];
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
export declare const CUSTOM_TOOLS: CustomTool[];
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
3
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
4
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
5
|
+
var __export = (target, all) => {
|
|
6
|
+
for (var name in all)
|
|
7
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
8
|
+
};
|
|
9
|
+
var __copyProps = (to, from, except, desc) => {
|
|
10
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
11
|
+
for (let key of __getOwnPropNames(from))
|
|
12
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
13
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
14
|
+
}
|
|
15
|
+
return to;
|
|
16
|
+
};
|
|
17
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
18
|
+
var provider_loader_exports = {};
|
|
19
|
+
__export(provider_loader_exports, {
|
|
20
|
+
loadProvider: () => loadProvider
|
|
21
|
+
});
|
|
22
|
+
module.exports = __toCommonJS(provider_loader_exports);
|
|
23
|
+
const PROVIDER_PACKAGES = {
|
|
24
|
+
default: "@aipanel/provider-opencode",
|
|
25
|
+
opencode: "@aipanel/provider-opencode",
|
|
26
|
+
deepseek: "@aipanel/provider-deepseek"
|
|
27
|
+
};
|
|
28
|
+
async function loadProvider(id, ctx) {
|
|
29
|
+
const pkg = PROVIDER_PACKAGES[id];
|
|
30
|
+
if (!pkg) {
|
|
31
|
+
throw new Error(
|
|
32
|
+
`\u672A\u77E5\u7684 Web Provider: "${id}"\u3002\u53EF\u7528 Provider: ${Object.keys(PROVIDER_PACKAGES).join(", ") || "(\u65E0)"}`
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
let mod;
|
|
36
|
+
try {
|
|
37
|
+
mod = await import(pkg);
|
|
38
|
+
} catch (e) {
|
|
39
|
+
const error = new Error(
|
|
40
|
+
`\u52A0\u8F7D Web Provider \u5305 "${pkg}" \u5931\u8D25\uFF08\u8BF7\u786E\u8BA4\u5DF2\u6B63\u786E\u5B89\u88C5\uFF09\uFF1A${e instanceof Error ? e.message : String(e)}`
|
|
41
|
+
);
|
|
42
|
+
error.cause = e;
|
|
43
|
+
throw error;
|
|
44
|
+
}
|
|
45
|
+
if (typeof mod.createProvider !== "function") {
|
|
46
|
+
throw new Error(`Provider \u5305 "${pkg}" \u672A\u5BFC\u51FA createProvider \u5DE5\u5382`);
|
|
47
|
+
}
|
|
48
|
+
return mod.createProvider(ctx);
|
|
49
|
+
}
|
|
50
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
51
|
+
0 && (module.exports = {
|
|
52
|
+
loadProvider
|
|
53
|
+
});
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { ProviderInitContext, WebProvider } from "@aipanel/core";
|
|
2
|
+
/**
|
|
3
|
+
* id → Provider npm 包名映射(登记制)
|
|
4
|
+
* "default" 是默认别名(未配置 provider 时的兜底),其他键为 Provider 真实 id。
|
|
5
|
+
* 这里只有包名字符串,无静态依赖;加载与初始化完全由 Provider 包自身完成。
|
|
6
|
+
*/
|
|
7
|
+
declare const PROVIDER_PACKAGES: {
|
|
8
|
+
readonly default: "@aipanel/provider-opencode";
|
|
9
|
+
readonly opencode: "@aipanel/provider-opencode";
|
|
10
|
+
readonly deepseek: "@aipanel/provider-deepseek";
|
|
11
|
+
};
|
|
12
|
+
/** 已登记的 Provider id 联合类型(与运行时映射保持单一事实来源) */
|
|
13
|
+
export type ProviderId = keyof typeof PROVIDER_PACKAGES;
|
|
14
|
+
/**
|
|
15
|
+
* 按 id 动态加载 Provider 包并初始化
|
|
16
|
+
* 约定:Provider 包必须导出 createProvider(ctx): WebProvider 工厂
|
|
17
|
+
*/
|
|
18
|
+
export declare function loadProvider(id: string, ctx: ProviderInitContext): Promise<WebProvider>;
|
|
19
|
+
export {};
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
var __create = Object.create;
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
6
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
+
var __export = (target, all) => {
|
|
8
|
+
for (var name in all)
|
|
9
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
10
|
+
};
|
|
11
|
+
var __copyProps = (to, from, except, desc) => {
|
|
12
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
13
|
+
for (let key of __getOwnPropNames(from))
|
|
14
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
15
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
16
|
+
}
|
|
17
|
+
return to;
|
|
18
|
+
};
|
|
19
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
20
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
21
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
22
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
23
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
24
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
25
|
+
mod
|
|
26
|
+
));
|
|
27
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
28
|
+
var proxy_server_exports = {};
|
|
29
|
+
__export(proxy_server_exports, {
|
|
30
|
+
startProxyServer: () => startProxyServer
|
|
31
|
+
});
|
|
32
|
+
module.exports = __toCommonJS(proxy_server_exports);
|
|
33
|
+
var import_http = __toESM(require("http"));
|
|
34
|
+
var import_core = require("@aipanel/core");
|
|
35
|
+
const log = (0, import_core.createLogger)("ProxyServer");
|
|
36
|
+
function startProxyServer(targetUrl, port, options = {}) {
|
|
37
|
+
return new Promise((resolve, reject) => {
|
|
38
|
+
const target = new URL(targetUrl);
|
|
39
|
+
const bridgeScript = options.bridgeScript ?? "";
|
|
40
|
+
const agent = new import_http.default.Agent({
|
|
41
|
+
keepAlive: true,
|
|
42
|
+
keepAliveMsecs: 3e4,
|
|
43
|
+
maxSockets: 50,
|
|
44
|
+
maxFreeSockets: 10,
|
|
45
|
+
timeout: 0
|
|
46
|
+
});
|
|
47
|
+
const server = import_http.default.createServer((req, res) => {
|
|
48
|
+
if (req.url === import_core.BRIDGE_SCRIPT_PATH) {
|
|
49
|
+
const body = bridgeScript;
|
|
50
|
+
res.writeHead(200, {
|
|
51
|
+
"content-type": "application/javascript; charset=utf-8",
|
|
52
|
+
"cache-control": "no-store",
|
|
53
|
+
"content-length": Buffer.byteLength(body)
|
|
54
|
+
});
|
|
55
|
+
res.end(body);
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
const requestOptions = {
|
|
59
|
+
agent,
|
|
60
|
+
hostname: target.hostname,
|
|
61
|
+
port: target.port,
|
|
62
|
+
path: req.url,
|
|
63
|
+
method: req.method,
|
|
64
|
+
headers: {
|
|
65
|
+
...req.headers,
|
|
66
|
+
host: target.host,
|
|
67
|
+
// 上游(如 dsh 的 trust fence)校验 Origin 必须匹配自身 origin;
|
|
68
|
+
// 页面经代理访问时浏览器发的 Origin 是代理端口,须改写为目标 origin 否则 403
|
|
69
|
+
origin: `http://${target.host}`,
|
|
70
|
+
"accept-encoding": "identity"
|
|
71
|
+
},
|
|
72
|
+
timeout: 0
|
|
73
|
+
};
|
|
74
|
+
const proxyReq = import_http.default.request(requestOptions, (proxyRes) => {
|
|
75
|
+
const rawContentType = proxyRes.headers["content-type"];
|
|
76
|
+
const contentType = Array.isArray(rawContentType) ? rawContentType[0] ?? "" : rawContentType ?? "";
|
|
77
|
+
if (contentType.includes("text/html")) {
|
|
78
|
+
const chunks = [];
|
|
79
|
+
proxyRes.on("data", (chunk) => {
|
|
80
|
+
chunks.push(chunk);
|
|
81
|
+
});
|
|
82
|
+
proxyRes.on("end", () => {
|
|
83
|
+
let body = Buffer.concat(chunks).toString("utf-8");
|
|
84
|
+
if (body.match(/<\/head>/i)) {
|
|
85
|
+
body = body.replace(
|
|
86
|
+
/<\/head>/i,
|
|
87
|
+
`<script src="${import_core.BRIDGE_SCRIPT_PATH}"></script></head>`
|
|
88
|
+
);
|
|
89
|
+
} else if (body.match(/<\/body>/i)) {
|
|
90
|
+
body = body.replace(
|
|
91
|
+
/<\/body>/i,
|
|
92
|
+
`<script src="${import_core.BRIDGE_SCRIPT_PATH}"></script></body>`
|
|
93
|
+
);
|
|
94
|
+
} else {
|
|
95
|
+
body += `<script src="${import_core.BRIDGE_SCRIPT_PATH}"></script>`;
|
|
96
|
+
}
|
|
97
|
+
const headers = {};
|
|
98
|
+
for (const [key, value] of Object.entries(proxyRes.headers)) {
|
|
99
|
+
if (value !== void 0 && key !== "content-encoding" && key !== "transfer-encoding" && key !== "content-length") {
|
|
100
|
+
headers[key] = value;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
headers["content-length"] = Buffer.byteLength(body);
|
|
104
|
+
res.writeHead(proxyRes.statusCode || 200, headers);
|
|
105
|
+
res.end(body);
|
|
106
|
+
});
|
|
107
|
+
} else {
|
|
108
|
+
res.writeHead(proxyRes.statusCode || 200, proxyRes.headers);
|
|
109
|
+
proxyRes.pipe(res);
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
proxyReq.on("error", (err) => {
|
|
113
|
+
log.error("Proxy error", { error: err.message, url: req.url });
|
|
114
|
+
res.writeHead(502);
|
|
115
|
+
res.end("Proxy error");
|
|
116
|
+
});
|
|
117
|
+
proxyReq.on("socket", (socket) => {
|
|
118
|
+
socket.setTimeout(0);
|
|
119
|
+
});
|
|
120
|
+
req.on("socket", (socket) => {
|
|
121
|
+
socket.setTimeout(0);
|
|
122
|
+
});
|
|
123
|
+
req.pipe(proxyReq);
|
|
124
|
+
});
|
|
125
|
+
server.on("error", (err) => {
|
|
126
|
+
reject(err);
|
|
127
|
+
});
|
|
128
|
+
server.on("upgrade", (req, clientSocket, head) => {
|
|
129
|
+
const upgradeOptions = {
|
|
130
|
+
hostname: target.hostname,
|
|
131
|
+
port: target.port,
|
|
132
|
+
path: req.url,
|
|
133
|
+
method: req.method,
|
|
134
|
+
// upgrade 不复用 keep-alive 连接池
|
|
135
|
+
agent: false,
|
|
136
|
+
headers: {
|
|
137
|
+
...req.headers,
|
|
138
|
+
host: target.host,
|
|
139
|
+
// 与 HTTP 分支一致:改写 Origin 为目标 origin,通过 dsh 的 trust fence
|
|
140
|
+
origin: `http://${target.host}`
|
|
141
|
+
}
|
|
142
|
+
};
|
|
143
|
+
const proxyReq = import_http.default.request(upgradeOptions);
|
|
144
|
+
proxyReq.on("upgrade", (proxyRes, proxySocket, proxyHead) => {
|
|
145
|
+
proxySocket.removeAllListeners("data");
|
|
146
|
+
proxySocket.setTimeout(0);
|
|
147
|
+
clientSocket.setTimeout(0);
|
|
148
|
+
const headBuf = [
|
|
149
|
+
`HTTP/1.1 ${proxyRes.statusCode} ${proxyRes.statusMessage}`,
|
|
150
|
+
...Object.entries(proxyRes.headers).map(([k, v]) => `${k}: ${v}`),
|
|
151
|
+
"",
|
|
152
|
+
""
|
|
153
|
+
].join("\r\n");
|
|
154
|
+
clientSocket.write(headBuf);
|
|
155
|
+
if (head?.length) proxySocket.write(head);
|
|
156
|
+
if (proxyHead?.length) clientSocket.write(proxyHead);
|
|
157
|
+
proxySocket.pipe(clientSocket);
|
|
158
|
+
clientSocket.pipe(proxySocket);
|
|
159
|
+
proxySocket.on("close", () => clientSocket.destroy());
|
|
160
|
+
clientSocket.on("close", () => proxySocket.destroy());
|
|
161
|
+
});
|
|
162
|
+
proxyReq.on("error", (err) => {
|
|
163
|
+
log.warn("Proxy websocket upgrade failed", { error: err.message, url: req.url });
|
|
164
|
+
clientSocket.destroy();
|
|
165
|
+
});
|
|
166
|
+
proxyReq.end();
|
|
167
|
+
});
|
|
168
|
+
server.timeout = 0;
|
|
169
|
+
server.keepAliveTimeout = 0;
|
|
170
|
+
server.listen(port, options.hostname || void 0, () => {
|
|
171
|
+
const address = server.address();
|
|
172
|
+
const actualPort = typeof address === "object" && address ? address.port : port;
|
|
173
|
+
log.debug(`Proxy server started on port ${actualPort} -> ${targetUrl}`);
|
|
174
|
+
resolve({ server, actualPort });
|
|
175
|
+
});
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
179
|
+
0 && (module.exports = {
|
|
180
|
+
startProxyServer
|
|
181
|
+
});
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import http from "http";
|
|
2
|
+
export interface ProxyServerOptions {
|
|
3
|
+
/** 注入到 HTML 的 Provider 侧桥接脚本(Provider 资产) */
|
|
4
|
+
bridgeScript?: string;
|
|
5
|
+
/** 绑定地址,需与端口检查使用的地址族一致,避免 IPv4/IPv6 不匹配 */
|
|
6
|
+
hostname?: string;
|
|
7
|
+
}
|
|
8
|
+
export interface ProxyServerResult {
|
|
9
|
+
server: http.Server;
|
|
10
|
+
actualPort: number;
|
|
11
|
+
}
|
|
12
|
+
export declare function startProxyServer(targetUrl: string, port: number, options?: ProxyServerOptions): Promise<ProxyServerResult>;
|
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
3
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
4
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
5
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
20
|
+
var service_exports = {};
|
|
21
|
+
__export(service_exports, {
|
|
22
|
+
AIPanelService: () => AIPanelService
|
|
23
|
+
});
|
|
24
|
+
module.exports = __toCommonJS(service_exports);
|
|
25
|
+
var import_core = require("@aipanel/core");
|
|
26
|
+
var import_node = require("@aipanel/core/node");
|
|
27
|
+
var import_system = require("../utils/system.cjs");
|
|
28
|
+
var import_proxy_server = require("./proxy-server.cjs");
|
|
29
|
+
const log = (0, import_node.createLogger)("Service");
|
|
30
|
+
class AIPanelService {
|
|
31
|
+
constructor(config, sseClients, onPortAllocated, onProxyPortAllocated) {
|
|
32
|
+
__publicField(this, "config", config);
|
|
33
|
+
__publicField(this, "sseClients", sseClients);
|
|
34
|
+
__publicField(this, "onPortAllocated", onPortAllocated);
|
|
35
|
+
__publicField(this, "onProxyPortAllocated", onProxyPortAllocated);
|
|
36
|
+
__publicField(this, "webProcess", null);
|
|
37
|
+
__publicField(this, "actualWebPort");
|
|
38
|
+
__publicField(this, "actualProxyPort");
|
|
39
|
+
__publicField(this, "isStarted", false);
|
|
40
|
+
__publicField(this, "startPromise", null);
|
|
41
|
+
__publicField(this, "proxyServer", null);
|
|
42
|
+
__publicField(this, "chromeMcpWarmupFailed", false);
|
|
43
|
+
__publicField(this, "chromeMcpWarmupErrorType", null);
|
|
44
|
+
__publicField(this, "chromeMcpWarmupErrorMessage", null);
|
|
45
|
+
__publicField(this, "currentTask", null);
|
|
46
|
+
__publicField(this, "workspaceRoot", null);
|
|
47
|
+
__publicField(this, "mcp", null);
|
|
48
|
+
__publicField(this, "provider", null);
|
|
49
|
+
__publicField(this, "unsubscribeEvents", null);
|
|
50
|
+
this.actualWebPort = config.webPort;
|
|
51
|
+
this.actualProxyPort = config.proxyPort ?? import_core.DEFAULT_PROXY_PORT;
|
|
52
|
+
}
|
|
53
|
+
/** 设置 Provider(由编排层动态加载后调用) */
|
|
54
|
+
setProvider(provider) {
|
|
55
|
+
this.provider = provider;
|
|
56
|
+
}
|
|
57
|
+
sendTaskUpdate(task, data) {
|
|
58
|
+
this.currentTask = { task, ...data };
|
|
59
|
+
this.sseClients.forEach((client) => {
|
|
60
|
+
try {
|
|
61
|
+
client.write(`data: ${JSON.stringify({ type: "TASK_UPDATE", task, ...data })}
|
|
62
|
+
|
|
63
|
+
`);
|
|
64
|
+
} catch (e) {
|
|
65
|
+
log.debug("Failed to send TASK_UPDATE event", { error: e });
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
async start(vitePort, corsOrigins, contextApiUrl, logsApiUrl, viteOrigin, mcp, vueDevtoolsApiUrl) {
|
|
70
|
+
if (this.isStarted && this.webProcess) {
|
|
71
|
+
log.debug("Services already started, skipping");
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
if (this.startPromise) {
|
|
75
|
+
log.debug("Waiting for existing start promise");
|
|
76
|
+
return this.startPromise;
|
|
77
|
+
}
|
|
78
|
+
this.mcp = mcp;
|
|
79
|
+
const provider = this.provider;
|
|
80
|
+
if (!provider) {
|
|
81
|
+
throw new Error("Provider \u672A\u521D\u59CB\u5316\uFF0C\u8BF7\u5148\u8C03\u7528 setProvider");
|
|
82
|
+
}
|
|
83
|
+
this.startPromise = (async () => {
|
|
84
|
+
const timer = log.timer("startServices", {
|
|
85
|
+
corsOrigins,
|
|
86
|
+
contextApiUrl,
|
|
87
|
+
logsApiUrl,
|
|
88
|
+
viteOrigin
|
|
89
|
+
});
|
|
90
|
+
log.info("Starting Web UI services...");
|
|
91
|
+
const orphanCount = await provider.killOrphans?.() ?? 0;
|
|
92
|
+
if (orphanCount > 0) {
|
|
93
|
+
log.debug(`Killed ${orphanCount} orphan Web UI process(es)`);
|
|
94
|
+
}
|
|
95
|
+
this.sendTaskUpdate("checking_provider");
|
|
96
|
+
const env = await provider.checkEnvironment();
|
|
97
|
+
if (!env.ok) {
|
|
98
|
+
log.error(env.message ?? "Provider environment check failed");
|
|
99
|
+
this.sendTaskUpdate("provider_not_installed");
|
|
100
|
+
this.startPromise = null;
|
|
101
|
+
timer.end("\u274C Provider environment check failed");
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
const providerVersion = env.version;
|
|
105
|
+
timer.checkpoint("Provider environment verified");
|
|
106
|
+
this.sendTaskUpdate("allocating_port");
|
|
107
|
+
this.actualWebPort = await (0, import_node.findAvailablePort)(this.config.webPort, this.config.hostname);
|
|
108
|
+
this.onPortAllocated(this.actualWebPort);
|
|
109
|
+
if (this.actualWebPort !== this.config.webPort) {
|
|
110
|
+
log.info(`Port ${this.config.webPort} is in use, using ${this.actualWebPort} instead`);
|
|
111
|
+
} else {
|
|
112
|
+
log.debug(`Using port ${this.actualWebPort}`);
|
|
113
|
+
}
|
|
114
|
+
timer.checkpoint("Port allocated");
|
|
115
|
+
this.workspaceRoot = (0, import_system.findGitRoot)(process.cwd());
|
|
116
|
+
log.debug(`Using workspace root: ${this.workspaceRoot}`);
|
|
117
|
+
this.sendTaskUpdate("preparing_runtime");
|
|
118
|
+
this.sendTaskUpdate("starting_web");
|
|
119
|
+
let webUrl;
|
|
120
|
+
try {
|
|
121
|
+
const startResult = await provider.start({
|
|
122
|
+
port: this.actualWebPort,
|
|
123
|
+
hostname: this.config.hostname,
|
|
124
|
+
cwd: this.workspaceRoot,
|
|
125
|
+
corsOrigins,
|
|
126
|
+
vitePort,
|
|
127
|
+
contextApiUrl,
|
|
128
|
+
logsApiUrl,
|
|
129
|
+
verbose: this.config.verbose,
|
|
130
|
+
vueDevtoolsApiUrl
|
|
131
|
+
});
|
|
132
|
+
this.webProcess = startResult.processHandle ?? null;
|
|
133
|
+
timer.checkpoint("Web process started");
|
|
134
|
+
webUrl = startResult.url;
|
|
135
|
+
log.debug(`Waiting for Web UI to become ready at ${webUrl}...`);
|
|
136
|
+
this.sendTaskUpdate("waiting_web_ready");
|
|
137
|
+
await (0, import_system.waitForServer)(webUrl, import_core.SERVER_START_TIMEOUT, this.webProcess ?? void 0);
|
|
138
|
+
if (this.webProcess?.exitCode !== null && this.webProcess?.exitCode !== void 0) {
|
|
139
|
+
throw new Error(`Web process exited with code ${this.webProcess.exitCode}`);
|
|
140
|
+
}
|
|
141
|
+
log.info(
|
|
142
|
+
`Web UI started at ${webUrl}${providerVersion ? ` (${provider.displayName} ${providerVersion})` : ""}`
|
|
143
|
+
);
|
|
144
|
+
} catch (e) {
|
|
145
|
+
log.error("Web UI failed to start", { error: e });
|
|
146
|
+
this.sendTaskUpdate("web_start_timeout");
|
|
147
|
+
await provider.stop();
|
|
148
|
+
this.webProcess = null;
|
|
149
|
+
this.startPromise = null;
|
|
150
|
+
timer.end("\u274C Web start timeout");
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
this.sendTaskUpdate("starting_proxy");
|
|
154
|
+
let proxyStartPort = this.config.proxyPort ?? import_core.DEFAULT_PROXY_PORT;
|
|
155
|
+
if (proxyStartPort === this.actualWebPort) {
|
|
156
|
+
proxyStartPort = this.actualWebPort + 1;
|
|
157
|
+
log.debug(`Proxy start port conflicts with web port, using ${proxyStartPort} instead`);
|
|
158
|
+
}
|
|
159
|
+
this.actualProxyPort = await (0, import_node.findAvailablePort)(proxyStartPort, this.config.hostname);
|
|
160
|
+
this.onProxyPortAllocated(this.actualProxyPort);
|
|
161
|
+
if (this.actualProxyPort !== (this.config.proxyPort ?? import_core.DEFAULT_PROXY_PORT)) {
|
|
162
|
+
log.info(
|
|
163
|
+
`Proxy port ${this.config.proxyPort ?? import_core.DEFAULT_PROXY_PORT} is in use, using ${this.actualProxyPort} instead`
|
|
164
|
+
);
|
|
165
|
+
} else {
|
|
166
|
+
log.debug(`Using proxy port ${this.actualProxyPort}`);
|
|
167
|
+
}
|
|
168
|
+
try {
|
|
169
|
+
const result = await (0, import_proxy_server.startProxyServer)(webUrl, this.actualProxyPort, {
|
|
170
|
+
bridgeScript: provider.bridgeScript,
|
|
171
|
+
hostname: this.config.hostname
|
|
172
|
+
});
|
|
173
|
+
this.proxyServer = result.server;
|
|
174
|
+
if (result.actualPort !== this.actualProxyPort) {
|
|
175
|
+
log.info(
|
|
176
|
+
`Proxy port ${this.actualProxyPort} was taken, using ${result.actualPort} instead`
|
|
177
|
+
);
|
|
178
|
+
this.actualProxyPort = result.actualPort;
|
|
179
|
+
this.onProxyPortAllocated(this.actualProxyPort);
|
|
180
|
+
}
|
|
181
|
+
} catch (err) {
|
|
182
|
+
const nodeErr = err;
|
|
183
|
+
if (nodeErr.code === "EADDRINUSE") {
|
|
184
|
+
log.debug(`Proxy port ${this.actualProxyPort} became unavailable, trying next port...`);
|
|
185
|
+
const nextPort = await (0, import_node.findAvailablePort)(this.actualProxyPort + 1, this.config.hostname);
|
|
186
|
+
const result = await (0, import_proxy_server.startProxyServer)(webUrl, nextPort, {
|
|
187
|
+
bridgeScript: provider.bridgeScript,
|
|
188
|
+
hostname: this.config.hostname
|
|
189
|
+
});
|
|
190
|
+
this.proxyServer = result.server;
|
|
191
|
+
this.actualProxyPort = result.actualPort;
|
|
192
|
+
this.onProxyPortAllocated(this.actualProxyPort);
|
|
193
|
+
log.debug(`Proxy server started on fallback port ${this.actualProxyPort}`);
|
|
194
|
+
} else {
|
|
195
|
+
log.error("Proxy server failed to start", { error: nodeErr });
|
|
196
|
+
this.sendTaskUpdate("proxy_start_failed", {
|
|
197
|
+
errorMessage: nodeErr.message
|
|
198
|
+
});
|
|
199
|
+
await provider.stop();
|
|
200
|
+
this.webProcess = null;
|
|
201
|
+
this.startPromise = null;
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
timer.checkpoint("Proxy server started");
|
|
206
|
+
this.sendTaskUpdate("warming_up_chrome");
|
|
207
|
+
let warmupFailed = false;
|
|
208
|
+
try {
|
|
209
|
+
const result = await mcp.verify();
|
|
210
|
+
if (!result.ok) throw new Error(result.error ?? "Chrome MCP not available");
|
|
211
|
+
timer.checkpoint("Chrome MCP warmup complete");
|
|
212
|
+
} catch (e) {
|
|
213
|
+
log.warn("Chrome MCP warmup failed", { error: e });
|
|
214
|
+
this.chromeMcpWarmupFailed = true;
|
|
215
|
+
warmupFailed = true;
|
|
216
|
+
this.chromeMcpWarmupErrorType = import_core.ChromeMcpWarmupErrorType.UNKNOWN;
|
|
217
|
+
this.chromeMcpWarmupErrorMessage = e instanceof Error ? e.message : String(e);
|
|
218
|
+
}
|
|
219
|
+
this.sendTaskUpdate("creating_session");
|
|
220
|
+
this.isStarted = true;
|
|
221
|
+
this.unsubscribeEvents?.();
|
|
222
|
+
this.unsubscribeEvents = provider.subscribeEvents((event) => {
|
|
223
|
+
this.sseClients.forEach((client) => {
|
|
224
|
+
try {
|
|
225
|
+
client.write(`data: ${JSON.stringify({ type: "SESSION_EVENT", event })}
|
|
226
|
+
|
|
227
|
+
`);
|
|
228
|
+
} catch (e) {
|
|
229
|
+
log.debug("Failed to send SESSION_EVENT", { error: e });
|
|
230
|
+
}
|
|
231
|
+
});
|
|
232
|
+
});
|
|
233
|
+
if (warmupFailed) {
|
|
234
|
+
this.sendTaskUpdate("chrome_mcp_failed", {
|
|
235
|
+
errorType: this.chromeMcpWarmupErrorType,
|
|
236
|
+
errorMessage: this.chromeMcpWarmupErrorMessage
|
|
237
|
+
});
|
|
238
|
+
} else {
|
|
239
|
+
this.sendTaskUpdate("ready");
|
|
240
|
+
}
|
|
241
|
+
timer.end("\u2713 Services started successfully");
|
|
242
|
+
})();
|
|
243
|
+
return this.startPromise;
|
|
244
|
+
}
|
|
245
|
+
async retryWarmupChromeMcp() {
|
|
246
|
+
if (!this.mcp) {
|
|
247
|
+
return { success: false, errorType: "UNKNOWN", errorMessage: "MCP not initialized" };
|
|
248
|
+
}
|
|
249
|
+
try {
|
|
250
|
+
const result = await this.mcp.verify();
|
|
251
|
+
if (result.ok) {
|
|
252
|
+
this.chromeMcpWarmupFailed = false;
|
|
253
|
+
this.sendTaskUpdate("ready");
|
|
254
|
+
return { success: true };
|
|
255
|
+
}
|
|
256
|
+
return {
|
|
257
|
+
success: false,
|
|
258
|
+
errorType: "CHROME_NOT_CONNECTED",
|
|
259
|
+
errorMessage: result.error ?? "Chrome MCP not available"
|
|
260
|
+
};
|
|
261
|
+
} catch (e) {
|
|
262
|
+
return {
|
|
263
|
+
success: false,
|
|
264
|
+
errorType: "UNKNOWN",
|
|
265
|
+
errorMessage: e instanceof Error ? e.message : String(e)
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
async stop() {
|
|
270
|
+
const timer = log.timer("stopServices");
|
|
271
|
+
log.info("Stopping Web UI services...");
|
|
272
|
+
this.unsubscribeEvents?.();
|
|
273
|
+
this.unsubscribeEvents = null;
|
|
274
|
+
if (this.proxyServer) {
|
|
275
|
+
log.debug("Closing proxy server");
|
|
276
|
+
this.proxyServer.close();
|
|
277
|
+
this.proxyServer = null;
|
|
278
|
+
}
|
|
279
|
+
await this.provider?.stop();
|
|
280
|
+
this.webProcess = null;
|
|
281
|
+
this.isStarted = false;
|
|
282
|
+
this.startPromise = null;
|
|
283
|
+
timer.end("\u2713 Services stopped");
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
287
|
+
0 && (module.exports = {
|
|
288
|
+
AIPanelService
|
|
289
|
+
});
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { ResultPromise } from "execa";
|
|
2
|
+
import type http from "http";
|
|
3
|
+
import type { PluginOptions, ServiceStartupTask, WebProvider } from "@aipanel/core";
|
|
4
|
+
import { ChromeMcpWarmupErrorType } from "@aipanel/core";
|
|
5
|
+
import type { McpProxy } from "./mcp-proxy";
|
|
6
|
+
export declare class AIPanelService {
|
|
7
|
+
private config;
|
|
8
|
+
private sseClients;
|
|
9
|
+
private onPortAllocated;
|
|
10
|
+
private onProxyPortAllocated;
|
|
11
|
+
webProcess: ResultPromise | null;
|
|
12
|
+
actualWebPort: number;
|
|
13
|
+
actualProxyPort: number;
|
|
14
|
+
isStarted: boolean;
|
|
15
|
+
private startPromise;
|
|
16
|
+
private proxyServer;
|
|
17
|
+
chromeMcpWarmupFailed: boolean;
|
|
18
|
+
chromeMcpWarmupErrorType: ChromeMcpWarmupErrorType | null;
|
|
19
|
+
chromeMcpWarmupErrorMessage: string | null;
|
|
20
|
+
currentTask: {
|
|
21
|
+
task: ServiceStartupTask;
|
|
22
|
+
data?: Record<string, unknown>;
|
|
23
|
+
} | null;
|
|
24
|
+
workspaceRoot: string | null;
|
|
25
|
+
private mcp;
|
|
26
|
+
private provider;
|
|
27
|
+
private unsubscribeEvents;
|
|
28
|
+
constructor(config: Required<PluginOptions>, sseClients: Set<http.ServerResponse>, onPortAllocated: (port: number) => void, onProxyPortAllocated: (port: number) => void);
|
|
29
|
+
/** 设置 Provider(由编排层动态加载后调用) */
|
|
30
|
+
setProvider(provider: WebProvider): void;
|
|
31
|
+
private sendTaskUpdate;
|
|
32
|
+
start(vitePort: number, corsOrigins: string[], contextApiUrl: string, logsApiUrl: string, viteOrigin: string, mcp: McpProxy, vueDevtoolsApiUrl?: string): Promise<void>;
|
|
33
|
+
retryWarmupChromeMcp(): Promise<{
|
|
34
|
+
success: boolean;
|
|
35
|
+
errorType?: string;
|
|
36
|
+
errorMessage?: string;
|
|
37
|
+
}>;
|
|
38
|
+
stop(): Promise<void>;
|
|
39
|
+
}
|