UNPKG

@convo-lang/convo-lang-cli

Version:
506 lines (503 loc) 18.8 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.ConvoCli = exports.createConvoCliAsync = exports.initConvoCliAsync = exports.getConvoCliConfigAsync = void 0; const convo_lang_1 = require("@convo-lang/convo-lang"); const convo_lang_bedrock_1 = require("@convo-lang/convo-lang-bedrock"); const convo_lang_browser_1 = require("@convo-lang/convo-lang-browser"); const convo_lang_make_1 = require("@convo-lang/convo-lang-make"); const common_1 = require("@iyio/common"); const json5_1 = require("@iyio/json5"); const node_common_1 = require("@iyio/node-common"); const vfs_1 = require("@iyio/vfs"); const vfs_node_1 = require("@iyio/vfs-node"); const promises_1 = require("fs/promises"); const node_os_1 = require("node:os"); const zod_1 = require("zod"); const convo_exec_js_1 = require("./convo-exec.js"); let configPromise = null; const getConvoCliConfigAsync = (options) => { return configPromise ?? (configPromise = _getConfigAsync(options)); }; exports.getConvoCliConfigAsync = getConvoCliConfigAsync; const _getConfigAsync = async (options) => { if (options.inlineConfig) { const inlineConfig = (0, json5_1.parseJson5)(options.inlineConfig); if (inlineConfig.overrideEnv === undefined) { inlineConfig.overrideEnv = true; } return inlineConfig; } let configPath = (typeof options.config === 'string' ? options.config : null) ?? '~/.config/convo/convo.json'; if (configPath.startsWith('~')) { configPath = (0, node_os_1.homedir)() + configPath.substring(1); } const configExists = await (0, node_common_1.pathExistsAsync)(configPath); if (!configExists && configPath && (typeof options.config !== 'object')) { throw new Error(`Convo config file not found a path - ${configPath}`); } let c = configExists ? await (0, node_common_1.readFileAsJsonAsync)(configPath) : {}; if (options.config && (typeof options.config === 'object')) { c = { ...c, ...(0, common_1.dupDeleteUndefined)(options.config), env: { ...c.env, ...(0, common_1.dupDeleteUndefined)(options.config.env) } }; } if (options.varsPath) { const defaultVars = c.defaultVars ?? (c.defaultVars = {}); for (const path of options.varsPath) { const value = (await (0, node_common_1.readFileAsStringAsync)(path)).trim(); if (value.startsWith('{')) { const obj = (0, json5_1.parseJson5)(value); if (obj && (typeof obj === 'object')) { for (const e in obj) { defaultVars[e] = obj[e]; } } } else { const lines = value.split('\n'); for (const l of lines) { const line = l.trim(); if (!line || line.startsWith('#')) { continue; } parseCliValue(line, defaultVars); } } } } if (options.vars) { const defaultVars = c.defaultVars ?? (c.defaultVars = {}); for (const v of options.vars) { const obj = (0, json5_1.parseJson5)(v); if (obj && (typeof obj === 'object')) { for (const e in obj) { defaultVars[e] = obj[e]; } } } } if (options.var) { const defaultVars = c.defaultVars ?? (c.defaultVars = {}); for (const v of options.var) { parseCliValue(v, defaultVars); } } return c; }; const parseCliValue = (value, assignTo) => { const i = value.indexOf('='); if (i == -1) { (0, common_1.setValueByPath)(assignTo, value, true); return; } const [name = '', type = 'string'] = value.substring(0, i).split(':'); let v = value.substring(i + 1); switch (type) { case 'number': (0, common_1.setValueByPath)(assignTo, name, Number(v) || 0); break; case 'bigint': (0, common_1.setValueByPath)(assignTo, name, BigInt(v) || 0); break; case 'boolean': (0, common_1.setValueByPath)(assignTo, name, (0, common_1.parseConfigBool)(v)); break; case 'object': (0, common_1.setValueByPath)(assignTo, name, (0, json5_1.parseJson5)(v)); break; default: (0, common_1.setValueByPath)(assignTo, name, v); break; } }; let initPromise = null; const initConvoCliAsync = (options) => { return initPromise ?? (initPromise = _initAsync(options)); }; exports.initConvoCliAsync = initConvoCliAsync; const _initAsync = async (options) => { const config = await (0, exports.getConvoCliConfigAsync)(options); const vfsCtrl = new vfs_1.VfsCtrl({ config: { mountPoints: [ { type: vfs_1.vfsMntTypes.file, mountPath: '/', sourceUrl: '/', } ], }, mntProviderConfig: { ctrls: [new vfs_node_1.VfsDiskMntCtrl()] }, }); const projectConfig = await (0, convo_lang_1.loadConvoProjectConfigFromVfsAsync)({ vfs: vfsCtrl, basePath: options.exeCwd ?? globalThis.process.cwd() }); (0, common_1.initRootScope)(reg => { if (config.env && !config.overrideEnv) { reg.addParams(config.env); } if (!options.config && !options.inlineConfig) { reg.addParams(new common_1.EnvParams()); } if (config.env && config.overrideEnv) { reg.addParams(config.env); } reg.addParams((0, common_1.deleteUndefined)({ [convo_lang_1.openAiApiKeyParam.typeName]: config.apiKey, [convo_lang_1.openAiBaseUrlParam.typeName]: config.apiBaseUrl, [convo_lang_1.openAiChatModelParam.typeName]: config.chatModel, [convo_lang_1.openAiAudioModelParam.typeName]: config.audioModel, [convo_lang_1.openAiImageModelParam.typeName]: config.imageModel, [convo_lang_1.openAiVisionModelParam.typeName]: config.visionModel, [convo_lang_1.openAiSecretsParam.typeName]: config.secrets, [convo_lang_1.convoCapabilitiesParams.typeName]: config.capabilities, [convo_lang_1.convoDefaultModelParam.typeName]: config.defaultModel?.trim() || 'gpt-4.1', })); reg.use(node_common_1.nodeCommonModule); reg.use(convo_lang_1.convoOpenAiModule); reg.use(convo_lang_bedrock_1.convoBedrockModule); if (config.env?.[convo_lang_1.openRouterApiKeyParam.typeName]) { reg.use(convo_lang_1.convoOpenRouterModule); } reg.implementService(convo_lang_1.convoImportService, () => new convo_lang_1.ConvoVfsImportService()); reg.implementService(convo_lang_1.convoImportService, () => new convo_lang_1.ConvoHttpImportService()); reg.implementService(vfs_1.vfs, () => vfsCtrl); if (projectConfig) { reg.implementService(convo_lang_1.convoProjectConfig, () => projectConfig); } }); await common_1.rootScope.getInitPromise(); return config; }; /** * Initializes the ConvoCli environment the returns a new ConvoCli object */ const createConvoCliAsync = async (options) => { await (0, exports.initConvoCliAsync)(options); return new ConvoCli(options); }; exports.createConvoCliAsync = createConvoCliAsync; const appendOut = (prefix, value, out) => { if (typeof value !== 'string') { value = JSON.stringify(value, (0, common_1.createJsonRefReplacer)(), 4); } if (!prefix) { out.push(value + '\n'); } else { const ary = value.split('\n'); for (let i = 0; i < ary.length; i++) { out.push(prefix + ary[i] + '\n'); } } }; class ConvoCli { constructor(options) { this.buffer = []; this._isDisposed = false; this.dynamicFunctionCallback = (scope) => { return new Promise((r, j) => { (0, node_common_1.readStdInLineAsync)().then(v => { if (v.startsWith('ERROR:')) { j(v.substring(6).trim()); return; } if (v.startsWith('RESULT:')) { v = v.substring(7); } v = v.trim(); try { r(JSON.parse(v)); } catch (ex) { j(ex); } }); const fn = scope.s.fn ?? 'function'; globalThis.process?.stdout.write(`CALL:${JSON.stringify({ fn, args: scope.paramValues ?? [] })}\n`); }); }; this.execConfirmAsync = async (command) => { if (this.allowExec === 'allow') { return true; } else if (this.allowExec === 'ask') { process.stdout.write(`Exec command requested\n> ${command}\nAllow y/N?\n`); const line = (await (0, node_common_1.readStdInLineAsync)()).toLowerCase(); return line === 'yes' || line === 'y'; } else { return false; } }; this.allowExec = options.allowExec; this.options = options; this.convo = (0, convo_lang_1.createConversationFromScope)(common_1.rootScope); if (options.config) if (options.cmdMode) { this.convo.dynamicFunctionCallback = this.dynamicFunctionCallback; (0, node_common_1.startReadingStdIn)(); } if (options.prepend) { this.convo.append(options.prepend); } if (this.options.exeCwd) { globalThis.process.chdir(this.options.exeCwd); this.convo.unregisteredVars[convo_lang_1.convoVars.__cwd] = (0, common_1.normalizePath)(globalThis.process.cwd()); } if (this.options.source) { this.convo.unregisteredVars[convo_lang_1.convoVars.__mainFile] = this.options.source; } } get isDisposed() { return this._isDisposed; } dispose() { if (this._isDisposed) { return; } this._isDisposed = true; this.convo.dispose(); } async runReplAsync(cancel) { if (!this.allowExec) { this.allowExec = 'ask'; } this.registerExec(true); this.convo.append(/*convo*/ ` > do __cwd=_getCwd() @edge > system The current working directory is: "{{__cwd}}" The current date and time is: "{{dateTime()}}" `); (0, node_common_1.startReadingStdIn)(); let len = this.convo.convo.length; console.log("Entering Convo-Lang REPL"); while (!this._isDisposed && !cancel?.isCanceled) { console.log('> user'); const line = await (0, node_common_1.readStdInLineAsync)(); if (this._isDisposed || cancel?.isCanceled) { return; } if (!line.trim()) { continue; } await this.convo.completeAsync('> user\n' + (0, convo_lang_1.escapeConvo)(line)); if (this._isDisposed || cancel?.isCanceled) { return; } const c = this.convo.convo; const append = c.substring(len); len = c.length; console.log(append); } } async outAsync(...chunks) { const { prefixOutput, cmdMode } = this.options; if (prefixOutput) { const str = chunks.join(''); chunks = []; appendOut(':', str, chunks); } if (this.options.printFlat || this.options.printState) { await this.convo.flattenAsync(); } if (this.options.printFlat) { const messages = this.convo.flat?.messages ?? []; for (const m of messages) { if (m.fn?.body) { delete m.fn.body; } } if (cmdMode) { chunks.push('FLAT:\n'); } appendOut(prefixOutput ? 'f:' : null, messages, chunks); } if (this.options.printState) { const vars = this.convo.flat?.exe.getUserSharedVars() ?? {}; if (cmdMode) { chunks.push('STATE:\n'); } appendOut(prefixOutput ? 's:' : null, vars, chunks); } if (this.options.printMessages) { const messages = this.convo.messages; if (cmdMode) { chunks.push('MESSAGES:\n'); } appendOut(prefixOutput ? 'm:' : null, messages, chunks); } if (cmdMode) { chunks.push('END:\n'); } if (this.options.out || this.options.bufferOutput) { if (typeof this.options.out === 'function') { this.options.out(...chunks); } else { this.buffer.push(...chunks); } } else { if (globalThis?.process?.stdout) { for (let i = 0; i < chunks.length; i++) { globalThis.process.stdout.write(chunks[i] ?? ''); } } else { console.log(chunks.join('')); } } } async executeAsync(cancel) { const config = await (0, exports.initConvoCliAsync)(this.options); if (config.defaultVars) { for (const e in config.defaultVars) { this.convo.defaultVars[e] = config.defaultVars[e]; } } if (!this.allowExec) { this.allowExec = config.allowExec ?? 'ask'; } let source; if (this.options.inline) { source = this.options.inline; } else if (this.options.source || this.options.stdin) { source = this.options.stdin ? await (0, node_common_1.readStdInAsStringAsync)() : await (0, node_common_1.readFileAsStringAsync)(this.options.source ?? ''); } if (source !== undefined) { let writeOut = true; if (this.options.make || this.options.makeTargets) { writeOut = false; await this.makeAsync(source); } else if (this.options.convert) { await this.convertCodeAsync(source); } else if (this.options.parse) { await this.parseCodeAsync(source); } else { await this.executeSourceCode(source); } if (writeOut) { this.writeOutputAsync(); } } if (this.options.repl) { await this.runReplAsync(cancel); } } registerExec(fullEnv) { this.convo.defineFunction({ name: 'exec', registerOnly: !fullEnv, description: 'Executes a shell command on the users computer', paramsType: zod_1.z.object({ cmd: zod_1.z.string().describe('The shell command to execute') }), scopeCallback: (0, convo_exec_js_1.createConvoExec)(typeof this.allowExec === 'function' ? this.allowExec : this.execConfirmAsync) }); if (fullEnv) { this.convo.defineFunction({ name: 'changeDirectory', description: 'Changes the current working directory', paramsType: zod_1.z.object({ dir: zod_1.z.string().describe('The directory to change to') }), callback: async ({ dir }) => { try { globalThis.process?.chdir(dir); return `Working directory change to ${globalThis.process.cwd()}`; } catch (ex) { return `Unable to move to specified directory: ${(0, common_1.getErrorMessage)(ex)}`; } } }); this.convo.defineFunction({ name: '_getCwd', local: true, callback: async (dir) => { return globalThis.process.cwd(); } }); } } async appendCodeAsync(code, options) { let filePath = options?.filePath ?? this.options.sourcePath ?? this.options.source; if (filePath) { filePath = await (0, promises_1.realpath)(filePath); } this.convo.append(code, { ...options, filePath }); } async executeSourceCode(code) { this.registerExec(false); await this.appendCodeAsync(code); const r = await this.convo.completeAsync(); if (r.error) { throw r.error; } await this.outAsync(this.convo.convo); } async makeAsync(code) { if (!this.options.exeCwd || !this.options.source) { return; } await this.appendCodeAsync(code); const flat = await this.convo.flattenAsync(); const options = (0, convo_lang_make_1.getConvoMakeOptionsFromVars)(this.options.source, this.options.exeCwd, flat.exe.sharedVars); if (!options) { return; } const ctrl = new convo_lang_make_1.ConvoMakeCtrl({ browserInf: new convo_lang_browser_1.ConvoBrowserCtrl(), ...options, }); try { if (this.options.makeTargets) { const debugOutput = await ctrl.getDebugOutputAsync(); await this.outAsync(JSON.stringify(debugOutput, null, 4)); } else { await ctrl.buildAsync(); } } finally { ctrl.dispose(); } } async convertCodeAsync(code) { await this.appendCodeAsync(code); const r = await this.convo.toModelInputStringAsync(); await this.outAsync(r); } async parseCodeAsync(code) { const r = (0, convo_lang_1.parseConvoCode)(code); if (r.error) { throw r.error; } await this.outAsync(JSON.stringify(r.result, null, this.options.parseFormat)); } async writeOutputAsync() { let out = this.options.out; if (out === '.') { out = this.options.source; } if (typeof out !== 'string') { return; } await (0, promises_1.writeFile)(out, this.buffer.join('')); } } exports.ConvoCli = ConvoCli; //# sourceMappingURL=ConvoCli.js.map