agentlang
Version:
The easiest way to build the most reliable AI agents - enterprise-grade teams of AI agents that collaborate with each other and humans
237 lines (212 loc) • 8.06 kB
text/typescript
import chalk from 'chalk';
import { Command } from 'commander';
import { AgentlangLanguageMetaData } from '../language/generated/module.js';
import { createAgentlangServices } from '../language/agentlang-module.js';
import {
ApplicationSpec,
internModule,
load,
loadAppConfig,
loadCoreModules,
runStandaloneStatements,
} from '../runtime/loader.js';
import { NodeFileSystem } from 'langium/node';
import { extractDocument } from '../runtime/loader.js';
import * as url from 'node:url';
import * as fs from 'node:fs/promises';
import * as path from 'node:path';
import { logger } from '../runtime/logger.js';
import { Module } from '../runtime/module.js';
import { ModuleDefinition } from '../language/generated/ast.js';
import { Config } from '../runtime/state.js';
import { prepareIntegrations } from '../runtime/integrations.js';
import { isExecGraphEnabled, isNodeEnv } from '../utils/runtime.js';
import { OpenAPIClientAxios } from 'openapi-client-axios';
import { registerOpenApiModule } from '../runtime/openapi.js';
import { initDatabase, resetDefaultDatabase } from '../runtime/resolvers/sqldb/database.js';
import { runInitFunctions } from '../runtime/util.js';
import { startServer } from '../api/http.js';
import { enableExecutionGraph } from '../runtime/exec-graph.js';
import { importModule } from '../runtime/jsmodules.js';
import {
isRuntimeMode_dev,
isRuntimeMode_prod,
setRuntimeMode_generate_migration,
setRuntimeMode_init_schema,
setRuntimeMode_migration,
setRuntimeMode_prod,
setRuntimeMode_undo_migration,
} from '../runtime/defs.js';
import { initGlobalApi } from '../runtime/api.js';
const __dirname = url.fileURLToPath(new URL('.', import.meta.url));
const packagePath = path.resolve(__dirname, '..', '..', 'package.json');
const packageContent = await fs.readFile(packagePath, 'utf-8');
export type GenerateOptions = {
destination?: string;
};
export default function (): void {
const program = new Command();
program.version(JSON.parse(packageContent).version);
const fileExtensions = AgentlangLanguageMetaData.fileExtensions.join(', ');
program
.command('run')
.argument('<file>', `source file (possible file extensions: ${fileExtensions})`)
.option('-c, --config <config>', 'configuration file')
.description('Loads and runs an agentlang module')
.action(runModule);
program
.command('parseAndValidate')
.argument('<file>', `source file (possible file extensions: ${fileExtensions})`)
.option('-d, --destination <dir>', 'destination directory of generating')
.description('Parses and validates an Agentlang module')
.action(parseAndValidate);
program
.command('initSchema')
.argument('<file>', `source file (possible file extensions: ${fileExtensions})`)
.option('-c, --config <config>', 'configuration file')
.description('Initialize database schema')
.action(initSchema);
program
.command('runMigrations')
.argument('<file>', `source file (possible file extensions: ${fileExtensions})`)
.option('-c, --config <config>', 'configuration file')
.description('Automatically perform migration of the schema')
.action(runMigrations);
program
.command('undoLastMigration')
.argument('<file>', `source file (possible file extensions: ${fileExtensions})`)
.option('-c, --config <config>', 'configuration file')
.description('Revoke the last migration')
.action(undoLastMigration);
program
.command('generateMigration')
.argument('<file>', `source file (possible file extensions: ${fileExtensions})`)
.option('-c, --config <config>', 'configuration file')
.description('Generate migration script')
.action(generateMigration);
program.parse(process.argv);
}
/**
* Parse and validate a program written in our language.
* Verifies that no lexer or parser errors occur.
* Implicitly also checks for validation errors while extracting the document
*
* @param fileName Program to validate
*/
export const parseAndValidate = async (fileName: string): Promise<void> => {
// retrieve the services for our language
const services = createAgentlangServices(NodeFileSystem).Agentlang;
// extract a document for our program
const document = await extractDocument(fileName, services);
// extract the parse result details
const parseResult = document.parseResult;
// verify no lexer, parser, or general diagnostic errors show up
if (parseResult.lexerErrors.length === 0 && parseResult.parserErrors.length === 0) {
console.log(chalk.green(`Parsed and validated ${fileName} successfully!`));
} else {
console.log(chalk.red(`Failed to parse and validate ${fileName}!`));
}
};
export async function runPostInitTasks(appSpec?: ApplicationSpec, config?: Config) {
await initDatabase(config?.store);
await importModule('../runtime/api.js', 'agentlang');
await runInitFunctions();
await runStandaloneStatements();
if (appSpec && (isRuntimeMode_dev() || isRuntimeMode_prod()))
startServer(appSpec, config?.service?.port || 8080, config?.service?.host, config);
}
let execGraphEnabled = false;
export async function runPreInitTasks(): Promise<boolean> {
initGlobalApi();
if (!execGraphEnabled && isExecGraphEnabled()) {
enableExecutionGraph();
execGraphEnabled = true;
}
let result: boolean = true;
await loadCoreModules().catch((reason: any) => {
const msg = `Failed to load core modules - ${reason.toString()}`;
logger.error(msg);
console.log(chalk.red(msg));
result = false;
});
return result;
}
export const runModule = async (fileName: string, releaseDb: boolean = false): Promise<void> => {
if (isRuntimeMode_dev() && process.env.NODE_ENV === 'production') {
setRuntimeMode_prod();
}
const r: boolean = await runPreInitTasks();
if (!r) {
throw new Error('Failed to initialize runtime');
}
const configDir =
path.dirname(fileName) === '.' ? process.cwd() : path.resolve(process.cwd(), fileName);
const config: Config = await loadAppConfig(configDir);
if (config.integrations) {
await prepareIntegrations(
config.integrations.host,
config.integrations.username,
config.integrations.password,
config.integrations.connections
);
}
if (config.openapi) {
await loadOpenApiSpec(config.openapi);
}
try {
await load(fileName, undefined, async (appSpec?: ApplicationSpec) => {
await runPostInitTasks(appSpec, config);
});
} catch (err: any) {
if (isNodeEnv && chalk) {
console.error(chalk.red(err));
} else {
console.error(err);
}
} finally {
if (releaseDb === true) {
resetDefaultDatabase();
}
}
};
async function initSchema(fileName: string) {
setRuntimeMode_init_schema();
await runModule(fileName, true);
}
async function runMigrations(fileName: string) {
setRuntimeMode_migration();
await runModule(fileName, true);
}
async function undoLastMigration(fileName: string) {
setRuntimeMode_undo_migration();
await runModule(fileName, true);
}
async function generateMigration(fileName: string) {
setRuntimeMode_generate_migration();
await runModule(fileName, true);
}
export async function internAndRunModule(
module: ModuleDefinition,
appSpec?: ApplicationSpec
): Promise<Module> {
const r: boolean = await runPreInitTasks();
if (!r) {
throw new Error('Failed to initialize runtime');
}
const rm: Module = await internModule(module);
await runPostInitTasks(appSpec);
return rm;
}
async function loadOpenApiSpec(openApiConfig: any[]) {
for (let i = 0; i < openApiConfig.length; ++i) {
const cfg: any = openApiConfig[i];
const api = new OpenAPIClientAxios({ definition: cfg.specUrl });
await api.init();
const client = await api.getClient();
client.defaults.baseURL = cfg.baseUrl
? cfg.baseUrl
: cfg.specUrl.substring(0, cfg.specUrl.lastIndexOf('/'));
const n = await registerOpenApiModule(cfg.name, { api: api, client: client });
logger.info(`OpenAPI module '${n}' registered`);
}
}