rivetkit 2.3.18-rc.4 → 2.3.19

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 (136) hide show
  1. package/dist/browser/client.d.ts +723 -616
  2. package/dist/browser/client.js +184 -28
  3. package/dist/browser/client.js.map +1 -1
  4. package/dist/browser/inspector/client.js +3 -1
  5. package/dist/browser/inspector/client.js.map +1 -1
  6. package/dist/tsup/actor/errors.cjs +6 -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 +5 -1
  11. package/dist/tsup/agent-os/index.cjs +3 -1
  12. package/dist/tsup/agent-os/index.cjs.map +1 -1
  13. package/dist/tsup/agent-os/index.d.cts +110 -3
  14. package/dist/tsup/agent-os/index.d.ts +110 -3
  15. package/dist/tsup/agent-os/index.js +3 -1
  16. package/dist/tsup/agent-os/index.js.map +1 -1
  17. package/dist/tsup/{chunk-ENDOWQTM.js → chunk-464WE4RF.js} +2 -2
  18. package/dist/tsup/{chunk-KKG2CTSI.js → chunk-4SC2NSAV.js} +2 -2
  19. package/dist/tsup/{chunk-R2WRP6VV.cjs → chunk-7A5EIXJJ.cjs} +7 -7
  20. package/dist/tsup/{chunk-R2WRP6VV.cjs.map → chunk-7A5EIXJJ.cjs.map} +1 -1
  21. package/dist/tsup/{chunk-GUVDWB2O.cjs → chunk-7BRRGBRY.cjs} +10 -10
  22. package/dist/tsup/{chunk-GUVDWB2O.cjs.map → chunk-7BRRGBRY.cjs.map} +1 -1
  23. package/dist/tsup/{chunk-KVPU65TI.js → chunk-7HTST3NX.js} +3 -3
  24. package/dist/tsup/{chunk-MS6C3B2I.js → chunk-BKX7UDTA.js} +185 -28
  25. package/dist/tsup/chunk-BKX7UDTA.js.map +1 -0
  26. package/dist/tsup/{chunk-FL2FDUSZ.js → chunk-DK3L22R6.js} +4 -4
  27. package/dist/tsup/{chunk-CAL23WOC.js → chunk-DN6MD3KK.js} +7 -7
  28. package/dist/tsup/chunk-DN6MD3KK.js.map +1 -0
  29. package/dist/tsup/{chunk-6W5VGLFT.js → chunk-EB77NZL4.js} +1 -1
  30. package/dist/tsup/chunk-EB77NZL4.js.map +1 -0
  31. package/dist/tsup/{chunk-4KGXFC6M.cjs → chunk-EO5UOXXY.cjs} +1 -1
  32. package/dist/tsup/chunk-EO5UOXXY.cjs.map +1 -0
  33. package/dist/tsup/{chunk-QFLG6UB4.cjs → chunk-G4U5L6AX.cjs} +3 -3
  34. package/dist/tsup/{chunk-QFLG6UB4.cjs.map → chunk-G4U5L6AX.cjs.map} +1 -1
  35. package/dist/tsup/{chunk-TGGF6SZN.cjs → chunk-IJUXBWH4.cjs} +351 -194
  36. package/dist/tsup/chunk-IJUXBWH4.cjs.map +1 -0
  37. package/dist/tsup/{chunk-2JSYYH6P.cjs → chunk-JX2ITWAV.cjs} +13 -2
  38. package/dist/tsup/chunk-JX2ITWAV.cjs.map +1 -0
  39. package/dist/tsup/{chunk-N3DJTMSZ.cjs → chunk-OGPFN6BT.cjs} +3 -3
  40. package/dist/tsup/{chunk-N3DJTMSZ.cjs.map → chunk-OGPFN6BT.cjs.map} +1 -1
  41. package/dist/tsup/{chunk-OUQUIBVW.js → chunk-P2OFXH4G.js} +12 -1
  42. package/dist/tsup/chunk-P2OFXH4G.js.map +1 -0
  43. package/dist/tsup/{chunk-F3IP3SGO.cjs → chunk-PPEIPCW4.cjs} +4 -4
  44. package/dist/tsup/{chunk-F3IP3SGO.cjs.map → chunk-PPEIPCW4.cjs.map} +1 -1
  45. package/dist/tsup/{chunk-7E2W2YSM.cjs → chunk-RJC5O2SD.cjs} +13 -13
  46. package/dist/tsup/chunk-RJC5O2SD.cjs.map +1 -0
  47. package/dist/tsup/{chunk-YQD6RRVP.js → chunk-WME6KOXW.js} +2 -2
  48. package/dist/tsup/{chunk-PLAJJOTE.js → chunk-X7RMJQGY.js} +2 -2
  49. package/dist/tsup/{chunk-4OMHYVOQ.cjs → chunk-Z4RAXFSD.cjs} +3 -3
  50. package/dist/tsup/{chunk-4OMHYVOQ.cjs.map → chunk-Z4RAXFSD.cjs.map} +1 -1
  51. package/dist/tsup/client/mod.cjs +6 -6
  52. package/dist/tsup/client/mod.d.cts +5 -5
  53. package/dist/tsup/client/mod.d.ts +5 -5
  54. package/dist/tsup/client/mod.js +5 -5
  55. package/dist/tsup/common/log.cjs +3 -3
  56. package/dist/tsup/common/log.js +2 -2
  57. package/dist/tsup/common/websocket.cjs +4 -4
  58. package/dist/tsup/common/websocket.js +3 -3
  59. package/dist/tsup/{config-gvHwMas-.d.cts → config-8n2AOhXK.d.cts} +114 -10
  60. package/dist/tsup/{config-Ug5M2MOr.d.ts → config-DEm0yIVV.d.ts} +114 -10
  61. package/dist/tsup/{context-DererUCI.d.ts → context-DGg2-IOm.d.ts} +1 -1
  62. package/dist/tsup/{context-BSCBvbfM.d.cts → context-DwjOVvlE.d.cts} +1 -1
  63. package/dist/tsup/db/drizzle.cjs +3 -3
  64. package/dist/tsup/db/drizzle.js +2 -2
  65. package/dist/tsup/db/mod.cjs +4 -4
  66. package/dist/tsup/db/mod.js +3 -3
  67. package/dist/tsup/dynamic/mod.cjs +2 -2
  68. package/dist/tsup/dynamic/mod.d.cts +3 -3
  69. package/dist/tsup/dynamic/mod.d.ts +3 -3
  70. package/dist/tsup/dynamic/mod.js +1 -1
  71. package/dist/tsup/inspector/mod.cjs +6 -6
  72. package/dist/tsup/inspector/mod.js +5 -5
  73. package/dist/tsup/inspector/workflow.cjs +5 -5
  74. package/dist/tsup/inspector/workflow.js +4 -4
  75. package/dist/tsup/inspector-tab/mod.cjs +2 -2
  76. package/dist/tsup/inspector-tab/mod.d.cts +4 -4
  77. package/dist/tsup/inspector-tab/mod.d.ts +4 -4
  78. package/dist/tsup/inspector-tab/mod.js +1 -1
  79. package/dist/tsup/mod.cjs +492 -319
  80. package/dist/tsup/mod.cjs.map +1 -1
  81. package/dist/tsup/mod.d.cts +5 -5
  82. package/dist/tsup/mod.d.ts +5 -5
  83. package/dist/tsup/mod.js +367 -194
  84. package/dist/tsup/mod.js.map +1 -1
  85. package/dist/tsup/test/mod.cjs +7 -7
  86. package/dist/tsup/test/mod.d.cts +3 -3
  87. package/dist/tsup/test/mod.d.ts +3 -3
  88. package/dist/tsup/test/mod.js +5 -5
  89. package/dist/tsup/{utils-Bbz-3411.d.cts → utils-CNFeS-Dk.d.cts} +1 -1
  90. package/dist/tsup/{utils-vvJQyygr.d.ts → utils-DsoDinPz.d.cts} +9 -1
  91. package/dist/tsup/{utils-vvJQyygr.d.cts → utils-DsoDinPz.d.ts} +9 -1
  92. package/dist/tsup/{utils-BqdWVlzm.d.ts → utils-OQHBjfcM.d.ts} +1 -1
  93. package/dist/tsup/utils.cjs +3 -3
  94. package/dist/tsup/utils.d.cts +2 -2
  95. package/dist/tsup/utils.d.ts +2 -2
  96. package/dist/tsup/utils.js +2 -2
  97. package/dist/tsup/workflow/mod.cjs +33 -27
  98. package/dist/tsup/workflow/mod.cjs.map +1 -1
  99. package/dist/tsup/workflow/mod.d.cts +5 -5
  100. package/dist/tsup/workflow/mod.d.ts +5 -5
  101. package/dist/tsup/workflow/mod.js +25 -19
  102. package/dist/tsup/workflow/mod.js.map +1 -1
  103. package/package.json +10 -8
  104. package/src/actor/config.ts +38 -0
  105. package/src/actor/errors.ts +31 -0
  106. package/src/client/actor-conn.ts +7 -0
  107. package/src/client/actor-handle.ts +57 -17
  108. package/src/client/client.ts +26 -6
  109. package/src/client/outbound-telemetry.ts +66 -0
  110. package/src/client/queue.ts +3 -0
  111. package/src/client/raw-utils.ts +3 -0
  112. package/src/common/actor-router-consts.ts +3 -0
  113. package/src/common/actor-telemetry-context.ts +19 -0
  114. package/src/common/otel-context.ts +99 -0
  115. package/src/common/utils.ts +1 -19
  116. package/src/engine-client/actor-http-client.ts +15 -0
  117. package/src/registry/index.ts +6 -0
  118. package/src/registry/napi-runtime.ts +155 -35
  119. package/src/registry/native.ts +277 -191
  120. package/src/registry/runtime.ts +66 -0
  121. package/src/registry/wasm-runtime.ts +40 -0
  122. package/src/workflow/driver.ts +20 -12
  123. package/dist/tsup/chunk-2JSYYH6P.cjs.map +0 -1
  124. package/dist/tsup/chunk-4KGXFC6M.cjs.map +0 -1
  125. package/dist/tsup/chunk-6W5VGLFT.js.map +0 -1
  126. package/dist/tsup/chunk-7E2W2YSM.cjs.map +0 -1
  127. package/dist/tsup/chunk-CAL23WOC.js.map +0 -1
  128. package/dist/tsup/chunk-MS6C3B2I.js.map +0 -1
  129. package/dist/tsup/chunk-OUQUIBVW.js.map +0 -1
  130. package/dist/tsup/chunk-TGGF6SZN.cjs.map +0 -1
  131. /package/dist/tsup/{chunk-ENDOWQTM.js.map → chunk-464WE4RF.js.map} +0 -0
  132. /package/dist/tsup/{chunk-KKG2CTSI.js.map → chunk-4SC2NSAV.js.map} +0 -0
  133. /package/dist/tsup/{chunk-KVPU65TI.js.map → chunk-7HTST3NX.js.map} +0 -0
  134. /package/dist/tsup/{chunk-FL2FDUSZ.js.map → chunk-DK3L22R6.js.map} +0 -0
  135. /package/dist/tsup/{chunk-YQD6RRVP.js.map → chunk-WME6KOXW.js.map} +0 -0
  136. /package/dist/tsup/{chunk-PLAJJOTE.js.map → chunk-X7RMJQGY.js.map} +0 -0
