@convo-lang/convo-lang
Version:
The language of AI
1,118 lines • 42.9 kB
JavaScript
import { getDirectoryName, getErrorMessage, getValueByAryPath, isPromise, isRooted, joinPaths, normalizePath, valueIsZodObject, zodCoerceObject } from '@iyio/common';
import { parseJson5 } from '@iyio/json5';
import { Conversation } from './Conversation.js';
import { ConvoError } from './ConvoError.js';
import { parseConvoType } from './convo-cached-parsing.js';
import { defaultConvoVars, sandboxConvoVars } from "./convo-default-vars.js";
import { convoArgsName, convoBodyFnName, convoFunctions, convoGlobalRef, convoLabeledScopeFnParamsToObj, convoMapFnName, convoStructFnName, convoTags, convoVars, createConvoScopeFunction, createOptionalConvoValue, defaultConvoPrintFunction, escapeConvo, getConvoSystemMessage, getConvoTag, isConvoScopeFunction, parseConvoJsonMessage, setConvoScopeError } from './convo-lib.js';
import { doesConvoContentHaveMessage } from './convo-parser.js';
import { convoFlowControllerKey, convoMessageSourcePathKey, convoScopeFnDefKey, convoScopeFnKey, convoScopeLocationMsgKey, convoScopeMsgKey, isConvoMessageModification } from "./convo-types.js";
import { convoValueToZodType } from './convo-zod.js';
const argsCacheKey = Symbol('argsCacheKey');
const returnCacheKey = Symbol('returnCacheKey');
export const executeConvoFunction = (fn, args = {}, message) => {
const exe = new ConvoExecutionContext();
const r = exe.executeFunction(fn, args, message);
return r.valuePromise ?? r.value;
};
const createDefaultScope = (vars) => {
return {
_d: true,
vars,
i: 0,
s: { s: 0, e: 0 },
};
};
const copyDefaultScope = (scope) => {
if (scope._d) {
scope = { ...scope };
delete scope._d;
}
return scope;
};
export class ConvoExecutionContext {
constructor(convo, parentConvo) {
this.nextSuspendId = 1;
this.suspendedScopes = {};
this.sharedSetters = [];
this.print = defaultConvoPrintFunction;
this.defaultThrowOnUndefined = false;
this.disableInlinePrompts = false;
this.maxInlinePromptDepth = 10;
this.isReadonly = 0;
this.convo = {
...convo,
exe: this,
convoPipeSink: convo?.convoPipeSink ?? ((value) => {
this.print('CONVO_PIPE <<', value);
})
};
this.sharedVars = { ...(parentConvo?.sandboxMode ? sandboxConvoVars : defaultConvoVars), [convoGlobalRef]: this.convo };
this.parentConvo = parentConvo;
}
getUserSharedVars() {
const vars = { ...this.sharedVars };
delete vars['convo'];
delete vars['graphCtrl'];
delete vars['evalJavascript'];
for (const e in defaultConvoVars) {
delete vars[e];
}
return vars;
}
getUserSharedVarsExcludeTypes() {
const vars = this.getUserSharedVars();
for (const e in vars) {
if (e[0] === e[0]?.toUpperCase() || (typeof vars[e] === 'function')) {
delete vars[e];
}
}
return vars;
}
loadFunctions(messages, externFunctions) {
for (const msg of messages) {
if (msg.fn && !msg.fn.call && !msg.fn.topLevel) {
this.setVar(true, createConvoScopeFunction({
usesLabels: true,
catchReturn: true,
sourceFn: msg.fn
}, (scope, ctx) => {
if (msg.fn?.body) {
const r = this.executeFunction(msg.fn, convoLabeledScopeFnParamsToObj(scope, msg.fn.params), msg, { locationOverride: scope[convoScopeLocationMsgKey] });
return r.valuePromise ?? r.value;
}
else {
const externFn = externFunctions?.[msg.fn?.name ?? ''];
if (!externFn) {
setConvoScopeError(scope, `No extern function provided for ${msg.fn?.name}`);
return;
}
return externFn(scope, ctx);
}
}), msg.fn.name);
}
}
if (externFunctions) {
for (const e in externFunctions) {
const fn = externFunctions[e];
if (!fn || this.sharedVars[e] !== undefined) {
continue;
}
this.setVar(true, createConvoScopeFunction(fn), e);
}
}
}
clearSharedSetters() {
this.sharedSetters.splice(0, this.sharedSetters.length);
}
executeStatement(statement, message) {
const vars = {};
const scope = {
i: 0,
vars,
s: statement,
};
if (message) {
scope[convoScopeMsgKey] = message;
}
return this.execute(scope, vars);
}
executeFunction(fn, args = {}, message, options) {
if (fn.call) {
throw new ConvoError('proxy-call-not-supported', { fn }, 'executeFunction does not support proxy calls. Use executeFunctionAsync instead');
}
const scheme = this.getConvoFunctionArgsScheme(fn);
let parsed = scheme.safeParse(args);
if (parsed.success === false) {
const r = zodCoerceObject(scheme, args);
if (r.result) {
parsed = { data: r.result, success: true };
}
else {
throw new ConvoError('invalid-args', { fn }, `Invalid args passed to convo function. fn = ${fn.name}, message = ${parsed.error.message}`);
}
}
args = parsed.data;
const vars = {
[convoArgsName]: args,
};
let scope;
if (fn.body) {
scope = {
i: 0,
vars,
[convoScopeFnDefKey]: fn,
s: {
fn: convoBodyFnName,
s: 0, e: 0,
params: options?.handlerName ? [
{
fn: options.handlerName,
s: 0, e: 0,
params: [
{
fn: convoFunctions.mapWithCapture,
s: 0, e: 0,
params: options.handlerHead ? [
{
s: 0, e: 0,
label: options.handlerHeadName ?? 'name',
value: options.handlerHead
},
...fn.body
] : fn.body,
}
]
}
] : fn.body,
},
};
for (const e in args) {
this.setVar(false, args[e], e, undefined, scope);
}
if (scheme.shape) {
for (const e in scheme.shape) {
if (vars[e] === undefined) {
this.setVar(false, undefined, e, undefined, scope);
}
}
}
}
else {
if (typeof this.sharedVars[fn.name] !== 'function') {
throw new ConvoError('function-not-defined', { fn }, `No function defined by name ${fn.name}`);
}
const params = [];
for (const e in args) {
params.push({
s: 0,
e: 0,
label: e,
value: args[e]
});
}
scope = {
i: 0,
vars,
s: {
fn: fn.name,
params,
s: 0,
e: 0,
},
};
}
if (message) {
scope[convoScopeMsgKey] = message;
}
if (options?.locationOverride) {
scope[convoScopeLocationMsgKey] = options.locationOverride;
}
return this.execute(scope, vars, this.getConvoFunctionReturnScheme(fn));
}
async executeFunctionAsync(fn, args = {}, message) {
const result = await this.executeFunctionResultAsync(fn, args, message);
if (result.valuePromise) {
return await result.valuePromise;
}
else {
return result.value;
}
}
async executeFunctionResultAsync(fn, args = {}, message) {
if (fn.call) {
const v = this.sharedVars[fn.name];
const callee = v?.[convoFlowControllerKey]?.sourceFn;
if (!callee && isConvoScopeFunction(v)) {
args = await this.paramsToObjAsync(fn.params, message);
const paramValues = [];
const labels = {};
for (const e in args) {
const value = args[e];
labels[e] = paramValues.length;
paramValues.push(value);
}
const scope = {
i: 0,
s: {
s: 0,
e: 0,
fn: fn.name,
},
vars: {
[convoArgsName]: args,
},
paramValues,
labels,
[convoScopeFnKey]: v,
[convoScopeFnDefKey]: fn,
};
const r = v(scope, this);
const isP = isPromise(r);
return {
scope,
value: isP ? undefined : r,
valuePromise: isP ? r : undefined,
};
}
if (!callee) {
// add exception for "responseWithText" function
throw new ConvoError('function-not-defined', { fn }, `executeFunctionResultAsync - No function defined by the name ${fn.name}`);
}
args = await this.paramsToObjAsync(fn.params, message);
fn = callee;
}
return this.executeFunction(fn, args, message);
}
getConvoFunctionArgsValue(fn, message) {
const r = this.paramsToObj(fn.params ?? [], message);
if (r.valuePromise) {
throw new ConvoError('function-call-args-suspended');
}
return r.value;
}
getConvoFunctionArgsScheme(fn, cache = true) {
if (cache) {
const s = fn[argsCacheKey];
if (s) {
return s;
}
}
let scheme;
if (fn.paramType) {
const type = this.getVarAsType(fn.paramType);
if (!type) {
throw new ConvoError('function-args-type-not-defined', { fn });
}
if (!valueIsZodObject(type)) {
throw new ConvoError('function-args-type-not-an-object', { fn });
}
scheme = type;
}
else {
scheme = this.paramsToScheme(fn.params ?? []);
}
if (cache) {
fn[argsCacheKey] = scheme;
}
return scheme;
}
getConvoFunctionReturnScheme(fn, cache = true) {
if (!fn.returnType) {
return undefined;
}
if (cache) {
const s = fn[returnCacheKey];
if (s) {
return s;
}
}
const typeVar = this.sharedVars[fn.returnType];
if (!typeVar) {
throw new ConvoError('function-return-type-not-defined', { fn }, `Function return type not defined. function = ${fn.name}, returnType = ${fn.returnType}`);
}
const scheme = convoValueToZodType(typeVar);
if (cache) {
fn[returnCacheKey] = scheme;
}
return scheme;
}
getVarAsType(name) {
const typeVar = this.sharedVars[name];
if (!typeVar) {
return undefined;
}
return convoValueToZodType(typeVar);
}
paramsToObj(params, message) {
const vars = {};
const scope = this.executeScope({
i: 0,
vars,
s: {
fn: convoMapFnName,
params,
s: 0,
e: 0,
}
}, undefined, createDefaultScope(vars));
if (message) {
scope[convoScopeMsgKey] = message;
}
return this.execute(scope, vars);
}
async paramsToObjAsync(params, message) {
const r = this.paramsToObj(params, message);
if (r.valuePromise) {
return await r.valuePromise;
}
else {
return r.value;
}
}
paramsToScheme(params) {
const vars = {};
const scope = this.executeScope({
i: 0,
vars,
s: {
fn: convoStructFnName,
params,
s: 0,
e: 0,
}
}, undefined, createDefaultScope(vars));
if (scope.si) {
throw new ConvoError('suspended-scheme-statements-not-supported', { statements: params }, 'scheme statements should not be suspended');
}
const zType = convoValueToZodType(scope.v);
if (!valueIsZodObject(zType)) {
throw new ConvoError('zod-object-expected', { statements: params }, 'ZodObject expected when converting ConvoStatements to zod type');
}
return zType;
}
execute(scope, vars, resultScheme) {
scope = this.executeScope(scope, undefined, createDefaultScope(vars));
if (scope.si) {
return { scope, valuePromise: new Promise((r, j) => {
if (!scope.onComplete) {
scope.onComplete = [];
}
if (!scope.onError) {
scope.onError = [];
}
scope.onError.push(j);
if (resultScheme) {
scope.onComplete.push(value => {
const parsed = resultScheme.safeParse(value);
if (parsed.success === true) {
r(parsed.data);
}
else if (parsed.success === false) {
j(new ConvoError('invalid-return-value-type', { statement: scope.s }, `Invalid result value - ${parsed.error.message}`));
}
else {
r(value);
}
});
}
else {
scope.onComplete.push(r);
}
}) };
}
else {
if (scope.error) {
throw scope.error;
}
else {
if (resultScheme) {
const parsed = resultScheme.safeParse(scope.v);
if (parsed.success === true) {
return { scope, value: parsed.data };
}
else if (parsed.success === false) {
throw new ConvoError('invalid-return-value-type', { statement: scope.s }, `Invalid result value - ${parsed.error.message}`);
}
}
return { scope, value: scope.v };
}
}
}
executeScope(scope, parent, defaultScope, resumeParamScope, prevPi) {
const statement = scope.s;
if (!scope[convoScopeMsgKey]) {
scope[convoScopeMsgKey] = parent?.[convoScopeMsgKey];
scope[convoScopeLocationMsgKey] = parent?.[convoScopeLocationMsgKey];
}
let value = undefined;
if (statement.fn) {
scope = copyDefaultScope(scope);
if (parent) {
scope[convoScopeFnDefKey] = parent[convoScopeFnDefKey];
}
const fn = scope[convoScopeFnKey] ?? (scope[convoScopeFnKey] = statement.fnPath ?
(getValueByAryPath(scope.vars, statement.fnPath)?.[statement.fn] ??
getValueByAryPath(this.sharedVars, statement.fnPath)?.[statement.fn]) :
this.sharedVars[statement.fn]) ?? this.dynamicFunctionCallback ?? this.convo.conversation?.dynamicFunctionCallback;
if (typeof fn !== 'function') {
const errPath = statement.fnPath ? statement.fnPath.join('.') + '.' + statement.fn : statement.fn;
setConvoScopeError(scope, `${errPath} is not a function`);
value = undefined;
return scope;
}
if (!scope.paramValues) {
scope.paramValues = [];
}
const flowCtrl = fn[convoFlowControllerKey];
const parentStartIndex = parent?.i ?? 0;
if (flowCtrl?.keepData && parent?.childCtrlData) {
const dr = parent.childCtrlData[parentStartIndex.toString()];
if (dr) {
scope.ctrlData = dr.ctrlData;
scope.childCtrlData = dr.childCtrlData;
}
}
const shouldExecute = flowCtrl?.shouldExecute?.(scope, parent, this) ?? true;
if (shouldExecute) {
delete scope.li;
if (flowCtrl?.startParam) {
const startI = flowCtrl.startParam(scope, parent, this);
if (startI === false) {
scope.i = statement.params?.length ?? 0;
}
else {
scope.i = Math.max(0, startI);
}
}
if (statement.params?.length) {
if (flowCtrl?.usesLabels && !scope.labels) {
scope.labels = {};
}
while (scope.i < statement.params.length && (scope.bi !== scope.i)) {
const paramStatement = statement.params[scope.i];
if (paramStatement) {
let paramScope;
if (resumeParamScope) {
paramScope = resumeParamScope;
resumeParamScope = undefined;
}
else {
const d = defaultScope;
d.s = paramStatement;
paramScope = this.executeScope(d, scope, defaultScope);
}
if (paramScope.error) {
setConvoScopeError(scope, paramScope.error);
return scope;
}
if (paramScope.si) {
this.suspendScope(scope, paramScope);
paramScope.pi = scope.si;
if (prevPi) {
scope.pi = prevPi;
}
return scope;
}
if (flowCtrl?.discardParams) {
scope.paramValues[0] = paramScope.v;
}
else {
scope.paramValues.push(paramScope.v);
}
if (paramScope.r) {
value = paramScope.v;
scope.r = true;
break;
}
if (paramScope.bl) {
if (flowCtrl?.catchBreak) {
break;
}
else if (scope.li === scope.i) {
delete scope.fromIndex;
delete scope.gotoIndex;
delete scope.li;
}
else {
scope.bl = true;
return scope;
}
}
if (scope.fromIndex === scope.i && scope.gotoIndex !== undefined) {
scope.i = scope.gotoIndex;
delete scope.fromIndex;
delete scope.gotoIndex;
}
else if (flowCtrl?.nextParam) {
const f = flowCtrl.nextParam(scope, parent, paramStatement, this);
if (f === false) {
break;
}
scope.i = f;
}
else {
scope.i++;
}
}
else {
setConvoScopeError(scope, 'Parameter expected');
return scope;
}
}
}
if (!scope.r) {
if (statement.fnPath) {
value = getValueByAryPath(this.sharedVars, statement.fnPath)?.[statement.fn]?.(...(scope.paramValues ?? emptyAry));
}
else {
value = fn(scope, this);
}
if (statement.prompt) {
if (this.disableInlinePrompts && !statement.prompt.isStatic) {
setConvoScopeError(scope, {
message: `Inline prompts not allowed in current content. Inline prompts can not be used in content messages or top level statements`,
statement,
});
return scope;
}
if (statement.prompt.isStatic) {
value = this.executeStaticPrompt(statement.prompt, value, scope);
}
else {
value = this.executePromptAsync(statement.prompt, scope);
}
}
}
}
if (scope.r) {
if (flowCtrl?.catchReturn) {
scope.r = false;
}
}
else if (flowCtrl) {
if (flowCtrl.keepData && parent) {
if (!parent.childCtrlData) {
parent.childCtrlData = {};
}
const dr = {
ctrlData: scope.ctrlData,
childCtrlData: scope.childCtrlData,
};
parent.childCtrlData[parentStartIndex.toString()] = dr;
}
if (flowCtrl.transformResult) {
value = flowCtrl.transformResult(value, scope, parent, this);
}
}
}
else if (statement.ref) {
value = this.getVarEx(statement.ref, statement.refPath, scope);
}
else if (statement.prompt) {
if (this.disableInlinePrompts && !statement.prompt.isStatic) {
setConvoScopeError(scope, {
message: `Inline prompts not allowed in current content. Inline prompts can not be used in content messages or top level statements`,
statement,
});
return scope;
}
if (statement.prompt.isStatic) {
value = this.executeStaticPrompt(statement.prompt, statement.value, scope);
}
else {
value = this.executePromptAsync(statement.prompt, scope);
}
}
else {
value = statement.value;
}
if (scope.error) {
return scope;
}
if (isPromise(value)) {
scope = copyDefaultScope(scope);
this.suspendScope(scope);
value.then(v => {
scope.v = v;
this.completeScope(scope, parent, defaultScope);
}).catch(e => {
setConvoScopeError(scope, {
message: `Promise throw error - ${e?.message}`,
error: e,
statement,
});
this.completeScope(scope, parent, defaultScope);
});
}
else {
scope.v = value;
this.completeScope(scope, parent, defaultScope);
}
return scope;
}
executeStaticPrompt(prompt, value, scope) {
this.beforeHandlePromptResult(prompt);
const valueIsString = typeof value === 'string';
if ((prompt.continue && prompt.isStatic) && valueIsString) {
if (!this.lastInlineConversation) {
this.lastInlineConversation = this.createInlineConversation(prompt);
}
this.applyInlinePrompt(prompt, this.lastInlineConversation, scope);
this.lastInlineConversation.append((prompt.hasRole ? '' : '> user\n') + value, { addTags: [{ name: convoTags.disableModifiers }] });
}
if (prompt.jsonType && valueIsString) {
value = parseJson5(value);
}
return this.handlePromptResult(prompt, value, scope);
}
createInlineConversation(prompt) {
const options = { disableAutoFlatten: true, disableTriggers: true, disableTransforms: !prompt.transforms };
return (this.parentConvo ?? new Conversation(options))?.clone({ inlinePrompt: prompt, triggerName: this.getVar(convoVars.__trigger) }, options);
}
applyInlinePrompt(prompt, convo, scope) {
convo.inlinePrompt = prompt;
for (const e in convo.defaultVars) {
delete convo.defaultVars[e];
}
const vars = this.getUserSharedVars();
for (const e in vars) {
convo.defaultVars[e] = vars[e];
}
for (const e in scope.vars) {
if (e in defaultConvoVars) {
continue;
}
convo.defaultVars[e] = scope.vars[e];
}
}
async executePromptAsync(prompt, scope) {
if (this.parentConvo && this.parentConvo.childDepth > this.maxInlinePromptDepth) {
throw new Error('Max inline prompt depth reached');
}
const sub = (prompt.continue && this.lastInlineConversation) ?
this.lastInlineConversation : this.createInlineConversation(prompt);
this.applyInlinePrompt(prompt, sub, scope);
if (prompt.continue || prompt.extend) {
this.lastInlineConversation = sub;
}
if (prompt.messages?.length) {
sub.appendMessageObject(prompt.messages);
}
this.beforeHandlePromptResult(prompt);
const disposeTask = prompt.task ? this.parentConvo?.addTask(prompt.task) : undefined;
let r;
try {
r = await sub.completeAsync();
}
finally {
disposeTask?.();
}
let value;
if (r.message?.format === 'json') {
value = parseJson5(r.message.content ?? '');
if (r.message.formatTypeName === 'TrueFalse') {
value = value?.isTrue;
}
}
else {
value = r.message?.content;
}
return this.handlePromptResult(prompt, value, scope);
}
beforeHandlePromptResult(prompt) {
// systemMessages
if (prompt.systemMessages) {
const append = (convo, type) => {
if (!convo.findMessage({ tag: convoTags.stdSystem, tagValue: type })) {
convo.append(getConvoSystemMessage(type), { disableAutoFlatten: true });
}
};
for (const s of prompt.systemMessages) {
if (this.parentConvo) {
append(this.parentConvo, s);
}
if (this.lastInlineConversation) {
append(this.lastInlineConversation, s);
}
}
}
}
handlePromptResult(prompt, value, scope) {
if (prompt?.not) {
value = !value;
}
if (prompt.assignOutputTo) {
this.setVar(undefined, value, prompt.assignOutputTo, undefined, scope);
}
if (this.parentConvo) {
// appendOutput
if (prompt.appendOutput) {
const output = typeof value === 'string' ? value : JSON.stringify(value);
this.parentConvo.append((doesConvoContentHaveMessage(output) ? '' : '> append\n') + output, { disableAutoFlatten: true });
}
// action
if (prompt.action) {
let content = value;
if (typeof content !== 'string') {
try {
content = JSON.stringify(content);
}
catch {
content = content + '';
}
}
if (isConvoMessageModification(prompt.action)) {
this.parentConvo.appendModification(prompt.action, content, this.flat);
}
else if (prompt.action === 'respond' && this.flat) {
this.parentConvo.appendResponse((prompt.hasRole && prompt.isStatic) ? content : `> assistant\n${escapeConvo(content)}`, this.flat);
}
}
}
return (!prompt.preSpace && (typeof value === 'string')) ? value.trim() : value;
}
suspendScope(scope, waitFor) {
if (!scope.si) {
scope.si = (this.nextSuspendId++).toString();
}
this.suspendedScopes[scope.si] = scope;
if (waitFor) {
scope.wi = waitFor.si;
}
}
completeScope(scope, parent, defaultScope) {
if (scope.wi) {
throw new ConvoError('scope-waiting', { statement: scope.s }, `scope waiting on scope(${scope.wi}) before resuming`);
}
const statement = scope.s;
if (statement.set) {
this.setVar(statement.shared, scope.v, statement.set, statement.setPath, scope);
}
if (statement.label && parent?.labels) {
parent.labels[statement.label] = statement.opt ? createOptionalConvoValue(parent.i) : parent.i;
}
delete scope.pi;
const resume = scope.si ? [] : null;
if (scope.si) {
const si = scope.si;
delete scope.si;
delete this.suspendedScopes[si];
for (const e in this.suspendedScopes) {
const ss = this.suspendedScopes[e];
if (ss?.wi === si) {
delete this.suspendedScopes[e];
delete ss.wi;
resume.push(ss);
}
}
}
if (scope.onComplete) {
const oc = scope.onComplete;
delete scope.onComplete;
delete scope.onError;
for (let i = 0; i < oc.length; i++) {
oc[i]?.(scope.v);
}
}
if (resume) {
for (const r of resume) {
const parent = r.pi ? this.suspendedScopes[r.pi] : undefined;
if (r.pi && !parent) {
throw new ConvoError('suspension-parent-not-found', { statement: scope.s });
}
const prevPi = r.pi;
delete r.pi;
this.executeScope(r, parent, defaultScope, scope, prevPi);
}
}
}
getRefValue(statement, scope, throwUndefined = true) {
if (!statement) {
return undefined;
}
if (!statement.ref) {
throw new ConvoError('variable-ref-required', { statement });
}
return this.getVarEx(statement.ref, statement.refPath, scope, throwUndefined);
}
getVarEx(name, path, scope, throwUndefined = this.defaultThrowOnUndefined) {
let value = scope?.vars[name] ?? this.sharedVars[name];
if (value === undefined && (scope ? !(name in scope.vars) : true) && !(name in this.sharedVars)) {
if (throwUndefined) {
setConvoScopeError(scope, `reference to undefined var - ${name}`);
}
}
else if (path) {
value = getValueByAryPath(value, path);
}
if (!path && value === undefined) {
return this.getVarAlias(name, scope);
}
else {
return value;
}
}
getVar(nameOrPath, scope, defaultValue) {
let path = undefined;
if (nameOrPath.includes('.')) {
path = nameOrPath.split('.');
nameOrPath = path.shift() ?? '';
}
return this.getVarEx(nameOrPath, path, scope ?? undefined, false) ?? defaultValue;
}
getStringVar(nameOrPath, scope, defaultValue) {
const val = this.getVar(nameOrPath, scope, defaultValue);
return (typeof val === 'string') ? val : undefined;
}
setRefValue(statement, value, scope) {
if (!statement) {
return value;
}
if (!statement.ref) {
throw new ConvoError('variable-ref-required', { statement });
}
this.setVar(statement.shared, value, statement.ref, statement.refPath, scope);
return value;
}
setDefaultVarValue(value, name, path) {
if (name in defaultConvoVars) {
setConvoScopeError(null, `Overriding builtin var not allowed - ${name}`);
return value;
}
if (this.sharedVars[name] !== undefined) {
return this.sharedVars[name];
}
return this.setVar(true, value, name, path);
}
setVar(shared, value, name, path, scope) {
if (name in defaultConvoVars) {
setConvoScopeError(scope, `Overriding builtin var not allowed - ${name}`);
return value;
}
if (this.isReadonly) {
const msg = `Current context is readonly. Unable to set ${name}`;
if (scope) {
setConvoScopeError(scope, msg);
return value;
}
else {
throw new Error(msg);
}
}
if (this.varPrefix) {
name = this.varPrefix + name;
}
const vars = (shared ||
!scope ||
(scope && scope.vars[name] === undefined && this.sharedVars[name] !== undefined)) ? this.sharedVars : scope.vars;
if (shared !== false && vars === this.sharedVars && (typeof value !== 'function')) {
const setterName = path ? name + '.' + path.join('.') : name;
const i = this.sharedSetters.indexOf(setterName);
if (i !== -1) {
this.sharedSetters.splice(i, 1);
}
this.sharedSetters.push(setterName);
}
if (path) {
let obj = vars[name];
if (obj === undefined || obj === null) {
if (this.defaultThrowOnUndefined) {
setConvoScopeError(scope, `reference to undefined var for setting path - ${name}`);
}
return value;
}
if (path.length > 1) {
obj = getValueByAryPath(obj, path, undefined, path.length - 1);
if (obj === undefined || obj === null) {
if (this.defaultThrowOnUndefined) {
setConvoScopeError(scope, `reference to undefined var at path - ${name}.${path.join('.')}`);
}
return value;
}
}
obj[path[path.length - 1] ?? ''] = value;
}
else {
vars[name] = value;
}
return value;
}
setVarUsingCompletionMessage(shared, msg, name, path, scope) {
if (msg.format === 'json') {
try {
this.setVar(shared, msg.content ? parseConvoJsonMessage(msg.content) : null, name, path, scope);
}
catch (ex) {
this.setVar(shared, getErrorMessage(ex), name, path, scope);
}
}
else {
this.setVar(shared, msg.content ?? '', name, path, scope);
}
}
consumeVars(otherExec) {
if (!otherExec) {
return;
}
for (const e in otherExec.sharedVars) {
if (this.sharedVars[e] === undefined) {
this.sharedVars[e] = otherExec.sharedVars[e];
}
}
}
getTagValueByName(msg, tagName, defaultValue) {
const tag = getConvoTag(msg?.tags, tagName);
if (!tag) {
return defaultValue;
}
return this.getTagValue(tag, defaultValue);
}
getTagValue(tag, defaultValue) {
let value;
if (tag.statement) {
const r = this.getTagStatementValue(tag);
value = r.length > 1 ? r : r[0];
}
else {
value = tag.value;
}
return value === undefined ? defaultValue : value;
}
isTagConditionTrueByName(msg, tagName, defaultValue = false) {
const tag = getConvoTag(msg?.tags, tagName);
if (!tag) {
return false;
}
return this.isTagConditionTrue(tag, defaultValue);
}
isTagConditionTrue(tag, defaultValue = false) {
if (tag.statement) {
return this.getTagStatementValue(tag).every(v => v);
}
else if (tag.value !== undefined) {
let tagValue = tag.value.trim();
if (!tagValue) {
return true;
}
const not = tagValue.startsWith('!');
if (not) {
tagValue = tagValue.substring(1).trim();
}
const parts = tagValue.split(/\s+/);
if (parts.length < 1) {
return false;
}
let value = this.getVar(parts[0] ?? '');
if (not) {
value = !value;
}
if (parts.length === 1) {
return value ? true : false;
}
let v2;
if (parts.length > 2) {
parts.shift();
v2 = parts.join(' ');
}
else {
v2 = parts[1];
}
return value?.toString() === v2;
}
else {
return defaultValue;
}
}
getTagStatementValue(tag, message) {
if (!tag.statement?.length) {
return [];
}
this.isReadonly++;
try {
const values = tag.statement.map(s => {
const r = this.executeStatement(s, message);
if (r.valuePromise) {
throw new Error('Tag value statements are not allowed to return promises');
}
return r.value;
});
return values;
}
finally {
this.isReadonly--;
}
}
enableRag(paramValues) {
this.setVar(true, true, convoVars.__rag);
let ragParams = this.getVar(convoVars.__ragParams);
if (!ragParams || (typeof ragParams !== 'object')) {
ragParams = {};
this.setVar(true, ragParams, convoVars.__ragParams);
}
if (!Array.isArray(ragParams.values)) {
ragParams.values = [];
}
const ary = ragParams.values;
if (paramValues) {
for (const v of paramValues) {
if (!ary.includes(v)) {
ary.push(v);
}
}
}
return ary;
}
clearRag() {
this.setVar(true, false, convoVars.__rag);
this.setVar(true, undefined, convoVars.__ragParams);
}
/**
* Returns the full path of the give path if the path is relative. The __cwd variable is used to
* determine the current working directory.
*/
getFullPath(path, scope) {
if (!path || (typeof path !== 'string')) {
return '.';
}
if (isRooted(path)) {
return normalizePath(path);
}
const cwd = this.getCwdOrUndefined(scope);
return normalizePath(cwd ? joinPaths(cwd, path) : path);
}
/**
* Gets the current working directory based on scope
*/
getCwd(scope) {
const cwd = this.sharedVars[convoVars.__cwd];
if (typeof cwd === 'string') {
return normalizePath(cwd);
}
const file = this.getVarAlias(convoVars.__file, scope);
if (typeof file === 'string') {
const r = normalizePath(getDirectoryName(file));
return r;
}
else {
return '.';
}
}
/**
* Gets the current working directory based on scope or undefined if the current working directory
* equals '.'
*/
getCwdOrUndefined(scope) {
const cwd = this.getCwd(scope);
return cwd === '.' ? undefined : cwd;
}
/**
* Gets built-in type aliases by name. Used to provide predefined types
* that are commonly used in Convo-Lang but not explicitly defined in user code.
*
* @param name - The name of the type alias to retrieve
* @returns The type definition, or undefined if the alias doesn't exist
*/
getVarAlias(name, scope) {
switch (name) {
case convoVars.__file: {
const msgFile = (scope?.[convoScopeLocationMsgKey] ?? scope?.[convoScopeMsgKey])?.[convoMessageSourcePathKey];
if (msgFile) {
return msgFile;
}
return this.getVar(convoVars.__mainFile);
}
case 'TrueFalse':
return parseConvoType('TrueFalse', /*convo*/ `
> define
TrueFalse=struct(
isTrue:boolean
)
`);
default:
return undefined;
}
}
}
const emptyAry = [];
//# sourceMappingURL=ConvoExecutionContext.js.map