vite-plugin-opencode-assistant 1.1.23 → 1.1.25
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/es/core/mcp-proxy.d.ts +21 -0
- package/es/core/mcp-proxy.mjs +254 -0
- package/es/core/opencode-web.d.ts +1 -1
- package/es/core/opencode-web.mjs +4 -4
- package/es/core/service.d.ts +2 -1
- package/es/core/service.mjs +4 -3
- package/es/endpoints/context.d.ts +35 -0
- package/es/endpoints/context.mjs +126 -2
- package/es/endpoints/index.d.ts +4 -2
- package/es/endpoints/index.mjs +4 -1
- package/es/endpoints/mcp-tools.d.ts +16 -0
- package/es/endpoints/mcp-tools.mjs +325 -0
- package/es/endpoints/mcp.d.ts +6 -0
- package/es/endpoints/mcp.mjs +259 -0
- package/es/index.mjs +71 -66
- package/lib/client.js +564 -563
- package/lib/core/mcp-proxy.cjs +286 -0
- package/lib/core/mcp-proxy.d.ts +21 -0
- package/lib/core/opencode-web.cjs +4 -4
- package/lib/core/opencode-web.d.ts +1 -1
- package/lib/core/service.cjs +4 -3
- package/lib/core/service.d.ts +2 -1
- package/lib/endpoints/context.cjs +130 -2
- package/lib/endpoints/context.d.ts +35 -0
- package/lib/endpoints/index.cjs +5 -1
- package/lib/endpoints/index.d.ts +4 -2
- package/lib/endpoints/mcp-tools.cjs +349 -0
- package/lib/endpoints/mcp-tools.d.ts +16 -0
- package/lib/endpoints/mcp.cjs +283 -0
- package/lib/endpoints/mcp.d.ts +6 -0
- package/lib/index.cjs +71 -66
- package/package.json +6 -5
|
@@ -0,0 +1,286 @@
|
|
|
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 __typeError = (msg) => {
|
|
8
|
+
throw TypeError(msg);
|
|
9
|
+
};
|
|
10
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
11
|
+
var __export = (target, all) => {
|
|
12
|
+
for (var name in all)
|
|
13
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
14
|
+
};
|
|
15
|
+
var __copyProps = (to, from, except, desc) => {
|
|
16
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
17
|
+
for (let key of __getOwnPropNames(from))
|
|
18
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
19
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
20
|
+
}
|
|
21
|
+
return to;
|
|
22
|
+
};
|
|
23
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
24
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
25
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
26
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
27
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
28
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
29
|
+
mod
|
|
30
|
+
));
|
|
31
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
32
|
+
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
33
|
+
var __accessCheck = (obj, member, msg) => member.has(obj) || __typeError("Cannot " + msg);
|
|
34
|
+
var __privateGet = (obj, member, getter) => (__accessCheck(obj, member, "read from private field"), getter ? getter.call(obj) : member.get(obj));
|
|
35
|
+
var __privateAdd = (obj, member, value) => member.has(obj) ? __typeError("Cannot add the same private member more than once") : member instanceof WeakSet ? member.add(obj) : member.set(obj, value);
|
|
36
|
+
var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "write to private field"), setter ? setter.call(obj, value) : member.set(obj, value), value);
|
|
37
|
+
var __privateMethod = (obj, member, method) => (__accessCheck(obj, member, "access private method"), method);
|
|
38
|
+
var __privateWrapper = (obj, member, setter, getter) => ({
|
|
39
|
+
set _(value) {
|
|
40
|
+
__privateSet(obj, member, value, setter);
|
|
41
|
+
},
|
|
42
|
+
get _() {
|
|
43
|
+
return __privateGet(obj, member, getter);
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
var __async = (__this, __arguments, generator) => {
|
|
47
|
+
return new Promise((resolve, reject) => {
|
|
48
|
+
var fulfilled = (value) => {
|
|
49
|
+
try {
|
|
50
|
+
step(generator.next(value));
|
|
51
|
+
} catch (e) {
|
|
52
|
+
reject(e);
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
var rejected = (value) => {
|
|
56
|
+
try {
|
|
57
|
+
step(generator.throw(value));
|
|
58
|
+
} catch (e) {
|
|
59
|
+
reject(e);
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
var step = (x) => x.done ? resolve(x.value) : Promise.resolve(x.value).then(fulfilled, rejected);
|
|
63
|
+
step((generator = generator.apply(__this, __arguments)).next());
|
|
64
|
+
});
|
|
65
|
+
};
|
|
66
|
+
var mcp_proxy_exports = {};
|
|
67
|
+
__export(mcp_proxy_exports, {
|
|
68
|
+
McpProxy: () => McpProxy
|
|
69
|
+
});
|
|
70
|
+
module.exports = __toCommonJS(mcp_proxy_exports);
|
|
71
|
+
const import_meta = {};
|
|
72
|
+
var import_node_child_process = require("node:child_process");
|
|
73
|
+
var import_node_readline = require("node:readline");
|
|
74
|
+
var import_node_crypto = __toESM(require("node:crypto"));
|
|
75
|
+
var import_node_module = require("node:module");
|
|
76
|
+
var import_node_path = __toESM(require("node:path"));
|
|
77
|
+
var import_node = require("@vite-plugin-opencode-assistant/shared/node");
|
|
78
|
+
var _proc, _rl, _messageId, _internalIdBase, _pending, _args, _startPromise, _idleTimer, _idleTimeout, _McpProxy_instances, doStart_fn, resetIdleTimer_fn;
|
|
79
|
+
const log = (0, import_node.createLogger)("McpProxy");
|
|
80
|
+
function resolveChromeDevToolsMcpBin() {
|
|
81
|
+
const require2 = (0, import_node_module.createRequire)(import_meta.url);
|
|
82
|
+
const pkgJsonPath = require2.resolve("chrome-devtools-mcp/package.json");
|
|
83
|
+
const pkgDir = import_node_path.default.dirname(pkgJsonPath);
|
|
84
|
+
const { bin } = require2(pkgJsonPath);
|
|
85
|
+
const binEntry = typeof bin === "string" ? bin : Object.values(bin)[0];
|
|
86
|
+
return import_node_path.default.resolve(pkgDir, binEntry);
|
|
87
|
+
}
|
|
88
|
+
class McpProxy {
|
|
89
|
+
constructor(options = {}) {
|
|
90
|
+
__privateAdd(this, _McpProxy_instances);
|
|
91
|
+
__privateAdd(this, _proc, null);
|
|
92
|
+
__privateAdd(this, _rl, null);
|
|
93
|
+
__privateAdd(this, _messageId, 0);
|
|
94
|
+
/** 内部调用使用的高位 ID 起始值,避免与客户端 ID 冲突 */
|
|
95
|
+
__privateAdd(this, _internalIdBase, 1e6);
|
|
96
|
+
__privateAdd(this, _pending, /* @__PURE__ */ new Map());
|
|
97
|
+
__privateAdd(this, _args);
|
|
98
|
+
__privateAdd(this, _startPromise, null);
|
|
99
|
+
__privateAdd(this, _idleTimer, null);
|
|
100
|
+
__privateAdd(this, _idleTimeout);
|
|
101
|
+
__publicField(this, "sessionId");
|
|
102
|
+
__publicField(this, "accessToken");
|
|
103
|
+
var _a, _b;
|
|
104
|
+
__privateSet(this, _args, (_a = options.args) != null ? _a : ["--auto-connect", "--no-usage-statistics"]);
|
|
105
|
+
__privateSet(this, _idleTimeout, (_b = options.idleTimeout) != null ? _b : 0);
|
|
106
|
+
this.sessionId = import_node_crypto.default.randomUUID();
|
|
107
|
+
this.accessToken = import_node_crypto.default.randomBytes(32).toString("hex");
|
|
108
|
+
}
|
|
109
|
+
get isRunning() {
|
|
110
|
+
return __privateGet(this, _proc) !== null && !__privateGet(this, _proc).killed;
|
|
111
|
+
}
|
|
112
|
+
start() {
|
|
113
|
+
return __async(this, null, function* () {
|
|
114
|
+
if (this.isRunning) return;
|
|
115
|
+
if (__privateGet(this, _startPromise)) return __privateGet(this, _startPromise);
|
|
116
|
+
__privateSet(this, _startPromise, __privateMethod(this, _McpProxy_instances, doStart_fn).call(this));
|
|
117
|
+
return __privateGet(this, _startPromise);
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
/** 转发原始 JSON-RPC 请求,保留客户端 ID */
|
|
121
|
+
forward(rawRequest) {
|
|
122
|
+
return __async(this, null, function* () {
|
|
123
|
+
yield this.start();
|
|
124
|
+
if (!__privateGet(this, _proc) || !__privateGet(this, _proc).stdin) {
|
|
125
|
+
throw new Error("MCP process not available");
|
|
126
|
+
}
|
|
127
|
+
__privateMethod(this, _McpProxy_instances, resetIdleTimer_fn).call(this);
|
|
128
|
+
let msg;
|
|
129
|
+
try {
|
|
130
|
+
msg = JSON.parse(rawRequest);
|
|
131
|
+
} catch (e) {
|
|
132
|
+
throw new Error("Invalid JSON-RPC request");
|
|
133
|
+
}
|
|
134
|
+
return new Promise((resolve, reject) => {
|
|
135
|
+
const id = msg.id;
|
|
136
|
+
if (id !== void 0) {
|
|
137
|
+
__privateGet(this, _pending).set(id, (raw) => resolve(JSON.stringify(raw)));
|
|
138
|
+
} else {
|
|
139
|
+
__privateGet(this, _proc).stdin.write(rawRequest + "\n");
|
|
140
|
+
resolve("");
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
__privateGet(this, _proc).stdin.write(rawRequest + "\n");
|
|
144
|
+
setTimeout(() => {
|
|
145
|
+
if (__privateGet(this, _pending).has(id)) {
|
|
146
|
+
__privateGet(this, _pending).delete(id);
|
|
147
|
+
reject(new Error("MCP forward timeout"));
|
|
148
|
+
}
|
|
149
|
+
}, 3e4);
|
|
150
|
+
});
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
/** 调用 MCP 工具 */
|
|
154
|
+
call(_0) {
|
|
155
|
+
return __async(this, arguments, function* (method, params = {}) {
|
|
156
|
+
yield this.start();
|
|
157
|
+
if (!__privateGet(this, _proc) || !__privateGet(this, _proc).stdin) {
|
|
158
|
+
throw new Error("MCP process not available");
|
|
159
|
+
}
|
|
160
|
+
__privateMethod(this, _McpProxy_instances, resetIdleTimer_fn).call(this);
|
|
161
|
+
const id = __privateGet(this, _internalIdBase) + ++__privateWrapper(this, _messageId)._;
|
|
162
|
+
return new Promise((resolve, reject) => {
|
|
163
|
+
__privateGet(this, _pending).set(id, resolve);
|
|
164
|
+
const request = JSON.stringify({ jsonrpc: "2.0", id, method, params });
|
|
165
|
+
__privateGet(this, _proc).stdin.write(request + "\n");
|
|
166
|
+
setTimeout(() => {
|
|
167
|
+
if (__privateGet(this, _pending).has(id)) {
|
|
168
|
+
__privateGet(this, _pending).delete(id);
|
|
169
|
+
reject(new Error(`MCP call timeout: ${method}`));
|
|
170
|
+
}
|
|
171
|
+
}, 3e4);
|
|
172
|
+
});
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
/** 直接调用 chrome-devtools-mcp 底层工具(内部使用) */
|
|
176
|
+
callChromeDevTool(_0) {
|
|
177
|
+
return __async(this, arguments, function* (name, args = {}) {
|
|
178
|
+
return this.call("tools/call", { name, arguments: args });
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
/** 验证 MCP 是否可用 + 预热 CDP 连接 */
|
|
182
|
+
verify() {
|
|
183
|
+
return __async(this, null, function* () {
|
|
184
|
+
try {
|
|
185
|
+
yield this.callChromeDevTool("list_pages", {});
|
|
186
|
+
return true;
|
|
187
|
+
} catch (e) {
|
|
188
|
+
return false;
|
|
189
|
+
}
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
stop() {
|
|
193
|
+
if (__privateGet(this, _idleTimer)) clearTimeout(__privateGet(this, _idleTimer));
|
|
194
|
+
const err = new Error("MCP server shutting down");
|
|
195
|
+
for (const resolve of __privateGet(this, _pending).values()) {
|
|
196
|
+
resolve({ error: { code: -32e3, message: err.message } });
|
|
197
|
+
}
|
|
198
|
+
__privateGet(this, _pending).clear();
|
|
199
|
+
if (__privateGet(this, _rl)) {
|
|
200
|
+
__privateGet(this, _rl).close();
|
|
201
|
+
__privateSet(this, _rl, null);
|
|
202
|
+
}
|
|
203
|
+
if (__privateGet(this, _proc) && !__privateGet(this, _proc).killed) {
|
|
204
|
+
__privateGet(this, _proc).kill();
|
|
205
|
+
__privateSet(this, _proc, null);
|
|
206
|
+
}
|
|
207
|
+
__privateSet(this, _startPromise, null);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
_proc = new WeakMap();
|
|
211
|
+
_rl = new WeakMap();
|
|
212
|
+
_messageId = new WeakMap();
|
|
213
|
+
_internalIdBase = new WeakMap();
|
|
214
|
+
_pending = new WeakMap();
|
|
215
|
+
_args = new WeakMap();
|
|
216
|
+
_startPromise = new WeakMap();
|
|
217
|
+
_idleTimer = new WeakMap();
|
|
218
|
+
_idleTimeout = new WeakMap();
|
|
219
|
+
_McpProxy_instances = new WeakSet();
|
|
220
|
+
doStart_fn = function() {
|
|
221
|
+
return __async(this, null, function* () {
|
|
222
|
+
var _a;
|
|
223
|
+
log.debug("Starting MCP process", { args: __privateGet(this, _args) });
|
|
224
|
+
try {
|
|
225
|
+
const binPath = resolveChromeDevToolsMcpBin();
|
|
226
|
+
__privateSet(this, _proc, (0, import_node_child_process.spawn)(binPath, __privateGet(this, _args), { stdio: ["pipe", "pipe", "pipe"] }));
|
|
227
|
+
log.debug("Using local chrome-devtools-mcp");
|
|
228
|
+
} catch (e) {
|
|
229
|
+
__privateSet(this, _proc, (0, import_node_child_process.spawn)("npx", ["-y", "chrome-devtools-mcp@latest", ...__privateGet(this, _args)], {
|
|
230
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
231
|
+
}));
|
|
232
|
+
}
|
|
233
|
+
__privateSet(this, _rl, (0, import_node_readline.createInterface)({ input: __privateGet(this, _proc).stdout }));
|
|
234
|
+
__privateGet(this, _rl).on("line", (line) => {
|
|
235
|
+
try {
|
|
236
|
+
const msg = JSON.parse(line);
|
|
237
|
+
log.debug("MCP stdout", {
|
|
238
|
+
id: msg.id,
|
|
239
|
+
method: msg.method,
|
|
240
|
+
hasResult: !!msg.result,
|
|
241
|
+
hasError: !!msg.error
|
|
242
|
+
});
|
|
243
|
+
if (msg.id !== void 0 && __privateGet(this, _pending).has(msg.id)) {
|
|
244
|
+
const resolve = __privateGet(this, _pending).get(msg.id);
|
|
245
|
+
__privateGet(this, _pending).delete(msg.id);
|
|
246
|
+
log.debug("MCP pending resolved", { id: msg.id });
|
|
247
|
+
resolve(msg);
|
|
248
|
+
}
|
|
249
|
+
} catch (e) {
|
|
250
|
+
}
|
|
251
|
+
});
|
|
252
|
+
(_a = __privateGet(this, _proc).stderr) == null ? void 0 : _a.on("data", (d) => {
|
|
253
|
+
const text = d.toString().trim();
|
|
254
|
+
if (text) log.debug("[MCP stderr]", { text: text.substring(0, 200) });
|
|
255
|
+
});
|
|
256
|
+
__privateGet(this, _proc).on("close", (code) => {
|
|
257
|
+
log.debug("MCP process closed", { code });
|
|
258
|
+
for (const resolve of __privateGet(this, _pending).values()) {
|
|
259
|
+
resolve({ error: { code: -32e3, message: `MCP process exited with code ${code}` } });
|
|
260
|
+
}
|
|
261
|
+
__privateGet(this, _pending).clear();
|
|
262
|
+
__privateSet(this, _proc, null);
|
|
263
|
+
__privateSet(this, _rl, null);
|
|
264
|
+
__privateSet(this, _startPromise, null);
|
|
265
|
+
});
|
|
266
|
+
yield this.call("initialize", {
|
|
267
|
+
protocolVersion: "2024-11-05",
|
|
268
|
+
capabilities: {},
|
|
269
|
+
clientInfo: { name: "vite-plugin-opencode", version: "1.0.0" }
|
|
270
|
+
});
|
|
271
|
+
log.debug("MCP proxy ready");
|
|
272
|
+
__privateSet(this, _startPromise, null);
|
|
273
|
+
});
|
|
274
|
+
};
|
|
275
|
+
resetIdleTimer_fn = function() {
|
|
276
|
+
if (__privateGet(this, _idleTimeout) <= 0) return;
|
|
277
|
+
if (__privateGet(this, _idleTimer)) clearTimeout(__privateGet(this, _idleTimer));
|
|
278
|
+
__privateSet(this, _idleTimer, setTimeout(() => {
|
|
279
|
+
log.debug("MCP process idle timeout, stopping");
|
|
280
|
+
this.stop();
|
|
281
|
+
}, __privateGet(this, _idleTimeout)));
|
|
282
|
+
};
|
|
283
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
284
|
+
0 && (module.exports = {
|
|
285
|
+
McpProxy
|
|
286
|
+
});
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export interface McpProxyOptions {
|
|
2
|
+
args?: string[];
|
|
3
|
+
idleTimeout?: number;
|
|
4
|
+
}
|
|
5
|
+
export declare class McpProxy {
|
|
6
|
+
#private;
|
|
7
|
+
readonly sessionId: string;
|
|
8
|
+
readonly accessToken: string;
|
|
9
|
+
constructor(options?: McpProxyOptions);
|
|
10
|
+
get isRunning(): boolean;
|
|
11
|
+
start(): Promise<void>;
|
|
12
|
+
/** 转发原始 JSON-RPC 请求,保留客户端 ID */
|
|
13
|
+
forward(rawRequest: string): Promise<string>;
|
|
14
|
+
/** 调用 MCP 工具 */
|
|
15
|
+
call(method: string, params?: Record<string, unknown>): Promise<unknown>;
|
|
16
|
+
/** 直接调用 chrome-devtools-mcp 底层工具(内部使用) */
|
|
17
|
+
callChromeDevTool(name: string, args?: Record<string, unknown>): Promise<unknown>;
|
|
18
|
+
/** 验证 MCP 是否可用 + 预热 CDP 连接 */
|
|
19
|
+
verify(): Promise<boolean>;
|
|
20
|
+
stop(): void;
|
|
21
|
+
}
|
|
@@ -53,11 +53,12 @@ var import_fs = __toESM(require("fs"));
|
|
|
53
53
|
var import_module = require("module");
|
|
54
54
|
var import_path = __toESM(require("path"));
|
|
55
55
|
var import_url = require("url");
|
|
56
|
+
var import_shared = require("@vite-plugin-opencode-assistant/shared");
|
|
56
57
|
var import_node = require("@vite-plugin-opencode-assistant/shared/node");
|
|
57
58
|
const require2 = (0, import_module.createRequire)(import_path.default.join(process.cwd(), "package.json"));
|
|
58
59
|
const packageDir = resolvePackageDir();
|
|
59
60
|
const log = (0, import_node.createLogger)("OpenCodeWeb");
|
|
60
|
-
function prepareOpenCodeRuntime(cwd) {
|
|
61
|
+
function prepareOpenCodeRuntime(cwd, vitePort, mcpToken) {
|
|
61
62
|
const cacheDir = import_path.default.join(cwd, "node_modules", ".cache", "opencode");
|
|
62
63
|
log.debug("Setting up OpenCode runtime", { cacheDir });
|
|
63
64
|
if (!import_fs.default.existsSync(cacheDir)) {
|
|
@@ -73,9 +74,8 @@ function prepareOpenCodeRuntime(cwd) {
|
|
|
73
74
|
plugin: plugins,
|
|
74
75
|
mcp: {
|
|
75
76
|
"chrome-devtools": {
|
|
76
|
-
type: "
|
|
77
|
-
|
|
78
|
-
enabled: true
|
|
77
|
+
type: "remote",
|
|
78
|
+
url: `http://localhost:${vitePort}${import_shared.MCP_API_PATH}?token=${mcpToken}`
|
|
79
79
|
}
|
|
80
80
|
}
|
|
81
81
|
},
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import type { ResultPromise } from "execa";
|
|
2
2
|
import type { WebOptions } from "@vite-plugin-opencode-assistant/shared";
|
|
3
|
-
export declare function prepareOpenCodeRuntime(cwd: string): string;
|
|
3
|
+
export declare function prepareOpenCodeRuntime(cwd: string, vitePort: number, mcpToken: string): string;
|
|
4
4
|
export declare function startOpenCodeWeb(options: WebOptions): ResultPromise;
|
package/lib/core/service.cjs
CHANGED
|
@@ -95,7 +95,7 @@ class OpenCodeService {
|
|
|
95
95
|
}
|
|
96
96
|
});
|
|
97
97
|
}
|
|
98
|
-
start(corsOrigins, contextApiUrl, logsApiUrl, viteOrigin) {
|
|
98
|
+
start(mcpToken, vitePort, corsOrigins, contextApiUrl, logsApiUrl, viteOrigin, mcp) {
|
|
99
99
|
return __async(this, null, function* () {
|
|
100
100
|
if (this.isStarted && this.webProcess) {
|
|
101
101
|
log.debug("Services already started, skipping");
|
|
@@ -156,7 +156,7 @@ Please install OpenCode first:
|
|
|
156
156
|
this.workspaceRoot = (0, import_system.findGitRoot)(process.cwd());
|
|
157
157
|
log.debug(`Using workspace root: ${this.workspaceRoot}`);
|
|
158
158
|
this.sendTaskUpdate("preparing_runtime");
|
|
159
|
-
const configDir = (0, import_opencode_web.prepareOpenCodeRuntime)(this.workspaceRoot);
|
|
159
|
+
const configDir = (0, import_opencode_web.prepareOpenCodeRuntime)(this.workspaceRoot, vitePort, mcpToken);
|
|
160
160
|
timer.checkpoint("Plugin setup complete");
|
|
161
161
|
this.sendTaskUpdate("starting_web");
|
|
162
162
|
log.debug("Starting OpenCode Web process...", {
|
|
@@ -246,7 +246,8 @@ Please install OpenCode first:
|
|
|
246
246
|
this.sendTaskUpdate("warming_up_chrome");
|
|
247
247
|
let warmupFailed = false;
|
|
248
248
|
try {
|
|
249
|
-
yield
|
|
249
|
+
const ok = yield mcp.verify();
|
|
250
|
+
if (!ok) throw new Error("MCP tools list returned empty");
|
|
250
251
|
timer.checkpoint("Chrome MCP warmup complete");
|
|
251
252
|
} catch (e) {
|
|
252
253
|
log.warn("Chrome MCP warmup failed", { error: e });
|
package/lib/core/service.d.ts
CHANGED
|
@@ -4,6 +4,7 @@ import type { ModelInfo } from "@vite-plugin-opencode-assistant/shared";
|
|
|
4
4
|
import type { OpenCodeOptions, ServiceStartupTask } from "@vite-plugin-opencode-assistant/shared";
|
|
5
5
|
import { ChromeMcpWarmupErrorType } from "@vite-plugin-opencode-assistant/shared";
|
|
6
6
|
import type { OpenCodeAPI } from "./api";
|
|
7
|
+
import type { McpProxy } from "./mcp-proxy";
|
|
7
8
|
export declare class OpenCodeService {
|
|
8
9
|
private config;
|
|
9
10
|
private api;
|
|
@@ -26,7 +27,7 @@ export declare class OpenCodeService {
|
|
|
26
27
|
workspaceRoot: string | null;
|
|
27
28
|
constructor(config: Required<OpenCodeOptions>, api: OpenCodeAPI, sseClients: Set<http.ServerResponse>, onPortAllocated: (port: number) => void, onProxyPortAllocated: (port: number) => void);
|
|
28
29
|
private sendTaskUpdate;
|
|
29
|
-
start(corsOrigins
|
|
30
|
+
start(mcpToken: string, vitePort: number, corsOrigins: string[], contextApiUrl: string, logsApiUrl: string, viteOrigin: string, mcp: McpProxy): Promise<void>;
|
|
30
31
|
getAvailableModels(): Promise<ModelInfo[]>;
|
|
31
32
|
retryWarmupChromeMcp(viteOrigin?: string, selectedModel?: {
|
|
32
33
|
providerID: string;
|
|
@@ -37,12 +37,131 @@ var __async = (__this, __arguments, generator) => {
|
|
|
37
37
|
};
|
|
38
38
|
var context_exports = {};
|
|
39
39
|
__export(context_exports, {
|
|
40
|
+
extractEvalValue: () => extractEvalValue,
|
|
41
|
+
getProjectOrigin: () => getProjectOrigin,
|
|
42
|
+
parseListPages: () => parseListPages,
|
|
43
|
+
resolveChromePageId: () => resolveChromePageId,
|
|
40
44
|
setupContextEndpoint: () => setupContextEndpoint
|
|
41
45
|
});
|
|
42
46
|
module.exports = __toCommonJS(context_exports);
|
|
43
47
|
var import_shared = require("@vite-plugin-opencode-assistant/shared");
|
|
44
48
|
var import_node = require("@vite-plugin-opencode-assistant/shared/node");
|
|
45
49
|
const log = (0, import_node.createLogger)("Endpoints:Context");
|
|
50
|
+
function getProjectOrigin(pageUrl) {
|
|
51
|
+
try {
|
|
52
|
+
return new URL(pageUrl).origin;
|
|
53
|
+
} catch (e) {
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
function parseListPages(text) {
|
|
58
|
+
const pages = [];
|
|
59
|
+
for (const line of text.split("\n")) {
|
|
60
|
+
const match = line.match(/^(\d+):\s*(.+?)\s*\((\S+)\)/);
|
|
61
|
+
if (match) {
|
|
62
|
+
pages.push({
|
|
63
|
+
pageId: parseInt(match[1], 10),
|
|
64
|
+
title: match[2].replace(/\s*\.{3}$/, "").trim(),
|
|
65
|
+
url: match[3],
|
|
66
|
+
selected: /\[selected\]/i.test(line)
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return pages;
|
|
71
|
+
}
|
|
72
|
+
function extractEvalValue(text) {
|
|
73
|
+
if (!text) return void 0;
|
|
74
|
+
const marker = "```json\n";
|
|
75
|
+
const startIdx = text.indexOf(marker);
|
|
76
|
+
if (startIdx < 0) return text.trim();
|
|
77
|
+
const contentStart = startIdx + marker.length;
|
|
78
|
+
const endIdx = text.indexOf("\n```", contentStart);
|
|
79
|
+
const jsonStr = endIdx < 0 ? text.substring(contentStart) : text.substring(contentStart, endIdx);
|
|
80
|
+
try {
|
|
81
|
+
return JSON.parse(jsonStr.trim());
|
|
82
|
+
} catch (e) {
|
|
83
|
+
return jsonStr.trim();
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
function resolveChromePageId(mcp, url, title, sessionId, pages, chromeSelectedPageId) {
|
|
87
|
+
return __async(this, null, function* () {
|
|
88
|
+
var _a, _b, _c, _d, _e, _f, _g;
|
|
89
|
+
if (!mcp || !mcp.isRunning) {
|
|
90
|
+
const reason = !mcp ? "MCP \u6A21\u5757\u672A\u521D\u59CB\u5316" : "Chrome DevTools MCP \u8FDB\u7A0B\u672A\u542F\u52A8";
|
|
91
|
+
log.debug(`resolveChromePageId: ${reason}`);
|
|
92
|
+
return { ok: false, error: reason };
|
|
93
|
+
}
|
|
94
|
+
if (!url) {
|
|
95
|
+
return { ok: false, error: "\u9875\u9762 URL \u4E3A\u7A7A\uFF0C\u5C1A\u672A\u6536\u5230\u4E0A\u4E0B\u6587\u4FE1\u606F" };
|
|
96
|
+
}
|
|
97
|
+
try {
|
|
98
|
+
if (!pages) {
|
|
99
|
+
const listResult = yield mcp.callChromeDevTool("list_pages", {});
|
|
100
|
+
const text = (_c = (_b = (_a = listResult == null ? void 0 : listResult.result) == null ? void 0 : _a.content) == null ? void 0 : _b[0]) == null ? void 0 : _c.text;
|
|
101
|
+
if (!text) {
|
|
102
|
+
return { ok: false, error: "\u65E0\u6CD5\u83B7\u53D6 Chrome \u9875\u9762\u5217\u8868\uFF0C\u8BF7\u786E\u8BA4\u5DF2\u6253\u5F00\u76EE\u6807\u9875\u9762" };
|
|
103
|
+
}
|
|
104
|
+
const allPages = parseListPages(text);
|
|
105
|
+
chromeSelectedPageId = (_d = allPages.find((p) => p.selected)) == null ? void 0 : _d.pageId;
|
|
106
|
+
const projectOrigin = getProjectOrigin(url);
|
|
107
|
+
pages = projectOrigin ? allPages.filter((p) => p.url.startsWith(projectOrigin)) : allPages;
|
|
108
|
+
}
|
|
109
|
+
log.debug("resolveChromePageId: list_pages result", {
|
|
110
|
+
pages: pages.map((p) => ({ id: p.pageId, url: p.url, title: p.title.substring(0, 40) })),
|
|
111
|
+
target: { url, title }
|
|
112
|
+
});
|
|
113
|
+
const normalizeUrl = (u) => u.replace(/\/$/, "");
|
|
114
|
+
const targetUrl = normalizeUrl(url);
|
|
115
|
+
if (sessionId) {
|
|
116
|
+
let matchedPageId = null;
|
|
117
|
+
for (const page of pages) {
|
|
118
|
+
yield mcp.callChromeDevTool("select_page", { pageId: page.pageId, bringToFront: false });
|
|
119
|
+
const evalResult = yield mcp.callChromeDevTool("evaluate_script", {
|
|
120
|
+
function: "() => sessionStorage.getItem('_opencode_pk')"
|
|
121
|
+
});
|
|
122
|
+
const rawText = (_g = (_f = (_e = evalResult == null ? void 0 : evalResult.result) == null ? void 0 : _e.content) == null ? void 0 : _f[0]) == null ? void 0 : _g.text;
|
|
123
|
+
const extracted = extractEvalValue(rawText);
|
|
124
|
+
if (extracted === sessionId) {
|
|
125
|
+
matchedPageId = page.pageId;
|
|
126
|
+
break;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
if (chromeSelectedPageId != null && chromeSelectedPageId !== matchedPageId) {
|
|
130
|
+
yield mcp.callChromeDevTool("select_page", {
|
|
131
|
+
pageId: chromeSelectedPageId,
|
|
132
|
+
bringToFront: false
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
if (matchedPageId != null) {
|
|
136
|
+
return { ok: true, pageId: matchedPageId };
|
|
137
|
+
}
|
|
138
|
+
return {
|
|
139
|
+
ok: false,
|
|
140
|
+
error: `\u672A\u80FD\u901A\u8FC7\u4F1A\u8BDD\u6807\u8BC6\u627E\u5230\u76EE\u6807\u9875\u9762\uFF0C\u8BF7\u786E\u8BA4\u76EE\u6807\u9875\u9762\u5DF2\u6253\u5F00`
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
const matches = pages.filter((p) => normalizeUrl(p.url) === targetUrl);
|
|
144
|
+
if (matches.length === 0) {
|
|
145
|
+
return {
|
|
146
|
+
ok: false,
|
|
147
|
+
error: `Chrome \u4E2D\u672A\u627E\u5230\u5339\u914D\u7684\u9875\u9762 (${targetUrl})\uFF0C\u8BF7\u786E\u8BA4\u672C\u5730\u5F00\u53D1\u9875\u9762\u5DF2\u5728 Chrome DevTools \u4E2D\u6253\u5F00`
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
if (matches.length === 1) {
|
|
151
|
+
log.debug("resolveChromePageId: URL matched", { pageId: matches[0].pageId });
|
|
152
|
+
return { ok: true, pageId: matches[0].pageId };
|
|
153
|
+
}
|
|
154
|
+
return {
|
|
155
|
+
ok: false,
|
|
156
|
+
error: `\u5B58\u5728 ${matches.length} \u4E2A\u540C URL \u7684\u9875\u9762\uFF0C\u7F3A\u5C11\u9875\u9762\u6807\u8BC6\u65E0\u6CD5\u533A\u5206\uFF0C\u8BF7\u5237\u65B0\u9875\u9762\u540E\u91CD\u8BD5`
|
|
157
|
+
};
|
|
158
|
+
} catch (err) {
|
|
159
|
+
const msg = `MCP \u8C03\u7528\u5931\u8D25: ${err.message}`;
|
|
160
|
+
log.debug("Failed to resolve pageId via MCP", { error: err.message });
|
|
161
|
+
return { ok: false, error: msg };
|
|
162
|
+
}
|
|
163
|
+
});
|
|
164
|
+
}
|
|
46
165
|
function setupContextEndpoint(server, ctx) {
|
|
47
166
|
server.middlewares.use(import_shared.CONTEXT_API_PATH, (req, res) => __async(null, null, function* () {
|
|
48
167
|
const reqCtx = new import_node.RequestContext(req.method || "GET", import_shared.CONTEXT_API_PATH);
|
|
@@ -58,7 +177,6 @@ function setupContextEndpoint(server, ctx) {
|
|
|
58
177
|
}
|
|
59
178
|
if (req.method === "GET") {
|
|
60
179
|
const pc = ctx.getPageContext();
|
|
61
|
-
log.debug(`[Context] GET \u2192 url=${pc.url} title=${pc.title} tabId=${pc.tabId}`);
|
|
62
180
|
res.writeHead(200);
|
|
63
181
|
res.end(JSON.stringify(pc));
|
|
64
182
|
reqCtx.end(200);
|
|
@@ -89,7 +207,11 @@ function setupContextEndpoint(server, ctx) {
|
|
|
89
207
|
}
|
|
90
208
|
if (req.method === "POST") {
|
|
91
209
|
let body = "";
|
|
92
|
-
req.on("data", (chunk) => body += chunk);
|
|
210
|
+
req.on("data", (chunk) => body += chunk.toString());
|
|
211
|
+
req.on("error", () => {
|
|
212
|
+
res.writeHead(400);
|
|
213
|
+
res.end(JSON.stringify({ error: "Request error" }));
|
|
214
|
+
});
|
|
93
215
|
req.on("end", () => {
|
|
94
216
|
var _a, _b, _c;
|
|
95
217
|
try {
|
|
@@ -99,6 +221,7 @@ function setupContextEndpoint(server, ctx) {
|
|
|
99
221
|
const newCtx = {
|
|
100
222
|
url: data.url || "",
|
|
101
223
|
title: data.title || "",
|
|
224
|
+
sessionId: data.sessionId,
|
|
102
225
|
tabId: (_a = data.tabId) != null ? _a : existing.tabId,
|
|
103
226
|
tabIndex: (_b = data.tabIndex) != null ? _b : existing.tabIndex,
|
|
104
227
|
selectedElements: data.selectedElements || []
|
|
@@ -111,6 +234,7 @@ function setupContextEndpoint(server, ctx) {
|
|
|
111
234
|
tabId,
|
|
112
235
|
url: newCtx.url,
|
|
113
236
|
title: newCtx.title,
|
|
237
|
+
sessionId: newCtx.sessionId,
|
|
114
238
|
selectedElementsCount: ((_c = newCtx.selectedElements) == null ? void 0 : _c.length) || 0
|
|
115
239
|
});
|
|
116
240
|
if (newCtx.selectedElements && newCtx.selectedElements.length > 0) {
|
|
@@ -144,5 +268,9 @@ function setupContextEndpoint(server, ctx) {
|
|
|
144
268
|
}
|
|
145
269
|
// Annotate the CommonJS export names for ESM import in node:
|
|
146
270
|
0 && (module.exports = {
|
|
271
|
+
extractEvalValue,
|
|
272
|
+
getProjectOrigin,
|
|
273
|
+
parseListPages,
|
|
274
|
+
resolveChromePageId,
|
|
147
275
|
setupContextEndpoint
|
|
148
276
|
});
|
|
@@ -1,3 +1,38 @@
|
|
|
1
1
|
import type { ViteDevServer } from "vite";
|
|
2
|
+
import type { McpProxy } from "../core/mcp-proxy";
|
|
2
3
|
import type { EndpointContext } from "./types";
|
|
4
|
+
/** 从页面 URL 提取项目 origin(protocol + host + port) */
|
|
5
|
+
export declare function getProjectOrigin(pageUrl: string): string | null;
|
|
6
|
+
export interface PageInfo {
|
|
7
|
+
pageId: number;
|
|
8
|
+
url: string;
|
|
9
|
+
title: string;
|
|
10
|
+
/** Chrome DevTools 当前选中的页面 */
|
|
11
|
+
selected: boolean;
|
|
12
|
+
}
|
|
13
|
+
/** 解析 list_pages 返回的文本。格式:"56: Title (URL) [selected]" */
|
|
14
|
+
export declare function parseListPages(text: string): PageInfo[];
|
|
15
|
+
/** 从 evaluate_script 返回的格式化文本中提取 JSON 值 */
|
|
16
|
+
export declare function extractEvalValue(text: string | undefined): string | undefined;
|
|
17
|
+
type PageIdResult = {
|
|
18
|
+
ok: true;
|
|
19
|
+
pageId: number;
|
|
20
|
+
} | {
|
|
21
|
+
ok: false;
|
|
22
|
+
error: string;
|
|
23
|
+
};
|
|
24
|
+
/**
|
|
25
|
+
* 通过 MCP 解析当前页面对应的 Chrome DevTools pageId
|
|
26
|
+
*
|
|
27
|
+
* 策略:
|
|
28
|
+
* 1. list_pages → 解析每行的 "pageId: title (URL)" 格式
|
|
29
|
+
* 2. 优先用 sessionId 匹配(跨导航可靠,遍历所有页面 evaluate_script)
|
|
30
|
+
* 3. 无 sessionId 时降级为 URL 精确匹配
|
|
31
|
+
*
|
|
32
|
+
* 失败时返回具体原因,由调用方透传给 Agent。
|
|
33
|
+
*/
|
|
34
|
+
export declare function resolveChromePageId(mcp: McpProxy | undefined, url: string, title: string, sessionId?: string, pages?: PageInfo[],
|
|
35
|
+
/** Chrome 当前选中的 pageId(调用方传入,避免过滤后丢失非项目页面信息) */
|
|
36
|
+
chromeSelectedPageId?: number): Promise<PageIdResult>;
|
|
3
37
|
export declare function setupContextEndpoint(server: ViteDevServer, ctx: EndpointContext): void;
|
|
38
|
+
export {};
|
package/lib/endpoints/index.cjs
CHANGED
|
@@ -19,6 +19,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
19
19
|
var endpoints_exports = {};
|
|
20
20
|
__export(endpoints_exports, {
|
|
21
21
|
LOGS_API_PATH: () => import_shared.LOGS_API_PATH,
|
|
22
|
+
MCP_API_PATH: () => import_mcp.MCP_API_PATH,
|
|
22
23
|
setupMiddlewares: () => setupMiddlewares
|
|
23
24
|
});
|
|
24
25
|
module.exports = __toCommonJS(endpoints_exports);
|
|
@@ -29,9 +30,10 @@ var import_sse = require("./sse.cjs");
|
|
|
29
30
|
var import_sessions = require("./sessions.cjs");
|
|
30
31
|
var import_warmup = require("./warmup.cjs");
|
|
31
32
|
var import_logs = require("./logs.cjs");
|
|
33
|
+
var import_mcp = require("./mcp.cjs");
|
|
32
34
|
var import_shared = require("@vite-plugin-opencode-assistant/shared");
|
|
33
35
|
__reExport(endpoints_exports, require("./types.cjs"), module.exports);
|
|
34
|
-
function setupMiddlewares(server, ctx) {
|
|
36
|
+
function setupMiddlewares(server, ctx, mcp) {
|
|
35
37
|
(0, import_widget.setupWidgetEndpoints)(server, ctx);
|
|
36
38
|
(0, import_context.setupContextEndpoint)(server, ctx);
|
|
37
39
|
(0, import_start.setupStartEndpoint)(server, ctx);
|
|
@@ -39,10 +41,12 @@ function setupMiddlewares(server, ctx) {
|
|
|
39
41
|
(0, import_sessions.setupSessionsEndpoint)(server, ctx);
|
|
40
42
|
(0, import_warmup.setupWarmupEndpoint)(server, ctx);
|
|
41
43
|
(0, import_logs.setupLogsEndpoint)(server);
|
|
44
|
+
if (mcp) (0, import_mcp.setupMcpEndpoint)(server, mcp, () => ctx.getPageContext());
|
|
42
45
|
}
|
|
43
46
|
// Annotate the CommonJS export names for ESM import in node:
|
|
44
47
|
0 && (module.exports = {
|
|
45
48
|
LOGS_API_PATH,
|
|
49
|
+
MCP_API_PATH,
|
|
46
50
|
setupMiddlewares,
|
|
47
51
|
...require("./types.cjs")
|
|
48
52
|
});
|
package/lib/endpoints/index.d.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import type { ViteDevServer } from "vite";
|
|
2
2
|
import type { EndpointContext } from "./types";
|
|
3
|
+
import { MCP_API_PATH } from "./mcp";
|
|
4
|
+
import type { McpProxy } from "../core/mcp-proxy";
|
|
3
5
|
import { LOGS_API_PATH } from "@vite-plugin-opencode-assistant/shared";
|
|
4
6
|
export * from "./types";
|
|
5
|
-
export { LOGS_API_PATH };
|
|
6
|
-
export declare function setupMiddlewares(server: ViteDevServer, ctx: EndpointContext): void;
|
|
7
|
+
export { LOGS_API_PATH, MCP_API_PATH };
|
|
8
|
+
export declare function setupMiddlewares(server: ViteDevServer, ctx: EndpointContext, mcp?: McpProxy): void;
|