rivetkit 2.3.0-rc.12 → 2.3.0-rc.14

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 (112) hide show
  1. package/dist/browser/client.d.ts +50 -0
  2. package/dist/browser/client.js +98 -84
  3. package/dist/browser/client.js.map +1 -1
  4. package/dist/browser/inspector/client.js +12 -2
  5. package/dist/browser/inspector/client.js.map +1 -1
  6. package/dist/tsup/actor/errors.d.cts +1 -1
  7. package/dist/tsup/actor/errors.d.ts +1 -1
  8. package/dist/tsup/agent-os/index.cjs +66 -3
  9. package/dist/tsup/agent-os/index.cjs.map +1 -1
  10. package/dist/tsup/agent-os/index.d.cts +50 -0
  11. package/dist/tsup/agent-os/index.d.ts +50 -0
  12. package/dist/tsup/agent-os/index.js +66 -3
  13. package/dist/tsup/agent-os/index.js.map +1 -1
  14. package/dist/tsup/{chunk-OLIJHKLL.js → chunk-2JVK6AFR.js} +6 -6
  15. package/dist/tsup/chunk-2JVK6AFR.js.map +1 -0
  16. package/dist/tsup/{chunk-7UZF56RS.js → chunk-4DGTEHMI.js} +7 -5
  17. package/dist/tsup/{chunk-7UZF56RS.js.map → chunk-4DGTEHMI.js.map} +1 -1
  18. package/dist/tsup/{chunk-OOB32JVG.js → chunk-5LHIXIMF.js} +2 -2
  19. package/dist/tsup/{chunk-EWVOWEMD.js → chunk-7URXZWTR.js} +4 -4
  20. package/dist/tsup/{chunk-QKSGGKGQ.js → chunk-CJYYFRLA.js} +2 -2
  21. package/dist/tsup/{chunk-WIMUFZVJ.js → chunk-DPIMKYNB.js} +61 -2
  22. package/dist/tsup/chunk-DPIMKYNB.js.map +1 -0
  23. package/dist/tsup/{chunk-VNMIAPPF.cjs → chunk-EHZQPH75.cjs} +21 -4
  24. package/dist/tsup/chunk-EHZQPH75.cjs.map +1 -0
  25. package/dist/tsup/{chunk-C7AAIILH.cjs → chunk-ICXZ74R5.cjs} +4 -4
  26. package/dist/tsup/{chunk-C7AAIILH.cjs.map → chunk-ICXZ74R5.cjs.map} +1 -1
  27. package/dist/tsup/{chunk-2U6RLFKX.cjs → chunk-LSLJJHHE.cjs} +142 -140
  28. package/dist/tsup/chunk-LSLJJHHE.cjs.map +1 -0
  29. package/dist/tsup/{chunk-WHYBAEWG.cjs → chunk-NIY3RSPX.cjs} +62 -3
  30. package/dist/tsup/chunk-NIY3RSPX.cjs.map +1 -0
  31. package/dist/tsup/{chunk-VLXRFJ7P.js → chunk-Q6R3ND6W.js} +2 -2
  32. package/dist/tsup/{chunk-2ZTBRZRS.cjs → chunk-QOIIPYGJ.cjs} +10 -10
  33. package/dist/tsup/{chunk-2ZTBRZRS.cjs.map → chunk-QOIIPYGJ.cjs.map} +1 -1
  34. package/dist/tsup/{chunk-UETC5RF7.cjs → chunk-T2LS2K7C.cjs} +3 -3
  35. package/dist/tsup/{chunk-UETC5RF7.cjs.map → chunk-T2LS2K7C.cjs.map} +1 -1
  36. package/dist/tsup/{chunk-3EVVOYFD.js → chunk-VD2BPZGJ.js} +20 -3
  37. package/dist/tsup/chunk-VD2BPZGJ.js.map +1 -0
  38. package/dist/tsup/{chunk-6KTMKPNU.cjs → chunk-W6WJZP5H.cjs} +10 -10
  39. package/dist/tsup/chunk-W6WJZP5H.cjs.map +1 -0
  40. package/dist/tsup/{chunk-SS56HFM2.cjs → chunk-ZDFGAXNV.cjs} +5 -5
  41. package/dist/tsup/{chunk-SS56HFM2.cjs.map → chunk-ZDFGAXNV.cjs.map} +1 -1
  42. package/dist/tsup/client/mod.cjs +6 -6
  43. package/dist/tsup/client/mod.d.cts +3 -3
  44. package/dist/tsup/client/mod.d.ts +3 -3
  45. package/dist/tsup/client/mod.js +5 -5
  46. package/dist/tsup/common/log.cjs +2 -2
  47. package/dist/tsup/common/log.js +1 -1
  48. package/dist/tsup/common/websocket.cjs +3 -3
  49. package/dist/tsup/common/websocket.js +2 -2
  50. package/dist/tsup/{config-DKgPGC0f.d.ts → config-BxWAw3iH.d.ts} +121 -2
  51. package/dist/tsup/{config-BtAh7oBu.d.cts → config-CZQQ-mso.d.cts} +121 -2
  52. package/dist/tsup/{context-Cfjl5pgz.d.cts → context-Bw7xq8w3.d.cts} +1 -1
  53. package/dist/tsup/{context-C-6dGebY.d.ts → context-D8QA76sV.d.ts} +1 -1
  54. package/dist/tsup/dynamic/mod.cjs +2 -2
  55. package/dist/tsup/dynamic/mod.d.cts +2 -2
  56. package/dist/tsup/dynamic/mod.d.ts +2 -2
  57. package/dist/tsup/dynamic/mod.js +1 -1
  58. package/dist/tsup/inspector/mod.cjs +5 -5
  59. package/dist/tsup/inspector/mod.js +4 -4
  60. package/dist/tsup/inspector-tab/mod.cjs +173 -0
  61. package/dist/tsup/inspector-tab/mod.cjs.map +1 -0
  62. package/dist/tsup/inspector-tab/mod.d.cts +250 -0
  63. package/dist/tsup/inspector-tab/mod.d.ts +250 -0
  64. package/dist/tsup/inspector-tab/mod.js +173 -0
  65. package/dist/tsup/inspector-tab/mod.js.map +1 -0
  66. package/dist/tsup/mod.cjs +189 -81
  67. package/dist/tsup/mod.cjs.map +1 -1
  68. package/dist/tsup/mod.d.cts +4 -4
  69. package/dist/tsup/mod.d.ts +4 -4
  70. package/dist/tsup/mod.js +124 -16
  71. package/dist/tsup/mod.js.map +1 -1
  72. package/dist/tsup/test/mod.cjs +9 -9
  73. package/dist/tsup/test/mod.d.cts +2 -2
  74. package/dist/tsup/test/mod.d.ts +2 -2
  75. package/dist/tsup/test/mod.js +5 -5
  76. package/dist/tsup/{utils-DVekpm4I.d.cts → utils-DQosb24I.d.cts} +1 -1
  77. package/dist/tsup/{utils-DVekpm4I.d.ts → utils-DQosb24I.d.ts} +1 -1
  78. package/dist/tsup/utils.cjs +2 -2
  79. package/dist/tsup/utils.d.cts +1 -1
  80. package/dist/tsup/utils.d.ts +1 -1
  81. package/dist/tsup/utils.js +1 -1
  82. package/dist/tsup/workflow/mod.cjs +11 -11
  83. package/dist/tsup/workflow/mod.cjs.map +1 -1
  84. package/dist/tsup/workflow/mod.d.cts +4 -4
  85. package/dist/tsup/workflow/mod.d.ts +4 -4
  86. package/dist/tsup/workflow/mod.js +5 -5
  87. package/package.json +19 -9
  88. package/src/actor/config.ts +91 -0
  89. package/src/actor/instance/mod.ts +4 -4
  90. package/src/actor/mod.ts +2 -0
  91. package/src/common/engine.ts +28 -1
  92. package/src/devtools-loader/index.ts +4 -7
  93. package/src/devtools-loader/serve-devtools.ts +26 -0
  94. package/src/engine-client/actor-http-client.ts +2 -2
  95. package/src/engine-client/ws-proxy.ts +5 -0
  96. package/src/inspector-tab/mod.ts +315 -0
  97. package/src/registry/config/index.ts +37 -7
  98. package/src/registry/index.ts +4 -2
  99. package/src/registry/native.ts +125 -8
  100. package/src/registry/runtime.ts +27 -1
  101. package/src/utils/env-vars.ts +6 -0
  102. package/dist/tsup/chunk-2U6RLFKX.cjs.map +0 -1
  103. package/dist/tsup/chunk-3EVVOYFD.js.map +0 -1
  104. package/dist/tsup/chunk-6KTMKPNU.cjs.map +0 -1
  105. package/dist/tsup/chunk-OLIJHKLL.js.map +0 -1
  106. package/dist/tsup/chunk-VNMIAPPF.cjs.map +0 -1
  107. package/dist/tsup/chunk-WHYBAEWG.cjs.map +0 -1
  108. package/dist/tsup/chunk-WIMUFZVJ.js.map +0 -1
  109. /package/dist/tsup/{chunk-OOB32JVG.js.map → chunk-5LHIXIMF.js.map} +0 -0
  110. /package/dist/tsup/{chunk-EWVOWEMD.js.map → chunk-7URXZWTR.js.map} +0 -0
  111. /package/dist/tsup/{chunk-QKSGGKGQ.js.map → chunk-CJYYFRLA.js.map} +0 -0
  112. /package/dist/tsup/{chunk-VLXRFJ7P.js.map → chunk-Q6R3ND6W.js.map} +0 -0
