- v2 foundation: ActionResult/DevError/ActorContext/ResourceRef/Execution types - ActionRegistry + ResourceRegistry (namespaced types, FAIL FAST on duplicates) - ActionRuntime pipeline: validate -> authorize -> middleware -> handler -> output validation -> audit -> event -> execution persisted (devos_action_executions) - Legacy bridge: v1 services registered as devops.*/core.* resources & actions (47 actions, 9 resources) - Unified Action API: POST /api/v2/actions/:actionId, GET discovery (actions/resources) - 10 v2 unit tests (registry fail-fast, validation, authz, failure audit, events)
78 lines
2.5 KiB
TypeScript
78 lines
2.5 KiB
TypeScript
/**
|
||
* # v2 统一 Action API(docs 03 §48/§90/§91)
|
||
* - GET /api/v2/actions Action Discovery
|
||
* - GET /api/v2/actions/:id 单个 Action 契约
|
||
* - POST /api/v2/actions/:actionId 执行(六入口之一的 REST 形态)
|
||
* - GET /api/v2/resources Resource Discovery
|
||
*/
|
||
import { Body, Controller, Get, Param, Post, Query } from "@nestjs/common";
|
||
import { z } from "zod";
|
||
import { ActionRuntime } from "../runtime/action-runtime";
|
||
import { actionRegistry } from "../registry/action-registry";
|
||
import { resourceRegistry } from "../registry/resource-registry";
|
||
import { ZodValidationPipe } from "../../common/pipes/zod-validation.pipe";
|
||
import { CurrentUser, CurrentWorkspace, Roles } from "../../common/decorators";
|
||
import type { RequestUser, RequestWorkspace } from "../../common/decorators";
|
||
import type { ActorContext } from "../foundation/types";
|
||
|
||
const ExecuteSchema = z.object({
|
||
input: z.record(z.unknown()).default({}),
|
||
});
|
||
|
||
@Controller("v2")
|
||
export class V2Controller {
|
||
constructor(private readonly runtime: ActionRuntime) {}
|
||
|
||
/** # Action Discovery */
|
||
@Get("actions")
|
||
listActions(@Query("source") source?: string) {
|
||
return this.runtime.discover((source as "agent" | "mcp" | undefined) ?? undefined);
|
||
}
|
||
|
||
/** # 单个 Action 契约 */
|
||
@Get("actions/:id")
|
||
getAction(@Param("id") id: string) {
|
||
const action = actionRegistry.get(id);
|
||
if (!action) return { found: false };
|
||
return {
|
||
found: true,
|
||
id: action.id,
|
||
resource: action.resource,
|
||
name: action.name,
|
||
description: action.description,
|
||
mode: action.mode,
|
||
permission: action.permission,
|
||
dangerous: action.dangerous ?? false,
|
||
inputSchema: action.inputSchema ?? undefined,
|
||
outputSchema: action.outputSchema ?? undefined,
|
||
};
|
||
}
|
||
|
||
/** # Resource Discovery */
|
||
@Get("resources")
|
||
listResources() {
|
||
return this.runtime.discoverResources();
|
||
}
|
||
|
||
/** # 执行 Action —— REST 入口(与 UI/MCP/Agent/CLI 同一 Runtime) */
|
||
@Roles("MEMBER")
|
||
@Post("actions/:actionId")
|
||
async execute(
|
||
@Param("actionId") actionId: string,
|
||
@CurrentWorkspace() ws: RequestWorkspace,
|
||
@CurrentUser() user: RequestUser,
|
||
@Body(new ZodValidationPipe(ExecuteSchema)) body: { input: Record<string, unknown> },
|
||
) {
|
||
const actor: ActorContext = {
|
||
type: "user",
|
||
id: user.id,
|
||
displayName: user.email,
|
||
organizationId: ws.id,
|
||
};
|
||
return this.runtime.execute(actionId, body.input, actor, {
|
||
source: "user",
|
||
organizationId: ws.id,
|
||
});
|
||
}
|
||
}
|