@rivetkit/engine-runner 25.7.1-rc.1

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/mod.js ADDED
@@ -0,0 +1,2044 @@
1
+ // src/mod.ts
2
+ import WebSocket3 from "ws";
3
+
4
+ // src/log.ts
5
+ var LOGGER = void 0;
6
+ function setLogger(logger2) {
7
+ LOGGER = logger2;
8
+ }
9
+ function logger() {
10
+ return LOGGER;
11
+ }
12
+
13
+ // src/websocket.ts
14
+ var webSocketPromise = null;
15
+ async function importWebSocket() {
16
+ if (webSocketPromise !== null) {
17
+ return webSocketPromise;
18
+ }
19
+ webSocketPromise = (async () => {
20
+ var _a, _b, _c;
21
+ let _WebSocket;
22
+ if (typeof WebSocket !== "undefined") {
23
+ _WebSocket = WebSocket;
24
+ (_a = logger()) == null ? void 0 : _a.debug({ msg: "using native websocket" });
25
+ } else {
26
+ try {
27
+ const ws = await import("ws");
28
+ _WebSocket = ws.default;
29
+ (_b = logger()) == null ? void 0 : _b.debug({ msg: "using websocket from npm" });
30
+ } catch {
31
+ _WebSocket = class MockWebSocket {
32
+ constructor() {
33
+ throw new Error(
34
+ 'WebSocket support requires installing the "ws" peer dependency.'
35
+ );
36
+ }
37
+ };
38
+ (_c = logger()) == null ? void 0 : _c.debug({ msg: "using mock websocket" });
39
+ }
40
+ }
41
+ return _WebSocket;
42
+ })();
43
+ return webSocketPromise;
44
+ }
45
+
46
+ // src/mod.ts
47
+ import * as protocol from "@rivetkit/engine-runner-protocol";
48
+
49
+ // src/utils.ts
50
+ function unreachable(x) {
51
+ throw `Unreachable: ${x}`;
52
+ }
53
+ function calculateBackoff(attempt, options = {}) {
54
+ const {
55
+ initialDelay = 1e3,
56
+ maxDelay = 3e4,
57
+ multiplier = 2,
58
+ jitter = true
59
+ } = options;
60
+ let delay = Math.min(initialDelay * Math.pow(multiplier, attempt), maxDelay);
61
+ if (jitter) {
62
+ delay = delay * (1 + Math.random() * 0.25);
63
+ }
64
+ return Math.floor(delay);
65
+ }
66
+
67
+ // src/tunnel.ts
68
+ import WebSocket2 from "ws";
69
+ import * as tunnel from "@rivetkit/engine-tunnel-protocol";
70
+
71
+ // src/websocket-tunnel-adapter.ts
72
+ var WebSocketTunnelAdapter = class {
73
+ #webSocketId;
74
+ #readyState = 0;
75
+ // CONNECTING
76
+ #eventListeners = /* @__PURE__ */ new Map();
77
+ #onopen = null;
78
+ #onclose = null;
79
+ #onerror = null;
80
+ #onmessage = null;
81
+ #bufferedAmount = 0;
82
+ #binaryType = "nodebuffer";
83
+ #extensions = "";
84
+ #protocol = "";
85
+ #url = "";
86
+ #sendCallback;
87
+ #closeCallback;
88
+ // Event buffering for events fired before listeners are attached
89
+ #bufferedEvents = [];
90
+ constructor(webSocketId, sendCallback, closeCallback) {
91
+ this.#webSocketId = webSocketId;
92
+ this.#sendCallback = sendCallback;
93
+ this.#closeCallback = closeCallback;
94
+ }
95
+ get readyState() {
96
+ return this.#readyState;
97
+ }
98
+ get bufferedAmount() {
99
+ return this.#bufferedAmount;
100
+ }
101
+ get binaryType() {
102
+ return this.#binaryType;
103
+ }
104
+ set binaryType(value) {
105
+ if (value === "nodebuffer" || value === "arraybuffer" || value === "blob") {
106
+ this.#binaryType = value;
107
+ }
108
+ }
109
+ get extensions() {
110
+ return this.#extensions;
111
+ }
112
+ get protocol() {
113
+ return this.#protocol;
114
+ }
115
+ get url() {
116
+ return this.#url;
117
+ }
118
+ get onopen() {
119
+ return this.#onopen;
120
+ }
121
+ set onopen(value) {
122
+ this.#onopen = value;
123
+ if (value) {
124
+ this.#flushBufferedEvents("open");
125
+ }
126
+ }
127
+ get onclose() {
128
+ return this.#onclose;
129
+ }
130
+ set onclose(value) {
131
+ this.#onclose = value;
132
+ if (value) {
133
+ this.#flushBufferedEvents("close");
134
+ }
135
+ }
136
+ get onerror() {
137
+ return this.#onerror;
138
+ }
139
+ set onerror(value) {
140
+ this.#onerror = value;
141
+ if (value) {
142
+ this.#flushBufferedEvents("error");
143
+ }
144
+ }
145
+ get onmessage() {
146
+ return this.#onmessage;
147
+ }
148
+ set onmessage(value) {
149
+ this.#onmessage = value;
150
+ if (value) {
151
+ this.#flushBufferedEvents("message");
152
+ }
153
+ }
154
+ send(data) {
155
+ if (this.#readyState !== 1) {
156
+ throw new Error("WebSocket is not open");
157
+ }
158
+ let isBinary = false;
159
+ let messageData;
160
+ if (typeof data === "string") {
161
+ messageData = data;
162
+ } else if (data instanceof ArrayBuffer) {
163
+ isBinary = true;
164
+ messageData = data;
165
+ } else if (ArrayBuffer.isView(data)) {
166
+ isBinary = true;
167
+ const view = data;
168
+ if (view.buffer instanceof SharedArrayBuffer) {
169
+ const bytes = new Uint8Array(view.buffer, view.byteOffset, view.byteLength);
170
+ messageData = bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength);
171
+ } else {
172
+ messageData = view.buffer.slice(
173
+ view.byteOffset,
174
+ view.byteOffset + view.byteLength
175
+ );
176
+ }
177
+ } else if (data instanceof Blob) {
178
+ throw new Error("Blob sending not implemented in tunnel adapter");
179
+ } else if (typeof Buffer !== "undefined" && Buffer.isBuffer(data)) {
180
+ isBinary = true;
181
+ const buf = data;
182
+ if (buf.buffer instanceof SharedArrayBuffer) {
183
+ const bytes = new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength);
184
+ messageData = bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength);
185
+ } else {
186
+ messageData = buf.buffer.slice(
187
+ buf.byteOffset,
188
+ buf.byteOffset + buf.byteLength
189
+ );
190
+ }
191
+ } else {
192
+ throw new Error("Invalid data type");
193
+ }
194
+ this.#sendCallback(messageData, isBinary);
195
+ }
196
+ close(code, reason) {
197
+ if (this.#readyState === 2 || // CLOSING
198
+ this.#readyState === 3) {
199
+ return;
200
+ }
201
+ this.#readyState = 2;
202
+ this.#closeCallback(code, reason);
203
+ this.#readyState = 3;
204
+ const closeEvent = {
205
+ wasClean: true,
206
+ code: code || 1e3,
207
+ reason: reason || "",
208
+ type: "close",
209
+ target: this
210
+ };
211
+ this.#fireEvent("close", closeEvent);
212
+ }
213
+ addEventListener(type, listener, options) {
214
+ if (typeof listener === "function") {
215
+ let listeners = this.#eventListeners.get(type);
216
+ if (!listeners) {
217
+ listeners = /* @__PURE__ */ new Set();
218
+ this.#eventListeners.set(type, listeners);
219
+ }
220
+ listeners.add(listener);
221
+ this.#flushBufferedEvents(type);
222
+ }
223
+ }
224
+ removeEventListener(type, listener, options) {
225
+ if (typeof listener === "function") {
226
+ const listeners = this.#eventListeners.get(type);
227
+ if (listeners) {
228
+ listeners.delete(listener);
229
+ }
230
+ }
231
+ }
232
+ dispatchEvent(event) {
233
+ return true;
234
+ }
235
+ #fireEvent(type, event) {
236
+ var _a, _b, _c, _d, _e;
237
+ const listeners = this.#eventListeners.get(type);
238
+ let hasListeners = false;
239
+ if (listeners && listeners.size > 0) {
240
+ hasListeners = true;
241
+ for (const listener of listeners) {
242
+ try {
243
+ listener.call(this, event);
244
+ } catch (error) {
245
+ (_a = logger()) == null ? void 0 : _a.error({ msg: "error in websocket event listener", error, type });
246
+ }
247
+ }
248
+ }
249
+ switch (type) {
250
+ case "open":
251
+ if (this.#onopen) {
252
+ hasListeners = true;
253
+ try {
254
+ this.#onopen.call(this, event);
255
+ } catch (error) {
256
+ (_b = logger()) == null ? void 0 : _b.error({ msg: "error in onopen handler", error });
257
+ }
258
+ }
259
+ break;
260
+ case "close":
261
+ if (this.#onclose) {
262
+ hasListeners = true;
263
+ try {
264
+ this.#onclose.call(this, event);
265
+ } catch (error) {
266
+ (_c = logger()) == null ? void 0 : _c.error({ msg: "error in onclose handler", error });
267
+ }
268
+ }
269
+ break;
270
+ case "error":
271
+ if (this.#onerror) {
272
+ hasListeners = true;
273
+ try {
274
+ this.#onerror.call(this, event);
275
+ } catch (error) {
276
+ (_d = logger()) == null ? void 0 : _d.error({ msg: "error in onerror handler", error });
277
+ }
278
+ }
279
+ break;
280
+ case "message":
281
+ if (this.#onmessage) {
282
+ hasListeners = true;
283
+ try {
284
+ this.#onmessage.call(this, event);
285
+ } catch (error) {
286
+ (_e = logger()) == null ? void 0 : _e.error({ msg: "error in onmessage handler", error });
287
+ }
288
+ }
289
+ break;
290
+ }
291
+ if (!hasListeners) {
292
+ this.#bufferedEvents.push({ type, event });
293
+ }
294
+ }
295
+ #flushBufferedEvents(type) {
296
+ var _a, _b, _c, _d, _e;
297
+ const eventsToFlush = this.#bufferedEvents.filter(
298
+ (buffered) => buffered.type === type
299
+ );
300
+ this.#bufferedEvents = this.#bufferedEvents.filter(
301
+ (buffered) => buffered.type !== type
302
+ );
303
+ for (const { event } of eventsToFlush) {
304
+ const listeners = this.#eventListeners.get(type);
305
+ if (listeners) {
306
+ for (const listener of listeners) {
307
+ try {
308
+ listener.call(this, event);
309
+ } catch (error) {
310
+ (_a = logger()) == null ? void 0 : _a.error({
311
+ msg: "error in websocket event listener",
312
+ error,
313
+ type
314
+ });
315
+ }
316
+ }
317
+ }
318
+ switch (type) {
319
+ case "open":
320
+ if (this.#onopen) {
321
+ try {
322
+ this.#onopen.call(this, event);
323
+ } catch (error) {
324
+ (_b = logger()) == null ? void 0 : _b.error({ msg: "error in onopen handler", error });
325
+ }
326
+ }
327
+ break;
328
+ case "close":
329
+ if (this.#onclose) {
330
+ try {
331
+ this.#onclose.call(this, event);
332
+ } catch (error) {
333
+ (_c = logger()) == null ? void 0 : _c.error({ msg: "error in onclose handler", error });
334
+ }
335
+ }
336
+ break;
337
+ case "error":
338
+ if (this.#onerror) {
339
+ try {
340
+ this.#onerror.call(this, event);
341
+ } catch (error) {
342
+ (_d = logger()) == null ? void 0 : _d.error({ msg: "error in onerror handler", error });
343
+ }
344
+ }
345
+ break;
346
+ case "message":
347
+ if (this.#onmessage) {
348
+ try {
349
+ this.#onmessage.call(this, event);
350
+ } catch (error) {
351
+ (_e = logger()) == null ? void 0 : _e.error({ msg: "error in onmessage handler", error });
352
+ }
353
+ }
354
+ break;
355
+ }
356
+ }
357
+ }
358
+ // Internal methods called by the Tunnel class
359
+ _handleOpen() {
360
+ if (this.#readyState !== 0) {
361
+ return;
362
+ }
363
+ this.#readyState = 1;
364
+ const event = {
365
+ type: "open",
366
+ target: this
367
+ };
368
+ this.#fireEvent("open", event);
369
+ }
370
+ _handleMessage(data, isBinary) {
371
+ if (this.#readyState !== 1) {
372
+ return;
373
+ }
374
+ let messageData;
375
+ if (isBinary) {
376
+ if (this.#binaryType === "nodebuffer") {
377
+ messageData = Buffer.from(data);
378
+ } else if (this.#binaryType === "arraybuffer") {
379
+ if (data instanceof Uint8Array) {
380
+ messageData = data.buffer.slice(
381
+ data.byteOffset,
382
+ data.byteOffset + data.byteLength
383
+ );
384
+ } else {
385
+ messageData = data;
386
+ }
387
+ } else {
388
+ throw new Error("Blob binaryType not supported in tunnel adapter");
389
+ }
390
+ } else {
391
+ messageData = data;
392
+ }
393
+ const event = {
394
+ data: messageData,
395
+ type: "message",
396
+ target: this
397
+ };
398
+ this.#fireEvent("message", event);
399
+ }
400
+ _handleClose(code, reason) {
401
+ if (this.#readyState === 3) {
402
+ return;
403
+ }
404
+ this.#readyState = 3;
405
+ const event = {
406
+ wasClean: true,
407
+ code: code || 1e3,
408
+ reason: reason || "",
409
+ type: "close",
410
+ target: this
411
+ };
412
+ this.#fireEvent("close", event);
413
+ }
414
+ _handleError(error) {
415
+ const event = {
416
+ type: "error",
417
+ target: this,
418
+ error
419
+ };
420
+ this.#fireEvent("error", event);
421
+ }
422
+ // WebSocket constants for compatibility
423
+ static CONNECTING = 0;
424
+ static OPEN = 1;
425
+ static CLOSING = 2;
426
+ static CLOSED = 3;
427
+ // Instance constants
428
+ CONNECTING = 0;
429
+ OPEN = 1;
430
+ CLOSING = 2;
431
+ CLOSED = 3;
432
+ // Additional methods for compatibility
433
+ ping(data, mask, cb) {
434
+ if (cb) cb(new Error("Ping not supported in tunnel adapter"));
435
+ }
436
+ pong(data, mask, cb) {
437
+ if (cb) cb(new Error("Pong not supported in tunnel adapter"));
438
+ }
439
+ terminate() {
440
+ this.#readyState = 3;
441
+ this.#closeCallback(1006, "Abnormal Closure");
442
+ const event = {
443
+ wasClean: false,
444
+ code: 1006,
445
+ reason: "Abnormal Closure",
446
+ type: "close",
447
+ target: this
448
+ };
449
+ this.#fireEvent("close", event);
450
+ }
451
+ };
452
+
453
+ // src/tunnel.ts
454
+ import { v4 as uuidv4 } from "uuid";
455
+ var GC_INTERVAL = 6e4;
456
+ var MESSAGE_ACK_TIMEOUT = 5e3;
457
+ var Tunnel = class {
458
+ #pegboardTunnelUrl;
459
+ #runner;
460
+ #tunnelWs;
461
+ #shutdown = false;
462
+ #reconnectTimeout;
463
+ #reconnectAttempt = 0;
464
+ #actorPendingRequests = /* @__PURE__ */ new Map();
465
+ #actorWebSockets = /* @__PURE__ */ new Map();
466
+ #pendingMessages = /* @__PURE__ */ new Map();
467
+ #gcInterval;
468
+ #callbacks;
469
+ constructor(runner, pegboardTunnelUrl, callbacks) {
470
+ this.#pegboardTunnelUrl = pegboardTunnelUrl;
471
+ this.#runner = runner;
472
+ this.#callbacks = callbacks;
473
+ }
474
+ start() {
475
+ var _a;
476
+ if (((_a = this.#tunnelWs) == null ? void 0 : _a.readyState) === WebSocket2.OPEN) {
477
+ return;
478
+ }
479
+ this.#connect();
480
+ this.#startGarbageCollector();
481
+ }
482
+ shutdown() {
483
+ this.#shutdown = true;
484
+ if (this.#reconnectTimeout) {
485
+ clearTimeout(this.#reconnectTimeout);
486
+ this.#reconnectTimeout = void 0;
487
+ }
488
+ if (this.#gcInterval) {
489
+ clearInterval(this.#gcInterval);
490
+ this.#gcInterval = void 0;
491
+ }
492
+ if (this.#tunnelWs) {
493
+ this.#tunnelWs.close();
494
+ this.#tunnelWs = void 0;
495
+ }
496
+ for (const [_, request] of this.#actorPendingRequests) {
497
+ request.reject(new Error("Tunnel shutting down"));
498
+ }
499
+ this.#actorPendingRequests.clear();
500
+ for (const [_, ws] of this.#actorWebSockets) {
501
+ ws.close();
502
+ }
503
+ this.#actorWebSockets.clear();
504
+ }
505
+ #sendMessage(requestId, messageKind) {
506
+ if (!this.#tunnelWs || this.#tunnelWs.readyState !== WebSocket2.OPEN) {
507
+ console.warn("Cannot send tunnel message, WebSocket not connected");
508
+ return;
509
+ }
510
+ const messageId = generateUuidBuffer();
511
+ const requestIdStr = bufferToString(requestId);
512
+ this.#pendingMessages.set(bufferToString(messageId), {
513
+ sentAt: Date.now(),
514
+ requestIdStr
515
+ });
516
+ const message = {
517
+ requestId,
518
+ messageId,
519
+ messageKind
520
+ };
521
+ const encoded = tunnel.encodeRunnerMessage(message);
522
+ this.#tunnelWs.send(encoded);
523
+ }
524
+ #sendAck(requestId, messageId) {
525
+ if (!this.#tunnelWs || this.#tunnelWs.readyState !== WebSocket2.OPEN) {
526
+ return;
527
+ }
528
+ const message = {
529
+ requestId,
530
+ messageId,
531
+ messageKind: { tag: "Ack", val: null }
532
+ };
533
+ const encoded = tunnel.encodeRunnerMessage(message);
534
+ this.#tunnelWs.send(encoded);
535
+ }
536
+ #startGarbageCollector() {
537
+ if (this.#gcInterval) {
538
+ clearInterval(this.#gcInterval);
539
+ }
540
+ this.#gcInterval = setInterval(() => {
541
+ this.#gc();
542
+ }, GC_INTERVAL);
543
+ }
544
+ #gc() {
545
+ const now = Date.now();
546
+ const messagesToDelete = [];
547
+ for (const [messageId, pendingMessage] of this.#pendingMessages) {
548
+ if (now - pendingMessage.sentAt > MESSAGE_ACK_TIMEOUT) {
549
+ messagesToDelete.push(messageId);
550
+ const requestIdStr = pendingMessage.requestIdStr;
551
+ const pendingRequest = this.#actorPendingRequests.get(requestIdStr);
552
+ if (pendingRequest) {
553
+ pendingRequest.reject(
554
+ new Error("Message acknowledgment timeout")
555
+ );
556
+ if (pendingRequest.streamController) {
557
+ pendingRequest.streamController.error(
558
+ new Error("Message acknowledgment timeout")
559
+ );
560
+ }
561
+ this.#actorPendingRequests.delete(requestIdStr);
562
+ }
563
+ const webSocket = this.#actorWebSockets.get(requestIdStr);
564
+ if (webSocket) {
565
+ webSocket.close(1e3, "Message acknowledgment timeout");
566
+ this.#actorWebSockets.delete(requestIdStr);
567
+ }
568
+ }
569
+ }
570
+ for (const messageId of messagesToDelete) {
571
+ this.#pendingMessages.delete(messageId);
572
+ console.warn(`Purged unacked message: ${messageId}`);
573
+ }
574
+ }
575
+ unregisterActor(actor) {
576
+ const actorId = actor.actorId;
577
+ for (const requestId of actor.requests) {
578
+ const pending = this.#actorPendingRequests.get(requestId);
579
+ if (pending) {
580
+ pending.reject(new Error(`Actor ${actorId} stopped`));
581
+ this.#actorPendingRequests.delete(requestId);
582
+ }
583
+ }
584
+ actor.requests.clear();
585
+ for (const webSocketId of actor.webSockets) {
586
+ const ws = this.#actorWebSockets.get(webSocketId);
587
+ if (ws) {
588
+ ws.close(1e3, "Actor stopped");
589
+ this.#actorWebSockets.delete(webSocketId);
590
+ }
591
+ }
592
+ actor.webSockets.clear();
593
+ }
594
+ async #fetch(actorId, request) {
595
+ var _a;
596
+ if (!this.#runner.hasActor(actorId)) {
597
+ (_a = logger()) == null ? void 0 : _a.warn({
598
+ msg: "ignoring request for unknown actor",
599
+ actorId
600
+ });
601
+ return new Response("Actor not found", { status: 404 });
602
+ }
603
+ const fetchHandler = this.#runner.config.fetch(actorId, request);
604
+ if (!fetchHandler) {
605
+ return new Response("Not Implemented", { status: 501 });
606
+ }
607
+ return fetchHandler;
608
+ }
609
+ #connect() {
610
+ var _a;
611
+ if (this.#shutdown) return;
612
+ try {
613
+ this.#tunnelWs = new WebSocket2(this.#pegboardTunnelUrl, {
614
+ headers: {
615
+ "x-rivet-target": "tunnel"
616
+ }
617
+ });
618
+ this.#tunnelWs.binaryType = "arraybuffer";
619
+ this.#tunnelWs.addEventListener("open", () => {
620
+ this.#reconnectAttempt = 0;
621
+ if (this.#reconnectTimeout) {
622
+ clearTimeout(this.#reconnectTimeout);
623
+ this.#reconnectTimeout = void 0;
624
+ }
625
+ this.#callbacks.onConnected();
626
+ });
627
+ this.#tunnelWs.addEventListener("message", async (event) => {
628
+ var _a2;
629
+ try {
630
+ await this.#handleMessage(event.data);
631
+ } catch (error) {
632
+ (_a2 = logger()) == null ? void 0 : _a2.error({
633
+ msg: "error handling tunnel message",
634
+ error
635
+ });
636
+ }
637
+ });
638
+ this.#tunnelWs.addEventListener("error", (event) => {
639
+ var _a2;
640
+ (_a2 = logger()) == null ? void 0 : _a2.error({ msg: "tunnel websocket error", event });
641
+ });
642
+ this.#tunnelWs.addEventListener("close", () => {
643
+ this.#callbacks.onDisconnected();
644
+ if (!this.#shutdown) {
645
+ this.#scheduleReconnect();
646
+ }
647
+ });
648
+ } catch (error) {
649
+ (_a = logger()) == null ? void 0 : _a.error({ msg: "failed to connect tunnel", error });
650
+ if (!this.#shutdown) {
651
+ this.#scheduleReconnect();
652
+ }
653
+ }
654
+ }
655
+ #scheduleReconnect() {
656
+ if (this.#shutdown) return;
657
+ const delay = calculateBackoff(this.#reconnectAttempt, {
658
+ initialDelay: 1e3,
659
+ maxDelay: 3e4,
660
+ multiplier: 2,
661
+ jitter: true
662
+ });
663
+ this.#reconnectAttempt++;
664
+ this.#reconnectTimeout = setTimeout(() => {
665
+ this.#connect();
666
+ }, delay);
667
+ }
668
+ async #handleMessage(data) {
669
+ const message = tunnel.decodeRunnerMessage(new Uint8Array(data));
670
+ if (message.messageKind.tag === "Ack") {
671
+ const msgIdStr = bufferToString(message.messageId);
672
+ const pending = this.#pendingMessages.get(msgIdStr);
673
+ if (pending) {
674
+ this.#pendingMessages.delete(msgIdStr);
675
+ }
676
+ } else {
677
+ this.#sendAck(message.requestId, message.messageId);
678
+ switch (message.messageKind.tag) {
679
+ case "ToServerRequestStart":
680
+ await this.#handleRequestStart(
681
+ message.requestId,
682
+ message.messageKind.val
683
+ );
684
+ break;
685
+ case "ToServerRequestChunk":
686
+ await this.#handleRequestChunk(
687
+ message.requestId,
688
+ message.messageKind.val
689
+ );
690
+ break;
691
+ case "ToServerRequestAbort":
692
+ await this.#handleRequestAbort(message.requestId);
693
+ break;
694
+ case "ToServerWebSocketOpen":
695
+ await this.#handleWebSocketOpen(
696
+ message.requestId,
697
+ message.messageKind.val
698
+ );
699
+ break;
700
+ case "ToServerWebSocketMessage":
701
+ await this.#handleWebSocketMessage(
702
+ message.requestId,
703
+ message.messageKind.val
704
+ );
705
+ break;
706
+ case "ToServerWebSocketClose":
707
+ await this.#handleWebSocketClose(
708
+ message.requestId,
709
+ message.messageKind.val
710
+ );
711
+ break;
712
+ case "ToClientResponseStart":
713
+ this.#handleResponseStart(
714
+ message.requestId,
715
+ message.messageKind.val
716
+ );
717
+ break;
718
+ case "ToClientResponseChunk":
719
+ this.#handleResponseChunk(
720
+ message.requestId,
721
+ message.messageKind.val
722
+ );
723
+ break;
724
+ case "ToClientResponseAbort":
725
+ this.#handleResponseAbort(message.requestId);
726
+ break;
727
+ case "ToClientWebSocketOpen":
728
+ this.#handleWebSocketOpenResponse(
729
+ message.requestId,
730
+ message.messageKind.val
731
+ );
732
+ break;
733
+ case "ToClientWebSocketMessage":
734
+ this.#handleWebSocketMessageResponse(
735
+ message.requestId,
736
+ message.messageKind.val
737
+ );
738
+ break;
739
+ case "ToClientWebSocketClose":
740
+ this.#handleWebSocketCloseResponse(
741
+ message.requestId,
742
+ message.messageKind.val
743
+ );
744
+ break;
745
+ }
746
+ }
747
+ }
748
+ async #handleRequestStart(requestId, req) {
749
+ var _a;
750
+ const requestIdStr = bufferToString(requestId);
751
+ const actor = this.#runner.getActor(req.actorId);
752
+ if (actor) {
753
+ actor.requests.add(requestIdStr);
754
+ }
755
+ try {
756
+ const headers = new Headers();
757
+ for (const [key, value] of req.headers) {
758
+ headers.append(key, value);
759
+ }
760
+ const request = new Request(`http://localhost${req.path}`, {
761
+ method: req.method,
762
+ headers,
763
+ body: req.body ? new Uint8Array(req.body) : void 0
764
+ });
765
+ if (req.stream) {
766
+ const stream = new ReadableStream({
767
+ start: (controller) => {
768
+ const existing = this.#actorPendingRequests.get(requestIdStr);
769
+ if (existing) {
770
+ existing.streamController = controller;
771
+ existing.actorId = req.actorId;
772
+ } else {
773
+ this.#actorPendingRequests.set(requestIdStr, {
774
+ resolve: () => {
775
+ },
776
+ reject: () => {
777
+ },
778
+ streamController: controller,
779
+ actorId: req.actorId
780
+ });
781
+ }
782
+ }
783
+ });
784
+ const streamingRequest = new Request(request, {
785
+ body: stream,
786
+ duplex: "half"
787
+ });
788
+ const response = await this.#fetch(
789
+ req.actorId,
790
+ streamingRequest
791
+ );
792
+ await this.#sendResponse(requestId, response);
793
+ } else {
794
+ const response = await this.#fetch(req.actorId, request);
795
+ await this.#sendResponse(requestId, response);
796
+ }
797
+ } catch (error) {
798
+ (_a = logger()) == null ? void 0 : _a.error({ msg: "error handling request", error });
799
+ this.#sendResponseError(requestId, 500, "Internal Server Error");
800
+ } finally {
801
+ const actor2 = this.#runner.getActor(req.actorId);
802
+ if (actor2) {
803
+ actor2.requests.delete(requestIdStr);
804
+ }
805
+ }
806
+ }
807
+ async #handleRequestChunk(requestId, chunk) {
808
+ const requestIdStr = bufferToString(requestId);
809
+ const pending = this.#actorPendingRequests.get(requestIdStr);
810
+ if (pending == null ? void 0 : pending.streamController) {
811
+ pending.streamController.enqueue(new Uint8Array(chunk.body));
812
+ if (chunk.finish) {
813
+ pending.streamController.close();
814
+ this.#actorPendingRequests.delete(requestIdStr);
815
+ }
816
+ }
817
+ }
818
+ async #handleRequestAbort(requestId) {
819
+ const requestIdStr = bufferToString(requestId);
820
+ const pending = this.#actorPendingRequests.get(requestIdStr);
821
+ if (pending == null ? void 0 : pending.streamController) {
822
+ pending.streamController.error(new Error("Request aborted"));
823
+ }
824
+ this.#actorPendingRequests.delete(requestIdStr);
825
+ }
826
+ async #sendResponse(requestId, response) {
827
+ const body = response.body ? await response.arrayBuffer() : null;
828
+ const headers = /* @__PURE__ */ new Map();
829
+ response.headers.forEach((value, key) => {
830
+ headers.set(key, value);
831
+ });
832
+ if (body && !headers.has("content-length")) {
833
+ headers.set("content-length", String(body.byteLength));
834
+ }
835
+ this.#sendMessage(requestId, {
836
+ tag: "ToClientResponseStart",
837
+ val: {
838
+ status: response.status,
839
+ headers,
840
+ body: body || null,
841
+ stream: false
842
+ }
843
+ });
844
+ }
845
+ #sendResponseError(requestId, status, message) {
846
+ const headers = /* @__PURE__ */ new Map();
847
+ headers.set("content-type", "text/plain");
848
+ this.#sendMessage(requestId, {
849
+ tag: "ToClientResponseStart",
850
+ val: {
851
+ status,
852
+ headers,
853
+ body: new TextEncoder().encode(message).buffer,
854
+ stream: false
855
+ }
856
+ });
857
+ }
858
+ async #handleWebSocketOpen(requestId, open) {
859
+ var _a, _b, _c;
860
+ const webSocketId = bufferToString(requestId);
861
+ const actor = this.#runner.getActor(open.actorId);
862
+ if (!actor) {
863
+ (_a = logger()) == null ? void 0 : _a.warn({
864
+ msg: "ignoring websocket for unknown actor",
865
+ actorId: open.actorId
866
+ });
867
+ this.#sendMessage(requestId, {
868
+ tag: "ToClientWebSocketClose",
869
+ val: {
870
+ code: 1011,
871
+ reason: "Actor not found"
872
+ }
873
+ });
874
+ return;
875
+ }
876
+ const websocketHandler = this.#runner.config.websocket;
877
+ if (!websocketHandler) {
878
+ console.error("No websocket handler configured for tunnel");
879
+ (_b = logger()) == null ? void 0 : _b.error({
880
+ msg: "no websocket handler configured for tunnel"
881
+ });
882
+ this.#sendMessage(requestId, {
883
+ tag: "ToClientWebSocketClose",
884
+ val: {
885
+ code: 1011,
886
+ reason: "Not Implemented"
887
+ }
888
+ });
889
+ return;
890
+ }
891
+ if (actor) {
892
+ actor.webSockets.add(webSocketId);
893
+ }
894
+ try {
895
+ const adapter = new WebSocketTunnelAdapter(
896
+ webSocketId,
897
+ (data, isBinary) => {
898
+ const dataBuffer = typeof data === "string" ? new TextEncoder().encode(data).buffer : data;
899
+ this.#sendMessage(requestId, {
900
+ tag: "ToClientWebSocketMessage",
901
+ val: {
902
+ data: dataBuffer,
903
+ binary: isBinary
904
+ }
905
+ });
906
+ },
907
+ (code, reason) => {
908
+ this.#sendMessage(requestId, {
909
+ tag: "ToClientWebSocketClose",
910
+ val: {
911
+ code: code || null,
912
+ reason: reason || null
913
+ }
914
+ });
915
+ this.#actorWebSockets.delete(webSocketId);
916
+ if (actor) {
917
+ actor.webSockets.delete(webSocketId);
918
+ }
919
+ }
920
+ );
921
+ this.#actorWebSockets.set(webSocketId, adapter);
922
+ this.#sendMessage(requestId, {
923
+ tag: "ToClientWebSocketOpen",
924
+ val: null
925
+ });
926
+ adapter._handleOpen();
927
+ const headerInit = {};
928
+ if (open.headers) {
929
+ for (const [k, v] of open.headers) {
930
+ headerInit[k] = v;
931
+ }
932
+ }
933
+ headerInit["Upgrade"] = "websocket";
934
+ headerInit["Connection"] = "Upgrade";
935
+ const request = new Request(`http://localhost${open.path}`, {
936
+ method: "GET",
937
+ headers: headerInit
938
+ });
939
+ await websocketHandler(open.actorId, adapter, request);
940
+ } catch (error) {
941
+ (_c = logger()) == null ? void 0 : _c.error({ msg: "error handling websocket open", error });
942
+ this.#sendMessage(requestId, {
943
+ tag: "ToClientWebSocketClose",
944
+ val: {
945
+ code: 1011,
946
+ reason: "Server Error"
947
+ }
948
+ });
949
+ this.#actorWebSockets.delete(webSocketId);
950
+ if (actor) {
951
+ actor.webSockets.delete(webSocketId);
952
+ }
953
+ }
954
+ }
955
+ async #handleWebSocketMessage(requestId, msg) {
956
+ const webSocketId = bufferToString(requestId);
957
+ const adapter = this.#actorWebSockets.get(webSocketId);
958
+ if (adapter) {
959
+ const data = msg.binary ? new Uint8Array(msg.data) : new TextDecoder().decode(new Uint8Array(msg.data));
960
+ adapter._handleMessage(data, msg.binary);
961
+ }
962
+ }
963
+ async #handleWebSocketClose(requestId, close) {
964
+ const webSocketId = bufferToString(requestId);
965
+ const adapter = this.#actorWebSockets.get(webSocketId);
966
+ if (adapter) {
967
+ adapter._handleClose(
968
+ close.code || void 0,
969
+ close.reason || void 0
970
+ );
971
+ this.#actorWebSockets.delete(webSocketId);
972
+ }
973
+ }
974
+ #handleResponseStart(requestId, resp) {
975
+ var _a;
976
+ const requestIdStr = bufferToString(requestId);
977
+ const pending = this.#actorPendingRequests.get(requestIdStr);
978
+ if (!pending) {
979
+ (_a = logger()) == null ? void 0 : _a.warn({
980
+ msg: "received response for unknown request",
981
+ requestId: requestIdStr
982
+ });
983
+ return;
984
+ }
985
+ const headers = new Headers();
986
+ for (const [key, value] of resp.headers) {
987
+ headers.append(key, value);
988
+ }
989
+ if (resp.stream) {
990
+ const stream = new ReadableStream({
991
+ start: (controller) => {
992
+ pending.streamController = controller;
993
+ }
994
+ });
995
+ const response = new Response(stream, {
996
+ status: resp.status,
997
+ headers
998
+ });
999
+ pending.resolve(response);
1000
+ } else {
1001
+ const body = resp.body ? new Uint8Array(resp.body) : null;
1002
+ const response = new Response(body, {
1003
+ status: resp.status,
1004
+ headers
1005
+ });
1006
+ pending.resolve(response);
1007
+ this.#actorPendingRequests.delete(requestIdStr);
1008
+ }
1009
+ }
1010
+ #handleResponseChunk(requestId, chunk) {
1011
+ const requestIdStr = bufferToString(requestId);
1012
+ const pending = this.#actorPendingRequests.get(requestIdStr);
1013
+ if (pending == null ? void 0 : pending.streamController) {
1014
+ pending.streamController.enqueue(new Uint8Array(chunk.body));
1015
+ if (chunk.finish) {
1016
+ pending.streamController.close();
1017
+ this.#actorPendingRequests.delete(requestIdStr);
1018
+ }
1019
+ }
1020
+ }
1021
+ #handleResponseAbort(requestId) {
1022
+ const requestIdStr = bufferToString(requestId);
1023
+ const pending = this.#actorPendingRequests.get(requestIdStr);
1024
+ if (pending == null ? void 0 : pending.streamController) {
1025
+ pending.streamController.error(new Error("Response aborted"));
1026
+ }
1027
+ this.#actorPendingRequests.delete(requestIdStr);
1028
+ }
1029
+ #handleWebSocketOpenResponse(requestId, open) {
1030
+ const webSocketId = bufferToString(requestId);
1031
+ const adapter = this.#actorWebSockets.get(webSocketId);
1032
+ if (adapter) {
1033
+ adapter._handleOpen();
1034
+ }
1035
+ }
1036
+ #handleWebSocketMessageResponse(requestId, msg) {
1037
+ const webSocketId = bufferToString(requestId);
1038
+ const adapter = this.#actorWebSockets.get(webSocketId);
1039
+ if (adapter) {
1040
+ const data = msg.binary ? new Uint8Array(msg.data) : new TextDecoder().decode(new Uint8Array(msg.data));
1041
+ adapter._handleMessage(data, msg.binary);
1042
+ }
1043
+ }
1044
+ #handleWebSocketCloseResponse(requestId, close) {
1045
+ const webSocketId = bufferToString(requestId);
1046
+ const adapter = this.#actorWebSockets.get(webSocketId);
1047
+ if (adapter) {
1048
+ adapter._handleClose(
1049
+ close.code || void 0,
1050
+ close.reason || void 0
1051
+ );
1052
+ this.#actorWebSockets.delete(webSocketId);
1053
+ }
1054
+ }
1055
+ };
1056
+ function bufferToString(buffer) {
1057
+ return Buffer.from(buffer).toString("base64");
1058
+ }
1059
+ function generateUuidBuffer() {
1060
+ const buffer = new Uint8Array(16);
1061
+ uuidv4(void 0, buffer);
1062
+ return buffer.buffer;
1063
+ }
1064
+
1065
+ // src/mod.ts
1066
+ var KV_EXPIRE = 3e4;
1067
+ var Runner = class {
1068
+ #config;
1069
+ get config() {
1070
+ return this.#config;
1071
+ }
1072
+ #actors = /* @__PURE__ */ new Map();
1073
+ #actorWebSockets = /* @__PURE__ */ new Map();
1074
+ // WebSocket
1075
+ #pegboardWebSocket;
1076
+ runnerId;
1077
+ #lastCommandIdx = -1;
1078
+ #pingLoop;
1079
+ #nextEventIdx = 0n;
1080
+ #started = false;
1081
+ #shutdown = false;
1082
+ #reconnectAttempt = 0;
1083
+ #reconnectTimeout;
1084
+ // Runner lost threshold management
1085
+ #runnerLostThreshold;
1086
+ #runnerLostTimeout;
1087
+ // Event storage for resending
1088
+ #eventHistory = [];
1089
+ #eventPruneInterval;
1090
+ // Command acknowledgment
1091
+ #ackInterval;
1092
+ // KV operations
1093
+ #nextRequestId = 0;
1094
+ #kvRequests = /* @__PURE__ */ new Map();
1095
+ #kvCleanupInterval;
1096
+ // Tunnel for HTTP/WebSocket forwarding
1097
+ #tunnel;
1098
+ constructor(config) {
1099
+ this.#config = config;
1100
+ if (this.#config.logger) setLogger(this.#config.logger);
1101
+ this.#eventPruneInterval = setInterval(() => {
1102
+ this.#pruneOldEvents();
1103
+ }, 6e4);
1104
+ this.#kvCleanupInterval = setInterval(() => {
1105
+ this.#cleanupOldKvRequests();
1106
+ }, 15e3);
1107
+ }
1108
+ // MARK: Manage actors
1109
+ sleepActor(actorId, generation) {
1110
+ const actor = this.getActor(actorId, generation);
1111
+ if (!actor) return;
1112
+ this.#sendActorIntent(actorId, actor.generation, "sleep");
1113
+ }
1114
+ async stopActor(actorId, generation) {
1115
+ const actor = this.#removeActor(actorId, generation);
1116
+ if (!actor) return;
1117
+ if (this.#tunnel) {
1118
+ this.#tunnel.unregisterActor(actor);
1119
+ }
1120
+ try {
1121
+ await this.#config.onActorStop(actorId, actor.generation);
1122
+ } catch (err) {
1123
+ console.error(`Error in onActorStop for actor ${actorId}:`, err);
1124
+ }
1125
+ this.#sendActorStateUpdate(actorId, actor.generation, "stopped");
1126
+ this.#config.onActorStop(actorId, actor.generation).catch((err) => {
1127
+ var _a;
1128
+ (_a = logger()) == null ? void 0 : _a.error({
1129
+ msg: "error in onactorstop for actor",
1130
+ actorId,
1131
+ err
1132
+ });
1133
+ });
1134
+ }
1135
+ #stopAllActors() {
1136
+ var _a;
1137
+ (_a = logger()) == null ? void 0 : _a.info(
1138
+ "stopping all actors due to runner lost threshold exceeded"
1139
+ );
1140
+ const actorIds = Array.from(this.#actors.keys());
1141
+ for (const actorId of actorIds) {
1142
+ this.stopActor(actorId);
1143
+ }
1144
+ }
1145
+ getActor(actorId, generation) {
1146
+ var _a, _b;
1147
+ const actor = this.#actors.get(actorId);
1148
+ if (!actor) {
1149
+ (_a = logger()) == null ? void 0 : _a.error({ msg: "actor not found", actorId });
1150
+ return void 0;
1151
+ }
1152
+ if (generation !== void 0 && actor.generation !== generation) {
1153
+ (_b = logger()) == null ? void 0 : _b.error({
1154
+ msg: "actor generation mismatch",
1155
+ actorId,
1156
+ generation
1157
+ });
1158
+ return void 0;
1159
+ }
1160
+ return actor;
1161
+ }
1162
+ hasActor(actorId, generation) {
1163
+ const actor = this.#actors.get(actorId);
1164
+ return !!actor && (generation === void 0 || actor.generation === generation);
1165
+ }
1166
+ #removeActor(actorId, generation) {
1167
+ var _a, _b, _c;
1168
+ const actor = this.#actors.get(actorId);
1169
+ if (!actor) {
1170
+ (_a = logger()) == null ? void 0 : _a.error({ msg: "actor not found", actorId });
1171
+ return void 0;
1172
+ }
1173
+ if (generation !== void 0 && actor.generation !== generation) {
1174
+ (_b = logger()) == null ? void 0 : _b.error({
1175
+ msg: "actor generation mismatch",
1176
+ actorId,
1177
+ generation
1178
+ });
1179
+ return void 0;
1180
+ }
1181
+ this.#actors.delete(actorId);
1182
+ const actorWebSockets = this.#actorWebSockets.get(actorId);
1183
+ if (actorWebSockets) {
1184
+ for (const ws of actorWebSockets) {
1185
+ try {
1186
+ ws.close(1e3, "Actor stopped");
1187
+ } catch (err) {
1188
+ (_c = logger()) == null ? void 0 : _c.error({
1189
+ msg: "error closing websocket for actor",
1190
+ actorId,
1191
+ err
1192
+ });
1193
+ }
1194
+ }
1195
+ this.#actorWebSockets.delete(actorId);
1196
+ }
1197
+ return actor;
1198
+ }
1199
+ // MARK: Start
1200
+ async start() {
1201
+ var _a;
1202
+ if (this.#started) throw new Error("Cannot call runner.start twice");
1203
+ this.#started = true;
1204
+ (_a = logger()) == null ? void 0 : _a.info("starting runner");
1205
+ try {
1206
+ await this.#openTunnelAndWait();
1207
+ await this.#openPegboardWebSocket();
1208
+ } catch (error) {
1209
+ this.#started = false;
1210
+ throw error;
1211
+ }
1212
+ if (!this.#config.noAutoShutdown) {
1213
+ process.on("SIGTERM", this.shutdown.bind(this, false, true));
1214
+ process.on("SIGINT", this.shutdown.bind(this, false, true));
1215
+ }
1216
+ }
1217
+ // MARK: Shutdown
1218
+ async shutdown(immediate, exit = false) {
1219
+ var _a, _b, _c, _d, _e, _f, _g, _h;
1220
+ (_a = logger()) == null ? void 0 : _a.info({ msg: "starting shutdown...", immediate });
1221
+ this.#shutdown = true;
1222
+ if (this.#reconnectTimeout) {
1223
+ clearTimeout(this.#reconnectTimeout);
1224
+ this.#reconnectTimeout = void 0;
1225
+ }
1226
+ if (this.#runnerLostTimeout) {
1227
+ clearTimeout(this.#runnerLostTimeout);
1228
+ this.#runnerLostTimeout = void 0;
1229
+ }
1230
+ if (this.#pingLoop) {
1231
+ clearInterval(this.#pingLoop);
1232
+ this.#pingLoop = void 0;
1233
+ }
1234
+ if (this.#ackInterval) {
1235
+ clearInterval(this.#ackInterval);
1236
+ this.#ackInterval = void 0;
1237
+ }
1238
+ if (this.#eventPruneInterval) {
1239
+ clearInterval(this.#eventPruneInterval);
1240
+ this.#eventPruneInterval = void 0;
1241
+ }
1242
+ if (this.#kvCleanupInterval) {
1243
+ clearInterval(this.#kvCleanupInterval);
1244
+ this.#kvCleanupInterval = void 0;
1245
+ }
1246
+ for (const request of this.#kvRequests.values()) {
1247
+ request.reject(
1248
+ new Error("WebSocket connection closed during shutdown")
1249
+ );
1250
+ }
1251
+ this.#kvRequests.clear();
1252
+ if (this.#pegboardWebSocket && this.#pegboardWebSocket.readyState === WebSocket3.OPEN) {
1253
+ const pegboardWebSocket = this.#pegboardWebSocket;
1254
+ if (immediate) {
1255
+ pegboardWebSocket.close(1e3, "Stopping");
1256
+ } else {
1257
+ try {
1258
+ (_b = logger()) == null ? void 0 : _b.info({
1259
+ msg: "sending stopping message",
1260
+ readyState: pegboardWebSocket.readyState
1261
+ });
1262
+ const encoded = protocol.encodeToServer({
1263
+ tag: "ToServerStopping",
1264
+ val: null
1265
+ });
1266
+ if (this.#pegboardWebSocket && this.#pegboardWebSocket.readyState === WebSocket3.OPEN) {
1267
+ this.#pegboardWebSocket.send(encoded);
1268
+ } else {
1269
+ (_c = logger()) == null ? void 0 : _c.error(
1270
+ "WebSocket not available or not open for sending data"
1271
+ );
1272
+ }
1273
+ const closePromise = new Promise((resolve) => {
1274
+ if (!pegboardWebSocket)
1275
+ throw new Error("missing pegboardWebSocket");
1276
+ pegboardWebSocket.addEventListener("close", (ev) => {
1277
+ var _a2;
1278
+ (_a2 = logger()) == null ? void 0 : _a2.info({
1279
+ msg: "connection closed",
1280
+ code: ev.code,
1281
+ reason: ev.reason.toString()
1282
+ });
1283
+ resolve();
1284
+ });
1285
+ });
1286
+ (_d = logger()) == null ? void 0 : _d.info("closing WebSocket");
1287
+ pegboardWebSocket.close(1e3, "Stopping");
1288
+ await closePromise;
1289
+ (_e = logger()) == null ? void 0 : _e.info("websocket shutdown completed");
1290
+ } catch (error) {
1291
+ (_f = logger()) == null ? void 0 : _f.error({
1292
+ msg: "error during websocket shutdown:",
1293
+ error
1294
+ });
1295
+ pegboardWebSocket.close();
1296
+ }
1297
+ }
1298
+ } else {
1299
+ (_g = logger()) == null ? void 0 : _g.warn("no runner WebSocket to shutdown or already closed");
1300
+ }
1301
+ if (this.#tunnel) {
1302
+ this.#tunnel.shutdown();
1303
+ (_h = logger()) == null ? void 0 : _h.info("tunnel shutdown completed");
1304
+ }
1305
+ if (exit) process.exit(0);
1306
+ this.#config.onShutdown();
1307
+ }
1308
+ // MARK: Networking
1309
+ get pegboardUrl() {
1310
+ const endpoint = this.#config.pegboardEndpoint || this.#config.endpoint;
1311
+ const wsEndpoint = endpoint.replace("http://", "ws://").replace("https://", "wss://");
1312
+ return `${wsEndpoint}?protocol_version=1&namespace=${encodeURIComponent(this.#config.namespace)}&runner_key=${encodeURIComponent(this.#config.runnerKey)}`;
1313
+ }
1314
+ get pegboardTunnelUrl() {
1315
+ const endpoint = this.#config.pegboardRelayEndpoint || this.#config.pegboardEndpoint || this.#config.endpoint;
1316
+ const wsEndpoint = endpoint.replace("http://", "ws://").replace("https://", "wss://");
1317
+ return `${wsEndpoint}?protocol_version=1&namespace=${encodeURIComponent(this.#config.namespace)}&runner_name=${encodeURIComponent(this.#config.runnerName)}&runner_key=${encodeURIComponent(this.#config.runnerKey)}`;
1318
+ }
1319
+ async #openTunnelAndWait() {
1320
+ return new Promise((resolve, reject) => {
1321
+ var _a, _b, _c;
1322
+ const url = this.pegboardTunnelUrl;
1323
+ (_a = logger()) == null ? void 0 : _a.info({ msg: "opening tunnel to:", url });
1324
+ (_b = logger()) == null ? void 0 : _b.info({
1325
+ msg: "current runner id:",
1326
+ runnerId: this.runnerId || "none"
1327
+ });
1328
+ (_c = logger()) == null ? void 0 : _c.info({
1329
+ msg: "active actors count:",
1330
+ actors: this.#actors.size
1331
+ });
1332
+ let connected = false;
1333
+ this.#tunnel = new Tunnel(this, url, {
1334
+ onConnected: () => {
1335
+ var _a2;
1336
+ if (!connected) {
1337
+ connected = true;
1338
+ (_a2 = logger()) == null ? void 0 : _a2.info("tunnel connected");
1339
+ resolve();
1340
+ }
1341
+ },
1342
+ onDisconnected: () => {
1343
+ if (!connected) {
1344
+ reject(new Error("Tunnel connection failed"));
1345
+ }
1346
+ }
1347
+ });
1348
+ this.#tunnel.start();
1349
+ });
1350
+ }
1351
+ // MARK: Runner protocol
1352
+ async #openPegboardWebSocket() {
1353
+ const WS = await importWebSocket();
1354
+ const ws = new WS(this.pegboardUrl, {
1355
+ headers: {
1356
+ "x-rivet-target": "runner-ws"
1357
+ }
1358
+ });
1359
+ this.#pegboardWebSocket = ws;
1360
+ ws.addEventListener("open", () => {
1361
+ var _a;
1362
+ (_a = logger()) == null ? void 0 : _a.info("Connected");
1363
+ this.#reconnectAttempt = 0;
1364
+ if (this.#reconnectTimeout) {
1365
+ clearTimeout(this.#reconnectTimeout);
1366
+ this.#reconnectTimeout = void 0;
1367
+ }
1368
+ if (this.#runnerLostTimeout) {
1369
+ clearTimeout(this.#runnerLostTimeout);
1370
+ this.#runnerLostTimeout = void 0;
1371
+ }
1372
+ const init = {
1373
+ name: this.#config.runnerName,
1374
+ version: this.#config.version,
1375
+ totalSlots: this.#config.totalSlots,
1376
+ lastCommandIdx: this.#lastCommandIdx >= 0 ? BigInt(this.#lastCommandIdx) : null,
1377
+ prepopulateActorNames: new Map(
1378
+ Object.entries(this.#config.prepopulateActorNames).map(
1379
+ ([name, data]) => [
1380
+ name,
1381
+ { metadata: JSON.stringify(data.metadata) }
1382
+ ]
1383
+ )
1384
+ ),
1385
+ metadata: JSON.stringify(this.#config.metadata)
1386
+ };
1387
+ this.#sendToServer({
1388
+ tag: "ToServerInit",
1389
+ val: init
1390
+ });
1391
+ this.#processUnsentKvRequests();
1392
+ const pingInterval = 1e3;
1393
+ const pingLoop = setInterval(() => {
1394
+ var _a2;
1395
+ if (ws.readyState === WebSocket3.OPEN) {
1396
+ this.#sendToServer({
1397
+ tag: "ToServerPing",
1398
+ val: {
1399
+ ts: BigInt(Date.now())
1400
+ }
1401
+ });
1402
+ } else {
1403
+ clearInterval(pingLoop);
1404
+ (_a2 = logger()) == null ? void 0 : _a2.info("WebSocket not open, stopping ping loop");
1405
+ }
1406
+ }, pingInterval);
1407
+ this.#pingLoop = pingLoop;
1408
+ const ackInterval = 5 * 60 * 1e3;
1409
+ const ackLoop = setInterval(() => {
1410
+ var _a2;
1411
+ if (ws.readyState === WebSocket3.OPEN) {
1412
+ this.#sendCommandAcknowledgment();
1413
+ } else {
1414
+ clearInterval(ackLoop);
1415
+ (_a2 = logger()) == null ? void 0 : _a2.info("WebSocket not open, stopping ack loop");
1416
+ }
1417
+ }, ackInterval);
1418
+ this.#ackInterval = ackLoop;
1419
+ });
1420
+ ws.addEventListener("message", async (ev) => {
1421
+ var _a, _b;
1422
+ let buf;
1423
+ if (ev.data instanceof Blob) {
1424
+ buf = new Uint8Array(await ev.data.arrayBuffer());
1425
+ } else if (Buffer.isBuffer(ev.data)) {
1426
+ buf = new Uint8Array(ev.data);
1427
+ } else {
1428
+ throw new Error("expected binary data, got " + typeof ev.data);
1429
+ }
1430
+ const message = protocol.decodeToClient(buf);
1431
+ if (message.tag === "ToClientInit") {
1432
+ const init = message.val;
1433
+ const hadRunnerId = !!this.runnerId;
1434
+ this.runnerId = init.runnerId;
1435
+ this.#runnerLostThreshold = ((_a = init.metadata) == null ? void 0 : _a.runnerLostThreshold) ? Number(init.metadata.runnerLostThreshold) : void 0;
1436
+ (_b = logger()) == null ? void 0 : _b.info({
1437
+ msg: "received init",
1438
+ runnerId: init.runnerId,
1439
+ lastEventIdx: init.lastEventIdx,
1440
+ runnerLostThreshold: this.#runnerLostThreshold
1441
+ });
1442
+ this.#resendUnacknowledgedEvents(init.lastEventIdx);
1443
+ this.#config.onConnected();
1444
+ } else if (message.tag === "ToClientCommands") {
1445
+ const commands = message.val;
1446
+ this.#handleCommands(commands);
1447
+ } else if (message.tag === "ToClientAckEvents") {
1448
+ throw new Error("TODO");
1449
+ } else if (message.tag === "ToClientKvResponse") {
1450
+ const kvResponse = message.val;
1451
+ this.#handleKvResponse(kvResponse);
1452
+ }
1453
+ });
1454
+ ws.addEventListener("error", (ev) => {
1455
+ var _a;
1456
+ (_a = logger()) == null ? void 0 : _a.error("WebSocket error:", ev.error);
1457
+ });
1458
+ ws.addEventListener("close", (ev) => {
1459
+ var _a, _b;
1460
+ (_a = logger()) == null ? void 0 : _a.info({
1461
+ msg: "connection closed",
1462
+ code: ev.code,
1463
+ reason: ev.reason.toString()
1464
+ });
1465
+ this.#config.onDisconnected();
1466
+ if (this.#pingLoop) {
1467
+ clearInterval(this.#pingLoop);
1468
+ this.#pingLoop = void 0;
1469
+ }
1470
+ if (this.#ackInterval) {
1471
+ clearInterval(this.#ackInterval);
1472
+ this.#ackInterval = void 0;
1473
+ }
1474
+ if (!this.#shutdown) {
1475
+ if (this.#runnerLostThreshold && this.#runnerLostThreshold > 0) {
1476
+ (_b = logger()) == null ? void 0 : _b.info({
1477
+ msg: "starting runner lost timeout",
1478
+ seconds: this.#runnerLostThreshold / 1e3
1479
+ });
1480
+ this.#runnerLostTimeout = setTimeout(() => {
1481
+ this.#stopAllActors();
1482
+ }, this.#runnerLostThreshold);
1483
+ }
1484
+ this.#scheduleReconnect();
1485
+ }
1486
+ });
1487
+ }
1488
+ #handleCommands(commands) {
1489
+ var _a, _b;
1490
+ (_a = logger()) == null ? void 0 : _a.info({
1491
+ msg: "received commands",
1492
+ commandCount: commands.length
1493
+ });
1494
+ for (const commandWrapper of commands) {
1495
+ (_b = logger()) == null ? void 0 : _b.info({ msg: "received command", commandWrapper });
1496
+ if (commandWrapper.inner.tag === "CommandStartActor") {
1497
+ this.#handleCommandStartActor(commandWrapper);
1498
+ } else if (commandWrapper.inner.tag === "CommandStopActor") {
1499
+ this.#handleCommandStopActor(commandWrapper);
1500
+ }
1501
+ this.#lastCommandIdx = Number(commandWrapper.index);
1502
+ }
1503
+ }
1504
+ #handleCommandStartActor(commandWrapper) {
1505
+ const startCommand = commandWrapper.inner.val;
1506
+ const actorId = startCommand.actorId;
1507
+ const generation = startCommand.generation;
1508
+ const config = startCommand.config;
1509
+ const actorConfig = {
1510
+ name: config.name,
1511
+ key: config.key,
1512
+ createTs: config.createTs,
1513
+ input: config.input ? new Uint8Array(config.input) : null
1514
+ };
1515
+ const instance = {
1516
+ actorId,
1517
+ generation,
1518
+ config: actorConfig,
1519
+ requests: /* @__PURE__ */ new Set(),
1520
+ webSockets: /* @__PURE__ */ new Set()
1521
+ };
1522
+ this.#actors.set(actorId, instance);
1523
+ this.#sendActorStateUpdate(actorId, generation, "running");
1524
+ this.#config.onActorStart(actorId, generation, actorConfig).catch((err) => {
1525
+ var _a;
1526
+ (_a = logger()) == null ? void 0 : _a.error({
1527
+ msg: "error in onactorstart for actor",
1528
+ actorId,
1529
+ err
1530
+ });
1531
+ this.stopActor(actorId, generation);
1532
+ });
1533
+ }
1534
+ #handleCommandStopActor(commandWrapper) {
1535
+ const stopCommand = commandWrapper.inner.val;
1536
+ const actorId = stopCommand.actorId;
1537
+ const generation = stopCommand.generation;
1538
+ this.stopActor(actorId, generation);
1539
+ }
1540
+ #sendActorIntent(actorId, generation, intentType) {
1541
+ var _a, _b;
1542
+ if (this.#shutdown) {
1543
+ (_a = logger()) == null ? void 0 : _a.warn("Runner is shut down, cannot send actor intent");
1544
+ return;
1545
+ }
1546
+ let actorIntent;
1547
+ if (intentType === "sleep") {
1548
+ actorIntent = { tag: "ActorIntentSleep", val: null };
1549
+ } else if (intentType === "stop") {
1550
+ actorIntent = {
1551
+ tag: "ActorIntentStop",
1552
+ val: null
1553
+ };
1554
+ } else {
1555
+ unreachable(intentType);
1556
+ }
1557
+ const intentEvent = {
1558
+ actorId,
1559
+ generation,
1560
+ intent: actorIntent
1561
+ };
1562
+ const eventIndex = this.#nextEventIdx++;
1563
+ const eventWrapper = {
1564
+ index: eventIndex,
1565
+ inner: {
1566
+ tag: "EventActorIntent",
1567
+ val: intentEvent
1568
+ }
1569
+ };
1570
+ this.#eventHistory.push({
1571
+ event: eventWrapper,
1572
+ timestamp: Date.now()
1573
+ });
1574
+ (_b = logger()) == null ? void 0 : _b.info({
1575
+ msg: "sending event to server",
1576
+ index: eventWrapper.index,
1577
+ tag: eventWrapper.inner.tag,
1578
+ val: eventWrapper.inner.val
1579
+ });
1580
+ this.#sendToServer({
1581
+ tag: "ToServerEvents",
1582
+ val: [eventWrapper]
1583
+ });
1584
+ }
1585
+ #sendActorStateUpdate(actorId, generation, stateType) {
1586
+ var _a, _b;
1587
+ if (this.#shutdown) {
1588
+ (_a = logger()) == null ? void 0 : _a.warn(
1589
+ "Runner is shut down, cannot send actor state update"
1590
+ );
1591
+ return;
1592
+ }
1593
+ let actorState;
1594
+ if (stateType === "running") {
1595
+ actorState = { tag: "ActorStateRunning", val: null };
1596
+ } else if (stateType === "stopped") {
1597
+ actorState = {
1598
+ tag: "ActorStateStopped",
1599
+ val: {
1600
+ code: protocol.StopCode.Ok,
1601
+ message: "hello"
1602
+ }
1603
+ };
1604
+ } else {
1605
+ unreachable(stateType);
1606
+ }
1607
+ const stateUpdateEvent = {
1608
+ actorId,
1609
+ generation,
1610
+ state: actorState
1611
+ };
1612
+ const eventIndex = this.#nextEventIdx++;
1613
+ const eventWrapper = {
1614
+ index: eventIndex,
1615
+ inner: {
1616
+ tag: "EventActorStateUpdate",
1617
+ val: stateUpdateEvent
1618
+ }
1619
+ };
1620
+ this.#eventHistory.push({
1621
+ event: eventWrapper,
1622
+ timestamp: Date.now()
1623
+ });
1624
+ (_b = logger()) == null ? void 0 : _b.info({
1625
+ msg: "sending event to server",
1626
+ index: eventWrapper.index,
1627
+ tag: eventWrapper.inner.tag,
1628
+ val: eventWrapper.inner.val
1629
+ });
1630
+ this.#sendToServer({
1631
+ tag: "ToServerEvents",
1632
+ val: [eventWrapper]
1633
+ });
1634
+ }
1635
+ #sendCommandAcknowledgment() {
1636
+ var _a;
1637
+ if (this.#shutdown) {
1638
+ (_a = logger()) == null ? void 0 : _a.warn(
1639
+ "Runner is shut down, cannot send command acknowledgment"
1640
+ );
1641
+ return;
1642
+ }
1643
+ if (this.#lastCommandIdx < 0) {
1644
+ return;
1645
+ }
1646
+ this.#sendToServer({
1647
+ tag: "ToServerAckCommands",
1648
+ val: {
1649
+ lastCommandIdx: BigInt(this.#lastCommandIdx)
1650
+ }
1651
+ });
1652
+ }
1653
+ #handleKvResponse(response) {
1654
+ var _a, _b;
1655
+ const requestId = response.requestId;
1656
+ const request = this.#kvRequests.get(requestId);
1657
+ if (!request) {
1658
+ const msg = "received kv response for unknown request id";
1659
+ if (logger()) {
1660
+ (_a = logger()) == null ? void 0 : _a.error({ msg, requestId });
1661
+ } else {
1662
+ (_b = logger()) == null ? void 0 : _b.error({ msg, requestId });
1663
+ }
1664
+ return;
1665
+ }
1666
+ this.#kvRequests.delete(requestId);
1667
+ if (response.data.tag === "KvErrorResponse") {
1668
+ request.reject(
1669
+ new Error(response.data.val.message || "Unknown KV error")
1670
+ );
1671
+ } else {
1672
+ request.resolve(response.data.val);
1673
+ }
1674
+ }
1675
+ #parseGetResponseSimple(response, requestedKeys) {
1676
+ const responseKeys = [];
1677
+ const responseValues = [];
1678
+ for (const key of response.keys) {
1679
+ responseKeys.push(new Uint8Array(key));
1680
+ }
1681
+ for (const value of response.values) {
1682
+ responseValues.push(new Uint8Array(value));
1683
+ }
1684
+ const result = [];
1685
+ for (const requestedKey of requestedKeys) {
1686
+ let found = false;
1687
+ for (let i = 0; i < responseKeys.length; i++) {
1688
+ if (this.#keysEqual(requestedKey, responseKeys[i])) {
1689
+ result.push(responseValues[i]);
1690
+ found = true;
1691
+ break;
1692
+ }
1693
+ }
1694
+ if (!found) {
1695
+ result.push(null);
1696
+ }
1697
+ }
1698
+ return result;
1699
+ }
1700
+ #keysEqual(key1, key2) {
1701
+ if (key1.length !== key2.length) return false;
1702
+ for (let i = 0; i < key1.length; i++) {
1703
+ if (key1[i] !== key2[i]) return false;
1704
+ }
1705
+ return true;
1706
+ }
1707
+ //#parseGetResponse(response: protocol.KvGetResponse) {
1708
+ // const keys: string[] = [];
1709
+ // const values: Uint8Array[] = [];
1710
+ // const metadata: { version: Uint8Array; createTs: bigint }[] = [];
1711
+ //
1712
+ // for (const key of response.keys) {
1713
+ // keys.push(new TextDecoder().decode(key));
1714
+ // }
1715
+ //
1716
+ // for (const value of response.values) {
1717
+ // values.push(new Uint8Array(value));
1718
+ // }
1719
+ //
1720
+ // for (const meta of response.metadata) {
1721
+ // metadata.push({
1722
+ // version: new Uint8Array(meta.version),
1723
+ // createTs: meta.createTs,
1724
+ // });
1725
+ // }
1726
+ //
1727
+ // return { keys, values, metadata };
1728
+ //}
1729
+ #parseListResponseSimple(response) {
1730
+ const result = [];
1731
+ for (let i = 0; i < response.keys.length; i++) {
1732
+ const key = response.keys[i];
1733
+ const value = response.values[i];
1734
+ if (key && value) {
1735
+ const keyBytes = new Uint8Array(key);
1736
+ const valueBytes = new Uint8Array(value);
1737
+ result.push([keyBytes, valueBytes]);
1738
+ }
1739
+ }
1740
+ return result;
1741
+ }
1742
+ //#parseListResponse(response: protocol.KvListResponse) {
1743
+ // const keys: string[] = [];
1744
+ // const values: Uint8Array[] = [];
1745
+ // const metadata: { version: Uint8Array; createTs: bigint }[] = [];
1746
+ //
1747
+ // for (const key of response.keys) {
1748
+ // keys.push(new TextDecoder().decode(key));
1749
+ // }
1750
+ //
1751
+ // for (const value of response.values) {
1752
+ // values.push(new Uint8Array(value));
1753
+ // }
1754
+ //
1755
+ // for (const meta of response.metadata) {
1756
+ // metadata.push({
1757
+ // version: new Uint8Array(meta.version),
1758
+ // createTs: meta.createTs,
1759
+ // });
1760
+ // }
1761
+ //
1762
+ // return { keys, values, metadata };
1763
+ //}
1764
+ // MARK: KV Operations
1765
+ async kvGet(actorId, keys) {
1766
+ const kvKeys = keys.map(
1767
+ (key) => key.buffer.slice(
1768
+ key.byteOffset,
1769
+ key.byteOffset + key.byteLength
1770
+ )
1771
+ );
1772
+ const requestData = {
1773
+ tag: "KvGetRequest",
1774
+ val: { keys: kvKeys }
1775
+ };
1776
+ const response = await this.#sendKvRequest(actorId, requestData);
1777
+ return this.#parseGetResponseSimple(response, keys);
1778
+ }
1779
+ async kvListAll(actorId, options) {
1780
+ const requestData = {
1781
+ tag: "KvListRequest",
1782
+ val: {
1783
+ query: { tag: "KvListAllQuery", val: null },
1784
+ reverse: (options == null ? void 0 : options.reverse) || null,
1785
+ limit: (options == null ? void 0 : options.limit) !== void 0 ? BigInt(options.limit) : null
1786
+ }
1787
+ };
1788
+ const response = await this.#sendKvRequest(actorId, requestData);
1789
+ return this.#parseListResponseSimple(response);
1790
+ }
1791
+ async kvListRange(actorId, start, end, exclusive, options) {
1792
+ const startKey = start.buffer.slice(
1793
+ start.byteOffset,
1794
+ start.byteOffset + start.byteLength
1795
+ );
1796
+ const endKey = end.buffer.slice(
1797
+ end.byteOffset,
1798
+ end.byteOffset + end.byteLength
1799
+ );
1800
+ const requestData = {
1801
+ tag: "KvListRequest",
1802
+ val: {
1803
+ query: {
1804
+ tag: "KvListRangeQuery",
1805
+ val: {
1806
+ start: startKey,
1807
+ end: endKey,
1808
+ exclusive: exclusive || false
1809
+ }
1810
+ },
1811
+ reverse: (options == null ? void 0 : options.reverse) || null,
1812
+ limit: (options == null ? void 0 : options.limit) !== void 0 ? BigInt(options.limit) : null
1813
+ }
1814
+ };
1815
+ const response = await this.#sendKvRequest(actorId, requestData);
1816
+ return this.#parseListResponseSimple(response);
1817
+ }
1818
+ async kvListPrefix(actorId, prefix, options) {
1819
+ const prefixKey = prefix.buffer.slice(
1820
+ prefix.byteOffset,
1821
+ prefix.byteOffset + prefix.byteLength
1822
+ );
1823
+ const requestData = {
1824
+ tag: "KvListRequest",
1825
+ val: {
1826
+ query: {
1827
+ tag: "KvListPrefixQuery",
1828
+ val: { key: prefixKey }
1829
+ },
1830
+ reverse: (options == null ? void 0 : options.reverse) || null,
1831
+ limit: (options == null ? void 0 : options.limit) !== void 0 ? BigInt(options.limit) : null
1832
+ }
1833
+ };
1834
+ const response = await this.#sendKvRequest(actorId, requestData);
1835
+ return this.#parseListResponseSimple(response);
1836
+ }
1837
+ async kvPut(actorId, entries) {
1838
+ const keys = entries.map(
1839
+ ([key, _value]) => key.buffer.slice(
1840
+ key.byteOffset,
1841
+ key.byteOffset + key.byteLength
1842
+ )
1843
+ );
1844
+ const values = entries.map(
1845
+ ([_key, value]) => value.buffer.slice(
1846
+ value.byteOffset,
1847
+ value.byteOffset + value.byteLength
1848
+ )
1849
+ );
1850
+ const requestData = {
1851
+ tag: "KvPutRequest",
1852
+ val: { keys, values }
1853
+ };
1854
+ await this.#sendKvRequest(actorId, requestData);
1855
+ }
1856
+ async kvDelete(actorId, keys) {
1857
+ const kvKeys = keys.map(
1858
+ (key) => key.buffer.slice(
1859
+ key.byteOffset,
1860
+ key.byteOffset + key.byteLength
1861
+ )
1862
+ );
1863
+ const requestData = {
1864
+ tag: "KvDeleteRequest",
1865
+ val: { keys: kvKeys }
1866
+ };
1867
+ await this.#sendKvRequest(actorId, requestData);
1868
+ }
1869
+ async kvDrop(actorId) {
1870
+ const requestData = {
1871
+ tag: "KvDropRequest",
1872
+ val: null
1873
+ };
1874
+ await this.#sendKvRequest(actorId, requestData);
1875
+ }
1876
+ // MARK: Alarm Operations
1877
+ setAlarm(actorId, alarmTs, generation) {
1878
+ const actor = this.getActor(actorId, generation);
1879
+ if (!actor) return;
1880
+ if (this.#shutdown) {
1881
+ console.warn("Runner is shut down, cannot set alarm");
1882
+ return;
1883
+ }
1884
+ const alarmEvent = {
1885
+ actorId,
1886
+ generation: actor.generation,
1887
+ alarmTs: alarmTs !== null ? BigInt(alarmTs) : null
1888
+ };
1889
+ const eventIndex = this.#nextEventIdx++;
1890
+ const eventWrapper = {
1891
+ index: eventIndex,
1892
+ inner: {
1893
+ tag: "EventActorSetAlarm",
1894
+ val: alarmEvent
1895
+ }
1896
+ };
1897
+ this.#eventHistory.push({
1898
+ event: eventWrapper,
1899
+ timestamp: Date.now()
1900
+ });
1901
+ this.#sendToServer({
1902
+ tag: "ToServerEvents",
1903
+ val: [eventWrapper]
1904
+ });
1905
+ }
1906
+ clearAlarm(actorId, generation) {
1907
+ this.setAlarm(actorId, null, generation);
1908
+ }
1909
+ #sendKvRequest(actorId, requestData) {
1910
+ return new Promise((resolve, reject) => {
1911
+ if (this.#shutdown) {
1912
+ reject(new Error("Runner is shut down"));
1913
+ return;
1914
+ }
1915
+ const requestId = this.#nextRequestId++;
1916
+ const isConnected = this.#pegboardWebSocket && this.#pegboardWebSocket.readyState === WebSocket3.OPEN;
1917
+ const requestEntry = {
1918
+ actorId,
1919
+ data: requestData,
1920
+ resolve,
1921
+ reject,
1922
+ sent: false,
1923
+ timestamp: Date.now()
1924
+ };
1925
+ this.#kvRequests.set(requestId, requestEntry);
1926
+ if (isConnected) {
1927
+ this.#sendSingleKvRequest(requestId);
1928
+ }
1929
+ });
1930
+ }
1931
+ #sendSingleKvRequest(requestId) {
1932
+ const request = this.#kvRequests.get(requestId);
1933
+ if (!request || request.sent) return;
1934
+ try {
1935
+ const kvRequest = {
1936
+ actorId: request.actorId,
1937
+ requestId,
1938
+ data: request.data
1939
+ };
1940
+ this.#sendToServer({
1941
+ tag: "ToServerKvRequest",
1942
+ val: kvRequest
1943
+ });
1944
+ request.sent = true;
1945
+ request.timestamp = Date.now();
1946
+ } catch (error) {
1947
+ this.#kvRequests.delete(requestId);
1948
+ request.reject(error);
1949
+ }
1950
+ }
1951
+ #processUnsentKvRequests() {
1952
+ if (!this.#pegboardWebSocket || this.#pegboardWebSocket.readyState !== WebSocket3.OPEN) {
1953
+ return;
1954
+ }
1955
+ let processedCount = 0;
1956
+ for (const [requestId, request] of this.#kvRequests.entries()) {
1957
+ if (!request.sent) {
1958
+ this.#sendSingleKvRequest(requestId);
1959
+ processedCount++;
1960
+ }
1961
+ }
1962
+ if (processedCount > 0) {
1963
+ }
1964
+ }
1965
+ #sendToServer(message) {
1966
+ var _a, _b;
1967
+ if (this.#shutdown) {
1968
+ (_a = logger()) == null ? void 0 : _a.warn(
1969
+ "Runner is shut down, cannot send message to server"
1970
+ );
1971
+ return;
1972
+ }
1973
+ const encoded = protocol.encodeToServer(message);
1974
+ if (this.#pegboardWebSocket && this.#pegboardWebSocket.readyState === WebSocket3.OPEN) {
1975
+ this.#pegboardWebSocket.send(encoded);
1976
+ } else {
1977
+ (_b = logger()) == null ? void 0 : _b.error(
1978
+ "WebSocket not available or not open for sending data"
1979
+ );
1980
+ }
1981
+ }
1982
+ #scheduleReconnect() {
1983
+ if (this.#shutdown) {
1984
+ return;
1985
+ }
1986
+ const delay = calculateBackoff(this.#reconnectAttempt, {
1987
+ initialDelay: 1e3,
1988
+ maxDelay: 3e4,
1989
+ multiplier: 2,
1990
+ jitter: true
1991
+ });
1992
+ this.#reconnectTimeout = setTimeout(async () => {
1993
+ if (!this.#shutdown) {
1994
+ this.#reconnectAttempt++;
1995
+ await this.#openPegboardWebSocket();
1996
+ }
1997
+ }, delay);
1998
+ }
1999
+ #resendUnacknowledgedEvents(lastEventIdx) {
2000
+ const eventsToResend = this.#eventHistory.filter(
2001
+ (item) => item.event.index > lastEventIdx
2002
+ );
2003
+ if (eventsToResend.length === 0) return;
2004
+ const events = eventsToResend.map((item) => item.event);
2005
+ this.#sendToServer({
2006
+ tag: "ToServerEvents",
2007
+ val: events
2008
+ });
2009
+ }
2010
+ // TODO(RVT-4986): Prune when server acks events instead of based on old events
2011
+ #pruneOldEvents() {
2012
+ const fiveMinutesAgo = Date.now() - 5 * 60 * 1e3;
2013
+ const originalLength = this.#eventHistory.length;
2014
+ this.#eventHistory = this.#eventHistory.filter(
2015
+ (item) => item.timestamp > fiveMinutesAgo
2016
+ );
2017
+ const prunedCount = originalLength - this.#eventHistory.length;
2018
+ if (prunedCount > 0) {
2019
+ }
2020
+ }
2021
+ #cleanupOldKvRequests() {
2022
+ const thirtySecondsAgo = Date.now() - KV_EXPIRE;
2023
+ const toDelete = [];
2024
+ for (const [requestId, request] of this.#kvRequests.entries()) {
2025
+ if (request.timestamp < thirtySecondsAgo) {
2026
+ request.reject(
2027
+ new Error(
2028
+ "KV request timed out waiting for WebSocket connection"
2029
+ )
2030
+ );
2031
+ toDelete.push(requestId);
2032
+ }
2033
+ }
2034
+ for (const requestId of toDelete) {
2035
+ this.#kvRequests.delete(requestId);
2036
+ }
2037
+ if (toDelete.length > 0) {
2038
+ }
2039
+ }
2040
+ };
2041
+ export {
2042
+ Runner
2043
+ };
2044
+ //# sourceMappingURL=mod.js.map