/** Generate model-visible Host/Client Service and Event inspect catalogs. */ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs' import { dirname, resolve } from 'node:path' import { projectCordisCatalog } from '@deepseek-ai/dsh-typert-generator' import type { CordisCatalogModel, ServiceMethodEntry } from '@deepseek-ai/dsh-typert-generator' import { CORDIS_CATALOG_POLICY } from './gen-cordis-catalog.ts' const root = resolve(import.meta.dirname, '..') const CLIENT_OUT = 'packages/extensions/cordis-client-runner/src/client/api-catalog.ts' const CLIENT_SERVICES: Readonly> = { layout: ['toggleSidebar', 'openDetails', 'closeDetails'], locale: ['getLocale', 'getSnapshot', 'subscribe', 'setLocale', 'addLanguage', 'register', 'bind'], sessions: ['open', 'openSubagent', 'setSubagentCatalogOpen', 'refreshSubagents', 'search', 'fork', 'scope', 'binding'], slots: ['register', 'inject'], theme: ['getTheme', 'setTheme', 'setFontSize', 'register', 'overrideTokens'], uiWorkspace: [ 'connectWorkspace', 'startSession', 'archiveSession', 'pickDirectory', 'listDirectory', 'createDirectory', ], workspaces: ['create', 'rename', 'delete', 'insertSessionBefore', 'archiveSession'], } const CLIENT_EVENTS = new Set([ 'connection/reset', 'locale/change', 'slots/changed', 'theme/change', ]) function methodName(method: ServiceMethodEntry): string | undefined { return /^(?:declare\s+)?(?:readonly\s+)?(?:async\s+)?([A-Za-z_$][\w$]*)/.exec(method.signature)?.[1] } function clientModel(model: CordisCatalogModel): CordisCatalogModel { const services = Object.entries(CLIENT_SERVICES).map(([key, allowed]) => { const matches = model.services.filter(service => service.key === key) if (matches.length !== 1) { throw new Error(`gen-cordis-inspect-catalog: expected one Client Service ${JSON.stringify(key)}, found ${matches.length}`) } const service = matches[0] as (typeof matches)[number] const names = new Set(allowed) const methods = service.methods.filter(method => names.has(methodName(method) ?? '')) const found = new Set(methods.map(method => methodName(method))) const missing = allowed.filter(name => !found.has(name)) if (missing.length > 0) { throw new Error( `gen-cordis-inspect-catalog: Client Service ${JSON.stringify(key)}` + ` is missing allowlisted method(s): ${missing.join(', ')}`, ) } return { ...service, methods } }) const events = [...CLIENT_EVENTS].map((name) => { const matches = model.events.filter(event => event.name === name) if (matches.length !== 1) { throw new Error(`gen-cordis-inspect-catalog: expected one Client Event ${JSON.stringify(name)}, found ${matches.length}`) } return matches[0] as (typeof matches)[number] }) return { services, events, } } function main(): void { const { projector, model } = projectCordisCatalog(root, CORDIS_CATALOG_POLICY, 'client') const destination = resolve(root, CLIENT_OUT) const source = projector.renderRuntimeApi(clientModel(model)) .replace('Generated by scripts/gen-cordis-api.ts', 'Generated by scripts/gen-cordis-inspect-catalog.ts') .replaceAll('pnpm run gen-cordis-api', 'pnpm run gen-cordis-inspect-catalog') .replaceAll('pnpm run verify-cordis-api', 'pnpm run verify-cordis-inspect-catalog') .replaceAll('@deepseek-ai/dsh-tool-cordis/api-catalog', '@deepseek-ai/dsh-cordis-client-runner/client/api-catalog') if (process.argv.includes('--check')) { if (!existsSync(destination) || readFileSync(destination, 'utf8') !== source) { throw new Error(`gen-cordis-inspect-catalog: ${CLIENT_OUT} is stale; run pnpm run gen-cordis-inspect-catalog`) } console.log(`gen-cordis-inspect-catalog: ${CLIENT_OUT} is up to date`) return } mkdirSync(dirname(destination), { recursive: true }) writeFileSync(destination, source) console.log(`gen-cordis-inspect-catalog: wrote ${CLIENT_OUT}`) } main()