1import * as Cloudflare from "alchemy/Cloudflare";
2import * as Effect from "effect/Effect";
3import * as HttpClientRequest from "effect/http/HttpClientRequest";
4import { Sandbox } from "./Sandbox.ts";
5
6export default class Agent extends Cloudflare.DurableObject<Agent>()(
7 "Agents",
8 Effect.gen(function* () {
9 const container = yield* Sandbox;
10 const state = yield* Cloudflare.DurableObjectState;
11
12 return Effect.gen(function* () {
13 const sessions = new Map<string, Cloudflare.WebSocket>();
14
15 for (const socket of yield* state.getWebSockets()) {
16 const session = socket.deserializeAttachment<{ id: string }>();
17 if (session) {
18 sessions.set(session.id, socket);
19 }
20 }
21
22 return {
23 exec: (command: string) => container.exec(command).pipe(Effect.orDie),
24 hello: () =>
25 Effect.gen(function* () {
26 const { fetch } = yield* container.getTcpPort(3000);
27 const response = yield* fetch(
28 HttpClientRequest.get("http://container/"),
29 );
30 return yield* response.text;
31 }).pipe(Effect.orDie),
32 increment: () =>
33 Effect.gen(function* () {
34 const { fetch } = yield* container.getTcpPort(3000);
35 const response = yield* fetch(
36 HttpClientRequest.post("http://container/increment"),
37 );
38 return yield* response.text;
39 }).pipe(Effect.orDie),
40 fetch: Effect.gen(function* () {
41 const [response, socket] = yield* Cloudflare.upgrade();
42 const id = "TODO";
43 socket.serializeAttachment({ id });
44 sessions.set(id, socket);
45 return response;
46 }).pipe(Effect.orDie),
47 webSocketMessage: Effect.fn(function* (
48 socket: Cloudflare.WebSocket,
49 message: string | Uint8Array,
50 ) {
51 const session = socket.deserializeAttachment<{ id: string }>();
52 if (!session) return;
53 const text =
54 typeof message === "string"
55 ? message
56 : new TextDecoder().decode(message);
57 for (const peer of sessions.values()) {
58 yield* peer.send(`[${session.id}] ${text}`);
59 }
60 }),
61 webSocketClose: Effect.fn(function* (
62 ws: Cloudflare.WebSocket,
63 code: number,
64 reason: string,
65 _wasClean: boolean,
66 ) {
67 const session = ws.deserializeAttachment<{ id: string }>();
68 if (session) {
69 sessions.delete(session.id);
70 }
71 yield* ws.close(code, reason);
72 }),
73 };
74 });
75 }).pipe(
76 Effect.provide(
77 Cloudflare.Containers.layer(Sandbox, {
78 enableInternet: true,
79 }),
80 ),
81 ),
82) {}