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