@@ -10,7 +10,7 @@ import {
10
10
  queueMetadataKey,
11
11
  workflowStoragePrefix,
12
12
  } from "@/actor/keys";
13
- import { ENGINE_ENDPOINT } from "@/common/engine";
13
+ import { buildEngineEndpoint, ENGINE_HOST, ENGINE_PORT } from "@/common/engine";
14
14
  import { type Logger, LogLevelSchema } from "@/common/log";
15
15
  import { VERSION } from "@/utils";
16
16
  import { tryParseEndpoint } from "@/utils/endpoint-parser";
@@ -20,6 +20,8 @@ import {
20
20
  getRivetkitRuntime,
21
21
  getRivetNamespace,
22
22
  getRivetRunEngine,
23
+ getRivetRunEngineHost,
24
+ getRivetRunEnginePort,
23
25
  getRivetRunEngineVersion,
24
26
  getRivetToken,
25
27
  isDev,
@@ -224,6 +226,27 @@ export const RegistryConfigSchema = z
224
226
  * Starts the full Rust engine process locally.
225
227
  */
226
228
  startEngine: z.boolean().default(() => getRivetRunEngine()),
229
+ /**
230
+ * @experimental
231
+ *
232
+ * Host to bind the spawned local engine process to.
233
+ */
234
+ engineHost: z
235
+ .string()
236
+ .optional()
237
+ .default(() => getRivetRunEngineHost() ?? ENGINE_HOST),
238
+ /**
239
+ * @experimental
240
+ *
241
+ * Port to bind the spawned local engine process to.
242
+ */
243
+ enginePort: z
244
+ .number()
245
+ .int()
246
+ .min(1)
247
+ .max(65_535)
248
+ .optional()
249
+ .default(() => getRivetRunEnginePort() ?? ENGINE_PORT),
227
250
  /** @experimental */
228
251
  engineVersion: z
229
252
  .string()
@@ -312,7 +335,9 @@ export const RegistryConfigSchema = z
312
335
  })
