@ai-ecoverse/slicc-kernel 1.19.0 → 1.20.0

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/index.js CHANGED
@@ -1,3 +1,242 @@
1
+ // src/client/dial-stream.ts
2
+ function errorWithCode(message) {
3
+ const code = /^E[A-Z]+/.exec(message)?.[0];
4
+ return Object.assign(new Error(message), code ? { code } : {});
5
+ }
6
+ function dialStream(port, onClose) {
7
+ let closed = false;
8
+ let reader;
9
+ const acks = [];
10
+ const readable = new ReadableStream({
11
+ start(controller) {
12
+ reader = controller;
13
+ },
14
+ pull() {
15
+ if (!closed) port.postMessage({ more: true });
16
+ },
17
+ cancel() {
18
+ reader = void 0;
19
+ close();
20
+ }
21
+ });
22
+ const finish = (fail2) => {
23
+ const controller = reader;
24
+ reader = void 0;
25
+ for (const ack of acks.splice(0)) {
26
+ if (fail2) ack.reject(fail2);
27
+ else ack.resolve();
28
+ }
29
+ if (!controller) return;
30
+ if (fail2) controller.error(fail2);
31
+ else controller.close();
32
+ };
33
+ function shut() {
34
+ if (closed) return;
35
+ closed = true;
36
+ port.close();
37
+ onClose?.();
38
+ }
39
+ function close() {
40
+ if (!closed) port.postMessage({ close: true });
41
+ shut();
42
+ finish();
43
+ }
44
+ port.addEventListener("message", ({ data }) => {
45
+ if (data instanceof Uint8Array) reader?.enqueue(data);
46
+ else if ("ack" in data) acks.shift()?.resolve();
47
+ else if ("end" in data) finish();
48
+ else finish(errorWithCode(data.error));
49
+ });
50
+ port.start?.();
51
+ const writable = new WritableStream({
52
+ write(chunk) {
53
+ if (closed) throw errorWithCode("EPIPE: the connection is closed");
54
+ const copy = chunk.slice();
55
+ const acked = new Promise((resolve, reject) => acks.push({ resolve, reject }));
56
+ port.postMessage(copy, [copy.buffer]);
57
+ return acked;
58
+ },
59
+ close() {
60
+ if (!closed) port.postMessage({ end: true });
61
+ },
62
+ abort() {
63
+ close();
64
+ }
65
+ });
66
+ const fail = (error) => {
67
+ shut();
68
+ finish(error);
69
+ };
70
+ return { readable, writable, close, fail };
71
+ }
72
+
73
+ // src/client/loopback-fetch.ts
74
+ var OWN_HEADERS = /* @__PURE__ */ new Set(["host", "connection", "content-length", "transfer-encoding"]);
75
+ var encoder = new TextEncoder();
76
+ var latin1 = new TextDecoder("latin1");
77
+ var ByteSource = class {
78
+ buffered = new Uint8Array(0);
79
+ reader;
80
+ constructor(reader) {
81
+ this.reader = reader;
82
+ }
83
+ async more() {
84
+ const { done, value } = await this.reader.read();
85
+ if (done) return false;
86
+ const joined = new Uint8Array(this.buffered.length + value.length);
87
+ joined.set(this.buffered);
88
+ joined.set(value, this.buffered.length);
89
+ this.buffered = joined;
90
+ return true;
91
+ }
92
+ async until(marker) {
93
+ const needle = encoder.encode(marker);
94
+ for (; ; ) {
95
+ const at = indexOf(this.buffered, needle);
96
+ if (at >= 0) return this.take(at + needle.length);
97
+ if (!await this.more()) return void 0;
98
+ }
99
+ }
100
+ async some(max) {
101
+ if (this.buffered.length === 0 && !await this.more()) return void 0;
102
+ return this.take(Math.min(max, this.buffered.length));
103
+ }
104
+ async exactly(count) {
105
+ while (this.buffered.length < count) if (!await this.more()) return void 0;
106
+ return this.take(count);
107
+ }
108
+ take(count) {
109
+ const out = this.buffered.slice(0, count);
110
+ this.buffered = this.buffered.slice(count);
111
+ return out;
112
+ }
113
+ };
114
+ function indexOf(haystack, needle) {
115
+ outer: for (let i = 0; i + needle.length <= haystack.length; i++) {
116
+ for (let j = 0; j < needle.length; j++) if (haystack[i + j] !== needle[j]) continue outer;
117
+ return i;
118
+ }
119
+ return -1;
120
+ }
121
+ function parseHead(bytes) {
122
+ const [statusLine = "", ...lines] = latin1.decode(bytes).split("\r\n");
123
+ const match = /^HTTP\/1\.[01] (\d{3})(?: (.*))?$/.exec(statusLine);
124
+ if (!match) throw errorWithCode(`EPROTO: not an HTTP/1.x response: ${statusLine.slice(0, 80)}`);
125
+ const headers = [];
126
+ for (const line of lines) {
127
+ const colon = line.indexOf(":");
128
+ if (colon > 0) headers.push([line.slice(0, colon).trim(), line.slice(colon + 1).trim()]);
129
+ }
130
+ return { status: Number(match[1]), statusText: match[2] ?? "", headers };
131
+ }
132
+ function header(head, name) {
133
+ return head.headers.find(([key]) => key.toLowerCase() === name)?.[1];
134
+ }
135
+ async function* chunked(source) {
136
+ for (; ; ) {
137
+ const line = await source.until("\r\n");
138
+ if (!line) throw errorWithCode("EPROTO: the chunked body ended early");
139
+ const size = Number.parseInt(latin1.decode(line).trim().split(";")[0] ?? "", 16);
140
+ if (!Number.isFinite(size)) throw errorWithCode("EPROTO: a malformed chunk size");
141
+ if (size === 0) return;
142
+ const data = await source.exactly(size);
143
+ if (!data || !await source.exactly(2)) throw errorWithCode("EPROTO: a chunk ended early");
144
+ yield data;
145
+ }
146
+ }
147
+ async function* counted(source, length) {
148
+ let left = length;
149
+ while (left > 0) {
150
+ const data = await source.some(left);
151
+ if (!data) throw errorWithCode("EPROTO: the body ended early");
152
+ left -= data.length;
153
+ yield data;
154
+ }
155
+ }
156
+ async function* untilClose(source) {
157
+ for (let data = await source.some(65536); data; data = await source.some(65536)) yield data;
158
+ }
159
+ function bodyOf(source, head, method) {
160
+ if (method === "HEAD" || head.status === 204 || head.status === 304) return void 0;
161
+ if (header(head, "transfer-encoding")?.toLowerCase().includes("chunked")) return chunked(source);
162
+ const length = header(head, "content-length");
163
+ if (length === void 0) return untilClose(source);
164
+ const size = Number(length);
165
+ if (!/^\d+$/.test(length.trim()) || !Number.isSafeInteger(size)) {
166
+ throw errorWithCode(`EPROTO: a malformed Content-Length: ${length}`);
167
+ }
168
+ return counted(source, size);
169
+ }
170
+ function stream(chunks3, socket, signal) {
171
+ return new ReadableStream({
172
+ start(controller) {
173
+ signal?.addEventListener("abort", () => controller.error(signal.reason), { once: true });
174
+ },
175
+ async pull(controller) {
176
+ try {
177
+ const next = await chunks3.next();
178
+ if (next.done) {
179
+ controller.close();
180
+ socket.close();
181
+ } else controller.enqueue(next.value);
182
+ } catch (err) {
183
+ controller.error(err);
184
+ socket.close();
185
+ }
186
+ },
187
+ cancel() {
188
+ socket.close();
189
+ }
190
+ });
191
+ }
192
+ async function requestBytes(request) {
193
+ const url = new URL(request.url);
194
+ const body = request.body ? new Uint8Array(await request.arrayBuffer()) : void 0;
195
+ const lines = [`${request.method} ${url.pathname}${url.search} HTTP/1.1`, `Host: ${url.host}`];
196
+ request.headers.forEach((value, name) => {
197
+ if (!OWN_HEADERS.has(name)) lines.push(`${name}: ${value}`);
198
+ });
199
+ if (body) lines.push(`Content-Length: ${body.length}`);
200
+ lines.push("Connection: close", "", "");
201
+ const head = encoder.encode(lines.join("\r\n"));
202
+ const out = new Uint8Array(head.length + (body?.length ?? 0));
203
+ out.set(head);
204
+ if (body) out.set(body, head.length);
205
+ return out;
206
+ }
207
+ async function loopbackFetch(dial, input, options) {
208
+ options.signal?.throwIfAborted();
209
+ const request = input instanceof Request ? input : new Request(input);
210
+ const bytes = await requestBytes(request);
211
+ const socket = await dial({
212
+ port: options.port,
213
+ ...options.host ? { host: options.host } : {}
214
+ });
215
+ options.signal?.addEventListener("abort", () => socket.close(), { once: true });
216
+ try {
217
+ const writer = socket.writable.getWriter();
218
+ await writer.write(bytes);
219
+ writer.releaseLock();
220
+ const source = new ByteSource(socket.readable.getReader());
221
+ let head;
222
+ do {
223
+ const raw = await source.until("\r\n\r\n");
224
+ if (!raw) throw errorWithCode("ECONNRESET: the connection closed before a response");
225
+ head = parseHead(raw);
226
+ } while (head.status >= 100 && head.status < 200);
227
+ const chunks3 = bodyOf(source, head, request.method);
228
+ if (!chunks3) socket.close();
229
+ return new Response(chunks3 ? stream(chunks3, socket, options.signal) : null, {
230
+ status: head.status,
231
+ statusText: head.statusText,
232
+ headers: head.headers
233
+ });
234
+ } catch (err) {
235
+ socket.close();
236
+ throw err;
237
+ }
238
+ }
239
+
1
240
  // src/body-idle.ts
