UNPKG

@adonis-agora/agent

Version:

Governed, durable-backed AI agent (chat + tool-calling + governance) for AdonisJS — part of the Agora ecosystem.

123 lines • 5.9 kB
import { builtinJsonSchemaValidator, toJsonSchema, validateProps, validatePropsSync, } from './schema.js'; /** Component names: an identifier a registry key, a tool name and a snake_case slug can all be derived from. */ export const COMPONENT_NAME = /^[A-Za-z][A-Za-z0-9]{0,63}$/; /** Declare a component. Validates the name and returns the definition unchanged (typed). */ export function defineComponent(definition, extras) { if (extras?.channels !== undefined) { definition = { ...definition, channels: { ...definition.channels, ...extras.channels } }; } if (!COMPONENT_NAME.test(definition.name)) { throw new Error(`genui: component name "${definition.name}" must be letters and digits, starting with a letter (e.g. DataTable)`); } if (definition.version !== undefined && (!Number.isSafeInteger(definition.version) || definition.version <= 0)) throw new RangeError('genui: component version must be a positive safe integer'); if (definition.fallbackText !== undefined && typeof definition.fallbackText !== 'function') throw new TypeError('genui: fallbackText must be a function'); if (definition.streaming !== undefined && definition.streaming !== 'partial' && definition.streaming !== 'complete') throw new TypeError("genui: streaming must be 'partial' or 'complete'"); if (definition.partialProps !== undefined && typeof definition.partialProps !== 'function') throw new TypeError('genui: partialProps must be a function'); if (definition.check !== undefined && typeof definition.check !== 'function') throw new TypeError('genui: check must be a function'); if (definition.channels !== undefined) { if (typeof definition.channels !== 'object' || definition.channels === null) throw new TypeError('genui: channels must be an object of conversions'); for (const [channel, conversion] of Object.entries(definition.channels)) { if (conversion === undefined || conversion === false || typeof conversion === 'function') continue; const spec = conversion; if (typeof spec !== 'object' || spec === null || typeof spec.convert !== 'function' || (spec.describe !== undefined && typeof spec.describe !== 'string' && typeof spec.describe !== 'function')) throw new TypeError(`genui: channels.${channel} must be a function, { convert, describe? } or false`); } } return definition; } /** * Collect component definitions into a {@link Catalog}. Names must be unique; to override a * definition (e.g. a builtin), use {@link Catalog.extend}. */ export function defineCatalog(components, options = {}) { const byName = new Map(); for (const component of components) { defineComponent(component); if (byName.has(component.name)) { throw new Error(`genui: component "${component.name}" is defined twice in one catalog`); } byName.set(component.name, component); } return buildCatalog(byName, options.jsonSchemaValidator ?? builtinJsonSchemaValidator); } function buildCatalog(byName, validator) { const list = Object.freeze([...byName.values()]); return { components: list, validator, get: (name) => byName.get(name), has: (name) => byName.has(name), modelComponents: () => list.filter((component) => component.internal !== true), async validate(component, props) { const definition = byName.get(component); if (definition === undefined) { return { ok: false, issues: [{ path: [], message: `unknown component "${component}"` }], }; } const result = await validateProps(definition.outputProps ?? definition.props, props, validator); return result; }, validateSync(component, props) { const definition = byName.get(component); if (definition === undefined) { return { ok: false, issues: [{ path: [], message: `unknown component "${component}"` }], }; } return validatePropsSync(definition.outputProps ?? definition.props, props, validator); }, jsonSchemaFor(component) { const definition = byName.get(component); return definition === undefined ? undefined : toJsonSchema(definition.outputProps ?? definition.props); }, extend(more) { const next = new Map(byName); for (const component of more) { defineComponent(component); next.set(component.name, component); } return buildCatalog(next, validator); }, }; } /** * The model components that stand on their own: every one but a layout (`children: true`). Outside * tree mode nothing can be nested, so a layout could only be pushed empty — it gets no * `ui__show_*` tool and is not offered by the generic show tool. */ export function standaloneComponents(catalog) { return catalog.modelComponents().filter((component) => component.children !== true); } /** `DataTable` → `data_table`, `KPICards` → `kpi_cards`. */ export function toSnakeCase(name) { return name .replace(/([a-z0-9])([A-Z])/g, '$1_$2') .replace(/([A-Z]+)([A-Z][a-z])/g, '$1_$2') .replace(/[^a-zA-Z0-9]+/g, '_') .toLowerCase(); } /** The per-component tool name: `ui__show_data_table` with the default prefix. */ export function toolNameFor(component, prefix = 'ui__show_') { return `${prefix}${toSnakeCase(component)}`; } //# sourceMappingURL=catalog.js.map