gen-cordis-inspect-catalog.ts 4.1 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091
  1. /** Generate model-visible Host/Client Service and Event inspect catalogs. */
  2. import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs'
  3. import { dirname, resolve } from 'node:path'
  4. import { projectCordisCatalog } from '@deepseek-ai/dsh-typert-generator'
  5. import type { CordisCatalogModel, ServiceMethodEntry } from '@deepseek-ai/dsh-typert-generator'
  6. import { CORDIS_CATALOG_POLICY } from './gen-cordis-catalog.ts'
  7. const root = resolve(import.meta.dirname, '..')
  8. const CLIENT_OUT = 'packages/extensions/cordis-client-runner/src/client/api-catalog.ts'
  9. const CLIENT_SERVICES: Readonly<Record<string, readonly string[]>> = {
  10. layout: ['selectPanel', 'beginNavigation', 'toggleSidebar', 'openRightbar', 'closeRightbar'],
  11. locale: ['getLocale', 'getSnapshot', 'subscribe', 'setLocale', 'addLanguage', 'register', 'bind'],
  12. sessions: ['retain', 'using', 'retainInfo', 'setSubagentCatalogOpen', 'refreshSubagents', 'search', 'fork', 'scope', 'binding'],
  13. slots: ['register', 'registerFactory', 'inject'],
  14. theme: ['getTheme', 'setTheme', 'setFontSize', 'register', 'overrideTokens'],
  15. uiWorkspace: [
  16. 'openSession', 'openWorkspace', 'forkSession', 'connectWorkspace', 'startSession', 'archiveSession', 'unarchiveSession',
  17. 'pickDirectory', 'listDirectory', 'createDirectory',
  18. ],
  19. workspaces: ['create', 'rename', 'delete', 'insertSessionBefore', 'archiveSession', 'unarchiveSession'],
  20. }
  21. const CLIENT_EVENTS = new Set([
  22. 'connection/reset',
  23. 'locale/change',
  24. 'slots/changed',
  25. 'theme/change',
  26. ])
  27. function methodName(method: ServiceMethodEntry): string | undefined {
  28. return /^(?:declare\s+)?(?:readonly\s+)?(?:async\s+)?([A-Za-z_$][\w$]*)/.exec(method.signature)?.[1]
  29. }
  30. function clientModel(model: CordisCatalogModel): CordisCatalogModel {
  31. const services = Object.entries(CLIENT_SERVICES).map(([key, allowed]) => {
  32. const matches = model.services.filter(service => service.key === key)
  33. if (matches.length !== 1) {
  34. throw new Error(`gen-cordis-inspect-catalog: expected one Client Service ${JSON.stringify(key)}, found ${matches.length}`)
  35. }
  36. const service = matches[0] as (typeof matches)[number]
  37. const names = new Set(allowed)
  38. const methods = service.methods.filter(method => names.has(methodName(method) ?? ''))
  39. const found = new Set(methods.map(method => methodName(method)))
  40. const missing = allowed.filter(name => !found.has(name))
  41. if (missing.length > 0) {
  42. throw new Error(
  43. `gen-cordis-inspect-catalog: Client Service ${JSON.stringify(key)}`
  44. + ` is missing allowlisted method(s): ${missing.join(', ')}`,
  45. )
  46. }
  47. return { ...service, methods }
  48. })
  49. const events = [...CLIENT_EVENTS].map((name) => {
  50. const matches = model.events.filter(event => event.name === name)
  51. if (matches.length !== 1) {
  52. throw new Error(`gen-cordis-inspect-catalog: expected one Client Event ${JSON.stringify(name)}, found ${matches.length}`)
  53. }
  54. return matches[0] as (typeof matches)[number]
  55. })
  56. return {
  57. services,
  58. events,
  59. }
  60. }
  61. function main(): void {
  62. const { projector, model } = projectCordisCatalog(root, {
  63. ...CORDIS_CATALOG_POLICY,
  64. runtimeDeclarationMaxChars: 4_096,
  65. }, 'client')
  66. const destination = resolve(root, CLIENT_OUT)
  67. const source = projector.renderRuntimeApi(clientModel(model))
  68. .replace('Generated by scripts/gen-cordis-api.ts', 'Generated by scripts/gen-cordis-inspect-catalog.ts')
  69. .replaceAll('pnpm run gen-cordis-api', 'pnpm run gen-cordis-inspect-catalog')
  70. .replaceAll('pnpm run verify-cordis-api', 'pnpm run verify-cordis-inspect-catalog')
  71. .replaceAll('@deepseek-ai/dsh-tool-cordis/api-catalog', '@deepseek-ai/dsh-cordis-client-runner/client/api-catalog')
  72. if (process.argv.includes('--check')) {
  73. if (!existsSync(destination) || readFileSync(destination, 'utf8') !== source) {
  74. throw new Error(`gen-cordis-inspect-catalog: ${CLIENT_OUT} is stale; run pnpm run gen-cordis-inspect-catalog`)
  75. }
  76. console.log(`gen-cordis-inspect-catalog: ${CLIENT_OUT} is up to date`)
  77. return
  78. }
  79. mkdirSync(dirname(destination), { recursive: true })
  80. writeFileSync(destination, source)
  81. console.log(`gen-cordis-inspect-catalog: wrote ${CLIENT_OUT}`)
  82. }
  83. main()