rivetkit 2.0.3 → 2.0.5

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 (237) 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-4NSUQZ2H.js → chunk-2MD57QF4.js} +119 -115
  10. package/dist/tsup/chunk-2MD57QF4.js.map +1 -0
  11. package/dist/tsup/{chunk-GIR3AFFI.cjs → chunk-5QGQK44L.cjs} +103 -44
  12. package/dist/tsup/chunk-5QGQK44L.cjs.map +1 -0
  13. package/dist/tsup/chunk-5YTI25C3.cjs +250 -0
  14. package/dist/tsup/chunk-5YTI25C3.cjs.map +1 -0
  15. package/dist/tsup/chunk-B2QGJGZQ.js +338 -0
  16. package/dist/tsup/chunk-B2QGJGZQ.js.map +1 -0
  17. package/dist/tsup/{chunk-3H7O2A7I.js → chunk-CFFKMUYH.js} +61 -22
  18. package/dist/tsup/chunk-CFFKMUYH.js.map +1 -0
  19. package/dist/tsup/{chunk-FLMTTN27.js → chunk-CKA54YQN.js} +15 -8
  20. package/dist/tsup/chunk-CKA54YQN.js.map +1 -0
  21. package/dist/tsup/chunk-D7NWUCRK.cjs +20 -0
  22. package/dist/tsup/chunk-D7NWUCRK.cjs.map +1 -0
  23. package/dist/tsup/{chunk-FCCPJNMA.cjs → chunk-FGFT4FVX.cjs} +12 -27
  24. package/dist/tsup/chunk-FGFT4FVX.cjs.map +1 -0
  25. package/dist/tsup/chunk-I5VTWPHW.js +20 -0
  26. package/dist/tsup/chunk-I5VTWPHW.js.map +1 -0
  27. package/dist/tsup/{chunk-6WKQDDUD.cjs → chunk-IRMBWX36.cjs} +146 -142
  28. package/dist/tsup/chunk-IRMBWX36.cjs.map +1 -0
  29. package/dist/tsup/chunk-L7QRXNWP.js +6562 -0
  30. package/dist/tsup/chunk-L7QRXNWP.js.map +1 -0
  31. package/dist/tsup/{chunk-R2OPSKIV.cjs → chunk-LZIBTLEY.cjs} +20 -13
  32. package/dist/tsup/chunk-LZIBTLEY.cjs.map +1 -0
  33. package/dist/tsup/chunk-MRZS2J4X.cjs +6562 -0
  34. package/dist/tsup/chunk-MRZS2J4X.cjs.map +1 -0
  35. package/dist/tsup/{chunk-PO4VLDWA.js → chunk-PG3K2LI7.js} +3 -5
  36. package/dist/tsup/chunk-PG3K2LI7.js.map +1 -0
  37. package/dist/tsup/{chunk-TZJKSBUQ.cjs → chunk-PHSQJ6QI.cjs} +3 -5
  38. package/dist/tsup/chunk-PHSQJ6QI.cjs.map +1 -0
  39. package/dist/tsup/chunk-RM2SVURR.cjs +338 -0
  40. package/dist/tsup/chunk-RM2SVURR.cjs.map +1 -0
  41. package/dist/tsup/{chunk-OGAPU3UG.cjs → chunk-WADSS5X4.cjs} +66 -27
  42. package/dist/tsup/chunk-WADSS5X4.cjs.map +1 -0
  43. package/dist/tsup/chunk-WNGOBAA7.js +250 -0
  44. package/dist/tsup/chunk-WNGOBAA7.js.map +1 -0
  45. package/dist/tsup/{chunk-INGJP237.js → chunk-YPZFLUO6.js} +103 -44
  46. package/dist/tsup/chunk-YPZFLUO6.js.map +1 -0
  47. package/dist/tsup/{chunk-6PDXBYI5.js → chunk-YW6Y6VNE.js} +8 -23
  48. package/dist/tsup/chunk-YW6Y6VNE.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-BvE-Oq7t.d.ts} +215 -234
  63. package/dist/tsup/{connection-BR_Ve4ku.d.cts → connection-DTzmWwU5.d.cts} +215 -234
  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 +615 -1357
  70. package/dist/tsup/driver-test-suite/mod.cjs.map +1 -1
  71. package/dist/tsup/driver-test-suite/mod.d.cts +12 -6
  72. package/dist/tsup/driver-test-suite/mod.d.ts +12 -6
  73. package/dist/tsup/driver-test-suite/mod.js +1334 -2076
  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-CctffZNL.d.cts} +2 -3
  86. package/dist/tsup/{router-endpoints-AYkXG8Tl.d.cts → router-endpoints-DFm1BglJ.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 +5 -3
  95. package/dist/tsup/utils.cjs.map +1 -1
  96. package/dist/tsup/utils.d.cts +19 -2
  97. package/dist/tsup/utils.d.ts +19 -2
  98. package/dist/tsup/utils.js +4 -2
  99. package/package.json +6 -6
  100. package/src/actor/action.ts +1 -5
  101. package/src/actor/config.ts +27 -295
  102. package/src/actor/connection.ts +9 -12
  103. package/src/actor/context.ts +1 -4
  104. package/src/actor/definition.ts +7 -11
  105. package/src/actor/errors.ts +98 -36
  106. package/src/actor/generic-conn-driver.ts +28 -16
  107. package/src/actor/instance.ts +177 -133
  108. package/src/actor/log.ts +4 -13
  109. package/src/actor/mod.ts +0 -5
  110. package/src/actor/protocol/old.ts +42 -26
  111. package/src/actor/protocol/serde.ts +1 -1
  112. package/src/actor/router-endpoints.ts +47 -39
  113. package/src/actor/router.ts +22 -19
  114. package/src/actor/unstable-react.ts +1 -1
  115. package/src/actor/utils.ts +6 -2
  116. package/src/client/actor-common.ts +1 -1
  117. package/src/client/actor-conn.ts +152 -91
  118. package/src/client/actor-handle.ts +85 -25
  119. package/src/client/actor-query.ts +65 -0
  120. package/src/client/client.ts +29 -98
  121. package/src/client/config.ts +44 -0
  122. package/src/client/errors.ts +1 -0
  123. package/src/client/log.ts +2 -4
  124. package/src/client/mod.ts +16 -12
  125. package/src/client/raw-utils.ts +82 -25
  126. package/src/client/utils.ts +5 -3
  127. package/src/common/fake-event-source.ts +10 -9
  128. package/src/common/inline-websocket-adapter2.ts +39 -30
  129. package/src/common/log.ts +176 -101
  130. package/src/common/logfmt.ts +21 -30
  131. package/src/common/router.ts +12 -19
  132. package/src/common/utils.ts +27 -13
  133. package/src/common/websocket.ts +0 -1
  134. package/src/driver-helpers/mod.ts +1 -1
  135. package/src/driver-test-suite/log.ts +1 -3
  136. package/src/driver-test-suite/mod.ts +87 -61
  137. package/src/driver-test-suite/test-inline-client-driver.ts +441 -255
  138. package/src/driver-test-suite/tests/actor-error-handling.ts +4 -12
  139. package/src/driver-test-suite/tests/actor-handle.ts +33 -0
  140. package/src/driver-test-suite/tests/actor-inspector.ts +2 -1
  141. package/src/driver-test-suite/tests/manager-driver.ts +5 -3
  142. package/src/driver-test-suite/tests/raw-http-direct-registry.ts +227 -226
  143. package/src/driver-test-suite/tests/raw-websocket-direct-registry.ts +393 -392
  144. package/src/driver-test-suite/tests/request-access.ts +112 -126
  145. package/src/driver-test-suite/utils.ts +10 -6
  146. package/src/drivers/default.ts +7 -4
  147. package/src/drivers/engine/actor-driver.ts +22 -13
  148. package/src/drivers/engine/config.ts +2 -10
  149. package/src/drivers/engine/kv.ts +1 -1
  150. package/src/drivers/engine/log.ts +1 -3
  151. package/src/drivers/engine/mod.ts +2 -3
  152. package/src/drivers/file-system/actor.ts +1 -1
  153. package/src/drivers/file-system/global-state.ts +36 -21
  154. package/src/drivers/file-system/log.ts +1 -3
  155. package/src/drivers/file-system/manager.ts +33 -15
  156. package/src/inspector/config.ts +9 -4
  157. package/src/inspector/log.ts +1 -1
  158. package/src/inspector/manager.ts +2 -2
  159. package/src/inspector/utils.ts +1 -1
  160. package/src/manager/driver.ts +10 -2
  161. package/src/manager/hono-websocket-adapter.ts +21 -12
  162. package/src/manager/log.ts +2 -4
  163. package/src/manager/mod.ts +1 -1
  164. package/src/manager/router.ts +378 -1390
  165. package/src/manager-api/routes/actors-create.ts +16 -0
  166. package/src/manager-api/routes/actors-delete.ts +4 -0
  167. package/src/manager-api/routes/actors-get-by-id.ts +7 -0
  168. package/src/manager-api/routes/actors-get-or-create-by-id.ts +29 -0
  169. package/src/manager-api/routes/actors-get.ts +7 -0
  170. package/src/manager-api/routes/common.ts +18 -0
  171. package/src/mod.ts +0 -2
  172. package/src/registry/config.ts +1 -1
  173. package/src/registry/log.ts +2 -4
  174. package/src/registry/mod.ts +63 -34
  175. package/src/registry/run-config.ts +39 -26
  176. package/src/registry/serve.ts +4 -5
  177. package/src/remote-manager-driver/actor-http-client.ts +74 -0
  178. package/src/remote-manager-driver/actor-websocket-client.ts +64 -0
  179. package/src/remote-manager-driver/api-endpoints.ts +79 -0
  180. package/src/remote-manager-driver/api-utils.ts +46 -0
  181. package/src/remote-manager-driver/log.ts +5 -0
  182. package/src/remote-manager-driver/mod.ts +275 -0
  183. package/src/{drivers/engine → remote-manager-driver}/ws-proxy.ts +24 -14
  184. package/src/serde.ts +8 -2
  185. package/src/test/log.ts +1 -3
  186. package/src/test/mod.ts +17 -16
  187. package/src/utils.ts +53 -0
  188. package/dist/tsup/chunk-2CRLFV6Z.cjs +0 -202
  189. package/dist/tsup/chunk-2CRLFV6Z.cjs.map +0 -1
  190. package/dist/tsup/chunk-3H7O2A7I.js.map +0 -1
  191. package/dist/tsup/chunk-42I3OZ3Q.js +0 -15
  192. package/dist/tsup/chunk-42I3OZ3Q.js.map +0 -1
  193. package/dist/tsup/chunk-4NSUQZ2H.js.map +0 -1
  194. package/dist/tsup/chunk-6PDXBYI5.js.map +0 -1
  195. package/dist/tsup/chunk-6WKQDDUD.cjs.map +0 -1
  196. package/dist/tsup/chunk-CTBOSFUH.cjs +0 -116
  197. package/dist/tsup/chunk-CTBOSFUH.cjs.map +0 -1
  198. package/dist/tsup/chunk-EGVZZFE2.js +0 -2857
  199. package/dist/tsup/chunk-EGVZZFE2.js.map +0 -1
  200. package/dist/tsup/chunk-FCCPJNMA.cjs.map +0 -1
  201. package/dist/tsup/chunk-FLMTTN27.js.map +0 -1
  202. package/dist/tsup/chunk-GIR3AFFI.cjs.map +0 -1
  203. package/dist/tsup/chunk-INGJP237.js.map +0 -1
  204. package/dist/tsup/chunk-KJCJLKRM.js +0 -116
  205. package/dist/tsup/chunk-KJCJLKRM.js.map +0 -1
  206. package/dist/tsup/chunk-KUPQZYUQ.cjs +0 -15
  207. package/dist/tsup/chunk-KUPQZYUQ.cjs.map +0 -1
  208. package/dist/tsup/chunk-O2MBYIXO.cjs +0 -2857
  209. package/dist/tsup/chunk-O2MBYIXO.cjs.map +0 -1
  210. package/dist/tsup/chunk-OGAPU3UG.cjs.map +0 -1
  211. package/dist/tsup/chunk-OV6AYD4S.js +0 -4406
  212. package/dist/tsup/chunk-OV6AYD4S.js.map +0 -1
  213. package/dist/tsup/chunk-PO4VLDWA.js.map +0 -1
  214. package/dist/tsup/chunk-R2OPSKIV.cjs.map +0 -1
  215. package/dist/tsup/chunk-TZJKSBUQ.cjs.map +0 -1
  216. package/dist/tsup/chunk-UBUC5C3G.cjs +0 -189
  217. package/dist/tsup/chunk-UBUC5C3G.cjs.map +0 -1
  218. package/dist/tsup/chunk-UIM22YJL.cjs +0 -4406
  219. package/dist/tsup/chunk-UIM22YJL.cjs.map +0 -1
  220. package/dist/tsup/chunk-URVFQMYI.cjs +0 -230
  221. package/dist/tsup/chunk-URVFQMYI.cjs.map +0 -1
  222. package/dist/tsup/chunk-UVUPOS46.js +0 -230
  223. package/dist/tsup/chunk-UVUPOS46.js.map +0 -1
  224. package/dist/tsup/chunk-VRRHBNJC.js +0 -189
  225. package/dist/tsup/chunk-VRRHBNJC.js.map +0 -1
  226. package/dist/tsup/chunk-XFSS33EQ.js +0 -202
  227. package/dist/tsup/chunk-XFSS33EQ.js.map +0 -1
  228. package/src/client/http-client-driver.ts +0 -326
  229. package/src/driver-test-suite/tests/actor-auth.ts +0 -591
  230. package/src/drivers/engine/api-endpoints.ts +0 -128
  231. package/src/drivers/engine/api-utils.ts +0 -70
  232. package/src/drivers/engine/manager-driver.ts +0 -391
  233. package/src/inline-client-driver/log.ts +0 -7
  234. package/src/inline-client-driver/mod.ts +0 -385
  235. package/src/manager/auth.ts +0 -121
  236. /package/src/{drivers/engine → actor}/keys.test.ts +0 -0
  237. /package/src/{drivers/engine → actor}/keys.ts +0 -0
@@ -1,127 +1,52 @@
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";
3
+ import { Hono } from "hono";
4
+ import { cors as corsMiddleware } from "hono/cors";
5
+ import { createMiddleware } from "hono/factory";
10
6
  import type { WSContext } from "hono/ws";
11
7
  import invariant from "invariant";
12
- import type { CloseEvent, MessageEvent, WebSocket } from "ws";
13
8
  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
9
  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";
10
+ ActorNotFound,
11
+ FeatureNotImplemented,
12
+ MissingActorHeader,
13
+ Unsupported,
14
+ WebSocketsNotEnabled,
15
+ } from "@/actor/errors";
16
+ import type { Encoding } from "@/client/mod";
34
17
  import {
35
18
  handleRouteError,
36
19
  handleRouteNotFound,
37
20
  loggerMiddleware,
38
21
  } from "@/common/router";
