rivetkit 2.0.3 → 2.0.4

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 (233) hide show
  1. package/README.md +11 -0
  2. package/dist/schemas/actor-persist/v1.ts +21 -24
  3. package/dist/schemas/client-protocol/v1.ts +6 -0
  4. package/dist/tsup/actor/errors.cjs +10 -2
  5. package/dist/tsup/actor/errors.cjs.map +1 -1
  6. package/dist/tsup/actor/errors.d.cts +17 -4
  7. package/dist/tsup/actor/errors.d.ts +17 -4
  8. package/dist/tsup/actor/errors.js +11 -3
  9. package/dist/tsup/{chunk-6PDXBYI5.js → chunk-3F2YSRJL.js} +8 -23
  10. package/dist/tsup/chunk-3F2YSRJL.js.map +1 -0
  11. package/dist/tsup/chunk-4CXBCT26.cjs +250 -0
  12. package/dist/tsup/chunk-4CXBCT26.cjs.map +1 -0
  13. package/dist/tsup/chunk-4R73YDN3.cjs +20 -0
  14. package/dist/tsup/chunk-4R73YDN3.cjs.map +1 -0
  15. package/dist/tsup/{chunk-OGAPU3UG.cjs → chunk-6LJT3QRL.cjs} +39 -25
  16. package/dist/tsup/chunk-6LJT3QRL.cjs.map +1 -0
  17. package/dist/tsup/{chunk-6WKQDDUD.cjs → chunk-GICQ3YCU.cjs} +143 -141
  18. package/dist/tsup/chunk-GICQ3YCU.cjs.map +1 -0
  19. package/dist/tsup/{chunk-FLMTTN27.js → chunk-H26RP6GD.js} +15 -8
  20. package/dist/tsup/chunk-H26RP6GD.js.map +1 -0
  21. package/dist/tsup/chunk-HI3HWJRC.js +20 -0
  22. package/dist/tsup/chunk-HI3HWJRC.js.map +1 -0
  23. package/dist/tsup/{chunk-4NSUQZ2H.js → chunk-HLLF4B4Q.js} +116 -114
  24. package/dist/tsup/chunk-HLLF4B4Q.js.map +1 -0
  25. package/dist/tsup/{chunk-FCCPJNMA.cjs → chunk-IH6CKNDW.cjs} +12 -27
  26. package/dist/tsup/chunk-IH6CKNDW.cjs.map +1 -0
  27. package/dist/tsup/chunk-LV2S3OU3.js +250 -0
  28. package/dist/tsup/chunk-LV2S3OU3.js.map +1 -0
  29. package/dist/tsup/{chunk-R2OPSKIV.cjs → chunk-LWNKVZG5.cjs} +20 -13
  30. package/dist/tsup/chunk-LWNKVZG5.cjs.map +1 -0
  31. package/dist/tsup/{chunk-INGJP237.js → chunk-NFU2BBT5.js} +102 -43
  32. package/dist/tsup/chunk-NFU2BBT5.js.map +1 -0
  33. package/dist/tsup/{chunk-3H7O2A7I.js → chunk-PQY7KKTL.js} +33 -19
  34. package/dist/tsup/chunk-PQY7KKTL.js.map +1 -0
  35. package/dist/tsup/{chunk-PO4VLDWA.js → chunk-QK72M5JB.js} +3 -5
  36. package/dist/tsup/chunk-QK72M5JB.js.map +1 -0
  37. package/dist/tsup/{chunk-TZJKSBUQ.cjs → chunk-QNNXFOQV.cjs} +3 -5
  38. package/dist/tsup/chunk-QNNXFOQV.cjs.map +1 -0
  39. package/dist/tsup/{chunk-GIR3AFFI.cjs → chunk-SBHHJ6QS.cjs} +102 -43
  40. package/dist/tsup/chunk-SBHHJ6QS.cjs.map +1 -0
  41. package/dist/tsup/chunk-TQ62L3X7.js +325 -0
  42. package/dist/tsup/chunk-TQ62L3X7.js.map +1 -0
  43. package/dist/tsup/chunk-VO7ZRVVD.cjs +6293 -0
  44. package/dist/tsup/chunk-VO7ZRVVD.cjs.map +1 -0
  45. package/dist/tsup/chunk-WHBPJNGW.cjs +325 -0
  46. package/dist/tsup/chunk-WHBPJNGW.cjs.map +1 -0
  47. package/dist/tsup/chunk-XJQHKJ4P.js +6293 -0
  48. package/dist/tsup/chunk-XJQHKJ4P.js.map +1 -0
  49. package/dist/tsup/client/mod.cjs +10 -10
  50. package/dist/tsup/client/mod.d.cts +7 -13
  51. package/dist/tsup/client/mod.d.ts +7 -13
  52. package/dist/tsup/client/mod.js +9 -9
  53. package/dist/tsup/common/log.cjs +12 -4
  54. package/dist/tsup/common/log.cjs.map +1 -1
  55. package/dist/tsup/common/log.d.cts +23 -17
  56. package/dist/tsup/common/log.d.ts +23 -17
  57. package/dist/tsup/common/log.js +15 -7
  58. package/dist/tsup/common/websocket.cjs +5 -5
  59. package/dist/tsup/common/websocket.js +4 -4
  60. package/dist/tsup/{common-CpqORuCq.d.cts → common-CXCe7s6i.d.cts} +2 -2
  61. package/dist/tsup/{common-CpqORuCq.d.ts → common-CXCe7s6i.d.ts} +2 -2
  62. package/dist/tsup/{connection-BwUMoe6n.d.ts → connection-BI-6UIBJ.d.ts} +196 -226
  63. package/dist/tsup/{connection-BR_Ve4ku.d.cts → connection-Dyd4NLGW.d.cts} +196 -226
  64. package/dist/tsup/driver-helpers/mod.cjs +6 -9
  65. package/dist/tsup/driver-helpers/mod.cjs.map +1 -1
  66. package/dist/tsup/driver-helpers/mod.d.cts +5 -6
  67. package/dist/tsup/driver-helpers/mod.d.ts +5 -6
  68. package/dist/tsup/driver-helpers/mod.js +6 -9
  69. package/dist/tsup/driver-test-suite/mod.cjs +155 -1363
  70. package/dist/tsup/driver-test-suite/mod.cjs.map +1 -1
  71. package/dist/tsup/driver-test-suite/mod.d.cts +11 -5
  72. package/dist/tsup/driver-test-suite/mod.d.ts +11 -5
  73. package/dist/tsup/driver-test-suite/mod.js +876 -2084
  74. package/dist/tsup/driver-test-suite/mod.js.map +1 -1
  75. package/dist/tsup/inspector/mod.cjs +6 -8
  76. package/dist/tsup/inspector/mod.cjs.map +1 -1
  77. package/dist/tsup/inspector/mod.d.cts +3 -3
  78. package/dist/tsup/inspector/mod.d.ts +3 -3
  79. package/dist/tsup/inspector/mod.js +8 -10
  80. package/dist/tsup/mod.cjs +9 -15
  81. package/dist/tsup/mod.cjs.map +1 -1
  82. package/dist/tsup/mod.d.cts +47 -42
  83. package/dist/tsup/mod.d.ts +47 -42
  84. package/dist/tsup/mod.js +10 -16
  85. package/dist/tsup/{router-endpoints-DAbqVFx2.d.ts → router-endpoints-BTe_Rsdn.d.cts} +2 -3
  86. package/dist/tsup/{router-endpoints-AYkXG8Tl.d.cts → router-endpoints-CBSrKHmo.d.ts} +2 -3
  87. package/dist/tsup/test/mod.cjs +10 -14
  88. package/dist/tsup/test/mod.cjs.map +1 -1
  89. package/dist/tsup/test/mod.d.cts +4 -5
  90. package/dist/tsup/test/mod.d.ts +4 -5
  91. package/dist/tsup/test/mod.js +9 -13
  92. package/dist/tsup/{utils-CT0cv4jd.d.ts → utils-fwx3o3K9.d.cts} +1 -0
  93. package/dist/tsup/{utils-CT0cv4jd.d.cts → utils-fwx3o3K9.d.ts} +1 -0
  94. package/dist/tsup/utils.cjs +3 -3
  95. package/dist/tsup/utils.d.cts +1 -1
  96. package/dist/tsup/utils.d.ts +1 -1
  97. package/dist/tsup/utils.js +2 -2
  98. package/package.json +4 -4
  99. package/src/actor/action.ts +1 -5
  100. package/src/actor/config.ts +27 -295
  101. package/src/actor/connection.ts +9 -12
  102. package/src/actor/context.ts +1 -4
  103. package/src/actor/definition.ts +7 -11
  104. package/src/actor/errors.ts +97 -35
  105. package/src/actor/generic-conn-driver.ts +28 -16
  106. package/src/actor/instance.ts +177 -133
  107. package/src/actor/log.ts +4 -13
  108. package/src/actor/mod.ts +0 -5
  109. package/src/actor/protocol/old.ts +42 -26
  110. package/src/actor/protocol/serde.ts +1 -1
  111. package/src/actor/router-endpoints.ts +41 -38
  112. package/src/actor/router.ts +20 -18
  113. package/src/actor/unstable-react.ts +1 -1
  114. package/src/actor/utils.ts +6 -2
  115. package/src/client/actor-common.ts +1 -1
  116. package/src/client/actor-conn.ts +152 -91
  117. package/src/client/actor-handle.ts +85 -25
  118. package/src/client/actor-query.ts +65 -0
  119. package/src/client/client.ts +29 -98
  120. package/src/client/config.ts +44 -0
  121. package/src/client/errors.ts +1 -0
  122. package/src/client/log.ts +2 -4
  123. package/src/client/mod.ts +16 -12
  124. package/src/client/raw-utils.ts +82 -25
  125. package/src/client/utils.ts +5 -3
  126. package/src/common/fake-event-source.ts +10 -9
  127. package/src/common/inline-websocket-adapter2.ts +39 -30
  128. package/src/common/log.ts +176 -101
  129. package/src/common/logfmt.ts +21 -30
  130. package/src/common/router.ts +12 -19
  131. package/src/common/utils.ts +27 -13
  132. package/src/common/websocket.ts +0 -1
  133. package/src/driver-helpers/mod.ts +1 -1
  134. package/src/driver-test-suite/log.ts +1 -3
  135. package/src/driver-test-suite/mod.ts +86 -60
  136. package/src/driver-test-suite/tests/actor-handle.ts +33 -0
  137. package/src/driver-test-suite/tests/manager-driver.ts +5 -3
  138. package/src/driver-test-suite/tests/raw-http-direct-registry.ts +227 -226
  139. package/src/driver-test-suite/tests/raw-websocket-direct-registry.ts +393 -392
  140. package/src/driver-test-suite/tests/request-access.ts +112 -126
  141. package/src/driver-test-suite/utils.ts +13 -10
  142. package/src/drivers/default.ts +7 -4
  143. package/src/drivers/engine/actor-driver.ts +22 -13
  144. package/src/drivers/engine/config.ts +2 -10
  145. package/src/drivers/engine/kv.ts +1 -1
  146. package/src/drivers/engine/log.ts +1 -3
  147. package/src/drivers/engine/mod.ts +2 -3
  148. package/src/drivers/file-system/actor.ts +1 -1
  149. package/src/drivers/file-system/global-state.ts +33 -20
  150. package/src/drivers/file-system/log.ts +1 -3
  151. package/src/drivers/file-system/manager.ts +31 -8
  152. package/src/inspector/config.ts +9 -4
  153. package/src/inspector/log.ts +1 -1
  154. package/src/inspector/manager.ts +2 -2
  155. package/src/inspector/utils.ts +1 -1
  156. package/src/manager/driver.ts +10 -2
  157. package/src/manager/hono-websocket-adapter.ts +21 -12
  158. package/src/manager/log.ts +2 -4
  159. package/src/manager/mod.ts +1 -1
  160. package/src/manager/router.ts +277 -1657
  161. package/src/manager-api/routes/actors-create.ts +16 -0
  162. package/src/manager-api/routes/actors-delete.ts +4 -0
  163. package/src/manager-api/routes/actors-get-by-id.ts +7 -0
  164. package/src/manager-api/routes/actors-get-or-create-by-id.ts +29 -0
  165. package/src/manager-api/routes/actors-get.ts +7 -0
  166. package/src/manager-api/routes/common.ts +18 -0
  167. package/src/mod.ts +0 -2
  168. package/src/registry/config.ts +1 -1
  169. package/src/registry/log.ts +2 -4
  170. package/src/registry/mod.ts +57 -24
  171. package/src/registry/run-config.ts +31 -33
  172. package/src/registry/serve.ts +4 -5
  173. package/src/remote-manager-driver/actor-http-client.ts +72 -0
  174. package/src/remote-manager-driver/actor-websocket-client.ts +63 -0
  175. package/src/remote-manager-driver/api-endpoints.ts +79 -0
  176. package/src/remote-manager-driver/api-utils.ts +43 -0
  177. package/src/remote-manager-driver/log.ts +5 -0
  178. package/src/remote-manager-driver/mod.ts +274 -0
  179. package/src/{drivers/engine → remote-manager-driver}/ws-proxy.ts +24 -14
  180. package/src/serde.ts +8 -2
  181. package/src/test/log.ts +1 -3
  182. package/src/test/mod.ts +17 -16
  183. package/dist/tsup/chunk-2CRLFV6Z.cjs +0 -202
  184. package/dist/tsup/chunk-2CRLFV6Z.cjs.map +0 -1
  185. package/dist/tsup/chunk-3H7O2A7I.js.map +0 -1
  186. package/dist/tsup/chunk-42I3OZ3Q.js +0 -15
  187. package/dist/tsup/chunk-42I3OZ3Q.js.map +0 -1
  188. package/dist/tsup/chunk-4NSUQZ2H.js.map +0 -1
  189. package/dist/tsup/chunk-6PDXBYI5.js.map +0 -1
  190. package/dist/tsup/chunk-6WKQDDUD.cjs.map +0 -1
  191. package/dist/tsup/chunk-CTBOSFUH.cjs +0 -116
  192. package/dist/tsup/chunk-CTBOSFUH.cjs.map +0 -1
  193. package/dist/tsup/chunk-EGVZZFE2.js +0 -2857
  194. package/dist/tsup/chunk-EGVZZFE2.js.map +0 -1
  195. package/dist/tsup/chunk-FCCPJNMA.cjs.map +0 -1
  196. package/dist/tsup/chunk-FLMTTN27.js.map +0 -1
  197. package/dist/tsup/chunk-GIR3AFFI.cjs.map +0 -1
  198. package/dist/tsup/chunk-INGJP237.js.map +0 -1
  199. package/dist/tsup/chunk-KJCJLKRM.js +0 -116
  200. package/dist/tsup/chunk-KJCJLKRM.js.map +0 -1
  201. package/dist/tsup/chunk-KUPQZYUQ.cjs +0 -15
  202. package/dist/tsup/chunk-KUPQZYUQ.cjs.map +0 -1
  203. package/dist/tsup/chunk-O2MBYIXO.cjs +0 -2857
  204. package/dist/tsup/chunk-O2MBYIXO.cjs.map +0 -1
  205. package/dist/tsup/chunk-OGAPU3UG.cjs.map +0 -1
  206. package/dist/tsup/chunk-OV6AYD4S.js +0 -4406
  207. package/dist/tsup/chunk-OV6AYD4S.js.map +0 -1
  208. package/dist/tsup/chunk-PO4VLDWA.js.map +0 -1
  209. package/dist/tsup/chunk-R2OPSKIV.cjs.map +0 -1
  210. package/dist/tsup/chunk-TZJKSBUQ.cjs.map +0 -1
  211. package/dist/tsup/chunk-UBUC5C3G.cjs +0 -189
  212. package/dist/tsup/chunk-UBUC5C3G.cjs.map +0 -1
  213. package/dist/tsup/chunk-UIM22YJL.cjs +0 -4406
  214. package/dist/tsup/chunk-UIM22YJL.cjs.map +0 -1
  215. package/dist/tsup/chunk-URVFQMYI.cjs +0 -230
  216. package/dist/tsup/chunk-URVFQMYI.cjs.map +0 -1
  217. package/dist/tsup/chunk-UVUPOS46.js +0 -230
  218. package/dist/tsup/chunk-UVUPOS46.js.map +0 -1
  219. package/dist/tsup/chunk-VRRHBNJC.js +0 -189
  220. package/dist/tsup/chunk-VRRHBNJC.js.map +0 -1
  221. package/dist/tsup/chunk-XFSS33EQ.js +0 -202
  222. package/dist/tsup/chunk-XFSS33EQ.js.map +0 -1
  223. package/src/client/http-client-driver.ts +0 -326
  224. package/src/driver-test-suite/test-inline-client-driver.ts +0 -402
  225. package/src/driver-test-suite/tests/actor-auth.ts +0 -591
  226. package/src/drivers/engine/api-endpoints.ts +0 -128
  227. package/src/drivers/engine/api-utils.ts +0 -70
  228. package/src/drivers/engine/manager-driver.ts +0 -391
  229. package/src/inline-client-driver/log.ts +0 -7
  230. package/src/inline-client-driver/mod.ts +0 -385
  231. package/src/manager/auth.ts +0 -121
  232. /package/src/{drivers/engine → actor}/keys.test.ts +0 -0
  233. /package/src/{drivers/engine → actor}/keys.ts +0 -0
