feat: add action registry for step-based recipe execution

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
zhixian
2026-02-17 22:44:20 +09:00
parent 1c96af57f1
commit ccfbc384b0

113
src/lib/actions.ts Normal file
View File

@@ -0,0 +1,113 @@
import { api } from "./api";
export interface ActionDef {
execute: (args: Record<string, unknown>) => Promise<unknown>;
describe: (args: Record<string, unknown>) => string;
}
function renderArgs(
args: Record<string, unknown>,
params: Record<string, string>,
): Record<string, unknown> {
const result: Record<string, unknown> = {};
for (const [key, value] of Object.entries(args)) {
if (typeof value === "string") {
let rendered = value;
for (const [paramId, paramValue] of Object.entries(params)) {
rendered = rendered.replaceAll(`{{${paramId}}}`, paramValue);
}
result[key] = rendered;
} else {
result[key] = value;
}
}
return result;
}
const registry: Record<string, ActionDef> = {
create_agent: {
execute: (args) =>
api.createAgent(
args.agentId as string,
args.modelProfileId as string | undefined,
args.independent as boolean | undefined,
),
describe: (args) =>
`Create ${args.independent ? "independent " : ""}agent "${args.agentId}"`,
},
setup_identity: {
execute: (args) =>
api.setupAgentIdentity(
args.agentId as string,
args.name as string,
args.emoji as string | undefined,
),
describe: (args) => {
const emoji = args.emoji ? ` ${args.emoji}` : "";
return `Set identity: ${args.name}${emoji}`;
},
},
bind_channel: {
execute: (args) =>
api.assignChannelAgent(
args.channelType as string,
args.peerId as string,
args.agentId as string,
),
describe: (args) =>
`Bind ${args.channelType} channel → agent "${args.agentId}"`,
},
config_patch: {
execute: (args) =>
api.applyConfigPatch(
args.patchTemplate as string,
args.params as Record<string, string>,
),
describe: () => "",
},
set_global_model: {
execute: (args) => api.setGlobalModel(args.profileId as string),
describe: (args) => `Set default model to ${args.profileId}`,
},
};
export function getAction(actionType: string): ActionDef | undefined {
return registry[actionType];
}
export interface ResolvedStep {
index: number;
action: string;
label: string;
args: Record<string, unknown>;
description: string;
}
export function resolveSteps(
steps: { action: string; label: string; args: Record<string, unknown> }[],
params: Record<string, string>,
): ResolvedStep[] {
return steps.map((step, index) => {
const resolved = renderArgs(step.args, params);
if (step.action === "config_patch") {
resolved.params = params;
}
const actionDef = getAction(step.action);
const description = actionDef?.describe(resolved) || step.label;
return {
index,
action: step.action,
label: step.label,
args: resolved,
description: description || step.label,
};
});
}
export async function executeStep(step: ResolvedStep): Promise<void> {
const actionDef = getAction(step.action);
if (!actionDef) {
throw new Error(`Unknown action type: ${step.action}`);
}
await actionDef.execute(step.args);
}