rivetkit 2.3.19 → 2.3.21

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 (146) hide show
  1. package/dist/browser/client.d.ts +82 -5
  2. package/dist/browser/client.js +302 -60
  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.map +1 -1
  7. package/dist/tsup/agent-os/index.cjs +35 -1
  8. package/dist/tsup/agent-os/index.cjs.map +1 -1
  9. package/dist/tsup/agent-os/index.d.cts +71 -4
  10. package/dist/tsup/agent-os/index.d.ts +71 -4
  11. package/dist/tsup/agent-os/index.js +35 -1
  12. package/dist/tsup/agent-os/index.js.map +1 -1
  13. package/dist/tsup/{chunk-X7RMJQGY.js → chunk-2CI2IEVN.js} +2 -2
  14. package/dist/tsup/{chunk-WME6KOXW.js → chunk-5EGLHQJJ.js} +2 -2
  15. package/dist/tsup/{chunk-464WE4RF.js → chunk-5IBUZFLP.js} +2 -2
  16. package/dist/tsup/{chunk-PPEIPCW4.cjs → chunk-6LRPYB4Q.cjs} +4 -4
  17. package/dist/tsup/chunk-6LRPYB4Q.cjs.map +1 -0
  18. package/dist/tsup/{chunk-7BRRGBRY.cjs → chunk-APOANSDG.cjs} +10 -10
  19. package/dist/tsup/chunk-APOANSDG.cjs.map +1 -0
  20. package/dist/tsup/{chunk-G4U5L6AX.cjs → chunk-ARGNQGYG.cjs} +3 -3
  21. package/dist/tsup/chunk-ARGNQGYG.cjs.map +1 -0
  22. package/dist/tsup/{chunk-IJUXBWH4.cjs → chunk-B5SNXPSS.cjs} +448 -199
  23. package/dist/tsup/chunk-B5SNXPSS.cjs.map +1 -0
  24. package/dist/tsup/{chunk-4SC2NSAV.js → chunk-CJXBGTVT.js} +2 -2
  25. package/dist/tsup/{chunk-EB77NZL4.js → chunk-CSOHAAZK.js} +33 -1
  26. package/dist/tsup/chunk-CSOHAAZK.js.map +1 -0
  27. package/dist/tsup/{chunk-Z4RAXFSD.cjs → chunk-GHKQC4PD.cjs} +3 -3
  28. package/dist/tsup/chunk-GHKQC4PD.cjs.map +1 -0
  29. package/dist/tsup/chunk-JX2ITWAV.cjs.map +1 -1
  30. package/dist/tsup/chunk-KVFIONKQ.cjs.map +1 -1
  31. package/dist/tsup/{chunk-RJC5O2SD.cjs → chunk-KWGN2ZBJ.cjs} +3 -2
  32. package/dist/tsup/chunk-KWGN2ZBJ.cjs.map +1 -0
  33. package/dist/tsup/{chunk-OGPFN6BT.cjs → chunk-LWSLKKZ5.cjs} +3 -3
  34. package/dist/tsup/chunk-LWSLKKZ5.cjs.map +1 -0
  35. package/dist/tsup/chunk-MYWZCDML.cjs.map +1 -1
  36. package/dist/tsup/{chunk-7A5EIXJJ.cjs → chunk-NL2EIKK6.cjs} +7 -7
  37. package/dist/tsup/chunk-NL2EIKK6.cjs.map +1 -0
  38. package/dist/tsup/{chunk-EO5UOXXY.cjs → chunk-NWCAIWHL.cjs} +34 -2
  39. package/dist/tsup/chunk-NWCAIWHL.cjs.map +1 -0
  40. package/dist/tsup/{chunk-BKX7UDTA.js → chunk-PYRI75DL.js} +314 -65
  41. package/dist/tsup/chunk-PYRI75DL.js.map +1 -0
  42. package/dist/tsup/{chunk-7HTST3NX.js → chunk-RFN3BG6D.js} +3 -3
  43. package/dist/tsup/chunk-RFN3BG6D.js.map +1 -0
  44. package/dist/tsup/{chunk-DN6MD3KK.js → chunk-RGIZNZYO.js} +3 -2
  45. package/dist/tsup/chunk-RGIZNZYO.js.map +1 -0
  46. package/dist/tsup/{chunk-DK3L22R6.js → chunk-VMMXXVDO.js} +4 -4
  47. package/dist/tsup/client/mod.cjs +5 -5
  48. package/dist/tsup/client/mod.cjs.map +1 -1
  49. package/dist/tsup/client/mod.d.cts +12 -3
  50. package/dist/tsup/client/mod.d.ts +12 -3
  51. package/dist/tsup/client/mod.js +4 -4
  52. package/dist/tsup/common/log.cjs +2 -2
  53. package/dist/tsup/common/log.cjs.map +1 -1
  54. package/dist/tsup/common/log.js +1 -1
  55. package/dist/tsup/common/websocket.cjs +3 -3
  56. package/dist/tsup/common/websocket.cjs.map +1 -1
  57. package/dist/tsup/common/websocket.js +2 -2
  58. package/dist/tsup/{config-DEm0yIVV.d.ts → config-Bc1nnvUl.d.ts} +76 -6
  59. package/dist/tsup/{config-8n2AOhXK.d.cts → config-Do_0guSS.d.cts} +76 -6
  60. package/dist/tsup/{context-DwjOVvlE.d.cts → context-CgNkhjdY.d.cts} +1 -1
  61. package/dist/tsup/{context-DGg2-IOm.d.ts → context-oGQxZDPz.d.ts} +1 -1
  62. package/dist/tsup/db/drizzle.cjs +2 -2
  63. package/dist/tsup/db/drizzle.cjs.map +1 -1
  64. package/dist/tsup/db/drizzle.js +1 -1
  65. package/dist/tsup/db/mod.cjs +3 -3
  66. package/dist/tsup/db/mod.cjs.map +1 -1
  67. package/dist/tsup/db/mod.js +2 -2
  68. package/dist/tsup/dynamic/mod.cjs +2 -2
  69. package/dist/tsup/dynamic/mod.cjs.map +1 -1
  70. package/dist/tsup/dynamic/mod.d.cts +2 -1
  71. package/dist/tsup/dynamic/mod.d.ts +2 -1
  72. package/dist/tsup/dynamic/mod.js +1 -1
  73. package/dist/tsup/inspector/mod.cjs +5 -5
  74. package/dist/tsup/inspector/mod.cjs.map +1 -1
  75. package/dist/tsup/inspector/mod.js +4 -4
  76. package/dist/tsup/inspector/workflow.cjs +4 -4
  77. package/dist/tsup/inspector/workflow.cjs.map +1 -1
  78. package/dist/tsup/inspector/workflow.js +3 -3
  79. package/dist/tsup/inspector-tab/mod.cjs +2 -2
  80. package/dist/tsup/inspector-tab/mod.cjs.map +1 -1
  81. package/dist/tsup/inspector-tab/mod.d.cts +3 -2
  82. package/dist/tsup/inspector-tab/mod.d.ts +3 -2
  83. package/dist/tsup/inspector-tab/mod.js +1 -1
  84. package/dist/tsup/mod.cjs +140 -136
  85. package/dist/tsup/mod.cjs.map +1 -1
  86. package/dist/tsup/mod.d.cts +4 -3
  87. package/dist/tsup/mod.d.ts +4 -3
  88. package/dist/tsup/mod.js +44 -40
  89. package/dist/tsup/mod.js.map +1 -1
  90. package/dist/tsup/test/mod.cjs +6 -6
  91. package/dist/tsup/test/mod.cjs.map +1 -1
  92. package/dist/tsup/test/mod.d.cts +2 -1
  93. package/dist/tsup/test/mod.d.ts +2 -1
  94. package/dist/tsup/test/mod.js +4 -4
  95. package/dist/tsup/unstable/migrations.cjs.map +1 -1
  96. package/dist/tsup/utils.cjs +2 -2
  97. package/dist/tsup/utils.cjs.map +1 -1
  98. package/dist/tsup/utils.js +1 -1
  99. package/dist/tsup/workflow/mod.cjs +10 -10
  100. package/dist/tsup/workflow/mod.cjs.map +1 -1
  101. package/dist/tsup/workflow/mod.d.cts +4 -3
  102. package/dist/tsup/workflow/mod.d.ts +4 -3
  103. package/dist/tsup/workflow/mod.js +5 -5
  104. package/package.json +10 -9
  105. package/src/actor/actions.ts +36 -0
  106. package/src/actor/config.ts +27 -0
  107. package/src/actor/definition.ts +6 -1
  108. package/src/actor/mod.ts +5 -0
  109. package/src/actor/tracing.ts +28 -0
  110. package/src/client/actor-conn.ts +34 -1
  111. package/src/client/actor-handle.ts +29 -1
  112. package/src/client/auth.ts +37 -0
  113. package/src/client/client.ts +10 -2
  114. package/src/client/config.ts +12 -1
  115. package/src/client/mod.ts +9 -0
  116. package/src/client/token-provider.test.ts +86 -0
  117. package/src/client/token-provider.ts +93 -0
  118. package/src/client/utils.ts +1 -0
  119. package/src/engine-client/actor-websocket-client.ts +2 -1
  120. package/src/engine-client/api-endpoints.ts +28 -0
  121. package/src/engine-client/driver.ts +4 -0
  122. package/src/engine-client/mod.ts +201 -64
  123. package/src/registry/config/envoy.ts +1 -17
  124. package/src/registry/config/index.test.ts +2 -2
  125. package/src/registry/config/index.ts +7 -2
  126. package/src/registry/index.ts +28 -0
  127. package/src/registry/native.ts +18 -0
  128. package/src/registry/runtime.ts +8 -0
  129. package/dist/tsup/chunk-7A5EIXJJ.cjs.map +0 -1
  130. package/dist/tsup/chunk-7BRRGBRY.cjs.map +0 -1
  131. package/dist/tsup/chunk-7HTST3NX.js.map +0 -1
  132. package/dist/tsup/chunk-BKX7UDTA.js.map +0 -1
  133. package/dist/tsup/chunk-DN6MD3KK.js.map +0 -1
  134. package/dist/tsup/chunk-EB77NZL4.js.map +0 -1
  135. package/dist/tsup/chunk-EO5UOXXY.cjs.map +0 -1
  136. package/dist/tsup/chunk-G4U5L6AX.cjs.map +0 -1
  137. package/dist/tsup/chunk-IJUXBWH4.cjs.map +0 -1
  138. package/dist/tsup/chunk-OGPFN6BT.cjs.map +0 -1
  139. package/dist/tsup/chunk-PPEIPCW4.cjs.map +0 -1
  140. package/dist/tsup/chunk-RJC5O2SD.cjs.map +0 -1
  141. package/dist/tsup/chunk-Z4RAXFSD.cjs.map +0 -1
  142. /package/dist/tsup/{chunk-X7RMJQGY.js.map → chunk-2CI2IEVN.js.map} +0 -0
  143. /package/dist/tsup/{chunk-WME6KOXW.js.map → chunk-5EGLHQJJ.js.map} +0 -0
  144. /package/dist/tsup/{chunk-464WE4RF.js.map → chunk-5IBUZFLP.js.map} +0 -0
  145. /package/dist/tsup/{chunk-4SC2NSAV.js.map → chunk-CJXBGTVT.js.map} +0 -0
  146. /package/dist/tsup/{chunk-DK3L22R6.js.map → chunk-VMMXXVDO.js.map} +0 -0
