rivetkit 2.3.27 → 2.3.28

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.
Files changed (85) hide show
  1. package/dist/browser/client.js +1 -4
  2. package/dist/browser/client.js.map +1 -1
  3. package/dist/browser/inspector/client.js +1 -4
  4. package/dist/browser/inspector/client.js.map +1 -1
  5. package/dist/tsup/agent-os/index.cjs +3 -2652
  6. package/dist/tsup/agent-os/index.cjs.map +1 -1
  7. package/dist/tsup/agent-os/index.d.cts +1 -2717
  8. package/dist/tsup/agent-os/index.d.ts +1 -2717
  9. package/dist/tsup/agent-os/index.js +3 -2652
  10. package/dist/tsup/agent-os/index.js.map +1 -1
  11. package/dist/tsup/{chunk-DIIXK2AW.js → chunk-3EYXDC64.js} +6 -6
  12. package/dist/tsup/chunk-3EYXDC64.js.map +1 -0
  13. package/dist/tsup/{chunk-HD5Y64WY.cjs → chunk-3X3QZVH6.cjs} +141 -141
  14. package/dist/tsup/chunk-3X3QZVH6.cjs.map +1 -0
  15. package/dist/tsup/{chunk-36WJAWHE.cjs → chunk-4LMR7FES.cjs} +7 -7
  16. package/dist/tsup/{chunk-36WJAWHE.cjs.map → chunk-4LMR7FES.cjs.map} +1 -1
  17. package/dist/tsup/{chunk-NEECPYAK.cjs → chunk-67PPGBOA.cjs} +3 -3
  18. package/dist/tsup/{chunk-NEECPYAK.cjs.map → chunk-67PPGBOA.cjs.map} +1 -1
  19. package/dist/tsup/{chunk-2WAICEBJ.cjs → chunk-DRG3YQ42.cjs} +3 -3
  20. package/dist/tsup/{chunk-2WAICEBJ.cjs.map → chunk-DRG3YQ42.cjs.map} +1 -1
  21. package/dist/tsup/{chunk-UR6UKRJW.cjs → chunk-FHZZ6PLK.cjs} +2 -5
  22. package/dist/tsup/chunk-FHZZ6PLK.cjs.map +1 -0
  23. package/dist/tsup/{chunk-NRYPHQWQ.js → chunk-IKMLO5KS.js} +4 -4
  24. package/dist/tsup/{chunk-M763VP2Z.js → chunk-KJBV7M2J.js} +3 -3
  25. package/dist/tsup/{chunk-PIRTTV4D.js → chunk-QLIVFNLT.js} +2 -2
  26. package/dist/tsup/{chunk-WU6DYFFQ.cjs → chunk-S5EZP52Q.cjs} +3 -3
  27. package/dist/tsup/{chunk-WU6DYFFQ.cjs.map → chunk-S5EZP52Q.cjs.map} +1 -1
  28. package/dist/tsup/{chunk-6JIDCRGT.cjs → chunk-SRRUW63T.cjs} +4 -4
  29. package/dist/tsup/{chunk-6JIDCRGT.cjs.map → chunk-SRRUW63T.cjs.map} +1 -1
  30. package/dist/tsup/{chunk-SREEDRMF.js → chunk-SWESH3VP.js} +2 -2
  31. package/dist/tsup/{chunk-ISPGIGCR.js → chunk-UEQ5NN24.js} +2 -2
  32. package/dist/tsup/{chunk-TCZ2DONE.js → chunk-WB3BJJWJ.js} +2 -5
  33. package/dist/tsup/chunk-WB3BJJWJ.js.map +1 -0
  34. package/dist/tsup/{chunk-AGGXTWDS.cjs → chunk-XQIPFU3I.cjs} +10 -10
  35. package/dist/tsup/{chunk-AGGXTWDS.cjs.map → chunk-XQIPFU3I.cjs.map} +1 -1
  36. package/dist/tsup/{chunk-V43A2WB2.js → chunk-XQVCT3CM.js} +2 -2
  37. package/dist/tsup/client/mod.cjs +5 -5
  38. package/dist/tsup/client/mod.js +4 -4
  39. package/dist/tsup/common/log.cjs +2 -2
  40. package/dist/tsup/common/log.js +1 -1
  41. package/dist/tsup/common/websocket.cjs +3 -3
  42. package/dist/tsup/common/websocket.js +2 -2
  43. package/dist/tsup/db/drizzle.cjs +2 -2
  44. package/dist/tsup/db/drizzle.js +1 -1
  45. package/dist/tsup/db/mod.cjs +3 -3
  46. package/dist/tsup/db/mod.js +2 -2
  47. package/dist/tsup/inspector/mod.cjs +5 -5
  48. package/dist/tsup/inspector/mod.js +4 -4
  49. package/dist/tsup/inspector/workflow.cjs +4 -4
  50. package/dist/tsup/inspector/workflow.js +3 -3
  51. package/dist/tsup/mod.cjs +84 -84
  52. package/dist/tsup/mod.cjs.map +1 -1
  53. package/dist/tsup/mod.js +5 -5
  54. package/dist/tsup/test/mod.cjs +6 -6
  55. package/dist/tsup/test/mod.js +4 -4
  56. package/dist/tsup/utils.cjs +2 -2
  57. package/dist/tsup/utils.js +1 -1
  58. package/dist/tsup/workflow/mod.cjs +7 -7
  59. package/dist/tsup/workflow/mod.js +4 -4
  60. package/package.json +9 -12
  61. package/src/agent-os/index.ts +5 -65
  62. package/src/client/actor-handle.ts +3 -1
  63. package/src/client/client.ts +8 -4
  64. package/dist/tsup/chunk-DIIXK2AW.js.map +0 -1
  65. package/dist/tsup/chunk-HD5Y64WY.cjs.map +0 -1
  66. package/dist/tsup/chunk-TCZ2DONE.js.map +0 -1
  67. package/dist/tsup/chunk-UR6UKRJW.cjs.map +0 -1
  68. package/src/agent-os/actor/cron.ts +0 -84
  69. package/src/agent-os/actor/db.ts +0 -54
  70. package/src/agent-os/actor/filesystem.ts +0 -131
  71. package/src/agent-os/actor/index.ts +0 -285
  72. package/src/agent-os/actor/network.ts +0 -58
  73. package/src/agent-os/actor/preview.ts +0 -190
  74. package/src/agent-os/actor/process.ts +0 -177
  75. package/src/agent-os/actor/session.ts +0 -518
  76. package/src/agent-os/actor/shell.ts +0 -60
  77. package/src/agent-os/config.ts +0 -79
  78. package/src/agent-os/fs/database-vfs.ts +0 -624
  79. package/src/agent-os/types.ts +0 -148
  80. /package/dist/tsup/{chunk-NRYPHQWQ.js.map → chunk-IKMLO5KS.js.map} +0 -0
  81. /package/dist/tsup/{chunk-M763VP2Z.js.map → chunk-KJBV7M2J.js.map} +0 -0
  82. /package/dist/tsup/{chunk-PIRTTV4D.js.map → chunk-QLIVFNLT.js.map} +0 -0
  83. /package/dist/tsup/{chunk-SREEDRMF.js.map → chunk-SWESH3VP.js.map} +0 -0
  84. /package/dist/tsup/{chunk-ISPGIGCR.js.map → chunk-UEQ5NN24.js.map} +0 -0
  85. /package/dist/tsup/{chunk-V43A2WB2.js.map → chunk-XQVCT3CM.js.map} +0 -0