39
- import {
40
- type DeconstructedError,
41
- deconstructError,
42
- noopNext,
43
- stringifyError,
44
- } from "@/common/utils";
22
+ import { deconstructError, noopNext } from "@/common/utils";
23
+ import { HEADER_ACTOR_ID } from "@/driver-helpers/mod";
24
+ import type {
25
+ TestInlineDriverCallRequest,
26
+ TestInlineDriverCallResponse,
27
+ } from "@/driver-test-suite/test-inline-client-driver";
45
28
  import { createManagerInspectorRouter } from "@/inspector/manager";
46
29
  import { secureInspector } from "@/inspector/utils";
47
- import type { UpgradeWebSocketArgs } from "@/mod";
30
+ import {
31
+ type ActorsCreateRequest,
32
+ ActorsCreateRequestSchema,
33
+ ActorsCreateResponseSchema,
34
+ } from "@/manager-api/routes/actors-create";
35
+ import { ActorsDeleteResponseSchema } from "@/manager-api/routes/actors-delete";
36
+ import { ActorsGetResponseSchema } from "@/manager-api/routes/actors-get";
37
+ import { ActorsGetByIdResponseSchema } from "@/manager-api/routes/actors-get-by-id";
38
+ import {
39
+ type ActorsGetOrCreateByIdRequest,
40
+ ActorsGetOrCreateByIdRequestSchema,
41
+ ActorsGetOrCreateByIdResponseSchema,
42
+ } from "@/manager-api/routes/actors-get-or-create-by-id";
43
+ import { RivetIdSchema } from "@/manager-api/routes/common";
44
+ import type { UniversalWebSocket, UpgradeWebSocketArgs } from "@/mod";
48
45
  import type { RegistryConfig } from "@/registry/config";