@@ -1,127 +1,41 @@
1
1
  import { createRoute, OpenAPIHono } from "@hono/zod-openapi";
2
2
  import * as cbor from "cbor-x";
3
- import {
4
- Hono,
5
- type Context as HonoContext,
6
- type MiddlewareHandler,
7
- } from "hono";
8
- import { cors } from "hono/cors";
9
- import { streamSSE } from "hono/streaming";
10
- import type { WSContext } from "hono/ws";
11
- import invariant from "invariant";
12
- import type { CloseEvent, MessageEvent, WebSocket } from "ws";
3
+ import { Hono } from "hono";
4
+ import { cors as corsMiddleware } from "hono/cors";
5
+ import { createMiddleware } from "hono/factory";
13
6
  import { z } from "zod";
14
- import * as errors from "@/actor/errors";
15
- import type { Transport } from "@/actor/protocol/old";
16
- import type { Encoding } from "@/actor/protocol/serde";
17
- import {
18
- PATH_CONNECT_WEBSOCKET,
19
- PATH_RAW_WEBSOCKET_PREFIX,
20
- } from "@/actor/router";
21
7
  import {
22
- ALLOWED_PUBLIC_HEADERS,
23
- getRequestEncoding,
24
- getRequestQuery,
25
- HEADER_ACTOR_ID,
26
- HEADER_ACTOR_QUERY,
27
- HEADER_AUTH_DATA,
28
- HEADER_CONN_ID,
29
- HEADER_CONN_PARAMS,
30
- HEADER_CONN_TOKEN,
31
- HEADER_ENCODING,
32
- } from "@/actor/router-endpoints";
33
- import type { ClientDriver } from "@/client/client";
8
+ ActorNotFound,
9
+ FeatureNotImplemented,
10
+ MissingActorHeader,
11
+ Unsupported,
12
+ WebSocketsNotEnabled,
13
+ } from "@/actor/errors";
34
14
  import {
35
15
  handleRouteError,
36
16
  handleRouteNotFound,
37
17
  loggerMiddleware,
38
18
  } from "@/common/router";
39
- import {
40
- type DeconstructedError,
41
- deconstructError,
42
- noopNext,
43
- stringifyError,
44
- } from "@/common/utils";
45
19
  import { createManagerInspectorRouter } from "@/inspector/manager";
46
20
  import { secureInspector } from "@/inspector/utils";
47
- import type { UpgradeWebSocketArgs } from "@/mod";
21
+ import {
22
+ type ActorsCreateRequest,
23
+ ActorsCreateRequestSchema,
24
+ ActorsCreateResponseSchema,
25
+ } from "@/manager-api/routes/actors-create";
26
+ import { ActorsDeleteResponseSchema } from "@/manager-api/routes/actors-delete";
27
+ import { ActorsGetResponseSchema } from "@/manager-api/routes/actors-get";
28
+ import { ActorsGetByIdResponseSchema } from "@/manager-api/routes/actors-get-by-id";
29
+ import {
30
+ type ActorsGetOrCreateByIdRequest,
31
+ ActorsGetOrCreateByIdRequestSchema,
32
+ ActorsGetOrCreateByIdResponseSchema,
33
+ } from "@/manager-api/routes/actors-get-or-create-by-id";
34
+ import { RivetIdSchema } from "@/manager-api/routes/common";
48
35
  import type { RegistryConfig } from "@/registry/config";
49
36
  import type { RunConfig } from "@/registry/run-config";
50
- import type * as protocol from "@/schemas/client-protocol/mod";
51
- import {
52
- HTTP_RESOLVE_RESPONSE_VERSIONED,
53
- TO_CLIENT_VERSIONED,
54
- } from "@/schemas/client-protocol/versioned";
55
- import { serializeWithEncoding } from "@/serde";
56
- import { bufferToArrayBuffer } from "@/utils";
57
- import { authenticateEndpoint } from "./auth";
58
37
  import type { ManagerDriver } from "./driver";
59
38
  import { logger } from "./log";
60
- import type { ActorQuery } from "./protocol/query";
61
- import {
62
- ActorQuerySchema,
63
- ConnectRequestSchema,
64
- ConnectWebSocketRequestSchema,
65
- ConnMessageRequestSchema,
66
- ResolveRequestSchema,
67
- } from "./protocol/query";
68
-
69
- /**
70
- * Parse WebSocket protocol headers for query and connection parameters
71
- */
72
- function parseWebSocketProtocols(protocols: string | undefined): {
73
- queryRaw: string | undefined;
74
- encodingRaw: string | undefined;
75
- connParamsRaw: string | undefined;
76
- } {
77
- let queryRaw: string | undefined;
78
- let encodingRaw: string | undefined;
79
- let connParamsRaw: string | undefined;
80
-
81
- if (protocols) {
82
- const protocolList = protocols.split(",").map((p) => p.trim());
83
- for (const protocol of protocolList) {
84
- if (protocol.startsWith("query.")) {
85
- queryRaw = decodeURIComponent(protocol.substring("query.".length));
86
- } else if (protocol.startsWith("encoding.")) {
87
- encodingRaw = protocol.substring("encoding.".length);
88
- } else if (protocol.startsWith("conn_params.")) {
89
- connParamsRaw = decodeURIComponent(
90
- protocol.substring("conn_params.".length),
91
- );
92
- }
93
- }
94
- }
95
-
96
- return { queryRaw, encodingRaw, connParamsRaw };
97
- }
98
-
99
- const OPENAPI_ENCODING = z.string().openapi({
100
- description: "The encoding format to use for the response (json, cbor)",
101
- example: "json",
102
- });
103
-
104
- const OPENAPI_ACTOR_QUERY = z.string().openapi({
105
- description: "Actor query information",
106
- });
107
-
108
- const OPENAPI_CONN_PARAMS = z.string().openapi({
109
- description: "Connection parameters",
110
- });
111
-
112
- const OPENAPI_ACTOR_ID = z.string().openapi({
113
- description: "Actor ID (used in some endpoints)",
114
- example: "actor-123456",
115
- });
116
-
117
- const OPENAPI_CONN_ID = z.string().openapi({
118
- description: "Connection ID",
119
- example: "conn-123456",
120
- });
121
-
122
- const OPENAPI_CONN_TOKEN = z.string().openapi({
123
- description: "Connection token",
124
- });
125
39
 