313
336
  : undefined;
314
337
 
315
- // Can't start a local engine and connect to a remote endpoint.
338
+ // RIVET_ENDPOINT configures what RivetKit connects to. Use
339
+ // engineHost/enginePort (or RIVET_RUN_ENGINE_HOST/PORT) to control the
340
+ // spawned local engine bind address.
316
341
  if (config.startEngine && parsedEndpoint) {
317
342
  ctx.addIssue({
318
343
  code: "custom",
@@ -329,12 +354,17 @@ export const RegistryConfigSchema = z
329
354
  });
330
355
  }
331
356
 
332
- // Flatten the endpoint and apply defaults for namespace/token
333
- // If startEngine is enabled, set endpoint to the engine endpoint.
357
+ // Flatten the endpoint and apply defaults for namespace/token.
358
+ const localEngineEndpoint = buildEngineEndpoint(
359
+ config.engineHost,
360
+ config.enginePort,
361
+ );
334
362
  const endpoint = config.startEngine
335
- ? ENGINE_ENDPOINT
363
+ ? localEngineEndpoint
336
364
  : (parsedEndpoint?.endpoint ??
337
- (isDevEnv ? ENGINE_ENDPOINT : undefined));
365
+ (isDevEnv
366
+ ? buildEngineEndpoint(ENGINE_HOST, ENGINE_PORT)
367
+ : undefined));
338
368
  const validateServerlessEndpoint = Boolean(
339
369
  config.startEngine || parsedEndpoint,
340
370
  );
