mirror of
https://github.com/aiclientproxy/proxycast.git
synced 2026-09-24 23:10:56 +08:00
277 lines
8.2 KiB
JavaScript
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;
|
|
});
|