@rivetkit/engine-runner 25.7.1-rc.1 → 25.7.2-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/.turbo/turbo-build.log +10 -10
- package/LICENSE +203 -0
- package/dist/mod.cjs +68 -289
- package/dist/mod.cjs.map +1 -1
- package/dist/mod.d.cts +3 -0
- package/dist/mod.d.ts +3 -0
- package/dist/mod.js +67 -288
- package/dist/mod.js.map +1 -1
- package/package.json +36 -37
- package/src/mod.ts +42 -65
- package/src/tunnel.ts +53 -298
package/dist/mod.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["/Users/nathan/engine/sdks/typescript/runner/dist/mod.cjs","../src/mod.ts","../src/log.ts","../src/websocket.ts","../src/utils.ts","../src/tunnel.ts","../src/websocket-tunnel-adapter.ts"],"names":["logger","_a"],"mappings":"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","file":"/Users/nathan/engine/sdks/typescript/runner/dist/mod.cjs","sourcesContent":[null,"import WebSocket from \"ws\";\nimport { importWebSocket } from \"./websocket.js\";\nimport * as protocol from \"@rivetkit/engine-runner-protocol\";\nimport { unreachable, calculateBackoff } from \"./utils\";\nimport { Tunnel } from \"./tunnel\";\nimport { WebSocketTunnelAdapter } from \"./websocket-tunnel-adapter\";\nimport type { Logger } from \"pino\";\nimport { setLogger, logger } from \"./log.js\";\n\nconst KV_EXPIRE: number = 30_000;\n\nexport interface ActorInstance {\n\tactorId: string;\n\tgeneration: number;\n\tconfig: ActorConfig;\n\trequests: Set<string>; // Track active request IDs\n\twebSockets: Set<string>; // Track active WebSocket IDs\n}\n\nexport interface ActorConfig {\n\tname: string;\n\tkey: string | null;\n\tcreateTs: bigint;\n\tinput: Uint8Array | null;\n}\n\nexport interface RunnerConfig {\n\tlogger?: Logger;\n\tversion: number;\n\tendpoint: string;\n\tpegboardEndpoint?: string;\n\tpegboardRelayEndpoint?: string;\n\tnamespace: string;\n\ttotalSlots: number;\n\trunnerName: string;\n\trunnerKey: string;\n\tprepopulateActorNames: Record<string, { metadata: Record<string, any> }>;\n\tmetadata?: Record<string, any>;\n\tonConnected: () => void;\n\tonDisconnected: () => void;\n\tonShutdown: () => void;\n\tfetch: (actorId: string, request: Request) => Promise<Response>;\n\twebsocket?: (actorId: string, ws: any, request: Request) => Promise<void>;\n\tonActorStart: (\n\t\tactorId: string,\n\t\tgeneration: number,\n\t\tconfig: ActorConfig,\n\t) => Promise<void>;\n\tonActorStop: (actorId: string, generation: number) => Promise<void>;\n\tnoAutoShutdown?: boolean;\n}\n\nexport interface KvListOptions {\n\treverse?: boolean;\n\tlimit?: number;\n}\n\ninterface KvRequestEntry {\n\tactorId: string;\n\tdata: protocol.KvRequestData;\n\tresolve: (value: any) => void;\n\treject: (error: unknown) => void;\n\tsent: boolean;\n\ttimestamp: number;\n}\n\nexport class Runner {\n\t#config: RunnerConfig;\n\n\tget config(): RunnerConfig {\n\t\treturn this.#config;\n\t}\n\n\t#actors: Map<string, ActorInstance> = new Map();\n\t#actorWebSockets: Map<string, Set<WebSocketTunnelAdapter>> = new Map();\n\n\t// WebSocket\n\t#pegboardWebSocket?: WebSocket;\n\trunnerId?: string;\n\t#lastCommandIdx: number = -1;\n\t#pingLoop?: NodeJS.Timeout;\n\t#nextEventIdx: bigint = 0n;\n\t#started: boolean = false;\n\t#shutdown: boolean = false;\n\t#reconnectAttempt: number = 0;\n\t#reconnectTimeout?: NodeJS.Timeout;\n\n\t// Runner lost threshold management\n\t#runnerLostThreshold?: number;\n\t#runnerLostTimeout?: NodeJS.Timeout;\n\n\t// Event storage for resending\n\t#eventHistory: { event: protocol.EventWrapper; timestamp: number }[] = [];\n\t#eventPruneInterval?: NodeJS.Timeout;\n\n\t// Command acknowledgment\n\t#ackInterval?: NodeJS.Timeout;\n\n\t// KV operations\n\t#nextRequestId: number = 0;\n\t#kvRequests: Map<number, KvRequestEntry> = new Map();\n\t#kvCleanupInterval?: NodeJS.Timeout;\n\n\t// Tunnel for HTTP/WebSocket forwarding\n\t#tunnel?: Tunnel;\n\n\tconstructor(config: RunnerConfig) {\n\t\tthis.#config = config;\n\t\tif (this.#config.logger) setLogger(this.#config.logger);\n\n\t\t// TODO(RVT-4986): Prune when server acks events\n\t\t// Start pruning old events every minute\n\t\tthis.#eventPruneInterval = setInterval(() => {\n\t\t\tthis.#pruneOldEvents();\n\t\t}, 60000); // Run every minute\n\n\t\t// Start cleaning up old unsent KV requests every 15 seconds\n\t\tthis.#kvCleanupInterval = setInterval(() => {\n\t\t\tthis.#cleanupOldKvRequests();\n\t\t}, 15000); // Run every 15 seconds\n\t}\n\n\t// MARK: Manage actors\n\tsleepActor(actorId: string, generation?: number) {\n\t\tconst actor = this.getActor(actorId, generation);\n\t\tif (!actor) return;\n\n\t\t// Keep the actor instance in memory during sleep\n\t\tthis.#sendActorIntent(actorId, actor.generation, \"sleep\");\n\n\t\t// NOTE: We do NOT remove the actor from this.#actors here\n\t\t// The server will send a StopActor command if it wants to fully stop\n\t}\n\n\tasync stopActor(actorId: string, generation?: number) {\n\t\tconst actor = this.#removeActor(actorId, generation);\n\t\tif (!actor) return;\n\n\t\t// Unregister actor from tunnel\n\t\tif (this.#tunnel) {\n\t\t\tthis.#tunnel.unregisterActor(actor);\n\t\t}\n\n\t\t// If onActorStop times out, Pegboard will handle this timeout with ACTOR_STOP_THRESHOLD_DURATION_MS\n\t\ttry {\n\t\t\tawait this.#config.onActorStop(actorId, actor.generation);\n\t\t} catch (err) {\n\t\t\tconsole.error(`Error in onActorStop for actor ${actorId}:`, err);\n\t\t}\n\n\t\tthis.#sendActorStateUpdate(actorId, actor.generation, \"stopped\");\n\n\t\tthis.#config.onActorStop(actorId, actor.generation).catch((err) => {\n\t\t\tlogger()?.error({\n\t\t\t\tmsg: \"error in onactorstop for actor\",\n\t\t\t\tactorId,\n\t\t\t\terr,\n\t\t\t});\n\t\t});\n\t}\n\n\t#stopAllActors() {\n\t\tlogger()?.info(\n\t\t\t\"stopping all actors due to runner lost threshold exceeded\",\n\t\t);\n\n\t\tconst actorIds = Array.from(this.#actors.keys());\n\t\tfor (const actorId of actorIds) {\n\t\t\tthis.stopActor(actorId);\n\t\t}\n\t}\n\n\tgetActor(actorId: string, generation?: number): ActorInstance | undefined {\n\t\tconst actor = this.#actors.get(actorId);\n\t\tif (!actor) {\n\t\t\tlogger()?.error({ msg: \"actor not found\", actorId });\n\t\t\treturn undefined;\n\t\t}\n\t\tif (generation !== undefined && actor.generation !== generation) {\n\t\t\tlogger()?.error({\n\t\t\t\tmsg: \"actor generation mismatch\",\n\t\t\t\tactorId,\n\t\t\t\tgeneration,\n\t\t\t});\n\t\t\treturn undefined;\n\t\t}\n\n\t\treturn actor;\n\t}\n\n\thasActor(actorId: string, generation?: number): boolean {\n\t\tconst actor = this.#actors.get(actorId);\n\n\t\treturn (\n\t\t\t!!actor &&\n\t\t\t(generation === undefined || actor.generation === generation)\n\t\t);\n\t}\n\n\t#removeActor(\n\t\tactorId: string,\n\t\tgeneration?: number,\n\t): ActorInstance | undefined {\n\t\tconst actor = this.#actors.get(actorId);\n\t\tif (!actor) {\n\t\t\tlogger()?.error({ msg: \"actor not found\", actorId });\n\t\t\treturn undefined;\n\t\t}\n\t\tif (generation !== undefined && actor.generation !== generation) {\n\t\t\tlogger()?.error({\n\t\t\t\tmsg: \"actor generation mismatch\",\n\t\t\t\tactorId,\n\t\t\t\tgeneration,\n\t\t\t});\n\t\t\treturn undefined;\n\t\t}\n\n\t\tthis.#actors.delete(actorId);\n\n\t\t// Close all WebSocket connections for this actor\n\t\tconst actorWebSockets = this.#actorWebSockets.get(actorId);\n\t\tif (actorWebSockets) {\n\t\t\tfor (const ws of actorWebSockets) {\n\t\t\t\ttry {\n\t\t\t\t\tws.close(1000, \"Actor stopped\");\n\t\t\t\t} catch (err) {\n\t\t\t\t\tlogger()?.error({\n\t\t\t\t\t\tmsg: \"error closing websocket for actor\",\n\t\t\t\t\t\tactorId,\n\t\t\t\t\t\terr,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\t\t\tthis.#actorWebSockets.delete(actorId);\n\t\t}\n\n\t\treturn actor;\n\t}\n\n\t// MARK: Start\n\tasync start() {\n\t\tif (this.#started) throw new Error(\"Cannot call runner.start twice\");\n\t\tthis.#started = true;\n\n\t\tlogger()?.info(\"starting runner\");\n\n\t\ttry {\n\t\t\t// Connect tunnel first and wait for it to be ready before connecting runner WebSocket\n\t\t\t// This prevents a race condition where the runner appears ready but can't accept network requests\n\t\t\tawait this.#openTunnelAndWait();\n\t\t\tawait this.#openPegboardWebSocket();\n\t\t} catch (error) {\n\t\t\tthis.#started = false;\n\t\t\tthrow error;\n\t\t}\n\n\t\tif (!this.#config.noAutoShutdown) {\n\t\t\tprocess.on(\"SIGTERM\", this.shutdown.bind(this, false, true));\n\t\t\tprocess.on(\"SIGINT\", this.shutdown.bind(this, false, true));\n\t\t}\n\t}\n\n\t// MARK: Shutdown\n\tasync shutdown(immediate: boolean, exit: boolean = false) {\n\t\tlogger()?.info({ msg: \"starting shutdown...\", immediate });\n\t\tthis.#shutdown = true;\n\n\t\t// Clear reconnect timeout\n\t\tif (this.#reconnectTimeout) {\n\t\t\tclearTimeout(this.#reconnectTimeout);\n\t\t\tthis.#reconnectTimeout = undefined;\n\t\t}\n\n\t\t// Clear runner lost timeout\n\t\tif (this.#runnerLostTimeout) {\n\t\t\tclearTimeout(this.#runnerLostTimeout);\n\t\t\tthis.#runnerLostTimeout = undefined;\n\t\t}\n\n\t\t// Clear ping loop\n\t\tif (this.#pingLoop) {\n\t\t\tclearInterval(this.#pingLoop);\n\t\t\tthis.#pingLoop = undefined;\n\t\t}\n\n\t\t// Clear ack interval\n\t\tif (this.#ackInterval) {\n\t\t\tclearInterval(this.#ackInterval);\n\t\t\tthis.#ackInterval = undefined;\n\t\t}\n\n\t\t// Clear event prune interval\n\t\tif (this.#eventPruneInterval) {\n\t\t\tclearInterval(this.#eventPruneInterval);\n\t\t\tthis.#eventPruneInterval = undefined;\n\t\t}\n\n\t\t// Clear KV cleanup interval\n\t\tif (this.#kvCleanupInterval) {\n\t\t\tclearInterval(this.#kvCleanupInterval);\n\t\t\tthis.#kvCleanupInterval = undefined;\n\t\t}\n\n\t\t// Reject all KV requests\n\t\tfor (const request of this.#kvRequests.values()) {\n\t\t\trequest.reject(\n\t\t\t\tnew Error(\"WebSocket connection closed during shutdown\"),\n\t\t\t);\n\t\t}\n\t\tthis.#kvRequests.clear();\n\n\t\t// Close WebSocket\n\t\tif (\n\t\t\tthis.#pegboardWebSocket &&\n\t\t\tthis.#pegboardWebSocket.readyState === WebSocket.OPEN\n\t\t) {\n\t\t\tconst pegboardWebSocket = this.#pegboardWebSocket;\n\t\t\tif (immediate) {\n\t\t\t\t// Stop immediately\n\t\t\t\tpegboardWebSocket.close(1000, \"Stopping\");\n\t\t\t} else {\n\t\t\t\t// Wait for actors to shut down before stopping\n\t\t\t\ttry {\n\t\t\t\t\tlogger()?.info({\n\t\t\t\t\t\tmsg: \"sending stopping message\",\n\t\t\t\t\t\treadyState: pegboardWebSocket.readyState,\n\t\t\t\t\t});\n\n\t\t\t\t\t// NOTE: We don't use #sendToServer here because that function checks if the runner is\n\t\t\t\t\t// shut down\n\t\t\t\t\tconst encoded = protocol.encodeToServer({\n\t\t\t\t\t\ttag: \"ToServerStopping\",\n\t\t\t\t\t\tval: null,\n\t\t\t\t\t});\n\t\t\t\t\tif (\n\t\t\t\t\t\tthis.#pegboardWebSocket &&\n\t\t\t\t\t\tthis.#pegboardWebSocket.readyState === WebSocket.OPEN\n\t\t\t\t\t) {\n\t\t\t\t\t\tthis.#pegboardWebSocket.send(encoded);\n\t\t\t\t\t} else {\n\t\t\t\t\t\tlogger()?.error(\n\t\t\t\t\t\t\t\"WebSocket not available or not open for sending data\",\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\n\t\t\t\t\tconst closePromise = new Promise<void>((resolve) => {\n\t\t\t\t\t\tif (!pegboardWebSocket)\n\t\t\t\t\t\t\tthrow new Error(\"missing pegboardWebSocket\");\n\n\t\t\t\t\t\tpegboardWebSocket.addEventListener(\"close\", (ev) => {\n\t\t\t\t\t\t\tlogger()?.info({\n\t\t\t\t\t\t\t\tmsg: \"connection closed\",\n\t\t\t\t\t\t\t\tcode: ev.code,\n\t\t\t\t\t\t\t\treason: ev.reason.toString(),\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\tresolve();\n\t\t\t\t\t\t});\n\t\t\t\t\t});\n\n\t\t\t\t\t// TODO: Wait for all actors to stop before closing ws\n\n\t\t\t\t\tlogger()?.info(\"closing WebSocket\");\n\t\t\t\t\tpegboardWebSocket.close(1000, \"Stopping\");\n\n\t\t\t\t\tawait closePromise;\n\n\t\t\t\t\tlogger()?.info(\"websocket shutdown completed\");\n\t\t\t\t} catch (error) {\n\t\t\t\t\tlogger()?.error({\n\t\t\t\t\t\tmsg: \"error during websocket shutdown:\",\n\t\t\t\t\t\terror,\n\t\t\t\t\t});\n\t\t\t\t\tpegboardWebSocket.close();\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\n\t\t\tlogger()?.warn(\"no runner WebSocket to shutdown or already closed\");\n\t\t}\n\n\t\t// Close tunnel\n\t\tif (this.#tunnel) {\n\t\t\tthis.#tunnel.shutdown();\n\t\t\tlogger()?.info(\"tunnel shutdown completed\");\n\t\t}\n\n\t\tif (exit) process.exit(0);\n\n\t\tthis.#config.onShutdown();\n\t}\n\n\t// MARK: Networking\n\tget pegboardUrl() {\n\t\tconst endpoint = this.#config.pegboardEndpoint || this.#config.endpoint;\n\t\tconst wsEndpoint = endpoint\n\t\t\t.replace(\"http://\", \"ws://\")\n\t\t\t.replace(\"https://\", \"wss://\");\n\t\treturn `${wsEndpoint}?protocol_version=1&namespace=${encodeURIComponent(this.#config.namespace)}&runner_key=${encodeURIComponent(this.#config.runnerKey)}`;\n\t}\n\n\tget pegboardTunnelUrl() {\n\t\tconst endpoint =\n\t\t\tthis.#config.pegboardRelayEndpoint ||\n\t\t\tthis.#config.pegboardEndpoint ||\n\t\t\tthis.#config.endpoint;\n\t\tconst wsEndpoint = endpoint\n\t\t\t.replace(\"http://\", \"ws://\")\n\t\t\t.replace(\"https://\", \"wss://\");\n\t\treturn `${wsEndpoint}?protocol_version=1&namespace=${encodeURIComponent(this.#config.namespace)}&runner_name=${encodeURIComponent(this.#config.runnerName)}&runner_key=${encodeURIComponent(this.#config.runnerKey)}`;\n\t}\n\n\tasync #openTunnelAndWait(): Promise<void> {\n\t\treturn new Promise((resolve, reject) => {\n\t\t\tconst url = this.pegboardTunnelUrl;\n\t\t\tlogger()?.info({ msg: \"opening tunnel to:\", url });\n\t\t\tlogger()?.info({\n\t\t\t\tmsg: \"current runner id:\",\n\t\t\t\trunnerId: this.runnerId || \"none\",\n\t\t\t});\n\t\t\tlogger()?.info({\n\t\t\t\tmsg: \"active actors count:\",\n\t\t\t\tactors: this.#actors.size,\n\t\t\t});\n\n\t\t\tlet connected = false;\n\n\t\t\tthis.#tunnel = new Tunnel(this, url, {\n\t\t\t\tonConnected: () => {\n\t\t\t\t\tif (!connected) {\n\t\t\t\t\t\tconnected = true;\n\t\t\t\t\t\tlogger()?.info(\"tunnel connected\");\n\t\t\t\t\t\tresolve();\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\tonDisconnected: () => {\n\t\t\t\t\tif (!connected) {\n\t\t\t\t\t\t// First connection attempt failed\n\t\t\t\t\t\treject(new Error(\"Tunnel connection failed\"));\n\t\t\t\t\t}\n\t\t\t\t\t// If already connected, tunnel will handle reconnection automatically\n\t\t\t\t},\n\t\t\t});\n\t\t\tthis.#tunnel.start();\n\t\t});\n\t}\n\n\t// MARK: Runner protocol\n\tasync #openPegboardWebSocket() {\n\t\tconst WS = await importWebSocket();\n\t\tconst ws = new WS(this.pegboardUrl, {\n\t\t\theaders: {\n\t\t\t\t\"x-rivet-target\": \"runner-ws\",\n\t\t\t},\n\t\t}) as any as WebSocket;\n\t\tthis.#pegboardWebSocket = ws;\n\n\t\tws.addEventListener(\"open\", () => {\n\t\t\tlogger()?.info(\"Connected\");\n\n\t\t\t// Reset reconnect attempt counter on successful connection\n\t\t\tthis.#reconnectAttempt = 0;\n\n\t\t\t// Clear any pending reconnect timeout\n\t\t\tif (this.#reconnectTimeout) {\n\t\t\t\tclearTimeout(this.#reconnectTimeout);\n\t\t\t\tthis.#reconnectTimeout = undefined;\n\t\t\t}\n\n\t\t\t// Clear any pending runner lost timeout since we're reconnecting\n\t\t\tif (this.#runnerLostTimeout) {\n\t\t\t\tclearTimeout(this.#runnerLostTimeout);\n\t\t\t\tthis.#runnerLostTimeout = undefined;\n\t\t\t}\n\n\t\t\t// Send init message\n\t\t\tconst init: protocol.ToServerInit = {\n\t\t\t\tname: this.#config.runnerName,\n\t\t\t\tversion: this.#config.version,\n\t\t\t\ttotalSlots: this.#config.totalSlots,\n\t\t\t\tlastCommandIdx:\n\t\t\t\t\tthis.#lastCommandIdx >= 0\n\t\t\t\t\t\t? BigInt(this.#lastCommandIdx)\n\t\t\t\t\t\t: null,\n\t\t\t\tprepopulateActorNames: new Map(\n\t\t\t\t\tObject.entries(this.#config.prepopulateActorNames).map(\n\t\t\t\t\t\t([name, data]) => [\n\t\t\t\t\t\t\tname,\n\t\t\t\t\t\t\t{ metadata: JSON.stringify(data.metadata) },\n\t\t\t\t\t\t],\n\t\t\t\t\t),\n\t\t\t\t),\n\t\t\t\tmetadata: JSON.stringify(this.#config.metadata),\n\t\t\t};\n\n\t\t\tthis.#sendToServer({\n\t\t\t\ttag: \"ToServerInit\",\n\t\t\t\tval: init,\n\t\t\t});\n\n\t\t\t// Process unsent KV requests\n\t\t\tthis.#processUnsentKvRequests();\n\n\t\t\t// Start ping interval\n\t\t\tconst pingInterval = 1000;\n\t\t\tconst pingLoop = setInterval(() => {\n\t\t\t\tif (ws.readyState === WebSocket.OPEN) {\n\t\t\t\t\tthis.#sendToServer({\n\t\t\t\t\t\ttag: \"ToServerPing\",\n\t\t\t\t\t\tval: {\n\t\t\t\t\t\t\tts: BigInt(Date.now()),\n\t\t\t\t\t\t},\n\t\t\t\t\t});\n\t\t\t\t} else {\n\t\t\t\t\tclearInterval(pingLoop);\n\t\t\t\t\tlogger()?.info(\"WebSocket not open, stopping ping loop\");\n\t\t\t\t}\n\t\t\t}, pingInterval);\n\t\t\tthis.#pingLoop = pingLoop;\n\n\t\t\t// Start command acknowledgment interval (5 minutes)\n\t\t\tconst ackInterval = 5 * 60 * 1000; // 5 minutes in milliseconds\n\t\t\tconst ackLoop = setInterval(() => {\n\t\t\t\tif (ws.readyState === WebSocket.OPEN) {\n\t\t\t\t\tthis.#sendCommandAcknowledgment();\n\t\t\t\t} else {\n\t\t\t\t\tclearInterval(ackLoop);\n\t\t\t\t\tlogger()?.info(\"WebSocket not open, stopping ack loop\");\n\t\t\t\t}\n\t\t\t}, ackInterval);\n\t\t\tthis.#ackInterval = ackLoop;\n\t\t});\n\n\t\tws.addEventListener(\"message\", async (ev) => {\n\t\t\tlet buf;\n\t\t\tif (ev.data instanceof Blob) {\n\t\t\t\tbuf = new Uint8Array(await ev.data.arrayBuffer());\n\t\t\t} else if (Buffer.isBuffer(ev.data)) {\n\t\t\t\tbuf = new Uint8Array(ev.data);\n\t\t\t} else {\n\t\t\t\tthrow new Error(\"expected binary data, got \" + typeof ev.data);\n\t\t\t}\n\n\t\t\t// Parse message\n\t\t\tconst message = protocol.decodeToClient(buf);\n\n\t\t\t// Handle message\n\t\t\tif (message.tag === \"ToClientInit\") {\n\t\t\t\tconst init = message.val;\n\t\t\t\tconst hadRunnerId = !!this.runnerId;\n\t\t\t\tthis.runnerId = init.runnerId;\n\n\t\t\t\t// Store the runner lost threshold from metadata\n\t\t\t\tthis.#runnerLostThreshold = init.metadata?.runnerLostThreshold\n\t\t\t\t\t? Number(init.metadata.runnerLostThreshold)\n\t\t\t\t\t: undefined;\n\n\t\t\t\tlogger()?.info({\n\t\t\t\t\tmsg: \"received init\",\n\t\t\t\t\trunnerId: init.runnerId,\n\t\t\t\t\tlastEventIdx: init.lastEventIdx,\n\t\t\t\t\trunnerLostThreshold: this.#runnerLostThreshold,\n\t\t\t\t});\n\n\t\t\t\t// Resend events that haven't been acknowledged\n\t\t\t\tthis.#resendUnacknowledgedEvents(init.lastEventIdx);\n\n\t\t\t\tthis.#config.onConnected();\n\t\t\t} else if (message.tag === \"ToClientCommands\") {\n\t\t\t\tconst commands = message.val;\n\t\t\t\tthis.#handleCommands(commands);\n\t\t\t} else if (message.tag === \"ToClientAckEvents\") {\n\t\t\t\tthrow new Error(\"TODO\");\n\t\t\t} else if (message.tag === \"ToClientKvResponse\") {\n\t\t\t\tconst kvResponse = message.val;\n\t\t\t\tthis.#handleKvResponse(kvResponse);\n\t\t\t}\n\t\t});\n\n\t\tws.addEventListener(\"error\", (ev) => {\n\t\t\tlogger()?.error(\"WebSocket error:\", ev.error);\n\t\t});\n\n\t\tws.addEventListener(\"close\", (ev) => {\n\t\t\tlogger()?.info({\n\t\t\t\tmsg: \"connection closed\",\n\t\t\t\tcode: ev.code,\n\t\t\t\treason: ev.reason.toString(),\n\t\t\t});\n\n\t\t\tthis.#config.onDisconnected();\n\n\t\t\t// Clear ping loop on close\n\t\t\tif (this.#pingLoop) {\n\t\t\t\tclearInterval(this.#pingLoop);\n\t\t\t\tthis.#pingLoop = undefined;\n\t\t\t}\n\n\t\t\t// Clear ack interval on close\n\t\t\tif (this.#ackInterval) {\n\t\t\t\tclearInterval(this.#ackInterval);\n\t\t\t\tthis.#ackInterval = undefined;\n\t\t\t}\n\n\t\t\tif (!this.#shutdown) {\n\t\t\t\t// Start runner lost timeout if we have a threshold and are not shutting down\n\t\t\t\tif (\n\t\t\t\t\tthis.#runnerLostThreshold &&\n\t\t\t\t\tthis.#runnerLostThreshold > 0\n\t\t\t\t) {\n\t\t\t\t\tlogger()?.info({\n\t\t\t\t\t\tmsg: \"starting runner lost timeout\",\n\t\t\t\t\t\tseconds: this.#runnerLostThreshold / 1000,\n\t\t\t\t\t});\n\t\t\t\t\tthis.#runnerLostTimeout = setTimeout(() => {\n\t\t\t\t\t\tthis.#stopAllActors();\n\t\t\t\t\t}, this.#runnerLostThreshold);\n\t\t\t\t}\n\n\t\t\t\t// Attempt to reconnect if not stopped\n\t\t\t\tthis.#scheduleReconnect();\n\t\t\t}\n\t\t});\n\t}\n\n\t#handleCommands(commands: protocol.ToClientCommands) {\n\t\tlogger()?.info({\n\t\t\tmsg: \"received commands\",\n\t\t\tcommandCount: commands.length,\n\t\t});\n\n\t\tfor (const commandWrapper of commands) {\n\t\t\tlogger()?.info({ msg: \"received command\", commandWrapper });\n\t\t\tif (commandWrapper.inner.tag === \"CommandStartActor\") {\n\t\t\t\tthis.#handleCommandStartActor(commandWrapper);\n\t\t\t} else if (commandWrapper.inner.tag === \"CommandStopActor\") {\n\t\t\t\tthis.#handleCommandStopActor(commandWrapper);\n\t\t\t}\n\n\t\t\tthis.#lastCommandIdx = Number(commandWrapper.index);\n\t\t}\n\t}\n\n\t#handleCommandStartActor(commandWrapper: protocol.CommandWrapper) {\n\t\tconst startCommand = commandWrapper.inner\n\t\t\t.val as protocol.CommandStartActor;\n\n\t\tconst actorId = startCommand.actorId;\n\t\tconst generation = startCommand.generation;\n\t\tconst config = startCommand.config;\n\n\t\tconst actorConfig: ActorConfig = {\n\t\t\tname: config.name,\n\t\t\tkey: config.key,\n\t\t\tcreateTs: config.createTs,\n\t\t\tinput: config.input ? new Uint8Array(config.input) : null,\n\t\t};\n\n\t\tconst instance: ActorInstance = {\n\t\t\tactorId,\n\t\t\tgeneration,\n\t\t\tconfig: actorConfig,\n\t\t\trequests: new Set(),\n\t\t\twebSockets: new Set(),\n\t\t};\n\n\t\tthis.#actors.set(actorId, instance);\n\n\t\tthis.#sendActorStateUpdate(actorId, generation, \"running\");\n\n\t\t// TODO: Add timeout to onActorStart\n\t\t// Call onActorStart asynchronously and handle errors\n\t\tthis.#config\n\t\t\t.onActorStart(actorId, generation, actorConfig)\n\t\t\t.catch((err) => {\n\t\t\t\tlogger()?.error({\n\t\t\t\t\tmsg: \"error in onactorstart for actor\",\n\t\t\t\t\tactorId,\n\t\t\t\t\terr,\n\t\t\t\t});\n\n\t\t\t\t// TODO: Mark as crashed\n\t\t\t\t// Send stopped state update if start failed\n\t\t\t\tthis.stopActor(actorId, generation);\n\t\t\t});\n\t}\n\n\t#handleCommandStopActor(commandWrapper: protocol.CommandWrapper) {\n\t\tconst stopCommand = commandWrapper.inner\n\t\t\t.val as protocol.CommandStopActor;\n\n\t\tconst actorId = stopCommand.actorId;\n\t\tconst generation = stopCommand.generation;\n\n\t\tthis.stopActor(actorId, generation);\n\t}\n\n\t#sendActorIntent(\n\t\tactorId: string,\n\t\tgeneration: number,\n\t\tintentType: \"sleep\" | \"stop\",\n\t) {\n\t\tif (this.#shutdown) {\n\t\t\tlogger()?.warn(\"Runner is shut down, cannot send actor intent\");\n\t\t\treturn;\n\t\t}\n\t\tlet actorIntent: protocol.ActorIntent;\n\n\t\tif (intentType === \"sleep\") {\n\t\t\tactorIntent = { tag: \"ActorIntentSleep\", val: null };\n\t\t} else if (intentType === \"stop\") {\n\t\t\tactorIntent = {\n\t\t\t\ttag: \"ActorIntentStop\",\n\t\t\t\tval: null,\n\t\t\t};\n\t\t} else {\n\t\t\tunreachable(intentType);\n\t\t}\n\n\t\tconst intentEvent: protocol.EventActorIntent = {\n\t\t\tactorId,\n\t\t\tgeneration,\n\t\t\tintent: actorIntent,\n\t\t};\n\n\t\tconst eventIndex = this.#nextEventIdx++;\n\t\tconst eventWrapper: protocol.EventWrapper = {\n\t\t\tindex: eventIndex,\n\t\t\tinner: {\n\t\t\t\ttag: \"EventActorIntent\",\n\t\t\t\tval: intentEvent,\n\t\t\t},\n\t\t};\n\n\t\t// Store event in history for potential resending\n\t\tthis.#eventHistory.push({\n\t\t\tevent: eventWrapper,\n\t\t\ttimestamp: Date.now(),\n\t\t});\n\n\t\tlogger()?.info({\n\t\t\tmsg: \"sending event to server\",\n\t\t\tindex: eventWrapper.index,\n\t\t\ttag: eventWrapper.inner.tag,\n\t\t\tval: eventWrapper.inner.val,\n\t\t});\n\n\t\tthis.#sendToServer({\n\t\t\ttag: \"ToServerEvents\",\n\t\t\tval: [eventWrapper],\n\t\t});\n\t}\n\n\t#sendActorStateUpdate(\n\t\tactorId: string,\n\t\tgeneration: number,\n\t\tstateType: \"running\" | \"stopped\",\n\t) {\n\t\tif (this.#shutdown) {\n\t\t\tlogger()?.warn(\n\t\t\t\t\"Runner is shut down, cannot send actor state update\",\n\t\t\t);\n\t\t\treturn;\n\t\t}\n\t\tlet actorState: protocol.ActorState;\n\n\t\tif (stateType === \"running\") {\n\t\t\tactorState = { tag: \"ActorStateRunning\", val: null };\n\t\t} else if (stateType === \"stopped\") {\n\t\t\tactorState = {\n\t\t\t\ttag: \"ActorStateStopped\",\n\t\t\t\tval: {\n\t\t\t\t\tcode: protocol.StopCode.Ok,\n\t\t\t\t\tmessage: \"hello\",\n\t\t\t\t},\n\t\t\t};\n\t\t} else {\n\t\t\tunreachable(stateType);\n\t\t}\n\n\t\tconst stateUpdateEvent: protocol.EventActorStateUpdate = {\n\t\t\tactorId,\n\t\t\tgeneration,\n\t\t\tstate: actorState,\n\t\t};\n\n\t\tconst eventIndex = this.#nextEventIdx++;\n\t\tconst eventWrapper: protocol.EventWrapper = {\n\t\t\tindex: eventIndex,\n\t\t\tinner: {\n\t\t\t\ttag: \"EventActorStateUpdate\",\n\t\t\t\tval: stateUpdateEvent,\n\t\t\t},\n\t\t};\n\n\t\t// Store event in history for potential resending\n\t\tthis.#eventHistory.push({\n\t\t\tevent: eventWrapper,\n\t\t\ttimestamp: Date.now(),\n\t\t});\n\n\t\tlogger()?.info({\n\t\t\tmsg: \"sending event to server\",\n\t\t\tindex: eventWrapper.index,\n\t\t\ttag: eventWrapper.inner.tag,\n\t\t\tval: eventWrapper.inner.val,\n\t\t});\n\n\t\tthis.#sendToServer({\n\t\t\ttag: \"ToServerEvents\",\n\t\t\tval: [eventWrapper],\n\t\t});\n\t}\n\n\t#sendCommandAcknowledgment() {\n\t\tif (this.#shutdown) {\n\t\t\tlogger()?.warn(\n\t\t\t\t\"Runner is shut down, cannot send command acknowledgment\",\n\t\t\t);\n\t\t\treturn;\n\t\t}\n\n\t\tif (this.#lastCommandIdx < 0) {\n\t\t\t// No commands received yet, nothing to acknowledge\n\t\t\treturn;\n\t\t}\n\n\t\t//logger()?.log(\"Sending command acknowledgment\", this.#lastCommandIdx);\n\n\t\tthis.#sendToServer({\n\t\t\ttag: \"ToServerAckCommands\",\n\t\t\tval: {\n\t\t\t\tlastCommandIdx: BigInt(this.#lastCommandIdx),\n\t\t\t},\n\t\t});\n\t}\n\n\t#handleKvResponse(response: protocol.ToClientKvResponse) {\n\t\tconst requestId = response.requestId;\n\t\tconst request = this.#kvRequests.get(requestId);\n\n\t\tif (!request) {\n\t\t\tconst msg = \"received kv response for unknown request id\";\n\t\t\tif (logger()) {\n\t\t\t\tlogger()?.error({ msg, requestId });\n\t\t\t} else {\n\t\t\t\tlogger()?.error({ msg, requestId });\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\tthis.#kvRequests.delete(requestId);\n\n\t\tif (response.data.tag === \"KvErrorResponse\") {\n\t\t\trequest.reject(\n\t\t\t\tnew Error(response.data.val.message || \"Unknown KV error\"),\n\t\t\t);\n\t\t} else {\n\t\t\trequest.resolve(response.data.val);\n\t\t}\n\t}\n\n\t#parseGetResponseSimple(\n\t\tresponse: protocol.KvGetResponse,\n\t\trequestedKeys: Uint8Array[],\n\t): (Uint8Array | null)[] {\n\t\t// Parse the response keys and values\n\t\tconst responseKeys: Uint8Array[] = [];\n\t\tconst responseValues: Uint8Array[] = [];\n\n\t\tfor (const key of response.keys) {\n\t\t\tresponseKeys.push(new Uint8Array(key));\n\t\t}\n\n\t\tfor (const value of response.values) {\n\t\t\tresponseValues.push(new Uint8Array(value));\n\t\t}\n\n\t\t// Map response back to requested key order\n\t\tconst result: (Uint8Array | null)[] = [];\n\t\tfor (const requestedKey of requestedKeys) {\n\t\t\tlet found = false;\n\t\t\tfor (let i = 0; i < responseKeys.length; i++) {\n\t\t\t\tif (this.#keysEqual(requestedKey, responseKeys[i])) {\n\t\t\t\t\tresult.push(responseValues[i]);\n\t\t\t\t\tfound = true;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (!found) {\n\t\t\t\tresult.push(null);\n\t\t\t}\n\t\t}\n\n\t\treturn result;\n\t}\n\n\t#keysEqual(key1: Uint8Array, key2: Uint8Array): boolean {\n\t\tif (key1.length !== key2.length) return false;\n\t\tfor (let i = 0; i < key1.length; i++) {\n\t\t\tif (key1[i] !== key2[i]) return false;\n\t\t}\n\t\treturn true;\n\t}\n\n\t//#parseGetResponse(response: protocol.KvGetResponse) {\n\t//\tconst keys: string[] = [];\n\t//\tconst values: Uint8Array[] = [];\n\t//\tconst metadata: { version: Uint8Array; createTs: bigint }[] = [];\n\t//\n\t//\tfor (const key of response.keys) {\n\t//\t\tkeys.push(new TextDecoder().decode(key));\n\t//\t}\n\t//\n\t//\tfor (const value of response.values) {\n\t//\t\tvalues.push(new Uint8Array(value));\n\t//\t}\n\t//\n\t//\tfor (const meta of response.metadata) {\n\t//\t\tmetadata.push({\n\t//\t\t\tversion: new Uint8Array(meta.version),\n\t//\t\t\tcreateTs: meta.createTs,\n\t//\t\t});\n\t//\t}\n\t//\n\t//\treturn { keys, values, metadata };\n\t//}\n\n\t#parseListResponseSimple(\n\t\tresponse: protocol.KvListResponse,\n\t): [Uint8Array, Uint8Array][] {\n\t\tconst result: [Uint8Array, Uint8Array][] = [];\n\n\t\tfor (let i = 0; i < response.keys.length; i++) {\n\t\t\tconst key = response.keys[i];\n\t\t\tconst value = response.values[i];\n\n\t\t\tif (key && value) {\n\t\t\t\tconst keyBytes = new Uint8Array(key);\n\t\t\t\tconst valueBytes = new Uint8Array(value);\n\t\t\t\tresult.push([keyBytes, valueBytes]);\n\t\t\t}\n\t\t}\n\n\t\treturn result;\n\t}\n\n\t//#parseListResponse(response: protocol.KvListResponse) {\n\t//\tconst keys: string[] = [];\n\t//\tconst values: Uint8Array[] = [];\n\t//\tconst metadata: { version: Uint8Array; createTs: bigint }[] = [];\n\t//\n\t//\tfor (const key of response.keys) {\n\t//\t\tkeys.push(new TextDecoder().decode(key));\n\t//\t}\n\t//\n\t//\tfor (const value of response.values) {\n\t//\t\tvalues.push(new Uint8Array(value));\n\t//\t}\n\t//\n\t//\tfor (const meta of response.metadata) {\n\t//\t\tmetadata.push({\n\t//\t\t\tversion: new Uint8Array(meta.version),\n\t//\t\t\tcreateTs: meta.createTs,\n\t//\t\t});\n\t//\t}\n\t//\n\t//\treturn { keys, values, metadata };\n\t//}\n\n\t// MARK: KV Operations\n\tasync kvGet(\n\t\tactorId: string,\n\t\tkeys: Uint8Array[],\n\t): Promise<(Uint8Array | null)[]> {\n\t\tconst kvKeys: protocol.KvKey[] = keys.map(\n\t\t\t(key) =>\n\t\t\t\tkey.buffer.slice(\n\t\t\t\t\tkey.byteOffset,\n\t\t\t\t\tkey.byteOffset + key.byteLength,\n\t\t\t\t) as ArrayBuffer,\n\t\t);\n\n\t\tconst requestData: protocol.KvRequestData = {\n\t\t\ttag: \"KvGetRequest\",\n\t\t\tval: { keys: kvKeys },\n\t\t};\n\n\t\tconst response = await this.#sendKvRequest(actorId, requestData);\n\t\treturn this.#parseGetResponseSimple(response, keys);\n\t}\n\n\tasync kvListAll(\n\t\tactorId: string,\n\t\toptions?: KvListOptions,\n\t): Promise<[Uint8Array, Uint8Array][]> {\n\t\tconst requestData: protocol.KvRequestData = {\n\t\t\ttag: \"KvListRequest\",\n\t\t\tval: {\n\t\t\t\tquery: { tag: \"KvListAllQuery\", val: null },\n\t\t\t\treverse: options?.reverse || null,\n\t\t\t\tlimit:\n\t\t\t\t\toptions?.limit !== undefined ? BigInt(options.limit) : null,\n\t\t\t},\n\t\t};\n\n\t\tconst response = await this.#sendKvRequest(actorId, requestData);\n\t\treturn this.#parseListResponseSimple(response);\n\t}\n\n\tasync kvListRange(\n\t\tactorId: string,\n\t\tstart: Uint8Array,\n\t\tend: Uint8Array,\n\t\texclusive?: boolean,\n\t\toptions?: KvListOptions,\n\t): Promise<[Uint8Array, Uint8Array][]> {\n\t\tconst startKey: protocol.KvKey = start.buffer.slice(\n\t\t\tstart.byteOffset,\n\t\t\tstart.byteOffset + start.byteLength,\n\t\t) as ArrayBuffer;\n\t\tconst endKey: protocol.KvKey = end.buffer.slice(\n\t\t\tend.byteOffset,\n\t\t\tend.byteOffset + end.byteLength,\n\t\t) as ArrayBuffer;\n\n\t\tconst requestData: protocol.KvRequestData = {\n\t\t\ttag: \"KvListRequest\",\n\t\t\tval: {\n\t\t\t\tquery: {\n\t\t\t\t\ttag: \"KvListRangeQuery\",\n\t\t\t\t\tval: {\n\t\t\t\t\t\tstart: startKey,\n\t\t\t\t\t\tend: endKey,\n\t\t\t\t\t\texclusive: exclusive || false,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\treverse: options?.reverse || null,\n\t\t\t\tlimit:\n\t\t\t\t\toptions?.limit !== undefined ? BigInt(options.limit) : null,\n\t\t\t},\n\t\t};\n\n\t\tconst response = await this.#sendKvRequest(actorId, requestData);\n\t\treturn this.#parseListResponseSimple(response);\n\t}\n\n\tasync kvListPrefix(\n\t\tactorId: string,\n\t\tprefix: Uint8Array,\n\t\toptions?: KvListOptions,\n\t): Promise<[Uint8Array, Uint8Array][]> {\n\t\tconst prefixKey: protocol.KvKey = prefix.buffer.slice(\n\t\t\tprefix.byteOffset,\n\t\t\tprefix.byteOffset + prefix.byteLength,\n\t\t) as ArrayBuffer;\n\n\t\tconst requestData: protocol.KvRequestData = {\n\t\t\ttag: \"KvListRequest\",\n\t\t\tval: {\n\t\t\t\tquery: {\n\t\t\t\t\ttag: \"KvListPrefixQuery\",\n\t\t\t\t\tval: { key: prefixKey },\n\t\t\t\t},\n\t\t\t\treverse: options?.reverse || null,\n\t\t\t\tlimit:\n\t\t\t\t\toptions?.limit !== undefined ? BigInt(options.limit) : null,\n\t\t\t},\n\t\t};\n\n\t\tconst response = await this.#sendKvRequest(actorId, requestData);\n\t\treturn this.#parseListResponseSimple(response);\n\t}\n\n\tasync kvPut(\n\t\tactorId: string,\n\t\tentries: [Uint8Array, Uint8Array][],\n\t): Promise<void> {\n\t\tconst keys: protocol.KvKey[] = entries.map(\n\t\t\t([key, _value]) =>\n\t\t\t\tkey.buffer.slice(\n\t\t\t\t\tkey.byteOffset,\n\t\t\t\t\tkey.byteOffset + key.byteLength,\n\t\t\t\t) as ArrayBuffer,\n\t\t);\n\t\tconst values: protocol.KvValue[] = entries.map(\n\t\t\t([_key, value]) =>\n\t\t\t\tvalue.buffer.slice(\n\t\t\t\t\tvalue.byteOffset,\n\t\t\t\t\tvalue.byteOffset + value.byteLength,\n\t\t\t\t) as ArrayBuffer,\n\t\t);\n\n\t\tconst requestData: protocol.KvRequestData = {\n\t\t\ttag: \"KvPutRequest\",\n\t\t\tval: { keys, values },\n\t\t};\n\n\t\tawait this.#sendKvRequest(actorId, requestData);\n\t}\n\n\tasync kvDelete(actorId: string, keys: Uint8Array[]): Promise<void> {\n\t\tconst kvKeys: protocol.KvKey[] = keys.map(\n\t\t\t(key) =>\n\t\t\t\tkey.buffer.slice(\n\t\t\t\t\tkey.byteOffset,\n\t\t\t\t\tkey.byteOffset + key.byteLength,\n\t\t\t\t) as ArrayBuffer,\n\t\t);\n\n\t\tconst requestData: protocol.KvRequestData = {\n\t\t\ttag: \"KvDeleteRequest\",\n\t\t\tval: { keys: kvKeys },\n\t\t};\n\n\t\tawait this.#sendKvRequest(actorId, requestData);\n\t}\n\n\tasync kvDrop(actorId: string): Promise<void> {\n\t\tconst requestData: protocol.KvRequestData = {\n\t\t\ttag: \"KvDropRequest\",\n\t\t\tval: null,\n\t\t};\n\n\t\tawait this.#sendKvRequest(actorId, requestData);\n\t}\n\n\t// MARK: Alarm Operations\n\tsetAlarm(actorId: string, alarmTs: number | null, generation?: number) {\n\t\tconst actor = this.getActor(actorId, generation);\n\t\tif (!actor) return;\n\n\t\tif (this.#shutdown) {\n\t\t\tconsole.warn(\"Runner is shut down, cannot set alarm\");\n\t\t\treturn;\n\t\t}\n\n\t\tconst alarmEvent: protocol.EventActorSetAlarm = {\n\t\t\tactorId,\n\t\t\tgeneration: actor.generation,\n\t\t\talarmTs: alarmTs !== null ? BigInt(alarmTs) : null,\n\t\t};\n\n\t\tconst eventIndex = this.#nextEventIdx++;\n\t\tconst eventWrapper: protocol.EventWrapper = {\n\t\t\tindex: eventIndex,\n\t\t\tinner: {\n\t\t\t\ttag: \"EventActorSetAlarm\",\n\t\t\t\tval: alarmEvent,\n\t\t\t},\n\t\t};\n\n\t\t// Store event in history for potential resending\n\t\tthis.#eventHistory.push({\n\t\t\tevent: eventWrapper,\n\t\t\ttimestamp: Date.now(),\n\t\t});\n\n\t\tthis.#sendToServer({\n\t\t\ttag: \"ToServerEvents\",\n\t\t\tval: [eventWrapper],\n\t\t});\n\t}\n\n\tclearAlarm(actorId: string, generation?: number) {\n\t\tthis.setAlarm(actorId, null, generation);\n\t}\n\n\t#sendKvRequest(\n\t\tactorId: string,\n\t\trequestData: protocol.KvRequestData,\n\t): Promise<any> {\n\t\treturn new Promise((resolve, reject) => {\n\t\t\tif (this.#shutdown) {\n\t\t\t\treject(new Error(\"Runner is shut down\"));\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst requestId = this.#nextRequestId++;\n\t\t\tconst isConnected =\n\t\t\t\tthis.#pegboardWebSocket &&\n\t\t\t\tthis.#pegboardWebSocket.readyState === WebSocket.OPEN;\n\n\t\t\t// Store the request\n\t\t\tconst requestEntry = {\n\t\t\t\tactorId,\n\t\t\t\tdata: requestData,\n\t\t\t\tresolve,\n\t\t\t\treject,\n\t\t\t\tsent: false,\n\t\t\t\ttimestamp: Date.now(),\n\t\t\t};\n\n\t\t\tthis.#kvRequests.set(requestId, requestEntry);\n\n\t\t\tif (isConnected) {\n\t\t\t\t// Send immediately\n\t\t\t\tthis.#sendSingleKvRequest(requestId);\n\t\t\t}\n\t\t});\n\t}\n\n\t#sendSingleKvRequest(requestId: number) {\n\t\tconst request = this.#kvRequests.get(requestId);\n\t\tif (!request || request.sent) return;\n\n\t\ttry {\n\t\t\tconst kvRequest: protocol.ToServerKvRequest = {\n\t\t\t\tactorId: request.actorId,\n\t\t\t\trequestId,\n\t\t\t\tdata: request.data,\n\t\t\t};\n\n\t\t\tthis.#sendToServer({\n\t\t\t\ttag: \"ToServerKvRequest\",\n\t\t\t\tval: kvRequest,\n\t\t\t});\n\n\t\t\t// Mark as sent and update timestamp\n\t\t\trequest.sent = true;\n\t\t\trequest.timestamp = Date.now();\n\t\t} catch (error) {\n\t\t\tthis.#kvRequests.delete(requestId);\n\t\t\trequest.reject(error);\n\t\t}\n\t}\n\n\t#processUnsentKvRequests() {\n\t\tif (\n\t\t\t!this.#pegboardWebSocket ||\n\t\t\tthis.#pegboardWebSocket.readyState !== WebSocket.OPEN\n\t\t) {\n\t\t\treturn;\n\t\t}\n\n\t\tlet processedCount = 0;\n\t\tfor (const [requestId, request] of this.#kvRequests.entries()) {\n\t\t\tif (!request.sent) {\n\t\t\t\tthis.#sendSingleKvRequest(requestId);\n\t\t\t\tprocessedCount++;\n\t\t\t}\n\t\t}\n\n\t\tif (processedCount > 0) {\n\t\t\t//logger()?.log(`Processed ${processedCount} queued KV requests`);\n\t\t}\n\t}\n\n\t#sendToServer(message: protocol.ToServer) {\n\t\tif (this.#shutdown) {\n\t\t\tlogger()?.warn(\n\t\t\t\t\"Runner is shut down, cannot send message to server\",\n\t\t\t);\n\t\t\treturn;\n\t\t}\n\n\t\tconst encoded = protocol.encodeToServer(message);\n\t\tif (\n\t\t\tthis.#pegboardWebSocket &&\n\t\t\tthis.#pegboardWebSocket.readyState === WebSocket.OPEN\n\t\t) {\n\t\t\tthis.#pegboardWebSocket.send(encoded);\n\t\t} else {\n\t\t\tlogger()?.error(\n\t\t\t\t\"WebSocket not available or not open for sending data\",\n\t\t\t);\n\t\t}\n\t}\n\n\t#scheduleReconnect() {\n\t\tif (this.#shutdown) {\n\t\t\t//logger()?.log(\"Runner is shut down, not attempting reconnect\");\n\t\t\treturn;\n\t\t}\n\n\t\tconst delay = calculateBackoff(this.#reconnectAttempt, {\n\t\t\tinitialDelay: 1000,\n\t\t\tmaxDelay: 30000,\n\t\t\tmultiplier: 2,\n\t\t\tjitter: true,\n\t\t});\n\n\t\t//logger()?.log(\n\t\t//\t`Scheduling reconnect attempt ${this.#reconnectAttempt + 1} in ${delay}ms`,\n\t\t//);\n\n\t\tthis.#reconnectTimeout = setTimeout(async () => {\n\t\t\tif (!this.#shutdown) {\n\t\t\t\tthis.#reconnectAttempt++;\n\t\t\t\t//logger()?.log(\n\t\t\t\t//\t`Attempting to reconnect (attempt ${this.#reconnectAttempt})...`,\n\t\t\t\t//);\n\t\t\t\tawait this.#openPegboardWebSocket();\n\t\t\t}\n\t\t}, delay);\n\t}\n\n\t#resendUnacknowledgedEvents(lastEventIdx: bigint) {\n\t\tconst eventsToResend = this.#eventHistory.filter(\n\t\t\t(item) => item.event.index > lastEventIdx,\n\t\t);\n\n\t\tif (eventsToResend.length === 0) return;\n\n\t\t//logger()?.log(\n\t\t//\t`Resending ${eventsToResend.length} unacknowledged events from index ${Number(lastEventIdx) + 1}`,\n\t\t//);\n\n\t\t// Resend events in batches\n\t\tconst events = eventsToResend.map((item) => item.event);\n\t\tthis.#sendToServer({\n\t\t\ttag: \"ToServerEvents\",\n\t\t\tval: events,\n\t\t});\n\t}\n\n\t// TODO(RVT-4986): Prune when server acks events instead of based on old events\n\t#pruneOldEvents() {\n\t\tconst fiveMinutesAgo = Date.now() - 5 * 60 * 1000;\n\t\tconst originalLength = this.#eventHistory.length;\n\n\t\t// Remove events older than 5 minutes\n\t\tthis.#eventHistory = this.#eventHistory.filter(\n\t\t\t(item) => item.timestamp > fiveMinutesAgo,\n\t\t);\n\n\t\tconst prunedCount = originalLength - this.#eventHistory.length;\n\t\tif (prunedCount > 0) {\n\t\t\t//logger()?.log(`Pruned ${prunedCount} old events from history`);\n\t\t}\n\t}\n\n\t#cleanupOldKvRequests() {\n\t\tconst thirtySecondsAgo = Date.now() - KV_EXPIRE;\n\t\tconst toDelete: number[] = [];\n\n\t\tfor (const [requestId, request] of this.#kvRequests.entries()) {\n\t\t\tif (request.timestamp < thirtySecondsAgo) {\n\t\t\t\trequest.reject(\n\t\t\t\t\tnew Error(\n\t\t\t\t\t\t\"KV request timed out waiting for WebSocket connection\",\n\t\t\t\t\t),\n\t\t\t\t);\n\t\t\t\ttoDelete.push(requestId);\n\t\t\t}\n\t\t}\n\n\t\tfor (const requestId of toDelete) {\n\t\t\tthis.#kvRequests.delete(requestId);\n\t\t}\n\n\t\tif (toDelete.length > 0) {\n\t\t\t//logger()?.log(`Cleaned up ${toDelete.length} expired KV requests`);\n\t\t}\n\t}\n}\n","import { Logger } from \"pino\";\n\nlet LOGGER: Logger | undefined = undefined;\n\nexport function setLogger(logger: Logger) {\n\tLOGGER = logger;\n}\n\nexport function logger(): Logger | undefined {\n\treturn LOGGER;\n}\n","import { logger } from \"./log\";\n\n// Global singleton promise that will be reused for subsequent calls\nlet webSocketPromise: Promise<typeof WebSocket> | null = null;\n\nexport async function importWebSocket(): Promise<typeof WebSocket> {\n\t// Return existing promise if we already started loading\n\tif (webSocketPromise !== null) {\n\t\treturn webSocketPromise;\n\t}\n\n\t// Create and store the promise\n\twebSocketPromise = (async () => {\n\t\tlet _WebSocket: typeof WebSocket;\n\n\t\tif (typeof WebSocket !== \"undefined\") {\n\t\t\t// Native\n\t\t\t_WebSocket = WebSocket as unknown as typeof WebSocket;\n\t\t\tlogger()?.debug({ msg: \"using native websocket\" });\n\t\t} else {\n\t\t\t// Node.js package\n\t\t\ttry {\n\t\t\t\tconst ws = await import(\"ws\");\n\t\t\t\t_WebSocket = ws.default as unknown as typeof WebSocket;\n\t\t\t\tlogger()?.debug({ msg: \"using websocket from npm\" });\n\t\t\t} catch {\n\t\t\t\t// WS not available\n\t\t\t\t_WebSocket = class MockWebSocket {\n\t\t\t\t\tconstructor() {\n\t\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t\t'WebSocket support requires installing the \"ws\" peer dependency.',\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t} as unknown as typeof WebSocket;\n\t\t\t\tlogger()?.debug({ msg: \"using mock websocket\" });\n\t\t\t}\n\t\t}\n\n\t\treturn _WebSocket;\n\t})();\n\n\treturn webSocketPromise;\n}\n","export function unreachable(x: never): never {\n\tthrow `Unreachable: ${x}`;\n}\n\nexport interface BackoffOptions {\n\tinitialDelay?: number;\n\tmaxDelay?: number;\n\tmultiplier?: number;\n\tjitter?: boolean;\n}\n\nexport function calculateBackoff(\n\tattempt: number,\n\toptions: BackoffOptions = {},\n): number {\n\tconst {\n\t\tinitialDelay = 1000,\n\t\tmaxDelay = 30000,\n\t\tmultiplier = 2,\n\t\tjitter = true,\n\t} = options;\n\n\tlet delay = Math.min(initialDelay * Math.pow(multiplier, attempt), maxDelay);\n\n\tif (jitter) {\n\t\t// Add random jitter between 0% and 25% of the delay\n\t\tdelay = delay * (1 + Math.random() * 0.25);\n\t}\n\n\treturn Math.floor(delay);\n}\n","import WebSocket from \"ws\";\nimport * as tunnel from \"@rivetkit/engine-tunnel-protocol\";\nimport { WebSocketTunnelAdapter } from \"./websocket-tunnel-adapter\";\nimport { calculateBackoff } from \"./utils\";\nimport type { Runner, ActorInstance } from \"./mod\";\nimport { v4 as uuidv4 } from \"uuid\";\nimport { logger } from \"./log\";\n\nconst GC_INTERVAL = 60000; // 60 seconds\nconst MESSAGE_ACK_TIMEOUT = 5000; // 5 seconds\n\ninterface PendingRequest {\n\tresolve: (response: Response) => void;\n\treject: (error: Error) => void;\n\tstreamController?: ReadableStreamDefaultController<Uint8Array>;\n\tactorId?: string;\n}\n\ninterface TunnelCallbacks {\n\tonConnected(): void;\n\tonDisconnected(): void;\n}\n\ninterface PendingMessage {\n\tsentAt: number;\n\trequestIdStr: string;\n}\n\nexport class Tunnel {\n\t#pegboardTunnelUrl: string;\n\n\t#runner: Runner;\n\n\t#tunnelWs?: WebSocket;\n\t#shutdown = false;\n\t#reconnectTimeout?: NodeJS.Timeout;\n\t#reconnectAttempt = 0;\n\n\t#actorPendingRequests: Map<string, PendingRequest> = new Map();\n\t#actorWebSockets: Map<string, WebSocketTunnelAdapter> = new Map();\n\n\t#pendingMessages: Map<string, PendingMessage> = new Map();\n\t#gcInterval?: NodeJS.Timeout;\n\n\t#callbacks: TunnelCallbacks;\n\n\tconstructor(\n\t\trunner: Runner,\n\t\tpegboardTunnelUrl: string,\n\t\tcallbacks: TunnelCallbacks,\n\t) {\n\t\tthis.#pegboardTunnelUrl = pegboardTunnelUrl;\n\t\tthis.#runner = runner;\n\t\tthis.#callbacks = callbacks;\n\t}\n\n\tstart(): void {\n\t\tif (this.#tunnelWs?.readyState === WebSocket.OPEN) {\n\t\t\treturn;\n\t\t}\n\n\t\tthis.#connect();\n\t\tthis.#startGarbageCollector();\n\t}\n\n\tshutdown() {\n\t\tthis.#shutdown = true;\n\n\t\tif (this.#reconnectTimeout) {\n\t\t\tclearTimeout(this.#reconnectTimeout);\n\t\t\tthis.#reconnectTimeout = undefined;\n\t\t}\n\n\t\tif (this.#gcInterval) {\n\t\t\tclearInterval(this.#gcInterval);\n\t\t\tthis.#gcInterval = undefined;\n\t\t}\n\n\t\tif (this.#tunnelWs) {\n\t\t\tthis.#tunnelWs.close();\n\t\t\tthis.#tunnelWs = undefined;\n\t\t}\n\n\t\t// TODO: Should we use unregisterActor instead\n\n\t\t// Reject all pending requests\n\t\tfor (const [_, request] of this.#actorPendingRequests) {\n\t\t\trequest.reject(new Error(\"Tunnel shutting down\"));\n\t\t}\n\t\tthis.#actorPendingRequests.clear();\n\n\t\t// Close all WebSockets\n\t\tfor (const [_, ws] of this.#actorWebSockets) {\n\t\t\tws.close();\n\t\t}\n\t\tthis.#actorWebSockets.clear();\n\t}\n\n\t#sendMessage(requestId: tunnel.RequestId, messageKind: tunnel.MessageKind) {\n\t\tif (!this.#tunnelWs || this.#tunnelWs.readyState !== WebSocket.OPEN) {\n\t\t\tconsole.warn(\"Cannot send tunnel message, WebSocket not connected\");\n\t\t\treturn;\n\t\t}\n\n\t\t// Build message\n\t\tconst messageId = generateUuidBuffer();\n\n\t\tconst requestIdStr = bufferToString(requestId);\n\t\tthis.#pendingMessages.set(bufferToString(messageId), {\n\t\t\tsentAt: Date.now(),\n\t\t\trequestIdStr,\n\t\t});\n\n\t\t// Send message\n\t\tconst message: tunnel.RunnerMessage = {\n\t\t\trequestId,\n\t\t\tmessageId,\n\t\t\tmessageKind,\n\t\t};\n\n\t\tconst encoded = tunnel.encodeRunnerMessage(message);\n\t\tthis.#tunnelWs.send(encoded);\n\t}\n\n\t#sendAck(requestId: tunnel.RequestId, messageId: tunnel.MessageId) {\n\t\tif (!this.#tunnelWs || this.#tunnelWs.readyState !== WebSocket.OPEN) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst message: tunnel.RunnerMessage = {\n\t\t\trequestId,\n\t\t\tmessageId,\n\t\t\tmessageKind: { tag: \"Ack\", val: null },\n\t\t};\n\n\t\tconst encoded = tunnel.encodeRunnerMessage(message);\n\t\tthis.#tunnelWs.send(encoded);\n\t}\n\n\t#startGarbageCollector() {\n\t\tif (this.#gcInterval) {\n\t\t\tclearInterval(this.#gcInterval);\n\t\t}\n\n\t\tthis.#gcInterval = setInterval(() => {\n\t\t\tthis.#gc();\n\t\t}, GC_INTERVAL);\n\t}\n\n\t#gc() {\n\t\tconst now = Date.now();\n\t\tconst messagesToDelete: string[] = [];\n\n\t\tfor (const [messageId, pendingMessage] of this.#pendingMessages) {\n\t\t\t// Check if message is older than timeout\n\t\t\tif (now - pendingMessage.sentAt > MESSAGE_ACK_TIMEOUT) {\n\t\t\t\tmessagesToDelete.push(messageId);\n\n\t\t\t\tconst requestIdStr = pendingMessage.requestIdStr;\n\n\t\t\t\t// Check if this is an HTTP request\n\t\t\t\tconst pendingRequest =\n\t\t\t\t\tthis.#actorPendingRequests.get(requestIdStr);\n\t\t\t\tif (pendingRequest) {\n\t\t\t\t\t// Reject the pending HTTP request\n\t\t\t\t\tpendingRequest.reject(\n\t\t\t\t\t\tnew Error(\"Message acknowledgment timeout\"),\n\t\t\t\t\t);\n\n\t\t\t\t\t// Close stream controller if it exists\n\t\t\t\t\tif (pendingRequest.streamController) {\n\t\t\t\t\t\tpendingRequest.streamController.error(\n\t\t\t\t\t\t\tnew Error(\"Message acknowledgment timeout\"),\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\n\t\t\t\t\t// Clean up from actorPendingRequests map\n\t\t\t\t\tthis.#actorPendingRequests.delete(requestIdStr);\n\t\t\t\t}\n\n\t\t\t\t// Check if this is a WebSocket\n\t\t\t\tconst webSocket = this.#actorWebSockets.get(requestIdStr);\n\t\t\t\tif (webSocket) {\n\t\t\t\t\t// Close the WebSocket connection\n\t\t\t\t\twebSocket.close(1000, \"Message acknowledgment timeout\");\n\n\t\t\t\t\t// Clean up from actorWebSockets map\n\t\t\t\t\tthis.#actorWebSockets.delete(requestIdStr);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Remove timed out messages\n\t\tfor (const messageId of messagesToDelete) {\n\t\t\tthis.#pendingMessages.delete(messageId);\n\t\t\tconsole.warn(`Purged unacked message: ${messageId}`);\n\t\t}\n\t}\n\n\tunregisterActor(actor: ActorInstance) {\n\t\tconst actorId = actor.actorId;\n\n\t\t// Terminate all requests for this actor\n\t\tfor (const requestId of actor.requests) {\n\t\t\tconst pending = this.#actorPendingRequests.get(requestId);\n\t\t\tif (pending) {\n\t\t\t\tpending.reject(new Error(`Actor ${actorId} stopped`));\n\t\t\t\tthis.#actorPendingRequests.delete(requestId);\n\t\t\t}\n\t\t}\n\t\tactor.requests.clear();\n\n\t\t// Close all WebSockets for this actor\n\t\tfor (const webSocketId of actor.webSockets) {\n\t\t\tconst ws = this.#actorWebSockets.get(webSocketId);\n\t\t\tif (ws) {\n\t\t\t\tws.close(1000, \"Actor stopped\");\n\t\t\t\tthis.#actorWebSockets.delete(webSocketId);\n\t\t\t}\n\t\t}\n\t\tactor.webSockets.clear();\n\t}\n\n\tasync #fetch(actorId: string, request: Request): Promise<Response> {\n\t\t// Validate actor exists\n\t\tif (!this.#runner.hasActor(actorId)) {\n\t\t\tlogger()?.warn({\n\t\t\t\tmsg: \"ignoring request for unknown actor\",\n\t\t\t\tactorId,\n\t\t\t});\n\t\t\treturn new Response(\"Actor not found\", { status: 404 });\n\t\t}\n\n\t\tconst fetchHandler = this.#runner.config.fetch(actorId, request);\n\n\t\tif (!fetchHandler) {\n\t\t\treturn new Response(\"Not Implemented\", { status: 501 });\n\t\t}\n\n\t\treturn fetchHandler;\n\t}\n\n\t#connect() {\n\t\tif (this.#shutdown) return;\n\n\t\ttry {\n\t\t\tthis.#tunnelWs = new WebSocket(this.#pegboardTunnelUrl, {\n\t\t\t\theaders: {\n\t\t\t\t\t\"x-rivet-target\": \"tunnel\",\n\t\t\t\t},\n\t\t\t});\n\n\t\t\tthis.#tunnelWs.binaryType = \"arraybuffer\";\n\n\t\t\tthis.#tunnelWs.addEventListener(\"open\", () => {\n\t\t\t\tthis.#reconnectAttempt = 0;\n\n\t\t\t\tif (this.#reconnectTimeout) {\n\t\t\t\t\tclearTimeout(this.#reconnectTimeout);\n\t\t\t\t\tthis.#reconnectTimeout = undefined;\n\t\t\t\t}\n\n\t\t\t\tthis.#callbacks.onConnected();\n\t\t\t});\n\n\t\t\tthis.#tunnelWs.addEventListener(\"message\", async (event) => {\n\t\t\t\ttry {\n\t\t\t\t\tawait this.#handleMessage(event.data as ArrayBuffer);\n\t\t\t\t} catch (error) {\n\t\t\t\t\tlogger()?.error({\n\t\t\t\t\t\tmsg: \"error handling tunnel message\",\n\t\t\t\t\t\terror,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t});\n\n\t\t\tthis.#tunnelWs.addEventListener(\"error\", (event) => {\n\t\t\t\tlogger()?.error({ msg: \"tunnel websocket error\", event });\n\t\t\t});\n\n\t\t\tthis.#tunnelWs.addEventListener(\"close\", () => {\n\t\t\t\tthis.#callbacks.onDisconnected();\n\n\t\t\t\tif (!this.#shutdown) {\n\t\t\t\t\tthis.#scheduleReconnect();\n\t\t\t\t}\n\t\t\t});\n\t\t} catch (error) {\n\t\t\tlogger()?.error({ msg: \"failed to connect tunnel\", error });\n\t\t\tif (!this.#shutdown) {\n\t\t\t\tthis.#scheduleReconnect();\n\t\t\t}\n\t\t}\n\t}\n\n\t#scheduleReconnect() {\n\t\tif (this.#shutdown) return;\n\n\t\tconst delay = calculateBackoff(this.#reconnectAttempt, {\n\t\t\tinitialDelay: 1000,\n\t\t\tmaxDelay: 30000,\n\t\t\tmultiplier: 2,\n\t\t\tjitter: true,\n\t\t});\n\n\t\tthis.#reconnectAttempt++;\n\n\t\tthis.#reconnectTimeout = setTimeout(() => {\n\t\t\tthis.#connect();\n\t\t}, delay);\n\t}\n\n\tasync #handleMessage(data: ArrayBuffer) {\n\t\tconst message = tunnel.decodeRunnerMessage(new Uint8Array(data));\n\n\t\tif (message.messageKind.tag === \"Ack\") {\n\t\t\t// Mark pending message as acknowledged and remove it\n\t\t\tconst msgIdStr = bufferToString(message.messageId);\n\t\t\tconst pending = this.#pendingMessages.get(msgIdStr);\n\t\t\tif (pending) {\n\t\t\t\tthis.#pendingMessages.delete(msgIdStr);\n\t\t\t}\n\t\t} else {\n\t\t\tthis.#sendAck(message.requestId, message.messageId);\n\t\t\tswitch (message.messageKind.tag) {\n\t\t\t\tcase \"ToServerRequestStart\":\n\t\t\t\t\tawait this.#handleRequestStart(\n\t\t\t\t\t\tmessage.requestId,\n\t\t\t\t\t\tmessage.messageKind.val,\n\t\t\t\t\t);\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"ToServerRequestChunk\":\n\t\t\t\t\tawait this.#handleRequestChunk(\n\t\t\t\t\t\tmessage.requestId,\n\t\t\t\t\t\tmessage.messageKind.val,\n\t\t\t\t\t);\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"ToServerRequestAbort\":\n\t\t\t\t\tawait this.#handleRequestAbort(message.requestId);\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"ToServerWebSocketOpen\":\n\t\t\t\t\tawait this.#handleWebSocketOpen(\n\t\t\t\t\t\tmessage.requestId,\n\t\t\t\t\t\tmessage.messageKind.val,\n\t\t\t\t\t);\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"ToServerWebSocketMessage\":\n\t\t\t\t\tawait this.#handleWebSocketMessage(\n\t\t\t\t\t\tmessage.requestId,\n\t\t\t\t\t\tmessage.messageKind.val,\n\t\t\t\t\t);\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"ToServerWebSocketClose\":\n\t\t\t\t\tawait this.#handleWebSocketClose(\n\t\t\t\t\t\tmessage.requestId,\n\t\t\t\t\t\tmessage.messageKind.val,\n\t\t\t\t\t);\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"ToClientResponseStart\":\n\t\t\t\t\tthis.#handleResponseStart(\n\t\t\t\t\t\tmessage.requestId,\n\t\t\t\t\t\tmessage.messageKind.val,\n\t\t\t\t\t);\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"ToClientResponseChunk\":\n\t\t\t\t\tthis.#handleResponseChunk(\n\t\t\t\t\t\tmessage.requestId,\n\t\t\t\t\t\tmessage.messageKind.val,\n\t\t\t\t\t);\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"ToClientResponseAbort\":\n\t\t\t\t\tthis.#handleResponseAbort(message.requestId);\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"ToClientWebSocketOpen\":\n\t\t\t\t\tthis.#handleWebSocketOpenResponse(\n\t\t\t\t\t\tmessage.requestId,\n\t\t\t\t\t\tmessage.messageKind.val,\n\t\t\t\t\t);\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"ToClientWebSocketMessage\":\n\t\t\t\t\tthis.#handleWebSocketMessageResponse(\n\t\t\t\t\t\tmessage.requestId,\n\t\t\t\t\t\tmessage.messageKind.val,\n\t\t\t\t\t);\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"ToClientWebSocketClose\":\n\t\t\t\t\tthis.#handleWebSocketCloseResponse(\n\t\t\t\t\t\tmessage.requestId,\n\t\t\t\t\t\tmessage.messageKind.val,\n\t\t\t\t\t);\n\t\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t}\n\n\tasync #handleRequestStart(\n\t\trequestId: ArrayBuffer,\n\t\treq: tunnel.ToServerRequestStart,\n\t) {\n\t\t// Track this request for the actor\n\t\tconst requestIdStr = bufferToString(requestId);\n\t\tconst actor = this.#runner.getActor(req.actorId);\n\t\tif (actor) {\n\t\t\tactor.requests.add(requestIdStr);\n\t\t}\n\n\t\ttry {\n\t\t\t// Convert headers map to Headers object\n\t\t\tconst headers = new Headers();\n\t\t\tfor (const [key, value] of req.headers) {\n\t\t\t\theaders.append(key, value);\n\t\t\t}\n\n\t\t\t// Create Request object\n\t\t\tconst request = new Request(`http://localhost${req.path}`, {\n\t\t\t\tmethod: req.method,\n\t\t\t\theaders,\n\t\t\t\tbody: req.body ? new Uint8Array(req.body) : undefined,\n\t\t\t});\n\n\t\t\t// Handle streaming request\n\t\t\tif (req.stream) {\n\t\t\t\t// Create a stream for the request body\n\t\t\t\tconst stream = new ReadableStream<Uint8Array>({\n\t\t\t\t\tstart: (controller) => {\n\t\t\t\t\t\t// Store controller for chunks\n\t\t\t\t\t\tconst existing =\n\t\t\t\t\t\t\tthis.#actorPendingRequests.get(requestIdStr);\n\t\t\t\t\t\tif (existing) {\n\t\t\t\t\t\t\texisting.streamController = controller;\n\t\t\t\t\t\t\texisting.actorId = req.actorId;\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tthis.#actorPendingRequests.set(requestIdStr, {\n\t\t\t\t\t\t\t\tresolve: () => {},\n\t\t\t\t\t\t\t\treject: () => {},\n\t\t\t\t\t\t\t\tstreamController: controller,\n\t\t\t\t\t\t\t\tactorId: req.actorId,\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\t\t\t\t});\n\n\t\t\t\t// Create request with streaming body\n\t\t\t\tconst streamingRequest = new Request(request, {\n\t\t\t\t\tbody: stream,\n\t\t\t\t\tduplex: \"half\",\n\t\t\t\t} as any);\n\n\t\t\t\t// Call fetch handler with validation\n\t\t\t\tconst response = await this.#fetch(\n\t\t\t\t\treq.actorId,\n\t\t\t\t\tstreamingRequest,\n\t\t\t\t);\n\t\t\t\tawait this.#sendResponse(requestId, response);\n\t\t\t} else {\n\t\t\t\t// Non-streaming request\n\t\t\t\tconst response = await this.#fetch(req.actorId, request);\n\t\t\t\tawait this.#sendResponse(requestId, response);\n\t\t\t}\n\t\t} catch (error) {\n\t\t\tlogger()?.error({ msg: \"error handling request\", error });\n\t\t\tthis.#sendResponseError(requestId, 500, \"Internal Server Error\");\n\t\t} finally {\n\t\t\t// Clean up request tracking\n\t\t\tconst actor = this.#runner.getActor(req.actorId);\n\t\t\tif (actor) {\n\t\t\t\tactor.requests.delete(requestIdStr);\n\t\t\t}\n\t\t}\n\t}\n\n\tasync #handleRequestChunk(\n\t\trequestId: ArrayBuffer,\n\t\tchunk: tunnel.ToServerRequestChunk,\n\t) {\n\t\tconst requestIdStr = bufferToString(requestId);\n\t\tconst pending = this.#actorPendingRequests.get(requestIdStr);\n\t\tif (pending?.streamController) {\n\t\t\tpending.streamController.enqueue(new Uint8Array(chunk.body));\n\t\t\tif (chunk.finish) {\n\t\t\t\tpending.streamController.close();\n\t\t\t\tthis.#actorPendingRequests.delete(requestIdStr);\n\t\t\t}\n\t\t}\n\t}\n\n\tasync #handleRequestAbort(requestId: ArrayBuffer) {\n\t\tconst requestIdStr = bufferToString(requestId);\n\t\tconst pending = this.#actorPendingRequests.get(requestIdStr);\n\t\tif (pending?.streamController) {\n\t\t\tpending.streamController.error(new Error(\"Request aborted\"));\n\t\t}\n\t\tthis.#actorPendingRequests.delete(requestIdStr);\n\t}\n\n\tasync #sendResponse(requestId: ArrayBuffer, response: Response) {\n\t\t// Always treat responses as non-streaming for now\n\t\t// In the future, we could detect streaming responses based on:\n\t\t// - Transfer-Encoding: chunked\n\t\t// - Content-Type: text/event-stream\n\t\t// - Explicit stream flag from the handler\n\n\t\t// Read the body first to get the actual content\n\t\tconst body = response.body ? await response.arrayBuffer() : null;\n\n\t\t// Convert headers to map and add Content-Length if not present\n\t\tconst headers = new Map<string, string>();\n\t\tresponse.headers.forEach((value, key) => {\n\t\t\theaders.set(key, value);\n\t\t});\n\n\t\t// Add Content-Length header if we have a body and it's not already set\n\t\tif (body && !headers.has(\"content-length\")) {\n\t\t\theaders.set(\"content-length\", String(body.byteLength));\n\t\t}\n\n\t\t// Send as non-streaming response\n\t\tthis.#sendMessage(requestId, {\n\t\t\ttag: \"ToClientResponseStart\",\n\t\t\tval: {\n\t\t\t\tstatus: response.status as tunnel.u16,\n\t\t\t\theaders,\n\t\t\t\tbody: body || null,\n\t\t\t\tstream: false,\n\t\t\t},\n\t\t});\n\t}\n\n\t#sendResponseError(\n\t\trequestId: ArrayBuffer,\n\t\tstatus: number,\n\t\tmessage: string,\n\t) {\n\t\tconst headers = new Map<string, string>();\n\t\theaders.set(\"content-type\", \"text/plain\");\n\n\t\tthis.#sendMessage(requestId, {\n\t\t\ttag: \"ToClientResponseStart\",\n\t\t\tval: {\n\t\t\t\tstatus: status as tunnel.u16,\n\t\t\t\theaders,\n\t\t\t\tbody: new TextEncoder().encode(message).buffer as ArrayBuffer,\n\t\t\t\tstream: false,\n\t\t\t},\n\t\t});\n\t}\n\n\tasync #handleWebSocketOpen(\n\t\trequestId: ArrayBuffer,\n\t\topen: tunnel.ToServerWebSocketOpen,\n\t) {\n\t\tconst webSocketId = bufferToString(requestId);\n\t\t// Validate actor exists\n\t\tconst actor = this.#runner.getActor(open.actorId);\n\t\tif (!actor) {\n\t\t\tlogger()?.warn({\n\t\t\t\tmsg: \"ignoring websocket for unknown actor\",\n\t\t\t\tactorId: open.actorId,\n\t\t\t});\n\t\t\t// Send close immediately\n\t\t\tthis.#sendMessage(requestId, {\n\t\t\t\ttag: \"ToClientWebSocketClose\",\n\t\t\t\tval: {\n\t\t\t\t\tcode: 1011,\n\t\t\t\t\treason: \"Actor not found\",\n\t\t\t\t},\n\t\t\t});\n\t\t\treturn;\n\t\t}\n\n\t\tconst websocketHandler = this.#runner.config.websocket;\n\n\t\tif (!websocketHandler) {\n\t\t\tconsole.error(\"No websocket handler configured for tunnel\");\n\t\t\tlogger()?.error({\n\t\t\t\tmsg: \"no websocket handler configured for tunnel\",\n\t\t\t});\n\t\t\t// Send close immediately\n\t\t\tthis.#sendMessage(requestId, {\n\t\t\t\ttag: \"ToClientWebSocketClose\",\n\t\t\t\tval: {\n\t\t\t\t\tcode: 1011,\n\t\t\t\t\treason: \"Not Implemented\",\n\t\t\t\t},\n\t\t\t});\n\t\t\treturn;\n\t\t}\n\n\t\t// Track this WebSocket for the actor\n\t\tif (actor) {\n\t\t\tactor.webSockets.add(webSocketId);\n\t\t}\n\n\t\ttry {\n\t\t\t// Create WebSocket adapter\n\t\t\tconst adapter = new WebSocketTunnelAdapter(\n\t\t\t\twebSocketId,\n\t\t\t\t(data: ArrayBuffer | string, isBinary: boolean) => {\n\t\t\t\t\t// Send message through tunnel\n\t\t\t\t\tconst dataBuffer =\n\t\t\t\t\t\ttypeof data === \"string\"\n\t\t\t\t\t\t\t? (new TextEncoder().encode(data)\n\t\t\t\t\t\t\t\t\t.buffer as ArrayBuffer)\n\t\t\t\t\t\t\t: data;\n\n\t\t\t\t\tthis.#sendMessage(requestId, {\n\t\t\t\t\t\ttag: \"ToClientWebSocketMessage\",\n\t\t\t\t\t\tval: {\n\t\t\t\t\t\t\tdata: dataBuffer,\n\t\t\t\t\t\t\tbinary: isBinary,\n\t\t\t\t\t\t},\n\t\t\t\t\t});\n\t\t\t\t},\n\t\t\t\t(code?: number, reason?: string) => {\n\t\t\t\t\t// Send close through tunnel\n\t\t\t\t\tthis.#sendMessage(requestId, {\n\t\t\t\t\t\ttag: \"ToClientWebSocketClose\",\n\t\t\t\t\t\tval: {\n\t\t\t\t\t\t\tcode: code || null,\n\t\t\t\t\t\t\treason: reason || null,\n\t\t\t\t\t\t},\n\t\t\t\t\t});\n\n\t\t\t\t\t// Remove from map\n\t\t\t\t\tthis.#actorWebSockets.delete(webSocketId);\n\n\t\t\t\t\t// Clean up actor tracking\n\t\t\t\t\tif (actor) {\n\t\t\t\t\t\tactor.webSockets.delete(webSocketId);\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t);\n\n\t\t\t// Store adapter\n\t\t\tthis.#actorWebSockets.set(webSocketId, adapter);\n\n\t\t\t// Send open confirmation\n\t\t\tthis.#sendMessage(requestId, {\n\t\t\t\ttag: \"ToClientWebSocketOpen\",\n\t\t\t\tval: null,\n\t\t\t});\n\n\t\t\t// Notify adapter that connection is open\n\t\t\tadapter._handleOpen();\n\n\t\t\t// Create a minimal request object for the websocket handler\n\t\t\t// Include original headers from the open message\n\t\t\tconst headerInit: Record<string, string> = {};\n\t\t\tif (open.headers) {\n\t\t\t\tfor (const [k, v] of open.headers as ReadonlyMap<\n\t\t\t\t\tstring,\n\t\t\t\t\tstring\n\t\t\t\t>) {\n\t\t\t\t\theaderInit[k] = v;\n\t\t\t\t}\n\t\t\t}\n\t\t\t// Ensure websocket upgrade headers are present\n\t\t\theaderInit[\"Upgrade\"] = \"websocket\";\n\t\t\theaderInit[\"Connection\"] = \"Upgrade\";\n\n\t\t\tconst request = new Request(`http://localhost${open.path}`, {\n\t\t\t\tmethod: \"GET\",\n\t\t\t\theaders: headerInit,\n\t\t\t});\n\n\t\t\t// Call websocket handler\n\t\t\tawait websocketHandler(open.actorId, adapter, request);\n\t\t} catch (error) {\n\t\t\tlogger()?.error({ msg: \"error handling websocket open\", error });\n\t\t\t// Send close on error\n\t\t\tthis.#sendMessage(requestId, {\n\t\t\t\ttag: \"ToClientWebSocketClose\",\n\t\t\t\tval: {\n\t\t\t\t\tcode: 1011,\n\t\t\t\t\treason: \"Server Error\",\n\t\t\t\t},\n\t\t\t});\n\n\t\t\tthis.#actorWebSockets.delete(webSocketId);\n\n\t\t\t// Clean up actor tracking\n\t\t\tif (actor) {\n\t\t\t\tactor.webSockets.delete(webSocketId);\n\t\t\t}\n\t\t}\n\t}\n\n\tasync #handleWebSocketMessage(\n\t\trequestId: ArrayBuffer,\n\t\tmsg: tunnel.ToServerWebSocketMessage,\n\t) {\n\t\tconst webSocketId = bufferToString(requestId);\n\t\tconst adapter = this.#actorWebSockets.get(webSocketId);\n\t\tif (adapter) {\n\t\t\tconst data = msg.binary\n\t\t\t\t? new Uint8Array(msg.data)\n\t\t\t\t: new TextDecoder().decode(new Uint8Array(msg.data));\n\n\t\t\tadapter._handleMessage(data, msg.binary);\n\t\t}\n\t}\n\n\tasync #handleWebSocketClose(\n\t\trequestId: ArrayBuffer,\n\t\tclose: tunnel.ToServerWebSocketClose,\n\t) {\n\t\tconst webSocketId = bufferToString(requestId);\n\t\tconst adapter = this.#actorWebSockets.get(webSocketId);\n\t\tif (adapter) {\n\t\t\tadapter._handleClose(\n\t\t\t\tclose.code || undefined,\n\t\t\t\tclose.reason || undefined,\n\t\t\t);\n\t\t\tthis.#actorWebSockets.delete(webSocketId);\n\t\t}\n\t}\n\n\t#handleResponseStart(\n\t\trequestId: ArrayBuffer,\n\t\tresp: tunnel.ToClientResponseStart,\n\t) {\n\t\tconst requestIdStr = bufferToString(requestId);\n\t\tconst pending = this.#actorPendingRequests.get(requestIdStr);\n\t\tif (!pending) {\n\t\t\tlogger()?.warn({\n\t\t\t\tmsg: \"received response for unknown request\",\n\t\t\t\trequestId: requestIdStr,\n\t\t\t});\n\t\t\treturn;\n\t\t}\n\n\t\t// Convert headers map to Headers object\n\t\tconst headers = new Headers();\n\t\tfor (const [key, value] of resp.headers) {\n\t\t\theaders.append(key, value);\n\t\t}\n\n\t\tif (resp.stream) {\n\t\t\t// Create streaming response\n\t\t\tconst stream = new ReadableStream<Uint8Array>({\n\t\t\t\tstart: (controller) => {\n\t\t\t\t\tpending.streamController = controller;\n\t\t\t\t},\n\t\t\t});\n\n\t\t\tconst response = new Response(stream, {\n\t\t\t\tstatus: resp.status,\n\t\t\t\theaders,\n\t\t\t});\n\n\t\t\tpending.resolve(response);\n\t\t} else {\n\t\t\t// Non-streaming response\n\t\t\tconst body = resp.body ? new Uint8Array(resp.body) : null;\n\t\t\tconst response = new Response(body, {\n\t\t\t\tstatus: resp.status,\n\t\t\t\theaders,\n\t\t\t});\n\n\t\t\tpending.resolve(response);\n\t\t\tthis.#actorPendingRequests.delete(requestIdStr);\n\t\t}\n\t}\n\n\t#handleResponseChunk(\n\t\trequestId: ArrayBuffer,\n\t\tchunk: tunnel.ToClientResponseChunk,\n\t) {\n\t\tconst requestIdStr = bufferToString(requestId);\n\t\tconst pending = this.#actorPendingRequests.get(requestIdStr);\n\t\tif (pending?.streamController) {\n\t\t\tpending.streamController.enqueue(new Uint8Array(chunk.body));\n\t\t\tif (chunk.finish) {\n\t\t\t\tpending.streamController.close();\n\t\t\t\tthis.#actorPendingRequests.delete(requestIdStr);\n\t\t\t}\n\t\t}\n\t}\n\n\t#handleResponseAbort(requestId: ArrayBuffer) {\n\t\tconst requestIdStr = bufferToString(requestId);\n\t\tconst pending = this.#actorPendingRequests.get(requestIdStr);\n\t\tif (pending?.streamController) {\n\t\t\tpending.streamController.error(new Error(\"Response aborted\"));\n\t\t}\n\t\tthis.#actorPendingRequests.delete(requestIdStr);\n\t}\n\n\t#handleWebSocketOpenResponse(\n\t\trequestId: ArrayBuffer,\n\t\topen: tunnel.ToClientWebSocketOpen,\n\t) {\n\t\tconst webSocketId = bufferToString(requestId);\n\t\tconst adapter = this.#actorWebSockets.get(webSocketId);\n\t\tif (adapter) {\n\t\t\tadapter._handleOpen();\n\t\t}\n\t}\n\n\t#handleWebSocketMessageResponse(\n\t\trequestId: ArrayBuffer,\n\t\tmsg: tunnel.ToClientWebSocketMessage,\n\t) {\n\t\tconst webSocketId = bufferToString(requestId);\n\t\tconst adapter = this.#actorWebSockets.get(webSocketId);\n\t\tif (adapter) {\n\t\t\tconst data = msg.binary\n\t\t\t\t? new Uint8Array(msg.data)\n\t\t\t\t: new TextDecoder().decode(new Uint8Array(msg.data));\n\n\t\t\tadapter._handleMessage(data, msg.binary);\n\t\t}\n\t}\n\n\t#handleWebSocketCloseResponse(\n\t\trequestId: ArrayBuffer,\n\t\tclose: tunnel.ToClientWebSocketClose,\n\t) {\n\t\tconst webSocketId = bufferToString(requestId);\n\t\tconst adapter = this.#actorWebSockets.get(webSocketId);\n\t\tif (adapter) {\n\t\t\tadapter._handleClose(\n\t\t\t\tclose.code || undefined,\n\t\t\t\tclose.reason || undefined,\n\t\t\t);\n\t\t\tthis.#actorWebSockets.delete(webSocketId);\n\t\t}\n\t}\n}\n\n/** Converts a buffer to a string. Used for storing strings in a lookup map. */\nfunction bufferToString(buffer: ArrayBuffer): string {\n\treturn Buffer.from(buffer).toString(\"base64\");\n}\n\n/** Generates a UUID as bytes. */\nfunction generateUuidBuffer(): ArrayBuffer {\n\tconst buffer = new Uint8Array(16);\n\tuuidv4(undefined, buffer);\n\treturn buffer.buffer;\n}\n","// WebSocket-like adapter for tunneled connections\n// Implements a subset of the WebSocket interface for compatibility with runner code\n\nimport { logger } from \"./log\";\n\nexport class WebSocketTunnelAdapter {\n\t#webSocketId: string;\n\t#readyState: number = 0; // CONNECTING\n\t#eventListeners: Map<string, Set<(event: any) => void>> = new Map();\n\t#onopen: ((this: any, ev: any) => any) | null = null;\n\t#onclose: ((this: any, ev: any) => any) | null = null;\n\t#onerror: ((this: any, ev: any) => any) | null = null;\n\t#onmessage: ((this: any, ev: any) => any) | null = null;\n\t#bufferedAmount = 0;\n\t#binaryType: \"nodebuffer\" | \"arraybuffer\" | \"blob\" = \"nodebuffer\";\n\t#extensions = \"\";\n\t#protocol = \"\";\n\t#url = \"\";\n\t#sendCallback: (data: ArrayBuffer | string, isBinary: boolean) => void;\n\t#closeCallback: (code?: number, reason?: string) => void;\n\t\n\t// Event buffering for events fired before listeners are attached\n\t#bufferedEvents: Array<{\n\t\ttype: string;\n\t\tevent: any;\n\t}> = [];\n\n\tconstructor(\n\t\twebSocketId: string,\n\t\tsendCallback: (data: ArrayBuffer | string, isBinary: boolean) => void,\n\t\tcloseCallback: (code?: number, reason?: string) => void\n\t) {\n\t\tthis.#webSocketId = webSocketId;\n\t\tthis.#sendCallback = sendCallback;\n\t\tthis.#closeCallback = closeCallback;\n\t}\n\n\tget readyState(): number {\n\t\treturn this.#readyState;\n\t}\n\n\tget bufferedAmount(): number {\n\t\treturn this.#bufferedAmount;\n\t}\n\n\tget binaryType(): string {\n\t\treturn this.#binaryType;\n\t}\n\n\tset binaryType(value: string) {\n\t\tif (value === \"nodebuffer\" || value === \"arraybuffer\" || value === \"blob\") {\n\t\t\tthis.#binaryType = value;\n\t\t}\n\t}\n\n\tget extensions(): string {\n\t\treturn this.#extensions;\n\t}\n\n\tget protocol(): string {\n\t\treturn this.#protocol;\n\t}\n\n\tget url(): string {\n\t\treturn this.#url;\n\t}\n\n\tget onopen(): ((this: any, ev: any) => any) | null {\n\t\treturn this.#onopen;\n\t}\n\n\tset onopen(value: ((this: any, ev: any) => any) | null) {\n\t\tthis.#onopen = value;\n\t\t// Flush any buffered open events when onopen is set\n\t\tif (value) {\n\t\t\tthis.#flushBufferedEvents(\"open\");\n\t\t}\n\t}\n\n\tget onclose(): ((this: any, ev: any) => any) | null {\n\t\treturn this.#onclose;\n\t}\n\n\tset onclose(value: ((this: any, ev: any) => any) | null) {\n\t\tthis.#onclose = value;\n\t\t// Flush any buffered close events when onclose is set\n\t\tif (value) {\n\t\t\tthis.#flushBufferedEvents(\"close\");\n\t\t}\n\t}\n\n\tget onerror(): ((this: any, ev: any) => any) | null {\n\t\treturn this.#onerror;\n\t}\n\n\tset onerror(value: ((this: any, ev: any) => any) | null) {\n\t\tthis.#onerror = value;\n\t\t// Flush any buffered error events when onerror is set\n\t\tif (value) {\n\t\t\tthis.#flushBufferedEvents(\"error\");\n\t\t}\n\t}\n\n\tget onmessage(): ((this: any, ev: any) => any) | null {\n\t\treturn this.#onmessage;\n\t}\n\n\tset onmessage(value: ((this: any, ev: any) => any) | null) {\n\t\tthis.#onmessage = value;\n\t\t// Flush any buffered message events when onmessage is set\n\t\tif (value) {\n\t\t\tthis.#flushBufferedEvents(\"message\");\n\t\t}\n\t}\n\n\tsend(data: string | ArrayBuffer | ArrayBufferView | Blob | Buffer): void {\n\t\tif (this.#readyState !== 1) { // OPEN\n\t\t\tthrow new Error(\"WebSocket is not open\");\n\t\t}\n\n\t\tlet isBinary = false;\n\t\tlet messageData: string | ArrayBuffer;\n\n\t\tif (typeof data === \"string\") {\n\t\t\tmessageData = data;\n\t\t} else if (data instanceof ArrayBuffer) {\n\t\t\tisBinary = true;\n\t\t\tmessageData = data;\n\t\t} else if (ArrayBuffer.isView(data)) {\n\t\t\tisBinary = true;\n\t\t\t// Convert ArrayBufferView to ArrayBuffer\n\t\t\tconst view = data as ArrayBufferView;\n\t\t\t// Check if it's a SharedArrayBuffer\n\t\t\tif (view.buffer instanceof SharedArrayBuffer) {\n\t\t\t\t// Copy SharedArrayBuffer to regular ArrayBuffer\n\t\t\t\tconst bytes = new Uint8Array(view.buffer, view.byteOffset, view.byteLength);\n\t\t\t\tmessageData = bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength) as unknown as ArrayBuffer;\n\t\t\t} else {\n\t\t\t\tmessageData = view.buffer.slice(\n\t\t\t\t\tview.byteOffset,\n\t\t\t\t\tview.byteOffset + view.byteLength\n\t\t\t\t) as ArrayBuffer;\n\t\t\t}\n\t\t} else if (data instanceof Blob) {\n\t\t\tthrow new Error(\"Blob sending not implemented in tunnel adapter\");\n\t\t} else if (typeof Buffer !== 'undefined' && Buffer.isBuffer(data)) {\n\t\t\tisBinary = true;\n\t\t\t// Convert Buffer to ArrayBuffer\n\t\t\tconst buf = data as Buffer;\n\t\t\t// Check if it's a SharedArrayBuffer\n\t\t\tif (buf.buffer instanceof SharedArrayBuffer) {\n\t\t\t\t// Copy SharedArrayBuffer to regular ArrayBuffer\n\t\t\t\tconst bytes = new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength);\n\t\t\t\tmessageData = bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength) as unknown as ArrayBuffer;\n\t\t\t} else {\n\t\t\t\tmessageData = buf.buffer.slice(\n\t\t\t\t\tbuf.byteOffset,\n\t\t\t\t\tbuf.byteOffset + buf.byteLength\n\t\t\t\t) as ArrayBuffer;\n\t\t\t}\n\t\t} else {\n\t\t\tthrow new Error(\"Invalid data type\");\n\t\t}\n\n\t\t// Send through tunnel\n\t\tthis.#sendCallback(messageData, isBinary);\n\t}\n\n\tclose(code?: number, reason?: string): void {\n\t\tif (\n\t\t\tthis.#readyState === 2 || // CLOSING\n\t\t\tthis.#readyState === 3 // CLOSED\n\t\t) {\n\t\t\treturn;\n\t\t}\n\n\t\tthis.#readyState = 2; // CLOSING\n\n\t\t// Send close through tunnel\n\t\tthis.#closeCallback(code, reason);\n\n\t\t// Update state and fire event\n\t\tthis.#readyState = 3; // CLOSED\n\t\t\n\t\tconst closeEvent = {\n\t\t\twasClean: true,\n\t\t\tcode: code || 1000,\n\t\t\treason: reason || \"\",\n\t\t\ttype: \"close\",\n\t\t\ttarget: this,\n\t\t};\n\t\t\n\t\tthis.#fireEvent(\"close\", closeEvent);\n\t}\n\n\taddEventListener(\n\t\ttype: string,\n\t\tlistener: (event: any) => void,\n\t\toptions?: boolean | any\n\t): void {\n\t\tif (typeof listener === \"function\") {\n\t\t\tlet listeners = this.#eventListeners.get(type);\n\t\t\tif (!listeners) {\n\t\t\t\tlisteners = new Set();\n\t\t\t\tthis.#eventListeners.set(type, listeners);\n\t\t\t}\n\t\t\tlisteners.add(listener);\n\n\t\t\t// Flush any buffered events for this type\n\t\t\tthis.#flushBufferedEvents(type);\n\t\t}\n\t}\n\n\tremoveEventListener(\n\t\ttype: string,\n\t\tlistener: (event: any) => void,\n\t\toptions?: boolean | any\n\t): void {\n\t\tif (typeof listener === \"function\") {\n\t\t\tconst listeners = this.#eventListeners.get(type);\n\t\t\tif (listeners) {\n\t\t\t\tlisteners.delete(listener);\n\t\t\t}\n\t\t}\n\t}\n\n\tdispatchEvent(event: any): boolean {\n\t\t// Simple implementation\n\t\treturn true;\n\t}\n\n\t#fireEvent(type: string, event: any): void {\n\t\t// Call all registered event listeners\n\t\tconst listeners = this.#eventListeners.get(type);\n\t\tlet hasListeners = false;\n\n\t\tif (listeners && listeners.size > 0) {\n\t\t\thasListeners = true;\n\t\t\tfor (const listener of listeners) {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tlistener.call(this, event);\n\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\tlogger()?.error({ msg: \"error in websocket event listener\", error, type });\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\n\t\t// Call the onX property if set\n\t\tswitch (type) {\n\t\t\tcase \"open\":\n\t\t\t\tif (this.#onopen) {\n\t\t\t\t\thasListeners = true;\n\t\t\t\t\ttry {\n\t\t\t\t\t\tthis.#onopen.call(this, event);\n\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\tlogger()?.error({ msg: \"error in onopen handler\", error });\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\tcase \"close\":\n\t\t\t\tif (this.#onclose) {\n\t\t\t\t\thasListeners = true;\n\t\t\t\t\ttry {\n\t\t\t\t\t\tthis.#onclose.call(this, event);\n\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\tlogger()?.error({ msg: \"error in onclose handler\", error });\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\tcase \"error\":\n\t\t\t\tif (this.#onerror) {\n\t\t\t\t\thasListeners = true;\n\t\t\t\t\ttry {\n\t\t\t\t\t\tthis.#onerror.call(this, event);\n\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\tlogger()?.error({ msg: \"error in onerror handler\", error });\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\tcase \"message\":\n\t\t\t\tif (this.#onmessage) {\n\t\t\t\t\thasListeners = true;\n\t\t\t\t\ttry {\n\t\t\t\t\t\tthis.#onmessage.call(this, event);\n\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\tlogger()?.error({ msg: \"error in onmessage handler\", error });\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t}\n\n\t\t// Buffer the event if no listeners are registered\n\t\tif (!hasListeners) {\n\t\t\tthis.#bufferedEvents.push({ type, event });\n\t\t}\n\t}\n\n\t#flushBufferedEvents(type: string): void {\n\t\tconst eventsToFlush = this.#bufferedEvents.filter(\n\t\t\t(buffered) => buffered.type === type\n\t\t);\n\t\tthis.#bufferedEvents = this.#bufferedEvents.filter(\n\t\t\t(buffered) => buffered.type !== type\n\t\t);\n\n\t\tfor (const { event } of eventsToFlush) {\n\t\t\t// Re-fire the event, which will now have listeners\n\t\t\tconst listeners = this.#eventListeners.get(type);\n\t\t\tif (listeners) {\n\t\t\t\tfor (const listener of listeners) {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tlistener.call(this, event);\n\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\tlogger()?.error({\n\t\t\t\t\t\t\tmsg: \"error in websocket event listener\",\n\t\t\t\t\t\t\terror,\n\t\t\t\t\t\t\ttype,\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Also call the onX handler if it exists\n\t\t\tswitch (type) {\n\t\t\t\tcase \"open\":\n\t\t\t\t\tif (this.#onopen) {\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tthis.#onopen.call(this, event);\n\t\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\t\tlogger()?.error({ msg: \"error in onopen handler\", error });\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"close\":\n\t\t\t\t\tif (this.#onclose) {\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tthis.#onclose.call(this, event);\n\t\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\t\tlogger()?.error({ msg: \"error in onclose handler\", error });\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"error\":\n\t\t\t\t\tif (this.#onerror) {\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tthis.#onerror.call(this, event);\n\t\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\t\tlogger()?.error({ msg: \"error in onerror handler\", error });\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"message\":\n\t\t\t\t\tif (this.#onmessage) {\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tthis.#onmessage.call(this, event);\n\t\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\t\tlogger()?.error({ msg: \"error in onmessage handler\", error });\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t}\n\n\t// Internal methods called by the Tunnel class\n\t_handleOpen(): void {\n\t\tif (this.#readyState !== 0) { // CONNECTING\n\t\t\treturn;\n\t\t}\n\n\t\tthis.#readyState = 1; // OPEN\n\t\t\n\t\tconst event = {\n\t\t\ttype: \"open\",\n\t\t\ttarget: this,\n\t\t};\n\t\t\n\t\tthis.#fireEvent(\"open\", event);\n\t}\n\n\t_handleMessage(data: string | Uint8Array, isBinary: boolean): void {\n\t\tif (this.#readyState !== 1) { // OPEN\n\t\t\treturn;\n\t\t}\n\n\t\tlet messageData: any;\n\t\t\n\t\tif (isBinary) {\n\t\t\t// Handle binary data based on binaryType\n\t\t\tif (this.#binaryType === \"nodebuffer\") {\n\t\t\t\t// Convert to Buffer for Node.js compatibility\n\t\t\t\tmessageData = Buffer.from(data as Uint8Array);\n\t\t\t} else if (this.#binaryType === \"arraybuffer\") {\n\t\t\t\t// Convert to ArrayBuffer\n\t\t\t\tif (data instanceof Uint8Array) {\n\t\t\t\t\tmessageData = data.buffer.slice(\n\t\t\t\t\t\tdata.byteOffset,\n\t\t\t\t\t\tdata.byteOffset + data.byteLength\n\t\t\t\t\t);\n\t\t\t\t} else {\n\t\t\t\t\tmessageData = data;\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\t// Blob type - not commonly used in Node.js\n\t\t\t\tthrow new Error(\"Blob binaryType not supported in tunnel adapter\");\n\t\t\t}\n\t\t} else {\n\t\t\tmessageData = data;\n\t\t}\n\n\t\tconst event = {\n\t\t\tdata: messageData,\n\t\t\ttype: \"message\",\n\t\t\ttarget: this,\n\t\t};\n\n\t\tthis.#fireEvent(\"message\", event);\n\t}\n\n\t_handleClose(code?: number, reason?: string): void {\n\t\tif (this.#readyState === 3) { // CLOSED\n\t\t\treturn;\n\t\t}\n\n\t\tthis.#readyState = 3; // CLOSED\n\n\t\tconst event = {\n\t\t\twasClean: true,\n\t\t\tcode: code || 1000,\n\t\t\treason: reason || \"\",\n\t\t\ttype: \"close\",\n\t\t\ttarget: this,\n\t\t};\n\n\t\tthis.#fireEvent(\"close\", event);\n\t}\n\n\t_handleError(error: Error): void {\n\t\tconst event = {\n\t\t\ttype: \"error\",\n\t\t\ttarget: this,\n\t\t\terror,\n\t\t};\n\n\t\tthis.#fireEvent(\"error\", event);\n\t}\n\n\t// WebSocket constants for compatibility\n\tstatic readonly CONNECTING = 0;\n\tstatic readonly OPEN = 1;\n\tstatic readonly CLOSING = 2;\n\tstatic readonly CLOSED = 3;\n\n\t// Instance constants\n\treadonly CONNECTING = 0;\n\treadonly OPEN = 1;\n\treadonly CLOSING = 2;\n\treadonly CLOSED = 3;\n\n\t// Additional methods for compatibility\n\tping(data?: any, mask?: boolean, cb?: (err: Error) => void): void {\n\t\t// Not implemented for tunnel - could be added if needed\n\t\tif (cb) cb(new Error(\"Ping not supported in tunnel adapter\"));\n\t}\n\n\tpong(data?: any, mask?: boolean, cb?: (err: Error) => void): void {\n\t\t// Not implemented for tunnel - could be added if needed\n\t\tif (cb) cb(new Error(\"Pong not supported in tunnel adapter\"));\n\t}\n\n\tterminate(): void {\n\t\t// Immediate close without close frame\n\t\tthis.#readyState = 3; // CLOSED\n\t\tthis.#closeCallback(1006, \"Abnormal Closure\");\n\t\t\n\t\tconst event = {\n\t\t\twasClean: false,\n\t\t\tcode: 1006,\n\t\t\treason: \"Abnormal Closure\",\n\t\t\ttype: \"close\",\n\t\t\ttarget: this,\n\t\t};\n\t\t\n\t\tthis.#fireEvent(\"close\", event);\n\t}\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["/Users/nathan/engine/sdks/typescript/runner/dist/mod.cjs","../src/log.ts","../src/websocket.ts","../src/mod.ts","../src/utils.ts","../src/websocket-tunnel-adapter.ts","../src/tunnel.ts"],"names":["logger","_a"],"mappings":"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","file":"/Users/nathan/engine/sdks/typescript/runner/dist/mod.cjs","sourcesContent":[null,"import { Logger } from \"pino\";\n\nlet LOGGER: Logger | undefined = undefined;\n\nexport function setLogger(logger: Logger) {\n\tLOGGER = logger;\n}\n\nexport function logger(): Logger | undefined {\n\treturn LOGGER;\n}\n","import { logger } from \"./log\";\n\n// Global singleton promise that will be reused for subsequent calls\nlet webSocketPromise: Promise<typeof WebSocket> | null = null;\n\nexport async function importWebSocket(): Promise<typeof WebSocket> {\n\t// Return existing promise if we already started loading\n\tif (webSocketPromise !== null) {\n\t\treturn webSocketPromise;\n\t}\n\n\t// Create and store the promise\n\twebSocketPromise = (async () => {\n\t\tlet _WebSocket: typeof WebSocket;\n\n\t\tif (typeof WebSocket !== \"undefined\") {\n\t\t\t// Native\n\t\t\t_WebSocket = WebSocket as unknown as typeof WebSocket;\n\t\t\tlogger()?.debug({ msg: \"using native websocket\" });\n\t\t} else {\n\t\t\t// Node.js package\n\t\t\ttry {\n\t\t\t\tconst ws = await import(\"ws\");\n\t\t\t\t_WebSocket = ws.default as unknown as typeof WebSocket;\n\t\t\t\tlogger()?.debug({ msg: \"using websocket from npm\" });\n\t\t\t} catch {\n\t\t\t\t// WS not available\n\t\t\t\t_WebSocket = class MockWebSocket {\n\t\t\t\t\tconstructor() {\n\t\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t\t'WebSocket support requires installing the \"ws\" peer dependency.',\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t} as unknown as typeof WebSocket;\n\t\t\t\tlogger()?.debug({ msg: \"using mock websocket\" });\n\t\t\t}\n\t\t}\n\n\t\treturn _WebSocket;\n\t})();\n\n\treturn webSocketPromise;\n}\n","import type WebSocket from \"ws\";\nimport { importWebSocket } from \"./websocket.js\";\nimport * as protocol from \"@rivetkit/engine-runner-protocol\";\nimport { unreachable, calculateBackoff } from \"./utils\";\nimport { Tunnel } from \"./tunnel\";\nimport type { WebSocketTunnelAdapter } from \"./websocket-tunnel-adapter\";\nimport type { Logger } from \"pino\";\nimport { setLogger, logger } from \"./log.js\";\n\nconst KV_EXPIRE: number = 30_000;\n\nexport interface ActorInstance {\n\tactorId: string;\n\tgeneration: number;\n\tconfig: ActorConfig;\n\trequests: Set<string>; // Track active request IDs\n\twebSockets: Set<string>; // Track active WebSocket IDs\n}\n\nexport interface ActorConfig {\n\tname: string;\n\tkey: string | null;\n\tcreateTs: bigint;\n\tinput: Uint8Array | null;\n}\n\nexport interface RunnerConfig {\n\tlogger?: Logger;\n\tversion: number;\n\tendpoint: string;\n\tpegboardEndpoint?: string;\n\tpegboardRelayEndpoint?: string;\n\tnamespace: string;\n\ttotalSlots: number;\n\trunnerName: string;\n\trunnerKey: string;\n\tprepopulateActorNames: Record<string, { metadata: Record<string, any> }>;\n\tmetadata?: Record<string, any>;\n\tonConnected: () => void;\n\tonDisconnected: () => void;\n\tonShutdown: () => void;\n\tfetch: (actorId: string, request: Request) => Promise<Response>;\n\twebsocket?: (actorId: string, ws: any, request: Request) => Promise<void>;\n\tonActorStart: (\n\t\tactorId: string,\n\t\tgeneration: number,\n\t\tconfig: ActorConfig,\n\t) => Promise<void>;\n\tonActorStop: (actorId: string, generation: number) => Promise<void>;\n\tnoAutoShutdown?: boolean;\n}\n\nexport interface KvListOptions {\n\treverse?: boolean;\n\tlimit?: number;\n}\n\ninterface KvRequestEntry {\n\tactorId: string;\n\tdata: protocol.KvRequestData;\n\tresolve: (value: any) => void;\n\treject: (error: unknown) => void;\n\tsent: boolean;\n\ttimestamp: number;\n}\n\nexport class Runner {\n\t#config: RunnerConfig;\n\n\tget config(): RunnerConfig {\n\t\treturn this.#config;\n\t}\n\n\t#actors: Map<string, ActorInstance> = new Map();\n\t#actorWebSockets: Map<string, Set<WebSocketTunnelAdapter>> = new Map();\n\n\t// WebSocket\n\t#pegboardWebSocket?: WebSocket;\n\trunnerId?: string;\n\t#lastCommandIdx: number = -1;\n\t#pingLoop?: NodeJS.Timeout;\n\t#nextEventIdx: bigint = 0n;\n\t#started: boolean = false;\n\t#shutdown: boolean = false;\n\t#reconnectAttempt: number = 0;\n\t#reconnectTimeout?: NodeJS.Timeout;\n\n\t// Runner lost threshold management\n\t#runnerLostThreshold?: number;\n\t#runnerLostTimeout?: NodeJS.Timeout;\n\n\t// Event storage for resending\n\t#eventHistory: { event: protocol.EventWrapper; timestamp: number }[] = [];\n\t#eventPruneInterval?: NodeJS.Timeout;\n\n\t// Command acknowledgment\n\t#ackInterval?: NodeJS.Timeout;\n\n\t// KV operations\n\t#nextRequestId: number = 0;\n\t#kvRequests: Map<number, KvRequestEntry> = new Map();\n\t#kvCleanupInterval?: NodeJS.Timeout;\n\n\t// Tunnel for HTTP/WebSocket forwarding\n\t#tunnel: Tunnel;\n\n\tconstructor(config: RunnerConfig) {\n\t\tthis.#config = config;\n\t\tif (this.#config.logger) setLogger(this.#config.logger);\n\n\t\tthis.#tunnel = new Tunnel(this);\n\n\t\t// TODO(RVT-4986): Prune when server acks events\n\t\t// Start pruning old events every minute\n\t\tthis.#eventPruneInterval = setInterval(() => {\n\t\t\tthis.#pruneOldEvents();\n\t\t}, 60000); // Run every minute\n\n\t\t// Start cleaning up old unsent KV requests every 15 seconds\n\t\tthis.#kvCleanupInterval = setInterval(() => {\n\t\t\tthis.#cleanupOldKvRequests();\n\t\t}, 15000); // Run every 15 seconds\n\t}\n\n\t// MARK: Manage actors\n\tsleepActor(actorId: string, generation?: number) {\n\t\tconst actor = this.getActor(actorId, generation);\n\t\tif (!actor) return;\n\n\t\t// Keep the actor instance in memory during sleep\n\t\tthis.#sendActorIntent(actorId, actor.generation, \"sleep\");\n\n\t\t// NOTE: We do NOT remove the actor from this.#actors here\n\t\t// The server will send a StopActor command if it wants to fully stop\n\t}\n\n\tasync stopActor(actorId: string, generation?: number) {\n\t\tconst actor = this.#removeActor(actorId, generation);\n\t\tif (!actor) return;\n\n\t\t// Unregister actor from tunnel\n\t\tthis.#tunnel.unregisterActor(actor);\n\n\t\t// If onActorStop times out, Pegboard will handle this timeout with ACTOR_STOP_THRESHOLD_DURATION_MS\n\t\ttry {\n\t\t\tawait this.#config.onActorStop(actorId, actor.generation);\n\t\t} catch (err) {\n\t\t\tconsole.error(`Error in onActorStop for actor ${actorId}:`, err);\n\t\t}\n\n\t\tthis.#sendActorStateUpdate(actorId, actor.generation, \"stopped\");\n\n\t\tthis.#config.onActorStop(actorId, actor.generation).catch((err) => {\n\t\t\tlogger()?.error({\n\t\t\t\tmsg: \"error in onactorstop for actor\",\n\t\t\t\tactorId,\n\t\t\t\terr,\n\t\t\t});\n\t\t});\n\t}\n\n\t#stopAllActors() {\n\t\tlogger()?.info(\n\t\t\t\"stopping all actors due to runner lost threshold exceeded\",\n\t\t);\n\n\t\tconst actorIds = Array.from(this.#actors.keys());\n\t\tfor (const actorId of actorIds) {\n\t\t\tthis.stopActor(actorId);\n\t\t}\n\t}\n\n\tgetActor(actorId: string, generation?: number): ActorInstance | undefined {\n\t\tconst actor = this.#actors.get(actorId);\n\t\tif (!actor) {\n\t\t\tlogger()?.error({ msg: \"actor not found\", actorId });\n\t\t\treturn undefined;\n\t\t}\n\t\tif (generation !== undefined && actor.generation !== generation) {\n\t\t\tlogger()?.error({\n\t\t\t\tmsg: \"actor generation mismatch\",\n\t\t\t\tactorId,\n\t\t\t\tgeneration,\n\t\t\t});\n\t\t\treturn undefined;\n\t\t}\n\n\t\treturn actor;\n\t}\n\n\thasActor(actorId: string, generation?: number): boolean {\n\t\tconst actor = this.#actors.get(actorId);\n\n\t\treturn (\n\t\t\t!!actor &&\n\t\t\t(generation === undefined || actor.generation === generation)\n\t\t);\n\t}\n\n\t#removeActor(\n\t\tactorId: string,\n\t\tgeneration?: number,\n\t): ActorInstance | undefined {\n\t\tconst actor = this.#actors.get(actorId);\n\t\tif (!actor) {\n\t\t\tlogger()?.error({ msg: \"actor not found\", actorId });\n\t\t\treturn undefined;\n\t\t}\n\t\tif (generation !== undefined && actor.generation !== generation) {\n\t\t\tlogger()?.error({\n\t\t\t\tmsg: \"actor generation mismatch\",\n\t\t\t\tactorId,\n\t\t\t\tgeneration,\n\t\t\t});\n\t\t\treturn undefined;\n\t\t}\n\n\t\tthis.#actors.delete(actorId);\n\n\t\t// Close all WebSocket connections for this actor\n\t\tconst actorWebSockets = this.#actorWebSockets.get(actorId);\n\t\tif (actorWebSockets) {\n\t\t\tfor (const ws of actorWebSockets) {\n\t\t\t\ttry {\n\t\t\t\t\tws.close(1000, \"Actor stopped\");\n\t\t\t\t} catch (err) {\n\t\t\t\t\tlogger()?.error({\n\t\t\t\t\t\tmsg: \"error closing websocket for actor\",\n\t\t\t\t\t\tactorId,\n\t\t\t\t\t\terr,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\t\t\tthis.#actorWebSockets.delete(actorId);\n\t\t}\n\n\t\treturn actor;\n\t}\n\n\t// MARK: Start\n\tasync start() {\n\t\tif (this.#started) throw new Error(\"Cannot call runner.start twice\");\n\t\tthis.#started = true;\n\n\t\tlogger()?.info(\"starting runner\");\n\n\t\tthis.#tunnel.start();\n\n\t\ttry {\n\t\t\tawait this.#openPegboardWebSocket();\n\t\t} catch (error) {\n\t\t\tthis.#started = false;\n\t\t\tthrow error;\n\t\t}\n\n\t\tif (!this.#config.noAutoShutdown) {\n\t\t\tprocess.on(\"SIGTERM\", this.shutdown.bind(this, false, true));\n\t\t\tprocess.on(\"SIGINT\", this.shutdown.bind(this, false, true));\n\t\t}\n\t}\n\n\t// MARK: Shutdown\n\tasync shutdown(immediate: boolean, exit: boolean = false) {\n\t\tlogger()?.info({ msg: \"starting shutdown...\", immediate });\n\t\tthis.#shutdown = true;\n\n\t\t// Clear reconnect timeout\n\t\tif (this.#reconnectTimeout) {\n\t\t\tclearTimeout(this.#reconnectTimeout);\n\t\t\tthis.#reconnectTimeout = undefined;\n\t\t}\n\n\t\t// Clear runner lost timeout\n\t\tif (this.#runnerLostTimeout) {\n\t\t\tclearTimeout(this.#runnerLostTimeout);\n\t\t\tthis.#runnerLostTimeout = undefined;\n\t\t}\n\n\t\t// Clear ping loop\n\t\tif (this.#pingLoop) {\n\t\t\tclearInterval(this.#pingLoop);\n\t\t\tthis.#pingLoop = undefined;\n\t\t}\n\n\t\t// Clear ack interval\n\t\tif (this.#ackInterval) {\n\t\t\tclearInterval(this.#ackInterval);\n\t\t\tthis.#ackInterval = undefined;\n\t\t}\n\n\t\t// Clear event prune interval\n\t\tif (this.#eventPruneInterval) {\n\t\t\tclearInterval(this.#eventPruneInterval);\n\t\t\tthis.#eventPruneInterval = undefined;\n\t\t}\n\n\t\t// Clear KV cleanup interval\n\t\tif (this.#kvCleanupInterval) {\n\t\t\tclearInterval(this.#kvCleanupInterval);\n\t\t\tthis.#kvCleanupInterval = undefined;\n\t\t}\n\n\t\t// Reject all KV requests\n\t\tfor (const request of this.#kvRequests.values()) {\n\t\t\trequest.reject(\n\t\t\t\tnew Error(\"WebSocket connection closed during shutdown\"),\n\t\t\t);\n\t\t}\n\t\tthis.#kvRequests.clear();\n\n\t\t// Close WebSocket\n\t\tif (\n\t\t\tthis.#pegboardWebSocket &&\n\t\t\tthis.#pegboardWebSocket.readyState === 1\n\t\t) {\n\t\t\tconst pegboardWebSocket = this.#pegboardWebSocket;\n\t\t\tif (immediate) {\n\t\t\t\t// Stop immediately\n\t\t\t\tpegboardWebSocket.close(1000, \"Stopping\");\n\t\t\t} else {\n\t\t\t\t// Wait for actors to shut down before stopping\n\t\t\t\ttry {\n\t\t\t\t\tlogger()?.info({\n\t\t\t\t\t\tmsg: \"sending stopping message\",\n\t\t\t\t\t\treadyState: pegboardWebSocket.readyState,\n\t\t\t\t\t});\n\n\t\t\t\t\t// NOTE: We don't use #sendToServer here because that function checks if the runner is\n\t\t\t\t\t// shut down\n\t\t\t\t\tconst encoded = protocol.encodeToServer({\n\t\t\t\t\t\ttag: \"ToServerStopping\",\n\t\t\t\t\t\tval: null,\n\t\t\t\t\t});\n\t\t\t\t\tif (\n\t\t\t\t\t\tthis.#pegboardWebSocket &&\n\t\t\t\t\t\tthis.#pegboardWebSocket.readyState === 1\n\t\t\t\t\t) {\n\t\t\t\t\t\tthis.#pegboardWebSocket.send(encoded);\n\t\t\t\t\t} else {\n\t\t\t\t\t\tlogger()?.error(\n\t\t\t\t\t\t\t\"WebSocket not available or not open for sending data\",\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\n\t\t\t\t\tconst closePromise = new Promise<void>((resolve) => {\n\t\t\t\t\t\tif (!pegboardWebSocket)\n\t\t\t\t\t\t\tthrow new Error(\"missing pegboardWebSocket\");\n\n\t\t\t\t\t\tpegboardWebSocket.addEventListener(\"close\", (ev) => {\n\t\t\t\t\t\t\tlogger()?.info({\n\t\t\t\t\t\t\t\tmsg: \"connection closed\",\n\t\t\t\t\t\t\t\tcode: ev.code,\n\t\t\t\t\t\t\t\treason: ev.reason.toString(),\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\tresolve();\n\t\t\t\t\t\t});\n\t\t\t\t\t});\n\n\t\t\t\t\t// TODO: Wait for all actors to stop before closing ws\n\n\t\t\t\t\tlogger()?.info(\"closing WebSocket\");\n\t\t\t\t\tpegboardWebSocket.close(1000, \"Stopping\");\n\n\t\t\t\t\tawait closePromise;\n\n\t\t\t\t\tlogger()?.info(\"websocket shutdown completed\");\n\t\t\t\t} catch (error) {\n\t\t\t\t\tlogger()?.error({\n\t\t\t\t\t\tmsg: \"error during websocket shutdown:\",\n\t\t\t\t\t\terror,\n\t\t\t\t\t});\n\t\t\t\t\tpegboardWebSocket.close();\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\n\t\t\tlogger()?.warn(\"no runner WebSocket to shutdown or already closed\");\n\t\t}\n\n\t\t// Close tunnel\n\t\tif (this.#tunnel) {\n\t\t\tthis.#tunnel.shutdown();\n\t\t\tlogger()?.info(\"tunnel shutdown completed\");\n\t\t}\n\n\t\tif (exit) process.exit(0);\n\n\t\tthis.#config.onShutdown();\n\t}\n\n\t// MARK: Networking\n\tget pegboardUrl() {\n\t\tconst endpoint = this.#config.pegboardEndpoint || this.#config.endpoint;\n\t\tconst wsEndpoint = endpoint\n\t\t\t.replace(\"http://\", \"ws://\")\n\t\t\t.replace(\"https://\", \"wss://\");\n\t\treturn `${wsEndpoint}?protocol_version=1&namespace=${encodeURIComponent(this.#config.namespace)}&runner_key=${encodeURIComponent(this.#config.runnerKey)}`;\n\t}\n\n\tget pegboardTunnelUrl() {\n\t\tconst endpoint =\n\t\t\tthis.#config.pegboardRelayEndpoint ||\n\t\t\tthis.#config.pegboardEndpoint ||\n\t\t\tthis.#config.endpoint;\n\t\tconst wsEndpoint = endpoint\n\t\t\t.replace(\"http://\", \"ws://\")\n\t\t\t.replace(\"https://\", \"wss://\");\n\t\treturn `${wsEndpoint}?protocol_version=1&namespace=${encodeURIComponent(this.#config.namespace)}&runner_name=${encodeURIComponent(this.#config.runnerName)}&runner_key=${encodeURIComponent(this.#config.runnerKey)}`;\n\t}\n\n\t// MARK: Runner protocol\n\tasync #openPegboardWebSocket() {\n\t\tconst WS = await importWebSocket();\n\t\tconst ws = new WS(this.pegboardUrl, {\n\t\t\theaders: {\n\t\t\t\t\"x-rivet-target\": \"runner\",\n\t\t\t},\n\t\t}) as any as WebSocket;\n\t\tthis.#pegboardWebSocket = ws;\n\n\t\tws.addEventListener(\"open\", () => {\n\t\t\tlogger()?.info(\"Connected\");\n\n\t\t\t// Reset reconnect attempt counter on successful connection\n\t\t\tthis.#reconnectAttempt = 0;\n\n\t\t\t// Clear any pending reconnect timeout\n\t\t\tif (this.#reconnectTimeout) {\n\t\t\t\tclearTimeout(this.#reconnectTimeout);\n\t\t\t\tthis.#reconnectTimeout = undefined;\n\t\t\t}\n\n\t\t\t// Clear any pending runner lost timeout since we're reconnecting\n\t\t\tif (this.#runnerLostTimeout) {\n\t\t\t\tclearTimeout(this.#runnerLostTimeout);\n\t\t\t\tthis.#runnerLostTimeout = undefined;\n\t\t\t}\n\n\t\t\t// Send init message\n\t\t\tconst init: protocol.ToServerInit = {\n\t\t\t\tname: this.#config.runnerName,\n\t\t\t\tversion: this.#config.version,\n\t\t\t\ttotalSlots: this.#config.totalSlots,\n\t\t\t\tlastCommandIdx:\n\t\t\t\t\tthis.#lastCommandIdx >= 0\n\t\t\t\t\t\t? BigInt(this.#lastCommandIdx)\n\t\t\t\t\t\t: null,\n\t\t\t\tprepopulateActorNames: new Map(\n\t\t\t\t\tObject.entries(this.#config.prepopulateActorNames).map(\n\t\t\t\t\t\t([name, data]) => [\n\t\t\t\t\t\t\tname,\n\t\t\t\t\t\t\t{ metadata: JSON.stringify(data.metadata) },\n\t\t\t\t\t\t],\n\t\t\t\t\t),\n\t\t\t\t),\n\t\t\t\tmetadata: JSON.stringify(this.#config.metadata),\n\t\t\t};\n\n\t\t\tthis.__sendToServer({\n\t\t\t\ttag: \"ToServerInit\",\n\t\t\t\tval: init,\n\t\t\t});\n\n\t\t\t// Process unsent KV requests\n\t\t\tthis.#processUnsentKvRequests();\n\n\t\t\t// Start ping interval\n\t\t\tconst pingInterval = 1000;\n\t\t\tconst pingLoop = setInterval(() => {\n\t\t\t\tif (ws.readyState === 1) {\n\t\t\t\t\tthis.__sendToServer({\n\t\t\t\t\t\ttag: \"ToServerPing\",\n\t\t\t\t\t\tval: {\n\t\t\t\t\t\t\tts: BigInt(Date.now()),\n\t\t\t\t\t\t},\n\t\t\t\t\t});\n\t\t\t\t} else {\n\t\t\t\t\tclearInterval(pingLoop);\n\t\t\t\t\tlogger()?.info(\"WebSocket not open, stopping ping loop\");\n\t\t\t\t}\n\t\t\t}, pingInterval);\n\t\t\tthis.#pingLoop = pingLoop;\n\n\t\t\t// Start command acknowledgment interval (5 minutes)\n\t\t\tconst ackInterval = 5 * 60 * 1000; // 5 minutes in milliseconds\n\t\t\tconst ackLoop = setInterval(() => {\n\t\t\t\tif (ws.readyState === 1) {\n\t\t\t\t\tthis.#sendCommandAcknowledgment();\n\t\t\t\t} else {\n\t\t\t\t\tclearInterval(ackLoop);\n\t\t\t\t\tlogger()?.info(\"WebSocket not open, stopping ack loop\");\n\t\t\t\t}\n\t\t\t}, ackInterval);\n\t\t\tthis.#ackInterval = ackLoop;\n\t\t});\n\n\t\tws.addEventListener(\"message\", async (ev) => {\n\t\t\tlet buf: Uint8Array;\n\t\t\tif (ev.data instanceof Blob) {\n\t\t\t\tbuf = new Uint8Array(await ev.data.arrayBuffer());\n\t\t\t} else if (Buffer.isBuffer(ev.data)) {\n\t\t\t\tbuf = new Uint8Array(ev.data);\n\t\t\t} else {\n\t\t\t\tthrow new Error(`expected binary data, got ${typeof ev.data}`);\n\t\t\t}\n\n\t\t\t// Parse message\n\t\t\tconst message = protocol.decodeToClient(buf);\n\n\t\t\t// Handle message\n\t\t\tif (message.tag === \"ToClientInit\") {\n\t\t\t\tconst init = message.val;\n\t\t\t\tthis.runnerId = init.runnerId;\n\n\t\t\t\t// Store the runner lost threshold from metadata\n\t\t\t\tthis.#runnerLostThreshold = init.metadata?.runnerLostThreshold\n\t\t\t\t\t? Number(init.metadata.runnerLostThreshold)\n\t\t\t\t\t: undefined;\n\n\t\t\t\tlogger()?.info({\n\t\t\t\t\tmsg: \"received init\",\n\t\t\t\t\trunnerId: init.runnerId,\n\t\t\t\t\tlastEventIdx: init.lastEventIdx,\n\t\t\t\t\trunnerLostThreshold: this.#runnerLostThreshold,\n\t\t\t\t});\n\n\t\t\t\t// Resend events that haven't been acknowledged\n\t\t\t\tthis.#resendUnacknowledgedEvents(init.lastEventIdx);\n\n\t\t\t\tthis.#config.onConnected();\n\t\t\t} else if (message.tag === \"ToClientCommands\") {\n\t\t\t\tconst commands = message.val;\n\t\t\t\tthis.#handleCommands(commands);\n\t\t\t} else if (message.tag === \"ToClientAckEvents\") {\n\t\t\t\tthrow new Error(\"TODO\");\n\t\t\t} else if (message.tag === \"ToClientKvResponse\") {\n\t\t\t\tconst kvResponse = message.val;\n\t\t\t\tthis.#handleKvResponse(kvResponse);\n\t\t\t} else if (message.tag === \"ToClientTunnelMessage\") {\n\t\t\t\tthis.#tunnel?.handleTunnelMessage(message.val);\n\t\t\t} else if (message.tag === \"ToClientClose\") {\n\t\t\t\t// TODO: Close ws\n\t\t\t} else {\n\t\t\t\tunreachable(message);\n\t\t\t}\n\t\t});\n\n\t\tws.addEventListener(\"error\", (ev) => {\n\t\t\tlogger()?.error(\"WebSocket error:\", ev.error);\n\t\t});\n\n\t\tws.addEventListener(\"close\", (ev) => {\n\t\t\tlogger()?.info({\n\t\t\t\tmsg: \"connection closed\",\n\t\t\t\tcode: ev.code,\n\t\t\t\treason: ev.reason.toString(),\n\t\t\t});\n\n\t\t\tthis.#config.onDisconnected();\n\n\t\t\t// Clear ping loop on close\n\t\t\tif (this.#pingLoop) {\n\t\t\t\tclearInterval(this.#pingLoop);\n\t\t\t\tthis.#pingLoop = undefined;\n\t\t\t}\n\n\t\t\t// Clear ack interval on close\n\t\t\tif (this.#ackInterval) {\n\t\t\t\tclearInterval(this.#ackInterval);\n\t\t\t\tthis.#ackInterval = undefined;\n\t\t\t}\n\n\t\t\tif (!this.#shutdown) {\n\t\t\t\t// Start runner lost timeout if we have a threshold and are not shutting down\n\t\t\t\tif (\n\t\t\t\t\tthis.#runnerLostThreshold &&\n\t\t\t\t\tthis.#runnerLostThreshold > 0\n\t\t\t\t) {\n\t\t\t\t\tlogger()?.info({\n\t\t\t\t\t\tmsg: \"starting runner lost timeout\",\n\t\t\t\t\t\tseconds: this.#runnerLostThreshold / 1000,\n\t\t\t\t\t});\n\t\t\t\t\tthis.#runnerLostTimeout = setTimeout(() => {\n\t\t\t\t\t\tthis.#stopAllActors();\n\t\t\t\t\t}, this.#runnerLostThreshold);\n\t\t\t\t}\n\n\t\t\t\t// Attempt to reconnect if not stopped\n\t\t\t\tthis.#scheduleReconnect();\n\t\t\t}\n\t\t});\n\t}\n\n\t#handleCommands(commands: protocol.ToClientCommands) {\n\t\tlogger()?.info({\n\t\t\tmsg: \"received commands\",\n\t\t\tcommandCount: commands.length,\n\t\t});\n\n\t\tfor (const commandWrapper of commands) {\n\t\t\tlogger()?.info({ msg: \"received command\", commandWrapper });\n\t\t\tif (commandWrapper.inner.tag === \"CommandStartActor\") {\n\t\t\t\tthis.#handleCommandStartActor(commandWrapper);\n\t\t\t} else if (commandWrapper.inner.tag === \"CommandStopActor\") {\n\t\t\t\tthis.#handleCommandStopActor(commandWrapper);\n\t\t\t} else {\n\t\t\t\tunreachable(commandWrapper.inner);\n\t\t\t}\n\n\t\t\tthis.#lastCommandIdx = Number(commandWrapper.index);\n\t\t}\n\t}\n\n\t#handleCommandStartActor(commandWrapper: protocol.CommandWrapper) {\n\t\tconst startCommand = commandWrapper.inner\n\t\t\t.val as protocol.CommandStartActor;\n\n\t\tconst actorId = startCommand.actorId;\n\t\tconst generation = startCommand.generation;\n\t\tconst config = startCommand.config;\n\n\t\tconst actorConfig: ActorConfig = {\n\t\t\tname: config.name,\n\t\t\tkey: config.key,\n\t\t\tcreateTs: config.createTs,\n\t\t\tinput: config.input ? new Uint8Array(config.input) : null,\n\t\t};\n\n\t\tconst instance: ActorInstance = {\n\t\t\tactorId,\n\t\t\tgeneration,\n\t\t\tconfig: actorConfig,\n\t\t\trequests: new Set(),\n\t\t\twebSockets: new Set(),\n\t\t};\n\n\t\tthis.#actors.set(actorId, instance);\n\n\t\tthis.#sendActorStateUpdate(actorId, generation, \"running\");\n\n\t\t// TODO: Add timeout to onActorStart\n\t\t// Call onActorStart asynchronously and handle errors\n\t\tthis.#config\n\t\t\t.onActorStart(actorId, generation, actorConfig)\n\t\t\t.catch((err) => {\n\t\t\t\tlogger()?.error({\n\t\t\t\t\tmsg: \"error in onactorstart for actor\",\n\t\t\t\t\tactorId,\n\t\t\t\t\terr,\n\t\t\t\t});\n\n\t\t\t\t// TODO: Mark as crashed\n\t\t\t\t// Send stopped state update if start failed\n\t\t\t\tthis.stopActor(actorId, generation);\n\t\t\t});\n\t}\n\n\t#handleCommandStopActor(commandWrapper: protocol.CommandWrapper) {\n\t\tconst stopCommand = commandWrapper.inner\n\t\t\t.val as protocol.CommandStopActor;\n\n\t\tconst actorId = stopCommand.actorId;\n\t\tconst generation = stopCommand.generation;\n\n\t\tthis.stopActor(actorId, generation);\n\t}\n\n\t#sendActorIntent(\n\t\tactorId: string,\n\t\tgeneration: number,\n\t\tintentType: \"sleep\" | \"stop\",\n\t) {\n\t\tif (this.#shutdown) {\n\t\t\tlogger()?.warn(\"Runner is shut down, cannot send actor intent\");\n\t\t\treturn;\n\t\t}\n\t\tlet actorIntent: protocol.ActorIntent;\n\n\t\tif (intentType === \"sleep\") {\n\t\t\tactorIntent = { tag: \"ActorIntentSleep\", val: null };\n\t\t} else if (intentType === \"stop\") {\n\t\t\tactorIntent = {\n\t\t\t\ttag: \"ActorIntentStop\",\n\t\t\t\tval: null,\n\t\t\t};\n\t\t} else {\n\t\t\tunreachable(intentType);\n\t\t}\n\n\t\tconst intentEvent: protocol.EventActorIntent = {\n\t\t\tactorId,\n\t\t\tgeneration,\n\t\t\tintent: actorIntent,\n\t\t};\n\n\t\tconst eventIndex = this.#nextEventIdx++;\n\t\tconst eventWrapper: protocol.EventWrapper = {\n\t\t\tindex: eventIndex,\n\t\t\tinner: {\n\t\t\t\ttag: \"EventActorIntent\",\n\t\t\t\tval: intentEvent,\n\t\t\t},\n\t\t};\n\n\t\t// Store event in history for potential resending\n\t\tthis.#eventHistory.push({\n\t\t\tevent: eventWrapper,\n\t\t\ttimestamp: Date.now(),\n\t\t});\n\n\t\tlogger()?.info({\n\t\t\tmsg: \"sending event to server\",\n\t\t\tindex: eventWrapper.index,\n\t\t\ttag: eventWrapper.inner.tag,\n\t\t\tval: eventWrapper.inner.val,\n\t\t});\n\n\t\tthis.__sendToServer({\n\t\t\ttag: \"ToServerEvents\",\n\t\t\tval: [eventWrapper],\n\t\t});\n\t}\n\n\t#sendActorStateUpdate(\n\t\tactorId: string,\n\t\tgeneration: number,\n\t\tstateType: \"running\" | \"stopped\",\n\t) {\n\t\tif (this.#shutdown) {\n\t\t\tlogger()?.warn(\n\t\t\t\t\"Runner is shut down, cannot send actor state update\",\n\t\t\t);\n\t\t\treturn;\n\t\t}\n\t\tlet actorState: protocol.ActorState;\n\n\t\tif (stateType === \"running\") {\n\t\t\tactorState = { tag: \"ActorStateRunning\", val: null };\n\t\t} else if (stateType === \"stopped\") {\n\t\t\tactorState = {\n\t\t\t\ttag: \"ActorStateStopped\",\n\t\t\t\tval: {\n\t\t\t\t\tcode: protocol.StopCode.Ok,\n\t\t\t\t\tmessage: \"hello\",\n\t\t\t\t},\n\t\t\t};\n\t\t} else {\n\t\t\tunreachable(stateType);\n\t\t}\n\n\t\tconst stateUpdateEvent: protocol.EventActorStateUpdate = {\n\t\t\tactorId,\n\t\t\tgeneration,\n\t\t\tstate: actorState,\n\t\t};\n\n\t\tconst eventIndex = this.#nextEventIdx++;\n\t\tconst eventWrapper: protocol.EventWrapper = {\n\t\t\tindex: eventIndex,\n\t\t\tinner: {\n\t\t\t\ttag: \"EventActorStateUpdate\",\n\t\t\t\tval: stateUpdateEvent,\n\t\t\t},\n\t\t};\n\n\t\t// Store event in history for potential resending\n\t\tthis.#eventHistory.push({\n\t\t\tevent: eventWrapper,\n\t\t\ttimestamp: Date.now(),\n\t\t});\n\n\t\tlogger()?.info({\n\t\t\tmsg: \"sending event to server\",\n\t\t\tindex: eventWrapper.index,\n\t\t\ttag: eventWrapper.inner.tag,\n\t\t\tval: eventWrapper.inner.val,\n\t\t});\n\n\t\tthis.__sendToServer({\n\t\t\ttag: \"ToServerEvents\",\n\t\t\tval: [eventWrapper],\n\t\t});\n\t}\n\n\t#sendCommandAcknowledgment() {\n\t\tif (this.#shutdown) {\n\t\t\tlogger()?.warn(\n\t\t\t\t\"Runner is shut down, cannot send command acknowledgment\",\n\t\t\t);\n\t\t\treturn;\n\t\t}\n\n\t\tif (this.#lastCommandIdx < 0) {\n\t\t\t// No commands received yet, nothing to acknowledge\n\t\t\treturn;\n\t\t}\n\n\t\t//logger()?.log(\"Sending command acknowledgment\", this.#lastCommandIdx);\n\n\t\tthis.__sendToServer({\n\t\t\ttag: \"ToServerAckCommands\",\n\t\t\tval: {\n\t\t\t\tlastCommandIdx: BigInt(this.#lastCommandIdx),\n\t\t\t},\n\t\t});\n\t}\n\n\t#handleKvResponse(response: protocol.ToClientKvResponse) {\n\t\tconst requestId = response.requestId;\n\t\tconst request = this.#kvRequests.get(requestId);\n\n\t\tif (!request) {\n\t\t\tconst msg = \"received kv response for unknown request id\";\n\t\t\tif (logger()) {\n\t\t\t\tlogger()?.error({ msg, requestId });\n\t\t\t} else {\n\t\t\t\tlogger()?.error({ msg, requestId });\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\tthis.#kvRequests.delete(requestId);\n\n\t\tif (response.data.tag === \"KvErrorResponse\") {\n\t\t\trequest.reject(\n\t\t\t\tnew Error(response.data.val.message || \"Unknown KV error\"),\n\t\t\t);\n\t\t} else {\n\t\t\trequest.resolve(response.data.val);\n\t\t}\n\t}\n\n\t#parseGetResponseSimple(\n\t\tresponse: protocol.KvGetResponse,\n\t\trequestedKeys: Uint8Array[],\n\t): (Uint8Array | null)[] {\n\t\t// Parse the response keys and values\n\t\tconst responseKeys: Uint8Array[] = [];\n\t\tconst responseValues: Uint8Array[] = [];\n\n\t\tfor (const key of response.keys) {\n\t\t\tresponseKeys.push(new Uint8Array(key));\n\t\t}\n\n\t\tfor (const value of response.values) {\n\t\t\tresponseValues.push(new Uint8Array(value));\n\t\t}\n\n\t\t// Map response back to requested key order\n\t\tconst result: (Uint8Array | null)[] = [];\n\t\tfor (const requestedKey of requestedKeys) {\n\t\t\tlet found = false;\n\t\t\tfor (let i = 0; i < responseKeys.length; i++) {\n\t\t\t\tif (this.#keysEqual(requestedKey, responseKeys[i])) {\n\t\t\t\t\tresult.push(responseValues[i]);\n\t\t\t\t\tfound = true;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (!found) {\n\t\t\t\tresult.push(null);\n\t\t\t}\n\t\t}\n\n\t\treturn result;\n\t}\n\n\t#keysEqual(key1: Uint8Array, key2: Uint8Array): boolean {\n\t\tif (key1.length !== key2.length) return false;\n\t\tfor (let i = 0; i < key1.length; i++) {\n\t\t\tif (key1[i] !== key2[i]) return false;\n\t\t}\n\t\treturn true;\n\t}\n\n\t//#parseGetResponse(response: protocol.KvGetResponse) {\n\t//\tconst keys: string[] = [];\n\t//\tconst values: Uint8Array[] = [];\n\t//\tconst metadata: { version: Uint8Array; createTs: bigint }[] = [];\n\t//\n\t//\tfor (const key of response.keys) {\n\t//\t\tkeys.push(new TextDecoder().decode(key));\n\t//\t}\n\t//\n\t//\tfor (const value of response.values) {\n\t//\t\tvalues.push(new Uint8Array(value));\n\t//\t}\n\t//\n\t//\tfor (const meta of response.metadata) {\n\t//\t\tmetadata.push({\n\t//\t\t\tversion: new Uint8Array(meta.version),\n\t//\t\t\tcreateTs: meta.createTs,\n\t//\t\t});\n\t//\t}\n\t//\n\t//\treturn { keys, values, metadata };\n\t//}\n\n\t#parseListResponseSimple(\n\t\tresponse: protocol.KvListResponse,\n\t): [Uint8Array, Uint8Array][] {\n\t\tconst result: [Uint8Array, Uint8Array][] = [];\n\n\t\tfor (let i = 0; i < response.keys.length; i++) {\n\t\t\tconst key = response.keys[i];\n\t\t\tconst value = response.values[i];\n\n\t\t\tif (key && value) {\n\t\t\t\tconst keyBytes = new Uint8Array(key);\n\t\t\t\tconst valueBytes = new Uint8Array(value);\n\t\t\t\tresult.push([keyBytes, valueBytes]);\n\t\t\t}\n\t\t}\n\n\t\treturn result;\n\t}\n\n\t//#parseListResponse(response: protocol.KvListResponse) {\n\t//\tconst keys: string[] = [];\n\t//\tconst values: Uint8Array[] = [];\n\t//\tconst metadata: { version: Uint8Array; createTs: bigint }[] = [];\n\t//\n\t//\tfor (const key of response.keys) {\n\t//\t\tkeys.push(new TextDecoder().decode(key));\n\t//\t}\n\t//\n\t//\tfor (const value of response.values) {\n\t//\t\tvalues.push(new Uint8Array(value));\n\t//\t}\n\t//\n\t//\tfor (const meta of response.metadata) {\n\t//\t\tmetadata.push({\n\t//\t\t\tversion: new Uint8Array(meta.version),\n\t//\t\t\tcreateTs: meta.createTs,\n\t//\t\t});\n\t//\t}\n\t//\n\t//\treturn { keys, values, metadata };\n\t//}\n\n\t// MARK: KV Operations\n\tasync kvGet(\n\t\tactorId: string,\n\t\tkeys: Uint8Array[],\n\t): Promise<(Uint8Array | null)[]> {\n\t\tconst kvKeys: protocol.KvKey[] = keys.map(\n\t\t\t(key) =>\n\t\t\t\tkey.buffer.slice(\n\t\t\t\t\tkey.byteOffset,\n\t\t\t\t\tkey.byteOffset + key.byteLength,\n\t\t\t\t) as ArrayBuffer,\n\t\t);\n\n\t\tconst requestData: protocol.KvRequestData = {\n\t\t\ttag: \"KvGetRequest\",\n\t\t\tval: { keys: kvKeys },\n\t\t};\n\n\t\tconst response = await this.#sendKvRequest(actorId, requestData);\n\t\treturn this.#parseGetResponseSimple(response, keys);\n\t}\n\n\tasync kvListAll(\n\t\tactorId: string,\n\t\toptions?: KvListOptions,\n\t): Promise<[Uint8Array, Uint8Array][]> {\n\t\tconst requestData: protocol.KvRequestData = {\n\t\t\ttag: \"KvListRequest\",\n\t\t\tval: {\n\t\t\t\tquery: { tag: \"KvListAllQuery\", val: null },\n\t\t\t\treverse: options?.reverse || null,\n\t\t\t\tlimit:\n\t\t\t\t\toptions?.limit !== undefined ? BigInt(options.limit) : null,\n\t\t\t},\n\t\t};\n\n\t\tconst response = await this.#sendKvRequest(actorId, requestData);\n\t\treturn this.#parseListResponseSimple(response);\n\t}\n\n\tasync kvListRange(\n\t\tactorId: string,\n\t\tstart: Uint8Array,\n\t\tend: Uint8Array,\n\t\texclusive?: boolean,\n\t\toptions?: KvListOptions,\n\t): Promise<[Uint8Array, Uint8Array][]> {\n\t\tconst startKey: protocol.KvKey = start.buffer.slice(\n\t\t\tstart.byteOffset,\n\t\t\tstart.byteOffset + start.byteLength,\n\t\t) as ArrayBuffer;\n\t\tconst endKey: protocol.KvKey = end.buffer.slice(\n\t\t\tend.byteOffset,\n\t\t\tend.byteOffset + end.byteLength,\n\t\t) as ArrayBuffer;\n\n\t\tconst requestData: protocol.KvRequestData = {\n\t\t\ttag: \"KvListRequest\",\n\t\t\tval: {\n\t\t\t\tquery: {\n\t\t\t\t\ttag: \"KvListRangeQuery\",\n\t\t\t\t\tval: {\n\t\t\t\t\t\tstart: startKey,\n\t\t\t\t\t\tend: endKey,\n\t\t\t\t\t\texclusive: exclusive || false,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\treverse: options?.reverse || null,\n\t\t\t\tlimit:\n\t\t\t\t\toptions?.limit !== undefined ? BigInt(options.limit) : null,\n\t\t\t},\n\t\t};\n\n\t\tconst response = await this.#sendKvRequest(actorId, requestData);\n\t\treturn this.#parseListResponseSimple(response);\n\t}\n\n\tasync kvListPrefix(\n\t\tactorId: string,\n\t\tprefix: Uint8Array,\n\t\toptions?: KvListOptions,\n\t): Promise<[Uint8Array, Uint8Array][]> {\n\t\tconst prefixKey: protocol.KvKey = prefix.buffer.slice(\n\t\t\tprefix.byteOffset,\n\t\t\tprefix.byteOffset + prefix.byteLength,\n\t\t) as ArrayBuffer;\n\n\t\tconst requestData: protocol.KvRequestData = {\n\t\t\ttag: \"KvListRequest\",\n\t\t\tval: {\n\t\t\t\tquery: {\n\t\t\t\t\ttag: \"KvListPrefixQuery\",\n\t\t\t\t\tval: { key: prefixKey },\n\t\t\t\t},\n\t\t\t\treverse: options?.reverse || null,\n\t\t\t\tlimit:\n\t\t\t\t\toptions?.limit !== undefined ? BigInt(options.limit) : null,\n\t\t\t},\n\t\t};\n\n\t\tconst response = await this.#sendKvRequest(actorId, requestData);\n\t\treturn this.#parseListResponseSimple(response);\n\t}\n\n\tasync kvPut(\n\t\tactorId: string,\n\t\tentries: [Uint8Array, Uint8Array][],\n\t): Promise<void> {\n\t\tconst keys: protocol.KvKey[] = entries.map(\n\t\t\t([key, _value]) =>\n\t\t\t\tkey.buffer.slice(\n\t\t\t\t\tkey.byteOffset,\n\t\t\t\t\tkey.byteOffset + key.byteLength,\n\t\t\t\t) as ArrayBuffer,\n\t\t);\n\t\tconst values: protocol.KvValue[] = entries.map(\n\t\t\t([_key, value]) =>\n\t\t\t\tvalue.buffer.slice(\n\t\t\t\t\tvalue.byteOffset,\n\t\t\t\t\tvalue.byteOffset + value.byteLength,\n\t\t\t\t) as ArrayBuffer,\n\t\t);\n\n\t\tconst requestData: protocol.KvRequestData = {\n\t\t\ttag: \"KvPutRequest\",\n\t\t\tval: { keys, values },\n\t\t};\n\n\t\tawait this.#sendKvRequest(actorId, requestData);\n\t}\n\n\tasync kvDelete(actorId: string, keys: Uint8Array[]): Promise<void> {\n\t\tconst kvKeys: protocol.KvKey[] = keys.map(\n\t\t\t(key) =>\n\t\t\t\tkey.buffer.slice(\n\t\t\t\t\tkey.byteOffset,\n\t\t\t\t\tkey.byteOffset + key.byteLength,\n\t\t\t\t) as ArrayBuffer,\n\t\t);\n\n\t\tconst requestData: protocol.KvRequestData = {\n\t\t\ttag: \"KvDeleteRequest\",\n\t\t\tval: { keys: kvKeys },\n\t\t};\n\n\t\tawait this.#sendKvRequest(actorId, requestData);\n\t}\n\n\tasync kvDrop(actorId: string): Promise<void> {\n\t\tconst requestData: protocol.KvRequestData = {\n\t\t\ttag: \"KvDropRequest\",\n\t\t\tval: null,\n\t\t};\n\n\t\tawait this.#sendKvRequest(actorId, requestData);\n\t}\n\n\t// MARK: Alarm Operations\n\tsetAlarm(actorId: string, alarmTs: number | null, generation?: number) {\n\t\tconst actor = this.getActor(actorId, generation);\n\t\tif (!actor) return;\n\n\t\tif (this.#shutdown) {\n\t\t\tconsole.warn(\"Runner is shut down, cannot set alarm\");\n\t\t\treturn;\n\t\t}\n\n\t\tconst alarmEvent: protocol.EventActorSetAlarm = {\n\t\t\tactorId,\n\t\t\tgeneration: actor.generation,\n\t\t\talarmTs: alarmTs !== null ? BigInt(alarmTs) : null,\n\t\t};\n\n\t\tconst eventIndex = this.#nextEventIdx++;\n\t\tconst eventWrapper: protocol.EventWrapper = {\n\t\t\tindex: eventIndex,\n\t\t\tinner: {\n\t\t\t\ttag: \"EventActorSetAlarm\",\n\t\t\t\tval: alarmEvent,\n\t\t\t},\n\t\t};\n\n\t\t// Store event in history for potential resending\n\t\tthis.#eventHistory.push({\n\t\t\tevent: eventWrapper,\n\t\t\ttimestamp: Date.now(),\n\t\t});\n\n\t\tthis.__sendToServer({\n\t\t\ttag: \"ToServerEvents\",\n\t\t\tval: [eventWrapper],\n\t\t});\n\t}\n\n\tclearAlarm(actorId: string, generation?: number) {\n\t\tthis.setAlarm(actorId, null, generation);\n\t}\n\n\t#sendKvRequest(\n\t\tactorId: string,\n\t\trequestData: protocol.KvRequestData,\n\t): Promise<any> {\n\t\treturn new Promise((resolve, reject) => {\n\t\t\tif (this.#shutdown) {\n\t\t\t\treject(new Error(\"Runner is shut down\"));\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst requestId = this.#nextRequestId++;\n\t\t\tconst isConnected =\n\t\t\t\tthis.#pegboardWebSocket &&\n\t\t\t\tthis.#pegboardWebSocket.readyState === 1;\n\n\t\t\t// Store the request\n\t\t\tconst requestEntry = {\n\t\t\t\tactorId,\n\t\t\t\tdata: requestData,\n\t\t\t\tresolve,\n\t\t\t\treject,\n\t\t\t\tsent: false,\n\t\t\t\ttimestamp: Date.now(),\n\t\t\t};\n\n\t\t\tthis.#kvRequests.set(requestId, requestEntry);\n\n\t\t\tif (isConnected) {\n\t\t\t\t// Send immediately\n\t\t\t\tthis.#sendSingleKvRequest(requestId);\n\t\t\t}\n\t\t});\n\t}\n\n\t#sendSingleKvRequest(requestId: number) {\n\t\tconst request = this.#kvRequests.get(requestId);\n\t\tif (!request || request.sent) return;\n\n\t\ttry {\n\t\t\tconst kvRequest: protocol.ToServerKvRequest = {\n\t\t\t\tactorId: request.actorId,\n\t\t\t\trequestId,\n\t\t\t\tdata: request.data,\n\t\t\t};\n\n\t\t\tthis.__sendToServer({\n\t\t\t\ttag: \"ToServerKvRequest\",\n\t\t\t\tval: kvRequest,\n\t\t\t});\n\n\t\t\t// Mark as sent and update timestamp\n\t\t\trequest.sent = true;\n\t\t\trequest.timestamp = Date.now();\n\t\t} catch (error) {\n\t\t\tthis.#kvRequests.delete(requestId);\n\t\t\trequest.reject(error);\n\t\t}\n\t}\n\n\t#processUnsentKvRequests() {\n\t\tif (\n\t\t\t!this.#pegboardWebSocket ||\n\t\t\tthis.#pegboardWebSocket.readyState !== 1\n\t\t) {\n\t\t\treturn;\n\t\t}\n\n\t\tlet processedCount = 0;\n\t\tfor (const [requestId, request] of this.#kvRequests.entries()) {\n\t\t\tif (!request.sent) {\n\t\t\t\tthis.#sendSingleKvRequest(requestId);\n\t\t\t\tprocessedCount++;\n\t\t\t}\n\t\t}\n\n\t\tif (processedCount > 0) {\n\t\t\t//logger()?.log(`Processed ${processedCount} queued KV requests`);\n\t\t}\n\t}\n\n\t__webSocketReady(): boolean {\n\t\treturn this.#pegboardWebSocket\n\t\t\t? this.#pegboardWebSocket.readyState === 1\n\t\t\t: false;\n\t}\n\n\t__sendToServer(message: protocol.ToServer) {\n\t\tif (this.#shutdown) {\n\t\t\tlogger()?.warn(\n\t\t\t\t\"Runner is shut down, cannot send message to server\",\n\t\t\t);\n\t\t\treturn;\n\t\t}\n\n\t\tconst encoded = protocol.encodeToServer(message);\n\t\tif (\n\t\t\tthis.#pegboardWebSocket &&\n\t\t\tthis.#pegboardWebSocket.readyState === 1\n\t\t) {\n\t\t\tthis.#pegboardWebSocket.send(encoded);\n\t\t} else {\n\t\t\tlogger()?.error(\n\t\t\t\t\"WebSocket not available or not open for sending data\",\n\t\t\t);\n\t\t}\n\t}\n\n\t#scheduleReconnect() {\n\t\tif (this.#shutdown) {\n\t\t\t//logger()?.log(\"Runner is shut down, not attempting reconnect\");\n\t\t\treturn;\n\t\t}\n\n\t\tconst delay = calculateBackoff(this.#reconnectAttempt, {\n\t\t\tinitialDelay: 1000,\n\t\t\tmaxDelay: 30000,\n\t\t\tmultiplier: 2,\n\t\t\tjitter: true,\n\t\t});\n\n\t\t//logger()?.log(\n\t\t//\t`Scheduling reconnect attempt ${this.#reconnectAttempt + 1} in ${delay}ms`,\n\t\t//);\n\n\t\tthis.#reconnectTimeout = setTimeout(async () => {\n\t\t\tif (!this.#shutdown) {\n\t\t\t\tthis.#reconnectAttempt++;\n\t\t\t\t//logger()?.log(\n\t\t\t\t//\t`Attempting to reconnect (attempt ${this.#reconnectAttempt})...`,\n\t\t\t\t//);\n\t\t\t\tawait this.#openPegboardWebSocket();\n\t\t\t}\n\t\t}, delay);\n\t}\n\n\t#resendUnacknowledgedEvents(lastEventIdx: bigint) {\n\t\tconst eventsToResend = this.#eventHistory.filter(\n\t\t\t(item) => item.event.index > lastEventIdx,\n\t\t);\n\n\t\tif (eventsToResend.length === 0) return;\n\n\t\t//logger()?.log(\n\t\t//\t`Resending ${eventsToResend.length} unacknowledged events from index ${Number(lastEventIdx) + 1}`,\n\t\t//);\n\n\t\t// Resend events in batches\n\t\tconst events = eventsToResend.map((item) => item.event);\n\t\tthis.__sendToServer({\n\t\t\ttag: \"ToServerEvents\",\n\t\t\tval: events,\n\t\t});\n\t}\n\n\t// TODO(RVT-4986): Prune when server acks events instead of based on old events\n\t#pruneOldEvents() {\n\t\tconst fiveMinutesAgo = Date.now() - 5 * 60 * 1000;\n\t\tconst originalLength = this.#eventHistory.length;\n\n\t\t// Remove events older than 5 minutes\n\t\tthis.#eventHistory = this.#eventHistory.filter(\n\t\t\t(item) => item.timestamp > fiveMinutesAgo,\n\t\t);\n\n\t\tconst prunedCount = originalLength - this.#eventHistory.length;\n\t\tif (prunedCount > 0) {\n\t\t\t//logger()?.log(`Pruned ${prunedCount} old events from history`);\n\t\t}\n\t}\n\n\t#cleanupOldKvRequests() {\n\t\tconst thirtySecondsAgo = Date.now() - KV_EXPIRE;\n\t\tconst toDelete: number[] = [];\n\n\t\tfor (const [requestId, request] of this.#kvRequests.entries()) {\n\t\t\tif (request.timestamp < thirtySecondsAgo) {\n\t\t\t\trequest.reject(\n\t\t\t\t\tnew Error(\n\t\t\t\t\t\t\"KV request timed out waiting for WebSocket connection\",\n\t\t\t\t\t),\n\t\t\t\t);\n\t\t\t\ttoDelete.push(requestId);\n\t\t\t}\n\t\t}\n\n\t\tfor (const requestId of toDelete) {\n\t\t\tthis.#kvRequests.delete(requestId);\n\t\t}\n\n\t\tif (toDelete.length > 0) {\n\t\t\t//logger()?.log(`Cleaned up ${toDelete.length} expired KV requests`);\n\t\t}\n\t}\n}\n","export function unreachable(x: never): never {\n\tthrow `Unreachable: ${x}`;\n}\n\nexport interface BackoffOptions {\n\tinitialDelay?: number;\n\tmaxDelay?: number;\n\tmultiplier?: number;\n\tjitter?: boolean;\n}\n\nexport function calculateBackoff(\n\tattempt: number,\n\toptions: BackoffOptions = {},\n): number {\n\tconst {\n\t\tinitialDelay = 1000,\n\t\tmaxDelay = 30000,\n\t\tmultiplier = 2,\n\t\tjitter = true,\n\t} = options;\n\n\tlet delay = Math.min(initialDelay * Math.pow(multiplier, attempt), maxDelay);\n\n\tif (jitter) {\n\t\t// Add random jitter between 0% and 25% of the delay\n\t\tdelay = delay * (1 + Math.random() * 0.25);\n\t}\n\n\treturn Math.floor(delay);\n}\n","// WebSocket-like adapter for tunneled connections\n// Implements a subset of the WebSocket interface for compatibility with runner code\n\nimport { logger } from \"./log\";\n\nexport class WebSocketTunnelAdapter {\n\t#webSocketId: string;\n\t#readyState: number = 0; // CONNECTING\n\t#eventListeners: Map<string, Set<(event: any) => void>> = new Map();\n\t#onopen: ((this: any, ev: any) => any) | null = null;\n\t#onclose: ((this: any, ev: any) => any) | null = null;\n\t#onerror: ((this: any, ev: any) => any) | null = null;\n\t#onmessage: ((this: any, ev: any) => any) | null = null;\n\t#bufferedAmount = 0;\n\t#binaryType: \"nodebuffer\" | \"arraybuffer\" | \"blob\" = \"nodebuffer\";\n\t#extensions = \"\";\n\t#protocol = \"\";\n\t#url = \"\";\n\t#sendCallback: (data: ArrayBuffer | string, isBinary: boolean) => void;\n\t#closeCallback: (code?: number, reason?: string) => void;\n\t\n\t// Event buffering for events fired before listeners are attached\n\t#bufferedEvents: Array<{\n\t\ttype: string;\n\t\tevent: any;\n\t}> = [];\n\n\tconstructor(\n\t\twebSocketId: string,\n\t\tsendCallback: (data: ArrayBuffer | string, isBinary: boolean) => void,\n\t\tcloseCallback: (code?: number, reason?: string) => void\n\t) {\n\t\tthis.#webSocketId = webSocketId;\n\t\tthis.#sendCallback = sendCallback;\n\t\tthis.#closeCallback = closeCallback;\n\t}\n\n\tget readyState(): number {\n\t\treturn this.#readyState;\n\t}\n\n\tget bufferedAmount(): number {\n\t\treturn this.#bufferedAmount;\n\t}\n\n\tget binaryType(): string {\n\t\treturn this.#binaryType;\n\t}\n\n\tset binaryType(value: string) {\n\t\tif (value === \"nodebuffer\" || value === \"arraybuffer\" || value === \"blob\") {\n\t\t\tthis.#binaryType = value;\n\t\t}\n\t}\n\n\tget extensions(): string {\n\t\treturn this.#extensions;\n\t}\n\n\tget protocol(): string {\n\t\treturn this.#protocol;\n\t}\n\n\tget url(): string {\n\t\treturn this.#url;\n\t}\n\n\tget onopen(): ((this: any, ev: any) => any) | null {\n\t\treturn this.#onopen;\n\t}\n\n\tset onopen(value: ((this: any, ev: any) => any) | null) {\n\t\tthis.#onopen = value;\n\t\t// Flush any buffered open events when onopen is set\n\t\tif (value) {\n\t\t\tthis.#flushBufferedEvents(\"open\");\n\t\t}\n\t}\n\n\tget onclose(): ((this: any, ev: any) => any) | null {\n\t\treturn this.#onclose;\n\t}\n\n\tset onclose(value: ((this: any, ev: any) => any) | null) {\n\t\tthis.#onclose = value;\n\t\t// Flush any buffered close events when onclose is set\n\t\tif (value) {\n\t\t\tthis.#flushBufferedEvents(\"close\");\n\t\t}\n\t}\n\n\tget onerror(): ((this: any, ev: any) => any) | null {\n\t\treturn this.#onerror;\n\t}\n\n\tset onerror(value: ((this: any, ev: any) => any) | null) {\n\t\tthis.#onerror = value;\n\t\t// Flush any buffered error events when onerror is set\n\t\tif (value) {\n\t\t\tthis.#flushBufferedEvents(\"error\");\n\t\t}\n\t}\n\n\tget onmessage(): ((this: any, ev: any) => any) | null {\n\t\treturn this.#onmessage;\n\t}\n\n\tset onmessage(value: ((this: any, ev: any) => any) | null) {\n\t\tthis.#onmessage = value;\n\t\t// Flush any buffered message events when onmessage is set\n\t\tif (value) {\n\t\t\tthis.#flushBufferedEvents(\"message\");\n\t\t}\n\t}\n\n\tsend(data: string | ArrayBuffer | ArrayBufferView | Blob | Buffer): void {\n\t\tif (this.#readyState !== 1) { // OPEN\n\t\t\tthrow new Error(\"WebSocket is not open\");\n\t\t}\n\n\t\tlet isBinary = false;\n\t\tlet messageData: string | ArrayBuffer;\n\n\t\tif (typeof data === \"string\") {\n\t\t\tmessageData = data;\n\t\t} else if (data instanceof ArrayBuffer) {\n\t\t\tisBinary = true;\n\t\t\tmessageData = data;\n\t\t} else if (ArrayBuffer.isView(data)) {\n\t\t\tisBinary = true;\n\t\t\t// Convert ArrayBufferView to ArrayBuffer\n\t\t\tconst view = data as ArrayBufferView;\n\t\t\t// Check if it's a SharedArrayBuffer\n\t\t\tif (view.buffer instanceof SharedArrayBuffer) {\n\t\t\t\t// Copy SharedArrayBuffer to regular ArrayBuffer\n\t\t\t\tconst bytes = new Uint8Array(view.buffer, view.byteOffset, view.byteLength);\n\t\t\t\tmessageData = bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength) as unknown as ArrayBuffer;\n\t\t\t} else {\n\t\t\t\tmessageData = view.buffer.slice(\n\t\t\t\t\tview.byteOffset,\n\t\t\t\t\tview.byteOffset + view.byteLength\n\t\t\t\t) as ArrayBuffer;\n\t\t\t}\n\t\t} else if (data instanceof Blob) {\n\t\t\tthrow new Error(\"Blob sending not implemented in tunnel adapter\");\n\t\t} else if (typeof Buffer !== 'undefined' && Buffer.isBuffer(data)) {\n\t\t\tisBinary = true;\n\t\t\t// Convert Buffer to ArrayBuffer\n\t\t\tconst buf = data as Buffer;\n\t\t\t// Check if it's a SharedArrayBuffer\n\t\t\tif (buf.buffer instanceof SharedArrayBuffer) {\n\t\t\t\t// Copy SharedArrayBuffer to regular ArrayBuffer\n\t\t\t\tconst bytes = new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength);\n\t\t\t\tmessageData = bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength) as unknown as ArrayBuffer;\n\t\t\t} else {\n\t\t\t\tmessageData = buf.buffer.slice(\n\t\t\t\t\tbuf.byteOffset,\n\t\t\t\t\tbuf.byteOffset + buf.byteLength\n\t\t\t\t) as ArrayBuffer;\n\t\t\t}\n\t\t} else {\n\t\t\tthrow new Error(\"Invalid data type\");\n\t\t}\n\n\t\t// Send through tunnel\n\t\tthis.#sendCallback(messageData, isBinary);\n\t}\n\n\tclose(code?: number, reason?: string): void {\n\t\tif (\n\t\t\tthis.#readyState === 2 || // CLOSING\n\t\t\tthis.#readyState === 3 // CLOSED\n\t\t) {\n\t\t\treturn;\n\t\t}\n\n\t\tthis.#readyState = 2; // CLOSING\n\n\t\t// Send close through tunnel\n\t\tthis.#closeCallback(code, reason);\n\n\t\t// Update state and fire event\n\t\tthis.#readyState = 3; // CLOSED\n\t\t\n\t\tconst closeEvent = {\n\t\t\twasClean: true,\n\t\t\tcode: code || 1000,\n\t\t\treason: reason || \"\",\n\t\t\ttype: \"close\",\n\t\t\ttarget: this,\n\t\t};\n\t\t\n\t\tthis.#fireEvent(\"close\", closeEvent);\n\t}\n\n\taddEventListener(\n\t\ttype: string,\n\t\tlistener: (event: any) => void,\n\t\toptions?: boolean | any\n\t): void {\n\t\tif (typeof listener === \"function\") {\n\t\t\tlet listeners = this.#eventListeners.get(type);\n\t\t\tif (!listeners) {\n\t\t\t\tlisteners = new Set();\n\t\t\t\tthis.#eventListeners.set(type, listeners);\n\t\t\t}\n\t\t\tlisteners.add(listener);\n\n\t\t\t// Flush any buffered events for this type\n\t\t\tthis.#flushBufferedEvents(type);\n\t\t}\n\t}\n\n\tremoveEventListener(\n\t\ttype: string,\n\t\tlistener: (event: any) => void,\n\t\toptions?: boolean | any\n\t): void {\n\t\tif (typeof listener === \"function\") {\n\t\t\tconst listeners = this.#eventListeners.get(type);\n\t\t\tif (listeners) {\n\t\t\t\tlisteners.delete(listener);\n\t\t\t}\n\t\t}\n\t}\n\n\tdispatchEvent(event: any): boolean {\n\t\t// Simple implementation\n\t\treturn true;\n\t}\n\n\t#fireEvent(type: string, event: any): void {\n\t\t// Call all registered event listeners\n\t\tconst listeners = this.#eventListeners.get(type);\n\t\tlet hasListeners = false;\n\n\t\tif (listeners && listeners.size > 0) {\n\t\t\thasListeners = true;\n\t\t\tfor (const listener of listeners) {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tlistener.call(this, event);\n\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\tlogger()?.error({ msg: \"error in websocket event listener\", error, type });\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\n\t\t// Call the onX property if set\n\t\tswitch (type) {\n\t\t\tcase \"open\":\n\t\t\t\tif (this.#onopen) {\n\t\t\t\t\thasListeners = true;\n\t\t\t\t\ttry {\n\t\t\t\t\t\tthis.#onopen.call(this, event);\n\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\tlogger()?.error({ msg: \"error in onopen handler\", error });\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\tcase \"close\":\n\t\t\t\tif (this.#onclose) {\n\t\t\t\t\thasListeners = true;\n\t\t\t\t\ttry {\n\t\t\t\t\t\tthis.#onclose.call(this, event);\n\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\tlogger()?.error({ msg: \"error in onclose handler\", error });\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\tcase \"error\":\n\t\t\t\tif (this.#onerror) {\n\t\t\t\t\thasListeners = true;\n\t\t\t\t\ttry {\n\t\t\t\t\t\tthis.#onerror.call(this, event);\n\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\tlogger()?.error({ msg: \"error in onerror handler\", error });\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\tcase \"message\":\n\t\t\t\tif (this.#onmessage) {\n\t\t\t\t\thasListeners = true;\n\t\t\t\t\ttry {\n\t\t\t\t\t\tthis.#onmessage.call(this, event);\n\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\tlogger()?.error({ msg: \"error in onmessage handler\", error });\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t}\n\n\t\t// Buffer the event if no listeners are registered\n\t\tif (!hasListeners) {\n\t\t\tthis.#bufferedEvents.push({ type, event });\n\t\t}\n\t}\n\n\t#flushBufferedEvents(type: string): void {\n\t\tconst eventsToFlush = this.#bufferedEvents.filter(\n\t\t\t(buffered) => buffered.type === type\n\t\t);\n\t\tthis.#bufferedEvents = this.#bufferedEvents.filter(\n\t\t\t(buffered) => buffered.type !== type\n\t\t);\n\n\t\tfor (const { event } of eventsToFlush) {\n\t\t\t// Re-fire the event, which will now have listeners\n\t\t\tconst listeners = this.#eventListeners.get(type);\n\t\t\tif (listeners) {\n\t\t\t\tfor (const listener of listeners) {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tlistener.call(this, event);\n\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\tlogger()?.error({\n\t\t\t\t\t\t\tmsg: \"error in websocket event listener\",\n\t\t\t\t\t\t\terror,\n\t\t\t\t\t\t\ttype,\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Also call the onX handler if it exists\n\t\t\tswitch (type) {\n\t\t\t\tcase \"open\":\n\t\t\t\t\tif (this.#onopen) {\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tthis.#onopen.call(this, event);\n\t\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\t\tlogger()?.error({ msg: \"error in onopen handler\", error });\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"close\":\n\t\t\t\t\tif (this.#onclose) {\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tthis.#onclose.call(this, event);\n\t\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\t\tlogger()?.error({ msg: \"error in onclose handler\", error });\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"error\":\n\t\t\t\t\tif (this.#onerror) {\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tthis.#onerror.call(this, event);\n\t\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\t\tlogger()?.error({ msg: \"error in onerror handler\", error });\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"message\":\n\t\t\t\t\tif (this.#onmessage) {\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tthis.#onmessage.call(this, event);\n\t\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\t\tlogger()?.error({ msg: \"error in onmessage handler\", error });\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t}\n\n\t// Internal methods called by the Tunnel class\n\t_handleOpen(): void {\n\t\tif (this.#readyState !== 0) { // CONNECTING\n\t\t\treturn;\n\t\t}\n\n\t\tthis.#readyState = 1; // OPEN\n\t\t\n\t\tconst event = {\n\t\t\ttype: \"open\",\n\t\t\ttarget: this,\n\t\t};\n\t\t\n\t\tthis.#fireEvent(\"open\", event);\n\t}\n\n\t_handleMessage(data: string | Uint8Array, isBinary: boolean): void {\n\t\tif (this.#readyState !== 1) { // OPEN\n\t\t\treturn;\n\t\t}\n\n\t\tlet messageData: any;\n\t\t\n\t\tif (isBinary) {\n\t\t\t// Handle binary data based on binaryType\n\t\t\tif (this.#binaryType === \"nodebuffer\") {\n\t\t\t\t// Convert to Buffer for Node.js compatibility\n\t\t\t\tmessageData = Buffer.from(data as Uint8Array);\n\t\t\t} else if (this.#binaryType === \"arraybuffer\") {\n\t\t\t\t// Convert to ArrayBuffer\n\t\t\t\tif (data instanceof Uint8Array) {\n\t\t\t\t\tmessageData = data.buffer.slice(\n\t\t\t\t\t\tdata.byteOffset,\n\t\t\t\t\t\tdata.byteOffset + data.byteLength\n\t\t\t\t\t);\n\t\t\t\t} else {\n\t\t\t\t\tmessageData = data;\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\t// Blob type - not commonly used in Node.js\n\t\t\t\tthrow new Error(\"Blob binaryType not supported in tunnel adapter\");\n\t\t\t}\n\t\t} else {\n\t\t\tmessageData = data;\n\t\t}\n\n\t\tconst event = {\n\t\t\tdata: messageData,\n\t\t\ttype: \"message\",\n\t\t\ttarget: this,\n\t\t};\n\n\t\tthis.#fireEvent(\"message\", event);\n\t}\n\n\t_handleClose(code?: number, reason?: string): void {\n\t\tif (this.#readyState === 3) { // CLOSED\n\t\t\treturn;\n\t\t}\n\n\t\tthis.#readyState = 3; // CLOSED\n\n\t\tconst event = {\n\t\t\twasClean: true,\n\t\t\tcode: code || 1000,\n\t\t\treason: reason || \"\",\n\t\t\ttype: \"close\",\n\t\t\ttarget: this,\n\t\t};\n\n\t\tthis.#fireEvent(\"close\", event);\n\t}\n\n\t_handleError(error: Error): void {\n\t\tconst event = {\n\t\t\ttype: \"error\",\n\t\t\ttarget: this,\n\t\t\terror,\n\t\t};\n\n\t\tthis.#fireEvent(\"error\", event);\n\t}\n\n\t// WebSocket constants for compatibility\n\tstatic readonly CONNECTING = 0;\n\tstatic readonly OPEN = 1;\n\tstatic readonly CLOSING = 2;\n\tstatic readonly CLOSED = 3;\n\n\t// Instance constants\n\treadonly CONNECTING = 0;\n\treadonly OPEN = 1;\n\treadonly CLOSING = 2;\n\treadonly CLOSED = 3;\n\n\t// Additional methods for compatibility\n\tping(data?: any, mask?: boolean, cb?: (err: Error) => void): void {\n\t\t// Not implemented for tunnel - could be added if needed\n\t\tif (cb) cb(new Error(\"Ping not supported in tunnel adapter\"));\n\t}\n\n\tpong(data?: any, mask?: boolean, cb?: (err: Error) => void): void {\n\t\t// Not implemented for tunnel - could be added if needed\n\t\tif (cb) cb(new Error(\"Pong not supported in tunnel adapter\"));\n\t}\n\n\tterminate(): void {\n\t\t// Immediate close without close frame\n\t\tthis.#readyState = 3; // CLOSED\n\t\tthis.#closeCallback(1006, \"Abnormal Closure\");\n\t\t\n\t\tconst event = {\n\t\t\twasClean: false,\n\t\t\tcode: 1006,\n\t\t\treason: \"Abnormal Closure\",\n\t\t\ttype: \"close\",\n\t\t\ttarget: this,\n\t\t};\n\t\t\n\t\tthis.#fireEvent(\"close\", event);\n\t}\n}\n","import * as protocol from \"@rivetkit/engine-runner-protocol\";\nimport type { RequestId, MessageId } from \"@rivetkit/engine-runner-protocol\";\nimport { WebSocketTunnelAdapter } from \"./websocket-tunnel-adapter\";\nimport type { Runner, ActorInstance } from \"./mod\";\nimport { v4 as uuidv4 } from \"uuid\";\nimport { logger } from \"./log\";\nimport { unreachable } from \"./utils\";\n\nconst GC_INTERVAL = 60000; // 60 seconds\nconst MESSAGE_ACK_TIMEOUT = 5000; // 5 seconds\n\ninterface PendingRequest {\n\tresolve: (response: Response) => void;\n\treject: (error: Error) => void;\n\tstreamController?: ReadableStreamDefaultController<Uint8Array>;\n\tactorId?: string;\n}\n\ninterface PendingMessage {\n\tsentAt: number;\n\trequestIdStr: string;\n}\n\nexport class Tunnel {\n\t#runner: Runner;\n\n\t#actorPendingRequests: Map<string, PendingRequest> = new Map();\n\t#actorWebSockets: Map<string, WebSocketTunnelAdapter> = new Map();\n\n\t#pendingMessages: Map<string, PendingMessage> = new Map();\n\t#gcInterval?: NodeJS.Timeout;\n\n\tconstructor(runner: Runner) {\n\t\tthis.#runner = runner;\n\t}\n\n\tstart(): void {\n\t\tthis.#startGarbageCollector();\n\t}\n\n\tshutdown() {\n\t\tif (this.#gcInterval) {\n\t\t\tclearInterval(this.#gcInterval);\n\t\t\tthis.#gcInterval = undefined;\n\t\t}\n\n\t\t// TODO: Should we use unregisterActor instead\n\n\t\t// Reject all pending requests\n\t\tfor (const [_, request] of this.#actorPendingRequests) {\n\t\t\trequest.reject(new Error(\"Tunnel shutting down\"));\n\t\t}\n\t\tthis.#actorPendingRequests.clear();\n\n\t\t// Close all WebSockets\n\t\tfor (const [_, ws] of this.#actorWebSockets) {\n\t\t\tws.close();\n\t\t}\n\t\tthis.#actorWebSockets.clear();\n\t}\n\n\t#sendMessage(\n\t\trequestId: RequestId,\n\t\tmessageKind: protocol.ToServerTunnelMessageKind,\n\t) {\n\t\t// TODO: Switch this with runner WS\n\t\tif (!this.#runner.__webSocketReady()) {\n\t\t\tconsole.warn(\"Cannot send tunnel message, WebSocket not connected\");\n\t\t\treturn;\n\t\t}\n\n\t\t// Build message\n\t\tconst messageId = generateUuidBuffer();\n\n\t\tconst requestIdStr = bufferToString(requestId);\n\t\tthis.#pendingMessages.set(bufferToString(messageId), {\n\t\t\tsentAt: Date.now(),\n\t\t\trequestIdStr,\n\t\t});\n\n\t\t// Send message\n\t\tconst message: protocol.ToServer = {\n\t\t\ttag: \"ToServerTunnelMessage\",\n\t\t\tval: {\n\t\t\t\trequestId,\n\t\t\t\tmessageId,\n\t\t\t\tmessageKind,\n\t\t\t},\n\t\t};\n\t\tthis.#runner.__sendToServer(message);\n\t}\n\n\t#sendAck(requestId: RequestId, messageId: MessageId) {\n\t\tif (!this.#runner.__webSocketReady()) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst message: protocol.ToServer = {\n\t\t\ttag: \"ToServerTunnelMessage\",\n\t\t\tval: {\n\t\t\t\trequestId,\n\t\t\t\tmessageId,\n\t\t\t\tmessageKind: { tag: \"TunnelAck\", val: null },\n\t\t\t},\n\t\t};\n\n\t\tthis.#runner.__sendToServer(message);\n\t}\n\n\t#startGarbageCollector() {\n\t\tif (this.#gcInterval) {\n\t\t\tclearInterval(this.#gcInterval);\n\t\t}\n\n\t\tthis.#gcInterval = setInterval(() => {\n\t\t\tthis.#gc();\n\t\t}, GC_INTERVAL);\n\t}\n\n\t#gc() {\n\t\tconst now = Date.now();\n\t\tconst messagesToDelete: string[] = [];\n\n\t\tfor (const [messageId, pendingMessage] of this.#pendingMessages) {\n\t\t\t// Check if message is older than timeout\n\t\t\tif (now - pendingMessage.sentAt > MESSAGE_ACK_TIMEOUT) {\n\t\t\t\tmessagesToDelete.push(messageId);\n\n\t\t\t\tconst requestIdStr = pendingMessage.requestIdStr;\n\n\t\t\t\t// Check if this is an HTTP request\n\t\t\t\tconst pendingRequest =\n\t\t\t\t\tthis.#actorPendingRequests.get(requestIdStr);\n\t\t\t\tif (pendingRequest) {\n\t\t\t\t\t// Reject the pending HTTP request\n\t\t\t\t\tpendingRequest.reject(\n\t\t\t\t\t\tnew Error(\"Message acknowledgment timeout\"),\n\t\t\t\t\t);\n\n\t\t\t\t\t// Close stream controller if it exists\n\t\t\t\t\tif (pendingRequest.streamController) {\n\t\t\t\t\t\tpendingRequest.streamController.error(\n\t\t\t\t\t\t\tnew Error(\"Message acknowledgment timeout\"),\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\n\t\t\t\t\t// Clean up from actorPendingRequests map\n\t\t\t\t\tthis.#actorPendingRequests.delete(requestIdStr);\n\t\t\t\t}\n\n\t\t\t\t// Check if this is a WebSocket\n\t\t\t\tconst webSocket = this.#actorWebSockets.get(requestIdStr);\n\t\t\t\tif (webSocket) {\n\t\t\t\t\t// Close the WebSocket connection\n\t\t\t\t\twebSocket.close(1000, \"Message acknowledgment timeout\");\n\n\t\t\t\t\t// Clean up from actorWebSockets map\n\t\t\t\t\tthis.#actorWebSockets.delete(requestIdStr);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Remove timed out messages\n\t\tfor (const messageId of messagesToDelete) {\n\t\t\tthis.#pendingMessages.delete(messageId);\n\t\t\tconsole.warn(`Purged unacked message: ${messageId}`);\n\t\t}\n\t}\n\n\tunregisterActor(actor: ActorInstance) {\n\t\tconst actorId = actor.actorId;\n\n\t\t// Terminate all requests for this actor\n\t\tfor (const requestId of actor.requests) {\n\t\t\tconst pending = this.#actorPendingRequests.get(requestId);\n\t\t\tif (pending) {\n\t\t\t\tpending.reject(new Error(`Actor ${actorId} stopped`));\n\t\t\t\tthis.#actorPendingRequests.delete(requestId);\n\t\t\t}\n\t\t}\n\t\tactor.requests.clear();\n\n\t\t// Close all WebSockets for this actor\n\t\tfor (const webSocketId of actor.webSockets) {\n\t\t\tconst ws = this.#actorWebSockets.get(webSocketId);\n\t\t\tif (ws) {\n\t\t\t\tws.close(1000, \"Actor stopped\");\n\t\t\t\tthis.#actorWebSockets.delete(webSocketId);\n\t\t\t}\n\t\t}\n\t\tactor.webSockets.clear();\n\t}\n\n\tasync #fetch(actorId: string, request: Request): Promise<Response> {\n\t\t// Validate actor exists\n\t\tif (!this.#runner.hasActor(actorId)) {\n\t\t\tlogger()?.warn({\n\t\t\t\tmsg: \"ignoring request for unknown actor\",\n\t\t\t\tactorId,\n\t\t\t});\n\t\t\treturn new Response(\"Actor not found\", { status: 404 });\n\t\t}\n\n\t\tconst fetchHandler = this.#runner.config.fetch(actorId, request);\n\n\t\tif (!fetchHandler) {\n\t\t\treturn new Response(\"Not Implemented\", { status: 501 });\n\t\t}\n\n\t\treturn fetchHandler;\n\t}\n\n\tasync handleTunnelMessage(message: protocol.ToClientTunnelMessage) {\n\t\tif (message.messageKind.tag === \"TunnelAck\") {\n\t\t\t// Mark pending message as acknowledged and remove it\n\t\t\tconst msgIdStr = bufferToString(message.messageId);\n\t\t\tconst pending = this.#pendingMessages.get(msgIdStr);\n\t\t\tif (pending) {\n\t\t\t\tthis.#pendingMessages.delete(msgIdStr);\n\t\t\t}\n\t\t} else {\n\t\t\tthis.#sendAck(message.requestId, message.messageId);\n\t\t\tswitch (message.messageKind.tag) {\n\t\t\t\tcase \"ToClientRequestStart\":\n\t\t\t\t\tawait this.#handleRequestStart(\n\t\t\t\t\t\tmessage.requestId,\n\t\t\t\t\t\tmessage.messageKind.val,\n\t\t\t\t\t);\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"ToClientRequestChunk\":\n\t\t\t\t\tawait this.#handleRequestChunk(\n\t\t\t\t\t\tmessage.requestId,\n\t\t\t\t\t\tmessage.messageKind.val,\n\t\t\t\t\t);\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"ToClientRequestAbort\":\n\t\t\t\t\tawait this.#handleRequestAbort(message.requestId);\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"ToClientWebSocketOpen\":\n\t\t\t\t\tawait this.#handleWebSocketOpen(\n\t\t\t\t\t\tmessage.requestId,\n\t\t\t\t\t\tmessage.messageKind.val,\n\t\t\t\t\t);\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"ToClientWebSocketMessage\":\n\t\t\t\t\tawait this.#handleWebSocketMessage(\n\t\t\t\t\t\tmessage.requestId,\n\t\t\t\t\t\tmessage.messageKind.val,\n\t\t\t\t\t);\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"ToClientWebSocketClose\":\n\t\t\t\t\tawait this.#handleWebSocketClose(\n\t\t\t\t\t\tmessage.requestId,\n\t\t\t\t\t\tmessage.messageKind.val,\n\t\t\t\t\t);\n\t\t\t\t\tbreak;\n\t\t\t\tdefault:\n\t\t\t\t\tunreachable(message.messageKind);\n\t\t\t}\n\t\t}\n\t}\n\n\tasync #handleRequestStart(\n\t\trequestId: ArrayBuffer,\n\t\treq: protocol.ToClientRequestStart,\n\t) {\n\t\t// Track this request for the actor\n\t\tconst requestIdStr = bufferToString(requestId);\n\t\tconst actor = this.#runner.getActor(req.actorId);\n\t\tif (actor) {\n\t\t\tactor.requests.add(requestIdStr);\n\t\t}\n\n\t\ttry {\n\t\t\t// Convert headers map to Headers object\n\t\t\tconst headers = new Headers();\n\t\t\tfor (const [key, value] of req.headers) {\n\t\t\t\theaders.append(key, value);\n\t\t\t}\n\n\t\t\t// Create Request object\n\t\t\tconst request = new Request(`http://localhost${req.path}`, {\n\t\t\t\tmethod: req.method,\n\t\t\t\theaders,\n\t\t\t\tbody: req.body ? new Uint8Array(req.body) : undefined,\n\t\t\t});\n\n\t\t\t// Handle streaming request\n\t\t\tif (req.stream) {\n\t\t\t\t// Create a stream for the request body\n\t\t\t\tconst stream = new ReadableStream<Uint8Array>({\n\t\t\t\t\tstart: (controller) => {\n\t\t\t\t\t\t// Store controller for chunks\n\t\t\t\t\t\tconst existing =\n\t\t\t\t\t\t\tthis.#actorPendingRequests.get(requestIdStr);\n\t\t\t\t\t\tif (existing) {\n\t\t\t\t\t\t\texisting.streamController = controller;\n\t\t\t\t\t\t\texisting.actorId = req.actorId;\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tthis.#actorPendingRequests.set(requestIdStr, {\n\t\t\t\t\t\t\t\tresolve: () => {},\n\t\t\t\t\t\t\t\treject: () => {},\n\t\t\t\t\t\t\t\tstreamController: controller,\n\t\t\t\t\t\t\t\tactorId: req.actorId,\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\t\t\t\t});\n\n\t\t\t\t// Create request with streaming body\n\t\t\t\tconst streamingRequest = new Request(request, {\n\t\t\t\t\tbody: stream,\n\t\t\t\t\tduplex: \"half\",\n\t\t\t\t} as any);\n\n\t\t\t\t// Call fetch handler with validation\n\t\t\t\tconst response = await this.#fetch(\n\t\t\t\t\treq.actorId,\n\t\t\t\t\tstreamingRequest,\n\t\t\t\t);\n\t\t\t\tawait this.#sendResponse(requestId, response);\n\t\t\t} else {\n\t\t\t\t// Non-streaming request\n\t\t\t\tconst response = await this.#fetch(req.actorId, request);\n\t\t\t\tawait this.#sendResponse(requestId, response);\n\t\t\t}\n\t\t} catch (error) {\n\t\t\tlogger()?.error({ msg: \"error handling request\", error });\n\t\t\tthis.#sendResponseError(requestId, 500, \"Internal Server Error\");\n\t\t} finally {\n\t\t\t// Clean up request tracking\n\t\t\tconst actor = this.#runner.getActor(req.actorId);\n\t\t\tif (actor) {\n\t\t\t\tactor.requests.delete(requestIdStr);\n\t\t\t}\n\t\t}\n\t}\n\n\tasync #handleRequestChunk(\n\t\trequestId: ArrayBuffer,\n\t\tchunk: protocol.ToClientRequestChunk,\n\t) {\n\t\tconst requestIdStr = bufferToString(requestId);\n\t\tconst pending = this.#actorPendingRequests.get(requestIdStr);\n\t\tif (pending?.streamController) {\n\t\t\tpending.streamController.enqueue(new Uint8Array(chunk.body));\n\t\t\tif (chunk.finish) {\n\t\t\t\tpending.streamController.close();\n\t\t\t\tthis.#actorPendingRequests.delete(requestIdStr);\n\t\t\t}\n\t\t}\n\t}\n\n\tasync #handleRequestAbort(requestId: ArrayBuffer) {\n\t\tconst requestIdStr = bufferToString(requestId);\n\t\tconst pending = this.#actorPendingRequests.get(requestIdStr);\n\t\tif (pending?.streamController) {\n\t\t\tpending.streamController.error(new Error(\"Request aborted\"));\n\t\t}\n\t\tthis.#actorPendingRequests.delete(requestIdStr);\n\t}\n\n\tasync #sendResponse(requestId: ArrayBuffer, response: Response) {\n\t\t// Always treat responses as non-streaming for now\n\t\t// In the future, we could detect streaming responses based on:\n\t\t// - Transfer-Encoding: chunked\n\t\t// - Content-Type: text/event-stream\n\t\t// - Explicit stream flag from the handler\n\n\t\t// Read the body first to get the actual content\n\t\tconst body = response.body ? await response.arrayBuffer() : null;\n\n\t\t// Convert headers to map and add Content-Length if not present\n\t\tconst headers = new Map<string, string>();\n\t\tresponse.headers.forEach((value, key) => {\n\t\t\theaders.set(key, value);\n\t\t});\n\n\t\t// Add Content-Length header if we have a body and it's not already set\n\t\tif (body && !headers.has(\"content-length\")) {\n\t\t\theaders.set(\"content-length\", String(body.byteLength));\n\t\t}\n\n\t\t// Send as non-streaming response\n\t\tthis.#sendMessage(requestId, {\n\t\t\ttag: \"ToServerResponseStart\",\n\t\t\tval: {\n\t\t\t\tstatus: response.status as protocol.u16,\n\t\t\t\theaders,\n\t\t\t\tbody: body || null,\n\t\t\t\tstream: false,\n\t\t\t},\n\t\t});\n\t}\n\n\t#sendResponseError(\n\t\trequestId: ArrayBuffer,\n\t\tstatus: number,\n\t\tmessage: string,\n\t) {\n\t\tconst headers = new Map<string, string>();\n\t\theaders.set(\"content-type\", \"text/plain\");\n\n\t\tthis.#sendMessage(requestId, {\n\t\t\ttag: \"ToServerResponseStart\",\n\t\t\tval: {\n\t\t\t\tstatus: status as protocol.u16,\n\t\t\t\theaders,\n\t\t\t\tbody: new TextEncoder().encode(message).buffer as ArrayBuffer,\n\t\t\t\tstream: false,\n\t\t\t},\n\t\t});\n\t}\n\n\tasync #handleWebSocketOpen(\n\t\trequestId: ArrayBuffer,\n\t\topen: protocol.ToClientWebSocketOpen,\n\t) {\n\t\tconst webSocketId = bufferToString(requestId);\n\t\t// Validate actor exists\n\t\tconst actor = this.#runner.getActor(open.actorId);\n\t\tif (!actor) {\n\t\t\tlogger()?.warn({\n\t\t\t\tmsg: \"ignoring websocket for unknown actor\",\n\t\t\t\tactorId: open.actorId,\n\t\t\t});\n\t\t\t// Send close immediately\n\t\t\tthis.#sendMessage(requestId, {\n\t\t\t\ttag: \"ToServerWebSocketClose\",\n\t\t\t\tval: {\n\t\t\t\t\tcode: 1011,\n\t\t\t\t\treason: \"Actor not found\",\n\t\t\t\t},\n\t\t\t});\n\t\t\treturn;\n\t\t}\n\n\t\tconst websocketHandler = this.#runner.config.websocket;\n\n\t\tif (!websocketHandler) {\n\t\t\tconsole.error(\"No websocket handler configured for tunnel\");\n\t\t\tlogger()?.error({\n\t\t\t\tmsg: \"no websocket handler configured for tunnel\",\n\t\t\t});\n\t\t\t// Send close immediately\n\t\t\tthis.#sendMessage(requestId, {\n\t\t\t\ttag: \"ToServerWebSocketClose\",\n\t\t\t\tval: {\n\t\t\t\t\tcode: 1011,\n\t\t\t\t\treason: \"Not Implemented\",\n\t\t\t\t},\n\t\t\t});\n\t\t\treturn;\n\t\t}\n\n\t\t// Track this WebSocket for the actor\n\t\tif (actor) {\n\t\t\tactor.webSockets.add(webSocketId);\n\t\t}\n\n\t\ttry {\n\t\t\t// Create WebSocket adapter\n\t\t\tconst adapter = new WebSocketTunnelAdapter(\n\t\t\t\twebSocketId,\n\t\t\t\t(data: ArrayBuffer | string, isBinary: boolean) => {\n\t\t\t\t\t// Send message through tunnel\n\t\t\t\t\tconst dataBuffer =\n\t\t\t\t\t\ttypeof data === \"string\"\n\t\t\t\t\t\t\t? (new TextEncoder().encode(data)\n\t\t\t\t\t\t\t\t\t.buffer as ArrayBuffer)\n\t\t\t\t\t\t\t: data;\n\n\t\t\t\t\tthis.#sendMessage(requestId, {\n\t\t\t\t\t\ttag: \"ToServerWebSocketMessage\",\n\t\t\t\t\t\tval: {\n\t\t\t\t\t\t\tdata: dataBuffer,\n\t\t\t\t\t\t\tbinary: isBinary,\n\t\t\t\t\t\t},\n\t\t\t\t\t});\n\t\t\t\t},\n\t\t\t\t(code?: number, reason?: string) => {\n\t\t\t\t\t// Send close through tunnel\n\t\t\t\t\tthis.#sendMessage(requestId, {\n\t\t\t\t\t\ttag: \"ToServerWebSocketClose\",\n\t\t\t\t\t\tval: {\n\t\t\t\t\t\t\tcode: code || null,\n\t\t\t\t\t\t\treason: reason || null,\n\t\t\t\t\t\t},\n\t\t\t\t\t});\n\n\t\t\t\t\t// Remove from map\n\t\t\t\t\tthis.#actorWebSockets.delete(webSocketId);\n\n\t\t\t\t\t// Clean up actor tracking\n\t\t\t\t\tif (actor) {\n\t\t\t\t\t\tactor.webSockets.delete(webSocketId);\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t);\n\n\t\t\t// Store adapter\n\t\t\tthis.#actorWebSockets.set(webSocketId, adapter);\n\n\t\t\t// Send open confirmation\n\t\t\tthis.#sendMessage(requestId, {\n\t\t\t\ttag: \"ToServerWebSocketOpen\",\n\t\t\t\tval: null,\n\t\t\t});\n\n\t\t\t// Notify adapter that connection is open\n\t\t\tadapter._handleOpen();\n\n\t\t\t// Create a minimal request object for the websocket handler\n\t\t\t// Include original headers from the open message\n\t\t\tconst headerInit: Record<string, string> = {};\n\t\t\tif (open.headers) {\n\t\t\t\tfor (const [k, v] of open.headers as ReadonlyMap<\n\t\t\t\t\tstring,\n\t\t\t\t\tstring\n\t\t\t\t>) {\n\t\t\t\t\theaderInit[k] = v;\n\t\t\t\t}\n\t\t\t}\n\t\t\t// Ensure websocket upgrade headers are present\n\t\t\theaderInit[\"Upgrade\"] = \"websocket\";\n\t\t\theaderInit[\"Connection\"] = \"Upgrade\";\n\n\t\t\tconst request = new Request(`http://localhost${open.path}`, {\n\t\t\t\tmethod: \"GET\",\n\t\t\t\theaders: headerInit,\n\t\t\t});\n\n\t\t\t// Call websocket handler\n\t\t\tawait websocketHandler(open.actorId, adapter, request);\n\t\t} catch (error) {\n\t\t\tlogger()?.error({ msg: \"error handling websocket open\", error });\n\t\t\t// Send close on error\n\t\t\tthis.#sendMessage(requestId, {\n\t\t\t\ttag: \"ToServerWebSocketClose\",\n\t\t\t\tval: {\n\t\t\t\t\tcode: 1011,\n\t\t\t\t\treason: \"Server Error\",\n\t\t\t\t},\n\t\t\t});\n\n\t\t\tthis.#actorWebSockets.delete(webSocketId);\n\n\t\t\t// Clean up actor tracking\n\t\t\tif (actor) {\n\t\t\t\tactor.webSockets.delete(webSocketId);\n\t\t\t}\n\t\t}\n\t}\n\n\tasync #handleWebSocketMessage(\n\t\trequestId: ArrayBuffer,\n\t\tmsg: protocol.ToServerWebSocketMessage,\n\t) {\n\t\tconst webSocketId = bufferToString(requestId);\n\t\tconst adapter = this.#actorWebSockets.get(webSocketId);\n\t\tif (adapter) {\n\t\t\tconst data = msg.binary\n\t\t\t\t? new Uint8Array(msg.data)\n\t\t\t\t: new TextDecoder().decode(new Uint8Array(msg.data));\n\n\t\t\tadapter._handleMessage(data, msg.binary);\n\t\t}\n\t}\n\n\tasync #handleWebSocketClose(\n\t\trequestId: ArrayBuffer,\n\t\tclose: protocol.ToServerWebSocketClose,\n\t) {\n\t\tconst webSocketId = bufferToString(requestId);\n\t\tconst adapter = this.#actorWebSockets.get(webSocketId);\n\t\tif (adapter) {\n\t\t\tadapter._handleClose(\n\t\t\t\tclose.code || undefined,\n\t\t\t\tclose.reason || undefined,\n\t\t\t);\n\t\t\tthis.#actorWebSockets.delete(webSocketId);\n\t\t}\n\t}\n}\n\n/** Converts a buffer to a string. Used for storing strings in a lookup map. */\nfunction bufferToString(buffer: ArrayBuffer): string {\n\treturn Buffer.from(buffer).toString(\"base64\");\n}\n\n/** Generates a UUID as bytes. */\nfunction generateUuidBuffer(): ArrayBuffer {\n\tconst buffer = new Uint8Array(16);\n\tuuidv4(undefined, buffer);\n\treturn buffer.buffer;\n}\n"]}
|
package/dist/mod.d.cts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import * as protocol from '@rivetkit/engine-runner-protocol';
|
|
1
2
|
import { Logger } from 'pino';
|
|
2
3
|
|
|
3
4
|
interface ActorInstance {
|
|
@@ -62,6 +63,8 @@ declare class Runner {
|
|
|
62
63
|
kvDrop(actorId: string): Promise<void>;
|
|
63
64
|
setAlarm(actorId: string, alarmTs: number | null, generation?: number): void;
|
|
64
65
|
clearAlarm(actorId: string, generation?: number): void;
|
|
66
|
+
__webSocketReady(): boolean;
|
|
67
|
+
__sendToServer(message: protocol.ToServer): void;
|
|
65
68
|
}
|
|
66
69
|
|
|
67
70
|
export { type ActorConfig, type ActorInstance, type KvListOptions, Runner, type RunnerConfig };
|
package/dist/mod.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import * as protocol from '@rivetkit/engine-runner-protocol';
|
|
1
2
|
import { Logger } from 'pino';
|
|
2
3
|
|
|
3
4
|
interface ActorInstance {
|
|
@@ -62,6 +63,8 @@ declare class Runner {
|
|
|
62
63
|
kvDrop(actorId: string): Promise<void>;
|
|
63
64
|
setAlarm(actorId: string, alarmTs: number | null, generation?: number): void;
|
|
64
65
|
clearAlarm(actorId: string, generation?: number): void;
|
|
66
|
+
__webSocketReady(): boolean;
|
|
67
|
+
__sendToServer(message: protocol.ToServer): void;
|
|
65
68
|
}
|
|
66
69
|
|
|
67
70
|
export { type ActorConfig, type ActorInstance, type KvListOptions, Runner, type RunnerConfig };
|