diff --git a/packages/next-js/src/mod.ts b/packages/next-js/src/mod.ts
index 1cc75ee43..a18f5d376 100644
--- a/packages/next-js/src/mod.ts
+++ b/packages/next-js/src/mod.ts
@@ -8,7 +8,7 @@ export const toNextHandler = (
inputConfig.disableServer = true;
inputConfig.disableActorDriver = true;
- const { fetch } = registry.startServerless(inputConfig);
+ const { fetch } = registry.start(inputConfig);
const fetchWrapper = async (
request: Request,
diff --git a/packages/rivetkit/src/client/config.ts b/packages/rivetkit/src/client/config.ts
index 73a5fa915..8369152c8 100644
--- a/packages/rivetkit/src/client/config.ts
+++ b/packages/rivetkit/src/client/config.ts
@@ -19,8 +19,6 @@ export const ClientConfigSchema = z.object({
.optional()
.transform((x) => x ?? getEnvUniversal("RIVET_TOKEN")),
- totalSlots: z.number().optional(),
-
headers: z.record(z.string()).optional().default({}),
/** Endpoint to connect to the Rivet engine. Can be configured via RIVET_ENGINE env var. */
diff --git a/packages/rivetkit/src/drivers/default.ts b/packages/rivetkit/src/drivers/default.ts
index 846d2471d..23b1691ff 100644
--- a/packages/rivetkit/src/drivers/default.ts
+++ b/packages/rivetkit/src/drivers/default.ts
@@ -10,7 +10,13 @@ import type { DriverConfig, RunConfig } from "@/registry/run-config";
export function chooseDefaultDriver(runConfig: RunConfig): DriverConfig {
if (runConfig.endpoint && runConfig.driver) {
throw new UserError(
- "Cannot specify both 'engine' and 'driver' in configuration",
+ "Cannot specify both 'endpoint' and 'driver' in configuration",
+ );
+ }
+
+ if (runConfig.runnerKind === "serverless" && !runConfig.endpoint) {
+ throw new UserError(
+ "Cannot use 'serverless' runnerKind without the 'endpoint' config set. This condition should not happen, since the endpoint should be configured by default by Rivet when using serverless runners.",
);
}
diff --git a/packages/rivetkit/src/drivers/engine/actor-driver.ts b/packages/rivetkit/src/drivers/engine/actor-driver.ts
index c6db28bce..3d95b453e 100644
--- a/packages/rivetkit/src/drivers/engine/actor-driver.ts
+++ b/packages/rivetkit/src/drivers/engine/actor-driver.ts
@@ -92,9 +92,9 @@ export class EngineActorDriver implements ActorDriver {
endpoint: config.endpoint,
token: runConfig.token ?? config.token,
pegboardEndpoint: config.pegboardEndpoint,
- namespace: config.namespace,
+ namespace: runConfig.namespace ?? config.namespace,
totalSlots: runConfig.totalSlots ?? config.totalSlots,
- runnerName: config.runnerName,
+ runnerName: runConfig.runnerName ?? config.runnerName,
runnerKey: config.runnerKey,
metadata: {
inspectorToken: this.#runConfig.inspector.token(),
diff --git a/packages/rivetkit/src/manager/router.ts b/packages/rivetkit/src/manager/router.ts
index 487a1d41e..8fbeb94af 100644
--- a/packages/rivetkit/src/manager/router.ts
+++ b/packages/rivetkit/src/manager/router.ts
@@ -50,6 +50,7 @@ import {
type Actor as ApiActor,
} from "@/manager-api/actors";
import { RivetIdSchema } from "@/manager-api/common";
+import type { ServerlessActorDriverBuilder } from "@/mod";
import type { RegistryConfig } from "@/registry/config";
import type { RunConfig } from "@/registry/run-config";
import type { ActorOutput, ManagerDriver } from "./driver";
@@ -79,7 +80,7 @@ export function createManagerRouter(
registryConfig: RegistryConfig,
runConfig: RunConfig,
managerDriver: ManagerDriver,
- serverlessActorDriverBuilder: (() => ActorDriver) | undefined,
+ serverlessActorDriverBuilder: ServerlessActorDriverBuilder | undefined,
): { router: Hono; openapi: OpenAPIHono } {
const router = new OpenAPIHono({ strict: false }).basePath(
runConfig.basePath,
@@ -121,10 +122,7 @@ export function createManagerRouter(
function addServerlessRoutes(
runConfig: RunConfig,
- serverlessActorDriverBuilder: (
- token: string | undefined,
- totalSlots: number | undefined,
- ) => ActorDriver,
+ serverlessActorDriverBuilder: ServerlessActorDriverBuilder,
router: OpenAPIHono,
) {
// Apply CORS
@@ -141,11 +139,18 @@ function addServerlessRoutes(
router.get("/start", async (c) => {
const token = c.req.header("x-rivet-token");
let totalSlots: number | undefined = parseInt(
- c.req.header("x-rivetkit-total-slots") as any,
+ c.req.header("x-rivet-total-slots") as any,
+ );
+ if (!Number.isFinite(totalSlots)) totalSlots = undefined;
+ const runnerName = c.req.header("x-rivet-runner-name");
+ const namespace = c.req.header("x-rivet-namespace-id");
+
+ const actorDriver = serverlessActorDriverBuilder(
+ token,
+ totalSlots,
+ runnerName,
+ namespace,
);
- if (isNaN(totalSlots)) totalSlots = undefined;
-
- const actorDriver = serverlessActorDriverBuilder(token, totalSlots);
invariant(
actorDriver.serverlessHandleStart,
"missing serverlessHandleStart on ActorDriver",
diff --git a/packages/rivetkit/src/registry/mod.ts b/packages/rivetkit/src/registry/mod.ts
index 4f95fda7b..508d0dcca 100644
--- a/packages/rivetkit/src/registry/mod.ts
+++ b/packages/rivetkit/src/registry/mod.ts
@@ -24,6 +24,13 @@ import {
} from "./run-config";
import { crossPlatformServe } from "./serve";
+export type ServerlessActorDriverBuilder = (
+ token?: string,
+ totalSlots?: number,
+ runnerName?: string,
+ namespace?: string,
+) => ActorDriver;
+
interface ServerOutput> {
/** Client to communicate with the actors. */
client: Client;
@@ -72,6 +79,9 @@ export class Registry {
config.disableActorDriver = true;
config.noWelcome = true;
}
+ if (config.runnerKind === "serverless") {
+ config.disableActorDriver = true;
+ }
// Configure getUpgradeWebSocket lazily so we can assign it in crossPlatformServe
let upgradeWebSocket: any;
@@ -117,6 +127,7 @@ export class Registry {
console.log();
}
+ let serverlessActorDriverBuilder: undefined | ServerlessActorDriverBuilder;
// HACK: We need to find a better way to let the driver itself decide when to start the actor driver
// Create runner
//
@@ -128,117 +139,22 @@ export class Registry {
managerDriver,
client,
);
- }
-
- const { router: hono } = createManagerRouter(
- this.#config,
- config,
- managerDriver,
- undefined,
- );
-
- // Start server
- if (!config.disableServer) {
- (async () => {
- const out = await crossPlatformServe(hono, undefined);
- upgradeWebSocket = out.upgradeWebSocket;
- })();
- }
-
- return {
- client,
- fetch: hono.fetch.bind(hono),
- };
- }
-
- public startServerless(inputConfig?: RunConfigInput): ServerOutput {
- const config = RunConfigSchema.parse(inputConfig);
-
- // Configure logger
- if (config.logging?.baseLogger) {
- // Use provided base logger
- configureBaseLogger(config.logging.baseLogger);
} else {
- // Configure default logger with log level from config
- // getPinoLevel will handle env variable priority
- configureDefaultLogger(config.logging?.level);
- }
-
- // Choose the driver based on configuration
- const driver = chooseDefaultDriver(config);
-
- // TODO: Find cleaner way of disabling by default
- if (driver.name === "engine") {
- config.inspector.enabled = false;
- config.disableServer = true;
- config.disableActorDriver = true;
- }
- if (driver.name === "cloudflare-workers") {
- config.inspector.enabled = false;
- config.disableServer = true;
- config.disableActorDriver = true;
- config.noWelcome = true;
- }
-
- // Configure getUpgradeWebSocket lazily so we can assign it in crossPlatformServe
- let upgradeWebSocket: any;
- if (!config.getUpgradeWebSocket) {
- config.getUpgradeWebSocket = () => upgradeWebSocket!;
- }
-
- // Create router
- const managerDriver = driver.manager(this.#config, config);
-
- // Create client
- const client = createClientWithDriver(managerDriver, config);
-
- const driverLog = managerDriver.extraStartupLog?.() ?? {};
- logger().info({
- msg: "rivetkit ready",
- driver: driver.name,
- definitions: Object.keys(this.#config.use).length,
- ...driverLog,
- });
- if (config.inspector?.enabled && managerDriver.inspector) {
- logger().info({ msg: "inspector ready", url: getInspectorUrl(config) });
- }
-
- // Print welcome information
- if (!config.noWelcome) {
- const displayInfo = managerDriver.displayInformation();
- console.log();
- console.log(` RivetKit ${pkg.version} (${displayInfo.name})`);
- console.log(` - Endpoint: http://127.0.0.1:6420`);
- for (const [k, v] of Object.entries(displayInfo.properties)) {
- const padding = " ".repeat(Math.max(0, 13 - k.length));
- console.log(` - ${k}:${padding}${v}`);
- }
- if (config.inspector?.enabled && managerDriver.inspector) {
- console.log(` - Inspector: ${getInspectorUrl(config)}`);
- }
- console.log();
- }
-
- let serverlessActorDriverBuilder:
- | ((token?: string, totalSlots?: number) => ActorDriver)
- | undefined = (
- token: string | undefined,
- totalSlots: number | undefined,
- ) => {
- // Override config
- if (token) config.token = token;
- if (totalSlots) config.totalSlots = totalSlots;
-
- return driver.actor(this.#config, config, managerDriver, client);
- };
-
- // HACK: We need to find a better way to let the driver itself decide when to start the actor driver
- // Create runner
- //
- // Even though we do not use the return value, this is required to start the code that will handle incoming actors
- if (!config.disableActorDriver) {
- const _actorDriver = serverlessActorDriverBuilder();
- serverlessActorDriverBuilder = undefined;
+ serverlessActorDriverBuilder = (
+ token,
+ totalSlots,
+ runnerName,
+ namespace,
+ ) => {
+ // Override config
+ if (token) config.token = token;
+ if (totalSlots) config.totalSlots = totalSlots;
+ if (runnerName) config.runnerName = runnerName;
+ if (namespace) config.namespace = namespace;
+
+ // Create new actor driver with updated config
+ return driver.actor(this.#config, config, managerDriver, client);
+ };
}
const { router: hono } = createManagerRouter(
diff --git a/packages/rivetkit/src/registry/run-config.ts b/packages/rivetkit/src/registry/run-config.ts
index 8bf13efe9..1e813c6b0 100644
--- a/packages/rivetkit/src/registry/run-config.ts
+++ b/packages/rivetkit/src/registry/run-config.ts
@@ -6,6 +6,7 @@ import { ClientConfigSchema } from "@/client/config";
import { LogLevelSchema } from "@/common/log";
import { InspectorConfigSchema } from "@/inspector/config";
import type { ManagerDriverBuilder } from "@/manager/driver";
+import { getEnvUniversal } from "@/utils";
type CorsOptions = NonNullable[0]>;
@@ -35,6 +36,22 @@ export const RunConfigSchema = ClientConfigSchema.extend({
/** @experimental */
disableActorDriver: z.boolean().optional().default(false),
+ /**
+ * @experimental
+ *
+ * Whether to run runners normally or have them managed
+ * serverlessly (by the Rivet Engine for example).
+ */
+ runnerKind: z
+ .enum(["serverless", "normal"])
+ .optional()
+ .default(() =>
+ getEnvUniversal("RIVET_RUNNER_KIND") === "serverless"
+ ? "serverless"
+ : "normal",
+ ),
+ totalSlots: z.number().optional(),
+
/**
* @experimental
*