@@ -1,5 +1,6 @@
1
1
  import type { AnyActorDefinition } from "@/actor/definition";
2
- import type { ActorSpecifier } from "@/actor/errors";
2
+ import { type ActorSpecifier, encodeErrorForBridge } from "@/actor/errors";
3
+ import type { ActorInvocationSpanContext } from "@/common/actor-telemetry-context";
3
4
  import {
4
5
  HEADER_CONN_PARAMS,
5
6
  HEADER_ENCODING,
@@ -24,6 +25,7 @@ import { AsyncMutex } from "@/common/database/shared";
24
25
  import type { Encoding, JsonCompatValue } from "@/common/encoding";
25
26
  import { deconstructError } from "@/common/utils";
26
27
  import type { EngineControlClient } from "@/engine-client/driver";
28
+ import type { StartCallSpan, CurrentActorInvocation } from "@/registry/runtime";
27
29
  import {
28
30
  decodeCborCompat,
29
31
  deserializeWithEncoding,
@@ -53,6 +55,7 @@ import { type ClientRaw, CREATE_ACTOR_CONN_PROXY } from "./client";
53
55
  import { ActorError, isSchedulingError } from "./errors";
54
56
  import { retryOnLifecycleBoundary } from "./lifecycle-errors";
55
57
  import { logger } from "./log";
58
+ import { outboundTelemetryHeaders } from "./outbound-telemetry";
56
59
  import {
57
60
  createQueueSender,
58
61
  type QueueSendNoWaitOptions,
@@ -82,6 +85,8 @@ export class ActorHandleRaw {
82
85
  #resolvedActorId?: string;
83
86
  #resolvingActorId?: Promise<string>;
84
87
  #queueSendMutex = new AsyncMutex();
88
+ #currentActorInvocation?: CurrentActorInvocation;
89
+ #startCallSpan?: StartCallSpan;
85
90
 
86
91
  /**
87
92
  * Do not call this directly.
@@ -99,6 +104,8 @@ export class ActorHandleRaw {
99
104
  actorResolutionState: ActorResolutionState,
100
105
  gatewayOptions: ActorGatewayOptions = {},
101
106
  signal?: AbortSignal,
107
+ currentActorInvocation?: CurrentActorInvocation,
108
+ startCallSpan?: StartCallSpan,
102
109
  ) {
103
110
  this.#client = client;
104
111
  this.#driver = driver;
@@ -108,6 +115,8 @@ export class ActorHandleRaw {
108
115
  this.#params = params;
109
116
  this.#getParams = getParams;
110
117
  this.#signal = signal;
118
+ this.#currentActorInvocation = currentActorInvocation;
119
+ this.#startCallSpan = startCallSpan;
111
120
  }
112
121
 
113
122
  async #resolveConnectionParams(): Promise<unknown> {
@@ -166,6 +175,10 @@ export class ActorHandleRaw {
166
175
  return await createQueueSender({
167
176
  encoding: this.#encoding,
168
177
  params: this.#params,
178
+ telemetryHeaders: () =>
179
+ outboundTelemetryHeaders(
180
+ this.#currentActorInvocation?.(),
181
+ ),
169
182
  customFetch: async (request: Request) => {
170
183
  return await this.#driver.sendRequest(
171
184
  target,
@@ -275,20 +288,41 @@ export class ActorHandleRaw {
275
288
  // when no per-call signal is provided.
276
289
  const signal = opts.signal ?? this.#signal;
277
290
  const optsWithSignal = { ...opts, signal };
291
+ // Open before retries so all attempts share one span and parent context.
292
+ const call = this.#startCallSpan?.(
293
+ getActorNameFromQuery(this.#actorResolutionState),
294
+ opts.name,
295
+ );
278
296
  const run = async () =>
279
- (await this.#sendActionNow(optsWithSignal)) as Response;
280
- if (opts.name === "destroy") {
281
- return await run();
297
+ (await this.#sendActionAttempts(
298
+ optsWithSignal,
299
+ call?.span,
300
+ )) as Response;
301
+ const send = async () => {
302
+ if (opts.name === "destroy") {
303
+ return await run();
304
+ }
305
+ return await retryOnLifecycleBoundary(run, { signal });
306
+ };
307
+ if (!call) {
308
+ return await send();
309
+ }
310
+ try {
311
+ const output = await send();
312
+ call.finish();
313
+ return output;
314
+ } catch (error) {
315
+ call.finish(encodeErrorForBridge(error));
316
+ throw error;
282
317
  }
283
-
284
- return await retryOnLifecycleBoundary(run, { signal });
285
318
  }
286
319
 
287
- async #sendActionNow(
320
+ async #sendActionAttempts(
288
321
  opts: {
289
322
  name: string;
290
323
  args: unknown[];
291
324
  } & ActorActionOptions,
325
+ callSpan?: ActorInvocationSpanContext,
292
326
  ): Promise<unknown> {
293
327
  const maxAttempts = this.#getDynamicQueryMaxAttempts();
294
328
  let useQueryTarget = isDynamicActorQuery(this.#actorResolutionState);
@@ -320,6 +354,16 @@ export class ActorHandleRaw {
320
354
  name: opts.name,
321
355
  encoding: this.#encoding,
322
356
  });
357
+ const headers: Record<string, string> = {
358
+ [HEADER_ENCODING]: this.#encoding,
359
+ ...outboundTelemetryHeaders(
360
+ this.#currentActorInvocation?.(),
361
+ callSpan,
362
+ ),
363
+ };
364
+ if (this.#params !== undefined) {
365
+ headers[HEADER_CONN_PARAMS] = JSON.stringify(this.#params);
366
+ }
323
367
  const output = await sendHttpRequest<
324
368
  protocol.HttpActionRequest,
325
369
  protocol.HttpActionResponse,
@@ -330,16 +374,7 @@ export class ActorHandleRaw {
330
374
  >({
331
375
  url: `http://actor/action/${encodeURIComponent(opts.name)}`,
332
376
  method: "POST",
333
- headers: {
334
- [HEADER_ENCODING]: this.#encoding,
335
- ...(this.#params !== undefined
336
- ? {
337
- [HEADER_CONN_PARAMS]: JSON.stringify(
338
- this.#params,
339
- ),
340
- }
341
- : {}),
342
- },
377
+ headers,
343
378
  body: opts.args,
344
379
  encoding: this.#encoding,
345
380
  customFetch: async (request) =>
@@ -643,6 +678,7 @@ export class ActorHandleRaw {
643
678
  this.#encoding,
644
679
  this.#actorResolutionState,
645
680
  resolveActorGatewayOptions(this.#gatewayOptions, options),
681
+ this.#currentActorInvocation,
646
682
  );
647
683
 
648
684
  return this.#client[CREATE_ACTOR_CONN_PROXY](
@@ -680,6 +716,9 @@ export class ActorHandleRaw {
680
716
  skipReadyWait,
681
717
  },
682
718
  );
719
+ const telemetryHeaders = outboundTelemetryHeaders(
720
+ this.#currentActorInvocation?.(),
721
+ );
683
722
 
684
723
  for (let attempt = 0; attempt < maxAttempts; attempt++) {
685
724
  let actorId: string | undefined;
@@ -698,6 +737,7 @@ export class ActorHandleRaw {
698
737
  clonesInputBody ? input.clone() : input,
699
738
  requestInit,
700
739
  gatewayOptions,
740
+ telemetryHeaders,
701
741
  );
702
742
  const retry = await this.#shouldRetryRawFetchResponse(
703
743
  response,
@@ -3,6 +3,7 @@ import type { ActorQuery } from "@/client/query";
3
3
  import type { Encoding } from "@/common/encoding";
4
4
  import type { EngineControlClient } from "@/engine-client/driver";
5
5
  import type { Registry } from "@/registry";
6
+ import type { StartCallSpan, CurrentActorInvocation } from "@/registry/runtime";
6
7
  import type { ActorActionFunction, ActorGatewayOptions } from "./actor-common";
7
8
  import {
8
9
  type ActorConn,
@@ -175,6 +176,20 @@ export interface Region {
175
176
  export const ACTOR_CONNS_SYMBOL = Symbol("actorConns");
176
177
  export const CREATE_ACTOR_CONN_PROXY = Symbol("createActorConnProxy");
177
178
 
179
+ /** Options for constructing a raw actor client. */
180
+ export interface ClientRawOptions {
181
+ encoding?: Encoding;
182
+ gateway?: ActorGatewayOptions;
183
+ }
184
+
185
+ /** Runtime-supplied wiring for clients created inside an actor. Set only by the native runtime. */
186
+ export interface ActorClientRuntimeOptions {
187
+ /** Supplies the calling actor's invocation so actor-to-actor clients propagate its trace and ray ID. */
188
+ currentActorInvocation?: CurrentActorInvocation;
189
+ /** Opens and finishes the Core span covering an actor-to-actor call. */
190
+ startCallSpan?: StartCallSpan;
191
+ }
192
+
178
193
  /**
179
194
  * Client for managing & connecting to actors.
180
195
  *
@@ -189,19 +204,22 @@ export class ClientRaw {
189
204
  #driver: EngineControlClient;
190
205
  #encodingKind: Encoding;
191
206
  #gatewayOptions: ActorGatewayOptions;
207
+ #currentActorInvocation?: CurrentActorInvocation;
208
+ #startCallSpan?: StartCallSpan;
192
209
 
193
210
  /**
194
211
  * Creates an instance of Client.
195
212
  */
196
213
  public constructor(
197
214
  driver: EngineControlClient,
198
- encoding: Encoding | undefined,
199
- gatewayOptions: ActorGatewayOptions = {},
215
+ options: ClientRawOptions & ActorClientRuntimeOptions = {},
200
216
  ) {
201
217
  this.#driver = driver;
202
218
 
203
- this.#encodingKind = encoding ?? "bare";
204
- this.#gatewayOptions = gatewayOptions;
219
+ this.#encodingKind = options.encoding ?? "bare";
220
+ this.#gatewayOptions = options.gateway ?? {};
221
+ this.#currentActorInvocation = options.currentActorInvocation;
222
+ this.#startCallSpan = options.startCallSpan;
205
223
  }
206
224
 
207
225
  /**
@@ -403,6 +421,8 @@ export class ClientRaw {
403
421
  actorQuery,
404
422
  this.#gatewayOptions,
405
423
  signal,
424
+ this.#currentActorInvocation,
425
+ this.#startCallSpan,
406
426
  );
407
427
  }
408
428
 
@@ -459,9 +479,9 @@ export type AnyClient = Client<Registry<any>>;
459
479
 
460
480
  export function createClientWithDriver<A extends Registry<any>>(
461
481
  driver: EngineControlClient,
462
- config: { encoding?: Encoding; gateway?: ActorGatewayOptions } = {},
482
+ options: ClientRawOptions & ActorClientRuntimeOptions = {},
463
483
  ): Client<A> {
464
- const client = new ClientRaw(driver, config.encoding, config.gateway);
484
+ const client = new ClientRaw(driver, options);
465
485
 
466
486
  // Create proxy for accessing actors by name
467
487
  return new Proxy(client, {
@@ -0,0 +1,66 @@
1
+ import {
2
+ HEADER_RIVET_RAY_ID,
3
+ HEADER_TRACEPARENT,
4
+ HEADER_TRACESTATE,
5
+ } from "@/common/actor-router-consts";
6
+ import type { ActorInvocationTraceContext } from "@/common/actor-telemetry-context";
7
+ import {
8
+ actorInvocationTraceHeaders,
9
+ readActiveRayId,
10
+ readActiveTraceHeaders,
11
+ } from "@/common/otel-context";
12
+
13
+ /**
14
+ * Headers that carry a caller's ray ID and trace context into an actor. One
15
+ * rule for every kind of outbound call, so an action call and a queue send
16
+ * made from the same place land in the same trace under the same ray ID.
17
+ *
18
+ * The ray ID is the calling invocation's when the caller is itself inside an
19
+ * actor, else the one placed in OpenTelemetry baggage by the surrounding
20
+ * request handler. The trace context is `callSpan` when the runtime opened a
21
+ * span for this call, else the application span active in this JavaScript
22
+ * context, else the calling actor's own Core invocation span.
23
+ */
24
+ export function outboundTelemetryHeaders(
25
+ invocation: ActorInvocationTraceContext | undefined,
26
+ callSpan?: ActorInvocationTraceContext["span"],
27
+ ): Record<string, string> {
28
+ const headers: Record<string, string> = {};
29
+ const rayId = invocation?.rayId ?? readActiveRayId();
30
+ if (rayId) {
31
+ headers[HEADER_RIVET_RAY_ID] = rayId;
32
+ }
33
+ const traceHeaders =
34
+ actorInvocationTraceHeaders(callSpan) ??
35
+ readActiveTraceHeaders() ??
36
+ actorInvocationTraceHeaders(invocation?.span);
37
+ if (traceHeaders) {
38
+ headers[HEADER_TRACEPARENT] = traceHeaders.traceparent;
39
+ if (traceHeaders.tracestate) {
40
+ headers[HEADER_TRACESTATE] = traceHeaders.tracestate;
41
+ }
42
+ }
43
+ return headers;
44
+ }
45
+
46
+ /**
47
+ * Adds outbound telemetry headers to a request whose caller may have set
48
+ * some already. A header the caller set wins. `traceparent` and `tracestate`
49
+ * describe one span between them, so when the caller set either, both stay
50
+ * as the caller set them and neither is added.
51
+ */
52
+ export function addOutboundTelemetryHeaders(
53
+ headers: Headers,
54
+ telemetry: Record<string, string>,
55
+ ): void {
56
+ const callerSetTraceContext =
57
+ headers.has(HEADER_TRACEPARENT) || headers.has(HEADER_TRACESTATE);
58
+ for (const [name, value] of Object.entries(telemetry)) {
59
+ const isTraceContext =
60
+ name === HEADER_TRACEPARENT || name === HEADER_TRACESTATE;
61
+ if (isTraceContext ? callerSetTraceContext : headers.has(name)) {
62
+ continue;
63
+ }
64
+ headers.set(name, value);
65
+ }
66
+ }
@@ -54,6 +54,8 @@ export interface QueueSendResult<TResponse = unknown> {
54
54
  interface QueueSenderOptions {
55
55
  encoding: Encoding;
56
56
  params: unknown;
57
+ /** Ray ID and trace context headers, read per send. */
58
+ telemetryHeaders: () => Record<string, string>;
57
59
  customFetch: (request: Request) => Promise<Response>;
58
60
  }
59
61
 
@@ -90,6 +92,7 @@ export function createQueueSender(
90
92
  method: "POST",
91
93
  headers: {
92
94
  [HEADER_ENCODING]: senderOptions.encoding,
95
+ ...senderOptions.telemetryHeaders(),
93
96
  ...(senderOptions.params !== undefined
94
97
  ? {
95
98
  [HEADER_CONN_PARAMS]: JSON.stringify(
@@ -12,6 +12,7 @@ import type {
12
12
  } from "@/engine-client/driver";
13
13
  import { ActorError } from "./errors";
14
14
  import { logger } from "./log";
15
+ import { addOutboundTelemetryHeaders } from "./outbound-telemetry";
15
16
 
16
17
  /** Buffer a one-shot ReadableStream body to bytes so every retry attempt can re-send it. */
17
18
  export async function prepareRetryableInit(
@@ -37,6 +38,7 @@ export async function rawHttpFetch(
37
38
  input: string | URL | Request,
38
39
  init?: RequestInit,
39
40
  options: GatewayRequestOptions = {},
41
+ telemetryHeaders: Record<string, string> = {},
40
42
  ): Promise<Response> {
41
43
  // Extract path and merge init options
42
44
  let path: string;
@@ -111,6 +113,7 @@ export async function rawHttpFetch(
111
113
  if (params) {
112
114
  proxyRequestHeaders.set(HEADER_CONN_PARAMS, JSON.stringify(params));
113
115
  }
116
+ addOutboundTelemetryHeaders(proxyRequestHeaders, telemetryHeaders);
114
117
 
115
118
  // Forward the request to the actor
116
119
  const proxyRequest = new Request(url, {
@@ -20,6 +20,9 @@ export const HEADER_ACTOR_GENERATION = "x-rivet-actor-generation";
20
20
  export const HEADER_ACTOR_KEY = "x-rivet-actor-key";
21
21
 
22
22
  export const HEADER_RIVET_TOKEN = "x-rivet-token";
23
+ export const HEADER_RIVET_RAY_ID = "x-rivet-ray-id";
24
+ export const HEADER_TRACEPARENT = "traceparent";
25
+ export const HEADER_TRACESTATE = "tracestate";
23
26
 
24
27
  // MARK: Manager Gateway Headers
25
28
  export const HEADER_RIVET_TARGET = "x-rivet-target";
@@ -0,0 +1,19 @@
1
+ /** Correlation owned by the currently executing Core actor invocation. */
2
+ export interface ActorInvocationTraceContext {
3
+ /** Ray ID the invocation received, absent when nothing upstream supplied one. */
4
+ readonly rayId?: string;
5
+ /** Core-owned invocation span context, absent when tracing is disabled. */
6
+ readonly span?: ActorInvocationSpanContext;
7
+ }
8
+
9
+ /** W3C span context of the Core invocation span. */
10
+ export interface ActorInvocationSpanContext {
11
+ /** W3C trace identifier. */
12
+ readonly traceId: string;
13
+ /** W3C span identifier for the Core invocation. */
14
+ readonly spanId: string;
15
+ /** OpenTelemetry trace flags encoded as an integer. */
16
+ readonly traceFlags: number;
17
+ /** Optional vendor trace state inherited by the Core invocation. */
18
+ readonly tracestate?: string;
19
+ }
@@ -0,0 +1,99 @@
1
+ import {
2
+ type Context,
3
+ context,
4
+ createTraceState,
5
+ isSpanContextValid,
6
+ propagation,
7
+ type SpanContext,
8
+ trace,
9
+ } from "@opentelemetry/api";
10
+ import type { ActorInvocationSpanContext } from "./actor-telemetry-context";
11
+
12
+ /** W3C headers derived from the active JavaScript OTel context. */
13
+ export interface ActiveTraceHeaders {
14
+ /** W3C Trace Context identifying the active trace and span. */
15
+ readonly traceparent: string;
16
+ /** Optional vendor trace state associated with the active span. */
17
+ readonly tracestate?: string;
18
+ }
19
+
20
+ /** Returns the active W3C trace context, when an OTel provider has installed one. */
21
+ export function readActiveTraceHeaders(): ActiveTraceHeaders | undefined {
22
+ return traceHeaders(trace.getSpanContext(context.active()));
23
+ }
24
+
25
+ /** W3C Baggage key that carries a ray ID through application code. */
26
+ export const RAY_BAGGAGE_KEY = "rivet.ray.id";
27
+
28
+ /**
29
+ * Returns the ray ID carried in the active OpenTelemetry baggage, so a request
30
+ * handler that received a ray ID can pass it to the actors it calls. The bound
31
+ * is the one Core applies on receipt, so an invalid value is never sent.
32
+ */
33
+ export function readActiveRayId(): string | undefined {
34
+ const value = propagation
35
+ .getBaggage(context.active())
36
+ ?.getEntry(RAY_BAGGAGE_KEY)?.value;
37
+ if (value && value.length <= 30 && /^[A-Za-z0-9_-]+$/.test(value)) {
38
+ return value;
39
+ }
40
+ return undefined;
41
+ }
42
+
43
+ /** W3C headers for the Core invocation span, or nothing when it is absent or invalid. */
44
+ export function actorInvocationTraceHeaders(
45
+ invocation: ActorInvocationSpanContext | undefined,
46
+ ): ActiveTraceHeaders | undefined {
47
+ const spanContext = invocationSpanContext(invocation);
48
+ if (!spanContext) return undefined;
49
+ return traceHeaders(spanContext);
50
+ }
51
+
52
+ function traceHeaders(
53
+ spanContext: SpanContext | undefined,
54
+ ): ActiveTraceHeaders | undefined {
55
+ if (!spanContext || !isSpanContextValid(spanContext)) return undefined;
56
+ const tracestate = spanContext.traceState?.serialize();
57
+ return {
58
+ traceparent: `00-${spanContext.traceId}-${spanContext.spanId}-${spanContext.traceFlags.toString(16).padStart(2, "0")}`,
59
+ ...(tracestate ? { tracestate } : {}),
60
+ };
61
+ }
62
+
63
+ /**
64
+ * Runs `run` with the Core invocation span as the active OpenTelemetry span,
65
+ * so application spans started inside an actor callback nest under it. With
66
+ * no span, or an invalid one, `run` executes unchanged.
67
+ */
68
+ export function runWithActorInvocationSpan<T>(
69
+ invocation: ActorInvocationSpanContext | undefined,
70
+ run: () => T,
71
+ ): T {
72
+ if (!invocation) return run();
73
+
74
+ const spanContext = invocationSpanContext(invocation);
75
+ if (!spanContext) return run();
76
+ const parent: Context = trace.setSpanContext(context.active(), spanContext);
77
+
78
+ return context.with(parent, run);
79
+ }
80
+
81
+ function invocationSpanContext(
82
+ invocation: ActorInvocationSpanContext | undefined,
83
+ ): SpanContext | undefined {
84
+ if (!invocation) return undefined;
85
+ try {
86
+ const spanContext: SpanContext = {
87
+ traceId: invocation.traceId,
88
+ spanId: invocation.spanId,
89
+ traceFlags: invocation.traceFlags,
90
+ traceState: invocation.tracestate
91
+ ? createTraceState(invocation.tracestate)
92
+ : undefined,
93
+ isRemote: false,
94
+ };
95
+ return isSpanContextValid(spanContext) ? spanContext : undefined;
96
+ } catch {
97
+ return undefined;
98
+ }
99
+ }
@@ -48,24 +48,6 @@ export interface DeconstructedError {
48
48
  actor?: errors.ActorSpecifier;
49
49
  }
50
50
 
51
- function isCanonicalStructuredRivetError(
52
- error: unknown,
53
- ): error is errors.RivetErrorLike {
54
- return (
55
- error instanceof errors.RivetError ||
56
- (typeof error === "object" &&
57
- error !== null &&
58
- "__type" in error &&
59
- error.__type === "RivetError" &&
60
- "group" in error &&
61
- typeof error.group === "string" &&
62
- "code" in error &&
63
- typeof error.code === "string" &&
64
- "message" in error &&
65
- typeof error.message === "string")
66
- );
67
- }
68
-
69
51
  /**
70
52
  * Deconstructs errors into response fields. Bridge callback errors that cross
71
53
  * into rivetkit-core are sanitized there; this only classifies JS-local errors.
@@ -85,7 +67,7 @@ export function deconstructError(
85
67
  let actor: errors.ActorSpecifier | undefined;
86
68
  // Structured errors from core or from pre-built `RivetError` instances are canonical.
87
69
  // Only unstructured errors go through the classifier below.
88
- if (isCanonicalStructuredRivetError(error)) {
70
+ if (errors.isCanonicalStructuredRivetError(error)) {
89
71
  statusCode = (
90
72
  typeof error.statusCode === "number"
91
73
  ? error.statusCode
@@ -1,9 +1,12 @@
1
1
  import type { ClientConfig } from "@/client/config";
2
2
  import {
3
3
  HEADER_RIVET_ACTOR,
4
+ HEADER_RIVET_RAY_ID,
4
5
  HEADER_RIVET_SKIP_READY_WAIT,
5
6
  HEADER_RIVET_TARGET,
6
7
  HEADER_RIVET_TOKEN,
8
+ HEADER_TRACEPARENT,
9
+ HEADER_TRACESTATE,
7
10
  } from "@/common/actor-router-consts";
8
11
  import { type GatewayRequestOptions, shouldSkipReadyWait } from "./driver";
9
12
 
@@ -55,6 +58,18 @@ function buildGuardHeaders(
55
58
  for (const [key, value] of Object.entries(runConfig.headers)) {
56
59
  headers.set(key, value as string);
57
60
  }
61
+ // Configured trace context is never sent: it would pin every call to one span.
62
+ for (const name of [HEADER_TRACEPARENT, HEADER_TRACESTATE]) {
63
+ headers.delete(name);
64
+ const value = actorRequest.headers.get(name);
65
+ if (value !== null) {
66
+ headers.set(name, value);
67
+ }
68
+ }
69
+ const requestRayId = actorRequest.headers.get(HEADER_RIVET_RAY_ID);
70
+ if (requestRayId !== null) {
71
+ headers.set(HEADER_RIVET_RAY_ID, requestRayId);
72
+ }
58
73
  // Add guard-specific headers
59
74
  if (runConfig.token) {
60
75
  headers.set(HEADER_RIVET_TOKEN, runConfig.token);
@@ -732,6 +732,7 @@ export class Registry<A extends RegistryActors> {
732
732
  async #drain(config: RegistryConfig): Promise<void> {
733
733
  const modeAPromise = this.#runtimeServeConfiguredPromise;
734
734
  const modeBPromise = this.#runtimeServerlessPromise;
735
+ const runtimes = new Set<CoreRuntime>();
735
736
 
736
737
  const gracePeriodMs =
737
738
  config.shutdown?.gracePeriodMs ??
@@ -751,6 +752,7 @@ export class Registry<A extends RegistryActors> {
751
752
  (async () => {
752
753
  try {
753
754
  const { runtime, registry } = await modeAPromise;
755
+ runtimes.add(runtime);
754
756
  await runtime.shutdownRegistry(registry);
755
757
  } catch (err) {
756
758
  logger().warn(
@@ -766,6 +768,7 @@ export class Registry<A extends RegistryActors> {
766
768
  (async () => {
767
769
  try {
768
770
  const { runtime, registry } = await modeBPromise;
771
+ runtimes.add(runtime);
769
772
  await runtime.shutdownRegistry(registry);
770
773
  } catch (err) {
771
774
  logger().warn(
@@ -795,6 +798,9 @@ export class Registry<A extends RegistryActors> {
795
798
  setTimeout(resolve, gracePeriodMs).unref?.(),
796
799
  ),
797
800
  ]);
801
+ await Promise.all(
802
+ [...runtimes].map((runtime) => runtime.shutdownTelemetry?.()),
803
+ );
798
804
  }
799
805
 
800
806
  async #actorStopThresholdMs(