/** * Schema Registry * * Collects plugin manifest schemas and combines them for manifest generation. * Provides a unified view of all plugin state schemas and capabilities. */ import type { PluginManifestSchema, JSONSchema, PluginCapabilities, SliceUIMetadata, } from "./manifest-schema.ts"; /** * Registry of all plugin manifest schemas * * Used by the manifest route to generate the GizmoManifest from static schemas * instead of inferring from runtime state. */ export interface SchemaRegistry { /** Map of plugin key to manifest schema */ schemas: Map; /** * Get combined state schema for all plugins * * Returns a JSON Schema with each plugin's state as a property * and plugin schemas in $defs. */ getStateSchema(): JSONSchema; /** * Get combined capabilities from all plugins */ getCapabilities(): PluginCapabilities; /** * Get UI metadata for all slices */ getSliceMetadata(): Record; /** * Get all route paths from plugins */ getRoutes(): Record; /** * Check if a plugin schema is registered */ hasSchema(pluginKey: string): boolean; /** * Get list of all registered slice keys */ getSliceKeys(): string[]; } /** * Create a schema registry from plugin manifest schemas * * @param schemas - Array of plugin manifest schemas * @returns SchemaRegistry instance * * @example * ```typescript * import { createSchemaRegistry } from "@gizmo-ai/server"; * import { AgentManifestSchema } from "@gizmo-ai/agent-plugin"; * import { HITLManifestSchema } from "@gizmo-ai/hitl-plugin"; * * const registry = createSchemaRegistry([ * AgentManifestSchema, * HITLManifestSchema, * ]); * * const stateSchema = registry.getStateSchema(); * const capabilities = registry.getCapabilities(); * ``` */ export function createSchemaRegistry( schemas: PluginManifestSchema[] ): SchemaRegistry { const schemaMap = new Map(); for (const schema of schemas) { schemaMap.set(schema.pluginKey, schema); } return { schemas: schemaMap, getStateSchema(): JSONSchema { const properties: Record = {}; const $defs: Record = {}; for (const [key, schema] of schemaMap) { if (schema.stateSchema) { // Reference the definition from properties properties[key] = { $ref: `#/$defs/${key}` }; $defs[key] = schema.stateSchema; } } return { type: "object", properties, $defs, }; }, getCapabilities(): PluginCapabilities { const features = new Set(); const tools = new Set(); let streaming = false; for (const schema of schemaMap.values()) { if (schema.capabilities) { if (schema.capabilities.features) { for (const feature of schema.capabilities.features) { features.add(feature); } } if (schema.capabilities.tools) { for (const tool of schema.capabilities.tools) { tools.add(tool); } } if (schema.capabilities.streaming) { streaming = true; } } } const result: PluginCapabilities = {}; if (features.size > 0) { result.features = Array.from(features); } if (tools.size > 0) { result.tools = Array.from(tools); } if (streaming) { result.streaming = true; } return result; }, getSliceMetadata(): Record { const metadata: Record = {}; for (const [key, schema] of schemaMap) { if (schema.uiMetadata) { metadata[key] = schema.uiMetadata; } } return metadata; }, getRoutes(): Record { const routes: Record = {}; for (const [key, schema] of schemaMap) { if (schema.routes && schema.routes.length > 0) { routes[key] = schema.routes; } } return routes; }, hasSchema(pluginKey: string): boolean { return schemaMap.has(pluginKey); }, getSliceKeys(): string[] { return Array.from(schemaMap.keys()); }, }; }