rivetkit 2.3.20 → 2.3.22

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 (148) hide show
  1. package/dist/browser/client.d.ts +43 -5
  2. package/dist/browser/client.js +315 -63
  3. package/dist/browser/client.js.map +1 -1
  4. package/dist/browser/inspector/client.js +2 -1
  5. package/dist/browser/inspector/client.js.map +1 -1
  6. package/dist/tsup/actor/errors.cjs +2 -2
  7. package/dist/tsup/actor/errors.cjs.map +1 -1
  8. package/dist/tsup/actor/errors.d.cts +1 -1
  9. package/dist/tsup/actor/errors.d.ts +1 -1
  10. package/dist/tsup/actor/errors.js +1 -1
  11. package/dist/tsup/agent-os/index.cjs +2 -1
  12. package/dist/tsup/agent-os/index.cjs.map +1 -1
  13. package/dist/tsup/agent-os/index.d.cts +32 -4
  14. package/dist/tsup/agent-os/index.d.ts +32 -4
  15. package/dist/tsup/agent-os/index.js +2 -1
  16. package/dist/tsup/agent-os/index.js.map +1 -1
  17. package/dist/tsup/{chunk-553CIQJY.js → chunk-47FSGXYN.js} +2 -2
  18. package/dist/tsup/{chunk-2NXLTFWI.js → chunk-4DTWJFIZ.js} +3 -3
  19. package/dist/tsup/chunk-4DTWJFIZ.js.map +1 -0
  20. package/dist/tsup/{chunk-2CE5UNWK.cjs → chunk-BDKVTNJF.cjs} +11 -10
  21. package/dist/tsup/chunk-BDKVTNJF.cjs.map +1 -0
  22. package/dist/tsup/{chunk-DHWZTUKA.js → chunk-DE2IFYJK.js} +4 -3
  23. package/dist/tsup/chunk-DE2IFYJK.js.map +1 -0
  24. package/dist/tsup/{chunk-DMQUERRN.js → chunk-GINS4O73.js} +2 -2
  25. package/dist/tsup/{chunk-3WS7Z64U.cjs → chunk-GZZNJGXY.cjs} +3 -3
  26. package/dist/tsup/chunk-GZZNJGXY.cjs.map +1 -0
  27. package/dist/tsup/{chunk-A36FMKOV.cjs → chunk-HEY27WZX.cjs} +479 -230
  28. package/dist/tsup/chunk-HEY27WZX.cjs.map +1 -0
  29. package/dist/tsup/{chunk-WAISW7KR.cjs → chunk-KEAUN5F5.cjs} +10 -10
  30. package/dist/tsup/chunk-KEAUN5F5.cjs.map +1 -0
  31. package/dist/tsup/chunk-KVFIONKQ.cjs.map +1 -1
  32. package/dist/tsup/{chunk-CVLNRT7O.cjs → chunk-LUMROGNM.cjs} +3 -3
  33. package/dist/tsup/chunk-LUMROGNM.cjs.map +1 -0
  34. package/dist/tsup/{chunk-JX2ITWAV.cjs → chunk-MGI2VVF6.cjs} +14 -4
  35. package/dist/tsup/chunk-MGI2VVF6.cjs.map +1 -0
  36. package/dist/tsup/chunk-MYWZCDML.cjs.map +1 -1
  37. package/dist/tsup/chunk-NWCAIWHL.cjs.map +1 -1
  38. package/dist/tsup/{chunk-RSIOGT2U.cjs → chunk-QOQA4JPN.cjs} +7 -7
  39. package/dist/tsup/chunk-QOQA4JPN.cjs.map +1 -0
  40. package/dist/tsup/{chunk-TZVAVPGJ.js → chunk-QXBNHJH5.js} +4 -4
  41. package/dist/tsup/{chunk-W2PXGWMR.js → chunk-RIPVNPR6.js} +2 -2
  42. package/dist/tsup/{chunk-5G2J26VM.js → chunk-VD47X7KQ.js} +318 -69
  43. package/dist/tsup/chunk-VD47X7KQ.js.map +1 -0
  44. package/dist/tsup/{chunk-76YTS32R.cjs → chunk-WLRGUAMW.cjs} +4 -4
  45. package/dist/tsup/chunk-WLRGUAMW.cjs.map +1 -0
  46. package/dist/tsup/{chunk-IHKDFKM4.cjs → chunk-WMNXGH25.cjs} +3 -3
  47. package/dist/tsup/chunk-WMNXGH25.cjs.map +1 -0
  48. package/dist/tsup/{chunk-SNNVRHLX.js → chunk-XZBDVEQN.js} +2 -2
  49. package/dist/tsup/{chunk-P2OFXH4G.js → chunk-ZOVJDAOW.js} +14 -4
  50. package/dist/tsup/chunk-ZOVJDAOW.js.map +1 -0
  51. package/dist/tsup/client/mod.cjs +6 -6
  52. package/dist/tsup/client/mod.cjs.map +1 -1
  53. package/dist/tsup/client/mod.d.cts +15 -6
  54. package/dist/tsup/client/mod.d.ts +15 -6
  55. package/dist/tsup/client/mod.js +5 -5
  56. package/dist/tsup/common/log.cjs +3 -3
  57. package/dist/tsup/common/log.cjs.map +1 -1
  58. package/dist/tsup/common/log.js +2 -2
  59. package/dist/tsup/common/websocket.cjs +4 -4
  60. package/dist/tsup/common/websocket.cjs.map +1 -1
  61. package/dist/tsup/common/websocket.js +3 -3
  62. package/dist/tsup/{config-Be-VOHaX.d.ts → config-Cv6pjCt-.d.ts} +37 -7
  63. package/dist/tsup/{config-Ct5uXxXk.d.cts → config-osdJqQwX.d.cts} +37 -7
  64. package/dist/tsup/{context-CXLa0BIM.d.ts → context-CoVbKBuk.d.ts} +1 -1
  65. package/dist/tsup/{context-Cw-11Q-B.d.cts → context-DpjrM7n7.d.cts} +1 -1
  66. package/dist/tsup/db/drizzle.cjs +3 -3
  67. package/dist/tsup/db/drizzle.cjs.map +1 -1
  68. package/dist/tsup/db/drizzle.js +2 -2
  69. package/dist/tsup/db/mod.cjs +4 -4
  70. package/dist/tsup/db/mod.cjs.map +1 -1
  71. package/dist/tsup/db/mod.js +3 -3
  72. package/dist/tsup/dynamic/mod.cjs.map +1 -1
  73. package/dist/tsup/dynamic/mod.d.cts +4 -3
  74. package/dist/tsup/dynamic/mod.d.ts +4 -3
  75. package/dist/tsup/inspector/mod.cjs +6 -6
  76. package/dist/tsup/inspector/mod.cjs.map +1 -1
  77. package/dist/tsup/inspector/mod.js +5 -5
  78. package/dist/tsup/inspector/workflow.cjs +5 -5
  79. package/dist/tsup/inspector/workflow.cjs.map +1 -1
  80. package/dist/tsup/inspector/workflow.js +4 -4
  81. package/dist/tsup/inspector-tab/mod.cjs.map +1 -1
  82. package/dist/tsup/inspector-tab/mod.d.cts +5 -4
  83. package/dist/tsup/inspector-tab/mod.d.ts +5 -4
  84. package/dist/tsup/mod.cjs +170 -158
  85. package/dist/tsup/mod.cjs.map +1 -1
  86. package/dist/tsup/mod.d.cts +6 -5
  87. package/dist/tsup/mod.d.ts +6 -5
  88. package/dist/tsup/mod.js +56 -44
  89. package/dist/tsup/mod.js.map +1 -1
  90. package/dist/tsup/test/mod.cjs +7 -7
  91. package/dist/tsup/test/mod.cjs.map +1 -1
  92. package/dist/tsup/test/mod.d.cts +4 -3
  93. package/dist/tsup/test/mod.d.ts +4 -3
  94. package/dist/tsup/test/mod.js +5 -5
  95. package/dist/tsup/unstable/migrations.cjs.map +1 -1
  96. package/dist/tsup/{utils-DsoDinPz.d.ts → utils-3rsE0JvI.d.cts} +12 -2
  97. package/dist/tsup/{utils-DsoDinPz.d.cts → utils-3rsE0JvI.d.ts} +12 -2
  98. package/dist/tsup/{utils-OQHBjfcM.d.ts → utils-C1O-SklO.d.ts} +1 -1
  99. package/dist/tsup/{utils-CNFeS-Dk.d.cts → utils-C6b0aYUM.d.cts} +1 -1
  100. package/dist/tsup/utils.cjs +3 -3
  101. package/dist/tsup/utils.cjs.map +1 -1
  102. package/dist/tsup/utils.d.cts +2 -2
  103. package/dist/tsup/utils.d.ts +2 -2
  104. package/dist/tsup/utils.js +2 -2
  105. package/dist/tsup/workflow/mod.cjs +11 -11
  106. package/dist/tsup/workflow/mod.cjs.map +1 -1
  107. package/dist/tsup/workflow/mod.d.cts +6 -5
  108. package/dist/tsup/workflow/mod.d.ts +6 -5
  109. package/dist/tsup/workflow/mod.js +5 -5
  110. package/package.json +10 -9
  111. package/src/actor/errors.ts +27 -4
  112. package/src/client/actor-conn.ts +34 -1
  113. package/src/client/actor-handle.ts +29 -1
  114. package/src/client/auth.ts +37 -0
  115. package/src/client/client.ts +10 -2
  116. package/src/client/config.ts +12 -1
  117. package/src/client/mod.ts +9 -0
  118. package/src/client/token-provider.test.ts +86 -0
  119. package/src/client/token-provider.ts +93 -0
  120. package/src/client/utils.ts +1 -0
  121. package/src/engine-client/actor-websocket-client.ts +2 -1
  122. package/src/engine-client/api-endpoints.ts +28 -0
  123. package/src/engine-client/driver.ts +4 -0
  124. package/src/engine-client/mod.ts +201 -64
  125. package/src/registry/config/envoy.ts +1 -17
  126. package/src/registry/config/index.test.ts +2 -2
  127. package/src/registry/config/index.ts +7 -2
  128. package/src/registry/index.ts +28 -0
  129. package/src/registry/native.ts +31 -3
  130. package/dist/tsup/chunk-2CE5UNWK.cjs.map +0 -1
  131. package/dist/tsup/chunk-2NXLTFWI.js.map +0 -1
  132. package/dist/tsup/chunk-3WS7Z64U.cjs.map +0 -1
  133. package/dist/tsup/chunk-5G2J26VM.js.map +0 -1
  134. package/dist/tsup/chunk-76YTS32R.cjs.map +0 -1
  135. package/dist/tsup/chunk-A36FMKOV.cjs.map +0 -1
  136. package/dist/tsup/chunk-CVLNRT7O.cjs.map +0 -1
  137. package/dist/tsup/chunk-DHWZTUKA.js.map +0 -1
  138. package/dist/tsup/chunk-IHKDFKM4.cjs.map +0 -1
  139. package/dist/tsup/chunk-JX2ITWAV.cjs.map +0 -1
  140. package/dist/tsup/chunk-P2OFXH4G.js.map +0 -1
  141. package/dist/tsup/chunk-RSIOGT2U.cjs.map +0 -1
  142. package/dist/tsup/chunk-WAISW7KR.cjs.map +0 -1
  143. package/src/registry/process-metrics.ts +0 -250
  144. /package/dist/tsup/{chunk-553CIQJY.js.map → chunk-47FSGXYN.js.map} +0 -0
  145. /package/dist/tsup/{chunk-DMQUERRN.js.map → chunk-GINS4O73.js.map} +0 -0
  146. /package/dist/tsup/{chunk-TZVAVPGJ.js.map → chunk-QXBNHJH5.js.map} +0 -0
  147. /package/dist/tsup/{chunk-W2PXGWMR.js.map → chunk-RIPVNPR6.js.map} +0 -0
  148. /package/dist/tsup/{chunk-SNNVRHLX.js.map → chunk-XZBDVEQN.js.map} +0 -0
