UNPKG

@convo-lang/convo-lang

Version:
1,389 lines 55.5 kB
import { SceneCtrl, aryRandomize, base64Encode, base64EncodeMarkdownImage, base64EncodeUrl, createJsonRefReplacer, deepClone, deepCompare, escapeHtml, escapeHtmlKeepDoubleQuote, getContentType, getErrorMessage, getFileNameNoExt, httpClient, joinPaths, markdownLineToString, objectToMarkdownBuffer, shortUuid, starStringTest, toCsvLines, uuid, valueIsZodObject } from "@iyio/common"; import { vfs } from "@iyio/vfs"; import { format } from "date-fns"; import { ConvoError } from "./ConvoError.js"; import { convoArgsName, convoArrayFnName, convoBodyFnName, convoCaseFnName, convoDateFormat, convoDefaultFnName, convoEnumFnName, convoFunctions, convoGlobalRef, convoJsonArrayFnName, convoJsonMapFnName, convoLabeledScopeParamsToObj, convoMapFnName, convoMetadataKey, convoParamsToObj, convoPipeFnName, convoStructFnName, convoSwitchFnName, convoTestFnName, convoVars, createConvoBaseTypeDef, createConvoMetadataForStatement, createConvoScopeFunction, createConvoType, isConvoTypeArray, makeAnyConvoType } from "./convo-lib.js"; import { convoPipeScopeFunction } from "./convo-pipe.js"; import { createConvoSceneDescription } from "./convo-scene-lib.js"; import { isConvoMarkdownLine } from "./convo-types.js"; import { convoTypeToJsonScheme, convoValueToZodType, describeConvoScheme } from "./convo-zod.js"; import { convoScopeFunctionReadDoc } from "./scope-functions/convoScopeFunctionReadDoc.js"; const ifFalse = Symbol(); const ifTrue = Symbol(); const breakIteration = Symbol(); const mdImg = createConvoScopeFunction(async (scope, ctx) => { const [url, description = getFileNameNoExt(url)?.trim().replace(/[^\w-. ]/g, '_') || 'image', contentType = getContentType(url)] = scope.paramValues ?? []; try { if (typeof url !== 'string') { throw new ConvoError('invalid-args', { statement: scope.s }, 'fsReadBase64Url expects first argument to be a string'); } if (typeof description !== 'string') { throw new ConvoError('invalid-args', { statement: scope.s }, 'fsReadBase64Url expects second argument to be a string'); } if (typeof contentType !== 'string') { throw new ConvoError('invalid-args', { statement: scope.s }, 'fsReadBase64Url expects third argument to be a string'); } const path = ctx.getFullPath(url, scope); const data = await vfs().readBufferAsync(path); return base64EncodeMarkdownImage(description, contentType, data); } catch (ex) { console.error('ERROR', ex); return ''; } }); const mapFn = makeAnyConvoType('map', createConvoScopeFunction({ usesLabels: true, }, convoLabeledScopeParamsToObj)); const arrayFn = makeAnyConvoType('array', (scope) => { return scope.paramValues ?? []; }); const and = createConvoScopeFunction({ discardParams: true, nextParam(scope) { const value = scope.paramValues ? scope.paramValues[scope.paramValues.length - 1] : undefined; if (value) { return scope.i + 1; } else { return false; } } }, scope => { const value = scope.paramValues ? scope.paramValues[scope.paramValues.length - 1] : undefined; return value ? true : false; }); const or = createConvoScopeFunction({ discardParams: true, nextParam(scope) { const value = scope.paramValues ? scope.paramValues[scope.paramValues.length - 1] : undefined; if (value) { return false; } else { return scope.i + 1; } } }, scope => { return scope.paramValues ? scope.paramValues[scope.paramValues.length - 1] : undefined; }); const describeStruct = createConvoScopeFunction(scope => { const type = convoValueToZodType(scope.paramValues?.[0]); if (!valueIsZodObject(type)) { throw new ConvoError('invalid-args', { statement: scope.s }, 'The first arg of new should be a type variable'); } return describeConvoScheme(type, scope.paramValues?.[1]); }); export const defaultConvoVarsBase = { [convoBodyFnName]: createConvoScopeFunction({ discardParams: true, catchReturn: true, }), string: createConvoBaseTypeDef('string'), number: createConvoBaseTypeDef('number'), int: createConvoBaseTypeDef('int'), time: createConvoBaseTypeDef('time'), void: createConvoBaseTypeDef('void'), boolean: createConvoBaseTypeDef('boolean'), any: createConvoBaseTypeDef('any'), object: createConvoBaseTypeDef('object'), ['true']: true, ['false']: false, ['null']: null, ['undefined']: undefined, [convoGlobalRef]: undefined, [convoPipeFnName]: convoPipeScopeFunction, [convoStructFnName]: makeAnyConvoType('map', createConvoScopeFunction({ usesLabels: true, }, (scope) => { scope.cm = true; return convoLabeledScopeParamsToObj(scope); })), [convoMapFnName]: mapFn, [convoArrayFnName]: arrayFn, [convoJsonMapFnName]: mapFn, [convoJsonArrayFnName]: arrayFn, [convoArgsName]: undefined, [convoEnumFnName]: createConvoScopeFunction(scope => { const type = createConvoType({ type: 'enum', enumValues: scope.paramValues ?? [], }); const metadata = createConvoMetadataForStatement(scope.s); type[convoMetadataKey] = metadata; return type; }), [convoFunctions.mapWithCapture]: createConvoScopeFunction({ usesLabels: true }, (scope) => { return convoParamsToObj(scope, undefined, '_'); }), is: createConvoScopeFunction(scope => { if (!scope.paramValues || scope.paramValues.length < 2) { return false; } const type = scope.paramValues[scope.paramValues.length - 1]; if (!type || (typeof type !== 'object')) { return false; } const scheme = convoValueToZodType(type); for (let i = 0; i < scope.paramValues.length - 1; i++) { const p = scheme.safeParse(scope.paramValues[i]); if (!p.success) { return false; } } return true; }), and, or, not: createConvoScopeFunction(scope => { if (!scope.paramValues?.length) { return true; } for (let i = 0; i < scope.paramValues.length; i++) { if (scope.paramValues[i]) { return false; } } return true; }), if: createConvoScopeFunction({ discardParams: true, nextParam(scope, parentScope) { const value = scope.paramValues ? scope.paramValues[scope.paramValues.length - 1] : undefined; if (value) { return scope.i + 1; } else { if (parentScope) { parentScope.i++; } return false; } } }, (scope) => { const value = scope.paramValues ? scope.paramValues[scope.paramValues.length - 1] : undefined; return value ? ifTrue : ifFalse; }), elif: createConvoScopeFunction({ discardParams: true, shouldExecute(scope, parentScope) { const prev = (parentScope?.paramValues && parentScope.paramValues[parentScope.paramValues.length - 1]); return prev === ifFalse; }, nextParam(scope) { const value = scope.paramValues ? scope.paramValues[scope.paramValues.length - 1] : undefined; if (value) { return scope.i + 1; } else { return false; } } }, scope => { const value = scope.paramValues ? scope.paramValues[scope.paramValues.length - 1] : undefined; return value ? ifTrue : ifFalse; }), else: createConvoScopeFunction({ discardParams: true, shouldExecute(scope, parentScope) { const prev = (parentScope?.paramValues && parentScope.paramValues[parentScope.paramValues.length - 1]); return prev === ifFalse; }, }, () => { return ifTrue; }), then: createConvoScopeFunction({ discardParams: true, shouldExecute(scope, parentScope) { const prev = (parentScope?.paramValues && parentScope.paramValues[parentScope.paramValues.length - 1]); return prev === ifTrue; }, }, () => { return ifTrue; }), while: createConvoScopeFunction({ discardParams: true, nextParam(scope, parentScope) { const value = scope.paramValues ? scope.paramValues[scope.paramValues.length - 1] : undefined; if (scope.i === 0 && parentScope) { delete parentScope.fromIndex; } if (scope.s.params && scope.i === scope.s.params.length - 1 && parentScope) { if (value) { parentScope.fromIndex = parentScope.i + 1; parentScope.gotoIndex = parentScope.i; parentScope.li = parentScope.i + 1; } } if (value) { return scope.i + 1; } else { if (parentScope) { parentScope.i++; } return false; } } }, (scope) => { const value = scope.paramValues ? scope.paramValues[scope.paramValues.length - 1] : undefined; if (value) { scope.ctrlData = ifTrue; } return scope.ctrlData ?? ifFalse; }), foreach: createConvoScopeFunction({ discardParams: true, keepData: true, startParam(scope, parentScope) { if (!scope.s.params?.length) { if (parentScope) { parentScope.i++; } return false; } return 0; }, nextParam(scope, parentScope) { if (scope.paramValues?.[0] === breakIteration) { if (parentScope) { parentScope.i++; } return false; } if (parentScope && scope.i + 1 === scope.s.params?.length) { parentScope.fromIndex = parentScope.i + 1; parentScope.gotoIndex = parentScope.i; parentScope.li = parentScope.i + 1; } return scope.i + 1; } }, (scope) => { const value = scope.paramValues ? scope.paramValues[scope.paramValues.length - 1] : undefined; if (value) { scope.ctrlData = ifTrue; } return scope.ctrlData ?? ifFalse; }), in: createConvoScopeFunction({ discardParams: true, keepData: true, nextParam(scope) { const value = scope.paramValues?.[0]; if (!value || (typeof value !== 'object')) { return false; } let it = scope.ctrlData; if (!it) { it = { i: 0 }; if (!Array.isArray(value)) { it.keys = Object.keys(value); } scope.ctrlData = it; } return false; } }, (scope) => { const it = scope.ctrlData; if (!it) { return breakIteration; } const value = scope.paramValues?.[0]; const isArray = Array.isArray(value); const ary = it.keys ?? value; if (it.i >= ary.length) { return breakIteration; } const r = isArray ? value[it.i] : { key: ary[it.i], value: value[ary[it.i]] }; it.i++; return r; }), break: createConvoScopeFunction({ discardParams: true, nextParam(scope, parentScope) { if (parentScope?.paramValues) { scope.ctrlData = parentScope.paramValues[parentScope.paramValues.length - 1]; } return scope.i + 1; } }, (scope) => { if (!scope.paramValues?.length) { scope.bl = true; return scope.ctrlData; } for (let i = 0; i < scope.paramValues.length; i++) { if (scope.paramValues[i]) { scope.bl = true; return scope.ctrlData; } } return scope.ctrlData; }), do: createConvoScopeFunction({ discardParams: true, }, scope => { return scope.paramValues ? scope.paramValues[scope.paramValues.length - 1] : undefined; }), fn: createConvoScopeFunction({ discardParams: true, catchReturn: true, }, () => { return undefined; }), return: createConvoScopeFunction(scope => { const value = scope.paramValues ? scope.paramValues[scope.paramValues.length - 1] : undefined; scope.r = true; return value; }), eq: createConvoScopeFunction(scope => { if (!scope.paramValues || scope.paramValues.length < 2) { return false; } for (let i = 1; i < scope.paramValues.length; i++) { if (scope.paramValues[i - 1] !== scope.paramValues[i]) { return false; } } return true; }), gt: createConvoScopeFunction(scope => { if (!scope.paramValues || scope.paramValues.length < 2) { return false; } for (let i = 1; i < scope.paramValues.length; i++) { if (!(scope.paramValues[i - 1] > scope.paramValues[i])) { return false; } } return true; }), gte: createConvoScopeFunction(scope => { if (!scope.paramValues || scope.paramValues.length < 2) { return false; } for (let i = 1; i < scope.paramValues.length; i++) { if (!(scope.paramValues[i - 1] >= scope.paramValues[i])) { return false; } } return true; }), lt: createConvoScopeFunction(scope => { if (!scope.paramValues || scope.paramValues.length < 2) { return false; } for (let i = 1; i < scope.paramValues.length; i++) { if (!(scope.paramValues[i - 1] < scope.paramValues[i])) { return false; } } return true; }), lte: createConvoScopeFunction(scope => { if (!scope.paramValues || scope.paramValues.length < 2) { return false; } for (let i = 1; i < scope.paramValues.length; i++) { if (!(scope.paramValues[i - 1] <= scope.paramValues[i])) { return false; } } return true; }), isIn: createConvoScopeFunction(scope => { if (!scope.paramValues || scope.paramValues.length < 2) { return false; } const value = scope.paramValues[0]; const condValue = scope.paramValues[1]; if ((typeof value === 'string') && (typeof condValue === 'string')) { return value.includes(condValue); } else if (Array.isArray(value)) { return value.includes(condValue); } else { return false; } }), contains: createConvoScopeFunction(scope => { if (!scope.paramValues || scope.paramValues.length < 2) { return false; } const value = scope.paramValues[0]; const condValue = scope.paramValues[1]; if ((typeof value === 'string') && (typeof condValue === 'string')) { return condValue.includes(value); } else if (Array.isArray(condValue)) { return condValue.includes(value); } else { return false; } }), regexMatch: createConvoScopeFunction(scope => { if (!scope.paramValues || scope.paramValues.length < 2) { return false; } const value = scope.paramValues[0]; const condValue = scope.paramValues[1]; try { if (typeof condValue === 'string') { const reg = new RegExp(condValue); return reg.test(value); } else if (condValue instanceof RegExp) { return condValue.test(value); } else { return false; } } catch { return false; } }), starMatch: createConvoScopeFunction(scope => { if (!scope.paramValues || scope.paramValues.length < 2) { return false; } const value = scope.paramValues[0]; const condValue = scope.paramValues[1]; if ((typeof value !== 'string') || (typeof condValue !== 'string')) { return false; } else { return starStringTest(condValue, value); } }), deepCompare: createConvoScopeFunction(scope => { if (!scope.paramValues || scope.paramValues.length < 2) { return false; } const value = scope.paramValues[0]; const condValue = scope.paramValues[1]; return deepCompare(value, condValue, scope.paramValues[2]); }), aryRandomize: createConvoScopeFunction(scope => { const ary = scope.paramValues?.[0]; if (Array.isArray(ary)) { return aryRandomize(ary); } return ary; }), aryAdd: createConvoScopeFunction(scope => { let ary = scope.paramValues?.[0]; if (!scope.paramValues || !Array.isArray(ary)) { return ary; } ary = [...ary]; for (let i = 1; i < scope.paramValues.length; i++) { ary.push(scope.paramValues[i]); } return ary; }), aryRemove: createConvoScopeFunction(scope => { let ary = scope.paramValues?.[0]; if (!scope.paramValues || !Array.isArray(ary)) { return []; } ary = [...ary]; for (let i = 1; i < scope.paramValues.length; i++) { const index = ary.indexOf(scope.paramValues[i]); if (index === -1) { continue; } ary.splice(index, 1); i--; } return ary; }), aryConcat: createConvoScopeFunction(scope => { const ary = []; if (!scope.paramValues?.length) { return ary; } for (const v of scope.paramValues) { if (Array.isArray(v)) { ary.push(...v); } else if (v !== undefined) { ary.push(v); } } return ary; }), aryDistinct: createConvoScopeFunction(scope => { const ary = []; if (!scope.paramValues?.length) { return ary; } for (const v of scope.paramValues) { if (Array.isArray(v)) { for (const a of v) { if (!ary.includes(a)) { ary.push(a); } } } else if (v !== undefined && !ary.includes(v)) { ary.push(v); } } return ary; }), aryJoin: createConvoScopeFunction(scope => { const ary = scope.paramValues?.[0]; if (!scope.paramValues || !Array.isArray(ary)) { return []; } if (scope.paramValues.length > 2) { const out = []; for (let i = 0; i < ary.length - 1; i++) { out.push(ary[i]); out.push(scope.paramValues[1 + (i % (scope.paramValues.length - 1))]); } if (ary.length) { out.push(ary[ary.length - 1]); } return out.join(''); } else { return ary.join(scope.paramValues[1] ?? ', '); } }), add: createConvoScopeFunction(scope => { if (!scope.paramValues?.length) { return undefined; } let value = scope.paramValues[0]; for (let i = 1; i < scope.paramValues.length; i++) { const v = scope.paramValues[i]; if (v !== undefined) { if (value === undefined) { value = v; } else { value += v; } } } return value; }), sub: createConvoScopeFunction(scope => { if (!scope.paramValues?.length) { return undefined; } let value = scope.paramValues[0]; for (let i = 1; i < scope.paramValues.length; i++) { const v = scope.paramValues[i]; if (v !== undefined) { if (value === undefined) { value = v; } else { value -= v; } } } return value; }), mul: createConvoScopeFunction(scope => { if (!scope.paramValues?.length) { return undefined; } let value = scope.paramValues[0]; for (let i = 1; i < scope.paramValues.length; i++) { const v = scope.paramValues[i]; if (v !== undefined) { if (value === undefined) { value = v; } else { value *= v; } } } return value; }), div: createConvoScopeFunction(scope => { if (!scope.paramValues?.length) { return undefined; } let value = scope.paramValues[0]; for (let i = 1; i < scope.paramValues.length; i++) { const v = scope.paramValues[i]; if (v !== undefined) { if (value === undefined) { value = v; } else { value /= v; } } } return value; }), mod: createConvoScopeFunction(scope => { if (!scope.paramValues?.length) { return undefined; } let value = scope.paramValues[0]; for (let i = 1; i < scope.paramValues.length; i++) { const v = scope.paramValues[i]; if (v !== undefined) { if (value === undefined) { value = v; } else { value %= v; } } } return value; }), pow: createConvoScopeFunction(scope => { if (!scope.paramValues?.length) { return undefined; } let value = scope.paramValues[0]; for (let i = 1; i < scope.paramValues.length; i++) { const v = scope.paramValues[i]; if (v !== undefined) { if (value === undefined) { value = v; } else { value = Math.pow(value, v); } } } return value; }), print: createConvoScopeFunction((scope, ctx) => { if (scope.paramValues) { ctx.print(...scope.paramValues); } return scope.paramValues?.[scope.paramValues?.length ?? 0]; }), inc: createConvoScopeFunction({ discardParams: true, startParam() { return 1; } }, (scope, ctx) => { if (!scope.s.params) { return undefined; } const value = scope.paramValues?.[0] ?? 1; let lastValue = value; const s = scope.s.params[0]; const sv = ctx.getRefValue(s, scope, false); lastValue = sv === undefined ? value : sv + value; ctx.setRefValue(s, lastValue, scope); return lastValue; }), dec: createConvoScopeFunction({ discardParams: true, startParam() { return 1; } }, (scope, ctx) => { if (!scope.s.params) { return undefined; } const value = scope.paramValues?.[0] ?? 1; let lastValue = value; const s = scope.s.params[0]; const sv = ctx.getRefValue(s, scope, false); lastValue = sv === undefined ? -value : sv - value; ctx.setRefValue(s, lastValue, scope); return lastValue; }), [convoSwitchFnName]: createConvoScopeFunction({ discardParams: true, nextParam(scope) { if (scope.i === 0) { if (scope.s.hmc) { scope.sv = scope.paramValues?.[0]; return 1; } else { return scope.paramValues?.[0] ? 1 : 2; } } if (scope.s.hmc) { if (scope.s.params && !scope.s.params[scope.i]?.mc) { scope.sv = scope.paramValues?.[0]; } const nextIndex = scope.ctrlData; if (nextIndex === undefined) { return scope.i + 1; } else { delete scope.ctrlData; return nextIndex; } } else { return false; } } }, scope => { return scope.paramValues?.[0]; }), [convoCaseFnName]: createConvoScopeFunction({ discardParams: true, nextParam(scope, parentScope) { if (parentScope?.s.fn !== convoSwitchFnName || !scope.s.params?.length) { return false; } const isMatch = parentScope.sv === scope.paramValues?.[0]; if (isMatch) { // let control flow move to next statement and do not check anymore statements parentScope.bi = parentScope.i + 2; return false; } if (scope.i === scope.s.params.length - 1) { // no matches found, skip next statement parentScope.ctrlData = parentScope.i + 2; return false; } return scope.i + 1; } }, () => { return undefined; }), [convoDefaultFnName]: createConvoScopeFunction({ discardParams: true, startParam(scope, parentScope) { if (parentScope?.s.fn !== convoSwitchFnName) { return false; } parentScope.bi = parentScope.i + 2; return false; } }, () => { return undefined; }), [convoTestFnName]: createConvoScopeFunction({ discardParams: true, nextParam(scope, parentScope) { if (parentScope?.s.fn !== convoSwitchFnName || !scope.s.params?.length) { return false; } const isMatch = scope.paramValues?.[0] ? true : false; if (isMatch) { // let control flow move to next statement and do not check anymore statements parentScope.bi = parentScope.i + 2; return false; } if (scope.i === scope.s.params.length - 1) { // no matches found, skip next statement parentScope.ctrlData = parentScope.i + 2; return false; } return scope.i + 1; } }, () => { return undefined; }), sleep: createConvoScopeFunction(scope => { const start = Date.now(); const delay = scope.paramValues?.[0]; return new Promise(r => { setTimeout(() => { r(Date.now() - start); }, typeof delay === 'number' ? delay : 0); }); }), rand: createConvoScopeFunction(scope => { const range = scope.paramValues?.[0]; if (typeof range === 'number') { return Math.round(Math.random() * range); } else { return Math.random(); } }), [convoFunctions.idx]: createConvoScopeFunction((scope, ctx) => { if (!scope.paramValues) { return undefined; } let value = scope.paramValues[0]; for (let i = 1; i < scope.paramValues.length; i++) { value = value?.[ctx.getVar(scope.paramValues?.[1] ?? '', scope, '')]; } return value; }), [convoFunctions.setDefault]: createConvoScopeFunction({ usesLabels: true }, (scope, ctx) => { const obj = mapFn(scope, ctx); if (obj) { for (const e in obj) { ctx.setDefaultVarValue(obj[e], e); } } return obj; }), encodeURI: createConvoScopeFunction(scope => { return encodeURI(scope.paramValues?.[0]?.toString() ?? ''); }), encodeURIComponent: createConvoScopeFunction(scope => { return encodeURIComponent(scope.paramValues?.[0]?.toString() ?? ''); }), now: createConvoScopeFunction(() => { return Date.now(); }), dateTime: createConvoScopeFunction(scope => { const f = scope.paramValues?.[0] ?? convoDateFormat; let time = scope.paramValues?.[1] ?? new Date(); switch (typeof time) { case 'string': time = new Date(time); break; case 'number': break; default: if (!(time instanceof Date)) { throw new ConvoError('invalid-args', { statement: scope.s }, 'Second arg of timeTime must be a string, number or Date object'); } break; } try { return format(time, f); } catch { return format(time, convoDateFormat); } }), [convoFunctions.today]: createConvoScopeFunction(() => { return format(new Date(), 'yyyy-MM-dd'); }), md: createConvoScopeFunction((scope) => { if (!scope.paramValues?.length) { return ''; } const out = []; for (let i = 0; i < scope.paramValues.length; i++) { const value = scope.paramValues[i]; if (value === undefined) { out.push('undefined'); } else if (value === null) { out.push('null'); } else if ((typeof value === 'object') && !isPrimitiveArray(value) && !(value instanceof Date)) { if (value[convoMetadataKey] || isConvoTypeArray(value)) { out.push(JSON.stringify(convoTypeToJsonScheme(value))); } else { try { out.push(JSON.stringify(value, null, 4)); } catch { try { out.push(JSON.stringify(value, createJsonRefReplacer(), 4)); } catch { out.push('[[object with excessive recursive references]]'); } } } } else { objectToMarkdownBuffer(value, out, '', 5); } } return out.join(''); }), toMarkdown: createConvoScopeFunction(scope => { if (!scope.paramValues?.length) { return ''; } const maxDepth = scope.paramValues[0]; if (typeof maxDepth !== 'number') { throw new ConvoError('invalid-args', { statement: scope.s }, 'The first arg of toMarkdown must be a number indicating the max depth'); } const out = []; for (let i = 1; i < scope.paramValues.length; i++) { objectToMarkdownBuffer(scope.paramValues[i], out, '', maxDepth); } return out.join(''); }), toJson: createConvoScopeFunction(scope => { const value = scope.paramValues?.[0]; if (value === undefined) { return 'undefined'; } return JSON.stringify(value, createJsonRefReplacer(), 4); }), toJsonMdBlock: createConvoScopeFunction(scope => { const value = scope.paramValues?.[0]; return ('``` json\n' + value === undefined ? 'undefined' : JSON.stringify(value, createJsonRefReplacer(), 4) + '\n```'); }), toJsonScheme: createConvoScopeFunction(scope => { const value = scope.paramValues?.[0]; return JSON.stringify(convoTypeToJsonScheme(value)); }), toCsv: createConvoScopeFunction(scope => { const value = scope.paramValues?.[0]; if (!Array.isArray(value)) { return '"!Invalid CSV Data"'; } return toCsvLines(value); }), toCsvMdBlock: createConvoScopeFunction(scope => { const value = scope.paramValues?.[0]; if (!Array.isArray(value)) { return '"!Invalid CSV Data"'; } return '``` csv\n' + toCsvLines(value) + '\n```'; }), merge: createConvoScopeFunction(scope => { const value = {}; if (scope.paramValues) { for (let i = 0; i < scope.paramValues.length; i++) { const item = scope.paramValues[i]; if (item && (typeof item === 'object')) { for (const e in item) { const v = item[e]; if (v !== undefined) { value[e] = v; } } } } } return value; }), setObjDefaults: createConvoScopeFunction((scope, ctx) => { let target = scope.paramValues?.[0]; const source = scope.paramValues?.[1]; if (typeof target === 'string') { let t = ctx.getVar(target, scope); if (t === undefined) { const parts = target.split('.'); target = parts.pop() ?? '_'; t = {}; ctx.setVar(true, t, target, parts.length ? parts : undefined); } target = t; } if (!target || !source || (typeof target !== 'object') || (typeof source !== 'object')) { return target; } for (const e in source) { const v = source[e]; if (v === undefined || target[e] !== undefined) { continue; } target[e] = v; } return target; }), ['new']: createConvoScopeFunction(scope => { const type = convoValueToZodType(scope.paramValues?.[0]); if (!valueIsZodObject(type)) { throw new ConvoError('invalid-args', { statement: scope.s }, 'The first arg of new should be a type variable'); } const r = type.safeParse(scope.paramValues?.[1] ?? {}); if (r.success) { return r.data; } else { throw new ConvoError('missing-defaults', { statement: scope.s }, 'The type has missing property defaults and can not be used with the new function - ' + r.error.message); } }), tmpl: createConvoScopeFunction((scope, ctx) => { const name = scope.paramValues?.[0]; const format = scope.paramValues?.[1] ?? 'plain'; const md = ctx.getVar(convoVars.__md, null, null); if (!md) { return ''; } return getConvoMarkdownVar(name, format, ctx); }), html: createConvoScopeFunction((scope) => { const params = scope.paramValues; if (!params?.length) { return ''; } if (params.length === 1) { return escapeHtml(params[0]?.toString?.() ?? ''); } const out = []; for (let i = 0; i < params.length; i++) { const p = params[i]; if (!p) { out.push(escapeHtml(p.toString?.() ?? '')); } } return out.join('\n'); }), describeStruct, xAtt: createConvoScopeFunction((scope) => { const value = scope.paramValues?.[0]; switch (typeof value) { case 'string': if (value.startsWith('{') && value.endsWith('}')) { return `"\\${escapeHtml(value)}"`; } else { return `"${escapeHtml(value)}"`; } case 'number': case 'boolean': case 'bigint': return `"{${value}}"`; case 'undefined': return '"{undefined}"'; default: try { return `'{${escapeHtmlKeepDoubleQuote(JSON.stringify(value))}}'`; } catch (ex) { return `'{${escapeHtmlKeepDoubleQuote(JSON.stringify({ __error: getErrorMessage(ex) }))}}'`; } } }), [convoFunctions.enableRag]: createConvoScopeFunction((scope, ctx) => { ctx.enableRag(Array.isArray(scope.paramValues) ? scope.paramValues : undefined); }), [convoFunctions.clearRag]: createConvoScopeFunction((scope, ctx) => { ctx.clearRag(); }), [convoFunctions.shortUuid]: createConvoScopeFunction(() => { return shortUuid(); }), [convoFunctions.uuid]: createConvoScopeFunction(() => { return uuid(); }), [convoFunctions.getVar]: createConvoScopeFunction((scope, ctx) => { const name = scope.paramValues?.[0]; if (typeof name !== 'string') { return undefined; } const v = ctx.getVar(name, scope); return v === undefined ? scope.paramValues?.[1] : v; }), [convoFunctions.setVar]: createConvoScopeFunction((scope, ctx) => { if (!scope.paramValues || scope.paramValues.length < 2) { return undefined; } let name = ''; for (let i = 0; i < scope.paramValues.length - 1; i++) { const n = scope.paramValues[i]; if (n) { name += (name ? '.' : '') + n; } } const v = scope.paramValues[scope.paramValues.length - 1]; if (name.includes('.')) { const parts = name.split('.'); ctx.setVar(true, v, parts.shift(), parts); } else { ctx.setVar(true, v, name); } return v; }), [convoFunctions.describeScene]: createConvoScopeFunction((scope, ctx) => { const ctrl = ctx.getVar(convoVars.__sceneCtrl); if (ctrl instanceof SceneCtrl) { const scene = ctrl.buildScene(); ctx.setVar(true, scene, convoVars.__lastDescribedScene); return createConvoSceneDescription(scene); } else { return 'Unable to see scene'; } }), [convoFunctions.getAgentList]: createConvoScopeFunction((scope, ctx) => { const agents = ctx.convo.conversation?.agents; if (!agents?.length) { return `<agentList>\n(no agents defined)\n</agentList>`; } return `<agentList>\n${agents.map(a => { const out = [`<agent>\nName: ${a.name}\n`]; if (a.description) { out.push(`Description: ${a.description}\n\n`); } if (a.capabilities.length) { out.push('Capabilities:\n'); for (const cap of a.capabilities) { out.push(`- ${cap}\n`); } } out.push('</agent>'); return out.join(''); }).join('\n\n')}\n</agentList>`; }), [convoFunctions.defineForm]: createConvoScopeFunction((scope, ctx) => { const form = deepClone(scope.paramValues?.[0]); if (!form) { return form; } if (ctx.getVar(convoVars.__formsEnabled) === undefined) { ctx.setVar(true, true, convoVars.__formsEnabled); } let formsAry = ctx.getVar(convoVars.__forms); if (!formsAry || !Array.isArray(formsAry)) { formsAry = []; ctx.setVar(true, formsAry, convoVars.__forms); } if (!form.id) { form.id = 'form-' + (formsAry.length + 1); } if (!form.items) { form.items = []; } for (let i = 0; i < form.items.length; i++) { let item = form.items[i]; if (!item) { form.items.splice(i, 1); i--; continue; } if (typeof item === 'string') { item = { id: 'item-' + (i + 1), type: 'question', question: item, }; form.items[i] = item; } if (!item.id) { item.id = 'item-' + (i + 1); } } formsAry.push(form); }), [convoFunctions.enableTransform]: createConvoScopeFunction((scope, ctx) => { if (!scope.paramValues) { return; } for (const t of scope.paramValues) { if (typeof t === 'string') { enableTransform(t, ctx); } else if (Array.isArray(t)) { for (const at of t) { if (typeof at === 'string') { enableTransform(at, ctx); } } } } }), [convoFunctions.enableAllTransforms]: createConvoScopeFunction((scope, ctx) => { enableTransform('all', ctx); }), [convoFunctions.pushConvoTask]: createConvoScopeFunction((scope, ctx) => { if (!scope.paramValues) { return; } const str = scope.paramValues.map(v => { if (typeof v === 'string') { return v; } else if (v === undefined || v === null) { return ''; } else { try { return JSON.stringify(v); } catch { return v + ''; } } }).join(' '); return ctx.convo.conversation?.addTask({ name: str }); }), [convoFunctions.popConvoTask]: createConvoScopeFunction((scope, ctx) => { ctx.convo.conversation?.popTask(); }), [convoFunctions.fsFullPath]: createConvoScopeFunction(async (scope, ctx) => { return ctx.getFullPath(scope.paramValues?.[0], scope); }), [convoFunctions.joinPaths]: createConvoScopeFunction(async (scope) => { if (!scope.paramValues) { return '.'; } const paths = scope.paramValues.filter(v => v && (typeof v === 'string')); return joinPaths(...paths); }), [convoFunctions.isUndefined]: createConvoScopeFunction(async (scope) => { if (!scope.paramValues) { return true; } for (const v of scope.paramValues) { if (v !== undefined) { return false; } } return true; }), [convoFunctions.secondMs]: createConvoScopeFunction(async (scope) => { return (scope.paramValues?.[0] ?? 0) * 1000; }), [convoFunctions.minuteMs]: createConvoScopeFunction(async (scope) => { return (scope.paramValues?.[0] ?? 0) * 60000; }), [convoFunctions.hourMs]: createConvoScopeFunction(async (scope) => { return (scope.paramValues?.[0] ?? 0) * 3600000; }), [convoFunctions.dayMs]: createConvoScopeFunction(async (scope) => { return (scope.paramValues?.[0] ?? 0) * 86400000; }), [convoFunctions.aryFindMatch]: createConvoScopeFunction(async (scope) => { const ary = scope.paramValues?.[0]; const match = scope.paramValues?.[1]; if (!Array.isArray(ary)) { return undefined; } for (let i = 0; i < ary.length; i++) { const item = ary[i]; if (isShallowEqualTo(match, item)) { return item; } } return undefined; }), [convoFunctions.aryRemoveMatch]: createConvoScopeFunction(async (scope) => { const ary = scope.paramValues?.[0]; const match = scope.paramValues?.[1]; if (!Array.isArray(ary)) { return false; } for (let i = 0; i < ary.length; i++) { const item = ary[i]; if (isShallowEqualTo(match, item)) { ary.splice(i, 1); return true; } } return false; }), }; Object.freeze(defaultConvoVarsBase); export const extendedConvoVars = { httpGet: createConvoScopeFunction(async (scope) => { let url = scope.paramValues?.[0]; let options = scope.paramValues?.[1]; if (typeof url !== 'string') { throw new ConvoError('invalid-args', { statement: scope.s }, "First arg must be a string URL"); } if (options && (typeof options !== 'object')) { url += `${url.includes('?') ? '&' : '?'}__input=${encodeURIComponent(options.toString())}`; options = undefined; } return await httpClient().getAsync(url, options); }), httpGetString: createConvoScopeFunction(async (scope) => { const url = scope.paramValues?.[0]; const options = scope.paramValues?.[1]; if (typeof url !== 'string') { throw new ConvoError('invalid-args', { statement: scope.s }, "First arg must be a string URL"); } return await httpClient().getStringAsync(url, options); }), httpPost: createConvoScopeFunction(async (scope) => { const url = scope.paramValues?.[0]; const body = scope.paramValues?.[1]; const options = scope.paramValues?.[2]; if (typeof url !== 'string') { throw new ConvoError('invalid-args', { statement: scope.s }, "First arg must be a string URL"); } return await httpClient().postAsync(url, body, options); }), httpPut: createConvoScopeFunction(async (scope) => { const url = scope.paramValues?.[0]; const body = scope.paramValues?.[1]; const options = scope.paramValues?.[2]; if (typeof url !== 'string') { throw new ConvoError('invalid-args', { statement: scope.s }, "First arg must be a string URL"); } return await httpClient().putAsync(url, body, options); }), httpPatch: createConvoScopeFunction(async (scope) => { const url = scope.paramValues?.[0]; const body = scope.paramValues?.[1]; const options = scope.paramValues?.[2]; if (typeof url !== 'string') { throw new ConvoError('invalid-args', { statement: scope.s }, "First arg must be a string URL"); } return await httpClient().patchAsync(url, body, options); }), httpDelete: createConvoScopeFunction(async (scope) => { const url = scope.paramValues?.[0]; const options = scope.paramValues?.[1]; if (typeof url !== 'string') { throw new ConvoError('invalid-args', { statement: scope.s }, "First arg must be a string URL"); } return await httpClient().deleteAsync(url, options); }), openBrowserWindow: createConvoScopeFunction((scope) => { const url = scope.paramValues?.[0]; const target = scope.paramValues?.[1] ?? '_blank'; if ((typeof url !== 'string') || (typeof target !== 'string')) { return false; } globalThis.window?.open(url, target); return true; }), [convoFunctions.readDoc]: convoScopeFunctionReadDoc, [convoFunctions.fsWriteJson]: createConvoScopeFunction(async (scope, ctx) => { if (typeof scope.paramValues?.[0] !== 'string') { throw new ConvoError('invalid-args', { statement: scope.s }, 'fsWriteJson expects first argument to be a string'); } const path = ctx.getFullPath(scope.paramValues[0], scope); const value = scope.paramValues[1]; await vfs().writeObjectAsync(path, value); return value; }), [convoFunctions.fsReadJson]: createConvoScopeFunction(async (scope, ctx) => { if (typeof scope.paramValues?.[0] !== 'string') { throw new ConvoError('invalid-args', { statement: scope.s }, 'fsReadJson expects first argument to be a string'); } const path = ctx.getFullPath(scope.paramValues?.[0], scope); try { return await vfs().readObjectAsync(path); } catch { return undefined; } }), [convoFunctions.fsMultiRead]: createConvoScopeFunction(async (scope, ctx) => { const options = scope.paramValues?.[0]; if (!options || (typeof options !== 'object') || (!Array.isArray(options.names))) { throw new ConvoError('invalid-args', { statement: scope.s }, 'fsMultiRead expects first argument an object of type ConvoMultiReadOptions'); } const all = await Promise.all(options.names.map(async (name) => { const path = ctx.getFullPath(options.pattern ? options.pattern.replace('*', name) : name, scope); let value = await vfs().readStringAsync(path); if (options.tagItemsWithName) { value = `<${name}>\n${value}\n</${name}>`; } if (options.itemTag) { value = `<${options.itemTag}>\n${value}\n</${options.itemTag}>`; } return value; })); let value = all.join('\n\n'); if (options.tag) { value = `<${options.tag}>\n${value}\n</${options.tag}>`; } return value; }), [convoFunctions.fsReadBase64]: createConvoScopeFunction(async (scope, ctx) => { const [url] = scope.paramValues ?? []; if (typeof url !== 'string') { throw new ConvoError('invalid-args', { statement: scope.s }, 'fsReadBase64 expects first argument to be a string'); } const path = ctx.getFullPath(url, scope); const data = await vfs().readStringAsync(path); return base64Encod