@convo-lang/convo-lang-cli
Version:
A Conversational Language
263 lines • 9.62 kB
JavaScript
import { openAiApiKeyParam, openAiAudioModelParam, openAiBaseUrlParam, openAiChatModelParam, openAiImageModelParam, openAiSecretsParam, openAiVisionModelParam } from '@convo-lang/ai-complete-openai';
import { convoCapabilitiesParams, convoVars, createConversationFromScope, parseConvoCode } from "@convo-lang/convo-lang";
import { openaiConvoModule } from '@convo-lang/convo-lang-openai';
import { EnvParams, createJsonRefReplacer, deleteUndefined, initRootScope, rootScope } from "@iyio/common";
import { parseJson5 } from '@iyio/json5';
import { nodeCommonModule, pathExistsAsync, readFileAsJsonAsync, readFileAsStringAsync, readStdInAsStringAsync, readStdInLineAsync, startReadingStdIn } from "@iyio/node-common";
import { writeFile } from "fs/promises";
import { homedir } from 'node:os';
import { createConvoExec } from './convo-exec';
let configPromise = null;
export const getConvoCliConfigAsync = (options) => {
return configPromise ?? (configPromise = _getConfigAsync(options));
};
const _getConfigAsync = async (options) => {
if (options.inlineConfig) {
const inlineConfig = parseJson5(options.inlineConfig);
if (inlineConfig.overrideEnv === undefined) {
inlineConfig.overrideEnv = true;
}
return inlineConfig;
}
let configPath = options.config ?? '~/.config/convo/convo.json';
if (configPath.startsWith('~')) {
configPath = homedir() + configPath.substring(1);
}
const configExists = await pathExistsAsync(configPath);
if (!configExists && options.config !== undefined) {
throw new Error(`Convo config file not found a path - ${configPath}`);
}
return await readFileAsJsonAsync(configPath);
};
let initPromise = null;
export const initConvoCliAsync = (options) => {
return initPromise ?? (initPromise = _initAsync(options));
};
const _initAsync = async (options) => {
const config = await getConvoCliConfigAsync(options);
initRootScope(reg => {
if (config.env && !config.overrideEnv) {
reg.addParams(config.env);
}
if (!options.config && !options.inlineConfig) {
reg.addParams(new EnvParams());
}
if (config.env && config.overrideEnv) {
reg.addParams(config.env);
}
reg.addParams(deleteUndefined({
[openAiApiKeyParam.typeName]: config.apiKey,
[openAiBaseUrlParam.typeName]: config.apiBaseUrl,
[openAiChatModelParam.typeName]: config.chatModel,
[openAiAudioModelParam.typeName]: config.audioModel,
[openAiImageModelParam.typeName]: config.imageModel,
[openAiVisionModelParam.typeName]: config.visionModel,
[openAiSecretsParam.typeName]: config.secrets,
[convoCapabilitiesParams.typeName]: config.capabilities,
}));
reg.use(nodeCommonModule);
reg.use(openaiConvoModule);
});
await rootScope.getInitPromise();
return config;
};
/**
* Initializes the ConvoCli environment the returns a new ConvoCli object
*/
export const createConvoCliAsync = async (options) => {
await initConvoCliAsync(options);
return new ConvoCli(options);
};
const appendOut = (prefix, value, out) => {
if (typeof value !== 'string') {
value = JSON.stringify(value, 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');
}
}
};
export class ConvoCli {
constructor(options) {
this.buffer = [];
this._isDisposed = false;
this.dynamicFunctionCallback = (scope) => {
return new Promise((r, j) => {
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 readStdInLineAsync()).toLowerCase();
return line === 'yes' || line === 'y';
}
else {
return false;
}
};
this.allowExec = options.allowExec;
this.options = options;
this.convo = createConversationFromScope(rootScope);
if (options.cmdMode) {
this.convo.dynamicFunctionCallback = this.dynamicFunctionCallback;
startReadingStdIn();
}
if (options.prepend) {
this.convo.append(options.prepend);
}
if (this.options.exeCwd) {
this.convo.unregisteredVars[convoVars.__cwd] = this.options.exeCwd;
}
}
get isDisposed() { return this._isDisposed; }
dispose() {
if (this._isDisposed) {
return;
}
this._isDisposed = true;
this.convo.dispose();
}
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() {
const config = await initConvoCliAsync(this.options);
if (!this.allowExec) {
this.allowExec = config.allowExec ?? 'ask';
}
if (this.options.inline) {
if (this.options.parse) {
await this.parseCodeAsync(this.options.inline);
}
else {
await this.executeSourceCode(this.options.inline);
}
this.writeOutputAsync();
}
else if (this.options.source || this.options.stdin) {
const source = this.options.stdin ?
await readStdInAsStringAsync() :
await readFileAsStringAsync(this.options.source ?? '');
if (this.options.parse) {
await this.parseCodeAsync(source);
}
else {
await this.executeSourceCode(source);
}
this.writeOutputAsync();
}
}
async executeSourceCode(code) {
this.convo.defineFunction({
name: 'exec',
registerOnly: true,
scopeCallback: createConvoExec(typeof this.allowExec === 'function' ?
this.allowExec : this.execConfirmAsync)
});
this.convo.append(code);
const r = await this.convo.completeAsync();
if (r.error) {
throw r.error;
}
await this.outAsync(this.convo.convo);
}
async parseCodeAsync(code) {
const r = 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 writeFile(out, this.buffer.join(''));
}
}
//# sourceMappingURL=ConvoCli.js.map