@convo-lang/convo-lang-cli
Version:
The language of AI
506 lines (503 loc) • 18.8 kB
JavaScript
"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()
> 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