UNPKG

genaiscript

Version:

A CLI for GenAIScript, a generative AI scripting framework.

178 lines 7.17 kB
// 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