vite-plugin-opencode-assistant 1.0.3 → 1.0.5

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.
Files changed (85) hide show
  1. package/es/client/index.d.ts +1 -0
  2. package/es/client/index.js +328 -0
  3. package/es/core/api.d.ts +14 -0
  4. package/es/core/api.js +294 -0
  5. package/es/core/injector.d.ts +2 -0
  6. package/es/core/injector.js +11 -0
  7. package/es/core/service.d.ts +18 -0
  8. package/es/core/service.js +163 -0
  9. package/es/endpoints/context.d.ts +3 -0
  10. package/es/endpoints/context.js +114 -0
  11. package/es/endpoints/index.d.ts +4 -0
  12. package/es/endpoints/index.js +16 -0
  13. package/es/endpoints/sessions.d.ts +3 -0
  14. package/es/endpoints/sessions.js +79 -0
  15. package/es/endpoints/sse.d.ts +3 -0
  16. package/es/endpoints/sse.js +56 -0
  17. package/es/endpoints/start.d.ts +3 -0
  18. package/es/endpoints/start.js +35 -0
  19. package/es/endpoints/types.d.ts +13 -0
  20. package/es/endpoints/widget.d.ts +3 -0
  21. package/es/endpoints/widget.js +57 -0
  22. package/es/index.d.ts +3 -0
  23. package/es/index.js +168 -0
  24. package/es/utils/paths.d.ts +3 -0
  25. package/es/utils/paths.js +38 -0
  26. package/es/utils/system.js +241 -0
  27. package/lib/client/index.d.ts +1 -0
  28. package/lib/client/index.js +328 -0
  29. package/lib/client.js +5597 -0
  30. package/lib/core/api.d.ts +14 -0
  31. package/lib/core/api.js +321 -0
  32. package/lib/core/injector.d.ts +2 -0
  33. package/lib/core/injector.js +34 -0
  34. package/lib/core/service.d.ts +18 -0
  35. package/lib/core/service.js +180 -0
  36. package/lib/endpoints/context.d.ts +3 -0
  37. package/lib/endpoints/context.js +137 -0
  38. package/lib/endpoints/index.d.ts +4 -0
  39. package/lib/endpoints/index.js +41 -0
  40. package/lib/endpoints/sessions.d.ts +3 -0
  41. package/lib/endpoints/sessions.js +102 -0
  42. package/lib/endpoints/sse.d.ts +3 -0
  43. package/lib/endpoints/sse.js +79 -0
  44. package/lib/endpoints/start.d.ts +3 -0
  45. package/lib/endpoints/start.js +58 -0
  46. package/lib/endpoints/types.d.ts +13 -0
  47. package/lib/endpoints/types.js +15 -0
  48. package/lib/endpoints/widget.d.ts +3 -0
  49. package/lib/endpoints/widget.js +90 -0
  50. package/lib/index.d.ts +3 -0
  51. package/lib/index.js +190 -0
  52. package/lib/style.css +1 -0
  53. package/lib/utils/paths.d.ts +3 -0
  54. package/lib/utils/paths.js +73 -0
  55. package/lib/utils/system.d.ts +5 -0
  56. package/lib/utils/system.js +274 -0
  57. package/package.json +29 -31
  58. package/README.md +0 -282
  59. package/dist/constants.d.ts +0 -73
  60. package/dist/constants.js +0 -74
  61. package/dist/constants.js.map +0 -1
  62. package/dist/logger.d.ts +0 -64
  63. package/dist/logger.js +0 -311
  64. package/dist/logger.js.map +0 -1
  65. package/dist/opencode/plugins/page-context.d.ts +0 -7
  66. package/dist/opencode/plugins/page-context.js +0 -345
  67. package/dist/opencode/plugins/page-context.js.map +0 -1
  68. package/dist/opencode/web.d.ts +0 -3
  69. package/dist/opencode/web.js +0 -81
  70. package/dist/opencode/web.js.map +0 -1
  71. package/dist/types.d.ts +0 -124
  72. package/dist/types.js +0 -2
  73. package/dist/types.js.map +0 -1
  74. package/dist/vite/client.js +0 -2288
  75. package/dist/vite/client.js.map +0 -1
  76. package/dist/vite/index.d.ts +0 -3
  77. package/dist/vite/index.js +0 -520
  78. package/dist/vite/index.js.map +0 -1
  79. package/dist/vite/injector.d.ts +0 -2
  80. package/dist/vite/injector.js +0 -6
  81. package/dist/vite/injector.js.map +0 -1
  82. package/dist/vite/utils.js +0 -206
  83. package/dist/vite/utils.js.map +0 -1
  84. /package/{dist/vite/client.d.ts → es/endpoints/types.js} +0 -0
  85. /package/{dist/vite/utils.d.ts → es/utils/system.d.ts} +0 -0