49
46
  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";
47
+ import { stringifyError } from "@/utils";
58
48
  import type { ManagerDriver } from "./driver";
59
49
  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
50
 
126
51
  function buildOpenApiResponses<T>(schema: T, validateBody: boolean) {
127
52
  return {
@@ -144,17 +69,9 @@ function buildOpenApiResponses<T>(schema: T, validateBody: boolean) {
144
69
  };
145
70
  }
146
71
 
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
72
  export function createManagerRouter(
155
73
  registryConfig: RegistryConfig,
156
74
  runConfig: RunConfig,
157
- inlineClientDriver: ClientDriver,
158
75
  managerDriver: ManagerDriver,
159
76
  validateBody: boolean,
160
77
  ): { router: Hono; openapi: OpenAPIHono } {
@@ -164,443 +81,323 @@ export function createManagerRouter(
164
81
 
165
82
  router.use("*", loggerMiddleware(logger()));
166
83
 
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();
84
+ const cors = runConfig.cors
85
+ ? corsMiddleware(runConfig.cors)
86
+ : createMiddleware((_c, next) => next());
87
+
88
+ // Actor proxy middleware - intercept requests with x-rivet-target=actor
89
+ router.use("*", cors, async (c, next) => {
90
+ const target = c.req.header("x-rivet-target");
91
+ const actorId = c.req.header("x-rivet-actor");
92
+
93
+ if (target === "actor") {
94
+ if (!actorId) {
95
+ throw new MissingActorHeader();
182
96
  }
183
97
 
184
- return cors({
185
- ...(runConfig.cors ?? {}),
186
- ...(runConfig.inspector?.cors ?? {}),
187
- origin: (origin, c) => {
188
- const inspectorOrigin = runConfig.inspector?.cors?.origin;
189
-
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
- }
98
+ logger().debug({
99
+ msg: "proxying request to actor",
100
+ actorId,
101
+ path: c.req.path,
102
+ method: c.req.method,
103
+ });
201
104
 
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
- }
105
+ // Handle WebSocket upgrade
106
+ if (c.req.header("upgrade") === "websocket") {
107
+ const upgradeWebSocket = runConfig.getUpgradeWebSocket?.();
108
+ if (!upgradeWebSocket) {
109
+ throw new WebSocketsNotEnabled();
110
+ }
210
111
 
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
- }
112
+ // For WebSocket, use the driver's proxyWebSocket method
113
+ // Extract any additional headers that might be needed
114
+ const encoding =
115
+ c.req.header("X-RivetKit-Encoding") ||
116
+ c.req.header("x-rivet-encoding") ||
117
+ "json";
118
+ const connParams =
119
+ c.req.header("X-RivetKit-Conn-Params") ||
120
+ c.req.header("x-rivet-conn-params");
121
+ const authData =
122
+ c.req.header("X-RivetKit-Auth-Data") ||
123
+ c.req.header("x-rivet-auth-data");
124
+
125
+ // Include query string if present
126
+ const pathWithQuery = c.req.url.includes("?")
127
+ ? c.req.path + c.req.url.substring(c.req.url.indexOf("?"))
128
+ : c.req.path;
129
+
130
+ return await managerDriver.proxyWebSocket(
131
+ c,
132
+ pathWithQuery,
133
+ actorId,
134
+ encoding as any, // Will be validated by driver
135
+ connParams ? JSON.parse(connParams) : undefined,
136
+ authData ? JSON.parse(authData) : undefined,
137
+ );
138
+ }
221
139
 
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
- }
140
+ // Handle regular HTTP requests
141
+ // Preserve all headers except the routing headers
142
+ const proxyHeaders = new Headers(c.req.raw.headers);
143
+ proxyHeaders.delete("x-rivet-target");
144
+ proxyHeaders.delete("x-rivet-actor");
228
145
 
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
- }
146
+ // Build the proxy request with the actor URL format
147
+ const url = new URL(c.req.url);
148
+ const proxyUrl = new URL(`http://actor${url.pathname}${url.search}`);
149
+
150
+ const proxyRequest = new Request(proxyUrl, {
151
+ method: c.req.raw.method,
152
+ headers: proxyHeaders,
153
+ body: c.req.raw.body,
154
+ signal: c.req.raw.signal,
155
+ });
156
+
157
+ return await managerDriver.proxyRequest(c, proxyRequest, actorId);
158
+ }
159
+
160
+ return next();
161
+ });
245
162
 
246
163
  // GET /
247
- router.get("/", (c: HonoContext) => {
164
+ router.get("/", cors, (c) => {
248
165
  return c.text(
249
- "This is an RivetKit registry.\n\nLearn more at https://rivetkit.org",
166
+ "This is a RivetKit server.\n\nLearn more at https://rivetkit.org",
250
167
  );
251
168
  });
252
169
 
253
- // POST /actors/resolve
170
+ // GET /actors/by-id
254
171
  {
255
- const ResolveQuerySchema = z
256
- .object({
257
- query: z.any().openapi({
258
- example: { getForId: { actorId: "actor-123" } },
172
+ const route = createRoute({
173
+ middleware: [cors],
174
+ method: "get",
175
+ path: "/actors/by-id",
176
+ request: {
177
+ query: z.object({
178
+ name: z.string(),
179
+ key: z.string(),
259
180
  }),
260
- })
261
- .openapi("ResolveQuery");
181
+ },
182
+ responses: buildOpenApiResponses(
183
+ ActorsGetByIdResponseSchema,
184
+ validateBody,
185
+ ),
186
+ });
262
187
 
263
- const ResolveResponseSchema = z
264
- .object({
265
- i: z.string().openapi({
266
- example: "actor-123",
267
- }),
268
- })
269
- .openapi("ResolveResponse");
188
+ router.openapi(route, async (c) => {
189
+ const { name, key } = c.req.valid("query");
270
190
 
271
- const resolveRoute = createRoute({
272
- method: "post",
273
- path: "/actors/resolve",
191
+ // Get actor by key from the driver
192
+ const actorOutput = await managerDriver.getWithKey({
193
+ c,
194
+ name,
195
+ key: [key], // Convert string to ActorKey array
196
+ });
197
+
198
+ return c.json({
199
+ actor_id: actorOutput?.actorId || null,
200
+ });
201
+ });
202
+ }
203
+
204
+ // PUT /actors/by-id
205
+ {
206
+ const route = createRoute({
207
+ cors: [cors],
208
+ method: "put",
209
+ path: "/actors/by-id",
274
210
  request: {
275
211
  body: {
276
212
  content: validateBody
277
213
  ? {
278
214
  "application/json": {
279
- schema: ResolveQuerySchema,
215
+ schema: ActorsGetOrCreateByIdRequestSchema,
280
216
  },
281
217
  }
282
218
  : {},
283
219
  },
284
- headers: z.object({
285
- [HEADER_ACTOR_QUERY]: OPENAPI_ACTOR_QUERY,
286
- }),
287
220
  },
288
- responses: buildOpenApiResponses(ResolveResponseSchema, validateBody),
221
+ responses: buildOpenApiResponses(
222
+ ActorsGetOrCreateByIdResponseSchema,
223
+ validateBody,
224
+ ),
289
225
  });
290
226
 
291
- router.openapi(resolveRoute, (c) =>
292
- handleResolveRequest(c, registryConfig, managerDriver),
293
- );
294
- }
227
+ router.openapi(route, async (c) => {
228
+ const body = validateBody
229
+ ? await c.req.json<ActorsGetOrCreateByIdRequest>()
230
+ : await c.req.json();
295
231
 
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
- );
232
+ // Parse and validate the request body if validation is enabled
233
+ if (validateBody) {
234
+ ActorsGetOrCreateByIdRequestSchema.parse(body);
307
235
  }
308
236
 
309
- return next();
310
- });
237
+ // Check if actor already exists
238
+ const existingActor = await managerDriver.getWithKey({
239
+ c,
240
+ name: body.name,
241
+ key: [body.key], // Convert string to ActorKey array
242
+ });
311
243
 
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
- });
244
+ if (existingActor) {
245
+ return c.json({
246
+ actor_id: existingActor.actorId,
247
+ created: false,
248
+ });
249
+ }
250
+
251
+ // Create new actor
252
+ const newActor = await managerDriver.getOrCreateWithKey({
253
+ c,
254
+ name: body.name,
255
+ key: [body.key], // Convert string to ActorKey array
256
+ input: body.input
257
+ ? cbor.decode(Buffer.from(body.input, "base64"))
258
+ : undefined,
259
+ region: undefined, // Not provided in the request schema
260
+ });
322
261
 
323
- router.openapi(wsRoute, () => {
324
- throw new Error("Should be unreachable");
262
+ return c.json({
263
+ actor_id: newActor.actorId,
264
+ created: true,
265
+ });
325
266
  });
326
267
  }
327
268
 
328
- // GET /actors/connect/sse
269
+ // GET /actors/{actor_id}
329
270
  {
330
- const sseRoute = createRoute({
271
+ const route = createRoute({
272
+ middleware: [cors],
331
273
  method: "get",
332
- path: "/actors/connect/sse",
274
+ path: "/actors/{actor_id}",
333
275
  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(),
276
+ params: z.object({
277
+ actor_id: RivetIdSchema,
338
278
  }),
339
279
  },
340
- responses: {
341
- 200: {
342
- description: "SSE stream",
343
- content: {
344
- "text/event-stream": {
345
- schema: z.unknown(),
346
- },
347
- },
348
- },
349
- },
280
+ responses: buildOpenApiResponses(ActorsGetResponseSchema, validateBody),
350
281
  });
351
282
 
352
- router.openapi(sseRoute, (c) =>
353
- handleSseConnectRequest(c, registryConfig, runConfig, managerDriver),
354
- );
355
- }
283
+ router.openapi(route, async (c) => {
284
+ const { actor_id } = c.req.valid("param");
356
285
 
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");
286
+ // Get actor by ID from the driver
287
+ const actorOutput = await managerDriver.getForId({
288
+ c,
289
+ name: "", // TODO: The API doesn't provide the name, this may need to be resolved
290
+ actorId: actor_id,
291
+ });
370
292
 
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");
293
+ if (!actorOutput) {
294
+ throw new ActorNotFound(actor_id);
295
+ }
384
296
 
385
- const ActionResponseSchema = z.any().openapi("ActionResponse");
297
+ // Transform ActorOutput to match ActorSchema
298
+ // Note: Some fields are not available from the driver and need defaults
299
+ const actor = {
300
+ actor_id: actorOutput.actorId,
301
+ name: actorOutput.name,
302
+ key: actorOutput.key,
303
+ namespace_id: "default", // Assert default namespace
304
+ runner_name_selector: "rivetkit", // Assert rivetkit runner
305
+ create_ts: Date.now(), // Not available from driver
306
+ connectable_ts: null,
307
+ destroy_ts: null,
308
+ sleep_ts: null,
309
+ start_ts: null,
310
+ };
386
311
 
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),
312
+ return c.json({ actor });
407
313
  });
408
-
409
- router.openapi(actionRoute, (c) =>
410
- handleActionRequest(c, registryConfig, runConfig, managerDriver),
411
- );
412
314
  }
413
315
 
414
- // POST /actors/message
316
+ // POST /actors
415
317
  {
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({
318
+ const route = createRoute({
319
+ middleware: [cors],
429
320
  method: "post",
430
- path: "/actors/message",
321
+ path: "/actors",
431
322
  request: {
432
323
  body: {
433
324
  content: validateBody
434
325
  ? {
435
326
  "application/json": {
436
- schema: ConnectionMessageRequestSchema,
327
+ schema: ActorsCreateRequestSchema,
437
328
  },
438
329
  }
439
330
  : {},
440
331
  },
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
332
  },
448
333
  responses: buildOpenApiResponses(
449
- ConnectionMessageResponseSchema,
334
+ ActorsCreateResponseSchema,
450
335
  validateBody,
451
336
  ),
452
337
  });
453
338
 
454
- router.openapi(messageRoute, (c) =>
455
- handleMessageRequest(c, registryConfig, runConfig, managerDriver),
456
- );
457
- }
339
+ router.openapi(route, async (c) => {
340
+ const body = validateBody
341
+ ? await c.req.json<ActorsCreateRequest>()
342
+ : await c.req.json();
458
343
 
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
- );
344
+ // Parse and validate the request body if validation is enabled
345
+ if (validateBody) {
346
+ ActorsCreateRequestSchema.parse(body);
541
347
  }
542
348
 
543
- return next();
544
- });
349
+ // Create actor using the driver
350
+ const actorOutput = await managerDriver.createActor({
351
+ c,
352
+ name: body.name,
353
+ key: [body.key || crypto.randomUUID()], // Generate key if not provided, convert to ActorKey array
354
+ input: body.input
355
+ ? cbor.decode(Buffer.from(body.input, "base64"))
356
+ : undefined,
357
+ region: undefined, // Not provided in the request schema
358
+ });
545
359
 
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
- });
360
+ // Transform ActorOutput to match ActorSchema
361
+ const actor = {
362
+ actor_id: actorOutput.actorId,
363
+ name: actorOutput.name,
364
+ key: actorOutput.key,
365
+ namespace_id: "default", // Assert default namespace
366
+ runner_name_selector: "rivetkit", // Assert rivetkit runner
367
+ create_ts: Date.now(),
368
+ connectable_ts: null,
369
+ destroy_ts: null,
370
+ sleep_ts: null,
371
+ start_ts: null,
372
+ };
563
373
 
564
- router.openapi(rawWebSocketRoute, () => {
565
- throw new Error("Should be unreachable");
374
+ return c.json({ actor });
566
375
  });
567
376
  }