@@ -336,7 +336,8 @@ export async function openWebSocketToGateway(
336
336
 
337
337
  logger().debug({
338
338
  msg: "opening websocket to actor via guard",
339
- gatewayUrl,
339
+ // Gateway URLs can embed credentials; never include the path or query in logs.
340
+ origin: new URL(gatewayUrl).origin,
340
341
  });
341
342
 
342
343
  // Create WebSocket connection
@@ -1,5 +1,6 @@
1
1
  import { serializeActorKey } from "@/actor/keys";
2
2
  import type { ClientConfig } from "@/client/client";
3
+ import type { IssuedToken, IssueTokenOptions } from "@/client/auth";
3
4
  import type { MetadataResponse } from "@/common/router";
4
5
  import type {
5
6
  ActorsCreateRequest,
@@ -12,6 +13,33 @@ import type {
12
13
  import type { RivetId } from "@/engine-api/common";
13
14
  import { apiCall } from "./api-utils";
14
15
 
16
+ /** Engine's JSON response uses snake_case; the public result uses camelCase. */
17
+ export type IssueTokenResponse = {
18
+ token: IssuedToken["token"];
19
+ issued_ts: IssuedToken["issuedAt"];
20
+ expires_ts: IssuedToken["expiresAt"];
21
+ };
22
+
23
+ export async function issueToken(
24
+ config: ClientConfig,
25
+ options: IssueTokenOptions,
26
+ ): Promise<IssueTokenResponse> {
27
+ return apiCall<
28
+ {
29
+ namespace: string;
30
+ subject?: IssueTokenOptions["subject"];
31
+ duration?: IssueTokenOptions["expiresIn"];
32
+ grants: IssueTokenOptions["grants"];
33
+ },
34
+ IssueTokenResponse
35
+ >(config, "POST", "/auth/tokens", {
36
+ namespace: config.namespace,
37
+ subject: options.subject,
38
+ duration: options.expiresIn,
39
+ grants: options.grants,
40
+ });
41
+ }
42
+
15
43
  // MARK: Get actor
16
44
  export async function getActor(
17
45
  config: ClientConfig,
@@ -1,6 +1,7 @@
1
1
  import type { Hono, Context as HonoContext } from "hono";
2
2
  import type { ActorKey, Encoding, UniversalWebSocket } from "@/actor/mod";
3
3
  import type { ActorQuery, CrashPolicy } from "@/client/query";
4
+ import type { IssuedToken, IssueTokenOptions } from "@/client/auth";
4
5
  import type { RegistryConfig } from "@/registry/config";
5
6
  import type { GetUpgradeWebSocket } from "@/utils";
6
7
 
@@ -17,6 +18,7 @@ export function shouldSkipReadyWait(
17
18
  }
18
19
 
19
20
  export interface EngineControlClient {
21
+ issueToken(options: IssueTokenOptions): Promise<IssuedToken>;
20
22
  getForId(input: GetForIdInput): Promise<ActorOutput | undefined>;
21
23
  getWithKey(input: GetWithKeyInput): Promise<ActorOutput | undefined>;
22
24
  getOrCreateWithKey(input: GetOrCreateWithKeyInput): Promise<ActorOutput>;
@@ -35,6 +37,8 @@ export interface EngineControlClient {
35
37
  params: unknown,
36
38
  options?: GatewayRequestOptions,
37
39
  ): Promise<UniversalWebSocket>;
40
+ /** Refresh a credential rejected during this WebSocket handshake, if supported. */
41
+ refreshAuthToken?(ws: UniversalWebSocket): Promise<boolean>;
38
42
  proxyRequest(
39
43
  c: HonoContext,
40
44
  actorRequest: Request,
@@ -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;
@@ -2,6 +2,7 @@ import { VirtualWebSocket } from "@rivetkit/virtual-websocket";
2
2
  import {
3
3
  flattenActionHandlers,
4
4
  flattenActionInputSchemas,
5
+ flattenActionTraceSamplers,
5
6
  } from "@/actor/actions";
6
7
  import {
7
8
  ACTOR_CONTEXT_INTERNAL_SYMBOL,
@@ -41,6 +42,7 @@ import {
41
42
  getQueueCanPublish,
42
43
  hasSchemaConfigKey,
43
44
  } from "@/actor/schema";
45
+ import type { ActionSampleRates } from "@/actor/tracing";
44
46
  import {
45
47
  type AnyClient,
46
48
  type Client,
@@ -112,6 +114,7 @@ import type {
112
114
  CoreRuntime,
113
115
  RegistryHandle,
114
116
  RuntimeActorConfig,
117
+ RuntimeActorTracingConfig,
115
118
  RuntimeBytes,
116
119
  RuntimeCronFire,
117
120
  RuntimeCronJobInfo,
@@ -3854,6 +3857,17 @@ function withConnContext(
3854
3857
  });
3855
3858
  }
3856
3859
 
3860
+ function buildActorTracingConfig(
3861
+ actions: unknown,
3862
+ tracing: { sampler?: number; actions?: ActionSampleRates } | undefined,
3863
+ ): RuntimeActorTracingConfig | undefined {
3864
+ if (tracing === undefined) return undefined;
3865
+ return {
3866
+ sampler: tracing.sampler,
3867
+ actions: flattenActionTraceSamplers(actions, tracing.actions),
3868
+ };
3869
+ }
3870
+
3857
3871
  function buildActorConfig(
3858
3872
  definition: AnyActorDefinition,
3859
3873
  registryConfig: RegistryConfig,
@@ -3874,6 +3888,10 @@ function buildActorConfig(
3874
3888
  hasDatabase: true,
3875
3889
  remoteSqlite: usesRemoteSqlite,
3876
3890
  sqliteProfiling,
3891
+ tracing: buildActorTracingConfig(
3892
+ definition.config.actions,
3893
+ definition.config.tracing,
3894
+ ),
3877
3895
  enableActorRuntimeSocket: options.enableActorRuntimeSocket === true,
3878
3896
  hasState:
3879
3897
  config.state !== undefined ||