genaiscript
Version:
A CLI for GenAIScript, a generative AI scripting framework.
178 lines • 7.17 kB
JavaScript
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
import { select, input, confirm, password } from "@inquirer/prompts";
import { run } from "@genaiscript/api";
import { MODEL_PROVIDERS, createYAML, deleteUndefinedValues, logInfo, logVerbose, logWarn, resolveLanguageModelConfigurations, resolveRuntimeHost, stderr, tryReadText, } from "@genaiscript/core";
import { parse } from "dotenv";
import { writeFile } from "fs/promises";
import { URL } from "node:url";
const YAML = createYAML();
/**
* Configures a selected language model provider by updating the environment file
* with necessary configuration parameters. Provides options to test or edit
* configurations interactively via CLI prompts.
*
* @param options - Options object for configuration.
* @param options.provider - The identifier of the provider to configure. If not provided, the user will be prompted to select one.
*
* The function guides the user through:
* - Selecting a provider if not specified.
* - Retrieving and displaying current configuration details.
* - Testing the provider's configuration.
* - Editing environment variables interactively. Supports secret values, enumerations, and basic validation.
* - Patching the environment file with updated values.
*/
export async function configure(options) {
const runtimeHost = resolveRuntimeHost();
while (true) {
const provider = options?.provider
? MODEL_PROVIDERS.find(({ id }) => options.provider === id)
: await select({
message: "Select a LLM provider to configure",
choices: MODEL_PROVIDERS.filter(({ hidden }) => !hidden).map((p) => ({
name: p.detail,
value: p,
description: `'${p.id}': https://microsoft.github.io/genaiscript/configuration/${p.id}`,
})),
});
if (!provider)
break;
logInfo(`configuring ${provider.id} (${provider.detail})`);
logVerbose(`- docs: https://microsoft.github.io/genaiscript/configuration/${provider.id}`);
while (true) {
const config = await runtimeHost.readConfig();
logVerbose(`- env file: ${config.envFile[0]}`);
const envText = (await tryReadText(config.envFile[0])) || "";
const env = parse(envText);
const conn = (await resolveLanguageModelConfigurations(provider.id, {
token: false,
error: true,
models: true,
}))?.[0];
if (conn) {
const { error, models, ...rest } = conn;
logInfo("");
logInfo(YAML.stringify(deleteUndefinedValues({
configuration: deleteUndefinedValues({
...rest,
models: models?.length ?? undefined,
}),
})));
if (error)
logWarn(`error: ${error}`);
else
logInfo(`configuration found!`);
}
else {
logWarn(`no configuration found`);
}
if (!provider.env) {
logInfo(`sorry, this provider is not yet configurable through the cli`);
break;
}
const envVars = Object.entries(provider.env);
if (!envVars.length) {
logInfo(`this provider does not have configuration flags`);
break;
}
if (!conn?.error) {
const test = await confirm({
message: `do you want to test the configuration?`,
});
if (test) {
const res = await run("configuration-tester", [], {
jsSource: `script({
unlisted: true,
system: [],
systemSafety: false,
})
$\`Write a one-word poem in code.\`
`,
provider: provider.id,
runTrace: false,
});
stderr.write("\n");
if (!res || res.error)
logWarn(`chat error!`);
else
logInfo(`chat successful!`);
}
const edit = await confirm({
message: `do you want to edit the configuration?`,
});
if (!edit)
break;
}
for (const ev of envVars) {
const [name, info] = ev;
const oldValue = env[name];
let value = oldValue;
if (value) {
const edit = await confirm({
message: `found a value for ${name}, do you want to edit?`,
});
if (!edit)
continue;
}
if (info.description)
logVerbose(`${name}: ${info.description}`);
if (info.secret) {
value = await password({
message: `enter a value for ${name}`,
mask: false,
});
}
else if (info.enum) {
value = await select({
message: `select a value for ${name}`,
default: value,
choices: info.enum.map((v) => ({
name: v,
value: v,
})),
});
}
else {
value = await input({
message: `enter a value for ${name}`,
default: value,
required: info.required,
theme: {
validationFailureMode: "keep",
},
validate: (v) => {
console.log(v);
if (info.format === "url") {
if (v && !URL.canParse(v))
return "invalid url";
}
return true;
},
});
}
if (value === "")
continue;
if (value !== oldValue)
await patchEnvFile(config.envFile[0], name, value);
}
}
}
}
async function patchEnvFile(filePath, key, value) {
logVerbose(`patching ${filePath}, ${key}`);
const fileContent = (await tryReadText(filePath)) || "";
const lines = fileContent.split("\n");
let found = false;
const updatedLines = lines.map((line) => {
if (line.startsWith(`${key}=`)) {
found = true;
return `${key}=${value}`;
}
return line;
});
if (!found) {
updatedLines.push(`${key}=${value}`);
}
await writeFile(filePath, updatedLines.join("\n"), "utf-8");
}
//# sourceMappingURL=configure.js.map