Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
139 changes: 94 additions & 45 deletions frontend/src/app/actor-builds-list.tsx

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

66 changes: 47 additions & 19 deletions rivetkit-typescript/packages/rivetkit/src/actor/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,17 +21,6 @@ import type {
} from "./contexts";
import type { AnyDatabaseProvider } from "./database";

/**
* Configuration object that can be returned from `workflow()` to provide
* metadata and the run handler.
*/
export interface RunConfig {
/** Icon to display in the inspector for this run handler */
icon?: string;
/** The actual run handler function */
run: (...args: any[]) => any;
}

export interface ActorTypes<
TState,
TConnParams,
Expand All @@ -53,24 +42,49 @@ const zFunction = <
T extends (...args: any[]) => any = (...args: unknown[]) => unknown,
>() => z.custom<T>((val) => typeof val === "function");

// Schema for run handler with metadata
export const RunConfigSchema = z.object({
/** Display name for the actor in the Inspector UI. */
name: z.string().optional(),
/** Icon for the actor in the Inspector UI. Can be an emoji or FontAwesome icon name. */
icon: z.string().optional(),
/** The run handler function. */
run: zFunction(),
});
export type RunConfig = z.infer<typeof RunConfigSchema>;

// Run can be either a function or an object with name/icon/run
const zRunHandler = z.union([zFunction(), RunConfigSchema]).optional();

/** Extract the run function from either a function or RunConfig object. */
export function getRunFunction(
run: ((...args: any[]) => any) | RunConfig | undefined,
): ((...args: any[]) => any) | undefined {
if (!run) return undefined;
if (typeof run === "function") return run;
return run.run;
}

/** Extract run metadata (name/icon) from RunConfig if provided. */
export function getRunMetadata(
run: ((...args: any[]) => any) | RunConfig | undefined,
): { name?: string; icon?: string } {
if (!run || typeof run === "function") return {};
return { name: run.name, icon: run.icon };
}

// This schema is used to validate the input at runtime. The generic types are defined below in `ActorConfig`.
//
// We don't use Zod generics with `z.custom` because:
// (a) there seems to be a weird bug in either Zod, tsup, or TSC that causese external packages to have different types from `z.infer` than from within the same package and
// (b) it makes the type definitions incredibly difficult to read as opposed to vanilla TypeScript.
// Schema for RunConfig objects returned by workflow()
const RunConfigSchema = z.object({
icon: z.string().optional(),
run: zFunction(),
});

export const ActorConfigSchema = z
.object({
onCreate: zFunction().optional(),
onDestroy: zFunction().optional(),
onWake: zFunction().optional(),
onSleep: zFunction().optional(),
run: z.union([zFunction(), RunConfigSchema]).optional(),
run: zRunHandler,
onStateChange: zFunction().optional(),
onBeforeConnect: zFunction().optional(),
onConnect: zFunction().optional(),
Expand All @@ -88,6 +102,10 @@ export const ActorConfigSchema = z
createVars: zFunction().optional(),
options: z
.object({
/** Display name for the actor in the Inspector UI. */
name: z.string().optional(),
/** Icon for the actor in the Inspector UI. Can be an emoji or FontAwesome icon name. */
icon: z.string().optional(),
createVarsTimeout: z.number().positive().default(5000),
createConnStateTimeout: z.number().positive().default(5000),
onConnectTimeout: z.number().positive().default(5000),
Expand Down Expand Up @@ -355,7 +373,7 @@ interface BaseActorConfig<
* On shutdown, the actor waits for this handler to complete with a
* configurable timeout (options.runStopTimeout, default 15s).
*
* Can be a function or a RunConfig object (returned by `workflow()`).
* Can be either a function or a RunConfig object with optional name/icon metadata.
*
* @returns Void or a Promise. If the promise exits, the actor crashes.
*/
Expand Down Expand Up @@ -688,6 +706,16 @@ export function test<

export const DocActorOptionsSchema = z
.object({
name: z
.string()
.optional()
.describe("Display name for the actor in the Inspector UI."),
icon: z
.string()
.optional()
.describe(
"Icon for the actor in the Inspector UI. Can be an emoji (e.g., '🚀') or FontAwesome icon name (e.g., 'rocket').",
),
createVarsTimeout: z
.number()
.optional()
Expand Down
11 changes: 3 additions & 8 deletions rivetkit-typescript/packages/rivetkit/src/actor/instance/mod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import {
CONN_VERSIONED,
} from "@/schemas/actor-persist/versioned";
import { EXTRA_ERROR_LOG } from "@/utils";
import type { ActorConfig } from "../config";
import { getRunFunction, type ActorConfig } from "../config";
import type { ConnDriver } from "../conn/driver";
import { createHttpDriver } from "../conn/drivers/http";
import {
Expand Down Expand Up @@ -1169,16 +1169,11 @@ export class ActorInstance<S, CP, CS, V, I, DB extends AnyDatabaseProvider> {
}

#startRunHandler() {
if (!this.#config.run) return;
const runFn = getRunFunction(this.#config.run);
if (!runFn) return;

this.#rLog.debug({ msg: "starting run handler" });

// Handle both function and RunConfig object (returned by workflow())
const runFn =
typeof this.#config.run === "function"
? this.#config.run
: this.#config.run.run;

const runSpan = this.startTraceSpan("actor.run");
const runResult = this.#traces.withSpan(runSpan, () =>
runFn(this.actorContext),
Expand Down
17 changes: 15 additions & 2 deletions rivetkit-typescript/packages/rivetkit/src/registry/config/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { z } from "zod";
import type { ActorDefinition, AnyActorDefinition } from "@/actor/definition";
import { getRunMetadata } from "@/actor/config";
import { type Logger, LogLevelSchema } from "@/common/log";
import { ENGINE_ENDPOINT } from "@/engine-process/constants";
import { InspectorConfigSchema } from "@/inspector/config";
Expand Down Expand Up @@ -265,9 +266,21 @@ export type RegistryConfigInput<A extends RegistryActors> = Omit<

export function buildActorNames(
config: RegistryConfig,
): Record<string, { metadata: Record<string, any> }> {
): Record<string, { metadata: Record<string, unknown> }> {
return Object.fromEntries(
Object.keys(config.use).map((name) => [name, { metadata: {} }]),
Object.keys(config.use).map((actorName) => {
const definition = config.use[actorName];
const options = definition.config.options ?? {};
const runMeta = getRunMetadata(definition.config.run);
const metadata: Record<string, unknown> = {};
// Actor options take precedence over run metadata
metadata.icon = options.icon ?? runMeta.icon;
metadata.name = options.name ?? runMeta.name;
// Remove undefined values
if (!metadata.icon) delete metadata.icon;
if (!metadata.name) delete metadata.name;
return [actorName, { metadata }];
}),
);
}

Expand Down
Loading
Loading