@@ -2,6 +2,12 @@ import type { Context as HonoContext } from "hono";
2
2
  import invariant from "invariant";
3
3
  import { deserializeActorKey, serializeActorKey } from "@/actor/keys";
4
4
  import type { ClientConfig } from "@/client/client";
5
+ import type { IssuedToken, IssueTokenOptions } from "@/client/auth";
6
+ import {
7
+ isInvalidToken,
8
+ isInvalidTokenResponse,
9
+ TokenProvider,
10
+ } from "@/client/token-provider";
5
11
  import {
6
12
  PATH_CONNECT,
7
13
  PATH_WEBSOCKET_BASE,
@@ -40,6 +46,7 @@ import {
40
46
  getActor,
41
47
  getActorByKey,
42
48
  getOrCreateActor,
49
+ issueToken as issueEngineToken,
43
50
  listActorsByName,
44
51
  } from "./api-endpoints";
45
52
  import { EngineApiError, getEndpoint } from "./api-utils";
@@ -50,6 +57,13 @@ import { createWebSocketProxy } from "./ws-proxy";
50
57
  export class RemoteEngineControlClient implements EngineControlClient {
51
58
  #config: ClientConfig;
52
59
  #metadataPromise: Promise<void> | undefined;
60
+ #tokenProvider?: TokenProvider;
61
+ #webSocketTokens = new WeakMap<
62
+ UniversalWebSocket,
63
+ {
64
+ refresh?: Promise<string>;
65
+ }
66
+ >();
53
67
 
54
68
  constructor(runConfig: ClientConfig) {
55
69
  // Disable health check if in Next.js build phase since there is no `/metadata` endpoint
@@ -65,6 +79,8 @@ export class RemoteEngineControlClient implements EngineControlClient {
65
79
  // Clone config so we can mutate the endpoint in #metadataPromise
66
80
  // NOTE: This is a shallow clone, so mutating nested properties will not do anything
67
81
  this.#config = { ...runConfig };
82
+ if (runConfig.getToken)
83
+ this.#tokenProvider = new TokenProvider(runConfig.getToken);
68
84
 
69
85
  // Perform metadata check if enabled
70
86
  if (!runConfig.disableMetadataLookup) {
@@ -80,7 +96,7 @@ export class RemoteEngineControlClient implements EngineControlClient {
80
96
  this.#config.namespace =
81
97
  metadataData.clientNamespace;
82
98
  }
83
- if (metadataData.clientToken) {
99
+ if (metadataData.clientToken && !this.#tokenProvider) {
84
100
  this.#config.token = metadataData.clientToken;
85
101
  }
86
102
 
@@ -88,7 +104,6 @@ export class RemoteEngineControlClient implements EngineControlClient {
88
104
  msg: "overriding client endpoint",
89
105
  endpoint: metadataData.clientEndpoint,
90
106
  namespace: metadataData.clientNamespace,
91
- token: metadataData.clientToken,
92
107
  });
93
108
  }
94
109
 
@@ -103,6 +118,51 @@ export class RemoteEngineControlClient implements EngineControlClient {
103
118
  }
104
119
  }
105
120
 
121
+ async #authorizedConfig(): Promise<ClientConfig> {
122
+ if (!this.#tokenProvider) return this.#config;
123
+ return { ...this.#config, token: await this.#tokenProvider.current() };
124
+ }
125
+
126
+ async #withCredential<T>(
127
+ request: (config: ClientConfig) => Promise<T>,
128
+ readOnly = false,
129
+ ): Promise<T> {
130
+ const config = await this.#authorizedConfig();
131
+ try {
132
+ return await request(config);
133
+ } catch (error) {
134
+ if (
135
+ this.#tokenProvider &&
136
+ config.token &&
137
+ error instanceof EngineApiError &&
138
+ error.statusCode === 401 &&
139
+ isInvalidToken(error.group, error.code)
140
+ ) {
141
+ if (readOnly) {
142
+ const token = await this.#tokenProvider.refreshIfCurrent(
143
+ config.token,
144
+ );
145
+ return request({ ...this.#config, token });
146
+ }
147
+ // Mutations may have executed; refresh the next call, not this one.
148
+ await this.#tokenProvider.refreshIfCurrent(config.token);
149
+ }
150
+ throw error;
151
+ }
152
+ }
153
+
154
+ async issueToken(options: IssueTokenOptions): Promise<IssuedToken> {
155
+ await this.#metadataPromise;
156
+ const response = await this.#withCredential((config) =>
157
+ issueEngineToken(config, options),
158
+ );
159
+ return {
160
+ token: response.token,
161
+ issuedAt: response.issued_ts,
162
+ expiresAt: response.expires_ts,
163
+ };
164
+ }
165
+
106
166
  async getForId({
107
167
  name,
108
168
  actorId,
@@ -110,7 +170,10 @@ export class RemoteEngineControlClient implements EngineControlClient {
110
170
  await this.#metadataPromise;
111
171
 
112
172
  // Fetch from API if not in cache
113
- const response = await getActor(this.#config, name, actorId);
173
+ const response = await this.#withCredential(
174
+ (config) => getActor(config, name, actorId),
175
+ true,
176
+ );
114
177
  const actor = response.actors[0];
115
178
  if (!actor) return undefined;
116
179
 
@@ -138,7 +201,10 @@ export class RemoteEngineControlClient implements EngineControlClient {
138
201
 
139
202
  // If not in local cache, fetch by key from API
140
203
  try {
141
- const response = await getActorByKey(this.#config, name, key);
204
+ const response = await this.#withCredential(
205
+ (config) => getActorByKey(config, name, key),
206
+ true,
207
+ );
142
208
  const actor = response.actors[0];
143
209
  if (!actor) return undefined;
144
210
 
@@ -183,18 +249,20 @@ export class RemoteEngineControlClient implements EngineControlClient {
183
249
  });
184
250
 
185
251
  try {
186
- const { actor, created } = await getOrCreateActor(this.#config, {
187
- datacenter: region,
188
- name,
189
- key: serializeActorKey(key),
190
- runner_name_selector: poolName ?? this.#config.poolName,
191
- input: actorInput
192
- ? uint8ArrayToBase64(
193
- encodeCborCompat(actorInput as JsonCompatValue),
194
- )
195
- : undefined,
196
- crash_policy: crashPolicy ?? "sleep",
197
- });
252
+ const { actor, created } = await this.#withCredential((config) =>
253
+ getOrCreateActor(config, {
254
+ datacenter: region,
255
+ name,
256
+ key: serializeActorKey(key),
257
+ runner_name_selector: poolName ?? this.#config.poolName,
258
+ input: actorInput
259
+ ? uint8ArrayToBase64(
260
+ encodeCborCompat(actorInput as JsonCompatValue),
261
+ )
262
+ : undefined,
263
+ crash_policy: crashPolicy ?? "sleep",
264
+ }),
265
+ );
198
266
 
199
267
  logger().info({
200
268
  msg: "getOrCreateWithKey: actor ready",
@@ -220,7 +288,10 @@ export class RemoteEngineControlClient implements EngineControlClient {
220
288
  key,
221
289
  });
222
290
 
223
- const response = await getActorByKey(this.#config, name, key);
291
+ const response = await this.#withCredential(
292
+ (config) => getActorByKey(config, name, key),
293
+ true,
294
+ );
224
295
  const existing = response.actors[0];
225
296
  if (!existing) throw error;
226
297
 
@@ -250,16 +321,20 @@ export class RemoteEngineControlClient implements EngineControlClient {
250
321
  logger().info({ msg: "creating actor via engine api", name, key });
251
322
 
252
323
  // Create actor via engine API
253
- const result = await createActor(this.#config, {
254
- datacenter: region,
255
- name,
256
- runner_name_selector: poolName ?? this.#config.poolName,
257
- key: serializeActorKey(key),
258
- input: input
259
- ? uint8ArrayToBase64(encodeCborCompat(input as JsonCompatValue))
260
- : undefined,
261
- crash_policy: crashPolicy ?? "sleep",
262
- });
324
+ const result = await this.#withCredential((config) =>
325
+ createActor(config, {
326
+ datacenter: region,
327
+ name,
328
+ runner_name_selector: poolName ?? this.#config.poolName,
329
+ key: serializeActorKey(key),
330
+ input: input
331
+ ? uint8ArrayToBase64(
332
+ encodeCborCompat(input as JsonCompatValue),
333
+ )
334
+ : undefined,
335
+ crash_policy: crashPolicy ?? "sleep",
336
+ }),
337
+ );
263
338
 
264
339
  logger().info({
265
340
  msg: "actor created",
@@ -276,7 +351,10 @@ export class RemoteEngineControlClient implements EngineControlClient {
276
351
 
277
352
  logger().debug({ msg: "listing actors via engine api", name });
278
353
 
279
- const response = await listActorsByName(this.#config, name);
354
+ const response = await this.#withCredential(
355
+ (config) => listActorsByName(config, name),
356
+ true,
357
+ );
280
358
 
281
359
  return response.actors.map(apiActorToOutput);
282
360
  }
@@ -286,7 +364,7 @@ export class RemoteEngineControlClient implements EngineControlClient {
286
364
 
287
365
  logger().info({ msg: "destroying actor via engine api", actorId });
288
366
 
289
- await destroyActor(this.#config, actorId);
367
+ await this.#withCredential((config) => destroyActor(config, actorId));
290
368
 
291
369
  logger().info({ msg: "actor destroyed", actorId });
292
370
  }
@@ -297,13 +375,7 @@ export class RemoteEngineControlClient implements EngineControlClient {
297
375
  options: GatewayRequestOptions = {},
298
376
  ): Promise<Response> {
299
377
  await this.#metadataPromise;
300
-
301
- const path = requestPath(actorRequest);
302
- const gatewayUrl = this.#buildGatewayUrlForTarget(
303
- target,
304
- path,
305
- options,
306
- );
378
+ const config = await this.#authorizedConfig();
307
379
  const httpOptions = {
308
380
  ...options,
309
381
  directActorId: shouldSkipReadyWait(options)
@@ -311,12 +383,37 @@ export class RemoteEngineControlClient implements EngineControlClient {
311
383
  : undefined,
312
384
  };
313
385
 
314
- return sendHttpRequestToGateway(
315
- this.#config,
316
- gatewayUrl,
317
- actorRequest,
318
- httpOptions,
319
- );
386
+ const send = (current: ClientConfig) =>
387
+ sendHttpRequestToGateway(
388
+ current,
389
+ this.#buildGatewayUrlForTarget(
390
+ current,
391
+ target,
392
+ requestPath(actorRequest),
393
+ options,
394
+ ),
395
+ actorRequest,
396
+ httpOptions,
397
+ );
398
+ const response = await send(config);
399
+ if (
400
+ !this.#tokenProvider ||
401
+ !config.token ||
402
+ !isInvalidTokenResponse(response)
403
+ )
404
+ return response;
405
+ if (
406
+ (actorRequest.method === "GET" || actorRequest.method === "HEAD") &&
407
+ !("getOrCreateForKey" in target)
408
+ ) {
409
+ const token = await this.#tokenProvider.refreshIfCurrent(
410
+ config.token,
411
+ );
412
+ await response.body?.cancel();
413
+ return send({ ...this.#config, token });
414
+ }
415
+ await this.#tokenProvider.refreshIfCurrent(config.token);
416
+ return response;
320
417
  }
321
418
 
322
419
  async openWebSocket(
@@ -327,15 +424,17 @@ export class RemoteEngineControlClient implements EngineControlClient {
327
424
  options: GatewayRequestOptions = {},
328
425
  ): Promise<UniversalWebSocket> {
329
426
  await this.#metadataPromise;
427
+ const config = await this.#authorizedConfig();
330
428
 
331
429
  const gatewayUrl = this.#buildGatewayUrlForTarget(
430
+ config,
332
431
  target,
333
432
  path,
334
433
  options,
335
434
  );
336
435
 
337
- return openWebSocketToGateway(
338
- this.#config,
436
+ const ws = await openWebSocketToGateway(
437
+ config,
339
438
  gatewayUrl,
340
439
  encoding,
341
440
  params,
@@ -346,6 +445,45 @@ export class RemoteEngineControlClient implements EngineControlClient {
346
445
  : undefined,
347
446
  },
348
447
  );
448
+ this.#watchWebSocketAuth(ws, config.token);
449
+ return ws;
450
+ }
451
+
452
+ async refreshAuthToken(ws: UniversalWebSocket): Promise<boolean> {
453
+ const context = this.#webSocketTokens.get(ws);
454
+ if (!context?.refresh) return false;
455
+ await context.refresh;
456
+ return true;
457
+ }
458
+
459
+ #watchWebSocketAuth(ws: UniversalWebSocket, token?: string): void {
460
+ const tokenProvider = this.#tokenProvider;
461
+ if (!tokenProvider || !token) return;
462
+ const context: { refresh?: Promise<string> } = {};
463
+ this.#webSocketTokens.set(ws, context);
464
+ ws.addEventListener(
465
+ "close",
466
+ (event: { code?: number; reason?: string }) => {
467
+ if (event.code !== 1008 || !event.reason) return;
468
+ const separator = event.reason.indexOf(".");
469
+ if (separator < 0) return;
470
+ const code = event.reason
471
+ .slice(separator + 1)
472
+ .split(/[\s:#]/, 1)[0];
473
+ if (isInvalidToken(event.reason.slice(0, separator), code)) {
474
+ context.refresh = tokenProvider
475
+ .refreshIfCurrent(token)
476
+ .catch((error) => {
477
+ logger().warn(
478
+ "failed to renew authentication token",
479
+ );
480
+ throw error;
481
+ });
482
+ // Raw websockets have no built-in reconnection; preempt unhandled rejection.
483
+ context.refresh.catch(() => {});
484
+ }
485
+ },
486
+ );
349
487
  }
350
488
 
351
489
  async buildGatewayUrl(
@@ -353,7 +491,12 @@ export class RemoteEngineControlClient implements EngineControlClient {
353
491
  options: GatewayRequestOptions = {},
354
492
  ): Promise<string> {
355
493
  await this.#metadataPromise;
356
- return this.#buildGatewayUrlForTarget(target, "", options);
494
+ return this.#buildGatewayUrlForTarget(
495
+ await this.#authorizedConfig(),
496
+ target,
497
+ "",
498
+ options,
499
+ );
357
500
  }
358
501
 
359
502
  async proxyRequest(
@@ -361,14 +504,7 @@ export class RemoteEngineControlClient implements EngineControlClient {
361
504
  actorRequest: Request,
362
505
  actorId: string,
363
506
  ): Promise<Response> {
364
- await this.#metadataPromise;
365
-
366
- const gatewayUrl = this.#buildGatewayUrlForTarget(
367
- { directId: actorId },
368
- requestPath(actorRequest),
369
- );
370
-
371
- return sendHttpRequestToGateway(this.#config, gatewayUrl, actorRequest);
507
+ return this.sendRequest({ directId: actorId }, actorRequest);
372
508
  }
373
509
 
374
510
  async proxyWebSocket(
@@ -379,11 +515,12 @@ export class RemoteEngineControlClient implements EngineControlClient {
379
515
  params: unknown,
380
516
  ): Promise<Response> {
381
517
  await this.#metadataPromise;
518
+ const config = await this.#authorizedConfig();
382
519
 
383
- const upgradeWebSocket = this.#config.getUpgradeWebSocket?.();
520
+ const upgradeWebSocket = config.getUpgradeWebSocket?.();
384
521
  invariant(upgradeWebSocket, "missing getUpgradeWebSocket");
385
522
 
386
- const endpoint = getEndpoint(this.#config);
523
+ const endpoint = getEndpoint(config);
387
524
  const guardUrl = combineUrlPath(endpoint, path);
388
525
  const wsGuardUrl = guardUrl.replace("http://", "ws://");
389
526
 
@@ -396,7 +533,7 @@ export class RemoteEngineControlClient implements EngineControlClient {
396
533
 
397
534
  // Build protocols
398
535
  const protocols = buildWebSocketProtocols(
399
- this.#config,
536
+ config,
400
537
  encoding,
401
538
  params,
402
539
  undefined,
@@ -419,11 +556,12 @@ export class RemoteEngineControlClient implements EngineControlClient {
419
556
  }
420
557
 
421
558
  #buildGatewayUrlForTarget(
559
+ config: ClientConfig,
422
560
  target: GatewayTarget,
423
561
  path: string,
424
562
  options: GatewayRequestOptions = {},
425
563
  ): string {
426
- const endpoint = getEndpoint(this.#config);
564
+ const endpoint = getEndpoint(config);
427
565
 
428
566
  if (
429
567
  shouldSkipReadyWait(options) &&
@@ -437,7 +575,7 @@ export class RemoteEngineControlClient implements EngineControlClient {
437
575
  return buildActorGatewayUrl(
438
576
  endpoint,
439
577
  target.directId,
440
- this.#config.token,
578
+ config.token,
441
579
  path,
442
580
  );
443
581
  }
@@ -446,7 +584,7 @@ export class RemoteEngineControlClient implements EngineControlClient {
446
584
  return buildActorGatewayUrl(
447
585
  endpoint,
448
586
  target.getForId.actorId,
449
- this.#config.token,
587
+ config.token,
450
588
  path,
451
589
  );
452
590
  }
@@ -454,15 +592,14 @@ export class RemoteEngineControlClient implements EngineControlClient {
454
592
  if ("getForKey" in target || "getOrCreateForKey" in target) {
455
593
  return buildActorQueryGatewayUrl(
456
594
  endpoint,
457
- this.#config.namespace,
595
+ config.namespace,
458
596
  target,
459
- this.#config.token,
597
+ config.token,
460
598
  path,
461
- this.#config.maxInputSize,
599
+ config.maxInputSize,
462
600
  undefined,
463
601
  "getOrCreateForKey" in target
464
- ? (target.getOrCreateForKey.poolName ??
465
- this.#config.poolName)
602
+ ? (target.getOrCreateForKey.poolName ?? config.poolName)
466
603
  : undefined,
467
604
  options,
468
605
  );
@@ -1,29 +1,13 @@
1
1
  import { z } from "zod/v4";
2
- import { getLogger } from "@/common/log";
3
2
  import {
4
- getNodeEnv,
5
3
  getRivetEnvoyVersion,
6
4
  getRivetPool,
7
5
  getRivetTotalSlots,
8
6
  } from "@/utils/env-vars";
9
7
 
10
- let warnedMissingVersion = false;
11
-
12
8
  export const EnvoyConfigSchema = z.object({
13
9
  poolName: z.string().default(() => getRivetPool() ?? "default"),
14
- version: z.number().default(() => {
15
- const version = getRivetEnvoyVersion();
16
- if (version !== undefined) return version;
17
-
18
- if (getNodeEnv() === "production" && !warnedMissingVersion) {
19
- warnedMissingVersion = true;
20
- getLogger("rivetkit").error(
21
- "RIVET_ENVOY_VERSION is not set. Actors will not be versioned, which means they won't be drained on deploy. This is only needed when self-hosting or using a custom envoy (not needed for Rivet Compute). Set this as a build arg in your Dockerfile. See https://rivet.dev/docs/actors/versions",
22
- );
23
- }
24
-
25
- return 1;
26
- }),
10
+ version: z.number().default(() => getRivetEnvoyVersion() ?? 1),
27
11
 
28
12
  // Deprecated.
29
13
  totalSlots: z.number().default(() => getRivetTotalSlots() ?? 100000),
@@ -12,7 +12,7 @@ afterEach(() => {
12
12
  });
13
13
 
14
14
  describe("RegistryConfigSchema", () => {
15
- test("defaults token to dev", () => {
15
+ test("defaults local Engine token", () => {
16
16
  delete process.env.RIVET_TOKEN;
17
17
 
18
18
  const config = RegistryConfigSchema.parse({
@@ -20,6 +20,6 @@ describe("RegistryConfigSchema", () => {
20
20
  startEngine: true,
21
21
  });
22
22
 
23
- expect(config.token).toBe("dev");
23
+ expect(config.token).toBe("default");
24
24
  });
25
25
  });
@@ -379,8 +379,13 @@ export const RegistryConfigSchema = z
379
379
  // Namespace priority: parsed from endpoint URL > config value (includes env var) > "default"
380
380
  const namespace =
381
381
  parsedEndpoint?.namespace ?? config.namespace ?? "default";
382
- // Token priority: parsed from endpoint URL > config value (includes env var) > "dev"
383
- const token = parsedEndpoint?.token ?? config.token ?? "dev";
382
+ // Token priority: parsed from endpoint URL > config value (includes env var)
383
+ const token =
384
+ parsedEndpoint?.token ??
385
+ config.token ??
386
+ (endpoint && isLocalEngineEndpoint(endpoint)
387
+ ? "default"
388
+ : undefined);
384
389
 
385
390
  // Parse publicEndpoint string (env var fallback is applied via transform in serverless schema)
386
391
  const parsedPublicEndpoint = config.serverless.publicEndpoint
@@ -4,6 +4,8 @@ import { configureBaseLogger } from "@/common/log";
4
4
  import { configureServerlessPool } from "@/serverless/configure";
5
5
  import { VERSION } from "@/utils";
6
6
  import {
7
+ getNodeEnv,
8
+ getRivetEnvoyVersion,
7
9
  getRivetkitPublicDir,
8
10
  getRivetkitRuntimeMode,
9
11
  parsePortEnv,
@@ -624,6 +626,32 @@ export class Registry<A extends RegistryActors> {
624
626
  */
625
627
  #startEnvoy(config: RegistryConfig, printWelcome: boolean) {
626
628
  if (!this.#runtimeServePromise) {
629
+ const configuredEnvoyVersion = this.#config.envoy?.version;
630
+ const envoyVersionSource =
631
+ configuredEnvoyVersion !== undefined
632
+ ? "config"
633
+ : getRivetEnvoyVersion() !== undefined
634
+ ? "environment"
635
+ : "default";
636
+
637
+ logger().info({
638
+ msg: "starting rivetkit envoy",
639
+ rivetkitVersion: VERSION,
640
+ envoyVersion: config.envoy.version,
641
+ envoyVersionSource,
642
+ });
643
+
644
+ if (
645
+ envoyVersionSource === "default" &&
646
+ getNodeEnv() === "production"
647
+ ) {
648
+ logger().error({
649
+ msg: "envoy version defaulted because neither config.envoy.version nor RIVET_ENVOY_VERSION is set; actors will not drain automatically across deploys. See https://rivet.dev/docs/actors/versions",
650
+ envoyVersion: config.envoy.version,
651
+ envoyVersionSource,
652
+ });
653
+ }
654
+
627
655
  const configuredRegistryPromise =
628
656
  this.#buildConfiguredRegistry(config);
629
657
  this.#runtimeServeConfiguredPromise = configuredRegistryPromise;
@@ -872,7 +872,19 @@ function encodeNativeCallbackError(error: unknown): Error {
872
872
  ? error
873
873
  : deconstructError(error, true);
874
874
 
875
- const bridgeError = new Error(encodeBridgeRivetError(structuredError), {
875
+ const { encoded, droppedMetadataCause } =
876
+ encodeBridgeRivetError(structuredError);
877
+ if (droppedMetadataCause !== undefined) {
878
+ logger().warn(
879
+ {
880
+ group: structuredError.group,
881
+ code: structuredError.code,
882
+ error: String(droppedMetadataCause),
883
+ },
884
+ "dropped error metadata that JSON cannot encode",
885
+ );
886
+ }
887
+ const bridgeError = new Error(encoded, {
876
888
  cause: error instanceof Error ? error : undefined,
877
889
  });
878
890
  return Object.assign(bridgeError, {
@@ -2771,6 +2783,11 @@ export class ActorContextHandleAdapter {
2771
2783
  }
2772
2784
  )[ACTOR_CONTEXT_INTERNAL_SYMBOL] = new NativeWorkflowRuntimeAdapter(
2773
2785
  this,
2786
+ () =>
2787
+ this.#logByInvocationScope.delete(
2788
+ this.#runtime.currentInvocationScope() ??
2789
+ NO_INVOCATION_SCOPE,
2790
+ ),
2774
2791
  );
2775
2792
  }
2776
2793
 
@@ -2978,7 +2995,10 @@ export class ActorContextHandleAdapter {
2978
2995
  );
2979
2996
  }
2980
2997
 
2981
- /** Cached per invocation, because the `run` context outlives workflow runs and steps. */
2998
+ /**
2999
+ * Cached per invocation, because the `run` context outlives workflow runs and steps.
3000
+ * A workflow queue receive clears the entry, because the run's ray becomes the message's ray.
3001
+ */
2982
3002
  get log() {
2983
3003
  const scope =
2984
3004
  this.#runtime.currentInvocationScope() ?? NO_INVOCATION_SCOPE;
@@ -3635,6 +3655,7 @@ type NativeWorkflowQueueMessage = Awaited<
3635
3655
 
3636
3656
  class NativeWorkflowRuntimeAdapter {
3637
3657
  #ctx: ActorContextHandleAdapter;
3658
+ #onMessagesReceived: () => void;
3638
3659
  #completions = new Map<string, (response?: unknown) => Promise<void>>();
3639
3660
 
3640
3661
  readonly id: string;
@@ -3702,8 +3723,12 @@ class NativeWorkflowRuntimeAdapter {
3702
3723
  ) => Promise<void>;
3703
3724
  };
3704
3725
 
3705
- constructor(ctx: ActorContextHandleAdapter) {
3726
+ constructor(
3727
+ ctx: ActorContextHandleAdapter,
3728
+ onMessagesReceived: () => void,
3729
+ ) {
3706
3730
  this.#ctx = ctx;
3731
+ this.#onMessagesReceived = onMessagesReceived;
3707
3732
  this.id = ctx.actorId;
3708
3733
  this.driver = {
3709
3734
  kvBatchGet: async (actorId, keys) => {
@@ -3749,6 +3774,9 @@ class NativeWorkflowRuntimeAdapter {
3749
3774
  timeout: timeout ?? 0,
3750
3775
  completable,
3751
3776
  });
3777
+ if (messages.length > 0) {
3778
+ this.#onMessagesReceived();
3779
+ }
3752
3780
  return messages.map((message) =>
3753
3781
  this.#wrapQueueMessage(message),
3754
3782
  );