import { execFileSync } from "node:child_process"; import { mkdirSync, readFileSync, writeFileSync } from "node:fs"; import { dirname, resolve } from "node:path"; import { fileURLToPath, pathToFileURL } from "node:url"; import { format } from "prettier"; import YAML from "yaml"; const scriptDirectory = dirname(fileURLToPath(import.meta.url)); const repoRoot = resolve(scriptDirectory, "../.."); const daemonRoot = resolve(repoRoot, "packages/agent/daemon"); const outputPath = resolve( repoRoot, "packages/agent/gui/generated/providerIdentityCatalog.ts" ); const capabilityOutputPath = resolve( repoRoot, "packages/agent/activity-core/src/generated/agentCapabilityKeys.ts" ); const openapiPath = resolve( repoRoot, "services/tuttid/api/openapi/tuttid.v1.yaml" ); const prettierConfigPath = resolve( repoRoot, "packages/configs/prettier/base.mjs" ); const checkOnly = process.argv.includes("--check"); const { default: prettierConfig } = await import( pathToFileURL(prettierConfigPath).href ); const openapiProviderSchemaNames = [ "DesktopAgentComposerDefaultsByProvider", "DesktopAgentGuiConversationRailCollapsedByProvider" ]; const openProviderIDPattern = "^[a-z][a-z0-9._:-]*$"; const providerStatusKinds = [ "codex_cli", "claude_cli", "opencode_cli", "generic_cli" ]; if (isMainModule()) { const catalog = readRegistryCatalog(); const capabilityKeys = readRegistryCapabilityKeys(); const openapi = YAML.parse(readFileSync(openapiPath, "utf8")); validateRegistryCatalogAgainstOpenAPI(catalog, openapi); validateCapabilityKeysAgainstOpenAPI(capabilityKeys, openapi); const source = await renderProviderIdentityCatalog(catalog); const capabilitySource = await renderAgentCapabilityKeys(capabilityKeys); if (checkOnly) { const existing = readFileSync(outputPath, "utf8"); if (existing !== source) { throw new Error( `Generated AgentGUI provider identity catalog is out of date: ${outputPath}` ); } const existingCapabilitySource = readFileSync(capabilityOutputPath, "utf8"); if (existingCapabilitySource !== capabilitySource) { throw new Error( `Generated agent capability keys are out of date: ${capabilityOutputPath}` ); } } else { mkdirSync(dirname(outputPath), { recursive: true }); writeFileSync(outputPath, source, "utf8"); mkdirSync(dirname(capabilityOutputPath), { recursive: true }); writeFileSync(capabilityOutputPath, capabilitySource, "utf8"); } } function readRegistryCapabilityKeys() { const output = execFileSync( "go", ["run", "./providerregistry/cmd/providercapabilitycatalog"], { cwd: daemonRoot, encoding: "utf8" } ); const keys = JSON.parse(output); if ( !Array.isArray(keys) || keys.length === 0 || keys.some((key) => typeof key !== "string" || key.trim() === "") || new Set(keys).size !== keys.length ) { throw new TypeError("provider capability catalog must be unique strings"); } return keys; } export async function renderAgentCapabilityKeys(keys) { return await format( `// Code generated by tools/scripts/generate-agent-gui-provider-catalog.mjs. DO NOT EDIT.\n\nexport const AGENT_CAPABILITY_KEYS = ${JSON.stringify(keys, null, 2)} as const;\nexport type AgentCapabilityKey = (typeof AGENT_CAPABILITY_KEYS)[number];\n`, { ...prettierConfig, parser: "typescript" } ); } function isMainModule() { return Boolean( process.argv[1] && resolve(process.argv[1]) === fileURLToPath(import.meta.url) ); } function readRegistryCatalog() { const output = execFileSync( "go", ["run", "./providerregistry/cmd/provideridentitycatalog"], { cwd: daemonRoot, encoding: "utf8" } ); const catalog = JSON.parse(output); validateRegistryCatalog(catalog); return catalog; } export async function renderProviderIdentityCatalog(catalog) { validateRegistryCatalog(catalog); return await format( `// Code generated by tools/scripts/generate-agent-gui-provider-catalog.mjs. DO NOT EDIT.\n\nexport const generatedProviderIdentityCatalog = ${JSON.stringify(catalog, null, 2)} as const;\n`, { ...prettierConfig, parser: "typescript" } ); } export function validateRegistryCatalog(catalog) { if (!Array.isArray(catalog)) { throw new TypeError("provider identity catalog must be an array"); } const providerIds = new Set(); const targetIds = new Set(); for (const entry of catalog) { if (!entry || typeof entry !== "object") { throw new TypeError("provider identity catalog entry must be an object"); } for (const key of ["providerId", "displayName", "iconKey", "localeKey"]) { if (typeof entry[key] !== "string" || entry[key].trim() === "") { throw new TypeError(`provider identity catalog ${key} is required`); } } if (!["", "anthropic", "openai"].includes(entry.modelPlanProtocol)) { throw new TypeError( `invalid model plan protocol for provider: ${entry.providerId}` ); } if (!providerStatusKinds.includes(entry.statusKind)) { throw new TypeError( `invalid status kind for provider: ${entry.providerId}` ); } if (!Array.isArray(entry.aliases)) { throw new TypeError("provider identity catalog aliases must be an array"); } if (providerIds.has(entry.providerId)) { throw new TypeError(`duplicate provider id: ${entry.providerId}`); } providerIds.add(entry.providerId); const target = entry.target; if ( !target || typeof target !== "object" || typeof target.id !== "string" || target.id.trim() === "" || typeof target.launchRefType !== "string" || target.launchRefType.trim() === "" || typeof target.enabled !== "boolean" || !Number.isInteger(target.sortOrder) ) { throw new TypeError(`invalid target for provider: ${entry.providerId}`); } if (targetIds.has(target.id)) { throw new TypeError(`duplicate target id: ${target.id}`); } targetIds.add(target.id); const desktop = entry.desktop; if ( !desktop || typeof desktop !== "object" || typeof desktop.managed !== "boolean" || !Number.isInteger(desktop.managedOrder) || desktop.managedOrder < 0 || desktop.managed !== desktop.managedOrder > 0 || !Number.isInteger(desktop.statusProbePriority) || desktop.statusProbePriority < 0 || desktop.managed !== desktop.statusProbePriority > 0 || typeof desktop.usageProbeKind !== "string" || typeof desktop.visibilityGate !== "string" || typeof desktop.runtimeProbeFallback !== "string" || typeof desktop.installBootstrap !== "boolean" || typeof desktop.refreshOnAccountChange !== "boolean" || (!desktop.managed && desktop.installBootstrap) || (!desktop.managed && desktop.refreshOnAccountChange) || typeof desktop.developerLogs !== "boolean" || typeof desktop.defaultProviderEligible !== "boolean" || !Number.isInteger(desktop.defaultProviderPriority) || desktop.defaultProviderPriority < 0 || desktop.defaultProviderEligible !== desktop.defaultProviderPriority > 0 || !Number.isInteger(desktop.unavailableDockOrderOffset) || desktop.unavailableDockOrderOffset < 0 ) { throw new TypeError( `invalid desktop integration for provider: ${entry.providerId}` ); } } } export function validateRegistryCatalogAgainstOpenAPI(catalog, openapi) { validateRegistryCatalog(catalog); const migratedProviderIds = catalog.map((entry) => entry.providerId); validateOpenProviderSchema( openapi, "AgentTargetProvider", migratedProviderIds ); validateOpenProviderSchema( openapi, "WorkspaceAgentProvider", migratedProviderIds ); for (const schemaName of openapiProviderSchemaNames) { const schema = openapi?.components?.schemas?.[schemaName]; if ( !schema || typeof schema !== "object" || schema.type !== "object" || schema.additionalProperties !== false || !schema.properties || typeof schema.properties !== "object" || Array.isArray(schema.properties) ) { throw new TypeError( `openapi provider-keyed schema must remain a closed object: ${schemaName}` ); } const actualProviderIds = Object.keys(schema.properties); assertSameStringSet( actualProviderIds, migratedProviderIds, `openapi provider schema drift: ${schemaName}` ); } const defaultProviderIds = openapi?.components?.schemas?.DesktopDefaultAgentProvider?.enum; if (!Array.isArray(defaultProviderIds)) { throw new TypeError( "openapi provider schema is missing: DesktopDefaultAgentProvider" ); } const supportedProviderIds = new Set(migratedProviderIds); for (const providerId of defaultProviderIds) { if (!supportedProviderIds.has(providerId)) { throw new TypeError( `openapi default provider is not registered: ${providerId}` ); } } assertSameStringSet( catalog .filter((entry) => entry.desktop.defaultProviderEligible) .map((entry) => entry.providerId), defaultProviderIds, "desktop default provider descriptor drift" ); } function validateOpenProviderSchema(openapi, schemaName, migratedProviderIds) { const schema = openapi?.components?.schemas?.[schemaName]; if ( !schema || typeof schema !== "object" || schema.type !== "string" || schema.enum !== undefined || schema.minLength !== 1 || schema.maxLength !== 128 || schema.pattern !== openProviderIDPattern ) { throw new TypeError( `openapi ${schemaName} must remain an open, bounded string schema` ); } const pattern = new RegExp(schema.pattern, "u"); for (const providerId of [...migratedProviderIds, "acp:extension-example"]) { if (!pattern.test(providerId)) { throw new TypeError( `openapi ${schemaName} rejects valid provider id: ${providerId}` ); } } for (const invalid of ["BadProvider", "bad provider", "$(provider)"]) { if (pattern.test(invalid)) { throw new TypeError( `openapi ${schemaName} accepts unsafe provider id: ${invalid}` ); } } } export function validateCapabilityKeysAgainstOpenAPI(keys, openapi) { const schema = openapi?.components?.schemas?.WorkspaceAgentCapabilities; if (!schema || typeof schema !== "object") { throw new TypeError("openapi WorkspaceAgentCapabilities schema is missing"); } assertSameStringSet( Object.keys(schema.properties ?? {}), keys, "openapi agent capability vocabulary drift" ); } function assertSameStringSet(actual, expected, label) { const normalizedActual = [...new Set(actual)].sort(); const normalizedExpected = [...new Set(expected)].sort(); if ( normalizedActual.length !== actual.length || normalizedExpected.length !== expected.length || JSON.stringify(normalizedActual) !== JSON.stringify(normalizedExpected) ) { throw new TypeError( `${label}: actual=${JSON.stringify(normalizedActual)} expected=${JSON.stringify(normalizedExpected)}` ); } }