@@ -1,131 +0,0 @@
1
- import type {
2
- AgentRegistryEntry,
3
- BatchReadResult,
4
- BatchWriteEntry,
5
- BatchWriteResult,
6
- DirEntry,
7
- ReaddirRecursiveOptions,
8
- } from "@rivet-dev/agent-os-core";
9
- import type { AgentOsActorConfig } from "../config";
10
- import type { AgentOsActionContext } from "../types";
11
- import { ensureVm } from "./index";
12
-
13
- // Infer types from AgentOs methods since @secure-exec/core is not a direct dep.
14
- type VirtualStat = Awaited<
15
- ReturnType<import("@rivet-dev/agent-os-core").AgentOs["stat"]>
16
- >;
17
- type DeleteOptions = Parameters<
18
- import("@rivet-dev/agent-os-core").AgentOs["delete"]
19
- >[1];
20
-
21
- // Build filesystem and agent registry actions for the actor factory.
22
- export function buildFilesystemActions<TConnParams>(
23
- config: AgentOsActorConfig<TConnParams>,
24
- ) {
25
- return {
26
- readFile: async (
27
- c: AgentOsActionContext<TConnParams>,
28
- path: string,
29
- ): Promise<Uint8Array> => {
30
- const agentOs = await ensureVm(c, config);
31
- return agentOs.readFile(path);
32
- },
33
-
34
- writeFile: async (
35
- c: AgentOsActionContext<TConnParams>,
36
- path: string,
37
- content: string | Uint8Array,
38
- ): Promise<void> => {
39
- const agentOs = await ensureVm(c, config);
40
- await agentOs.writeFile(path, content);
41
- },
42
-
43
- readFiles: async (
44
- c: AgentOsActionContext<TConnParams>,
45
- paths: string[],
46
- ): Promise<BatchReadResult[]> => {
47
- const agentOs = await ensureVm(c, config);
48
- return agentOs.readFiles(paths);
49
- },
50
-
51
- writeFiles: async (
52
- c: AgentOsActionContext<TConnParams>,
53
- entries: BatchWriteEntry[],
54
- ): Promise<BatchWriteResult[]> => {
55
- const agentOs = await ensureVm(c, config);
56
- return agentOs.writeFiles(entries);
57
- },
58
-
59
- mkdir: async (
60
- c: AgentOsActionContext<TConnParams>,
61
- path: string,
62
- options?: { recursive?: boolean },
63
- ): Promise<void> => {
64
- const agentOs = await ensureVm(c, config);
65
- await agentOs.mkdir(path, options);
66
- },
67
-
68
- readdir: async (
69
- c: AgentOsActionContext<TConnParams>,
70
- path: string,
71
- ): Promise<string[]> => {
72
- const agentOs = await ensureVm(c, config);
73
- return agentOs.readdir(path);
74
- },
75
-
76
- readdirRecursive: async (
77
- c: AgentOsActionContext<TConnParams>,
78
- path: string,
79
- options?: ReaddirRecursiveOptions,
80
- ): Promise<DirEntry[]> => {
81
- const agentOs = await ensureVm(c, config);
82
- return agentOs.readdirRecursive(path, options);
83
- },
84
-
85
- stat: async (
86
- c: AgentOsActionContext<TConnParams>,
87
- path: string,
88
- ): Promise<VirtualStat> => {
89
- const agentOs = await ensureVm(c, config);
90
- return agentOs.stat(path);
91
- },
92
-
93
- exists: async (
94
- c: AgentOsActionContext<TConnParams>,
95
- path: string,
96
- ): Promise<boolean> => {
97
- const agentOs = await ensureVm(c, config);
98
- return agentOs.exists(path);
99
- },
100
-
101
- move: async (
102
- c: AgentOsActionContext<TConnParams>,
103
- from: string,
104
- to: string,
105
- ): Promise<void> => {
106
- const agentOs = await ensureVm(c, config);
107
- await agentOs.move(from, to);
108
- },
109
-
110
- deleteFile: async (
111
- c: AgentOsActionContext<TConnParams>,
112
- path: string,
113
- options?: DeleteOptions,
114
- ): Promise<void> => {
115
- const agentOs = await ensureVm(c, config);
116
- await agentOs.delete(path, options);
117
- },
118
-
119
- // TODO: mountFs and unmountFs are not exposed as actor actions because
120
- // filesystem drivers (VirtualFileSystem) are not serializable over the
121
- // network. Mount filesystems via the `options.mounts` config in agentOs()
122
- // instead. See: https://github.com/rivet-dev/rivet/issues/XXXX
123
-
124
- listAgents: async (
125
- c: AgentOsActionContext<TConnParams>,
126
- ): Promise<AgentRegistryEntry[]> => {
127
- const agentOs = await ensureVm(c, config);
128
- return agentOs.listAgents();
129
- },
130
- };
131
- }
@@ -1,285 +0,0 @@
1
- import type { AgentOsOptions, MountConfig } from "@rivet-dev/agent-os-core";
2
- import { AgentOs, createInMemoryFileSystem } from "@rivet-dev/agent-os-core";
3
- import { type ActorDefinition, actor, event } from "@/actor/mod";
4
- import type { DatabaseProvider, RawAccess } from "@/common/database/config";
5
- import { db } from "@/common/database/mod";
6
- import {
7
- type AgentOsActorConfig,
8
- type AgentOsActorConfigInput,
9
- agentOsActorConfigSchema,
10
- } from "../config";
11
- import type {
12
- AgentOsActionContext,
13
- AgentOsActorState,
14
- AgentOsActorVars,
15
- CronEventPayload,
16
- PermissionRequestPayload,
17
- ProcessExitPayload,
18
- ProcessOutputPayload,
19
- SessionEventPayload,
20
- ShellDataPayload,
21
- VmBootedPayload,
22
- VmShutdownPayload,
23
- } from "../types";
24
- import { buildCronActions } from "./cron";
25
- import { migrateAgentOsTables } from "./db";
26
- import { buildFilesystemActions } from "./filesystem";
27
- import { buildNetworkActions } from "./network";
28
- import { buildOnRequestHandler, buildPreviewActions } from "./preview";
29
- import { buildProcessActions } from "./process";
30
- import {
31
- buildConfigActions,
32
- buildPromptActions,
33
- buildSessionActions,
34
- buildSessionPersistenceActions,
35
- } from "./session";
36
- import { buildShellActions } from "./shell";
37
-
38
- // --- VM lifecycle helpers ---
39
-
40
- async function ensureVm<TConnParams>(
41
- c: AgentOsActionContext<TConnParams>,
42
- config: AgentOsActorConfig<TConnParams>,
43
- ): Promise<AgentOs> {
44
- if (c.vars.agentOs) {
45
- return c.vars.agentOs;
46
- }
47
-
48
- const start = Date.now();
49
-
50
- // Build options with in-memory VFS as default working directory mount.
51
- const options = buildVmOptions(config.options);
52
-
53
- const agentOs = await AgentOs.create(options);
54
- c.vars.agentOs = agentOs;
55
-
56
- // Wire cron events to actor events.
57
- agentOs.onCronEvent((cronEvent) => {
58
- c.broadcast("cronEvent", { event: cronEvent });
59
- });
60
-
61
- c.broadcast("vmBooted", {});
62
- c.log.info({
63
- msg: "agent-os vm booted",
64
- bootDurationMs: Date.now() - start,
65
- });
66
-
67
- return agentOs;
68
- }
69
-
70
- function buildVmOptions(userOptions?: AgentOsOptions): AgentOsOptions {
71
- const userMounts = userOptions?.mounts ?? [];
72
-
73
- // Check if the user already provided a mount at /home/user. If so, respect
74
- // their override and skip the default in-memory VFS mount.
75
- const hasWorkdirMount = userMounts.some(
76
- (m: MountConfig) => m.path === "/home/user",
77
- );
78
-
79
- if (hasWorkdirMount) {
80
- return userOptions ?? {};
81
- }
82
-
83
- // TODO: Reimplement with persistent backend (SQLite-backed metadata and
84
- // actor storage-backed blocks) so VM filesystem state survives sleep/wake.
85
- const memMount: MountConfig = {
86
- path: "/home/user",
87
- driver: createInMemoryFileSystem(),
88
- };
89
-
90
- return {
91
- ...userOptions,
92
- mounts: [memMount, ...userMounts],
93
- };
94
- }
95
-
96
- // --- Prevent-sleep coordination ---
97
-
98
- function syncPreventSleep<TConnParams>(
99
- c: AgentOsActionContext<TConnParams>,
100
- ): void {
101
- const shouldPrevent =
102
- c.vars.activeSessionIds.size > 0 ||
103
- c.vars.activeProcesses.size > 0 ||
104
- c.vars.activeHooks.size > 0 ||
105
- c.vars.activeShells.size > 0;
106
-
107
- c.setPreventSleep(shouldPrevent);
108
-
109
- c.log.info({
110
- msg: "agent-os prevent sleep sync",
111
- preventSleep: shouldPrevent,
112
- activeSessions: c.vars.activeSessionIds.size,
113
- activeProcesses: c.vars.activeProcesses.size,
114
- activeHooks: c.vars.activeHooks.size,
115
- activeShells: c.vars.activeShells.size,
116
- });
117
- }
118
-
119
- // --- Hook tracking ---
120
-
121
- function runHook<TConnParams>(
122
- c: AgentOsActionContext<TConnParams>,
123
- name: string,
124
- callback: () => void | Promise<void>,
125
- ): void {
126
- const promise = Promise.resolve(callback())
127
- .catch((error) =>
128
- c.log.error({ msg: "agent-os hook failed", hookName: name, error }),
129
- )
130
- .finally(() => {
131
- c.vars.activeHooks.delete(promise);
132
- syncPreventSleep(c);
133
- });
134
- c.vars.activeHooks.add(promise);
135
- syncPreventSleep(c);
136
- c.waitUntil(promise);
137
- }
138
-
139
- // --- Public API ---
140
-
141
- export function agentOs<TConnParams = undefined>(
142
- config: AgentOsActorConfigInput<TConnParams>,
143
- ): ActorDefinition<
144
- AgentOsActorState,
145
- TConnParams,
146
- undefined,
147
- AgentOsActorVars,
148
- undefined,
149
- DatabaseProvider<RawAccess>,
150
- {
151
- sessionEvent: typeof sessionEventToken;
152
- permissionRequest: typeof permissionRequestToken;
153
- vmBooted: typeof vmBootedToken;
154
- vmShutdown: typeof vmShutdownToken;
155
- processOutput: typeof processOutputToken;
156
- processExit: typeof processExitToken;
157
- shellData: typeof shellDataToken;
158
- cronEvent: typeof cronEventToken;
159
- },
160
- Record<never, never>,
161
- any
162
- > {
163
- const parsedConfig = agentOsActorConfigSchema.parse(
164
- config,
165
- ) as AgentOsActorConfig<TConnParams>;
166
- const actions = {
167
- ...buildSessionActions(parsedConfig),
168
- ...buildPromptActions(parsedConfig),
169
- ...buildConfigActions(parsedConfig),
170
- ...buildSessionPersistenceActions(parsedConfig),
171
- ...buildProcessActions(parsedConfig),
172
- ...buildFilesystemActions(parsedConfig),
173
- ...buildPreviewActions(parsedConfig),
174
- ...buildShellActions(parsedConfig),
175
- ...buildCronActions(parsedConfig),
176
- ...buildNetworkActions(parsedConfig),
177
- };
178
-
179
- return actor<
180
- AgentOsActorState,
181
- TConnParams,
182
- undefined,
183
- AgentOsActorVars,
184
- undefined,
185
- DatabaseProvider<RawAccess>,
186
- {
187
- sessionEvent: typeof sessionEventToken;
188
- permissionRequest: typeof permissionRequestToken;
189
- vmBooted: typeof vmBootedToken;
190
- vmShutdown: typeof vmShutdownToken;
191
- processOutput: typeof processOutputToken;
192
- processExit: typeof processExitToken;
193
- shellData: typeof shellDataToken;
194
- cronEvent: typeof cronEventToken;
195
- },
196
- Record<never, never>,
197
- typeof actions
198
- >({
199
- options: {
200
- sleepGracePeriod: 900_000,
201
- actionTimeout: 900_000,
202
- },
203
- createState: async () => ({}),
204
- createVars: () => ({
205
- agentOs: null,
206
- activeSessionIds: new Set<string>(),
207
- activeProcesses: new Set<number>(),
208
- activeHooks: new Set<Promise<void>>(),
209
- activeShells: new Set<string>(),
210
- sessions: new Set(),
211
- }),
212
- db: db({
213
- onMigrate: migrateAgentOsTables,
214
- }),
215
- events: {
216
- sessionEvent: sessionEventToken,
217
- permissionRequest: permissionRequestToken,
218
- vmBooted: vmBootedToken,
219
- vmShutdown: vmShutdownToken,
220
- processOutput: processOutputToken,
221
- processExit: processExitToken,
222
- shellData: shellDataToken,
223
- cronEvent: cronEventToken,
224
- },
225
- onBeforeConnect: parsedConfig.onBeforeConnect
226
- ? async (ctx, params) => {
227
- // Skip user auth for preview URL requests. The signed token
228
- // in onRequest is the credential; browsers navigating preview
229
- // URLs cannot supply actor connection params.
230
- if (ctx.request) {
231
- const url = new URL(ctx.request.url);
232
- if (url.pathname.startsWith("/fetch/")) {
233
- return;
234
- }
235
- }
236
- await parsedConfig.onBeforeConnect?.(ctx, params);
237
- }
238
- : undefined,
239
- onRequest: buildOnRequestHandler(parsedConfig),
240
- onSleep: async (c) => {
241
- c.log.info({
242
- msg: "agent-os vm shutdown for sleep",
243
- activeSessions: c.vars.sessions.size,
244
- activeProcesses: c.vars.activeProcesses.size,
245
- activeShells: c.vars.activeShells.size,
246
- });
247
-
248
- if (c.vars.agentOs) {
249
- await c.vars.agentOs.dispose();
250
- c.vars.agentOs = null;
251
- }
252
-
253
- c.broadcast("vmShutdown", { reason: "sleep" as const });
254
- },
255
- onDestroy: async (c) => {
256
- c.log.info({
257
- msg: "agent-os vm shutdown for destroy",
258
- activeSessions: c.vars.sessions.size,
259
- activeProcesses: c.vars.activeProcesses.size,
260
- activeShells: c.vars.activeShells.size,
261
- });
262
-
263
- if (c.vars.agentOs) {
264
- await c.vars.agentOs.dispose();
265
- c.vars.agentOs = null;
266
- }
267
-
268
- c.broadcast("vmShutdown", { reason: "destroy" as const });
269
- },
270
- actions,
271
- });
272
- }
273
-
274
- // Event type tokens. Declared at module level so they can be referenced in
275
- // the actor generic type parameters.
276
- const sessionEventToken = event<SessionEventPayload>();
277
- const permissionRequestToken = event<PermissionRequestPayload>();
278
- const vmBootedToken = event<VmBootedPayload>();
279
- const vmShutdownToken = event<VmShutdownPayload>();
280
- const processOutputToken = event<ProcessOutputPayload>();
281
- const processExitToken = event<ProcessExitPayload>();
282
- const shellDataToken = event<ShellDataPayload>();
283
- const cronEventToken = event<CronEventPayload>();
284
-
285
- export { ensureVm, syncPreventSleep, runHook };
@@ -1,58 +0,0 @@
1
- import type { AgentOsActorConfig } from "../config";
2
- import type { AgentOsActionContext } from "../types";
3
- import { ensureVm } from "./index";
4
-
5
- // Serializable fetch options for the actor action boundary.
6
- export interface VmFetchOptions {
7
- method?: string;
8
- headers?: Record<string, string>;
9
- body?: string | Uint8Array;
10
- }
11
-
12
- // Serializable fetch result returned by the actor action.
13
- export interface VmFetchResult {
14
- status: number;
15
- statusText: string;
16
- headers: Record<string, string>;
17
- body: Uint8Array;
18
- }
19
-
20
- // Build network actions for the actor factory.
21
- export function buildNetworkActions<TConnParams>(
22
- config: AgentOsActorConfig<TConnParams>,
23
- ) {
24
- return {
25
- vmFetch: async (
26
- c: AgentOsActionContext<TConnParams>,
27
- port: number,
28
- url: string,
29
- options?: VmFetchOptions,
30
- ): Promise<VmFetchResult> => {
31
- const agentOs = await ensureVm(c, config);
32
-
33
- const headers = new Headers(options?.headers);
34
- const request = new Request(url, {
35
- method: options?.method ?? "GET",
36
- headers,
37
- body: options?.body ?? null,
38
- });
39
-
40
- const response = await agentOs.fetch(port, request);
41
-
42
- // Serialize response headers to a plain object.
43
- const responseHeaders: Record<string, string> = {};
44
- response.headers.forEach((value, key) => {
45
- responseHeaders[key] = value;
46
- });
47
-
48
- const body = new Uint8Array(await response.arrayBuffer());
49
-
50
- return {
51
- status: response.status,
52
- statusText: response.statusText,
53
- headers: responseHeaders,
54
- body,
55
- };
56
- },
57
- };
58
- }
@@ -1,190 +0,0 @@
1
- import crypto from "node:crypto";
2
- import type { RequestContext } from "@/actor/config";
3
- import type { DatabaseProvider, RawAccess } from "@/common/database/config";
4
- import type { AgentOsActorConfig } from "../config";
5
- import type {
6
- AgentOsActionContext,
7
- AgentOsActorState,
8
- AgentOsActorVars,
9
- } from "../types";
10
- import { ensureVm } from "./index";
11
-
12
- // Generate a 32-character lowercase alphanumeric token (a-z0-9).
13
- // 36^32 ~= 1.6e49 possible tokens, brute-force infeasible.
14
- export function generateToken(): string {
15
- const bytes = crypto.randomBytes(32);
16
- const alphabet = "abcdefghijklmnopqrstuvwxyz0123456789";
17
- let token = "";
18
- for (let i = 0; i < 32; i++) {
19
- token += alphabet[bytes[i]! % alphabet.length];
20
- }
21
- return token;
22
- }
23
-
24
- // CORS headers added to all preview proxy responses.
25
- const CORS_HEADERS: Record<string, string> = {
26
- "Access-Control-Allow-Origin": "*",
27
- "Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, OPTIONS",
28
- "Access-Control-Allow-Headers": "*",
29
- };
30
-
31
- function addCorsHeaders(response: Response): Response {
32
- const headers = new Headers(response.headers);
33
- for (const [key, value] of Object.entries(CORS_HEADERS)) {
34
- headers.set(key, value);
35
- }
36
- return new Response(response.body, {
37
- status: response.status,
38
- statusText: response.statusText,
39
- headers,
40
- });
41
- }
42
-
43
- type AgentOsRequestContext<TConnParams> = RequestContext<
44
- AgentOsActorState,
45
- TConnParams,
46
- undefined,
47
- AgentOsActorVars,
48
- undefined,
49
- DatabaseProvider<RawAccess>
50
- >;
51
-
52
- export function buildOnRequestHandler<TConnParams>(
53
- config: AgentOsActorConfig<TConnParams>,
54
- ) {
55
- return async (
56
- c: AgentOsRequestContext<TConnParams>,
57
- request: Request,
58
- ): Promise<Response> => {
59
- const url = new URL(request.url);
60
- const pathname = url.pathname;
61
-
62
- // Expect paths like /fetch/{token} or /fetch/{token}/remaining/path.
63
- const match = pathname.match(/^\/fetch\/([a-z0-9]+)(\/.*)?$/);
64
- if (!match) {
65
- return new Response("Not Found", { status: 404 });
66
- }
67
-
68
- // Handle OPTIONS preflight before token validation.
69
- if (request.method === "OPTIONS") {
70
- return new Response(null, { status: 204, headers: CORS_HEADERS });
71
- }
72
-
73
- const token = match[1]!;
74
- const remainingPath = match[2] ?? "/";
75
-
76
- // Validate token from SQLite.
77
- const now = Date.now();
78
- const rows: { port: number }[] = await c.db.execute(
79
- `SELECT port FROM agent_os_preview_tokens WHERE token = ? AND expires_at > ?`,
80
- token,
81
- now,
82
- );
83
-
84
- if (rows.length === 0) {
85
- c.log.warn({ msg: "agent-os preview auth failed", token });
86
- return addCorsHeaders(new Response("Forbidden", { status: 403 }));
87
- }
88
-
89
- const port = rows[0]?.port;
90
-
91
- // Boot the VM if needed.
92
- const agentOs = await ensureVm(
93
- c as AgentOsActionContext<TConnParams>,
94
- config,
95
- );
96
-
97
- // Build the request to proxy through the VM's virtual network.
98
- const vmUrl = `http://localhost:${port}${remainingPath}${url.search}`;
99
- const vmRequest = new Request(vmUrl, {
100
- method: request.method,
101
- headers: request.headers,
102
- body: request.body,
103
- duplex: "half",
104
- } as RequestInit);
105
-
106
- const vmResponse = await agentOs.fetch(port, vmRequest);
107
-
108
- c.log.info({
109
- msg: "agent-os preview request proxied",
110
- port,
111
- method: request.method,
112
- path: remainingPath,
113
- status: vmResponse.status,
114
- });
115
-
116
- return addCorsHeaders(vmResponse);
117
- };
118
- }
119
-
120
- export function buildPreviewActions<TConnParams>(
121
- config: AgentOsActorConfig<TConnParams>,
122
- ) {
123
- return {
124
- createSignedPreviewUrl: async (
125
- c: AgentOsActionContext<TConnParams>,
126
- port: number,
127
- expiresInSeconds?: number,
128
- ): Promise<{
129
- path: string;
130
- token: string;
131
- port: number;
132
- expiresAt: number;
133
- }> => {
134
- await ensureVm(c, config);
135
-
136
- const effectiveExpires =
137
- expiresInSeconds ?? config.preview.defaultExpiresInSeconds;
138
- const maxExpires = config.preview.maxExpiresInSeconds;
139
-
140
- if (effectiveExpires < 1 || effectiveExpires > maxExpires) {
141
- throw new Error(
142
- `expiresInSeconds must be between 1 and ${maxExpires}`,
143
- );
144
- }
145
-
146
- const token = generateToken();
147
- const now = Date.now();
148
- const expiresAt = now + effectiveExpires * 1000;
149
-
150
- // Insert token and lazy-delete expired tokens.
151
- await c.db.execute(
152
- `INSERT INTO agent_os_preview_tokens (token, port, created_at, expires_at)
153
- VALUES (?, ?, ?, ?)`,
154
- token,
155
- port,
156
- now,
157
- expiresAt,
158
- );
159
- await c.db.execute(
160
- `DELETE FROM agent_os_preview_tokens WHERE expires_at <= ?`,
161
- now,
162
- );
163
-
164
- // Path relative to the actor's gateway URL. Full URL is
165
- // `${gatewayUrl}/request/fetch/${token}` where gatewayUrl
166
- // comes from the client's getGatewayUrl().
167
- const path = `/request/fetch/${token}`;
168
-
169
- c.log.info({
170
- msg: "agent-os preview token created",
171
- port,
172
- expiresInSeconds: effectiveExpires,
173
- });
174
-
175
- return { path, token, port, expiresAt };
176
- },
177
-
178
- expireSignedPreviewUrl: async (
179
- c: AgentOsActionContext<TConnParams>,
180
- token: string,
181
- ): Promise<void> => {
182
- await c.db.execute(
183
- `DELETE FROM agent_os_preview_tokens WHERE token = ?`,
184
- token,
185
- );
186
-
187
- c.log.info({ msg: "agent-os preview token expired", token });
188
- },
189
- };
190
- }