UNPKG

@convo-lang/convo-lang

Version:
1,118 lines 42.9 kB
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