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
391 lines (371 loc) • 12.9 kB
JavaScript
import { default as ai } from './ai.js';
import { default as auth } from './auth.js';
import { default as files } from './files.js';
import { DefaultModuleName, DefaultModules, escapeSpecialChars, isString, restoreSpecialChars, } from '../util.js';
import { isInstanceOfType, makeInstance, newInstanceAttributes } from '../module.js';
import { Environment, evaluate, evaluateStatements, parseAndEvaluateStatement, restartFlow, } from '../interpreter.js';
import { logger } from '../logger.js';
import { parseModule, parseStatements } from '../../language/parser.js';
import { FlowSuspensionTag, ForceReadPermFlag, PathAttributeName } from '../defs.js';
import { getMonitor, getMonitorsForEvent } from '../monitor.js';
const CoreModuleDefinition = `module ${DefaultModuleName}
import "./modules/core.js" Core
entity timer {
name String ,
duration Int,
unit ,
trigger String,
status // Inited, Cancelled, Running
}
entity auditlog {
id UUID ,
action , // Create, Delete, Update
resource String, // __path__
timestamp DateTime ,
previous_value Any ,
user String,
token String
}
entity suspension {
id UUID ,
continuation String[], // rest of the patterns to execute
env Any, // serialized environment-object
createdOn DateTime ,
createdBy String
}
entity activeSuspension {
id UUID
}
resolver suspensionResolver ["${DefaultModuleName}/activeSuspension"] {
query Core.lookupActiveSuspension
}
workflow createSuspension {
{suspension
{id createSuspension.id
continuation createSuspension.continuation,
env createSuspension.env,
createdBy createSuspension.createdBy}}
}
workflow restartSuspension {
await Core.restartSuspension(restartSuspension.id, restartSuspension.data)
}
entity Monitor {
id String ,
eventInstance Any,
eventName String ,
user String ,
totalLatencyMs Int,
data String
}
event fetchEventMonitor {
eventName String
}
workflow fetchEventMonitor {
{Monitor {eventName? fetchEventMonitor.eventName}} [m]
Core.eventMonitorData(m)
}
event fetchEventMonitors {
eventName String,
limit Int ,
offset Int
}
workflow fetchEventMonitors {
{Monitor {eventName? fetchEventMonitors.eventName}} result
Core.eventMonitorsData(result, fetchEventMonitors.limit, fetchEventMonitors.offset)
}
event EventMonitor {
id String
}
workflow EventMonitor {
{Monitor {id? EventMonitor.id}} [m];
Core.eventMonitorData(m)
}
record ValidationRequest {
data Any
}
record ValidationResult {
status ,
reason String
}
event validateModule extends ValidationRequest {
}
workflow validateModule {
await Core.validateModule(validateModule.data)
}
entity Migration {
appVersion String ,
ups String ,
downs String
}
`;
export const CoreModules = [];
export function registerCoreModules() {
DefaultModules.add(DefaultModuleName);
CoreModules.push(CoreModuleDefinition);
[auth, ai, files].forEach((mdef) => {
CoreModules.push(mdef);
DefaultModules.add(mdef);
});
}
export function setTimerRunning(timerInst) {
timerInst.attributes.set('status', 'R');
}
export async function maybeCancelTimer(name, timer, env) {
await parseAndEvaluateStatement(`{agentlang/timer {name? "${name}"}}`, undefined, env).then((result) => {
if (result === null || (result instanceof Array && result.length == 0)) {
clearInterval(timer);
}
});
}
async function addAudit(env, action, resource, previuos_value) {
const user = env.getActiveUser();
const token = env.getActiveToken();
const newEnv = new Environment('auditlog', env).setInKernelMode(true);
newEnv.bind(ForceReadPermFlag, true);
const r = await parseAndEvaluateStatement(`{agentlang/auditlog {
action "${action}",
resource "${resource}",
previous_value "${previuos_value ? escapeSpecialChars(JSON.stringify(previuos_value.asObject())) : ''}",
user "${user}",
token "${token ? token : ''}"
}}`, user, newEnv);
if (!isInstanceOfType(r, 'agentlang/auditlog')) {
logger.warn(`Failed to create auditlog for action ${action} and resource ${resource} for user ${user}`);
}
}
export async function addCreateAudit(resource, env) {
await addAudit(env, 'c', resource);
}
export async function addDeleteAudit(resource, previous_value, env) {
await addAudit(env, 'd', resource, previous_value);
}
export async function addUpdateAudit(resource, previous_value, env) {
await addAudit(env, 'u', resource, previous_value);
}
export async function createSuspension(suspId, continuation, env) {
const user = env.getActiveUser();
const newEnv = new Environment('susp', env).setInKernelMode(true);
const envObj = env.asSerializableObject();
const inst = makeInstance('agentlang', 'createSuspension', newInstanceAttributes()
.set('id', suspId)
.set('continuation', continuation)
.set('env', envObj)
.set('createdBy', user));
const r = await evaluate(inst, undefined, newEnv);
if (!isInstanceOfType(r, 'agentlang/suspension')) {
logger.warn(`Failed to create suspension for user ${user}`);
return undefined;
}
return r.lookup('id');
}
function isFlowSuspension(cont) {
return cont.length > 0 && cont[0] == FlowSuspensionTag;
}
async function loadSuspension(suspId, env) {
const newEnv = new Environment('auditlog', env).setInKernelMode(true);
const r = await parseAndEvaluateStatement(`{agentlang/suspension {id? "${suspId}"}}`, undefined, newEnv);
if (r instanceof Array && r.length > 0) {
const inst = r[0];
const cont = inst.lookup('continuation');
const ifs = isFlowSuspension(cont);
const stmts = ifs ? new Array() : await parseStatements(cont);
const envStr = inst.lookup('env');
const suspEnv = Environment.FromSerializableObject(JSON.parse(envStr));
return {
continuation: stmts,
env: suspEnv,
flowContext: ifs ? cont : undefined,
};
}
return undefined;
}
async function deleteSuspension(suspId, env) {
try {
await parseAndEvaluateStatement(`purge {agentlang/suspension {id? "${suspId}"}}`, undefined, env);
return suspId;
}
catch (err) {
logger.warn(`Failed to delete suspension ${suspId} - ${err}`);
return undefined;
}
}
export async function restartSuspension(suspId, userData, env) {
const susp = await loadSuspension(suspId, env);
if (susp) {
if (susp.flowContext) {
await restartFlow(susp.flowContext, userData, susp.env);
}
else {
susp.env.bindSuspensionUserData(userData);
await evaluateStatements(susp.continuation, susp.env);
}
await deleteSuspension(suspId, env);
return susp.env.getLastResult();
}
else {
logger.warn(`Suspension ${suspId} not found`);
return undefined;
}
}
export async function lookupActiveSuspension(resolver, inst, queryAll) {
if (!queryAll) {
const data = inst.lookupQueryVal(PathAttributeName).split('/')[1];
if (data) {
const parts = data.split(':');
const id = parts[0];
const userData = parts[1];
return await restartSuspension(id, userData, resolver.getEnvironment());
}
else {
return [];
}
}
else {
return [];
}
}
export async function flushMonitoringData(monitorId) {
const m = getMonitor(monitorId);
try {
if (m) {
const data = btoa(JSON.stringify(m.asObject()));
const inst = m.getEventInstance();
const eventInstance = inst ? btoa(JSON.stringify(inst.asSerializableObject())) : '';
const user = m.getUser() || 'admin';
const latency = m.getTotalLatencyMs();
const env = new Environment(`monitor-${monitorId}-env`);
const eventName = inst ? inst.getFqName() : monitorId;
await parseAndEvaluateStatement(`{agentlang/Monitor {id "${monitorId}", eventName "${eventName}", eventInstance "${eventInstance}", user "${user}", totalLatencyMs ${latency}, data "${data}"}}`, undefined, env);
await env.commitAllTransactions();
}
else {
logger.warn(`Failed to locate monitor with id ${monitorId}`);
}
}
catch (reason) {
logger.error(`Failed to flush monitor ${monitorId} - ${reason}`);
}
}
export async function fetchLatestMonitorForEvent(eventName) {
const monitors = getMonitorsForEvent(eventName);
const len = monitors.length;
if (len > 0) {
return [monitors[len - 1].asObject()];
}
return [];
}
export async function fetchMonitorsForEvent(eventName, limit, offset) {
const monitors = getMonitorsForEvent(eventName);
const r = limit === 0 ? monitors : monitors.slice(offset, offset + limit);
if (r.length > 0) {
return r.map((m) => {
return m.asObject();
});
}
return [];
}
export function eventMonitorData(inst) {
if (inst)
return JSON.parse(atob(inst.lookup('data')));
else
return null;
}
export function eventMonitorsData(insts, limit, offset) {
if (insts) {
if (limit !== undefined && offset !== undefined) {
insts = limit === 0 ? insts : insts.slice(offset, offset + limit);
}
return insts.map((inst) => {
return eventMonitorData(inst);
});
}
else
return null;
}
export async function validateModule(moduleDef) {
try {
if (isString(moduleDef)) {
await parseModule(moduleDef);
return makeInstance('agentlang', 'ValidationResult', newInstanceAttributes().set('status', 'ok'));
}
else {
const xs = Object.entries(moduleDef);
for (let i = 0; i < xs.length; ++i) {
const x = xs[i][1];
if (isString(x) && x.trimStart().startsWith('module')) {
return await validateModule(x);
}
}
throw new Error(`no module definitions found in object`);
}
}
catch (reason) {
return makeInstance('agentlang', 'ValidationResult', newInstanceAttributes().set('status', 'error').set('reason', `${reason}`));
}
}
const SqlSep = ';\n\n';
export async function saveMigration(version, ups, downs) {
try {
const env = new Environment(`migrations-${version}-env`);
await parseAndEvaluateStatement(`purge {agentlang/Migration {appVersion? "${version}"}}`, undefined, env);
let ups_str = '';
if (ups) {
ups_str = escapeSpecialChars(ups
.map((s) => {
return s.trim();
})
.join(SqlSep));
}
let downs_str = '';
if (downs) {
downs_str = escapeSpecialChars(downs
.map((s) => {
return s.trim();
})
.join(SqlSep));
}
const inst = await parseAndEvaluateStatement(`{agentlang/Migration {
appVersion "${version}",
ups "${ups_str}",
downs "${downs_str}"}}`);
if (isInstanceOfType(inst, 'agentlang/Migration') && inst.lookup('appVersion') === version) {
await env.commitAllTransactions();
return true;
}
else {
logger.warn(`Failed to save migration for version ${version}`);
}
}
catch (reason) {
logger.error(`Failed to save migration for version ${version} - ${reason}`);
}
return false;
}
export async function loadMigration(version) {
try {
const env = new Environment(`migrations-${version}-env`);
const insts = await parseAndEvaluateStatement(`{agentlang/Migration {appVersion? "${version}"}}`, undefined, env);
if (insts && insts.length > 0) {
return insts[0];
}
}
catch (reason) {
logger.error(`Failed to lookup migration for version ${version} - ${reason}`);
}
return undefined;
}
export function migrationUps(inst) {
const ups = inst.lookup('ups');
if (ups) {
return restoreSpecialChars(ups).split(SqlSep);
}
return undefined;
}
export function migrationDowns(inst) {
const downs = inst.lookup('downs');
if (downs) {
return restoreSpecialChars(downs).split(SqlSep);
}
return undefined;
}
//# sourceMappingURL=core.js.map