@@ -0,0 +1 @@
1
+ import "@vite-plugin-opencode-assistant/components/style.css";
@@ -0,0 +1,328 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __defProps = Object.defineProperties;
3
+ var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
4
+ var __getOwnPropSymbols = Object.getOwnPropertySymbols;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __propIsEnum = Object.prototype.propertyIsEnumerable;
7
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
8
+ var __spreadValues = (a, b) => {
9
+ for (var prop in b || (b = {}))
10
+ if (__hasOwnProp.call(b, prop))
11
+ __defNormalProp(a, prop, b[prop]);
12
+ if (__getOwnPropSymbols)
13
+ for (var prop of __getOwnPropSymbols(b)) {
14
+ if (__propIsEnum.call(b, prop))
15
+ __defNormalProp(a, prop, b[prop]);
16
+ }
17
+ return a;
18
+ };
19
+ var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
20
+ var __async = (__this, __arguments, generator) => {
21
+ return new Promise((resolve, reject) => {
22
+ var fulfilled = (value) => {
23
+ try {
24
+ step(generator.next(value));
25
+ } catch (e) {
26
+ reject(e);
27
+ }
28
+ };
29
+ var rejected = (value) => {
30
+ try {
31
+ step(generator.throw(value));
32
+ } catch (e) {
33
+ reject(e);
34
+ }
35
+ };
36
+ var step = (x) => x.done ? resolve(x.value) : Promise.resolve(x.value).then(fulfilled, rejected);
37
+ step((generator = generator.apply(__this, __arguments)).next());
38
+ });
39
+ };
40
+ import { createApp, ref, watch, onMounted, h, computed } from "vue";
41
+ import { OpenCodeWidget } from "@vite-plugin-opencode-assistant/components";
42
+ import "@vite-plugin-opencode-assistant/components/style.css";
43
+ import { CONFIG_DATA_ATTR } from "@vite-plugin-opencode-assistant/shared";
44
+ let config = {};
45
+ const scriptTag = document.querySelector(`script[${CONFIG_DATA_ATTR}]`);
46
+ if (scriptTag) {
47
+ const configBase64 = scriptTag.getAttribute(CONFIG_DATA_ATTR);
48
+ if (configBase64) {
49
+ try {
50
+ config = JSON.parse(atob(configBase64));
51
+ } catch (e) {
52
+ console.error("[OpenCode] Failed to parse config:", e);
53
+ }
54
+ }
55
+ }
56
+ const App = {
57
+ setup() {
58
+ const open = ref(false);
59
+ const selectMode = ref(false);
60
+ const sessionListCollapsed = ref(true);
61
+ const loading = ref(false);
62
+ const loadingSessionList = ref(void 0);
63
+ const iframeSrc = ref("");
64
+ const currentSessionId = ref(null);
65
+ const sessions = ref([]);
66
+ const selectedElements = ref([]);
67
+ const widgetRef = ref(null);
68
+ const {
69
+ webUrl = "",
70
+ position = "bottom-right",
71
+ theme = "auto",
72
+ open: autoOpen = false,
73
+ sessionUrl: initialSessionUrl = "",
74
+ lazy = false,
75
+ hotkey = "ctrl+k",
76
+ cwd = ""
77
+ } = config;
78
+ const isWaitingForSession = ref(!initialSessionUrl);
79
+ const computedLoading = computed(() => loading.value || isWaitingForSession.value);
80
+ let servicesStarted = !lazy;
81
+ const extractSessionId = (url) => {
82
+ if (!url) return null;
83
+ const match = url.match(/\/session\/([^/?]+)/);
84
+ return match ? match[1] : null;
85
+ };
86
+ currentSessionId.value = extractSessionId(initialSessionUrl);
87
+ if (servicesStarted && initialSessionUrl) {
88
+ iframeSrc.value = initialSessionUrl;
89
+ }
90
+ try {
91
+ const stored = sessionStorage.getItem("__opencode_selected_elements__");
92
+ if (stored) {
93
+ selectedElements.value = JSON.parse(stored);
94
+ }
95
+ } catch (e) {
96
+ }
97
+ watch(
98
+ selectedElements,
99
+ (val) => {
100
+ sessionStorage.setItem("__opencode_selected_elements__", JSON.stringify(val));
101
+ },
102
+ { deep: true }
103
+ );
104
+ const showNotification = (msg) => {
105
+ var _a, _b;
106
+ (_b = (_a = widgetRef.value) == null ? void 0 : _a.showNotification) == null ? void 0 : _b.call(_a, msg);
107
+ };
108
+ const loadSessions = () => __async(null, null, function* () {
109
+ loadingSessionList.value = true;
110
+ try {
111
+ const response = yield fetch("/__opencode_sessions__");
112
+ const data = yield response.json();
113
+ sessions.value = data.filter((s) => s.directory === cwd && s.title !== "__chrome_mcp_warmup__").map((s) => {
114
+ var _a;
115
+ return __spreadProps(__spreadValues({}, s), {
116
+ updatedAt: ((_a = s.time) == null ? void 0 : _a.updated) || Date.now()
117
+ });
118
+ });
119
+ } catch (e) {
120
+ console.error("[OpenCode] Failed to load sessions:", e);
121
+ } finally {
122
+ loadingSessionList.value = false;
123
+ }
124
+ });
125
+ const createSession = () => __async(null, null, function* () {
126
+ try {
127
+ const response = yield fetch("/__opencode_sessions__", { method: "POST" });
128
+ const newSession = yield response.json();
129
+ yield loadSessions();
130
+ currentSessionId.value = newSession.id;
131
+ iframeSrc.value = `${webUrl}/${btoa(cwd)}/session/${newSession.id}`;
132
+ } catch (e) {
133
+ showNotification("\u521B\u5EFA\u4F1A\u8BDD\u5931\u8D25");
134
+ }
135
+ });
136
+ const deleteSession = (session) => __async(null, null, function* () {
137
+ try {
138
+ yield fetch(`/__opencode_sessions__?id=${session.id}`, { method: "DELETE" });
139
+ yield loadSessions();
140
+ showNotification("\u4F1A\u8BDD\u5DF2\u5220\u9664");
141
+ if (currentSessionId.value === session.id) {
142
+ if (sessions.value.length > 0) {
143
+ const nextSession = sessions.value[0];
144
+ currentSessionId.value = nextSession.id;
145
+ iframeSrc.value = `${webUrl}/${btoa(cwd)}/session/${nextSession.id}`;
146
+ } else {
147
+ currentSessionId.value = null;
148
+ iframeSrc.value = "";
149
+ }
150
+ }
151
+ } catch (e) {
152
+ showNotification("\u5220\u9664\u4F1A\u8BDD\u5931\u8D25");
153
+ }
154
+ });
155
+ const selectSession = (session) => {
156
+ if (currentSessionId.value === session.id) return;
157
+ currentSessionId.value = session.id;
158
+ loading.value = true;
159
+ iframeSrc.value = `${webUrl}/${btoa(cwd)}/session/${session.id}`;
160
+ setTimeout(() => {
161
+ loading.value = false;
162
+ }, 500);
163
+ };
164
+ let sseConnection = null;
165
+ const setupSSE = () => {
166
+ if (!servicesStarted || sseConnection) return;
167
+ sseConnection = new EventSource("/__opencode_events__");
168
+ sseConnection.onmessage = (event) => {
169
+ try {
170
+ const data = JSON.parse(event.data);
171
+ if (data.type === "CONNECTED") {
172
+ updateContext(true);
173
+ } else if (data.type === "SESSION_READY") {
174
+ if (data.sessionUrl && !iframeSrc.value) {
175
+ iframeSrc.value = data.sessionUrl;
176
+ currentSessionId.value = extractSessionId(data.sessionUrl);
177
+ }
178
+ isWaitingForSession.value = false;
179
+ } else if (data.type === "CLEAR_ELEMENTS") {
180
+ selectedElements.value = [];
181
+ }
182
+ } catch (e) {
183
+ }
184
+ };
185
+ };
186
+ let currentPageUrl = "";
187
+ let currentPageTitle = "";
188
+ const updateContext = (force = false) => {
189
+ if (!servicesStarted) return;
190
+ const newUrl = window.location.href;
191
+ const newTitle = document.title;
192
+ if (force || newUrl !== currentPageUrl || newTitle !== currentPageTitle) {
193
+ currentPageUrl = newUrl;
194
+ currentPageTitle = newTitle;
195
+ fetch("/__opencode_context__", {
196
+ method: "POST",
197
+ headers: { "Content-Type": "application/json" },
198
+ body: JSON.stringify({
199
+ url: newUrl,
200
+ title: newTitle,
201
+ selectedElements: selectedElements.value
202
+ })
203
+ }).catch(() => {
204
+ });
205
+ }
206
+ };
207
+ const ensureServicesStarted = () => __async(null, null, function* () {
208
+ if (servicesStarted) return true;
209
+ try {
210
+ const res = yield fetch("/__opencode_start__");
211
+ const data = yield res.json();
212
+ if (data.success) {
213
+ servicesStarted = true;
214
+ if (data.sessionUrl) {
215
+ iframeSrc.value = data.sessionUrl;
216
+ currentSessionId.value = extractSessionId(data.sessionUrl);
217
+ isWaitingForSession.value = false;
218
+ }
219
+ setupSSE();
220
+ return true;
221
+ }
222
+ } catch (e) {
223
+ }
224
+ return false;
225
+ });
226
+ onMounted(() => {
227
+ if (servicesStarted) {
228
+ loadSessions();
229
+ setupSSE();
230
+ updateContext(true);
231
+ }
232
+ if (autoOpen && servicesStarted) {
233
+ setTimeout(() => {
234
+ open.value = true;
235
+ }, 1e3);
236
+ }
237
+ const originalPushState = history.pushState;
238
+ const originalReplaceState = history.replaceState;
239
+ history.pushState = function(...args) {
240
+ originalPushState.apply(this, args);
241
+ setTimeout(updateContext, 0);
242
+ };
243
+ history.replaceState = function(...args) {
244
+ originalReplaceState.apply(this, args);
245
+ setTimeout(updateContext, 0);
246
+ };
247
+ window.addEventListener("popstate", () => setTimeout(updateContext, 0));
248
+ window.addEventListener("hashchange", () => setTimeout(updateContext, 0));
249
+ const titleObserver = new MutationObserver(() => {
250
+ if (document.title !== currentPageTitle) updateContext();
251
+ });
252
+ if (document.head) {
253
+ titleObserver.observe(document.head, { childList: true, subtree: true });
254
+ }
255
+ });
256
+ const handleToggle = (val) => __async(null, null, function* () {
257
+ if (lazy && !servicesStarted && val) {
258
+ loading.value = true;
259
+ const started = yield ensureServicesStarted();
260
+ loading.value = false;
261
+ if (!started) {
262
+ showNotification("\u670D\u52A1\u542F\u52A8\u5931\u8D25");
263
+ return;
264
+ }
265
+ }
266
+ open.value = val;
267
+ if (val) updateContext();
268
+ });
269
+ const handleSelectNode = (element) => {
270
+ const exists = selectedElements.value.some(
271
+ (el) => el.filePath === element.filePath && el.line === element.line
272
+ );
273
+ if (!exists) {
274
+ selectedElements.value.push(element);
275
+ showNotification(`\u5DF2\u9009\u4E2D\u5143\u7D20 (${selectedElements.value.length}\u4E2A)`);
276
+ updateContext(true);
277
+ } else {
278
+ showNotification("\u8BE5\u5143\u7D20\u5DF2\u9009\u4E2D");
279
+ }
280
+ };
281
+ const handleClearSelected = () => {
282
+ selectedElements.value = [];
283
+ updateContext(true);
284
+ showNotification("\u5DF2\u6E05\u9664\u6240\u6709\u9009\u4E2D\u5143\u7D20");
285
+ };
286
+ return () => {
287
+ return h(OpenCodeWidget, {
288
+ ref: widgetRef,
289
+ position,
290
+ theme,
291
+ open: open.value,
292
+ selectMode: selectMode.value,
293
+ sessionListCollapsed: sessionListCollapsed.value,
294
+ loading: computedLoading.value,
295
+ loadingSessionList: loadingSessionList.value,
296
+ iframeSrc: iframeSrc.value,
297
+ currentSessionId: currentSessionId.value,
298
+ sessions: sessions.value,
299
+ selectedElements: selectedElements.value,
300
+ hotkeyLabel: hotkey,
301
+ "onUpdate:open": handleToggle,
302
+ "onUpdate:selectMode": (val) => {
303
+ selectMode.value = val;
304
+ },
305
+ "onUpdate:sessionListCollapsed": (val) => {
306
+ sessionListCollapsed.value = val;
307
+ },
308
+ "onCreate-session": createSession,
309
+ "onDelete-session": deleteSession,
310
+ "onSelect-session": selectSession,
311
+ "onClick-selected-node": handleSelectNode,
312
+ "onClear-selected-nodes": handleClearSelected,
313
+ "onRemove-selected-node": ({ index }) => {
314
+ selectedElements.value.splice(index, 1);
315
+ updateContext(true);
316
+ },
317
+ "onEmpty-action": createSession
318
+ });
319
+ };
320
+ }
321
+ };
322
+ const INIT_MARKER = "__OPENCODE_INITIALIZED__";
323
+ if (!window[INIT_MARKER]) {
324
+ window[INIT_MARKER] = true;
325
+ const container = document.createElement("div");
326
+ document.body.appendChild(container);
327
+ createApp(App).mount(container);
328
+ }
@@ -0,0 +1,14 @@
1
+ import type { SessionInfo } from "@vite-plugin-opencode-assistant/shared";
2
+ export declare class OpenCodeAPI {
3
+ private hostname;
4
+ private getPort;
5
+ private warmupChromeMcpConfig;
6
+ constructor(hostname: string, getPort: () => number, warmupChromeMcpConfig?: boolean);
7
+ private createHttpRequest;
8
+ getSessions(retries?: number): Promise<SessionInfo[]>;
9
+ createSession(retries?: number, title?: string): Promise<SessionInfo>;
10
+ deleteSession(sessionId: string, retries?: number): Promise<void>;
11
+ getToolIds(retries?: number): Promise<string[]>;
12
+ warmupChromeMcp(viteOrigin?: string): Promise<void>;
13
+ getOrCreateSession(): Promise<string>;
14
+ }
package/es/core/api.js ADDED
@@ -0,0 +1,294 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
3
+ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
4
+ var __async = (__this, __arguments, generator) => {
5
+ return new Promise((resolve, reject) => {
6
+ var fulfilled = (value) => {
7
+ try {
8
+ step(generator.next(value));
9
+ } catch (e) {
10
+ reject(e);
11
+ }
12
+ };
13
+ var rejected = (value) => {
14
+ try {
15
+ step(generator.throw(value));
16
+ } catch (e) {
17
+ reject(e);
18
+ }
19
+ };
20
+ var step = (x) => x.done ? resolve(x.value) : Promise.resolve(x.value).then(fulfilled, rejected);
21
+ step((generator = generator.apply(__this, __arguments)).next());
22
+ });
23
+ };
24
+ import http from "http";
25
+ import {
26
+ PerformanceTimer,
27
+ createLogger,
28
+ DEFAULT_RETRIES,
29
+ RETRY_DELAY
30
+ } from "@vite-plugin-opencode-assistant/shared";
31
+ const log = createLogger("API");
32
+ function sleep(ms) {
33
+ return new Promise((resolve) => setTimeout(resolve, ms));
34
+ }
35
+ function base64Encode(str) {
36
+ return Buffer.from(str).toString("base64");
37
+ }
38
+ class OpenCodeAPI {
39
+ constructor(hostname, getPort, warmupChromeMcpConfig = false) {
40
+ __publicField(this, "hostname", hostname);
41
+ __publicField(this, "getPort", getPort);
42
+ __publicField(this, "warmupChromeMcpConfig", warmupChromeMcpConfig);
43
+ }
44
+ createHttpRequest(options, body) {
45
+ const timer = new PerformanceTimer("HTTP Request", {
46
+ operation: `${options.method || "GET"} ${options.path}`
47
+ });
48
+ return new Promise((resolve, reject) => {
49
+ const req = http.request(options, (res) => {
50
+ let data = "";
51
+ res.on("data", (chunk) => data += chunk);
52
+ res.on("end", () => {
53
+ try {
54
+ const result = JSON.parse(data);
55
+ timer.end(`\u2713 Status: ${res.statusCode}`);
56
+ resolve(result);
57
+ } catch (e) {
58
+ timer.end("\u274C JSON parse error");
59
+ reject(new Error(`JSON parse error: ${data.substring(0, 100)}`));
60
+ }
61
+ });
62
+ });
63
+ req.on("error", (e) => {
64
+ timer.end("\u274C Request failed");
65
+ reject(e);
66
+ });
67
+ if (body) req.write(body);
68
+ req.end();
69
+ });
70
+ }
71
+ getSessions() {
72
+ return __async(this, arguments, function* (retries = DEFAULT_RETRIES) {
73
+ const timer = log.timer("getSessions", { retries });
74
+ let lastError = null;
75
+ for (let i = 0; i < retries; i++) {
76
+ try {
77
+ log.debug(`Attempt ${i + 1}/${retries}`, { operation: "getSessions" });
78
+ const sessions = yield this.createHttpRequest({
79
+ hostname: this.hostname,
80
+ port: this.getPort(),
81
+ path: "/session"
82
+ });
83
+ timer.end(`Found ${sessions.length} sessions`);
84
+ return sessions;
85
+ } catch (e) {
86
+ lastError = e instanceof Error ? e : new Error(String(e));
87
+ log.debug(`Attempt ${i + 1} failed: ${lastError.message}`, {
88
+ operation: "getSessions"
89
+ });
90
+ if (i < retries - 1) {
91
+ log.debug(`Retrying in ${RETRY_DELAY}ms...`, {
92
+ operation: "getSessions"
93
+ });
94
+ yield sleep(RETRY_DELAY);
95
+ }
96
+ }
97
+ }
98
+ timer.end("\u274C All retries exhausted");
99
+ throw lastError;
100
+ });
101
+ }
102
+ createSession() {
103
+ return __async(this, arguments, function* (retries = DEFAULT_RETRIES, title) {
104
+ const timer = log.timer("createSession", { retries, title });
105
+ let lastError = null;
106
+ for (let i = 0; i < retries; i++) {
107
+ try {
108
+ log.debug(`Attempt ${i + 1}/${retries}`, {
109
+ operation: "createSession",
110
+ title
111
+ });
112
+ const requestBody = title ? JSON.stringify({ title }) : void 0;
113
+ const session = yield this.createHttpRequest(
114
+ {
115
+ hostname: this.hostname,
116
+ port: this.getPort(),
117
+ path: "/session",
118
+ method: "POST",
119
+ headers: requestBody ? { "Content-Type": "application/json" } : void 0
120
+ },
121
+ requestBody
122
+ );
123
+ timer.end(`Created session: ${session.id}`);
124
+ return session;
125
+ } catch (e) {
126
+ lastError = e instanceof Error ? e : new Error(String(e));
127
+ log.debug(`Attempt ${i + 1} failed: ${lastError.message}`, {
128
+ operation: "createSession"
129
+ });
130
+ if (i < retries - 1) {
131
+ log.debug(`Retrying in ${RETRY_DELAY}ms...`, {
132
+ operation: "createSession"
133
+ });
134
+ yield sleep(RETRY_DELAY);
135
+ }
136
+ }
137
+ }
138
+ timer.end("\u274C All retries exhausted");
139
+ throw lastError;
140
+ });
141
+ }
142
+ deleteSession(_0) {
143
+ return __async(this, arguments, function* (sessionId, retries = DEFAULT_RETRIES) {
144
+ const timer = log.timer("deleteSession", { sessionId, retries });
145
+ let lastError = null;
146
+ for (let i = 0; i < retries; i++) {
147
+ try {
148
+ log.debug(`Attempt ${i + 1}/${retries}`, {
149
+ operation: "deleteSession",
150
+ sessionId
151
+ });
152
+ yield this.createHttpRequest({
153
+ hostname: this.hostname,
154
+ port: this.getPort(),
155
+ path: `/session/${sessionId}`,
156
+ method: "DELETE"
157
+ });
158
+ timer.end(`Deleted session: ${sessionId}`);
159
+ return;
160
+ } catch (e) {
161
+ lastError = e instanceof Error ? e : new Error(String(e));
162
+ log.debug(`Attempt ${i + 1} failed: ${lastError.message}`, {
163
+ operation: "deleteSession",
164
+ sessionId
165
+ });
166
+ if (i < retries - 1) {
167
+ log.debug(`Retrying in ${RETRY_DELAY}ms...`, {
168
+ operation: "deleteSession",
169
+ sessionId
170
+ });
171
+ yield sleep(RETRY_DELAY);
172
+ }
173
+ }
174
+ }
175
+ timer.end("\u274C All retries exhausted");
176
+ throw lastError;
177
+ });
178
+ }
179
+ getToolIds() {
180
+ return __async(this, arguments, function* (retries = DEFAULT_RETRIES) {
181
+ const timer = log.timer("getToolIds", { retries });
182
+ let lastError = null;
183
+ for (let i = 0; i < retries; i++) {
184
+ try {
185
+ log.debug(`Attempt ${i + 1}/${retries}`, {
186
+ operation: "getToolIds"
187
+ });
188
+ const toolIds = yield this.createHttpRequest({
189
+ hostname: this.hostname,
190
+ port: this.getPort(),
191
+ path: "/experimental/tool/ids"
192
+ });
193
+ timer.end(`Found ${toolIds.length} tools`);
194
+ return toolIds;
195
+ } catch (e) {
196
+ lastError = e instanceof Error ? e : new Error(String(e));
197
+ log.debug(`Attempt ${i + 1} failed: ${lastError.message}`, {
198
+ operation: "getToolIds"
199
+ });
200
+ if (i < retries - 1) {
201
+ log.debug(`Retrying in ${RETRY_DELAY}ms...`, {
202
+ operation: "getToolIds"
203
+ });
204
+ yield sleep(RETRY_DELAY);
205
+ }
206
+ }
207
+ }
208
+ timer.end("\u274C All retries exhausted");
209
+ throw lastError;
210
+ });
211
+ }
212
+ warmupChromeMcp(viteOrigin) {
213
+ return __async(this, null, function* () {
214
+ if (!this.warmupChromeMcpConfig) return;
215
+ const timer = log.timer("warmupChromeMcp", { viteOrigin });
216
+ let warmupSessionId = null;
217
+ try {
218
+ const warmupSession = yield this.createSession(DEFAULT_RETRIES, "__chrome_mcp_warmup__");
219
+ warmupSessionId = warmupSession.id;
220
+ let chromeToolIds;
221
+ try {
222
+ const toolIds = yield this.getToolIds();
223
+ chromeToolIds = toolIds.filter((toolId) => /chrome[-_]?devtools/i.test(toolId));
224
+ log.debug("Resolved Chrome MCP tool ids", {
225
+ chromeToolIds
226
+ });
227
+ } catch (e) {
228
+ log.debug("Failed to resolve Chrome MCP tool ids", { error: e });
229
+ }
230
+ const prompt = [
231
+ "Call the browser tool list_pages immediately to establish the Chrome DevTools MCP connection.",
232
+ viteOrigin ? `If there are no pages, call new_page with ${viteOrigin}.` : "If there are no pages, call new_page with about:blank.",
233
+ "Do not read or modify project files.",
234
+ "Do not use any non-browser tools.",
235
+ "After the tool call is complete, reply with exactly: ready"
236
+ ].join(" ");
237
+ yield this.createHttpRequest(
238
+ {
239
+ hostname: this.hostname,
240
+ port: this.getPort(),
241
+ path: `/session/${warmupSessionId}/message`,
242
+ method: "POST",
243
+ headers: { "Content-Type": "application/json" }
244
+ },
245
+ JSON.stringify({
246
+ system: "You are warming up Chrome DevTools MCP during startup. You must use the available browser tools immediately before replying.",
247
+ tools: (chromeToolIds == null ? void 0 : chromeToolIds.length) ? chromeToolIds : void 0,
248
+ parts: [{ type: "text", text: prompt }]
249
+ })
250
+ );
251
+ timer.end("Chrome MCP warmed up");
252
+ } catch (e) {
253
+ log.warn("Failed to warm up Chrome MCP", { error: e });
254
+ timer.end("Chrome MCP warmup skipped");
255
+ } finally {
256
+ if (warmupSessionId) {
257
+ try {
258
+ yield this.deleteSession(warmupSessionId, 5);
259
+ } catch (e) {
260
+ log.warn("Failed to delete warmup session after retries", {
261
+ error: e,
262
+ warmupSessionId
263
+ });
264
+ }
265
+ }
266
+ }
267
+ });
268
+ }
269
+ getOrCreateSession() {
270
+ return __async(this, null, function* () {
271
+ const timer = log.timer("getOrCreateSession");
272
+ const projectDir = process.cwd();
273
+ log.debug("Getting sessions...", { projectDir });
274
+ const sessions = yield this.getSessions();
275
+ log.debug(`Found ${sessions.length} sessions`, {
276
+ sessions: sessions.map((s) => ({ id: s.id, directory: s.directory }))
277
+ });
278
+ const matchingSession = sessions.find((s) => s.directory === projectDir);
279
+ if (matchingSession) {
280
+ const url2 = `http://${this.hostname}:${this.getPort()}/${base64Encode(projectDir)}/session/${matchingSession.id}`;
281
+ timer.end(`Using existing session: ${matchingSession.id}`);
282
+ return url2;
283
+ }
284
+ log.debug("Creating new session...", { projectDir });
285
+ const newSession = yield this.createSession();
286
+ const url = `http://${this.hostname}:${this.getPort()}/${base64Encode(projectDir)}/session/${newSession.id}`;
287
+ timer.end(`Created new session: ${newSession.id}`);
288
+ return url;
289
+ });
290
+ }
291
+ }
292
+ export {
293
+ OpenCodeAPI
294
+ };
@@ -0,0 +1,2 @@
1
+ import type { WidgetOptions } from "@vite-plugin-opencode-assistant/shared";
2
+ export declare function injectWidget(options: WidgetOptions): string;
@@ -0,0 +1,11 @@
1
+ import { CONFIG_DATA_ATTR, WIDGET_SCRIPT_PATH } from "@vite-plugin-opencode-assistant/shared";
2
+ function injectWidget(options) {
3
+ const configBase64 = Buffer.from(JSON.stringify(options)).toString("base64");
4
+ const scriptTag = `<script type="module" src="${WIDGET_SCRIPT_PATH}" ${CONFIG_DATA_ATTR}="${configBase64}"></script>`;
5
+ const styleTag = `<link rel="stylesheet" href="/__opencode_widget__.css" />`;
6
+ return `${styleTag}
7
+ ${scriptTag}`;
8
+ }
9
+ export {
10
+ injectWidget
11
+ };
@@ -0,0 +1,18 @@
1
+ import type { ResultPromise } from "execa";
2
+ import type http from "http";
3
+ import type { OpenCodeOptions } from "@vite-plugin-opencode-assistant/shared";
4
+ import type { OpenCodeAPI } from "./api.js";
5
+ export declare class OpenCodeService {
6
+ private config;
7
+ private api;
8
+ private sseClients;
9
+ private onPortAllocated;
10
+ webProcess: ResultPromise | null;
11
+ actualWebPort: number;
12
+ isStarted: boolean;
13
+ private startPromise;
14
+ sessionUrl: string | null;
15
+ constructor(config: Required<OpenCodeOptions>, api: OpenCodeAPI, sseClients: Set<http.ServerResponse>, onPortAllocated: (port: number) => void);
16
+ start(corsOrigins?: string[], contextApiUrl?: string, viteOrigin?: string): Promise<void>;
17
+ stop(): Promise<void>;
18
+ }