Files
proxycast/scripts/generate-agent-runtime-clients.mjs
T
2026-04-11 01:17:15 +08:00

277 lines
8.2 KiB
JavaScript

import fs from "node:fs/promises";
import path from "node:path";
import { fileURLToPath } from "node:url";
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const projectRoot = path.resolve(__dirname, "..");
const schemaPath = path.join(
projectRoot,
"src/lib/governance/agentRuntimeCommandSchema.json",
);
const catalogPath = path.join(
projectRoot,
"src/lib/governance/agentCommandCatalog.json",
);
const outputPath = path.join(
projectRoot,
"src/lib/api/agentRuntime/commandManifest.generated.ts",
);
const allowedDomains = new Set([
"thread",
"session",
"export",
"inventory",
"subagent",
]);
const allowedLifecycles = new Set(["current", "compat", "deprecated"]);
const allowedMockStrategies = new Set([
"default-mock",
"mock-priority",
"bridge-only",
]);
function toCommandKey(command) {
return command
.replace(/^agent_runtime_/, "")
.split("_")
.map((segment, index) =>
index === 0
? segment
: segment.charAt(0).toUpperCase() + segment.slice(1),
)
.join("");
}
async function readJson(jsonPath) {
const raw = await fs.readFile(jsonPath, "utf8");
return JSON.parse(raw);
}
function validateSchema(schema) {
const commands = Array.isArray(schema?.commands) ? schema.commands : [];
if (commands.length === 0) {
throw new Error("agentRuntimeCommandSchema.json 不能为空");
}
const seenCommands = new Set();
const seenKeys = new Set();
for (const entry of commands) {
const command = String(entry?.command ?? "").trim();
const domain = String(entry?.domain ?? "").trim();
const requestType = String(entry?.requestType ?? "").trim();
const responseType = String(entry?.responseType ?? "").trim();
const lifecycle = String(entry?.lifecycle ?? "").trim();
const mockStrategy = String(entry?.mockStrategy ?? "").trim();
const docsSection = String(entry?.docsSection ?? "").trim();
const key = toCommandKey(command);
if (!command.startsWith("agent_runtime_")) {
throw new Error(`schema 命令必须以 agent_runtime_ 开头: ${command}`);
}
if (seenCommands.has(command)) {
throw new Error(`schema 命令重复: ${command}`);
}
if (seenKeys.has(key)) {
throw new Error(`schema 命令 key 重复: ${key}`);
}
if (!allowedDomains.has(domain)) {
throw new Error(`schema domain 非法: ${domain}`);
}
if (!requestType) {
throw new Error(`schema 缺少 requestType: ${command}`);
}
if (!responseType) {
throw new Error(`schema 缺少 responseType: ${command}`);
}
if (!allowedLifecycles.has(lifecycle)) {
throw new Error(`schema lifecycle 非法: ${command} -> ${lifecycle}`);
}
if (!allowedMockStrategies.has(mockStrategy)) {
throw new Error(
`schema mockStrategy 非法: ${command} -> ${mockStrategy}`,
);
}
if (!docsSection) {
throw new Error(`schema 缺少 docsSection: ${command}`);
}
seenCommands.add(command);
seenKeys.add(key);
}
return commands.map((entry) => ({
command: String(entry.command),
key: toCommandKey(String(entry.command)),
domain: String(entry.domain),
requestType: String(entry.requestType),
responseType: String(entry.responseType),
lifecycle: String(entry.lifecycle),
mockStrategy: String(entry.mockStrategy),
docsSection: String(entry.docsSection),
}));
}
function assertCatalogMatchesSchema(schemaEntries, catalog) {
const schemaCommands = schemaEntries.map((entry) => entry.command);
const catalogCommands = Array.isArray(catalog?.runtimeGatewayCommands)
? catalog.runtimeGatewayCommands.filter((command) =>
String(command).startsWith("agent_runtime_"),
)
: [];
const schemaSet = new Set(schemaCommands);
const catalogSet = new Set(catalogCommands);
const missingFromCatalog = schemaCommands.filter(
(command) => !catalogSet.has(command),
);
const extraInCatalog = catalogCommands.filter(
(command) => !schemaSet.has(command),
);
if (missingFromCatalog.length === 0 && extraInCatalog.length === 0) {
return;
}
const lines = [];
if (missingFromCatalog.length > 0) {
lines.push("catalog 缺少以下 agent_runtime 命令:");
for (const command of missingFromCatalog) {
lines.push(`- ${command}`);
}
}
if (extraInCatalog.length > 0) {
lines.push("catalog 多出以下未进入 schema 的 agent_runtime 命令:");
for (const command of extraInCatalog) {
lines.push(`- ${command}`);
}
}
throw new Error(lines.join("\n"));
}
function buildGeneratedOutput(schemaEntries) {
const domains = [...allowedDomains];
const lifecycles = [...allowedLifecycles];
const mockStrategies = [...allowedMockStrategies];
const commandObjectBody = schemaEntries
.map((entry) => ` ${entry.key}: ${JSON.stringify(entry.command)},`)
.join("\n");
const descriptorBody = schemaEntries
.map(
(entry) => ` {
key: ${JSON.stringify(entry.key)},
command: AGENT_RUNTIME_COMMANDS.${entry.key},
domain: ${JSON.stringify(entry.domain)},
requestType: ${JSON.stringify(entry.requestType)},
responseType: ${JSON.stringify(entry.responseType)},
lifecycle: ${JSON.stringify(entry.lifecycle)},
mockStrategy: ${JSON.stringify(entry.mockStrategy)},
docsSection: ${JSON.stringify(entry.docsSection)},
},`,
)
.join("\n");
const namesBody = schemaEntries
.map((entry) => ` AGENT_RUNTIME_COMMANDS.${entry.key},`)
.join("\n");
const byDomainBody = domains
.map((domain) => {
const entries = schemaEntries.filter((entry) => entry.domain === domain);
const lines = entries.map(
(entry) => ` AGENT_RUNTIME_COMMANDS.${entry.key},`,
);
return ` ${JSON.stringify(domain)}: [\n${lines.join("\n")}\n ],`;
})
.join("\n");
return `/**
* 由 scripts/generate-agent-runtime-clients.mjs 自动生成,请勿手改。
*/
export const AGENT_RUNTIME_COMMANDS = {
${commandObjectBody}
} as const;
export type AgentRuntimeCommandKey = keyof typeof AGENT_RUNTIME_COMMANDS;
export type AgentRuntimeCommandName =
(typeof AGENT_RUNTIME_COMMANDS)[AgentRuntimeCommandKey];
export type AgentRuntimeCommandDomain = ${domains
.map((value) => JSON.stringify(value))
.join(" | ")};
export type AgentRuntimeCommandLifecycle = ${lifecycles
.map((value) => JSON.stringify(value))
.join(" | ")};
export type AgentRuntimeCommandMockStrategy = ${mockStrategies
.map((value) => JSON.stringify(value))
.join(" | ")};
export interface AgentRuntimeCommandDescriptor {
readonly key: AgentRuntimeCommandKey;
readonly command: AgentRuntimeCommandName;
readonly domain: AgentRuntimeCommandDomain;
readonly requestType: string;
readonly responseType: string;
readonly lifecycle: AgentRuntimeCommandLifecycle;
readonly mockStrategy: AgentRuntimeCommandMockStrategy;
readonly docsSection: string;
}
export const AGENT_RUNTIME_COMMAND_DESCRIPTORS = [
${descriptorBody}
] as const satisfies readonly AgentRuntimeCommandDescriptor[];
export const AGENT_RUNTIME_COMMAND_NAMES = [
${namesBody}
] as const satisfies readonly AgentRuntimeCommandName[];
export const AGENT_RUNTIME_COMMANDS_BY_DOMAIN = {
${byDomainBody}
} as const satisfies Record<
AgentRuntimeCommandDomain,
readonly AgentRuntimeCommandName[]
>;
`;
}
async function main() {
const schema = await readJson(schemaPath);
const catalog = await readJson(catalogPath);
const schemaEntries = validateSchema(schema);
assertCatalogMatchesSchema(schemaEntries, catalog);
const output = buildGeneratedOutput(schemaEntries);
const checkOnly = process.argv.includes("--check");
let current = "";
try {
current = await fs.readFile(outputPath, "utf8");
} catch (error) {
if (error?.code !== "ENOENT") {
throw error;
}
}
if (checkOnly) {
if (current !== output) {
throw new Error(
"agent runtime generated manifest 已过期,请先运行 npm run generate:agent-runtime-clients",
);
}
return;
}
await fs.mkdir(path.dirname(outputPath), { recursive: true });
await fs.writeFile(outputPath, output, "utf8");
}
main().catch((error) => {
console.error(
`[generate-agent-runtime-clients] ${error instanceof Error ? error.message : String(error)}`,
);
process.exitCode = 1;
});