2
241
  var BODY_IDLE_MS = 5 * 60 * 1e3;
3
242
  function idleFailure(ms) {
@@ -277,7 +516,7 @@ var RemoteTransport = class {
277
516
  };
278
517
 
279
518
  // src/client/protocol.ts
280
- var PROTOCOL = [1, 3];
519
+ var PROTOCOL = [1, 4];
281
520
  function locksOf(scope = globalThis) {
282
521
  const locks = scope.navigator?.locks;
283
522
  return typeof locks?.request === "function" ? locks : void 0;
@@ -304,8 +543,8 @@ var KernelCallError = class extends Error {
304
543
  this.code = code;
305
544
  }
306
545
  };
307
- var encoder = new TextEncoder();
308
- var bytesOf = (data) => typeof data === "string" ? encoder.encode(data) : data;
546
+ var encoder2 = new TextEncoder();
547
+ var bytesOf = (data) => typeof data === "string" ? encoder2.encode(data) : data;
309
548
  var UNAVAILABLE = {
310
549
  manualRedirects: false,
311
550
  encodedBodies: false,
@@ -413,6 +652,10 @@ var Channel = class {
413
652
  else call.resolve(reply.result);
414
653
  }
415
654
  };
655
+ async function dialOn(channel, { port, host }) {
656
+ const req = { op: "dial", port, ...host ? { host } : {} };
657
+ return dialStream(await since(channel, 4, "dial", () => channel.call(req)));
658
+ }
416
659
  async function spawnOn(channel, argv, opts) {
417
660
  const { onStdout, onStderr, stdin, group: _, ...rest } = opts;
418
661
  if (opts.pgid !== void 0)
@@ -591,6 +834,8 @@ async function attachKernel(port, options = {}) {
591
834
  const { signal, ...rest } = req;
592
835
  return transport.fetch({ ...rest, signal: signal ?? new AbortController().signal });
593
836
  },
837
+ dial: (options2) => dialOn(channel, options2),
838
+ loopbackFetch: (input, options2) => loopbackFetch((o) => dialOn(channel, o), input, options2),
594
839
  fs: fsOn(channel),
595
840
  async close(o = {}) {
596
841
  if (channel.failure) return;
@@ -717,7 +962,9 @@ async function checkLocalProxy(options) {
717
962
  probe: {
718
963
  rawFetch: reply.rawFetch,
719
964
  requestBodyStreaming: reply.requestBodyStreaming,
720
- maxRequestBodyBytes: reply.maxRequestBodyBytes
965
+ maxRequestBodyBytes: reply.maxRequestBodyBytes,
966
+ ...typeof reply.kernelTunnel === "number" ? { kernelTunnel: reply.kernelTunnel } : {},
967
+ ...typeof reply.kernelPort === "number" ? { kernelPort: reply.kernelPort } : {}
721
968
  }
722
969
  };
723
970
  }
@@ -784,6 +1031,24 @@ function streamer(callback) {
784
1031
  const decoder = new TextDecoder();
785
1032
  return (bytes) => callback?.(decoder.decode(bytes, { stream: true }));
786
1033
  }
1034
+ function dialer(call) {
1035
+ const open = /* @__PURE__ */ new Set();
1036
+ const dial = async ({ port, host }) => {
1037
+ let reply;
1038
+ try {
1039
+ reply = await call({ op: "dial", port, ...host ? { host } : {} });
1040
+ } catch (err) {
1041
+ throw errorWithCode(err.message);
1042
+ }
1043
+ const handle = dialStream(reply, () => open.delete(handle));
1044
+ open.add(handle);
1045
+ return handle;
1046
+ };
1047
+ const reset = () => {
1048
+ for (const handle of [...open]) handle.fail(errorWithCode("ECONNRESET: the kernel is gone"));
1049
+ };
1050
+ return { dial, reset };
1051
+ }
787
1052
  async function createKernel(options = {}) {
788
1053
  if (!globalThis.crossOriginIsolated) throw new Error(ISOLATION);
789
1054
  const url = options.worker ?? new URL("./kernel-worker.js", import.meta.url);
@@ -791,7 +1056,9 @@ async function createKernel(options = {}) {
791
1056
  const pending = /* @__PURE__ */ new Map();
792
1057
  let nextId = 0;
793
1058
  let failure2;
1059
+ const dials = dialer((req) => call(req));
794
1060
  const fail = (error) => {
1061
+ dials.reset();
795
1062
  failure2 = error;
796
1063
  bridge?.close();
797
1064
  for (const call2 of pending.values()) call2.reject(error);
@@ -897,6 +1164,7 @@ async function createKernel(options = {}) {
897
1164
  close: () => send2({ action: "close" })
898
1165
  };
899
1166
  }
1167
+ const { dial } = dials;
900
1168
  await call({
901
1169
  op: "init",
902
1170
  ...options.root ? { root: options.root } : {},
@@ -928,6 +1196,8 @@ async function createKernel(options = {}) {
928
1196
  },
929
1197
  openTerminal,
930
1198
  connect: async () => await call({ op: "connect" }),
1199
+ dial,
1200
+ loopbackFetch: (input, fetchOptions) => loopbackFetch(dial, input, fetchOptions),
931
1201
  mount: async (spec) => await call({ op: "mount", spec }),
932
1202
  umount: async (target) => void await call({ op: "umount", target }),
933
1203
  mounts: async () => await call({ op: "mounts" }),