@@ -367,7 +397,7 @@ export const RegistryConfigSchema = z
367
397
  // In dev mode, clients connect directly to the local Rivet Engine.
368
398
  const publicEndpoint =
369
399
  parsedPublicEndpoint?.endpoint ??
370
- (isDevEnv && config.startEngine ? ENGINE_ENDPOINT : undefined);
400
+ (isDevEnv && config.startEngine ? endpoint : undefined);
371
401
  // We extract publicNamespace to validate that it matches the backend
372
402
  // namespace (see validation above), not for functional use.
373
403
  const publicNamespace = parsedPublicEndpoint?.namespace;
@@ -1,5 +1,5 @@
1
1
  import { Hono } from "hono";
2
- import { ENGINE_ENDPOINT } from "@/common/engine";
2
+ import { isLocalEngineEndpoint } from "@/common/engine";
3
3
  import { configureServerlessPool } from "@/serverless/configure";
4
4
  import { detectRuntime, VERSION } from "@/utils";
5
5
  import { crossPlatformServe, loadRuntimeServeStatic } from "@/utils/serve";
@@ -700,7 +700,9 @@ export class Registry<A extends RegistryActors> {
700
700
 
701
701
  if (config.endpoint) {
702
702
  const endpointType =
703
- config.endpoint === ENGINE_ENDPOINT ? "local native" : "remote";
703
+ config.startEngine || isLocalEngineEndpoint(config.endpoint)
704
+ ? "local native"
705
+ : "remote";
704
706
  logLine("Endpoint", `${config.endpoint} (${endpointType})`);
705
707
  }
706
708
 
@@ -31,6 +31,7 @@ import {
31
31
  } from "@/client/client";
32
32
  import { convertRegistryConfigToClientConfig } from "@/client/config";
33
33
  import { HEADER_CONN_PARAMS } from "@/common/actor-router-consts";
34
+ import { isLocalEngineEndpoint } from "@/common/engine";
34
35
  import type { AnyDatabaseProvider } from "@/common/database/config";
35
36
  import { wrapJsNativeDatabase } from "@/common/database/native-database";
36
37
  import { assertJsonCompatValue, type JsonCompatValue } from "@/common/encoding";
@@ -71,12 +72,15 @@ import type {
71
72
  RuntimeActorConfig,
72
73
  RuntimeBytes,
73
74
  RuntimeHttpResponse,
75
+ RuntimeInspectorTabEntry,
74
76
  RuntimeQueueMessage,
75
77
  RuntimeServeConfig,
76
78
  RuntimeStateDeltaPayload,
77
79
  WebSocketHandle,
78
80
  } from "./runtime";
79
81
  import { loadWasmRuntime } from "./wasm-runtime";
82
+ import nodeFs from "node:fs";
83
+ import nodePath from "node:path";
80
84
  import { createWriteThroughProxy } from "./write-through-proxy";
81
85
 
82
86
  const textEncoder = new TextEncoder();
@@ -627,6 +631,14 @@ function encodeValue(value: unknown): RuntimeBytes {
627
631
  return encodeCborCompat(value as JsonCompatValue);
628
632
  }
629
633
 
634
+ function normalizeArgs(value: unknown): unknown[] {
635
+ return Array.isArray(value)
636
+ ? value
637
+ : value === undefined || value === null
638
+ ? []
639
+ : [value];
640
+ }
641
+
630
642
  function unwrapTsfnPayload<T>(error: unknown, payload: T): T {
631
643
  if (error !== null && error !== undefined) {
632
644
  throw error;
@@ -1093,11 +1105,7 @@ function wrapNativeCallback<Args extends Array<unknown>, Result>(
1093
1105
 
1094
1106
  function decodeArgs(value?: RuntimeBytes | null): unknown[] {
1095
1107
  const decoded = decodeValue<unknown>(value);
1096
- return Array.isArray(decoded)
1097
- ? decoded
1098
- : decoded === undefined
1099
- ? []
1100
- : [decoded];
1108
+ return normalizeArgs(decoded);
1101
1109
  }
1102
1110
 
1103
1111
  function buildRequest(init: {
@@ -3323,9 +3331,87 @@ function buildActorConfig(
3323
3331
  actions: Object.keys((config.actions ?? {}) as Record<string, unknown>)
3324
3332
  .sort()
3325
3333
  .map((name) => ({ name })),
3334
+ inspectorTabs: buildInspectorTabs(config.inspector),
3326
3335
  };
3327
3336
  }
3328
3337
 
3338
+ function buildInspectorTabs(
3339
+ inspector: unknown,
3340
+ ): Array<RuntimeInspectorTabEntry> | undefined {
3341
+ if (!inspector || typeof inspector !== "object") return undefined;
3342
+ const tabs = (inspector as { tabs?: unknown }).tabs;
3343
+ if (!Array.isArray(tabs) || tabs.length === 0) return undefined;
3344
+ return tabs.map((raw) => {
3345
+ const entry = raw as {
3346
+ id: string;
3347
+ label?: string;
3348
+ source?: string;
3349
+ icon?: string;
3350
+ hidden?: boolean;
3351
+ };
3352
+ if (entry.hidden === true) {
3353
+ return { id: entry.id, hidden: true };
3354
+ }
3355
+ // Resolve the author's source path against the current working
3356
+ // directory so the Rust runtime gets an absolute path. The author
3357
+ // runs the actor process from their project root by convention.
3358
+ const resolved =
3359
+ entry.source !== undefined
3360
+ ? nodePath.resolve(entry.source)
3361
+ : undefined;
3362
+ if (resolved !== undefined) {
3363
+ validateInspectorTabSource(entry.id, resolved);
3364
+ }
3365
+ return {
3366
+ id: entry.id,
3367
+ label: entry.label,
3368
+ icon: entry.icon,
3369
+ source: resolved,
3370
+ };
3371
+ });
3372
+ }
3373
+
3374
+ function validateInspectorTabSource(tabId: string, resolved: string): void {
3375
+ // Catch obviously dangerous misconfigurations at registry construction
3376
+ // rather than silently exposing the wrong subtree over the unauthenticated
3377
+ // `/inspector/custom-tabs/<id>/*` route. Fail loudly so misconfigured
3378
+ // actors never start.
3379
+ if (resolved === nodePath.parse(resolved).root) {
3380
+ throw new Error(
3381
+ `inspector.tabs[id="${tabId}"].source resolves to the filesystem root (${resolved}). ` +
3382
+ "Point it at the tab's own static-asset directory instead.",
3383
+ );
3384
+ }
3385
+ let stat: import("node:fs").Stats;
3386
+ try {
3387
+ stat = nodeFs.statSync(resolved);
3388
+ } catch (err) {
3389
+ const code = (err as NodeJS.ErrnoException)?.code;
3390
+ if (code === "ENOENT") {
3391
+ throw new Error(
3392
+ `inspector.tabs[id="${tabId}"].source (${resolved}) does not exist.`,
3393
+ );
3394
+ }
3395
+ if (code === "EACCES") {
3396
+ throw new Error(
3397
+ `inspector.tabs[id="${tabId}"].source (${resolved}) is not readable (EACCES).`,
3398
+ );
3399
+ }
3400
+ throw new Error(
3401
+ `inspector.tabs[id="${tabId}"].source (${resolved}) could not be stat'd: ${
3402
+ (err as Error)?.message ?? err
3403
+ }`,
3404
+ );
3405
+ }
3406
+ if (!stat.isDirectory()) {
3407
+ throw new Error(
3408
+ `inspector.tabs[id="${tabId}"].source (${resolved}) must be a directory, got ${
3409
+ stat.isFile() ? "file" : "non-directory"
3410
+ }.`,
3411
+ );
3412
+ }
3413
+ }
3414
+
3329
3415
  export function buildNativeFactory(
3330
3416
  runtime: CoreRuntime,
3331
3417
  registryConfig: RegistryConfig,
@@ -3756,14 +3842,38 @@ export function buildNativeFactory(
3756
3842
  404,
3757
3843
  );
3758
3844
  }
3759
- const body = (await jsRequest.json()) as { args?: unknown[] };
3845
+ const body = (await jsRequest.json()) as {
3846
+ args?: unknown;
3847
+ properties?: unknown;
3848
+ };
3849
+ if (body.args !== undefined && body.properties !== undefined) {
3850
+ return jsonResponse(
3851
+ { error: "use either args or properties, not both" },
3852
+ { status: 400 },
3853
+ );
3854
+ }
3855
+ if (
3856
+ body.properties !== undefined &&
3857
+ (body.properties === null ||
3858
+ typeof body.properties !== "object" ||
3859
+ Array.isArray(body.properties))
3860
+ ) {
3861
+ return jsonResponse(
3862
+ { error: "properties must be an object" },
3863
+ { status: 400 },
3864
+ );
3865
+ }
3866
+ const args =
3867
+ body.properties !== undefined
3868
+ ? [body.properties]
3869
+ : normalizeArgs(body.args);
3760
3870
  try {
3761
3871
  const output = await action(
3762
3872
  actorCtx,
3763
3873
  ...validateActionArgs(
3764
3874
  schemaConfig.actionInputSchemas,
3765
3875
  actionName,
3766
- body.args ?? [],
3876
+ args,
3767
3877
  ),
3768
3878
  );
3769
3879
  return jsonResponse({ output });
@@ -4647,9 +4757,16 @@ export async function buildServeConfig(
4647
4757
  serverlessMaxStartPayloadBytes: config.serverless.maxStartPayloadBytes,
4648
4758
  };
4649
4759
 
4650
- if (config.startEngine) {
4760
+ // Provide the engine binary path whenever the core will manage a local
4761
+ // engine. The core auto-spawns the engine for any loopback endpoint (its
4762
+ // EngineSpawnMode::Auto), not only when `startEngine` is set, so gating the
4763
+ // binary path on `startEngine` alone leaves auto-spawn unable to locate the
4764
+ // npm-installed engine binary and fail with engine.binary_unavailable.
4765
+ if (config.startEngine || isLocalEngineEndpoint(config.endpoint)) {
4651
4766
  const { getEnginePath } = await loadEngineCli();
4652
4767
  serveConfig.engineBinaryPath = getEnginePath();
4768
+ serveConfig.engineHost = config.engineHost;
4769
+ serveConfig.enginePort = config.enginePort;
4653
4770
  }
4654
4771
  if (config.test?.enabled) {
4655
4772
  serveConfig.inspectorTestToken =
@@ -1,4 +1,5 @@
1
1
  import type { SqliteNativeMetrics } from "@/common/database/config";
2
+ import { isLocalEngineEndpoint } from "@/common/engine";
2
3
  import type { RegistryConfig } from "./config";
3
4
 
4
5
  declare const handleBrand: unique symbol;
@@ -225,6 +226,22 @@ export interface RuntimeActorConfig {
225
226
  preloadMaxWorkflowBytes?: number;
226
227
  preloadMaxConnectionsBytes?: number;
227
228
  actions?: Array<{ name: string }>;
229
+ inspectorTabs?: Array<RuntimeInspectorTabEntry>;
230
+ }
231
+
232
+ export interface RuntimeInspectorTabEntry {
233
+ id: string;
234
+ /** Required for custom entries; omitted for built-in hides. */
235
+ label?: string;
236
+ /**
237
+ * Required for custom entries — absolute path to the source directory.
238
+ * Resolved on the TS side before being handed to the runtime.
239
+ */
240
+ source?: string;
241
+ /** Optional icon id for custom entries. */
242
+ icon?: string;
243
+ /** Set to true for built-in hide entries. */
244
+ hidden?: boolean;
228
245
  }
229
246
 
230
247
  export interface RuntimeServeConfig {
@@ -234,6 +251,8 @@ export interface RuntimeServeConfig {
234
251
  namespace: string;
235
252
  poolName: string;
236
253
  engineBinaryPath?: string;
254
+ engineHost?: string;
255
+ enginePort?: number;
237
256
  handleInspectorHttpInRuntime?: boolean;
238
257
  inspectorTestToken?: string;
239
258
  serverlessBasePath?: string;
@@ -581,8 +600,15 @@ export async function buildServeConfig(
581
600
  serverlessMaxStartPayloadBytes: config.serverless.maxStartPayloadBytes,
582
601
  };
583
602
 
584
- if (config.startEngine) {
603
+ // Provide the engine binary path whenever the core will manage a local
604
+ // engine. The core auto-spawns the engine for any loopback endpoint (its
605
+ // EngineSpawnMode::Auto), not only when `startEngine` is set, so gating the
606
+ // binary path on `startEngine` alone leaves auto-spawn unable to locate the
607
+ // npm-installed engine binary and fail with engine.binary_unavailable.
608
+ if (config.startEngine || isLocalEngineEndpoint(config.endpoint)) {
585
609
  serveConfig.engineBinaryPath = await loadEnginePath();
610
+ serveConfig.engineHost = config.engineHost;
611
+ serveConfig.enginePort = config.enginePort;
586
612
  }
587
613
  if (config.test?.enabled) {
588
614
  serveConfig.inspectorTestToken =
@@ -22,6 +22,12 @@ export const getRivetTotalSlots = (): number | undefined => {
22
22
  };
23
23
  export const getRivetRunEngine = (): boolean =>
24
24
  getEnvUniversal("RIVET_RUN_ENGINE") === "1";
25
+ export const getRivetRunEngineHost = (): string | undefined =>
26
+ getEnvUniversal("RIVET_RUN_ENGINE_HOST");
27
+ export const getRivetRunEnginePort = (): number | undefined => {
28
+ const value = getEnvUniversal("RIVET_RUN_ENGINE_PORT");
29
+ return value !== undefined ? parseInt(value, 10) : undefined;
30
+ };
25
31
  export const getRivetRunEngineVersion = (): string | undefined =>
26
32
  getEnvUniversal("RIVET_RUN_ENGINE_VERSION");
27
33
  export const getRivetEnvoyKind = (): string | undefined =>