vite-plugin-opencode-assistant 1.0.14 → 1.0.16
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/client/App.vue.d.ts +6 -0
- package/es/client/App.vue.js +300 -0
- package/es/client/components/ChromeWarmupError-sfc.css +1 -0
- package/es/client/components/ChromeWarmupError.vue.d.ts +11 -0
- package/es/client/components/ChromeWarmupError.vue.js +196 -0
- package/es/client/components/LoadingContent.vue.d.ts +5 -0
- package/es/client/components/LoadingContent.vue.js +39 -0
- package/es/client/composables/useContext.d.ts +8 -0
- package/es/client/composables/useContext.js +63 -0
- package/es/client/composables/useHotkey.d.ts +12 -0
- package/es/client/composables/useHotkey.js +41 -0
- package/es/client/composables/useSSE.d.ts +20 -0
- package/es/client/composables/useSSE.js +61 -0
- package/es/client/composables/useSelectedElements.d.ts +19 -0
- package/es/client/composables/useSelectedElements.js +43 -0
- package/es/client/composables/useServiceStatus.d.ts +13 -0
- package/es/client/composables/useServiceStatus.js +53 -0
- package/es/client/composables/useSessions.d.ts +26 -0
- package/es/client/composables/useSessions.js +127 -0
- package/es/client/composables/useTheme.d.ts +12 -0
- package/es/client/composables/useTheme.js +42 -0
- package/es/client/index.d.ts +1 -1
- package/es/client/index.js +5 -675
- package/es/client/styles.css +1 -0
- package/es/core/api.d.ts +18 -6
- package/es/core/api.js +324 -69
- package/es/core/proxy-server.d.ts +5 -1
- package/es/core/proxy-server.js +201 -70
- package/es/core/service.d.ts +9 -2
- package/es/core/service.js +80 -44
- package/es/endpoints/index.js +1 -1
- package/es/endpoints/sse.js +0 -3
- package/es/endpoints/start.d.ts +1 -2
- package/es/endpoints/start.js +2 -2
- package/es/endpoints/types.d.ts +5 -2
- package/es/endpoints/warmup.js +15 -3
- package/es/index.js +8 -12
- package/es/utils/system.d.ts +3 -1
- package/es/utils/system.js +39 -10
- package/lib/client/App.vue.d.ts +6 -0
- package/lib/client/App.vue.js +329 -0
- package/lib/client/components/ChromeWarmupError-sfc.css +1 -0
- package/lib/client/components/ChromeWarmupError.vue.d.ts +11 -0
- package/lib/client/components/ChromeWarmupError.vue.js +215 -0
- package/lib/client/components/LoadingContent.vue.d.ts +5 -0
- package/lib/client/components/LoadingContent.vue.js +58 -0
- package/lib/client/composables/useContext.d.ts +8 -0
- package/lib/client/composables/useContext.js +86 -0
- package/lib/client/composables/useHotkey.d.ts +12 -0
- package/lib/client/composables/useHotkey.js +66 -0
- package/lib/client/composables/useSSE.d.ts +20 -0
- package/lib/client/composables/useSSE.js +84 -0
- package/lib/client/composables/useSelectedElements.d.ts +19 -0
- package/lib/client/composables/useSelectedElements.js +66 -0
- package/lib/client/composables/useServiceStatus.d.ts +13 -0
- package/lib/client/composables/useServiceStatus.js +76 -0
- package/lib/client/composables/useSessions.d.ts +26 -0
- package/lib/client/composables/useSessions.js +148 -0
- package/lib/client/composables/useTheme.d.ts +12 -0
- package/lib/client/composables/useTheme.js +65 -0
- package/lib/client/index.d.ts +1 -1
- package/lib/client/index.js +22 -667
- package/lib/client/styles.css +1 -0
- package/lib/client.js +2988 -2973
- package/lib/core/api.d.ts +18 -6
- package/lib/core/api.js +321 -74
- package/lib/core/proxy-server.d.ts +5 -1
- package/lib/core/proxy-server.js +201 -70
- package/lib/core/service.d.ts +9 -2
- package/lib/core/service.js +76 -43
- package/lib/endpoints/index.js +1 -1
- package/lib/endpoints/sse.js +0 -3
- package/lib/endpoints/start.d.ts +1 -2
- package/lib/endpoints/start.js +2 -2
- package/lib/endpoints/types.d.ts +5 -2
- package/lib/endpoints/warmup.js +15 -3
- package/lib/index.js +8 -12
- package/lib/style.css +1 -1
- package/lib/utils/system.d.ts +3 -1
- package/lib/utils/system.js +39 -5
- package/package.json +4 -4
package/lib/core/proxy-server.js
CHANGED
|
@@ -130,90 +130,221 @@ function generateBridgeScript(options) {
|
|
|
130
130
|
}
|
|
131
131
|
});
|
|
132
132
|
|
|
133
|
+
// === \u601D\u8003\u72B6\u6001\u76D1\u542C (\u5B8C\u5168\u590D\u523B OpenCode Web \u5B9E\u73B0) ===
|
|
134
|
+
// OpenCode Web \u6838\u5FC3\u903B\u8F91:
|
|
135
|
+
// working = !!pending() || sessionStatus().type !== "idle"
|
|
136
|
+
// pending = \u6700\u540E\u4E00\u6761\u672A\u5B8C\u6210\u7684 assistant \u6D88\u606F (time.completed \u4E0D\u662F\u6570\u5B57)
|
|
137
|
+
// sessionStatus = sync.data.session_status[sessionID]
|
|
138
|
+
|
|
139
|
+
let eventSource = null;
|
|
140
|
+
const sessionStatus = {};
|
|
141
|
+
const pendingMessages = {};
|
|
142
|
+
|
|
143
|
+
function getCurrentSessionID() {
|
|
144
|
+
const match = window.location.pathname.match(/\\/session\\/([^\\/]+)/);
|
|
145
|
+
return match ? match[1] : null;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function isPending(message) {
|
|
149
|
+
return message.role === 'assistant' && typeof message.time?.completed !== 'number';
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function updateThinkingState(sessionID) {
|
|
153
|
+
const status = sessionStatus[sessionID];
|
|
154
|
+
const pending = pendingMessages[sessionID];
|
|
155
|
+
|
|
156
|
+
const isThinking = !!pending || (status && status.type !== 'idle');
|
|
157
|
+
|
|
158
|
+
if (window.parent !== window) {
|
|
159
|
+
window.parent.postMessage({
|
|
160
|
+
type: 'OPENCODE_THINKING_STATE',
|
|
161
|
+
thinking: isThinking,
|
|
162
|
+
sessionID: sessionID,
|
|
163
|
+
statusType: status?.type || 'idle',
|
|
164
|
+
hasPending: !!pending
|
|
165
|
+
}, '*');
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function handleEvent(payload) {
|
|
170
|
+
const type = payload.type;
|
|
171
|
+
const props = payload.properties;
|
|
172
|
+
|
|
173
|
+
switch (type) {
|
|
174
|
+
case 'session.status': {
|
|
175
|
+
const sessionID = props.sessionID;
|
|
176
|
+
sessionStatus[sessionID] = props.status;
|
|
177
|
+
updateThinkingState(sessionID);
|
|
178
|
+
break;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
case 'message.updated': {
|
|
182
|
+
const info = props.info;
|
|
183
|
+
if (!info || !info.sessionID) break;
|
|
184
|
+
const sessionID = info.sessionID;
|
|
185
|
+
|
|
186
|
+
if (info.role === 'assistant') {
|
|
187
|
+
if (isPending(info)) {
|
|
188
|
+
pendingMessages[sessionID] = info;
|
|
189
|
+
} else {
|
|
190
|
+
delete pendingMessages[sessionID];
|
|
191
|
+
}
|
|
192
|
+
updateThinkingState(sessionID);
|
|
193
|
+
}
|
|
194
|
+
break;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
case 'message.part.delta': {
|
|
198
|
+
const sessionID = props.sessionID;
|
|
199
|
+
if (sessionID && !pendingMessages[sessionID]) {
|
|
200
|
+
pendingMessages[sessionID] = { role: 'assistant', time: {} };
|
|
201
|
+
updateThinkingState(sessionID);
|
|
202
|
+
}
|
|
203
|
+
break;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function setupThinkingListener() {
|
|
209
|
+
if (eventSource) {
|
|
210
|
+
eventSource.close();
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
eventSource = new EventSource('/global/event');
|
|
214
|
+
|
|
215
|
+
eventSource.onmessage = function(event) {
|
|
216
|
+
try {
|
|
217
|
+
const data = JSON.parse(event.data);
|
|
218
|
+
const payload = data.payload;
|
|
219
|
+
if (!payload) return;
|
|
220
|
+
handleEvent(payload);
|
|
221
|
+
} catch (e) {
|
|
222
|
+
// ignore parse errors
|
|
223
|
+
}
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
eventSource.onerror = function(err) {
|
|
227
|
+
console.warn('[OpenCode Bridge] SSE connection error, retrying in 3s...');
|
|
228
|
+
eventSource.close();
|
|
229
|
+
setTimeout(setupThinkingListener, 3000);
|
|
230
|
+
};
|
|
231
|
+
|
|
232
|
+
console.log('[OpenCode Bridge] SSE listener setup complete');
|
|
233
|
+
}
|
|
234
|
+
|
|
133
235
|
// === \u5C31\u7EEA\u901A\u77E5 ===
|
|
134
|
-
|
|
236
|
+
function init() {
|
|
135
237
|
if (window.parent !== window) {
|
|
136
238
|
window.parent.postMessage({ type: "OPENCODE_READY" }, "*");
|
|
137
239
|
}
|
|
240
|
+
setupThinkingListener();
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
if (document.readyState === 'loading') {
|
|
244
|
+
document.addEventListener('DOMContentLoaded', init);
|
|
245
|
+
} else {
|
|
246
|
+
init();
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
window.addEventListener('beforeunload', function() {
|
|
250
|
+
if (eventSource) {
|
|
251
|
+
eventSource.close();
|
|
252
|
+
eventSource = null;
|
|
253
|
+
}
|
|
138
254
|
});
|
|
139
255
|
})();
|
|
140
256
|
`;
|
|
141
257
|
}
|
|
142
258
|
function startProxyServer(targetUrl, port, options = {}) {
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
res.end(body);
|
|
154
|
-
return;
|
|
155
|
-
}
|
|
156
|
-
const options2 = {
|
|
157
|
-
hostname: target.hostname,
|
|
158
|
-
port: target.port,
|
|
159
|
-
path: req.url,
|
|
160
|
-
method: req.method,
|
|
161
|
-
headers: __spreadProps(__spreadValues({}, req.headers), {
|
|
162
|
-
host: target.host,
|
|
163
|
-
// Don't accept compressed responses so we can modify HTML
|
|
164
|
-
"accept-encoding": "identity"
|
|
165
|
-
})
|
|
166
|
-
};
|
|
167
|
-
const proxyReq = import_http.default.request(options2, (proxyRes) => {
|
|
168
|
-
var _a;
|
|
169
|
-
const rawContentType = proxyRes.headers["content-type"];
|
|
170
|
-
const contentType = Array.isArray(rawContentType) ? (_a = rawContentType[0]) != null ? _a : "" : rawContentType != null ? rawContentType : "";
|
|
171
|
-
if (contentType.includes("text/html")) {
|
|
172
|
-
const chunks = [];
|
|
173
|
-
proxyRes.on("data", (chunk) => {
|
|
174
|
-
chunks.push(chunk);
|
|
175
|
-
});
|
|
176
|
-
proxyRes.on("end", () => {
|
|
177
|
-
let body = Buffer.concat(chunks).toString("utf-8");
|
|
178
|
-
if (body.match(/<\/head>/i)) {
|
|
179
|
-
body = body.replace(
|
|
180
|
-
/<\/head>/i,
|
|
181
|
-
'<script src="/__opencode_bridge__.js"></script></head>'
|
|
182
|
-
);
|
|
183
|
-
} else if (body.match(/<\/body>/i)) {
|
|
184
|
-
body = body.replace(
|
|
185
|
-
/<\/body>/i,
|
|
186
|
-
'<script src="/__opencode_bridge__.js"></script></body>'
|
|
187
|
-
);
|
|
188
|
-
} else {
|
|
189
|
-
body += '<script src="/__opencode_bridge__.js"></script>';
|
|
190
|
-
}
|
|
191
|
-
const headers = {};
|
|
192
|
-
for (const [key, value] of Object.entries(proxyRes.headers)) {
|
|
193
|
-
if (value !== void 0 && key !== "content-encoding" && key !== "transfer-encoding" && key !== "content-length") {
|
|
194
|
-
headers[key] = value;
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
headers["content-length"] = Buffer.byteLength(body);
|
|
198
|
-
res.writeHead(proxyRes.statusCode || 200, headers);
|
|
199
|
-
res.end(body);
|
|
259
|
+
return new Promise((resolve, reject) => {
|
|
260
|
+
const target = new URL(targetUrl);
|
|
261
|
+
const bridgeScript = generateBridgeScript(options);
|
|
262
|
+
const server = import_http.default.createServer((req, res) => {
|
|
263
|
+
if (req.url === "/__opencode_bridge__.js") {
|
|
264
|
+
const body = bridgeScript;
|
|
265
|
+
res.writeHead(200, {
|
|
266
|
+
"content-type": "application/javascript; charset=utf-8",
|
|
267
|
+
"cache-control": "no-store",
|
|
268
|
+
"content-length": Buffer.byteLength(body)
|
|
200
269
|
});
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
proxyRes.pipe(res);
|
|
270
|
+
res.end(body);
|
|
271
|
+
return;
|
|
204
272
|
}
|
|
273
|
+
const requestOptions = {
|
|
274
|
+
hostname: target.hostname,
|
|
275
|
+
port: target.port,
|
|
276
|
+
path: req.url,
|
|
277
|
+
method: req.method,
|
|
278
|
+
headers: __spreadProps(__spreadValues({}, req.headers), {
|
|
279
|
+
host: target.host,
|
|
280
|
+
"accept-encoding": "identity"
|
|
281
|
+
}),
|
|
282
|
+
timeout: 0
|
|
283
|
+
};
|
|
284
|
+
const proxyReq = import_http.default.request(requestOptions, (proxyRes) => {
|
|
285
|
+
var _a;
|
|
286
|
+
const rawContentType = proxyRes.headers["content-type"];
|
|
287
|
+
const contentType = Array.isArray(rawContentType) ? (_a = rawContentType[0]) != null ? _a : "" : rawContentType != null ? rawContentType : "";
|
|
288
|
+
if (contentType.includes("text/html")) {
|
|
289
|
+
const chunks = [];
|
|
290
|
+
proxyRes.on("data", (chunk) => {
|
|
291
|
+
chunks.push(chunk);
|
|
292
|
+
});
|
|
293
|
+
proxyRes.on("end", () => {
|
|
294
|
+
let body = Buffer.concat(chunks).toString("utf-8");
|
|
295
|
+
if (body.match(/<\/head>/i)) {
|
|
296
|
+
body = body.replace(
|
|
297
|
+
/<\/head>/i,
|
|
298
|
+
'<script src="/__opencode_bridge__.js"></script></head>'
|
|
299
|
+
);
|
|
300
|
+
} else if (body.match(/<\/body>/i)) {
|
|
301
|
+
body = body.replace(
|
|
302
|
+
/<\/body>/i,
|
|
303
|
+
'<script src="/__opencode_bridge__.js"></script></body>'
|
|
304
|
+
);
|
|
305
|
+
} else {
|
|
306
|
+
body += '<script src="/__opencode_bridge__.js"></script>';
|
|
307
|
+
}
|
|
308
|
+
const headers = {};
|
|
309
|
+
for (const [key, value] of Object.entries(proxyRes.headers)) {
|
|
310
|
+
if (value !== void 0 && key !== "content-encoding" && key !== "transfer-encoding" && key !== "content-length") {
|
|
311
|
+
headers[key] = value;
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
headers["content-length"] = Buffer.byteLength(body);
|
|
315
|
+
res.writeHead(proxyRes.statusCode || 200, headers);
|
|
316
|
+
res.end(body);
|
|
317
|
+
});
|
|
318
|
+
} else {
|
|
319
|
+
res.writeHead(proxyRes.statusCode || 200, proxyRes.headers);
|
|
320
|
+
proxyRes.pipe(res);
|
|
321
|
+
}
|
|
322
|
+
});
|
|
323
|
+
proxyReq.on("error", (err) => {
|
|
324
|
+
log.error("Proxy error", { error: err.message, url: req.url });
|
|
325
|
+
res.writeHead(502);
|
|
326
|
+
res.end("Proxy error");
|
|
327
|
+
});
|
|
328
|
+
proxyReq.on("socket", (socket) => {
|
|
329
|
+
socket.setTimeout(0);
|
|
330
|
+
});
|
|
331
|
+
req.on("socket", (socket) => {
|
|
332
|
+
socket.setTimeout(0);
|
|
333
|
+
});
|
|
334
|
+
req.pipe(proxyReq);
|
|
205
335
|
});
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
336
|
+
server.on("error", (err) => {
|
|
337
|
+
reject(err);
|
|
338
|
+
});
|
|
339
|
+
server.timeout = 0;
|
|
340
|
+
server.keepAliveTimeout = 0;
|
|
341
|
+
server.listen(port, () => {
|
|
342
|
+
const address = server.address();
|
|
343
|
+
const actualPort = typeof address === "object" && address ? address.port : port;
|
|
344
|
+
log.info(`Proxy server started on port ${actualPort} -> ${targetUrl}`);
|
|
345
|
+
resolve({ server, actualPort });
|
|
210
346
|
});
|
|
211
|
-
req.pipe(proxyReq);
|
|
212
|
-
});
|
|
213
|
-
server.listen(port, () => {
|
|
214
|
-
log.info(`Proxy server started on port ${port} -> ${targetUrl}`);
|
|
215
347
|
});
|
|
216
|
-
return server;
|
|
217
348
|
}
|
|
218
349
|
// Annotate the CommonJS export names for ESM import in node:
|
|
219
350
|
0 && (module.exports = {
|
package/lib/core/service.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { ResultPromise } from "execa";
|
|
2
2
|
import type http from "http";
|
|
3
3
|
import type { OpenCodeOptions, ServiceStartupTask } from "@vite-plugin-opencode-assistant/shared";
|
|
4
|
+
import { ChromeMcpWarmupErrorType } from "@vite-plugin-opencode-assistant/shared";
|
|
4
5
|
import type { OpenCodeAPI } from "./api.js";
|
|
5
6
|
export declare class OpenCodeService {
|
|
6
7
|
private config;
|
|
@@ -13,16 +14,22 @@ export declare class OpenCodeService {
|
|
|
13
14
|
actualProxyPort: number;
|
|
14
15
|
isStarted: boolean;
|
|
15
16
|
private startPromise;
|
|
16
|
-
sessionUrl: string | null;
|
|
17
17
|
private proxyServer;
|
|
18
18
|
chromeMcpWarmupFailed: boolean;
|
|
19
|
+
chromeMcpWarmupErrorType: ChromeMcpWarmupErrorType | null;
|
|
20
|
+
chromeMcpWarmupErrorMessage: string | null;
|
|
19
21
|
currentTask: {
|
|
20
22
|
task: ServiceStartupTask;
|
|
21
23
|
data?: Record<string, unknown>;
|
|
22
24
|
} | null;
|
|
25
|
+
workspaceRoot: string | null;
|
|
23
26
|
constructor(config: Required<OpenCodeOptions>, api: OpenCodeAPI, sseClients: Set<http.ServerResponse>, onPortAllocated: (port: number) => void, onProxyPortAllocated: (port: number) => void);
|
|
24
27
|
private sendTaskUpdate;
|
|
25
28
|
start(corsOrigins?: string[], contextApiUrl?: string, viteOrigin?: string): Promise<void>;
|
|
26
|
-
retryWarmupChromeMcp(viteOrigin?: string): Promise<
|
|
29
|
+
retryWarmupChromeMcp(viteOrigin?: string): Promise<{
|
|
30
|
+
success: boolean;
|
|
31
|
+
errorType?: string;
|
|
32
|
+
errorMessage?: string;
|
|
33
|
+
}>;
|
|
27
34
|
stop(): Promise<void>;
|
|
28
35
|
}
|
package/lib/core/service.js
CHANGED
|
@@ -72,10 +72,12 @@ class OpenCodeService {
|
|
|
72
72
|
__publicField(this, "actualProxyPort");
|
|
73
73
|
__publicField(this, "isStarted", false);
|
|
74
74
|
__publicField(this, "startPromise", null);
|
|
75
|
-
__publicField(this, "sessionUrl", null);
|
|
76
75
|
__publicField(this, "proxyServer", null);
|
|
77
76
|
__publicField(this, "chromeMcpWarmupFailed", false);
|
|
77
|
+
__publicField(this, "chromeMcpWarmupErrorType", null);
|
|
78
|
+
__publicField(this, "chromeMcpWarmupErrorMessage", null);
|
|
78
79
|
__publicField(this, "currentTask", null);
|
|
80
|
+
__publicField(this, "workspaceRoot", null);
|
|
79
81
|
var _a;
|
|
80
82
|
this.actualWebPort = config.webPort;
|
|
81
83
|
this.actualProxyPort = (_a = config.proxyPort) != null ? _a : import_shared.DEFAULT_PROXY_PORT;
|
|
@@ -103,7 +105,7 @@ class OpenCodeService {
|
|
|
103
105
|
return this.startPromise;
|
|
104
106
|
}
|
|
105
107
|
this.startPromise = (() => __async(this, null, function* () {
|
|
106
|
-
var _a, _b, _c;
|
|
108
|
+
var _a, _b, _c, _d, _e;
|
|
107
109
|
const timer = log.timer("startServices", {
|
|
108
110
|
corsOrigins,
|
|
109
111
|
contextApiUrl,
|
|
@@ -149,8 +151,10 @@ Please install OpenCode first:
|
|
|
149
151
|
log.debug(`Using port ${this.actualWebPort}`);
|
|
150
152
|
}
|
|
151
153
|
timer.checkpoint("Port allocated");
|
|
154
|
+
this.workspaceRoot = (0, import_system.findGitRoot)(process.cwd());
|
|
155
|
+
log.info(`Using workspace root: ${this.workspaceRoot}`);
|
|
152
156
|
this.sendTaskUpdate("preparing_runtime");
|
|
153
|
-
const configDir = (0, import_opencode.prepareOpenCodeRuntime)(
|
|
157
|
+
const configDir = (0, import_opencode.prepareOpenCodeRuntime)(this.workspaceRoot);
|
|
154
158
|
timer.checkpoint("Plugin setup complete");
|
|
155
159
|
this.sendTaskUpdate("starting_web");
|
|
156
160
|
log.debug("Starting OpenCode Web process...", {
|
|
@@ -162,7 +166,7 @@ Please install OpenCode first:
|
|
|
162
166
|
port: this.actualWebPort,
|
|
163
167
|
hostname: this.config.hostname,
|
|
164
168
|
serverUrl: "",
|
|
165
|
-
cwd:
|
|
169
|
+
cwd: this.workspaceRoot,
|
|
166
170
|
configDir,
|
|
167
171
|
corsOrigins,
|
|
168
172
|
contextApiUrl
|
|
@@ -172,7 +176,10 @@ Please install OpenCode first:
|
|
|
172
176
|
log.info(`Waiting for OpenCode Web to become ready at ${webUrl}...`);
|
|
173
177
|
this.sendTaskUpdate("waiting_web_ready");
|
|
174
178
|
try {
|
|
175
|
-
yield (0, import_system.waitForServer)(webUrl, import_shared.SERVER_START_TIMEOUT);
|
|
179
|
+
yield (0, import_system.waitForServer)(webUrl, import_shared.SERVER_START_TIMEOUT, this.webProcess);
|
|
180
|
+
if (((_a = this.webProcess) == null ? void 0 : _a.exitCode) !== null && ((_b = this.webProcess) == null ? void 0 : _b.exitCode) !== void 0) {
|
|
181
|
+
throw new Error(`OpenCode process exited with code ${this.webProcess.exitCode}`);
|
|
182
|
+
}
|
|
176
183
|
log.info(`OpenCode Web started at ${webUrl}`);
|
|
177
184
|
} catch (e) {
|
|
178
185
|
log.error("OpenCode Web failed to start", { error: e });
|
|
@@ -182,60 +189,80 @@ Please install OpenCode first:
|
|
|
182
189
|
return;
|
|
183
190
|
}
|
|
184
191
|
this.sendTaskUpdate("starting_proxy");
|
|
185
|
-
this.
|
|
186
|
-
|
|
187
|
-
this.
|
|
188
|
-
|
|
192
|
+
let proxyStartPort = (_c = this.config.proxyPort) != null ? _c : import_shared.DEFAULT_PROXY_PORT;
|
|
193
|
+
if (proxyStartPort === this.actualWebPort) {
|
|
194
|
+
proxyStartPort = this.actualWebPort + 1;
|
|
195
|
+
log.debug(`Proxy start port conflicts with web port, using ${proxyStartPort} instead`);
|
|
196
|
+
}
|
|
197
|
+
this.actualProxyPort = yield (0, import_system.findAvailablePort)(proxyStartPort, this.config.hostname);
|
|
189
198
|
this.onProxyPortAllocated(this.actualProxyPort);
|
|
190
|
-
if (this.actualProxyPort !== ((
|
|
199
|
+
if (this.actualProxyPort !== ((_d = this.config.proxyPort) != null ? _d : import_shared.DEFAULT_PROXY_PORT)) {
|
|
191
200
|
log.info(
|
|
192
|
-
`Proxy port ${(
|
|
201
|
+
`Proxy port ${(_e = this.config.proxyPort) != null ? _e : import_shared.DEFAULT_PROXY_PORT} is in use, using ${this.actualProxyPort} instead`
|
|
193
202
|
);
|
|
194
203
|
} else {
|
|
195
204
|
log.debug(`Using proxy port ${this.actualProxyPort}`);
|
|
196
205
|
}
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
206
|
+
try {
|
|
207
|
+
const result = yield (0, import_proxy_server.startProxyServer)(webUrl, this.actualProxyPort, {
|
|
208
|
+
theme: this.config.theme,
|
|
209
|
+
language: this.config.language,
|
|
210
|
+
settings: this.config.settings
|
|
211
|
+
});
|
|
212
|
+
this.proxyServer = result.server;
|
|
213
|
+
if (result.actualPort !== this.actualProxyPort) {
|
|
214
|
+
log.info(
|
|
215
|
+
`Proxy port ${this.actualProxyPort} was taken, using ${result.actualPort} instead`
|
|
216
|
+
);
|
|
217
|
+
this.actualProxyPort = result.actualPort;
|
|
218
|
+
this.onProxyPortAllocated(this.actualProxyPort);
|
|
219
|
+
}
|
|
220
|
+
} catch (err) {
|
|
221
|
+
const nodeErr = err;
|
|
222
|
+
if (nodeErr.code === "EADDRINUSE") {
|
|
223
|
+
log.warn(`Proxy port ${this.actualProxyPort} became unavailable, trying next port...`);
|
|
224
|
+
const nextPort = yield (0, import_system.findAvailablePort)(this.actualProxyPort + 1, this.config.hostname);
|
|
225
|
+
const result = yield (0, import_proxy_server.startProxyServer)(webUrl, nextPort, {
|
|
226
|
+
theme: this.config.theme,
|
|
227
|
+
language: this.config.language,
|
|
228
|
+
settings: this.config.settings
|
|
229
|
+
});
|
|
230
|
+
this.proxyServer = result.server;
|
|
231
|
+
this.actualProxyPort = result.actualPort;
|
|
232
|
+
this.onProxyPortAllocated(this.actualProxyPort);
|
|
233
|
+
log.info(`Proxy server started on fallback port ${this.actualProxyPort}`);
|
|
234
|
+
} else {
|
|
235
|
+
throw err;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
202
238
|
timer.checkpoint("Proxy server started");
|
|
203
239
|
this.sendTaskUpdate("warming_up_chrome");
|
|
204
240
|
let warmupFailed = false;
|
|
205
241
|
try {
|
|
206
|
-
yield this.api.warmupChromeMcp(viteOrigin);
|
|
242
|
+
yield this.api.warmupChromeMcp(this.workspaceRoot, viteOrigin);
|
|
207
243
|
timer.checkpoint("Chrome MCP warmup complete");
|
|
208
244
|
} catch (e) {
|
|
209
245
|
log.warn("Chrome MCP warmup failed", { error: e });
|
|
210
246
|
this.chromeMcpWarmupFailed = true;
|
|
211
247
|
warmupFailed = true;
|
|
248
|
+
if (e instanceof import_shared.ChromeMcpWarmupError) {
|
|
249
|
+
this.chromeMcpWarmupErrorType = e.type;
|
|
250
|
+
this.chromeMcpWarmupErrorMessage = e.message;
|
|
251
|
+
} else {
|
|
252
|
+
this.chromeMcpWarmupErrorType = import_shared.ChromeMcpWarmupErrorType.UNKNOWN;
|
|
253
|
+
this.chromeMcpWarmupErrorMessage = e instanceof Error ? e.message : String(e);
|
|
254
|
+
}
|
|
212
255
|
}
|
|
213
256
|
this.sendTaskUpdate("creating_session");
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
} catch (e) {
|
|
220
|
-
log.warn("Failed to get/create session", { error: e });
|
|
221
|
-
sessionFailed = true;
|
|
222
|
-
}
|
|
223
|
-
if (sessionFailed) {
|
|
224
|
-
this.sendTaskUpdate("session_creation_failed");
|
|
225
|
-
this.isStarted = false;
|
|
226
|
-
this.startPromise = null;
|
|
227
|
-
} else if (warmupFailed) {
|
|
228
|
-
this.sendTaskUpdate("chrome_mcp_failed", { sessionUrl: this.sessionUrl });
|
|
229
|
-
this.isStarted = true;
|
|
230
|
-
} else {
|
|
231
|
-
this.sendTaskUpdate("ready", { sessionUrl: this.sessionUrl });
|
|
232
|
-
}
|
|
233
|
-
if (!sessionFailed) {
|
|
257
|
+
if (warmupFailed) {
|
|
258
|
+
this.sendTaskUpdate("chrome_mcp_failed", {
|
|
259
|
+
errorType: this.chromeMcpWarmupErrorType,
|
|
260
|
+
errorMessage: this.chromeMcpWarmupErrorMessage
|
|
261
|
+
});
|
|
234
262
|
this.isStarted = true;
|
|
235
263
|
} else {
|
|
236
|
-
this.
|
|
264
|
+
this.sendTaskUpdate("ready");
|
|
237
265
|
}
|
|
238
|
-
log.debug(`OpenCode services started successfully: ${this.sessionUrl || webUrl}`);
|
|
239
266
|
timer.end("\u2713 Services started successfully");
|
|
240
267
|
}))();
|
|
241
268
|
return this.startPromise;
|
|
@@ -243,12 +270,18 @@ Please install OpenCode first:
|
|
|
243
270
|
}
|
|
244
271
|
retryWarmupChromeMcp(viteOrigin) {
|
|
245
272
|
return __async(this, null, function* () {
|
|
246
|
-
const
|
|
247
|
-
if (success) {
|
|
273
|
+
const result = yield this.api.retryWarmupChromeMcp(this.workspaceRoot, viteOrigin);
|
|
274
|
+
if (result.success) {
|
|
248
275
|
this.chromeMcpWarmupFailed = false;
|
|
249
|
-
this.sendTaskUpdate("ready"
|
|
276
|
+
this.sendTaskUpdate("ready");
|
|
277
|
+
return { success: true };
|
|
250
278
|
}
|
|
251
|
-
|
|
279
|
+
const error = result.error;
|
|
280
|
+
return {
|
|
281
|
+
success: false,
|
|
282
|
+
errorType: error == null ? void 0 : error.type,
|
|
283
|
+
errorMessage: (error == null ? void 0 : error.message) || "Unknown error"
|
|
284
|
+
};
|
|
252
285
|
});
|
|
253
286
|
}
|
|
254
287
|
stop() {
|
package/lib/endpoints/index.js
CHANGED
|
@@ -31,7 +31,7 @@ __reExport(endpoints_exports, require("./types.js"), module.exports);
|
|
|
31
31
|
function setupMiddlewares(server, ctx) {
|
|
32
32
|
(0, import_widget.setupWidgetEndpoints)(server, ctx);
|
|
33
33
|
(0, import_context.setupContextEndpoint)(server, ctx);
|
|
34
|
-
(0, import_start.setupStartEndpoint)(server
|
|
34
|
+
(0, import_start.setupStartEndpoint)(server);
|
|
35
35
|
(0, import_sse.setupSseEndpoint)(server, ctx);
|
|
36
36
|
(0, import_sessions.setupSessionsEndpoint)(server, ctx);
|
|
37
37
|
(0, import_warmup.setupWarmupEndpoint)(server, ctx);
|
package/lib/endpoints/sse.js
CHANGED
package/lib/endpoints/start.d.ts
CHANGED
package/lib/endpoints/start.js
CHANGED
|
@@ -42,13 +42,13 @@ __export(start_exports, {
|
|
|
42
42
|
module.exports = __toCommonJS(start_exports);
|
|
43
43
|
var import_shared = require("@vite-plugin-opencode-assistant/shared");
|
|
44
44
|
var import_shared2 = require("@vite-plugin-opencode-assistant/shared");
|
|
45
|
-
function setupStartEndpoint(server
|
|
45
|
+
function setupStartEndpoint(server) {
|
|
46
46
|
server.middlewares.use(import_shared.START_API_PATH, (_req, res) => __async(null, null, function* () {
|
|
47
47
|
const reqCtx = new import_shared2.RequestContext("GET", import_shared.START_API_PATH);
|
|
48
48
|
res.setHeader("Content-Type", "application/json");
|
|
49
49
|
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
50
50
|
res.writeHead(200);
|
|
51
|
-
res.end(JSON.stringify({ success: true
|
|
51
|
+
res.end(JSON.stringify({ success: true }));
|
|
52
52
|
reqCtx.end(200);
|
|
53
53
|
}));
|
|
54
54
|
}
|
package/lib/endpoints/types.d.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import type { PageContext, SessionInfo, ServiceStartupTask } from "@vite-plugin-opencode-assistant/shared";
|
|
2
2
|
import type http from "http";
|
|
3
3
|
export interface EndpointContext {
|
|
4
|
-
get sessionUrl(): string | null;
|
|
5
4
|
get webUrl(): string | null;
|
|
6
5
|
get sseClients(): Set<http.ServerResponse>;
|
|
7
6
|
get pageContext(): PageContext;
|
|
@@ -16,5 +15,9 @@ export interface EndpointContext {
|
|
|
16
15
|
deleteSession: (id: string) => Promise<void>;
|
|
17
16
|
resolveWidgetPath: () => string;
|
|
18
17
|
resolveWidgetStylePath: () => string;
|
|
19
|
-
retryWarmupChromeMcp: () => Promise<
|
|
18
|
+
retryWarmupChromeMcp: () => Promise<{
|
|
19
|
+
success: boolean;
|
|
20
|
+
errorType?: string;
|
|
21
|
+
errorMessage?: string;
|
|
22
|
+
}>;
|
|
20
23
|
}
|
package/lib/endpoints/warmup.js
CHANGED
|
@@ -50,15 +50,27 @@ function setupWarmupEndpoint(server, ctx) {
|
|
|
50
50
|
return;
|
|
51
51
|
}
|
|
52
52
|
try {
|
|
53
|
-
const
|
|
53
|
+
const result = yield ctx.retryWarmupChromeMcp();
|
|
54
54
|
res.setHeader("Content-Type", "application/json");
|
|
55
55
|
res.writeHead(200);
|
|
56
|
-
|
|
56
|
+
if (result.success) {
|
|
57
|
+
res.end(JSON.stringify({ success: true }));
|
|
58
|
+
} else {
|
|
59
|
+
res.end(JSON.stringify({
|
|
60
|
+
success: false,
|
|
61
|
+
errorType: result.errorType,
|
|
62
|
+
error: result.errorMessage
|
|
63
|
+
}));
|
|
64
|
+
}
|
|
57
65
|
} catch (e) {
|
|
58
66
|
log.error("Failed to retry warmup", { error: e });
|
|
59
67
|
res.setHeader("Content-Type", "application/json");
|
|
60
68
|
res.writeHead(500);
|
|
61
|
-
res.end(JSON.stringify({
|
|
69
|
+
res.end(JSON.stringify({
|
|
70
|
+
success: false,
|
|
71
|
+
errorType: "UNKNOWN",
|
|
72
|
+
error: String(e)
|
|
73
|
+
}));
|
|
62
74
|
}
|
|
63
75
|
}));
|
|
64
76
|
}
|
package/lib/index.js
CHANGED
|
@@ -92,7 +92,12 @@ function createOpenCodePlugin(options = {}) {
|
|
|
92
92
|
let actualProxyPort = (_a = config.proxyPort) != null ? _a : import_shared.DEFAULT_PROXY_PORT;
|
|
93
93
|
let pageContext = { url: "", title: "" };
|
|
94
94
|
const sseClients = /* @__PURE__ */ new Set();
|
|
95
|
-
const api = new import_api.OpenCodeAPI(
|
|
95
|
+
const api = new import_api.OpenCodeAPI(
|
|
96
|
+
config.hostname,
|
|
97
|
+
() => actualWebPort,
|
|
98
|
+
() => actualProxyPort,
|
|
99
|
+
config.warmupChromeMcp
|
|
100
|
+
);
|
|
96
101
|
const service = new import_service.OpenCodeService(
|
|
97
102
|
config,
|
|
98
103
|
api,
|
|
@@ -117,9 +122,6 @@ function createOpenCodePlugin(options = {}) {
|
|
|
117
122
|
let viteOrigin = "";
|
|
118
123
|
const getViteOrigin = () => viteOrigin;
|
|
119
124
|
(0, import_endpoints.setupMiddlewares)(server, {
|
|
120
|
-
get sessionUrl() {
|
|
121
|
-
return service.sessionUrl;
|
|
122
|
-
},
|
|
123
125
|
get webUrl() {
|
|
124
126
|
return actualWebPort ? `http://${config.hostname}:${actualWebPort}` : null;
|
|
125
127
|
},
|
|
@@ -138,8 +140,8 @@ function createOpenCodePlugin(options = {}) {
|
|
|
138
140
|
get currentTask() {
|
|
139
141
|
return service.currentTask;
|
|
140
142
|
},
|
|
141
|
-
getSessions: () => api.getSessions(),
|
|
142
|
-
createSession: () => api.createSession(),
|
|
143
|
+
getSessions: () => api.getSessions(service.workspaceRoot),
|
|
144
|
+
createSession: () => api.createSession(service.workspaceRoot),
|
|
143
145
|
deleteSession: (id) => api.deleteSession(id),
|
|
144
146
|
resolveWidgetPath: import_paths.resolveWidgetPath,
|
|
145
147
|
resolveWidgetStylePath: import_paths.resolveWidgetStylePath,
|
|
@@ -195,15 +197,9 @@ function createOpenCodePlugin(options = {}) {
|
|
|
195
197
|
transformIndexHtml(html) {
|
|
196
198
|
const timer = log.timer("transformIndexHtml");
|
|
197
199
|
const widget = (0, import_injector.injectWidget)({
|
|
198
|
-
webUrl: `http://${config.hostname}:${actualWebPort}`,
|
|
199
|
-
proxyUrl: `http://${config.hostname}:${actualProxyPort}`,
|
|
200
|
-
serverUrl: `http://${config.hostname}:${actualWebPort}`,
|
|
201
200
|
position: config.position,
|
|
202
201
|
theme: config.theme,
|
|
203
202
|
open: config.open,
|
|
204
|
-
autoReload: config.autoReload,
|
|
205
|
-
cwd: process.cwd(),
|
|
206
|
-
// 不再注入 sessionUrl,客户端完全依赖 SSE 状态同步
|
|
207
203
|
hotkey: config.hotkey
|
|
208
204
|
});
|
|
209
205
|
timer.end();
|