UNPKG

@iyio/convo-lang

Version:

A conversational language.

1,227 lines (1,225 loc) 69.3 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.Conversation = void 0; const common_1 = require("@iyio/common"); const rxjs_1 = require("rxjs"); const ConvoError_1 = require("./ConvoError"); const ConvoExecutionContext_1 = require("./ConvoExecutionContext"); const convo_lib_1 = require("./convo-lib"); const convo_parser_1 = require("./convo-parser"); const convo_types_1 = require("./convo-types"); const convo_zod_1 = require("./convo-zod"); const convo_deps_1 = require("./convo.deps"); const createConvoVisionFunction_1 = require("./createConvoVisionFunction"); class Conversation { get convo() { return this._convo.join('\n\n'); } getConvoStrings() { return [...this._convo]; } get messages() { return this._messages; } get onAppend() { return this._onAppend; } get activeTaskCountSubject() { return this._activeTaskCount; } get activeTaskCount() { return this._activeTaskCount.value; } get trackTimeSubject() { return this._trackTime; } get trackTime() { return this._trackTime.value; } set trackTime(value) { if (value == this._trackTime.value) { return; } this._trackTime.next(value); } get trackTokensSubject() { return this._trackTokens; } get trackTokens() { return this._trackTokens.value; } set trackTokens(value) { if (value == this._trackTokens.value) { return; } this._trackTokens.next(value); } get trackModelSubject() { return this._trackModel; } get trackModel() { return this._trackModel.value; } set trackModel(value) { if (value == this._trackModel.value) { return; } this._trackModel.next(value); } get debugModeSubject() { return this._debugMode; } get debugMode() { return this._debugMode.value; } set debugMode(value) { if (value == this._debugMode.value) { return; } this._debugMode.next(value); } get flatSubject() { return this._flat; } /** * A reference to the last flattening of the conversation */ get flat() { return this._flat.value; } get subTasksSubject() { return this._subTasks; } get subTasks() { return this._subTasks.value; } get beforeAppend() { return this._beforeAppend; } constructor(options = {}) { this._convo = []; this._messages = []; this._onAppend = new rxjs_1.Subject(); this._activeTaskCount = new rxjs_1.BehaviorSubject(0); this._debugMode = new rxjs_1.BehaviorSubject(false); this._flat = new rxjs_1.BehaviorSubject(null); this._subTasks = new rxjs_1.BehaviorSubject([]); this._beforeAppend = new rxjs_1.Subject(); /** * Unregistered variables will be available during execution but will not be added to the code * of the conversation. For example the __cwd var is often used to set the current working * directory but is not added to the conversation code. */ this.unregisteredVars = {}; this.externFunctions = {}; this.print = convo_lib_1.defaultConvoPrintFunction; this._isDisposed = false; this._defaultApiKey = null; this.enabledCapabilities = []; this.autoFlattenId = 0; this.autoFlatPromiseRef = null; this._isCompleting = new rxjs_1.BehaviorSubject(false); this.importMessages = []; this.debugToConversation = (...args) => { if (!args.length) { return; } const out = []; for (const v of args) { if (typeof v === 'string') { out.push(v); } else { try { out.push(JSON.stringify(v, null, 4) ?? ''); } catch { out.push(v?.toString() ?? ''); } } } const debugComment = (0, convo_lib_1.convoStringToComment)(out.join('\n')); this.append(`> debug\n${debugComment}`); }; this.definitionItems = []; this.preAssignMessages = []; const { userRoles = ['user'], roleMap = {}, completionService = convo_deps_1.convoCompletionService.get(), capabilities = [], serviceCapabilities = [], maxAutoCompleteDepth = 10, trackTime = false, trackTokens = false, trackModel = false, disableAutoFlatten = false, autoFlattenDelayMs = 30, ragCallback, debug, debugMode, disableMessageCapabilities = false, initConvo, defaultVars, onConstructed, define, } = options; this.defaultOptions = options; this.defaultVars = defaultVars ? defaultVars : {}; this.userRoles = userRoles; this.roleMap = roleMap; this.completionService = completionService; this.capabilities = [...capabilities]; this.disableMessageCapabilities = disableMessageCapabilities; this.serviceCapabilities = serviceCapabilities; this.maxAutoCompleteDepth = maxAutoCompleteDepth; this._trackTime = new rxjs_1.BehaviorSubject(trackTime); this._trackTokens = new rxjs_1.BehaviorSubject(trackTokens); this._trackModel = new rxjs_1.BehaviorSubject(trackModel); this.disableAutoFlatten = disableAutoFlatten; this.autoFlattenDelayMs = autoFlattenDelayMs; this.ragCallback = ragCallback; this.debug = debug; if (debugMode) { this.debugMode = true; } if (initConvo) { this.append(initConvo, true); } if (define) { this.define(define); } onConstructed?.(this); } getMessageListCapabilities(msgs) { let firstMsg; let lastMsg; for (let i = 0; i < msgs.length; i++) { const f = msgs[i]; if (f && (!f.fn || f.fn.topLevel)) { firstMsg = f; break; } } for (let i = msgs.length - 1; i >= 0; i--) { const f = msgs[i]; if (f && (!f.fn || f.fn.topLevel)) { lastMsg = f; break; } } if (!firstMsg && !lastMsg) { return []; } if (firstMsg === lastMsg) { lastMsg = undefined; } const tags = []; if (firstMsg?.tags) { const t = (0, convo_lib_1.mapToConvoTags)(firstMsg.tags); tags.push(...t); } if (lastMsg?.tags) { const t = (0, convo_lib_1.mapToConvoTags)(lastMsg.tags); tags.push(...t); } return this.getMessageCapabilities(tags) ?? []; } /** * Gets the capabilities enabled by the given tags. If disableMessageCapabilities is true * undefined is always returned */ getMessageCapabilities(tags) { if (!tags || this.disableMessageCapabilities) { return undefined; } let capList; for (const tag of tags) { switch (tag.name) { case convo_lib_1.convoTags.capability: if (tag.value) { const caps = tag.value.split(','); for (const c of caps) { const cap = c.trim(); if ((0, convo_types_1.isConvoCapability)(cap) && !capList?.includes(cap)) { if (!capList) { capList = []; } capList.push(cap); } } } break; case convo_lib_1.convoTags.enableVision: if (!capList?.includes('vision')) { if (!capList) { capList = []; } capList.push('vision'); } } } if (!capList) { return undefined; } for (const cap of capList) { this.enableCapability(cap); } return capList; } get isDisposed() { return this._isDisposed; } dispose() { if (this._isDisposed) { return; } this.autoFlattenId++; this._isDisposed = true; } setDefaultApiKey(key) { this._defaultApiKey = key; } getDefaultApiKey() { return this._defaultApiKey; } parseCode(code) { return (0, convo_parser_1.parseConvoCode)(code, { parseMarkdown: this.defaultOptions.parseMarkdown }); } enableCapability(cap) { if (this.enabledCapabilities.includes(cap)) { return; } this.enabledCapabilities.push(cap); switch (cap) { case 'vision': this.define({ hidden: true, fn: (0, createConvoVisionFunction_1.createConvoVisionFunction)() }, true); break; } } autoUpdateCompletionService() { if (!this.completionService) { this.completionService = convo_deps_1.convoCompletionService.get(); } } createChild(options) { const convo = new Conversation({ ...this.defaultOptions, debug: this.debugToConversation, debugMode: this.shouldDebug(), ...options }); if (this._defaultApiKey) { convo.setDefaultApiKey(this._defaultApiKey); } return convo; } /** * Creates a new Conversation and appends the messages of this conversation to the newly * created conversation. */ clone(options) { const conversation = new Conversation(this.defaultOptions); let messages = this.messages; if (options?.noFunctions) { messages = messages.filter(m => !m.fn || m.fn.topLevel); } if (options?.systemOnly) { messages = messages.filter(m => m.role === 'system' || m.fn?.topLevel || m.fn?.name === convo_lib_1.convoFunctions.getState); } conversation.appendMessageObject(messages); for (const e in this.externFunctions) { const f = this.externFunctions[e]; if (f) { conversation.externFunctions[e] = f; } } return conversation; } /** * Creates a new Conversation and appends the system messages of this conversation to the newly * created conversation. */ cloneSystem() { return this.clone({ systemOnly: true }); } /** * Creates a new Conversation and appends the non-function messages of this conversation to the newly * created conversation. */ cloneWithNoFunctions() { return this.clone({ noFunctions: true }); } /** * Appends new messages to the conversation and by default does not add code to the conversation. */ appendMessageObject(message, { disableAutoFlatten, appendCode } = {}) { const messages = (0, common_1.asArray)(message); this._messages.push(...messages); if (appendCode) { for (const msg of messages) { let messages = (0, convo_lib_1.convoMessageToString)(msg); if (this._beforeAppend.observed) { messages = this.transformMessageBeforeAppend(messages); } this._convo.push(messages); } } this._onAppend.next({ text: '', messages, }); if (!this.disableAutoFlatten && !disableAutoFlatten) { this.autoFlattenAsync(false); } } transformMessageBeforeAppend(messages) { const append = { text: messages, messages: [] }; this._beforeAppend.next(append); return append.text; } append(messages, mergeWithPrev = false, throwOnError = true) { let visibleContent = undefined; let hasHidden = false; if (Array.isArray(messages)) { hasHidden = messages.some(m => (typeof m === 'object') ? m.hidden : false); if (hasHidden) { visibleContent = messages.filter(m => (typeof m === 'string') || !m.hidden).map(m => (typeof m === 'string') ? m : m.content).join(''); messages = messages.map(m => (typeof m === 'string') ? m : m.content).join(''); } else { messages = messages.map(m => (typeof m === 'string') ? m : m.content).join(''); } } if (this._beforeAppend.observed) { messages = this.transformMessageBeforeAppend(messages); } const r = this.parseCode(messages); if (r.error) { if (!throwOnError) { return r; } throw r.error; } if (hasHidden) { messages = visibleContent ?? ''; } if (messages) { if (mergeWithPrev && this._convo.length) { this._convo[this._convo.length - 1] += '\n' + messages; } else { this._convo.push(messages); } } if (r.result) { for (const m of r.result) { this._messages.push(m); } } this._onAppend.next({ text: messages, messages: r.result ?? [] }); if (!this.disableAutoFlatten) { this.autoFlattenAsync(false); } return r; } async autoFlattenAsync(skipDelay) { const id = ++this.autoFlattenId; if (this.autoFlattenDelayMs > 0 && !skipDelay) { await (0, common_1.delayAsync)(this.autoFlattenDelayMs); if (this.isDisposed || id !== this.autoFlattenId) { return undefined; } } return await this.getAutoFlattenPromise(id); } getAutoFlattenPromise(id) { if (this.autoFlatPromiseRef?.id === id) { return this.autoFlatPromiseRef.promise; } const promise = this.setFlattenAsync(id); this.autoFlatPromiseRef = { id, promise, }; return promise; } async setFlattenAsync(id) { const flat = await this.flattenAsync(undefined, { setCurrent: false }); if (this.isDisposed || id !== this.autoFlattenId) { return undefined; } this.setFlat(flat, false); return flat; } /** * Get the flattened version of this Conversation. * @param noCache If true the Conversation will not used the current cached version of the * flattening and will be re-flattened. */ async getLastAutoFlatAsync(noCache = false) { if (noCache) { return (await this.autoFlattenAsync(true)) ?? this.flat ?? undefined; } return (this.flat ?? (await this.getAutoFlattenPromise(this.autoFlattenId)) ?? this.flat ?? undefined); } appendUserMessage(message, options) { this.append((0, convo_lib_1.formatConvoMessage)('user', message, this.getPrefixTags(options))); } appendAssistantMessage(message, options) { this.append((0, convo_lib_1.formatConvoMessage)('assistant', message, this.getPrefixTags(options))); } appendMessage(role, message, options) { this.append((0, convo_lib_1.formatConvoMessage)(role, message, this.getPrefixTags(options))); } appendDefine(defineCode, description) { return this.append((description ? (0, convo_lib_1.convoDescriptionToComment)(description) + '\n' : '') + '> define\n' + defineCode); } appendTopLevel(defineCode, description) { return this.append((description ? (0, convo_lib_1.convoDescriptionToComment)(description) + '\n' : '') + '> do\n' + defineCode); } getVar(nameOrPath, defaultValue) { return this._flat.value?.exe?.getVar(nameOrPath, null, defaultValue); } getPrefixTags(options) { let tags = ''; const msg = options?.msg; if (this.trackTime || this.getVar(convo_lib_1.convoVars.__trackTime)) { tags += `@${convo_lib_1.convoTags.time} ${(0, convo_lib_1.getConvoDateString)()}\n`; } if (!msg) { return tags; } if (options?.includeTokenUsage && (this.trackTokens || this.getVar(convo_lib_1.convoVars.__trackTokenUsage))) { tags += `@${convo_lib_1.convoTags.tokenUsage} ${(0, convo_lib_1.convoUsageTokensToString)(msg)}\n`; } if (msg.model && (this.trackModel || this.getVar(convo_lib_1.convoVars.__trackModel))) { tags += `@${convo_lib_1.convoTags.model} ${msg.model}\n`; } if (msg.endpoint) { tags += `@${convo_lib_1.convoTags.endpoint} ${msg.endpoint}\n`; } if (msg.format) { tags += `@${convo_lib_1.convoTags.format} ${msg.format}\n`; } if (msg.assignTo) { tags += `@${convo_lib_1.convoTags.assign} ${msg.assignTo}\n`; } return tags; } setFlat(flat, dup = true) { if (this.isDisposed) { return; } this.autoFlattenId++; this._flat.next(dup ? { ...flat } : flat); } async callFunctionAsync(fn, args = {}, options) { const c = await this.tryCompleteAsync(options?.task, options, flat => { if (typeof fn === 'object') { flat.exe.loadFunctions([{ fn, role: 'function' }]); } return [{ callFn: typeof fn === 'string' ? fn : fn.name, callParams: args }]; }); return c.returnValues?.[0]; } appendFunctionCall(functionName, args) { this.append(`@${convo_lib_1.convoTags.toolId} call_${(0, common_1.shortUuid)()}\n> call ${functionName}(${args === undefined ? '' : (0, convo_lib_1.spreadConvoArgs)(args, true)})`); } completeWithFunctionCallAsync(name, args, options) { this.appendFunctionCall(name, args); return this.completeAsync(options); } /** * Appends a user message then competes the conversation * @param append Optional message to append before submitting */ completeUserMessageAsync(userMessage) { this.appendUserMessage(userMessage); return this.completeAsync(); } /** * Submits the current conversation and optionally appends messages to the conversation before * submitting. * @param append Optional message to append before submitting */ async completeAsync(appendOrOptions) { if (typeof appendOrOptions === 'string') { this.append(appendOrOptions); appendOrOptions = undefined; } if (appendOrOptions?.append) { this.append(appendOrOptions.append); } const completionService = this.completionService; if (appendOrOptions?.debug) { console.info('Conversation.completeAsync:\n', appendOrOptions.append); } const result = await this.tryCompleteAsync(appendOrOptions?.task, appendOrOptions, flat => completionService?.completeConvoAsync(flat) ?? []); if (appendOrOptions?.debug) { console.info('Conversation.completeAsync Result:\n', result.messages ? (result.messages.length === 1 ? result.messages[0] : result.messages) : result); } return result; } /** * Completes the conversation and returns the last message as JSON. It is recommended using * `@json` mode with the last message that is appended. */ async completeJsonAsync(appendOrOptions) { const r = await this.completeAsync(appendOrOptions); if (r.message?.content === undefined) { return undefined; } try { return (0, convo_lib_1.parseConvoJsonMessage)(r.message.content); } catch { return undefined; } } /** * Completes the conversation and returns the last message as JSON. It is recommended using * `@json` mode with the last message that is appended. */ async completeJsonSchemeAsync(params, userMessage) { const r = await this.completeAsync(/*convo*/ ` > define JsonScheme=${(0, convo_zod_1.zodSchemeToConvoTypeString)(params)} @json JsonScheme > user ${(0, convo_lib_1.escapeConvoMessageContent)(userMessage)} `); if (r.message?.content === undefined) { return undefined; } try { return (0, convo_lib_1.parseConvoJsonMessage)(r.message.content); } catch { return undefined; } } /** * Completes the conversation and returns the last message call params. The last message of the * conversation should instruct the LLM to call a function. */ async callStubFunctionAsync(appendOrOptions) { if (appendOrOptions === undefined) { appendOrOptions = {}; } else if (typeof appendOrOptions === 'string') { appendOrOptions = { append: appendOrOptions }; } appendOrOptions.returnOnCall = true; const r = await this.completeAsync(appendOrOptions); return r.message?.callParams; } async tryCompleteAsync(task, additionalOptions, getCompletion, autoCompleteDepth = 0, prevCompletion, preReturnValues) { if (this._isCompleting.value) { return { status: 'busy', messages: [], task: task ?? convo_lib_1.defaultConvoTask, }; } else { this._isCompleting.next(true); try { return await this._completeAsync(additionalOptions?.usage, task, additionalOptions, getCompletion, autoCompleteDepth, prevCompletion, preReturnValues); } finally { this._isCompleting.next(false); } } } get isCompletingSubject() { return this._isCompleting; } get isCompleting() { return this._isCompleting.value; } async _completeAsync(usage, task, additionalOptions, getCompletion, autoCompleteDepth, prevCompletion, preReturnValues, templates, updateTaskCount = true, lastFnCall) { if (task === undefined) { task = convo_lib_1.defaultConvoTask; } const isDefaultTask = task === convo_lib_1.defaultConvoTask; if (updateTaskCount) { this._activeTaskCount.next(this.activeTaskCount + 1); } const messageStartIndex = this._messages.length; try { const append = []; const flatExe = this.createConvoExecutionContext(append); const flat = await this.flattenAsync(flatExe, { setCurrent: false, task, discardTemplates: !isDefaultTask || templates !== undefined, threadFilter: additionalOptions?.threadFilter, }); if (flat.templates && !templates) { templates = flat.templates; } const exe = flat.exe; if (this._isDisposed) { return { messages: [], status: 'disposed', task }; } for (const e in this.unregisteredVars) { flat.exe.setVar(false, this.unregisteredVars[e], e); } let ragDoc; let ragMsg; const lastFlatMsg = (0, convo_lib_1.getLastCompletionMessage)(flat.messages); if (lastFlatMsg && task === convo_lib_1.defaultConvoTask && flat.ragMode && this.ragCallback && lastFlatMsg?.role === convo_lib_1.convoRoles.user) { const ragParams = flat.exe.getVar(convo_lib_1.convoVars.__ragParams); const tol = flat.exe.getVar(convo_lib_1.convoVars.__ragTol); ragDoc = await this.ragCallback({ params: ragParams && (typeof ragParams === 'object') ? ragParams : {}, lastMessage: lastFlatMsg, flat, conversation: this, tolerance: (typeof tol === 'number') ? tol : convo_lib_1.defaultConvoRagTol }); } if (ragDoc) { ragMsg = (0, convo_lib_1.convoRagDocRefToMessage)(ragDoc, convo_lib_1.convoRoles.rag); flat.messages.push(this.flattenMsg(ragMsg, true)); this.applyRagMode(flat.messages, flat.ragMode); this.appendMessageObject(ragMsg, { disableAutoFlatten: true, appendCode: true }); } if (isDefaultTask) { this.setFlat(flat); } const lastMsg = this.messages[this.messages.length - 1]; const lastMsgIsFnCall = lastMsg?.fn?.call ? true : false; const completion = (lastMsg?.fn?.call ? [{ callFn: lastMsg.fn.name, callParams: exe.getConvoFunctionArgsValue(lastMsg.fn), tags: { toolId: (0, convo_lib_1.getConvoTag)(lastMsg.tags, convo_lib_1.convoTags.toolId)?.value ?? '' } }] : await getCompletion(flat)); if (this._isDisposed) { return { messages: [], status: 'disposed', task }; } let cMsg = undefined; let returnValues = undefined; let lastResultValue = undefined; for (const msg of completion) { let includeTokenUsage = (msg.inputTokens || msg.outputTokens) ? true : false; const tagsCode = msg.tags ? (0, convo_lib_1.convoTagMapToCode)(msg.tags, '\n') : ''; if (msg.format === 'json' && msg.content) { let json = (0, convo_lib_1.parseConvoJsonMessage)(msg.content); if (msg.formatIsArray && !Array.isArray(json) && Array.isArray(json.values)) { json = json.values; } msg.content = JSON.stringify(json, null, 4); } if (msg.content) { cMsg = msg; if (isDefaultTask) { this.append(`${this.getPrefixTags({ includeTokenUsage, msg })}${tagsCode}> ${this.getReversedMappedRole(msg.role)}\n${(0, convo_lib_1.escapeConvoMessageContent)(msg.content)}\n`); } includeTokenUsage = false; } if (additionalOptions?.returnOnCall && msg.callFn) { cMsg = msg; } else if (msg.callFn) { let callMessage; if (lastMsgIsFnCall) { callMessage = lastMsg; if (!callMessage) { throw new Error('Call last message failed'); } } else { const callMsg = `${this.getPrefixTags({ includeTokenUsage, msg })}${tagsCode}> call ${msg.callFn}(${msg.callParams === undefined ? '' : (0, convo_lib_1.spreadConvoArgs)(msg.callParams, true)})`; const result = isDefaultTask ? this.append(callMsg) : this.parseCode(callMsg); includeTokenUsage = false; callMessage = result.result?.[0]; if (result.result?.length !== 1 || !callMessage) { throw new ConvoError_1.ConvoError('function-call-parse-count', { completion: msg }, 'failed to parse function call. Exactly 1 function call should have been parsed'); } } exe.clearSharedSetters(); if (!callMessage.fn?.call) { continue; } const callResult = await exe.executeFunctionResultAsync(callMessage.fn); if (this._isDisposed) { return { messages: [], status: 'disposed', task }; } const callResultValue = callResult.valuePromise ? (await callResult.valuePromise) : callResult.value; const target = this._messages.find(m => m.fn && !m.fn.call && m.fn?.name === callMessage?.fn?.name); const disableAutoComplete = (!isDefaultTask || exe.getVarEx(convo_lib_1.convoDisableAutoCompleteName, undefined, callResult.scope, false) === true || (0, convo_lib_1.containsConvoTag)(target?.tags, convo_lib_1.convoTags.disableAutoComplete)); if (!returnValues) { returnValues = []; } returnValues.push(callResultValue); if (target?.fn) { lastFnCall = { name: target.fn.name, message: target, fn: target.fn, returnValue: callResultValue }; } lastResultValue = (typeof callResultValue === 'function') ? undefined : callResultValue; const lines = [`${this.getPrefixTags()}> result`]; if (exe.sharedSetters) { for (const s of exe.sharedSetters) { lines.push(`${s}=${JSON.stringify(exe.sharedVars[s], null, 4)}`); } } if ((typeof lastResultValue === 'string') && lastResultValue.length > 50 && lastResultValue.includes('\n') && !lastResultValue.includes('---')) { lines.push(`${convo_lib_1.convoResultReturnName}=---\n${lastResultValue}\n---`); } else { lines.push(`${convo_lib_1.convoResultReturnName}=${JSON.stringify(lastResultValue, null, 4)}`); } lines.push(''); if (isDefaultTask) { this.append(lines.join('\n'), true); } if (disableAutoComplete) { lastResultValue = undefined; } if (isDefaultTask) { this.setFlat(flat); } } if (includeTokenUsage && isDefaultTask) { this.append(`${this.getPrefixTags({ includeTokenUsage, msg })}> define\n// token usage placeholder`); includeTokenUsage = false; } } if (prevCompletion) { completion.unshift(...prevCompletion); } if (preReturnValues) { if (returnValues) { returnValues.unshift(...preReturnValues); } else { returnValues = preReturnValues; } } if (append.length) { if (isDefaultTask) { for (const a of append) { this.append(a); } } } else if (lastResultValue !== undefined && autoCompleteDepth < this.maxAutoCompleteDepth && !additionalOptions?.returnOnCalled) { return await this._completeAsync(undefined, task, additionalOptions, getCompletion, autoCompleteDepth + 1, completion, returnValues, templates, undefined, lastFnCall); } if (isDefaultTask && templates?.length) { this.writeTemplates(templates, flat); } if (!this.disableAutoFlatten && isDefaultTask) { this.autoFlattenAsync(false); } if (flat.taskTriggers?.[convo_lib_1.convoTaskTriggers.onResponse]) { this.startSubTasks(flat, convo_lib_1.convoTaskTriggers.onResponse, getCompletion, autoCompleteDepth + 1, additionalOptions); } return { status: 'complete', message: cMsg, messages: completion, exe, returnValues, lastFnCall, task }; } finally { if (usage) { (0, convo_lib_1.addConvoUsageTokens)(usage, this.getTokenUsage(messageStartIndex)); } if (updateTaskCount) { this._activeTaskCount.next(this.activeTaskCount - 1); } } } writeTemplates(templates, flat) { for (let i = 0; i < templates.length; i++) { const tmpl = templates[i]; if (!tmpl?.watchPath || !tmpl.message.statement?.source) { continue; } const value = flat.exe.getVar(tmpl.watchPath); if (value !== tmpl.startValue && (tmpl.matchValue === undefined ? true : (value?.toString() ?? '') === tmpl.matchValue)) { const tags = []; if (tmpl.message.tags) { for (let t = 0; t < tmpl.message.tags.length; t++) { const tag = tmpl.message.tags[t]; if (!tag || tag.name === convo_lib_1.convoTags.template) { continue; } tags.push(`@${tag.name}${tag.value ? ' ' + tag.value : ''}\n`); } } const tmplMsg = `@${convo_lib_1.convoTags.sourceTemplate}${tmpl.name ? ' ' + tmpl.name : ''}\n${tags.join('')}${tmpl.message.statement.source}`; this.append(tmplMsg); } } } startSubTasks(flat, trigger, getCompletion, autoCompleteDepth, additionalOptions) { const tasks = flat.taskTriggers?.[trigger]; if (!tasks?.length) { return; } let added = false; const remove = []; const subs = tasks.map(task => { const promise = this._completeAsync(undefined, task, additionalOptions, getCompletion, autoCompleteDepth); const sub = { name: task, promise }; promise.then(() => { if (added) { (0, common_1.removeBehaviorSubjectAryValue)(this._subTasks, sub); } else { remove.push(sub); } }); return sub; }); (0, common_1.pushBehaviorSubjectAryMany)(this._subTasks, subs); added = true; if (remove.length) { (0, common_1.removeBehaviorSubjectAryValueMany)(this._subTasks, remove); } } getReversedMappedRole(role) { if (!role) { return 'user'; } for (const e in this.roleMap) { if (this.roleMap[e] === role) { return e; } } return role; } getMappedRole(role) { if (!role) { return 'user'; } return this.roleMap[role] ?? role; } createConvoExecutionContext(append = []) { const flatExe = new ConvoExecutionContext_1.ConvoExecutionContext({ conversation: this, convoPipeSink: (value) => { if (!(typeof value === 'string')) { value = value?.toString(); if (!value?.trim()) { return value; } } append.push(value); return value; } }); flatExe.print = this.print; for (const e in this.defaultVars) { flatExe.setVar(true, this.defaultVars[e], e); } return flatExe; } flattenMsg(msg, setContent) { const flat = { role: this.getMappedRole(msg.role), tags: msg.tags ? (0, convo_lib_1.convoTagsToMap)(msg.tags) : undefined, }; if (setContent) { flat.content = msg.content; } if (msg.component !== undefined) { flat.component = msg.component; } if (msg.sourceId !== undefined) { flat.sourceId = msg.sourceId; } if (msg.sourceUrl !== undefined) { flat.sourceUrl = msg.sourceUrl; } if (msg.sourceName !== undefined) { flat.sourceName = msg.sourceName; } if (msg.isSuggestion !== undefined) { flat.isSuggestion = msg.isSuggestion; } if (msg.renderTarget) { flat.renderTarget = msg.renderTarget; } if (msg.renderOnly) { flat.renderOnly = true; } if (msg.markdown) { flat.markdown = msg.markdown; } if (this.userRoles.includes(flat.role)) { flat.isUser = true; } if (msg.tid) { flat.tid = msg.tid; } return flat; } async loadImportsAsync(msg) { if (this.importMessages.includes(msg) || !msg.tags) { return; } const handler = this.defaultOptions.importHandler; if (!handler) { throw new Error('No conversation import handler defined'); } this.importMessages.push(msg); const index = Math.max(0, this._messages.indexOf(msg)); for (const t of msg.tags) { if (t.name !== convo_lib_1.convoTags.import || !t.value) { continue; } await this.importAsync(t.value, index); } } async importAsync(name, index) { const handler = this.defaultOptions.importHandler; if (!handler) { throw new Error('No conversation import handler defined'); } const result = await handler({ name }); if (!result) { throw new Error(`Convo import (${name}) not found`); } let convo = result.convo ?? ''; if (result.type) { convo = ('> define\n' + (0, common_1.asArray)(result.type).map(t => `${t.name} = ${(0, convo_zod_1.schemeToConvoTypeString)(t.type)}`).join('\n') + '\n\n' + convo); } if (!convo) { return []; } const r = this.parseCode(convo); if (r.error) { throw r.error; } if (r.result) { this._messages.splice(index ?? this._messages.length, 0, ...r.result); } return r.result ?? []; } async flattenAsync(exe = this.createConvoExecutionContext(), { task = convo_lib_1.defaultConvoTask, setCurrent = task === convo_lib_1.defaultConvoTask, discardTemplates, threadFilter, } = {}) { const isDefaultTask = task === convo_lib_1.defaultConvoTask; const messages = []; const edgePairs = []; const mdVarCtx = { indexMap: {}, vars: {}, varCount: 0, }; exe.setVar(true, mdVarCtx.vars, convo_lib_1.convoVars.__md); exe.loadFunctions(this._messages, this.externFunctions); let hasNonDefaultTasks = false; let maxTaskMsgCount = -1; let taskTriggers; let templates; for (let i = 0; i < this._messages.length; i++) { const msg = this._messages[i]; if (!msg) { continue; } if ((0, convo_lib_1.containsConvoTag)(msg.tags, convo_lib_1.convoTags.import) && !this.importMessages.includes(msg)) { await this.loadImportsAsync(msg); i--; continue; } if (msg.role === 'user' && !msg.content && !msg.statement) { continue; } const template = (0, convo_lib_1.getConvoTag)(msg.tags, convo_lib_1.convoTags.template)?.value; if (template) { if (discardTemplates) { continue; } const tmpl = (0, convo_lib_1.parseConvoMessageTemplate)(msg, template); if (!templates) { templates = []; } templates.push(tmpl); continue; } threadFilter = this.getThreadFilter(exe, threadFilter); if (threadFilter && !(0, convo_lib_1.isConvoThreadFilterMatch)(threadFilter, msg.tid)) { continue; } const flat = this.flattenMsg(msg, false); const setMdVars = (this.defaultOptions.setMarkdownVars || (0, convo_lib_1.containsConvoTag)(msg.tags, convo_lib_1.convoTags.markdownVars)); const shouldParseMd = (setMdVars || this.defaultOptions.parseMarkdown || (0, convo_lib_1.containsConvoTag)(msg.tags, convo_lib_1.convoTags.markdown)); const msgTask = (0, convo_lib_1.getConvoTag)(msg.tags, convo_lib_1.convoTags.task)?.value ?? convo_lib_1.defaultConvoTask; if (msgTask !== convo_lib_1.defaultConvoTask) { hasNonDefaultTasks = true; flat.task = msgTask; if (isDefaultTask) { const trigger = (0, convo_lib_1.getConvoTag)(msg.tags, convo_lib_1.convoTags.taskTrigger)?.value; if (trigger) { if (!taskTriggers) { taskTriggers = {}; } const ary = taskTriggers[trigger] ?? (taskTriggers[trigger] = []); if (!ary.includes(msgTask)) { ary.push(msgTask); } } } } if (msgTask === task) { const maxTasks = (0, convo_lib_1.getConvoTag)(msg.tags, convo_lib_1.convoTags.maxTaskMessageCount)?.value; if (maxTasks) { maxTaskMsgCount = (0, common_1.safeParseNumber)(maxTasks, maxTaskMsgCount); } } if (msg.fn) { if (msg.fn.local || msg.fn.call) { continue; } else if (msg.fn.topLevel) { exe.clearSharedSetters(); const r = exe.executeFunction(msg.fn); if (r.valuePromise) { await r.valuePromise; } if (exe.sharedSetters.length) { const varSetter = { role: msg.role ?? 'define', }; varSetter.setVars = {}; for (const v of exe.sharedSetters) { varSetter.setVars[v] = exe.getVar(v); } messages.push(varSetter); } const prev = this._messages[i - 1]; if (msg.role === 'result' && prev?.fn?.call) { flat.role = 'function'; flat.called = prev.fn; flat.calledReturn = exe.getVarEx(convo_lib_1.convoResultReturnName, undefined, undefined, false); flat.calledParams = exe.getConvoFunctionArgsValue(prev.fn); if (prev.tags) { flat.tags = flat.tags ? { ...(0, convo_lib_1.convoTagsToMap)(prev.tags), ...flat.tags } : (0, convo_lib_1.convoTagsToMap)(prev.tags); } } else { continue; } } else { flat.role = 'function'; flat.fn = msg.fn; flat.fnParams = exe.getConvoFunctionArgsScheme(msg.fn); } } else if (msg.statement) { if ((0, convo_lib_1.containsConvoTag)(msg.tags, convo_lib_1.convoTags.edge)) { flat.edge = true; edgePairs.push({ flat, msg: msg, shouldParseMd, setMdVars }); } else { await flattenMsgAsync(exe, msg.statement, flat, shouldParseMd); } } else if (msg.content !== undefined) { if ((0, convo_lib_1.containsConvoTag)(msg.tags, convo_lib_1.convoTags.concat)) { const prev = messages[messages.length - 1]; if (prev?.content !== undefined) { const tag = (0, convo_lib_1.getConvoTag)(msg.tags, convo_lib_1.convoTags.condition); if (tag?.value && !this.isTagConditionTrue(exe, tag.value)) { continue; } prev.content += '\n\n' + msg.content; continue; } } flat.content = msg.content; } else { continue; } messages.push(flat); if (!flat.edge) { this.applyTagsAndState(msg, flat, messages, exe, setMdVars, mdVarCtx); } } for (const pair of edgePairs) { if (pair.msg.statement) { await flattenMsgAsync(exe, pair.msg.statement, pair.flat, pair.shouldParseMd); } this.applyTagsAndState(pair.msg, pair.flat, messages, exe, pair.setMdVars, mdVarCtx); } const ragStr = exe.getVar(convo_lib_1.convoVars.__rag); const ragMode = (0, convo_types_1.isConvoRagMode)(ragStr) ? ragStr : undefined; this.applyRagMode(messages, ragMode); let capabilities = [...this.serviceCapabilities, ...this.getMessageListCapabilities(messages)]; if (!isDefaultTask) { capabilities = capabilities.filter(c => c !== 'vision'); } if (capabilities.includes('vision') && isDefaultTask) { const systemMessage = messages.find(m => m.role === 'system'); const content = exe.getVar(convo_lib_1.convoVars.__visionServiceSystemMessage, null, convo_lib_1.defaultConvoVisionSystemMessage); if (systemMessage) { systemMessage.content = ((systemMessage.content ? systemMessage.content + '\n\n' : '') + content); } else { messages.unshift({ role: 'system', content }); } } const shouldDebug = this.shouldDebug(exe); const debug = shouldDebug ? (this.debug ?? this.debugToConversation) : undefined; if (shouldDebug) { exe.print = (...args) => { debug?.(...args); return (0, convo_lib_1.defaultConvoPrintFunction)(...args); }; } if (!isDefaultTask || hasNonDefaultTasks) { if (isDefaultTask) { for (let i = 0; i < messages.length; i++) { const msg = messages[i]; if ((msg?.task ?? convo_lib_1.defaultConvoTask) !== convo_lib_1.defaultConvoTask) { messages.splice(i, 1); i--; } } } else { const taskMsgs = []; let taskHasSystem = false; let otherMsgCount = 0; for (let i = 0; i < messages.length; i++) { const msg = messages[i]; if (!msg) { continue; } if (msg.task === task) { if (msg.role === 'system') { taskHasSystem = true; } taskMsgs.push(msg); messages.splice(i, 1); i--; } else if (msg.fn || msg.called) { messages.splice(i, 1); i--; } else if (msg.role !== 'system') { otherMsgCount++; } } if (taskHasSystem) { for (let i = 0; i < messages.length; i++) { const msg = messages[i]; if (msg?.role === 'system') { messages.splice(i, 1); i--; } } } if (maxTaskMsgCount !== -1 && otherMsgCount > maxTaskMsgCount) { let index = 0; while (otherMsgCount > maxTaskMsgCount && index < messages.length) { const msg = messages[index]; if (!msg || msg.role === 'system') { index++; continue; } messages.splice(index, 1); otherMsgCount--; } } for (let i = 0; i < taskMsgs.length; i++) { const msg = taskMsgs[i]; if (msg) { messages.push(msg); } }