@marimo-team/islands 0.25.3-dev312 → 0.25.3-dev316
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/dist/{ErrorBoundary-CCrWQ8Zi.js → ErrorBoundary-C3G05Nb1.js} +1 -1
- package/dist/{chat-ui-DkuxGEDp.js → chat-ui-DASKYW38.js} +13 -13
- package/dist/{code-visibility-DLBADpvq.js → code-visibility-B3_gXvem.js} +31 -32
- package/dist/{common-Cc-bvilP.js → common-Vd6TgXHQ.js} +5074 -5074
- package/dist/{config-BVYA1P0C.js → config-CL7mwGLX.js} +87 -129
- package/dist/{data-grid-overlay-editor-CAEih52I.js → data-grid-overlay-editor-DLEPVcX1.js} +1 -1
- package/dist/{error-entries-istW7Xbs.js → error-entries-kb4H6PAT.js} +533 -508
- package/dist/feedback-button-gqE30Uez.js +370 -0
- package/dist/{formats-CMa7L3E8.js → formats-qhBmYb0l.js} +1 -1
- package/dist/{glide-data-editor-hsf5AOwE.js → glide-data-editor-BmtEcZoo.js} +3 -3
- package/dist/{globals-Blgj9EWO.js → globals-BIt_qRYC.js} +1 -1
- package/dist/{html-to-image-pPbR2cDx.js → html-to-image-DR8VPB7E.js} +4018 -4021
- package/dist/main.js +606 -607
- package/dist/{number-overlay-editor-BgNsDtQP.js → number-overlay-editor-Bbxt9i1c.js} +1 -1
- package/dist/{process-output-BySO1nIs.js → process-output-BDMtqL6K.js} +2 -2
- package/dist/{reveal-component-MUD9gGln.js → reveal-component-DXFsIk5S.js} +4 -4
- package/dist/static-state-Dtocd9UO.js +68 -0
- package/dist/{vega-component-Bly2lDhz.js → vega-component-CeN8zClP.js} +9 -9
- package/package.json +1 -1
- package/src/__tests__/mount.test.ts +17 -58
- package/src/components/editor/actions/useRestartKernel.tsx +0 -5
- package/src/components/editor/cell/__tests__/useRunCells.test.tsx +0 -3
- package/src/components/editor/cell/useRunCells.ts +2 -2
- package/src/components/editor/chrome/wrapper/footer-items/backend-status.tsx +2 -16
- package/src/components/editor/renderers/cell-array.tsx +0 -3
- package/src/core/edit-app.tsx +2 -7
- package/src/core/executions/__tests__/run.test.ts +0 -3
- package/src/core/executions/index.ts +1 -1
- package/src/core/executions/run.ts +1 -14
- package/src/core/islands/bootstrap.ts +0 -1
- package/src/core/network/__tests__/requests-network.test.ts +0 -16
- package/src/core/network/connection.ts +0 -5
- package/src/core/network/requests-toasting.tsx +1 -49
- package/src/core/network/resolve.ts +1 -5
- package/src/core/notebook/replica/__tests__/replica.test.ts +3 -3
- package/src/core/notebook/replica/connection.ts +1 -5
- package/src/core/runtime/__tests__/createWsUrl.test.ts +4 -16
- package/src/core/runtime/__tests__/runtime.test.ts +58 -168
- package/src/core/runtime/adapter.ts +1 -8
- package/src/core/runtime/config.ts +4 -59
- package/src/core/runtime/runtime.ts +1 -31
- package/src/core/runtime/types.ts +0 -5
- package/src/core/session/__tests__/useNotebookReplica.hook.test.tsx +5 -5
- package/src/core/session/__tests__/useNotebookSession.hook.test.tsx +125 -29
- package/src/core/session/connection-utils.ts +2 -14
- package/src/core/session/useNotebookSession.tsx +19 -12
- package/src/mount.tsx +3 -9
- package/src/utils/errors.ts +0 -7
- package/dist/feedback-button-BJvIOw-c.js +0 -371
- package/dist/toDate-5qPtZ6HL.js +0 -94
- package/src/components/editor/alerts/connecting-alert.tsx +0 -27
- package/src/core/network/__tests__/requests-lazy.test.ts +0 -322
- package/src/core/network/requests-lazy.ts +0 -222
|
@@ -196,7 +196,7 @@ describe("NotebookReplica lifetime", () => {
|
|
|
196
196
|
}
|
|
197
197
|
attempt.receive({
|
|
198
198
|
event: "marimo",
|
|
199
|
-
data: { marimo: { op: "
|
|
199
|
+
data: { marimo: { op: "interrupted" } },
|
|
200
200
|
});
|
|
201
201
|
await vi.waitFor(() =>
|
|
202
202
|
expect(store.get(client.connectionAtom).state).toBe(
|
|
@@ -419,10 +419,10 @@ describe("NotebookReplica lifetime", () => {
|
|
|
419
419
|
earlier.receive(kernelReady);
|
|
420
420
|
current.receive({
|
|
421
421
|
event: "marimo",
|
|
422
|
-
data: { marimo: { op: "
|
|
422
|
+
data: { marimo: { op: "interrupted" } },
|
|
423
423
|
});
|
|
424
424
|
expect(onNotification).toHaveBeenCalledExactlyOnceWith({
|
|
425
|
-
data: { op: "
|
|
425
|
+
data: { op: "interrupted" },
|
|
426
426
|
});
|
|
427
427
|
});
|
|
428
428
|
});
|
|
@@ -4,7 +4,6 @@ export const ConnectionState = {
|
|
|
4
4
|
NOT_STARTED: "NOT_STARTED",
|
|
5
5
|
CONNECTING: "CONNECTING",
|
|
6
6
|
OPEN: "OPEN",
|
|
7
|
-
CLOSING: "CLOSING",
|
|
8
7
|
CLOSED: "CLOSED",
|
|
9
8
|
} as const;
|
|
10
9
|
|
|
@@ -29,10 +28,7 @@ export type ConnectionStatus =
|
|
|
29
28
|
state: typeof ConnectionState.CONNECTING;
|
|
30
29
|
}
|
|
31
30
|
| {
|
|
32
|
-
state:
|
|
33
|
-
| typeof ConnectionState.OPEN
|
|
34
|
-
| typeof ConnectionState.CLOSING
|
|
35
|
-
| typeof ConnectionState.NOT_STARTED;
|
|
31
|
+
state: typeof ConnectionState.OPEN | typeof ConnectionState.NOT_STARTED;
|
|
36
32
|
};
|
|
37
33
|
|
|
38
34
|
/** The recovery policy for a closed connection. */
|
|
@@ -9,7 +9,6 @@ describe("RuntimeManager.getWsURL", () => {
|
|
|
9
9
|
it("should return a URL with the wss protocol when the baseURI uses https", () => {
|
|
10
10
|
const runtime = new RuntimeManager({
|
|
11
11
|
url: "https://marimo.app/",
|
|
12
|
-
lazy: true,
|
|
13
12
|
});
|
|
14
13
|
const sessionId = "1234" as SessionId;
|
|
15
14
|
const url = runtime.getWsURL(sessionId);
|
|
@@ -22,7 +21,6 @@ describe("RuntimeManager.getWsURL", () => {
|
|
|
22
21
|
it("should return a URL with the ws protocol when the baseURI uses http", () => {
|
|
23
22
|
const runtime = new RuntimeManager({
|
|
24
23
|
url: "http://marimo.app/",
|
|
25
|
-
lazy: true,
|
|
26
24
|
});
|
|
27
25
|
const sessionId = "1234" as SessionId;
|
|
28
26
|
const url = runtime.getWsURL(sessionId);
|
|
@@ -35,7 +33,6 @@ describe("RuntimeManager.getWsURL", () => {
|
|
|
35
33
|
it("should work with nested baseURI", () => {
|
|
36
34
|
const runtime = new RuntimeManager({
|
|
37
35
|
url: "http://marimo.app/nested/",
|
|
38
|
-
lazy: true,
|
|
39
36
|
});
|
|
40
37
|
const sessionId = "1234" as SessionId;
|
|
41
38
|
const url = runtime.getWsURL(sessionId);
|
|
@@ -48,7 +45,6 @@ describe("RuntimeManager.getWsURL", () => {
|
|
|
48
45
|
it("should work with nested baseURI and query params", () => {
|
|
49
46
|
const runtime = new RuntimeManager({
|
|
50
47
|
url: "http://marimo.app/nested/?foo=bar",
|
|
51
|
-
lazy: true,
|
|
52
48
|
});
|
|
53
49
|
window.history.pushState({}, "", "/nested/?file=test.py");
|
|
54
50
|
const sessionId = "1234" as SessionId;
|
|
@@ -62,7 +58,6 @@ describe("RuntimeManager.getWsURL", () => {
|
|
|
62
58
|
it("should include all query params from current page", () => {
|
|
63
59
|
const runtime = new RuntimeManager({
|
|
64
60
|
url: "http://marimo.app/",
|
|
65
|
-
lazy: true,
|
|
66
61
|
});
|
|
67
62
|
window.history.pushState({}, "", "/?file=test.py&search=test&other=last");
|
|
68
63
|
const sessionId = "1234" as SessionId;
|
|
@@ -83,13 +78,9 @@ describe.each(["getWsURL", "getSseURL"] as const)(
|
|
|
83
78
|
(method) => {
|
|
84
79
|
it("preserves runtime parameters and repeated page values", () => {
|
|
85
80
|
window.history.pushState({}, "", "/?tag=page-one&tag=page-two");
|
|
86
|
-
const runtime = new RuntimeManager(
|
|
87
|
-
{
|
|
88
|
-
|
|
89
|
-
lazy: true,
|
|
90
|
-
},
|
|
91
|
-
true,
|
|
92
|
-
);
|
|
81
|
+
const runtime = new RuntimeManager({
|
|
82
|
+
url: `${window.location.origin}/nested?proxy_token=secret&tag=base&base=one&base=two`,
|
|
83
|
+
});
|
|
93
84
|
const url = runtime[method]("session" as SessionId);
|
|
94
85
|
expect([...url.searchParams]).toEqual([
|
|
95
86
|
["proxy_token", "secret"],
|
|
@@ -122,10 +113,7 @@ describe.each(["getWsURL", "getSseURL"] as const)(
|
|
|
122
113
|
"",
|
|
123
114
|
`/?${notebookParams}&session_id=page-session`,
|
|
124
115
|
);
|
|
125
|
-
const runtime = new RuntimeManager(
|
|
126
|
-
{ url: window.location.origin, lazy: true },
|
|
127
|
-
true,
|
|
128
|
-
);
|
|
116
|
+
const runtime = new RuntimeManager({ url: window.location.origin });
|
|
129
117
|
const url = runtime[method]("actual-session" as SessionId);
|
|
130
118
|
|
|
131
119
|
expect(Object.fromEntries(url.searchParams)).toEqual({
|
|
@@ -18,13 +18,11 @@ vi.mock("@/utils/Logger", () => ({ Logger: Mocks.quietLogger() }));
|
|
|
18
18
|
describe("RuntimeManager", () => {
|
|
19
19
|
const mockConfig: RuntimeConfig = {
|
|
20
20
|
url: "https://example.com",
|
|
21
|
-
lazy: true,
|
|
22
21
|
authToken: "test-token",
|
|
23
22
|
};
|
|
24
23
|
|
|
25
24
|
const mockConfigWithoutToken: RuntimeConfig = {
|
|
26
25
|
url: "http://localhost:8080",
|
|
27
|
-
lazy: true,
|
|
28
26
|
};
|
|
29
27
|
|
|
30
28
|
describe("constructor", () => {
|
|
@@ -45,7 +43,6 @@ describe("RuntimeManager", () => {
|
|
|
45
43
|
it("should handle URLs without trailing slash", () => {
|
|
46
44
|
const runtime = new RuntimeManager({
|
|
47
45
|
url: "https://example.com/path",
|
|
48
|
-
lazy: true,
|
|
49
46
|
});
|
|
50
47
|
expect(runtime.httpURL.toString()).toBe("https://example.com/path");
|
|
51
48
|
});
|
|
@@ -78,7 +75,6 @@ describe("RuntimeManager", () => {
|
|
|
78
75
|
it("should preserve existing query params", () => {
|
|
79
76
|
const runtime = new RuntimeManager({
|
|
80
77
|
url: "http://example.com?foo=bar",
|
|
81
|
-
lazy: true,
|
|
82
78
|
});
|
|
83
79
|
const sessionId = "1234" as SessionId;
|
|
84
80
|
const url = runtime.getWsURL(sessionId);
|
|
@@ -91,14 +87,10 @@ describe("RuntimeManager", () => {
|
|
|
91
87
|
describe("cross-origin auth token in WS URLs", () => {
|
|
92
88
|
it("should add access_token to WS URL when cross-origin with authToken", () => {
|
|
93
89
|
// example.com is cross-origin relative to the test environment (localhost)
|
|
94
|
-
const runtime = new RuntimeManager(
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
authToken: "my-secret-token",
|
|
99
|
-
},
|
|
100
|
-
true,
|
|
101
|
-
);
|
|
90
|
+
const runtime = new RuntimeManager({
|
|
91
|
+
url: "https://sandbox.example.com",
|
|
92
|
+
authToken: "my-secret-token",
|
|
93
|
+
});
|
|
102
94
|
const url = runtime.getWsURL("s_123" as SessionId);
|
|
103
95
|
|
|
104
96
|
expect(url.searchParams.get("access_token")).toBe("my-secret-token");
|
|
@@ -106,41 +98,29 @@ describe("RuntimeManager", () => {
|
|
|
106
98
|
});
|
|
107
99
|
|
|
108
100
|
it("should not add access_token to WS URL when same-origin", () => {
|
|
109
|
-
const runtime = new RuntimeManager(
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
authToken: "my-secret-token",
|
|
114
|
-
},
|
|
115
|
-
true,
|
|
116
|
-
);
|
|
101
|
+
const runtime = new RuntimeManager({
|
|
102
|
+
url: window.location.origin,
|
|
103
|
+
authToken: "my-secret-token",
|
|
104
|
+
});
|
|
117
105
|
const url = runtime.getWsURL("s_123" as SessionId);
|
|
118
106
|
|
|
119
107
|
expect(url.searchParams.get("access_token")).toBeNull();
|
|
120
108
|
});
|
|
121
109
|
|
|
122
110
|
it("should not add access_token when no authToken is configured", () => {
|
|
123
|
-
const runtime = new RuntimeManager(
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
lazy: true,
|
|
127
|
-
},
|
|
128
|
-
true,
|
|
129
|
-
);
|
|
111
|
+
const runtime = new RuntimeManager({
|
|
112
|
+
url: "https://sandbox.example.com",
|
|
113
|
+
});
|
|
130
114
|
const url = runtime.getWsURL("s_123" as SessionId);
|
|
131
115
|
|
|
132
116
|
expect(url.searchParams.get("access_token")).toBeNull();
|
|
133
117
|
});
|
|
134
118
|
|
|
135
119
|
it("should add access_token to all WS URL types when cross-origin", () => {
|
|
136
|
-
const runtime = new RuntimeManager(
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
authToken: "my-secret-token",
|
|
141
|
-
},
|
|
142
|
-
true,
|
|
143
|
-
);
|
|
120
|
+
const runtime = new RuntimeManager({
|
|
121
|
+
url: "https://sandbox.example.com",
|
|
122
|
+
authToken: "my-secret-token",
|
|
123
|
+
});
|
|
144
124
|
|
|
145
125
|
const wsUrl = runtime.getWsURL("s_123" as SessionId);
|
|
146
126
|
const terminalUrl = runtime.getTerminalWsURL();
|
|
@@ -154,14 +134,10 @@ describe("RuntimeManager", () => {
|
|
|
154
134
|
|
|
155
135
|
describe("formatNavigableHttpURL", () => {
|
|
156
136
|
it("adds access_token when cross-origin with authToken", () => {
|
|
157
|
-
const runtime = new RuntimeManager(
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
authToken: "my-secret-token",
|
|
162
|
-
},
|
|
163
|
-
true,
|
|
164
|
-
);
|
|
137
|
+
const runtime = new RuntimeManager({
|
|
138
|
+
url: "https://sandbox.example.com",
|
|
139
|
+
authToken: "my-secret-token",
|
|
140
|
+
});
|
|
165
141
|
const url = runtime.formatNavigableHttpURL(
|
|
166
142
|
"api/files/download",
|
|
167
143
|
new URLSearchParams({ path: "/tmp/data.csv" }),
|
|
@@ -173,14 +149,10 @@ describe("RuntimeManager", () => {
|
|
|
173
149
|
});
|
|
174
150
|
|
|
175
151
|
it("omits access_token when same-origin", () => {
|
|
176
|
-
const runtime = new RuntimeManager(
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
authToken: "my-secret-token",
|
|
181
|
-
},
|
|
182
|
-
true,
|
|
183
|
-
);
|
|
152
|
+
const runtime = new RuntimeManager({
|
|
153
|
+
url: window.location.origin,
|
|
154
|
+
authToken: "my-secret-token",
|
|
155
|
+
});
|
|
184
156
|
const url = runtime.formatNavigableHttpURL(
|
|
185
157
|
"api/files/download",
|
|
186
158
|
new URLSearchParams({ path: "/tmp/data.csv" }),
|
|
@@ -191,20 +163,16 @@ describe("RuntimeManager", () => {
|
|
|
191
163
|
});
|
|
192
164
|
|
|
193
165
|
it("omits access_token when no authToken configured", () => {
|
|
194
|
-
const runtime = new RuntimeManager(
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
);
|
|
166
|
+
const runtime = new RuntimeManager({
|
|
167
|
+
url: "https://sandbox.example.com",
|
|
168
|
+
});
|
|
198
169
|
const url = runtime.formatNavigableHttpURL("api/files/download");
|
|
199
170
|
|
|
200
171
|
expect(url.searchParams.get("access_token")).toBeNull();
|
|
201
172
|
});
|
|
202
173
|
|
|
203
174
|
it("does not forward a page-inherited access_token when same-origin", () => {
|
|
204
|
-
const runtime = new RuntimeManager(
|
|
205
|
-
{ url: window.location.origin, lazy: true },
|
|
206
|
-
true,
|
|
207
|
-
);
|
|
175
|
+
const runtime = new RuntimeManager({ url: window.location.origin });
|
|
208
176
|
const original = window.location.href;
|
|
209
177
|
window.history.replaceState({}, "", "/?access_token=page-token");
|
|
210
178
|
try {
|
|
@@ -218,14 +186,10 @@ describe("RuntimeManager", () => {
|
|
|
218
186
|
|
|
219
187
|
describe("getNotebookURL", () => {
|
|
220
188
|
it("addresses the notebook below the host API without the auth token", () => {
|
|
221
|
-
const runtime = new RuntimeManager(
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
authToken: "my-secret-token",
|
|
226
|
-
},
|
|
227
|
-
true,
|
|
228
|
-
);
|
|
189
|
+
const runtime = new RuntimeManager({
|
|
190
|
+
url: "https://sandbox.example.com/base",
|
|
191
|
+
authToken: "my-secret-token",
|
|
192
|
+
});
|
|
229
193
|
const url = runtime.getNotebookURL("n/1");
|
|
230
194
|
|
|
231
195
|
expect(url.pathname).toBe("/base/api/marimo/v1/notebooks/n%2F1");
|
|
@@ -238,14 +202,10 @@ describe("RuntimeManager", () => {
|
|
|
238
202
|
// SSE requests can send headers, so auth travels in the
|
|
239
203
|
// Authorization header; a token in the URL would leak into
|
|
240
204
|
// proxy and server logs.
|
|
241
|
-
const runtime = new RuntimeManager(
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
authToken: "my-secret-token",
|
|
246
|
-
},
|
|
247
|
-
true,
|
|
248
|
-
);
|
|
205
|
+
const runtime = new RuntimeManager({
|
|
206
|
+
url: "https://sandbox.example.com",
|
|
207
|
+
authToken: "my-secret-token",
|
|
208
|
+
});
|
|
249
209
|
const url = runtime.getSseURL("s_123" as SessionId);
|
|
250
210
|
|
|
251
211
|
expect(url.searchParams.get("access_token")).toBeNull();
|
|
@@ -303,7 +263,6 @@ describe("RuntimeManager", () => {
|
|
|
303
263
|
it("should return copilot URL without non-auth query params", () => {
|
|
304
264
|
const runtime = new RuntimeManager({
|
|
305
265
|
url: "https://example.com?foo=bar&baz=qux",
|
|
306
|
-
lazy: true,
|
|
307
266
|
});
|
|
308
267
|
const url = runtime.getLSPURL("copilot");
|
|
309
268
|
|
|
@@ -314,14 +273,10 @@ describe("RuntimeManager", () => {
|
|
|
314
273
|
});
|
|
315
274
|
|
|
316
275
|
it("should preserve access_token on copilot URL when cross-origin", () => {
|
|
317
|
-
const runtime = new RuntimeManager(
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
authToken: "my-secret-token",
|
|
322
|
-
},
|
|
323
|
-
true,
|
|
324
|
-
);
|
|
276
|
+
const runtime = new RuntimeManager({
|
|
277
|
+
url: "https://sandbox.example.com?foo=bar",
|
|
278
|
+
authToken: "my-secret-token",
|
|
279
|
+
});
|
|
325
280
|
const url = runtime.getLSPURL("copilot");
|
|
326
281
|
|
|
327
282
|
expect(url.protocol).toBe("wss:");
|
|
@@ -332,14 +287,10 @@ describe("RuntimeManager", () => {
|
|
|
332
287
|
});
|
|
333
288
|
|
|
334
289
|
it("should not have access_token on copilot URL when same-origin", () => {
|
|
335
|
-
const runtime = new RuntimeManager(
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
authToken: "my-secret-token",
|
|
340
|
-
},
|
|
341
|
-
true,
|
|
342
|
-
);
|
|
290
|
+
const runtime = new RuntimeManager({
|
|
291
|
+
url: window.location.origin,
|
|
292
|
+
authToken: "my-secret-token",
|
|
293
|
+
});
|
|
343
294
|
const url = runtime.getLSPURL("copilot");
|
|
344
295
|
|
|
345
296
|
expect(url.protocol).toBe("ws:");
|
|
@@ -431,13 +382,10 @@ describe("RuntimeManager", () => {
|
|
|
431
382
|
url: "https://sandbox.example.com/health?some_value=abc123",
|
|
432
383
|
});
|
|
433
384
|
|
|
434
|
-
const runtime = new RuntimeManager(
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
},
|
|
439
|
-
true,
|
|
440
|
-
);
|
|
385
|
+
const runtime = new RuntimeManager({
|
|
386
|
+
...mockConfig,
|
|
387
|
+
url: "https://backend.example.com/lazy?some_value=abc123",
|
|
388
|
+
});
|
|
441
389
|
const result = await runtime.probeHealth();
|
|
442
390
|
expect(result).toBe(true);
|
|
443
391
|
expect(runtime.httpURL.hostname).toBe("backend.example.com");
|
|
@@ -452,13 +400,10 @@ describe("RuntimeManager", () => {
|
|
|
452
400
|
url: "https://sandbox.example.com/health?some_value=abc123",
|
|
453
401
|
});
|
|
454
402
|
|
|
455
|
-
const runtime = new RuntimeManager(
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
},
|
|
460
|
-
true, // lazy — don't call init() in constructor
|
|
461
|
-
);
|
|
403
|
+
const runtime = new RuntimeManager({
|
|
404
|
+
...mockConfig,
|
|
405
|
+
url: "https://backend.example.com/lazy?some_value=abc123",
|
|
406
|
+
});
|
|
462
407
|
const result = await runtime.reconcileFromHealth();
|
|
463
408
|
|
|
464
409
|
expect(result).toBe(true);
|
|
@@ -476,7 +421,7 @@ describe("RuntimeManager", () => {
|
|
|
476
421
|
|
|
477
422
|
describe("waitForHealthy", () => {
|
|
478
423
|
it("should resolve immediately if healthy", async () => {
|
|
479
|
-
const runtime = new RuntimeManager(mockConfig
|
|
424
|
+
const runtime = new RuntimeManager(mockConfig);
|
|
480
425
|
|
|
481
426
|
vi.spyOn(runtime, "reconcileFromHealth").mockResolvedValue(true);
|
|
482
427
|
runtime.init();
|
|
@@ -485,7 +430,7 @@ describe("RuntimeManager", () => {
|
|
|
485
430
|
});
|
|
486
431
|
|
|
487
432
|
it("should retry and eventually succeed", async () => {
|
|
488
|
-
const runtime = new RuntimeManager(mockConfig
|
|
433
|
+
const runtime = new RuntimeManager(mockConfig);
|
|
489
434
|
const healthySpy = vi
|
|
490
435
|
.spyOn(runtime, "reconcileFromHealth")
|
|
491
436
|
.mockResolvedValueOnce(false)
|
|
@@ -499,7 +444,7 @@ describe("RuntimeManager", () => {
|
|
|
499
444
|
});
|
|
500
445
|
|
|
501
446
|
it("should throw after max retries", async () => {
|
|
502
|
-
const runtime = new RuntimeManager(mockConfig
|
|
447
|
+
const runtime = new RuntimeManager(mockConfig);
|
|
503
448
|
vi.spyOn(runtime, "reconcileFromHealth").mockResolvedValue(false);
|
|
504
449
|
runtime.init({ disableRetryDelay: true });
|
|
505
450
|
|
|
@@ -509,42 +454,6 @@ describe("RuntimeManager", () => {
|
|
|
509
454
|
});
|
|
510
455
|
});
|
|
511
456
|
|
|
512
|
-
describe("start", () => {
|
|
513
|
-
it("starts once and lets later callers join the same attempt", async () => {
|
|
514
|
-
const runtime = new RuntimeManager(mockConfig, true);
|
|
515
|
-
const healthySpy = vi
|
|
516
|
-
.spyOn(runtime, "reconcileFromHealth")
|
|
517
|
-
.mockResolvedValue(true);
|
|
518
|
-
|
|
519
|
-
await Promise.all([runtime.start(), runtime.start()]);
|
|
520
|
-
|
|
521
|
-
expect(healthySpy).toHaveBeenCalledTimes(1);
|
|
522
|
-
});
|
|
523
|
-
|
|
524
|
-
it("tries again after an attempt gave up", async () => {
|
|
525
|
-
const runtime = new RuntimeManager(mockConfig, true);
|
|
526
|
-
const healthySpy = vi
|
|
527
|
-
.spyOn(runtime, "reconcileFromHealth")
|
|
528
|
-
.mockResolvedValue(false);
|
|
529
|
-
vi.spyOn(runtime, "init").mockImplementation(async () => {
|
|
530
|
-
await runtime.reconcileFromHealth();
|
|
531
|
-
throw new Error("Failed to connect after 25 retries");
|
|
532
|
-
});
|
|
533
|
-
|
|
534
|
-
await expect(runtime.start()).rejects.toThrow("Failed to connect");
|
|
535
|
-
healthySpy.mockResolvedValue(true);
|
|
536
|
-
vi.spyOn(runtime, "init").mockImplementation(async () => {
|
|
537
|
-
await runtime.reconcileFromHealth();
|
|
538
|
-
await runtime.waitForHealthy().catch(() => undefined);
|
|
539
|
-
});
|
|
540
|
-
|
|
541
|
-
// The second attempt is a new one, not the old failure replayed.
|
|
542
|
-
vi.spyOn(runtime, "waitForHealthy").mockResolvedValue(undefined);
|
|
543
|
-
await expect(runtime.start()).resolves.toBeUndefined();
|
|
544
|
-
expect(healthySpy).toHaveBeenCalledTimes(2);
|
|
545
|
-
});
|
|
546
|
-
});
|
|
547
|
-
|
|
548
457
|
describe("headers", () => {
|
|
549
458
|
it("should return headers with auth token", () => {
|
|
550
459
|
const runtime = new RuntimeManager(mockConfig);
|
|
@@ -646,7 +555,6 @@ describe("RuntimeManager", () => {
|
|
|
646
555
|
it("should remove query params from base URI", async () => {
|
|
647
556
|
const configWithQueryParams: RuntimeConfig = {
|
|
648
557
|
url: "https://example.com/foo?param1=value1¶m2=value2",
|
|
649
|
-
lazy: true,
|
|
650
558
|
authToken: "test-token",
|
|
651
559
|
};
|
|
652
560
|
|
|
@@ -670,7 +578,6 @@ describe("RuntimeManager", () => {
|
|
|
670
578
|
it("should handle URLs with nested paths", () => {
|
|
671
579
|
const runtime = new RuntimeManager({
|
|
672
580
|
url: "https://example.com/nested/path/",
|
|
673
|
-
lazy: true,
|
|
674
581
|
});
|
|
675
582
|
const wsUrl = runtime.getWsURL("test" as SessionId);
|
|
676
583
|
const aiUrl = runtime.getAiURL("completion");
|
|
@@ -692,7 +599,6 @@ describe("RuntimeManager", () => {
|
|
|
692
599
|
|
|
693
600
|
const runtime = new RuntimeManager({
|
|
694
601
|
url: "https://example.com/path?base_param=existing",
|
|
695
|
-
lazy: true,
|
|
696
602
|
});
|
|
697
603
|
|
|
698
604
|
const wsUrl = runtime.getWsURL("test" as SessionId);
|
|
@@ -734,7 +640,6 @@ describe("RuntimeManager", () => {
|
|
|
734
640
|
it("should handle URLs with query parameters and fragments", () => {
|
|
735
641
|
const runtime = new RuntimeManager({
|
|
736
642
|
url: "https://example.com/path?existing=param#fragment",
|
|
737
|
-
lazy: true,
|
|
738
643
|
});
|
|
739
644
|
const wsUrl = runtime.getWsURL("test" as SessionId);
|
|
740
645
|
|
|
@@ -746,18 +651,16 @@ describe("RuntimeManager", () => {
|
|
|
746
651
|
|
|
747
652
|
it("should throw for invalid URLs", () => {
|
|
748
653
|
expect(() => {
|
|
749
|
-
new RuntimeManager({ url: "not-a-url"
|
|
654
|
+
new RuntimeManager({ url: "not-a-url" });
|
|
750
655
|
}).toThrow("Invalid runtime URL");
|
|
751
656
|
});
|
|
752
657
|
|
|
753
658
|
it("should handle http to ws conversion correctly", () => {
|
|
754
659
|
const httpRuntime = new RuntimeManager({
|
|
755
660
|
url: "http://localhost",
|
|
756
|
-
lazy: true,
|
|
757
661
|
});
|
|
758
662
|
const httpsRuntime = new RuntimeManager({
|
|
759
663
|
url: "https://localhost",
|
|
760
|
-
lazy: true,
|
|
761
664
|
});
|
|
762
665
|
|
|
763
666
|
expect(httpRuntime.getWsURL("test" as SessionId).protocol).toBe("ws:");
|
|
@@ -767,7 +670,6 @@ describe("RuntimeManager", () => {
|
|
|
767
670
|
it("should handle blob URLs", () => {
|
|
768
671
|
const runtime = new RuntimeManager({
|
|
769
672
|
url: "blob:https://example.com/12345678-1234-1234-1234-123456789abc",
|
|
770
|
-
lazy: true,
|
|
771
673
|
});
|
|
772
674
|
|
|
773
675
|
expect(runtime.httpURL.toString()).toBe(
|
|
@@ -778,7 +680,6 @@ describe("RuntimeManager", () => {
|
|
|
778
680
|
it("warns when creating WebSocket URLs from blob URLs", () => {
|
|
779
681
|
const runtime = new RuntimeManager({
|
|
780
682
|
url: "blob:https://example.com/12345678-1234-1234-1234-123456789abc",
|
|
781
|
-
lazy: true,
|
|
782
683
|
});
|
|
783
684
|
const sessionId = "test" as SessionId;
|
|
784
685
|
|
|
@@ -791,7 +692,6 @@ describe("RuntimeManager", () => {
|
|
|
791
692
|
it("should handle blob URLs in AI URLs", () => {
|
|
792
693
|
const runtime = new RuntimeManager({
|
|
793
694
|
url: "blob:https://example.com/12345678-1234-1234-1234-123456789abc",
|
|
794
|
-
lazy: true,
|
|
795
695
|
});
|
|
796
696
|
const aiUrl = runtime.getAiURL("completion");
|
|
797
697
|
|
|
@@ -804,7 +704,6 @@ describe("RuntimeManager", () => {
|
|
|
804
704
|
it("should handle blob URLs in health check URLs", () => {
|
|
805
705
|
const runtime = new RuntimeManager({
|
|
806
706
|
url: "blob:https://example.com/12345678-1234-1234-1234-123456789abc",
|
|
807
|
-
lazy: true,
|
|
808
707
|
});
|
|
809
708
|
const healthUrl = runtime.healthURL();
|
|
810
709
|
|
|
@@ -817,7 +716,6 @@ describe("RuntimeManager", () => {
|
|
|
817
716
|
it("should handle URLs with userinfo", () => {
|
|
818
717
|
const runtime = new RuntimeManager({
|
|
819
718
|
url: "https://user:pass@example.com",
|
|
820
|
-
lazy: true,
|
|
821
719
|
});
|
|
822
720
|
const wsUrl = runtime.getWsURL("test" as SessionId);
|
|
823
721
|
|
|
@@ -830,7 +728,6 @@ describe("RuntimeManager", () => {
|
|
|
830
728
|
it("should handle IPv6 addresses", () => {
|
|
831
729
|
const runtime = new RuntimeManager({
|
|
832
730
|
url: "http://[::1]:8080",
|
|
833
|
-
lazy: true,
|
|
834
731
|
});
|
|
835
732
|
const wsUrl = runtime.getWsURL("test" as SessionId);
|
|
836
733
|
|
|
@@ -842,7 +739,6 @@ describe("RuntimeManager", () => {
|
|
|
842
739
|
it("should handle URLs with encoded characters", () => {
|
|
843
740
|
const runtime = new RuntimeManager({
|
|
844
741
|
url: "https://example.com/path%20with%20spaces",
|
|
845
|
-
lazy: true,
|
|
846
742
|
});
|
|
847
743
|
const aiUrl = runtime.getAiURL("completion");
|
|
848
744
|
|
|
@@ -852,7 +748,6 @@ describe("RuntimeManager", () => {
|
|
|
852
748
|
it("should handle URLs with multiple trailing slashes", () => {
|
|
853
749
|
const runtime = new RuntimeManager({
|
|
854
750
|
url: "https://example.com/path///",
|
|
855
|
-
lazy: true,
|
|
856
751
|
});
|
|
857
752
|
const aiUrl = runtime.getAiURL("completion");
|
|
858
753
|
|
|
@@ -862,7 +757,6 @@ describe("RuntimeManager", () => {
|
|
|
862
757
|
it("should handle URLs with port numbers", () => {
|
|
863
758
|
const runtime = new RuntimeManager({
|
|
864
759
|
url: "https://example.com:9443/app",
|
|
865
|
-
lazy: true,
|
|
866
760
|
});
|
|
867
761
|
const wsUrl = runtime.getWsURL("test" as SessionId);
|
|
868
762
|
|
|
@@ -880,7 +774,7 @@ describe("RuntimeManager", () => {
|
|
|
880
774
|
];
|
|
881
775
|
|
|
882
776
|
variants.forEach((url) => {
|
|
883
|
-
const runtime = new RuntimeManager({ url
|
|
777
|
+
const runtime = new RuntimeManager({ url });
|
|
884
778
|
const wsUrl = runtime.getWsURL("test" as SessionId);
|
|
885
779
|
expect(wsUrl.protocol).toBe("ws:");
|
|
886
780
|
});
|
|
@@ -889,7 +783,6 @@ describe("RuntimeManager", () => {
|
|
|
889
783
|
it("should handle URLs with complex query parameters", () => {
|
|
890
784
|
const runtime = new RuntimeManager({
|
|
891
785
|
url: "https://example.com?param1=value1¶m2=value%20encoded&empty=",
|
|
892
|
-
lazy: true,
|
|
893
786
|
});
|
|
894
787
|
const wsUrl = runtime.getWsURL("test" as SessionId);
|
|
895
788
|
|
|
@@ -902,7 +795,6 @@ describe("RuntimeManager", () => {
|
|
|
902
795
|
it("should accept data URLs (valid URL format)", () => {
|
|
903
796
|
const runtime = new RuntimeManager({
|
|
904
797
|
url: "data:text/plain;base64,SGVsbG8gV29ybGQ=",
|
|
905
|
-
lazy: true,
|
|
906
798
|
});
|
|
907
799
|
expect(runtime.httpURL.protocol).toBe("data:");
|
|
908
800
|
});
|
|
@@ -910,7 +802,6 @@ describe("RuntimeManager", () => {
|
|
|
910
802
|
it("should accept file URLs (valid URL format)", () => {
|
|
911
803
|
const runtime = new RuntimeManager({
|
|
912
804
|
url: "file:///path/to/file",
|
|
913
|
-
lazy: true,
|
|
914
805
|
});
|
|
915
806
|
expect(runtime.httpURL.protocol).toBe("file:");
|
|
916
807
|
});
|
|
@@ -918,14 +809,13 @@ describe("RuntimeManager", () => {
|
|
|
918
809
|
it("should accept custom protocol URLs (valid URL format)", () => {
|
|
919
810
|
const runtime = new RuntimeManager({
|
|
920
811
|
url: "custom://example.com",
|
|
921
|
-
lazy: true,
|
|
922
812
|
});
|
|
923
813
|
expect(runtime.httpURL.protocol).toBe("custom:");
|
|
924
814
|
});
|
|
925
815
|
|
|
926
816
|
it("should handle empty string URL", () => {
|
|
927
817
|
expect(() => {
|
|
928
|
-
new RuntimeManager({ url: ""
|
|
818
|
+
new RuntimeManager({ url: "" });
|
|
929
819
|
}).toThrow("Invalid runtime URL");
|
|
930
820
|
});
|
|
931
821
|
|
|
@@ -940,7 +830,7 @@ describe("RuntimeManager", () => {
|
|
|
940
830
|
|
|
941
831
|
malformedUrls.forEach((url) => {
|
|
942
832
|
expect(() => {
|
|
943
|
-
new RuntimeManager({ url
|
|
833
|
+
new RuntimeManager({ url });
|
|
944
834
|
}).toThrow("Invalid runtime URL");
|
|
945
835
|
});
|
|
946
836
|
});
|
|
@@ -82,17 +82,10 @@ const remoteStateAtom = atom<AdapterState>((get) => {
|
|
|
82
82
|
return { kind: "connecting", progress: { label: "Connecting…" } };
|
|
83
83
|
case ConnectionState.NOT_STARTED:
|
|
84
84
|
return { kind: "connecting", progress: { label: "Not connected" } };
|
|
85
|
-
case ConnectionState.CLOSING:
|
|
86
85
|
case ConnectionState.CLOSED:
|
|
87
86
|
return {
|
|
88
87
|
kind: "failed",
|
|
89
|
-
error: {
|
|
90
|
-
message:
|
|
91
|
-
conn.state === ConnectionState.CLOSED
|
|
92
|
-
? conn.reason
|
|
93
|
-
: "Disconnecting",
|
|
94
|
-
errorKind: "runtime",
|
|
95
|
-
},
|
|
88
|
+
error: { message: conn.reason, errorKind: "runtime" },
|
|
96
89
|
};
|
|
97
90
|
}
|
|
98
91
|
});
|