@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
JavaScript
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