568
377
 
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
- }
378
+ // TODO:
379
+ // // DELETE /actors/{actor_id}
380
+ // {
381
+ // const route = createRoute({
382
+ // middleware: [cors],
383
+ // method: "delete",
384
+ // path: "/actors/{actor_id}",
385
+ // request: {
386
+ // params: z.object({
387
+ // actor_id: RivetIdSchema,
388
+ // }),
389
+ // },
390
+ // responses: buildOpenApiResponses(
391
+ // ActorsDeleteResponseSchema,
392
+ // validateBody,
393
+ // ),
394
+ // });
395
+ //
396
+ // router.openapi(route, async (c) => {
397
+ // const { actor_id } = c.req.valid("param");
398
+ //
399
+ // });
400
+ // }
604
401
 
605
402
  if (registryConfig.test.enabled) {
606
403
  // Add HTTP endpoint to test the inline client
@@ -612,7 +409,8 @@ export function createManagerRouter(
612
409
  const { encoding, transport, method, args }: TestInlineDriverCallRequest =
613
410
  cbor.decode(new Uint8Array(buffer));
614
411
 
615
- logger().debug("received inline request", {
412
+ logger().debug({
413
+ msg: "received inline request",
616
414
  encoding,
617
415
  transport,
618
416
  method,
@@ -622,9 +420,7 @@ export function createManagerRouter(
622
420
  // Forward inline driver request
623
421
  let response: TestInlineDriverCallResponse<unknown>;
624
422
  try {
625
- const output = await ((inlineClientDriver as any)[method] as any)(
626
- ...args,
627
- );
423
+ const output = await ((managerDriver as any)[method] as any)(...args);
628
424
  response = { ok: output };
629
425
  } catch (rawErr) {
630
426
  const err = deconstructError(rawErr, logger(), {}, true);
@@ -634,144 +430,84 @@ export function createManagerRouter(
634
430
  return c.body(cbor.encode(response));
635
431
  });
636
432
 
637
- router.get(".test/inline-driver/connect-websocket", async (c) => {
433
+ router.get(".test/inline-driver/connect-websocket/*", async (c) => {
638
434
  const upgradeWebSocket = runConfig.getUpgradeWebSocket?.();
639
435
  invariant(upgradeWebSocket, "websockets not supported on this platform");
640
436
 
641
437
  return upgradeWebSocket(async (c: any) => {
642
438
  const {
643
- actorQuery: actorQueryRaw,
439
+ path,
440
+ actorId,
644
441
  params: paramsRaw,
645
442
  encodingKind,
646
443
  } = c.req.query() as {
647
- actorQuery: string;
444
+ path: string;
445
+ actorId: string;
648
446
  params?: string;
649
447
  encodingKind: Encoding;
650
448
  };
651
- const actorQuery = JSON.parse(actorQueryRaw);
652
449
  const params =
653
450
  paramsRaw !== undefined ? JSON.parse(paramsRaw) : undefined;
654
451
 
655
- logger().debug("received test inline driver websocket", {
656
- actorQuery,
452
+ logger().debug({
453
+ msg: "received test inline driver websocket",
454
+ actorId,
657
455
  params,
658
456
  encodingKind,
457
+ path: path,
659
458
  });
660
459
 
661
460
  // 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,
461
+ const clientWsPromise = managerDriver.openWebSocket(
702
462
  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,
463
+ actorId,
711
464
  encodingKind,
712
465
  params,
713
- path,
714
- protocols,
715
- undefined,
716
466
  );
717
467
 
718
- logger().debug("calling createTestWebSocketProxy");
719
- return await createTestWebSocketProxy(clientWsPromise, "raw");
468
+ return await createTestWebSocketProxy(clientWsPromise, "standard");
720
469
  })(c, noopNext());
721
470
  });
722
471
 
723
- // Raw HTTP endpoint for test inline driver
724
- router.all(".test/inline-driver/raw-http/*", async (c) => {
472
+ router.all(".test/inline-driver/send-request/*", async (c) => {
725
473
  // 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);
474
+ const actorId = c.req.header(HEADER_ACTOR_ID);
729
475
 
730
- if (!actorQueryHeader || !encodingHeader) {
476
+ if (!actorId) {
731
477
  return c.text("Missing required headers", 400);
732
478
  }
733
479
 
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;
480
+ // Extract the path after /send-request/
740
481
  const pathOnly =
741
- fullPath.split("/.test/inline-driver/raw-http/")[1] || "";
482
+ c.req.path.split("/.test/inline-driver/send-request/")[1] || "";
742
483
 
743
484
  // Include query string
744
485
  const url = new URL(c.req.url);
745
486
  const pathWithQuery = pathOnly + url.search;
746
487
 
747
- logger().debug("received test inline driver raw http", {
748
- actorQuery,
749
- params,
750
- encoding,
488
+ logger().debug({
489
+ msg: "received test inline driver raw http",
490
+ actorId,
751
491
  path: pathWithQuery,
752
492
  method: c.req.method,
753
493
  });
754
494
 
755
495
  try {
756
496
  // 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
- {
497
+ const response = await managerDriver.sendRequest(
498
+ actorId,
499
+ new Request(`http://actor/${pathWithQuery}`, {
764
500
  method: c.req.method,
765
501
  headers: c.req.raw.headers,
766
502
  body: c.req.raw.body,
767
- },
768
- undefined,
503
+ }),
769
504
  );
770
505
 
771
506
  // Return the response directly
772
507
  return response;
773
508
  } catch (error) {
774
- logger().error("error in test inline raw http", {
509
+ logger().error({
510
+ msg: "error in test inline raw http",
775
511
  error: stringifyError(error),
776
512
  });
777
513
 
@@ -796,128 +532,56 @@ export function createManagerRouter(
796
532
  router as unknown as Hono,
797
533
  );
798
534
 
799
- // Mount on both / and /registry
800
- //
801
- // We do this because the default requests are to `/registry/*`.
802
- //
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
- );
535
+ if (runConfig.inspector?.enabled) {
536
+ if (!managerDriver.inspector) {
537
+ throw new Unsupported("inspector");
861
538
  }
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");
539
+ router.route(
540
+ "/inspect",
541
+ new Hono<{ Variables: { inspector: any } }>()
542
+ .use(corsMiddleware(runConfig.inspector.cors))
543
+ .use(secureInspector(runConfig))
544
+ .use((c, next) => {
545
+ c.set("inspector", managerDriver.inspector!);
546
+ return next();
547
+ })
548
+ .route("/", createManagerInspectorRouter()),
549
+ );
887
550
  }
888
551
 
889
- logger().debug("actor query result", {
890
- actorId: actorOutput.actorId,
891
- });
892
- return { actorId: actorOutput.actorId };
893
- }
552
+ // Error handling
553
+ router.notFound(handleRouteNotFound);
554
+ router.onError(handleRouteError);
894
555
 
556
+ return { router: router as Hono, openapi: router };
557
+ }
895
558
  /**
896
559
  * Creates a WebSocket proxy for test endpoints that forwards messages between server and client WebSockets
897
560
  */
898
561
  async function createTestWebSocketProxy(
899
- clientWsPromise: Promise<WebSocket>,
562
+ clientWsPromise: Promise<UniversalWebSocket>,
900
563
  connectionType: string,
901
564
  ): Promise<UpgradeWebSocketArgs> {
902
565
  // Store a reference to the resolved WebSocket
903
- let clientWs: WebSocket | null = null;
566
+ let clientWs: UniversalWebSocket | null = null;
904
567
  try {
905
568
  // Resolve the client WebSocket promise
906
- logger().debug("awaiting client websocket promise");
569
+ logger().debug({ msg: "awaiting client websocket promise" });
907
570
  const ws = await clientWsPromise;
908
571
  clientWs = ws;
909
- logger().debug("client websocket promise resolved", {
572
+ logger().debug({
573
+ msg: "client websocket promise resolved",
910
574
  constructor: ws?.constructor.name,
911
575
  });
912
576
 
913
577
  // Wait for ws to open
914
578
  await new Promise<void>((resolve, reject) => {
915
579
  const onOpen = () => {
916
- logger().debug("test websocket connection opened");
580
+ logger().debug({ msg: "test websocket connection opened" });
917
581
  resolve();
918
582
  };
919
583
  const onError = (error: any) => {
920
- logger().error("test websocket connection failed", { error });
584
+ logger().error({ msg: "test websocket connection failed", error });
921
585
  reject(
922
586
  new Error(`Failed to open WebSocket: ${error.message || error}`),
923
587
  );
@@ -926,10 +590,10 @@ async function createTestWebSocketProxy(
926
590
  ws.addEventListener("error", onError);
927
591
  });
928
592
  } catch (error) {
929
- logger().error(
930
- `failed to establish client ${connectionType} websocket connection`,
931
- { error },
932
- );
593
+ logger().error({
594
+ msg: `failed to establish client ${connectionType} websocket connection`,
595
+ error,
596
+ });
933
597
  return {
934
598
  onOpen: (_evt, serverWs) => {
935
599
  serverWs.close(1011, "Failed to establish connection");
@@ -943,10 +607,13 @@ async function createTestWebSocketProxy(
943
607
  // Create WebSocket proxy handlers to relay messages between client and server
944
608
  return {
945
609
  onOpen: (_evt: any, serverWs: WSContext) => {
946
- logger().debug(`test ${connectionType} websocket connection opened`);
610
+ logger().debug({
611
+ msg: `test ${connectionType} websocket connection opened`,
612
+ });
947
613
 
948
614
  // Check WebSocket type
949
- logger().debug("clientWs info", {
615
+ logger().debug({
616
+ msg: "clientWs info",
950
617
  constructor: clientWs.constructor.name,
951
618
  hasAddEventListener: typeof clientWs.addEventListener === "function",
952
619
  readyState: clientWs.readyState,
@@ -954,19 +621,17 @@ async function createTestWebSocketProxy(
954
621
 
955
622
  // Add message handler to forward messages from client to server
956
623
  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
- );
624
+ logger().debug({
625
+ msg: `test ${connectionType} websocket connection message from client`,
626
+ dataType: typeof clientEvt.data,
627
+ isBlob: clientEvt.data instanceof Blob,
628
+ isArrayBuffer: clientEvt.data instanceof ArrayBuffer,
629
+ dataConstructor: clientEvt.data?.constructor?.name,
630
+ dataStr:
631
+ typeof clientEvt.data === "string"
632
+ ? clientEvt.data.substring(0, 100)
633
+ : undefined,
634
+ });
970
635
 
971
636
  if (serverWs.readyState === 1) {
972
637
  // OPEN
@@ -975,21 +640,21 @@ async function createTestWebSocketProxy(
975
640
  clientEvt.data
976
641
  .arrayBuffer()
977
642
  .then((buffer) => {
978
- logger().debug(
979
- "converted client blob to arraybuffer, sending to server",
980
- {
981
- bufferSize: buffer.byteLength,
982
- },
983
- );
643
+ logger().debug({
644
+ msg: "converted client blob to arraybuffer, sending to server",
645
+ bufferSize: buffer.byteLength,
646
+ });
984
647
  serverWs.send(buffer as any);
985
648
  })
986
649
  .catch((error) => {
987
- logger().error("failed to convert blob to arraybuffer", {
650
+ logger().error({
651
+ msg: "failed to convert blob to arraybuffer",
988
652
  error,
989
653
  });
990
654
  });
991
655
  } else {
992
- logger().debug("sending client data directly to server", {
656
+ logger().debug({
657
+ msg: "sending client data directly to server",
993
658
  dataType: typeof clientEvt.data,
994
659
  dataLength:
995
660
  typeof clientEvt.data === "string"
@@ -1002,8 +667,10 @@ async function createTestWebSocketProxy(
1002
667
  });
1003
668
 
1004
669
  // Add close handler to close server when client closes
1005
- clientWs.addEventListener("close", (clientEvt: CloseEvent) => {
1006
- logger().debug(`test ${connectionType} websocket connection closed`);
670
+ clientWs.addEventListener("close", (clientEvt: any) => {
671
+ logger().debug({
672
+ msg: `test ${connectionType} websocket connection closed`,
673
+ });
1007
674
 
1008
675
  if (serverWs.readyState !== 3) {
1009
676
  // Not CLOSED
@@ -1013,7 +680,9 @@ async function createTestWebSocketProxy(
1013
680
 
1014
681
  // Add error handler
1015
682
  clientWs.addEventListener("error", () => {
1016
- logger().debug(`test ${connectionType} websocket connection error`);
683
+ logger().debug({
684
+ msg: `test ${connectionType} websocket connection error`,
685
+ });
1017
686
 
1018
687
  if (serverWs.readyState !== 3) {
1019
688
  // Not CLOSED
@@ -1022,7 +691,8 @@ async function createTestWebSocketProxy(
1022
691
  });
1023
692
  },
1024
693
  onMessage: (evt: { data: any }) => {
1025
- logger().debug("received message from server", {
694
+ logger().debug({
695
+ msg: "received message from server",
1026
696
  dataType: typeof evt.data,
1027
697
  isBlob: evt.data instanceof Blob,
1028
698
  isArrayBuffer: evt.data instanceof ArrayBuffer,
@@ -1039,18 +709,21 @@ async function createTestWebSocketProxy(
1039
709
  evt.data
1040
710
  .arrayBuffer()
1041
711
  .then((buffer) => {
1042
- logger().debug("converted blob to arraybuffer, sending", {
712
+ logger().debug({
713
+ msg: "converted blob to arraybuffer, sending",
1043
714
  bufferSize: buffer.byteLength,
1044
715
  });
1045
716
  clientWs.send(buffer);
1046
717
  })
1047
718
  .catch((error) => {
1048
- logger().error("failed to convert blob to arraybuffer", {
719
+ logger().error({
720
+ msg: "failed to convert blob to arraybuffer",
1049
721
  error,
1050
722
  });
1051
723
  });
1052
724
  } else {
1053
- logger().debug("sending data directly", {
725
+ logger().debug({
726
+ msg: "sending data directly",
1054
727
  dataType: typeof evt.data,
1055
728
  dataLength:
1056
729
  typeof evt.data === "string" ? evt.data.length : undefined,
@@ -1067,7 +740,8 @@ async function createTestWebSocketProxy(
1067
740
  },
1068
741
  serverWs: WSContext,
1069
742
  ) => {
1070
- logger().debug(`server ${connectionType} websocket closed`, {
743
+ logger().debug({
744
+ msg: `server ${connectionType} websocket closed`,
1071
745
  wasClean: event.wasClean,
1072
746
  code: event.code,
1073
747
  reason: event.reason,
@@ -1088,7 +762,10 @@ async function createTestWebSocketProxy(
1088
762
  }
1089
763
  },
1090
764
  onError: (error: unknown) => {
1091
- logger().error(`error in server ${connectionType} websocket`, { error });
765
+ logger().error({
766
+ msg: `error in server ${connectionType} websocket`,
767
+ error,
768
+ });
1092
769
 
1093
770
  // Close the client websocket on error
1094
771
  if (
@@ -1101,692 +778,3 @@ async function createTestWebSocketProxy(
1101
778
  },
1102
779
  };
1103
780
  }
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
- }
1664
-
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);
1699
- }
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,
1737
- );
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
- }
1754
- }
1755
-
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);
1776
-
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
- };
1792
- }