126
40
  function buildOpenApiResponses<T>(schema: T, validateBody: boolean) {
127
41
  return {
@@ -144,17 +58,9 @@ function buildOpenApiResponses<T>(schema: T, validateBody: boolean) {
144
58
  };
145
59
  }
146
60
 
147
- /**
148
- * Only use `validateBody` to `true` if you need to export OpenAPI JSON.
149
- *
150
- * If left enabled for production, this will cause errors. We disable JSON validation since:
151
- * - It prevents us from proxying requests, since validating the body requires consuming the body so we can't forward the body
152
- * - We validate all types at the actor router layer since most requests are proxied
153
- */
154
61
  export function createManagerRouter(
155
62
  registryConfig: RegistryConfig,
156
63
  runConfig: RunConfig,
157
- inlineClientDriver: ClientDriver,
158
64
  managerDriver: ManagerDriver,
159
65
  validateBody: boolean,
160
66
  ): { router: Hono; openapi: OpenAPIHono } {
@@ -164,1629 +70,343 @@ export function createManagerRouter(
164
70
 
165
71
  router.use("*", loggerMiddleware(logger()));
166
72
 
167
- if (runConfig.cors || runConfig.inspector?.cors) {
168
- router.use("*", async (c, next) => {
169
- // Don't apply to WebSocket routes
170
- // HACK: This could be insecure if we had a varargs path. We have to check the path suffix for WS since we don't know the path that this router was mounted.
171
- // HACK: Checking "/websocket/" is not safe, but there is no other way to handle this if we don't know the base path this is
172
- // mounted on
173
- const path = c.req.path;
174
- if (
175
- path.endsWith("/actors/connect/websocket") ||
176
- path.includes("/actors/raw/websocket/") ||
177
- // inspectors implement their own CORS handling
178
- path.endsWith("/inspect") ||
179
- path.endsWith("/actors/inspect")
180
- ) {
181
- return next();
73
+ const cors = runConfig.cors
74
+ ? corsMiddleware(runConfig.cors)
75
+ : createMiddleware((_c, next) => next());
76
+
77
+ // Actor proxy middleware - intercept requests with x-rivet-target=actor
78
+ router.use("*", cors, async (c, next) => {
79
+ const target = c.req.header("x-rivet-target");
80
+ const actorId = c.req.header("x-rivet-actor");
81
+
82
+ if (target === "actor") {
83
+ if (!actorId) {
84
+ throw new MissingActorHeader();
182
85
  }
183
86
 
184
- return cors({
185
- ...(runConfig.cors ?? {}),
186
- ...(runConfig.inspector?.cors ?? {}),
187
- origin: (origin, c) => {
188
- const inspectorOrigin = runConfig.inspector?.cors?.origin;
87
+ logger().debug({
88
+ msg: "proxying request to actor",
89
+ actorId,
90
+ path: c.req.path,
91
+ method: c.req.method,
92
+ });
189
93
 
190
- if (inspectorOrigin !== undefined) {
191
- if (typeof inspectorOrigin === "function") {
192
- const allowed = inspectorOrigin(origin, c);
193
- if (allowed) return allowed;
194
- // Proceed to next CORS config if none provided
195
- } else if (Array.isArray(inspectorOrigin)) {
196
- return inspectorOrigin.includes(origin) ? origin : undefined;
197
- } else {
198
- return inspectorOrigin;
199
- }
200
- }
94
+ // Handle WebSocket upgrade
95
+ if (c.req.header("upgrade") === "websocket") {
96
+ const upgradeWebSocket = runConfig.getUpgradeWebSocket?.();
97
+ if (!upgradeWebSocket) {
98
+ throw new WebSocketsNotEnabled();
99
+ }
201
100
 
202
- if (runConfig.cors?.origin !== undefined) {
203
- if (typeof runConfig.cors.origin === "function") {
204
- const allowed = runConfig.cors.origin(origin, c);
205
- if (allowed) return allowed;
206
- } else {
207
- return runConfig.cors.origin as string;
208
- }
209
- }
101
+ // For WebSocket, use the driver's proxyWebSocket method
102
+ // Extract any additional headers that might be needed
103
+ const encoding =
104
+ c.req.header("X-RivetKit-Encoding") ||
105
+ c.req.header("x-rivet-encoding") ||
106
+ "json";
107
+ const connParams =
108
+ c.req.header("X-RivetKit-Conn-Params") ||
109
+ c.req.header("x-rivet-conn-params");
110
+ const authData =
111
+ c.req.header("X-RivetKit-Auth-Data") ||
112
+ c.req.header("x-rivet-auth-data");
113
+
114
+ // Include query string if present
115
+ const pathWithQuery = c.req.url.includes("?")
116
+ ? c.req.path + c.req.url.substring(c.req.url.indexOf("?"))
117
+ : c.req.path;
118
+
119
+ return await managerDriver.proxyWebSocket(
120
+ c,
121
+ pathWithQuery,
122
+ actorId,
123
+ encoding as any, // Will be validated by driver
124
+ connParams ? JSON.parse(connParams) : undefined,
125
+ authData ? JSON.parse(authData) : undefined,
126
+ );
127
+ }
210
128
 
211
- return null;
212
- },
213
- allowMethods: (origin, c) => {
214
- const inspectorMethods = runConfig.inspector?.cors?.allowMethods;
215
- if (inspectorMethods) {
216
- if (typeof inspectorMethods === "function") {
217
- return inspectorMethods(origin, c);
218
- }
219
- return inspectorMethods;
220
- }
129
+ // Handle regular HTTP requests
130
+ // Preserve all headers except the routing headers
131
+ const proxyHeaders = new Headers(c.req.raw.headers);
132
+ proxyHeaders.delete("x-rivet-target");
133
+ proxyHeaders.delete("x-rivet-actor");
221
134
 
222
- if (runConfig.cors?.allowMethods) {
223
- if (typeof runConfig.cors.allowMethods === "function") {
224
- return runConfig.cors.allowMethods(origin, c);
225
- }
226
- return runConfig.cors.allowMethods;
227
- }
135
+ // Build the proxy request with the actor URL format
136
+ const url = new URL(c.req.url);
137
+ const proxyUrl = new URL(`http://actor${url.pathname}${url.search}`);
228
138
 
229
- return [];
230
- },
231
- allowHeaders: [
232
- ...(runConfig.cors?.allowHeaders ?? []),
233
- ...(runConfig.inspector?.cors?.allowHeaders ?? []),
234
- ...ALLOWED_PUBLIC_HEADERS,
235
- "Content-Type",
236
- "User-Agent",
237
- ],
238
- credentials:
239
- runConfig.cors?.credentials ??
240
- runConfig.inspector?.cors?.credentials ??
241
- true,
242
- })(c, next);
243
- });
244
- }
139
+ const proxyRequest = new Request(proxyUrl, {
140
+ method: c.req.method,
141
+ headers: proxyHeaders,
142
+ body: c.req.raw.body,
143
+ });
144
+
145
+ return await managerDriver.proxyRequest(c, proxyRequest, actorId);
146
+ }
147
+
148
+ return next();
149
+ });
245
150
 
246
151
  // GET /
247
- router.get("/", (c: HonoContext) => {
152
+ router.get("/", cors, (c) => {
248
153
  return c.text(
249
- "This is an RivetKit registry.\n\nLearn more at https://rivetkit.org",
154
+ "This is a RivetKit server.\n\nLearn more at https://rivetkit.org",
250
155
  );
251
156
  });
252
157
 
253
- // POST /actors/resolve
158
+ // GET /actors/by-id
254
159
  {
255
- const ResolveQuerySchema = z
256
- .object({
257
- query: z.any().openapi({
258
- example: { getForId: { actorId: "actor-123" } },
160
+ const route = createRoute({
161
+ middleware: [cors],
162
+ method: "get",
163
+ path: "/actors/by-id",
164
+ request: {
165
+ query: z.object({
166
+ name: z.string(),
167
+ key: z.string(),
259
168
  }),
260
- })
261
- .openapi("ResolveQuery");
169
+ },
170
+ responses: buildOpenApiResponses(
171
+ ActorsGetByIdResponseSchema,
172
+ validateBody,
173
+ ),
174
+ });
262
175
 
263
- const ResolveResponseSchema = z
264
- .object({
265
- i: z.string().openapi({
266
- example: "actor-123",
267
- }),
268
- })
269
- .openapi("ResolveResponse");
176
+ router.openapi(route, async (c) => {
177
+ const { name, key } = c.req.valid("query");
270
178
 
271
- const resolveRoute = createRoute({
272
- method: "post",
273
- path: "/actors/resolve",
179
+ // Get actor by key from the driver
180
+ const actorOutput = await managerDriver.getWithKey({
181
+ c,
182
+ name,
183
+ key: [key], // Convert string to ActorKey array
184
+ });
185
+
186
+ return c.json({
187
+ actor_id: actorOutput?.actorId || null,
188
+ });
189
+ });
190
+ }
191
+
192
+ // PUT /actors/by-id
193
+ {
194
+ const route = createRoute({
195
+ cors: [cors],
196
+ method: "put",
197
+ path: "/actors/by-id",
274
198
  request: {
275
199
  body: {
276
200
  content: validateBody
277
201
  ? {
278
202
  "application/json": {
279
- schema: ResolveQuerySchema,
203
+ schema: ActorsGetOrCreateByIdRequestSchema,
280
204
  },
281
205
  }
282
206
  : {},
283
207
  },
284
- headers: z.object({
285
- [HEADER_ACTOR_QUERY]: OPENAPI_ACTOR_QUERY,
286
- }),
287
208
  },
288
- responses: buildOpenApiResponses(ResolveResponseSchema, validateBody),
209
+ responses: buildOpenApiResponses(
210
+ ActorsGetOrCreateByIdResponseSchema,
211
+ validateBody,
212
+ ),
289
213
  });
290
214
 
291
- router.openapi(resolveRoute, (c) =>
292
- handleResolveRequest(c, registryConfig, managerDriver),
293
- );
294
- }
215
+ router.openapi(route, async (c) => {
216
+ const body = validateBody
217
+ ? await c.req.json<ActorsGetOrCreateByIdRequest>()
218
+ : await c.req.json();
295
219
 
296
- // GET /actors/connect/websocket
297
- {
298
- // HACK: WebSockets don't work with mounts, so we need to dynamically match the trailing path
299
- router.use("*", (c, next) => {
300
- if (c.req.path.endsWith("/actors/connect/websocket")) {
301
- return handleWebSocketConnectRequest(
302
- c,
303
- registryConfig,
304
- runConfig,
305
- managerDriver,
306
- );
220
+ // Parse and validate the request body if validation is enabled
221
+ if (validateBody) {
222
+ ActorsGetOrCreateByIdRequestSchema.parse(body);
307
223
  }
308
224
 
309
- return next();
310
- });
225
+ // Check if actor already exists
226
+ const existingActor = await managerDriver.getWithKey({
227
+ c,
228
+ name: body.name,
229
+ key: [body.key], // Convert string to ActorKey array
230
+ });
311
231
 
312
- // This route is a noop, just used to generate docs
313
- const wsRoute = createRoute({
314
- method: "get",
315
- path: "/actors/connect/websocket",
316
- responses: {
317
- 101: {
318
- description: "WebSocket upgrade",
319
- },
320
- },
321
- });
232
+ if (existingActor) {
233
+ return c.json({
234
+ actor_id: existingActor.actorId,
235
+ created: false,
236
+ });
237
+ }
322
238
 
323
- router.openapi(wsRoute, () => {
324
- throw new Error("Should be unreachable");
239
+ // Create new actor
240
+ const newActor = await managerDriver.getOrCreateWithKey({
241
+ c,
242
+ name: body.name,
243
+ key: [body.key], // Convert string to ActorKey array
244
+ input: body.input
245
+ ? cbor.decode(Buffer.from(body.input, "base64"))
246
+ : undefined,
247
+ region: undefined, // Not provided in the request schema
248
+ });
249
+
250
+ return c.json({
251
+ actor_id: newActor.actorId,
252
+ created: true,
253
+ });
325
254
  });
326
255
  }
327
256
 
328
- // GET /actors/connect/sse
257
+ // GET /actors/{actor_id}
329
258
  {
330
- const sseRoute = createRoute({
259
+ const route = createRoute({
260
+ middleware: [cors],
331
261
  method: "get",
332
- path: "/actors/connect/sse",
262
+ path: "/actors/{actor_id}",
333
263
  request: {
334
- headers: z.object({
335
- [HEADER_ENCODING]: OPENAPI_ENCODING,
336
- [HEADER_ACTOR_QUERY]: OPENAPI_ACTOR_QUERY,
337
- [HEADER_CONN_PARAMS]: OPENAPI_CONN_PARAMS.optional(),
264
+ params: z.object({
265
+ actor_id: RivetIdSchema,
338
266
  }),
339
267
  },
340
- responses: {
341
- 200: {
342
- description: "SSE stream",
343
- content: {
344
- "text/event-stream": {
345
- schema: z.unknown(),
346
- },
347
- },
348
- },
349
- },
268
+ responses: buildOpenApiResponses(ActorsGetResponseSchema, validateBody),
350
269
  });
351
270
 
352
- router.openapi(sseRoute, (c) =>
353
- handleSseConnectRequest(c, registryConfig, runConfig, managerDriver),
354
- );
355
- }
271
+ router.openapi(route, async (c) => {
272
+ const { actor_id } = c.req.valid("param");
356
273
 
357
- // POST /actors/action/:action
358
- {
359
- const ActionParamsSchema = z
360
- .object({
361
- action: z.string().openapi({
362
- param: {
363
- name: "action",
364
- in: "path",
365
- },
366
- example: "myAction",
367
- }),
368
- })
369
- .openapi("ActionParams");
274
+ // Get actor by ID from the driver
275
+ const actorOutput = await managerDriver.getForId({
276
+ c,
277
+ name: "", // TODO: The API doesn't provide the name, this may need to be resolved
278
+ actorId: actor_id,
279
+ });
370
280
 
371
- const ActionRequestSchema = z
372
- .object({
373
- query: z.any().openapi({
374
- example: { getForId: { actorId: "actor-123" } },
375
- }),
376
- body: z
377
- .any()
378
- .optional()
379
- .openapi({
380
- example: { param1: "value1", param2: 123 },
381
- }),
382
- })
383
- .openapi("ActionRequest");
281
+ if (!actorOutput) {
282
+ throw new ActorNotFound(actor_id);
283
+ }
384
284
 
385
- const ActionResponseSchema = z.any().openapi("ActionResponse");
285
+ // Transform ActorOutput to match ActorSchema
286
+ // Note: Some fields are not available from the driver and need defaults
287
+ const actor = {
288
+ actor_id: actorOutput.actorId,
289
+ name: actorOutput.name,
290
+ key: actorOutput.key,
291
+ namespace_id: "", // Not available from driver
292
+ runner_name_selector: "", // Not available from driver
293
+ create_ts: Date.now(), // Not available from driver
294
+ connectable_ts: null,
295
+ destroy_ts: null,
296
+ sleep_ts: null,
297
+ start_ts: null,
298
+ };
386
299
 
387
- const actionRoute = createRoute({
388
- method: "post",
389
- path: "/actors/actions/{action}",
390
- request: {
391
- params: ActionParamsSchema,
392
- body: {
393
- content: validateBody
394
- ? {
395
- "application/json": {
396
- schema: ActionRequestSchema,
397
- },
398
- }
399
- : {},
400
- },
401
- headers: z.object({
402
- [HEADER_ENCODING]: OPENAPI_ENCODING,
403
- [HEADER_CONN_PARAMS]: OPENAPI_CONN_PARAMS.optional(),
404
- }),
405
- },
406
- responses: buildOpenApiResponses(ActionResponseSchema, validateBody),
300
+ return c.json({ actor });
407
301
  });
408
-
409
- router.openapi(actionRoute, (c) =>
410
- handleActionRequest(c, registryConfig, runConfig, managerDriver),
411
- );
412
302
  }
413
303
 
414
- // POST /actors/message
304
+ // POST /actors
415
305
  {
416
- const ConnectionMessageRequestSchema = z
417
- .object({
418
- message: z.any().openapi({
419
- example: { type: "message", content: "Hello, actor!" },
420
- }),
421
- })
422
- .openapi("ConnectionMessageRequest");
423
-
424
- const ConnectionMessageResponseSchema = z
425
- .any()
426
- .openapi("ConnectionMessageResponse");
427
-
428
- const messageRoute = createRoute({
306
+ const route = createRoute({
307
+ middleware: [cors],
429
308
  method: "post",
430
- path: "/actors/message",
309
+ path: "/actors",
431
310
  request: {
432
311
  body: {
433
312
  content: validateBody
434
313
  ? {
435
314
  "application/json": {
436
- schema: ConnectionMessageRequestSchema,
315
+ schema: ActorsCreateRequestSchema,
437
316
  },
438
317
  }
439
318
  : {},
440
319
  },
441
- headers: z.object({
442
- [HEADER_ACTOR_ID]: OPENAPI_ACTOR_ID,
443
- [HEADER_CONN_ID]: OPENAPI_CONN_ID,
444
- [HEADER_ENCODING]: OPENAPI_ENCODING,
445
- [HEADER_CONN_TOKEN]: OPENAPI_CONN_TOKEN,
446
- }),
447
320
  },
448
321
  responses: buildOpenApiResponses(
449
- ConnectionMessageResponseSchema,
322
+ ActorsCreateResponseSchema,
450
323
  validateBody,
451
324
  ),
452
325
  });
453
326
 
454
- router.openapi(messageRoute, (c) =>
455
- handleMessageRequest(c, registryConfig, runConfig, managerDriver),
456
- );
457
- }
458
-
459
- // Raw HTTP endpoints - /actors/raw/http/*
460
- {
461
- const RawHttpRequestBodySchema = z.any().optional().openapi({
462
- description: "Raw request body (can be any content type)",
463
- });
464
-
465
- const RawHttpResponseSchema = z.any().openapi({
466
- description: "Raw response from actor's onFetch handler",
467
- });
468
-
469
- // Define common route config
470
- const rawHttpRouteConfig = {
471
- path: "/actors/raw/http/*",
472
- request: {
473
- headers: z.object({
474
- [HEADER_ACTOR_QUERY]: OPENAPI_ACTOR_QUERY.optional(),
475
- [HEADER_CONN_PARAMS]: OPENAPI_CONN_PARAMS.optional(),
476
- }),
477
- body: {
478
- content: {
479
- "*/*": {
480
- schema: RawHttpRequestBodySchema,
481
- },
482
- },
483
- },
484
- },
485
- responses: {
486
- 200: {
487
- description: "Success - response from actor's onFetch handler",
488
- content: {
489
- "*/*": {
490
- schema: RawHttpResponseSchema,
491
- },
492
- },
493
- },
494
- 404: {
495
- description: "Actor does not have an onFetch handler",
496
- },
497
- 500: {
498
- description: "Internal server error or invalid response from actor",
499
- },
500
- },
501
- };
502
-
503
- // Create routes for each HTTP method
504
- const httpMethods = [
505
- "get",
506
- "post",
507
- "put",
508
- "delete",
509
- "patch",
510
- "head",
511
- "options",
512
- ] as const;
513
- for (const method of httpMethods) {
514
- const route = createRoute({
515
- method,
516
- ...rawHttpRouteConfig,
517
- });
518
-
519
- router.openapi(route, async (c) => {
520
- return handleRawHttpRequest(
521
- c,
522
- registryConfig,
523
- runConfig,
524
- managerDriver,
525
- );
526
- });
527
- }
528
- }
529
-
530
- // Raw WebSocket endpoint - /actors/raw/websocket/*
531
- {
532
- // HACK: WebSockets don't work with mounts, so we need to dynamically match the trailing path
533
- router.use("*", async (c, next) => {
534
- if (c.req.path.includes("/raw/websocket/")) {
535
- return handleRawWebSocketRequest(
536
- c,
537
- registryConfig,
538
- runConfig,
539
- managerDriver,
540
- );
541
- }
542
-
543
- return next();
544
- });
545
-
546
- // This route is a noop, just used to generate docs
547
- const rawWebSocketRoute = createRoute({
548
- method: "get",
549
- path: "/actors/raw/websocket/*",
550
- request: {},
551
- responses: {
552
- 101: {
553
- description: "WebSocket upgrade successful",
554
- },
555
- 400: {
556
- description: "WebSockets not enabled or invalid request",
557
- },
558
- 404: {
559
- description: "Actor does not have an onWebSocket handler",
560
- },
561
- },
562
- });
563
-
564
- router.openapi(rawWebSocketRoute, () => {
565
- throw new Error("Should be unreachable");
566
- });
567
- }
568
-
569
- if (runConfig.inspector?.enabled) {
570
- router.route(
571
- "/actors/inspect",
572
- new Hono()
573
- .use(
574
- cors(runConfig.inspector.cors),
575
- secureInspector(runConfig),
576
- universalActorProxy({
577
- registryConfig,
578
- runConfig,
579
- driver: managerDriver,
580
- }),
581
- )
582
- .all("/", (c) =>
583
- // this should be handled by the actor proxy, but just in case
584
- c.text("Unreachable.", 404),
585
- ),
586
- );
587
- router.route(
588
- "/inspect",
589
- new Hono()
590
- .use(
591
- cors(runConfig.inspector.cors),
592
- secureInspector(runConfig),
593
- async (c, next) => {
594
- const inspector = managerDriver.inspector;
595
- invariant(inspector, "inspector not supported on this platform");
596
-
597
- c.set("inspector", inspector);
598
- await next();
599
- },
600
- )
601
- .route("/", createManagerInspectorRouter()),
602
- );
603
- }
604
-
605
- if (registryConfig.test.enabled) {
606
- // Add HTTP endpoint to test the inline client
607
- //
608
- // We have to do this in a router since this needs to run in the same server as the RivetKit registry. Some test contexts to not run in the same server.
609
- router.post(".test/inline-driver/call", async (c) => {
610
- // TODO: use openapi instead
611
- const buffer = await c.req.arrayBuffer();
612
- const { encoding, transport, method, args }: TestInlineDriverCallRequest =
613
- cbor.decode(new Uint8Array(buffer));
614
-
615
- logger().debug("received inline request", {
616
- encoding,
617
- transport,
618
- method,
619
- args,
620
- });
621
-
622
- // Forward inline driver request
623
- let response: TestInlineDriverCallResponse<unknown>;
624
- try {
625
- const output = await ((inlineClientDriver as any)[method] as any)(
626
- ...args,
627
- );
628
- response = { ok: output };
629
- } catch (rawErr) {
630
- const err = deconstructError(rawErr, logger(), {}, true);
631
- response = { err };
632
- }
633
-
634
- return c.body(cbor.encode(response));
635
- });
636
-
637
- router.get(".test/inline-driver/connect-websocket", async (c) => {
638
- const upgradeWebSocket = runConfig.getUpgradeWebSocket?.();
639
- invariant(upgradeWebSocket, "websockets not supported on this platform");
640
-
641
- return upgradeWebSocket(async (c: any) => {
642
- const {
643
- actorQuery: actorQueryRaw,
644
- params: paramsRaw,
645
- encodingKind,
646
- } = c.req.query() as {
647
- actorQuery: string;
648
- params?: string;
649
- encodingKind: Encoding;
650
- };
651
- const actorQuery = JSON.parse(actorQueryRaw);
652
- const params =
653
- paramsRaw !== undefined ? JSON.parse(paramsRaw) : undefined;
654
-
655
- logger().debug("received test inline driver websocket", {
656
- actorQuery,
657
- params,
658
- encodingKind,
659
- });
660
-
661
- // Connect to the actor using the inline client driver - this returns a Promise<WebSocket>
662
- const clientWsPromise = inlineClientDriver.connectWebSocket(
663
- undefined,
664
- actorQuery,
665
- encodingKind,
666
- params,
667
- undefined,
668
- );
669
-
670
- return await createTestWebSocketProxy(clientWsPromise, "standard");
671
- })(c, noopNext());
672
- });
673
-
674
- router.get(".test/inline-driver/raw-websocket", async (c) => {
675
- const upgradeWebSocket = runConfig.getUpgradeWebSocket?.();
676
- invariant(upgradeWebSocket, "websockets not supported on this platform");
677
-
678
- return upgradeWebSocket(async (c: any) => {
679
- const {
680
- actorQuery: actorQueryRaw,
681
- params: paramsRaw,
682
- encodingKind,
683
- path,
684
- protocols: protocolsRaw,
685
- } = c.req.query() as {
686
- actorQuery: string;
687
- params?: string;
688
- encodingKind: Encoding;
689
- path: string;
690
- protocols?: string;
691
- };
692
- const actorQuery = JSON.parse(actorQueryRaw);
693
- const params =
694
- paramsRaw !== undefined ? JSON.parse(paramsRaw) : undefined;
695
- const protocols =
696
- protocolsRaw !== undefined ? JSON.parse(protocolsRaw) : undefined;
697
-
698
- logger().debug("received test inline driver raw websocket", {
699
- actorQuery,
700
- params,
701
- encodingKind,
702
- path,
703
- protocols,
704
- });
705
-
706
- // Connect to the actor using the inline client driver - this returns a Promise<WebSocket>
707
- logger().debug("calling inlineClientDriver.rawWebSocket");
708
- const clientWsPromise = inlineClientDriver.rawWebSocket(
709
- undefined,
710
- actorQuery,
711
- encodingKind,
712
- params,
713
- path,
714
- protocols,
715
- undefined,
716
- );
717
-
718
- logger().debug("calling createTestWebSocketProxy");
719
- return await createTestWebSocketProxy(clientWsPromise, "raw");
720
- })(c, noopNext());
721
- });
722
-
723
- // Raw HTTP endpoint for test inline driver
724
- router.all(".test/inline-driver/raw-http/*", async (c) => {
725
- // Extract parameters from headers
726
- const actorQueryHeader = c.req.header(HEADER_ACTOR_QUERY);
727
- const paramsHeader = c.req.header(HEADER_CONN_PARAMS);
728
- const encodingHeader = c.req.header(HEADER_ENCODING);
327
+ router.openapi(route, async (c) => {
328
+ const body = validateBody
329
+ ? await c.req.json<ActorsCreateRequest>()
330
+ : await c.req.json();
729
331
 
730
- if (!actorQueryHeader || !encodingHeader) {
731
- return c.text("Missing required headers", 400);
332
+ // Parse and validate the request body if validation is enabled
333
+ if (validateBody) {
334
+ ActorsCreateRequestSchema.parse(body);
732
335
  }
733
336
 
734
- const actorQuery = JSON.parse(actorQueryHeader);
735
- const params = paramsHeader ? JSON.parse(paramsHeader) : undefined;
736
- const encoding = encodingHeader as Encoding;
737
-
738
- // Extract the path after /raw-http/
739
- const fullPath = c.req.path;
740
- const pathOnly =
741
- fullPath.split("/.test/inline-driver/raw-http/")[1] || "";
742
-
743
- // Include query string
744
- const url = new URL(c.req.url);
745
- const pathWithQuery = pathOnly + url.search;
746
-
747
- logger().debug("received test inline driver raw http", {
748
- actorQuery,
749
- params,
750
- encoding,
751
- path: pathWithQuery,
752
- method: c.req.method,
337
+ // Create actor using the driver
338
+ const actorOutput = await managerDriver.createActor({
339
+ c,
340
+ name: body.name,
341
+ key: [body.key || crypto.randomUUID()], // Generate key if not provided, convert to ActorKey array
342
+ input: body.input
343
+ ? cbor.decode(Buffer.from(body.input, "base64"))
344
+ : undefined,
345
+ region: undefined, // Not provided in the request schema
753
346
  });
754
347
 
755
- try {
756
- // Forward the request using the inline client driver
757
- const response = await inlineClientDriver.rawHttpRequest(
758
- undefined,
759
- actorQuery,
760
- encoding,
761
- params,
762
- pathWithQuery,
763
- {
764
- method: c.req.method,
765
- headers: c.req.raw.headers,
766
- body: c.req.raw.body,
767
- },
768
- undefined,
769
- );
770
-
771
- // Return the response directly
772
- return response;
773
- } catch (error) {
774
- logger().error("error in test inline raw http", {
775
- error: stringifyError(error),
776
- });
348
+ // Transform ActorOutput to match ActorSchema
349
+ const actor = {
350
+ actor_id: actorOutput.actorId,
351
+ name: actorOutput.name,
352
+ key: actorOutput.key,
353
+ namespace_id: "", // Not available from driver
354
+ runner_name_selector: body.runner_name_selector,
355
+ create_ts: Date.now(),
356
+ connectable_ts: null,
357
+ destroy_ts: null,
358
+ sleep_ts: null,
359
+ start_ts: null,
360
+ };
777
361
 
778
- // Return error response
779
- const err = deconstructError(error, logger(), {}, true);
780
- return c.json(
781
- {
782
- error: {
783
- code: err.code,
784
- message: err.message,
785
- metadata: err.metadata,
786
- },
787
- },
788
- err.statusCode,
789
- );
790
- }
362
+ return c.json({ actor });
791
363
  });
792
364
  }
793
365
 
794
- managerDriver.modifyManagerRouter?.(
795
- registryConfig,
796
- router as unknown as Hono,
797
- );
798
-
799
- // Mount on both / and /registry
366
+ // TODO:
367
+ // // DELETE /actors/{actor_id}
368
+ // {
369
+ // const route = createRoute({
370
+ // middleware: [cors],
371
+ // method: "delete",
372
+ // path: "/actors/{actor_id}",
373
+ // request: {
374
+ // params: z.object({
375
+ // actor_id: RivetIdSchema,
376
+ // }),
377
+ // },
378
+ // responses: buildOpenApiResponses(
379
+ // ActorsDeleteResponseSchema,
380
+ // validateBody,
381
+ // ),
382
+ // });
800
383
  //
801
- // We do this because the default requests are to `/registry/*`.
384
+ // router.openapi(route, async (c) => {
385
+ // const { actor_id } = c.req.valid("param");
802
386
  //
803
- // If using `app.fetch` directly in a non-hono router, paths
804
- // might not be truncated so they'll come to this router as
805
- // `/registry/*`. If mounted correctly in Hono, requests will
806
- // come in at the root as `/*`.
807
- const mountedRouter = new Hono();
808
- mountedRouter.route("/", router);
809
- mountedRouter.route("/registry", router);
810
-
811
- // IMPORTANT: These must be on `mountedRouter` instead of `router` or else they will not be called.
812
- mountedRouter.notFound(handleRouteNotFound);
813
- mountedRouter.onError(handleRouteError.bind(undefined, {}));
814
-
815
- return { router: mountedRouter, openapi: router };
816
- }
817
-
818
- export interface TestInlineDriverCallRequest {
819
- encoding: Encoding;
820
- transport: Transport;
821
- method: string;
822
- args: unknown[];
823
- }
824
-
825
- export type TestInlineDriverCallResponse<T> =
826
- | {
827
- ok: T;
828
- }
829
- | {
830
- err: DeconstructedError;
831
- };
832
-
833
- /**
834
- * Query the manager driver to get or create a actor based on the provided query
835
- */
836
- export async function queryActor(
837
- c: HonoContext,
838
- query: ActorQuery,
839
- driver: ManagerDriver,
840
- ): Promise<{ actorId: string }> {
841
- logger().debug("querying actor", { query });
842
- let actorOutput: { actorId: string };
843
- if ("getForId" in query) {
844
- const output = await driver.getForId({
845
- c,
846
- name: query.getForId.name,
847
- actorId: query.getForId.actorId,
848
- });
849
- if (!output) throw new errors.ActorNotFound(query.getForId.actorId);
850
- actorOutput = output;
851
- } else if ("getForKey" in query) {
852
- const existingActor = await driver.getWithKey({
853
- c,
854
- name: query.getForKey.name,
855
- key: query.getForKey.key,
856
- });
857
- if (!existingActor) {
858
- throw new errors.ActorNotFound(
859
- `${query.getForKey.name}:${JSON.stringify(query.getForKey.key)}`,
860
- );
861
- }
862
- actorOutput = existingActor;
863
- } else if ("getOrCreateForKey" in query) {
864
- const getOrCreateOutput = await driver.getOrCreateWithKey({
865
- c,
866
- name: query.getOrCreateForKey.name,
867
- key: query.getOrCreateForKey.key,
868
- input: query.getOrCreateForKey.input,
869
- region: query.getOrCreateForKey.region,
870
- });
871
- actorOutput = {
872
- actorId: getOrCreateOutput.actorId,
873
- };
874
- } else if ("create" in query) {
875
- const createOutput = await driver.createActor({
876
- c,
877
- name: query.create.name,
878
- key: query.create.key,
879
- input: query.create.input,
880
- region: query.create.region,
881
- });
882
- actorOutput = {
883
- actorId: createOutput.actorId,
884
- };
885
- } else {
886
- throw new errors.InvalidRequest("Invalid query format");
887
- }
888
-
889
- logger().debug("actor query result", {
890
- actorId: actorOutput.actorId,
891
- });
892
- return { actorId: actorOutput.actorId };
893
- }
894
-
895
- /**
896
- * Creates a WebSocket proxy for test endpoints that forwards messages between server and client WebSockets
897
- */
898
- async function createTestWebSocketProxy(
899
- clientWsPromise: Promise<WebSocket>,
900
- connectionType: string,
901
- ): Promise<UpgradeWebSocketArgs> {
902
- // Store a reference to the resolved WebSocket
903
- let clientWs: WebSocket | null = null;
904
- try {
905
- // Resolve the client WebSocket promise
906
- logger().debug("awaiting client websocket promise");
907
- const ws = await clientWsPromise;
908
- clientWs = ws;
909
- logger().debug("client websocket promise resolved", {
910
- constructor: ws?.constructor.name,
911
- });
912
-
913
- // Wait for ws to open
914
- await new Promise<void>((resolve, reject) => {
915
- const onOpen = () => {
916
- logger().debug("test websocket connection opened");
917
- resolve();
918
- };
919
- const onError = (error: any) => {
920
- logger().error("test websocket connection failed", { error });
921
- reject(
922
- new Error(`Failed to open WebSocket: ${error.message || error}`),
923
- );
924
- };
925
- ws.addEventListener("open", onOpen);
926
- ws.addEventListener("error", onError);
927
- });
928
- } catch (error) {
929
- logger().error(
930
- `failed to establish client ${connectionType} websocket connection`,
931
- { error },
932
- );
933
- return {
934
- onOpen: (_evt, serverWs) => {
935
- serverWs.close(1011, "Failed to establish connection");
936
- },
937
- onMessage: () => {},
938
- onError: () => {},
939
- onClose: () => {},
940
- };
941
- }
942
-
943
- // Create WebSocket proxy handlers to relay messages between client and server
944
- return {
945
- onOpen: (_evt: any, serverWs: WSContext) => {
946
- logger().debug(`test ${connectionType} websocket connection opened`);
947
-
948
- // Check WebSocket type
949
- logger().debug("clientWs info", {
950
- constructor: clientWs.constructor.name,
951
- hasAddEventListener: typeof clientWs.addEventListener === "function",
952
- readyState: clientWs.readyState,
953
- });
954
-
955
- // Add message handler to forward messages from client to server
956
- clientWs.addEventListener("message", (clientEvt: MessageEvent) => {
957
- logger().debug(
958
- `test ${connectionType} websocket connection message from client`,
959
- {
960
- dataType: typeof clientEvt.data,
961
- isBlob: clientEvt.data instanceof Blob,
962
- isArrayBuffer: clientEvt.data instanceof ArrayBuffer,
963
- dataConstructor: clientEvt.data?.constructor?.name,
964
- dataStr:
965
- typeof clientEvt.data === "string"
966
- ? clientEvt.data.substring(0, 100)
967
- : undefined,
968
- },
969
- );
970
-
971
- if (serverWs.readyState === 1) {
972
- // OPEN
973
- // Handle Blob data
974
- if (clientEvt.data instanceof Blob) {
975
- clientEvt.data
976
- .arrayBuffer()
977
- .then((buffer) => {
978
- logger().debug(
979
- "converted client blob to arraybuffer, sending to server",
980
- {
981
- bufferSize: buffer.byteLength,
982
- },
983
- );
984
- serverWs.send(buffer as any);
985
- })
986
- .catch((error) => {
987
- logger().error("failed to convert blob to arraybuffer", {
988
- error,
989
- });
990
- });
991
- } else {
992
- logger().debug("sending client data directly to server", {
993
- dataType: typeof clientEvt.data,
994
- dataLength:
995
- typeof clientEvt.data === "string"
996
- ? clientEvt.data.length
997
- : undefined,
998
- });
999
- serverWs.send(clientEvt.data as any);
1000
- }
1001
- }
1002
- });
1003
-
1004
- // Add close handler to close server when client closes
1005
- clientWs.addEventListener("close", (clientEvt: CloseEvent) => {
1006
- logger().debug(`test ${connectionType} websocket connection closed`);
1007
-
1008
- if (serverWs.readyState !== 3) {
1009
- // Not CLOSED
1010
- serverWs.close(clientEvt.code, clientEvt.reason);
1011
- }
1012
- });
1013
-
1014
- // Add error handler
1015
- clientWs.addEventListener("error", () => {
1016
- logger().debug(`test ${connectionType} websocket connection error`);
1017
-
1018
- if (serverWs.readyState !== 3) {
1019
- // Not CLOSED
1020
- serverWs.close(1011, "Error in client websocket");
1021
- }
1022
- });
1023
- },
1024
- onMessage: (evt: { data: any }) => {
1025
- logger().debug("received message from server", {
1026
- dataType: typeof evt.data,
1027
- isBlob: evt.data instanceof Blob,
1028
- isArrayBuffer: evt.data instanceof ArrayBuffer,
1029
- dataConstructor: evt.data?.constructor?.name,
1030
- dataStr:
1031
- typeof evt.data === "string" ? evt.data.substring(0, 100) : undefined,
1032
- });
1033
-
1034
- // Forward messages from server websocket to client websocket
1035
- if (clientWs.readyState === 1) {
1036
- // OPEN
1037
- // Handle Blob data
1038
- if (evt.data instanceof Blob) {
1039
- evt.data
1040
- .arrayBuffer()
1041
- .then((buffer) => {
1042
- logger().debug("converted blob to arraybuffer, sending", {
1043
- bufferSize: buffer.byteLength,
1044
- });
1045
- clientWs.send(buffer);
1046
- })
1047
- .catch((error) => {
1048
- logger().error("failed to convert blob to arraybuffer", {
1049
- error,
1050
- });
1051
- });
1052
- } else {
1053
- logger().debug("sending data directly", {
1054
- dataType: typeof evt.data,
1055
- dataLength:
1056
- typeof evt.data === "string" ? evt.data.length : undefined,
1057
- });
1058
- clientWs.send(evt.data);
1059
- }
1060
- }
1061
- },
1062
- onClose: (
1063
- event: {
1064
- wasClean: boolean;
1065
- code: number;
1066
- reason: string;
1067
- },
1068
- serverWs: WSContext,
1069
- ) => {
1070
- logger().debug(`server ${connectionType} websocket closed`, {
1071
- wasClean: event.wasClean,
1072
- code: event.code,
1073
- reason: event.reason,
1074
- });
1075
-
1076
- // HACK: Close socket in order to fix bug with Cloudflare leaving WS in closing state
1077
- // https://github.com/cloudflare/workerd/issues/2569
1078
- serverWs.close(1000, "hack_force_close");
1079
-
1080
- // Close the client websocket when the server websocket closes
1081
- if (
1082
- clientWs &&
1083
- clientWs.readyState !== clientWs.CLOSED &&
1084
- clientWs.readyState !== clientWs.CLOSING
1085
- ) {
1086
- // Don't pass code/message since this may affect how close events are triggered
1087
- clientWs.close(1000, event.reason);
1088
- }
1089
- },
1090
- onError: (error: unknown) => {
1091
- logger().error(`error in server ${connectionType} websocket`, { error });
1092
-
1093
- // Close the client websocket on error
1094
- if (
1095
- clientWs &&
1096
- clientWs.readyState !== clientWs.CLOSED &&
1097
- clientWs.readyState !== clientWs.CLOSING
1098
- ) {
1099
- clientWs.close(1011, "Error in server websocket");
1100
- }
1101
- },
1102
- };
1103
- }
1104
-
1105
- /**
1106
- * Handle SSE connection request
1107
- */
1108
- async function handleSseConnectRequest(
1109
- c: HonoContext,
1110
- registryConfig: RegistryConfig,
1111
- _runConfig: RunConfig,
1112
- driver: ManagerDriver,
1113
- ): Promise<Response> {
1114
- let encoding: Encoding | undefined;
1115
- try {
1116
- encoding = getRequestEncoding(c.req);
1117
- logger().debug("sse connection request received", { encoding });
1118
-
1119
- const params = ConnectRequestSchema.safeParse({
1120
- query: getRequestQuery(c),
1121
- encoding: c.req.header(HEADER_ENCODING),
1122
- connParams: c.req.header(HEADER_CONN_PARAMS),
1123
- });
1124
-
1125
- if (!params.success) {
1126
- logger().error("invalid connection parameters", {
1127
- error: params.error,
1128
- });
1129
- throw new errors.InvalidRequest(params.error);
1130
- }
1131
-
1132
- const query = params.data.query;
1133
-
1134
- // Parse connection parameters for authentication
1135
- const connParams = params.data.connParams
1136
- ? JSON.parse(params.data.connParams)
1137
- : undefined;
1138
-
1139
- // Authenticate the request
1140
- const authData = await authenticateEndpoint(
1141
- c,
1142
- driver,
1143
- registryConfig,
1144
- query,
1145
- ["connect"],
1146
- connParams,
1147
- );
1148
-
1149
- // Get the actor ID
1150
- const { actorId } = await queryActor(c, query, driver);
1151
- invariant(actorId, "Missing actor ID");
1152
- logger().debug("sse connection to actor", { actorId });
1153
-
1154
- // Handle based on mode
1155
- logger().debug("using custom proxy mode for sse connection");
1156
- const url = new URL("http://actor/connect/sse");
1157
-
1158
- // Always build fresh request to prevent forwarding unwanted headers
1159
- const proxyRequestHeaderes = new Headers();
1160
- proxyRequestHeaderes.set(HEADER_ENCODING, params.data.encoding);
1161
- if (params.data.connParams) {
1162
- proxyRequestHeaderes.set(HEADER_CONN_PARAMS, params.data.connParams);
1163
- }
1164
- if (authData) {
1165
- proxyRequestHeaderes.set(HEADER_AUTH_DATA, JSON.stringify(authData));
1166
- }
1167
-
1168
- const proxyRequest = new Request(url, { headers: proxyRequestHeaderes });
1169
-
1170
- return await driver.proxyRequest(c, proxyRequest, actorId);
1171
- } catch (error) {
1172
- // If we receive an error during setup, we send the error and close the socket immediately
1173
- //
1174
- // We have to return the error over SSE since SSE clients cannot read vanilla HTTP responses
1175
-
1176
- const { code, message, metadata } = deconstructError(error, logger(), {
1177
- sseEvent: "setup",
1178
- });
1179
-
1180
- return streamSSE(c, async (stream) => {
1181
- try {
1182
- if (encoding) {
1183
- // Serialize and send the connection error
1184
- const errorMsg: protocol.ToClient = {
1185
- body: {
1186
- tag: "Error",
1187
- val: {
1188
- code,
1189
- message,
1190
- metadata: bufferToArrayBuffer(cbor.encode(metadata)),
1191
- actionId: null,
1192
- },
1193
- },
1194
- };
1195
-
1196
- // Send the error message to the client
1197
- const serialized = serializeWithEncoding(
1198
- encoding,
1199
- errorMsg,
1200
- TO_CLIENT_VERSIONED,
1201
- );
1202
- await stream.writeSSE({
1203
- data:
1204
- typeof serialized === "string"
1205
- ? serialized
1206
- : Buffer.from(serialized).toString("base64"),
1207
- });
1208
- } else {
1209
- // We don't know the encoding, send an error and close
1210
- await stream.writeSSE({
1211
- data: code,
1212
- event: "error",
1213
- });
1214
- }
1215
- } catch (serializeError) {
1216
- logger().error("failed to send error to sse client", {
1217
- error: serializeError,
1218
- });
1219
- await stream.writeSSE({
1220
- data: "internal error during error handling",
1221
- event: "error",
1222
- });
1223
- }
1224
-
1225
- // Stream will exit completely once function exits
1226
- });
1227
- }
1228
- }
1229
-
1230
- /**
1231
- * Handle WebSocket connection request
1232
- */
1233
- async function handleWebSocketConnectRequest(
1234
- c: HonoContext,
1235
- registryConfig: RegistryConfig,
1236
- runConfig: RunConfig,
1237
- driver: ManagerDriver,
1238
- ): Promise<Response> {
1239
- const upgradeWebSocket = runConfig.getUpgradeWebSocket?.();
1240
- if (!upgradeWebSocket) {
1241
- return c.text(
1242
- "WebSockets are not enabled for this driver. Use SSE instead.",
1243
- 400,
1244
- );
1245
- }
1246
-
1247
- let encoding: Encoding | undefined;
1248
- try {
1249
- logger().debug("websocket connection request received");
1250
-
1251
- // Parse configuration from Sec-WebSocket-Protocol header
1252
- //
1253
- // We use this instead of query parameters since this is more secure than
1254
- // query parameters. Query parameters often get logged.
1255
- //
1256
- // Browsers don't support using headers, so this is the only way to
1257
- // pass data securely.
1258
- const protocols = c.req.header("sec-websocket-protocol");
1259
- const { queryRaw, encodingRaw, connParamsRaw } =
1260
- parseWebSocketProtocols(protocols);
1261
-
1262
- // Parse query
1263
- let queryUnvalidated: unknown;
1264
- try {
1265
- queryUnvalidated = JSON.parse(queryRaw!);
1266
- } catch (error) {
1267
- logger().error("invalid query json", { error });
1268
- throw new errors.InvalidQueryJSON(error);
1269
- }
1270
-
1271
- // Parse conn params
1272
- let connParamsUnvalidated: unknown = null;
1273
- try {
1274
- if (connParamsRaw) {
1275
- connParamsUnvalidated = JSON.parse(connParamsRaw!);
1276
- }
1277
- } catch (error) {
1278
- logger().error("invalid conn params", { error });
1279
- throw new errors.InvalidParams(
1280
- `Invalid params JSON: ${stringifyError(error)}`,
1281
- );
1282
- }
1283
-
1284
- // We can't use the standard headers with WebSockets
1285
- //
1286
- // All other information will be sent over the socket itself, since that data needs to be E2EE
1287
- const params = ConnectWebSocketRequestSchema.safeParse({
1288
- query: queryUnvalidated,
1289
- encoding: encodingRaw,
1290
- connParams: connParamsUnvalidated,
1291
- });
1292
- if (!params.success) {
1293
- logger().error("invalid connection parameters", {
1294
- error: params.error,
1295
- });
1296
- throw new errors.InvalidRequest(params.error);
1297
- }
1298
- encoding = params.data.encoding;
1299
-
1300
- // Authenticate endpoint
1301
- const authData = await authenticateEndpoint(
1302
- c,
1303
- driver,
1304
- registryConfig,
1305
- params.data.query,
1306
- ["connect"],
1307
- connParamsRaw,
1308
- );
1309
-
1310
- // Get the actor ID
1311
- const { actorId } = await queryActor(c, params.data.query, driver);
1312
- logger().debug("found actor for websocket connection", {
1313
- actorId,
1314
- });
1315
- invariant(actorId, "missing actor id");
1316
-
1317
- // Proxy the WebSocket connection to the actor
1318
- //
1319
- // The proxyWebSocket handler will:
1320
- // 1. Validate the WebSocket upgrade request
1321
- // 2. Forward the request to the actor with the appropriate path
1322
- // 3. Handle the WebSocket pair and proxy messages between client and actor
1323
- return await driver.proxyWebSocket(
1324
- c,
1325
- PATH_CONNECT_WEBSOCKET,
1326
- actorId,
1327
- params.data.encoding,
1328
- params.data.connParams,
1329
- authData,
1330
- );
1331
- } catch (error) {
1332
- // If we receive an error during setup, we send the error and close the socket immediately
1333
- //
1334
- // We have to return the error over WS since WebSocket clients cannot read vanilla HTTP responses
1335
-
1336
- const { code, message, metadata } = deconstructError(error, logger(), {
1337
- wsEvent: "setup",
1338
- });
1339
-
1340
- return await upgradeWebSocket(() => ({
1341
- onOpen: (_evt: unknown, ws: WSContext) => {
1342
- if (encoding) {
1343
- try {
1344
- // Serialize and send the connection error
1345
- const errorMsg: protocol.ToClient = {
1346
- body: {
1347
- tag: "Error",
1348
- val: {
1349
- code,
1350
- message,
1351
- metadata: bufferToArrayBuffer(cbor.encode(metadata)),
1352
- actionId: null,
1353
- },
1354
- },
1355
- };
1356
-
1357
- // Send the error message to the client
1358
- const serialized = serializeWithEncoding(
1359
- encoding,
1360
- errorMsg,
1361
- TO_CLIENT_VERSIONED,
1362
- );
1363
- ws.send(serialized);
1364
-
1365
- // Close the connection with an error code
1366
- ws.close(1011, code);
1367
- } catch (serializeError) {
1368
- logger().error("failed to send error to websocket client", {
1369
- error: serializeError,
1370
- });
1371
- ws.close(1011, "internal error during error handling");
1372
- }
1373
- } else {
1374
- // We don't know the encoding so we send what we can
1375
- ws.close(1011, code);
1376
- }
1377
- },
1378
- }))(c, noopNext());
1379
- }
1380
- }
1381
-
1382
- /**
1383
- * Handle a connection message request to a actor
1384
- *
1385
- * There is no authentication handler on this request since the connection
1386
- * token is used to authenticate the message.
1387
- */
1388
- async function handleMessageRequest(
1389
- c: HonoContext,
1390
- _registryConfig: RegistryConfig,
1391
- _runConfig: RunConfig,
1392
- driver: ManagerDriver,
1393
- ): Promise<Response> {
1394
- logger().debug("connection message request received");
1395
- try {
1396
- const params = ConnMessageRequestSchema.safeParse({
1397
- actorId: c.req.header(HEADER_ACTOR_ID),
1398
- connId: c.req.header(HEADER_CONN_ID),
1399
- encoding: c.req.header(HEADER_ENCODING),
1400
- connToken: c.req.header(HEADER_CONN_TOKEN),
1401
- });
1402
- if (!params.success) {
1403
- logger().error("invalid connection parameters", {
1404
- error: params.error,
1405
- });
1406
- throw new errors.InvalidRequest(params.error);
1407
- }
1408
- const { actorId, connId, encoding, connToken } = params.data;
1409
-
1410
- // TODO: This endpoint can be used to exhause resources (DoS attack) on an actor if you know the actor ID:
1411
- // 1. Get the actor ID (usually this is reasonably secure, but we don't assume actor ID is sensitive)
1412
- // 2. Spam messages to the actor (the conn token can be invalid)
1413
- // 3. The actor will be exhausted processing messages — even if the token is invalid
1414
- //
1415
- // The solution is we need to move the authorization of the connection token to this request handler
1416
- // AND include the actor ID in the connection token so we can verify that it has permission to send
1417
- // a message to that actor. This would require changing the token to a JWT so we can include a secure
1418
- // payload, but this requires managing a private key & managing key rotations.
1419
- //
1420
- // All other solutions (e.g. include the actor name as a header or include the actor name in the actor ID)
1421
- // have exploits that allow the caller to send messages to arbitrary actors.
1422
- //
1423
- // Currently, we assume this is not a critical problem because requests will likely get rate
1424
- // limited before enough messages are passed to the actor to exhaust resources.
1425
-
1426
- const url = new URL("http://actor/connections/message");
1427
-
1428
- // Always build fresh request to prevent forwarding unwanted headers
1429
- const proxyRequestHeaders = new Headers();
1430
- proxyRequestHeaders.set(HEADER_ENCODING, encoding);
1431
- proxyRequestHeaders.set(HEADER_CONN_ID, connId);
1432
- proxyRequestHeaders.set(HEADER_CONN_TOKEN, connToken);
1433
-
1434
- const proxyRequest = new Request(url, {
1435
- method: "POST",
1436
- body: c.req.raw.body,
1437
- duplex: "half",
1438
- headers: proxyRequestHeaders,
1439
- });
1440
-
1441
- return await driver.proxyRequest(c, proxyRequest, actorId);
1442
- } catch (error) {
1443
- logger().error("error proxying connection message", { error });
1444
-
1445
- // Use ProxyError if it's not already an ActorError
1446
- if (!errors.ActorError.isActorError(error)) {
1447
- throw new errors.ProxyError("connection message", error);
1448
- } else {
1449
- throw error;
1450
- }
1451
- }
1452
- }
1453
-
1454
- /**
1455
- * Handle an action request to a actor
1456
- */
1457
- async function handleActionRequest(
1458
- c: HonoContext,
1459
- registryConfig: RegistryConfig,
1460
- _runConfig: RunConfig,
1461
- driver: ManagerDriver,
1462
- ): Promise<Response> {
1463
- try {
1464
- const actionName = c.req.param("action");
1465
- logger().debug("action call received", { actionName });
1466
-
1467
- const params = ConnectRequestSchema.safeParse({
1468
- query: getRequestQuery(c),
1469
- encoding: c.req.header(HEADER_ENCODING),
1470
- connParams: c.req.header(HEADER_CONN_PARAMS),
1471
- });
1472
-
1473
- if (!params.success) {
1474
- logger().error("invalid connection parameters", {
1475
- error: params.error,
1476
- });
1477
- throw new errors.InvalidRequest(params.error);
1478
- }
1479
-
1480
- // Parse connection parameters for authentication
1481
- const connParams = params.data.connParams
1482
- ? JSON.parse(params.data.connParams)
1483
- : undefined;
1484
-
1485
- // Authenticate the request
1486
- const authData = await authenticateEndpoint(
1487
- c,
1488
- driver,
1489
- registryConfig,
1490
- params.data.query,
1491
- ["action"],
1492
- connParams,
1493
- );
1494
-
1495
- // Get the actor ID
1496
- const { actorId } = await queryActor(c, params.data.query, driver);
1497
- logger().debug("found actor for action", { actorId });
1498
- invariant(actorId, "Missing actor ID");
1499
-
1500
- const url = new URL(
1501
- `http://actor/action/${encodeURIComponent(actionName)}`,
1502
- );
1503
-
1504
- // Always build fresh request to prevent forwarding unwanted headers
1505
- const proxyRequestHeaders = new Headers();
1506
- proxyRequestHeaders.set(HEADER_ENCODING, params.data.encoding);
1507
- if (params.data.connParams) {
1508
- proxyRequestHeaders.set(HEADER_CONN_PARAMS, params.data.connParams);
1509
- }
1510
- if (authData) {
1511
- proxyRequestHeaders.set(HEADER_AUTH_DATA, JSON.stringify(authData));
1512
- }
1513
-
1514
- const proxyRequest = new Request(url, {
1515
- method: "POST",
1516
- body: c.req.raw.body,
1517
- headers: proxyRequestHeaders,
1518
- });
1519
-
1520
- return await driver.proxyRequest(c, proxyRequest, actorId);
1521
- } catch (error) {
1522
- logger().error("error in action handler", { error: stringifyError(error) });
1523
-
1524
- // Use ProxyError if it's not already an ActorError
1525
- if (!errors.ActorError.isActorError(error)) {
1526
- throw new errors.ProxyError("Action call", error);
1527
- } else {
1528
- throw error;
1529
- }
1530
- }
1531
- }
1532
-
1533
- /**
1534
- * Handle the resolve request to get a actor ID from a query
1535
- */
1536
- async function handleResolveRequest(
1537
- c: HonoContext,
1538
- registryConfig: RegistryConfig,
1539
- driver: ManagerDriver,
1540
- ): Promise<Response> {
1541
- const encoding = getRequestEncoding(c.req);
1542
- logger().debug("resolve request encoding", { encoding });
1543
-
1544
- const params = ResolveRequestSchema.safeParse({
1545
- query: getRequestQuery(c),
1546
- connParams: c.req.header(HEADER_CONN_PARAMS),
1547
- });
1548
- if (!params.success) {
1549
- logger().error("invalid connection parameters", {
1550
- error: params.error,
1551
- });
1552
- throw new errors.InvalidRequest(params.error);
1553
- }
1554
-
1555
- // Parse connection parameters for authentication
1556
- const connParams = params.data.connParams
1557
- ? JSON.parse(params.data.connParams)
1558
- : undefined;
1559
-
1560
- const query = params.data.query;
1561
-
1562
- // Authenticate the request
1563
- await authenticateEndpoint(c, driver, registryConfig, query, [], connParams);
1564
-
1565
- // Get the actor ID
1566
- const { actorId } = await queryActor(c, query, driver);
1567
- logger().debug("resolved actor", { actorId });
1568
- invariant(actorId, "Missing actor ID");
1569
-
1570
- // Format response according to protocol
1571
- const response: protocol.HttpResolveResponse = {
1572
- actorId,
1573
- };
1574
- const serialized = serializeWithEncoding(
1575
- encoding,
1576
- response,
1577
- HTTP_RESOLVE_RESPONSE_VERSIONED,
1578
- );
1579
- return c.body(serialized);
1580
- }
1581
-
1582
- /**
1583
- * Handle raw HTTP requests to an actor
1584
- */
1585
- async function handleRawHttpRequest(
1586
- c: HonoContext,
1587
- registryConfig: RegistryConfig,
1588
- _runConfig: RunConfig,
1589
- driver: ManagerDriver,
1590
- ): Promise<Response> {
1591
- try {
1592
- const subpath = c.req.path.split("/raw/http/")[1] || "";
1593
- logger().debug("raw http request received", { subpath });
1594
-
1595
- // Get actor query from header (consistent with other endpoints)
1596
- const queryHeader = c.req.header(HEADER_ACTOR_QUERY);
1597
- if (!queryHeader) {
1598
- throw new errors.InvalidRequest("Missing actor query header");
1599
- }
1600
- const query: ActorQuery = JSON.parse(queryHeader);
1601
-
1602
- // Parse connection parameters for authentication
1603
- const connParamsHeader = c.req.header(HEADER_CONN_PARAMS);
1604
- const connParams = connParamsHeader
1605
- ? JSON.parse(connParamsHeader)
1606
- : undefined;
1607
-
1608
- // Authenticate the request
1609
- const authData = await authenticateEndpoint(
1610
- c,
1611
- driver,
1612
- registryConfig,
1613
- query,
1614
- ["action"],
1615
- connParams,
1616
- );
1617
-
1618
- // Get the actor ID
1619
- const { actorId } = await queryActor(c, query, driver);
1620
- logger().debug("found actor for raw http", { actorId });
1621
- invariant(actorId, "Missing actor ID");
1622
-
1623
- // Preserve the original URL's query parameters
1624
- const originalUrl = new URL(c.req.url);
1625
- const url = new URL(
1626
- `http://actor/raw/http/${subpath}${originalUrl.search}`,
1627
- );
1628
-
1629
- // Forward the request to the actor
1630
-
1631
- logger().debug("rewriting http url", {
1632
- from: c.req.url,
1633
- to: url,
1634
- });
1635
-
1636
- const proxyRequestHeaders = new Headers(c.req.raw.headers);
1637
- if (connParams) {
1638
- proxyRequestHeaders.set(HEADER_CONN_PARAMS, JSON.stringify(connParams));
1639
- }
1640
- if (authData) {
1641
- proxyRequestHeaders.set(HEADER_AUTH_DATA, JSON.stringify(authData));
1642
- }
1643
-
1644
- const proxyRequest = new Request(url, {
1645
- method: c.req.method,
1646
- headers: proxyRequestHeaders,
1647
- body: c.req.raw.body,
1648
- });
1649
-
1650
- return await driver.proxyRequest(c, proxyRequest, actorId);
1651
- } catch (error) {
1652
- logger().error("error in raw http handler", {
1653
- error: stringifyError(error),
1654
- });
1655
-
1656
- // Use ProxyError if it's not already an ActorError
1657
- if (!errors.ActorError.isActorError(error)) {
1658
- throw new errors.ProxyError("Raw HTTP request", error);
1659
- } else {
1660
- throw error;
1661
- }
1662
- }
1663
- }
387
+ // });
388
+ // }
1664
389
 
1665
- /**
1666
- * Handle raw WebSocket requests to an actor
1667
- */
1668
- async function handleRawWebSocketRequest(
1669
- c: HonoContext,
1670
- registryConfig: RegistryConfig,
1671
- runConfig: RunConfig,
1672
- driver: ManagerDriver,
1673
- ): Promise<Response> {
1674
- const upgradeWebSocket = runConfig.getUpgradeWebSocket?.();
1675
- if (!upgradeWebSocket) {
1676
- return c.text("WebSockets are not enabled for this driver.", 400);
1677
- }
1678
-
1679
- try {
1680
- const subpath = c.req.path.split("/raw/websocket/")[1] || "";
1681
- logger().debug("raw websocket request received", { subpath });
1682
-
1683
- // Parse protocols from Sec-WebSocket-Protocol header
1684
- const protocols = c.req.header("sec-websocket-protocol");
1685
- const {
1686
- queryRaw: queryFromProtocol,
1687
- connParamsRaw: connParamsFromProtocol,
1688
- } = parseWebSocketProtocols(protocols);
1689
-
1690
- if (!queryFromProtocol) {
1691
- throw new errors.InvalidRequest("Missing query in WebSocket protocol");
1692
- }
1693
- const query = JSON.parse(queryFromProtocol);
1694
-
1695
- // Parse connection parameters from protocol
1696
- let connParams: unknown;
1697
- if (connParamsFromProtocol) {
1698
- connParams = JSON.parse(connParamsFromProtocol);
390
+ if (runConfig.inspector?.enabled) {
391
+ if (!managerDriver.inspector) {
392
+ throw new Unsupported("inspector");
1699
393
  }
1700
-
1701
- // Authenticate the request
1702
- const authData = await authenticateEndpoint(
1703
- c,
1704
- driver,
1705
- registryConfig,
1706
- query,
1707
- ["action"],
1708
- connParams,
1709
- );
1710
-
1711
- // Get the actor ID
1712
- const { actorId } = await queryActor(c, query, driver);
1713
- logger().debug("found actor for raw websocket", { actorId });
1714
- invariant(actorId, "Missing actor ID");
1715
-
1716
- logger().debug("using custom proxy mode for raw websocket");
1717
-
1718
- // Preserve the original URL's query parameters
1719
- const originalUrl = new URL(c.req.url);
1720
- const proxyPath = `${PATH_RAW_WEBSOCKET_PREFIX}${subpath}${originalUrl.search}`;
1721
-
1722
- logger().debug("manager router proxyWebSocket", {
1723
- originalUrl: c.req.url,
1724
- subpath,
1725
- search: originalUrl.search,
1726
- proxyPath,
1727
- });
1728
-
1729
- // For raw WebSocket, we need to use proxyWebSocket instead of proxyRequest
1730
- return await driver.proxyWebSocket(
1731
- c,
1732
- proxyPath,
1733
- actorId,
1734
- "json", // Default encoding for raw WebSocket
1735
- connParams,
1736
- authData,
394
+ router.route(
395
+ "/inspect",
396
+ new Hono<{ Variables: { inspector: any } }>()
397
+ .use(corsMiddleware(runConfig.inspector.cors))
398
+ .use(secureInspector(runConfig))
399
+ .use((c, next) => {
400
+ c.set("inspector", managerDriver.inspector!);
401
+ return next();
402
+ })
403
+ .route("/", createManagerInspectorRouter()),
1737
404
  );
1738
- } catch (error) {
1739
- // If we receive an error during setup, we send the error and close the socket immediately
1740
- //
1741
- // We have to return the error over WS since WebSocket clients cannot read vanilla HTTP responses
1742
-
1743
- const { code } = deconstructError(error, logger(), {
1744
- wsEvent: "setup",
1745
- });
1746
-
1747
- return await upgradeWebSocket(() => ({
1748
- onOpen: (_evt: unknown, ws: WSContext) => {
1749
- // Close with message so we can see the error on the client
1750
- ws.close(1011, code);
1751
- },
1752
- }))(c, noopNext());
1753
405
  }
1754
- }
1755
406
 
1756
- function universalActorProxy({
1757
- registryConfig,
1758
- runConfig,
1759
- driver,
1760
- }: {
1761
- registryConfig: RegistryConfig;
1762
- runConfig: RunConfig;
1763
- driver: ManagerDriver;
1764
- }): MiddlewareHandler {
1765
- return async (c, _next) => {
1766
- if (c.req.header("upgrade") === "websocket") {
1767
- return handleRawWebSocketRequest(c, registryConfig, runConfig, driver);
1768
- } else {
1769
- const queryHeader = c.req.header(HEADER_ACTOR_QUERY);
1770
- if (!queryHeader) {
1771
- throw new errors.InvalidRequest("Missing actor query header");
1772
- }
1773
- const query = ActorQuerySchema.parse(JSON.parse(queryHeader));
1774
-
1775
- const { actorId } = await queryActor(c, query, driver);
407
+ // Error handling
408
+ router.notFound(handleRouteNotFound);
409
+ router.onError(handleRouteError);
1776
410
 
1777
- const url = new URL(c.req.url);
1778
- url.hostname = "actor";
1779
- url.pathname = url.pathname
1780
- .replace(new RegExp(`^${runConfig.basePath}`, ""), "")
1781
- .replace(/^\/?registry\/actors/, "")
1782
- .replace(/^\/?actors/, ""); // Remove /registry prefix if present
1783
-
1784
- const proxyRequest = new Request(url, {
1785
- method: c.req.method,
1786
- headers: c.req.raw.headers,
1787
- body: c.req.raw.body,
1788
- });
1789
- return await driver.proxyRequest(c, proxyRequest, actorId);
1790
- }
1791
- };
411
+ return { router: router as Hono, openapi: router };
1792
412
  }