alwaysai
Version:
The alwaysAI command-line interface (CLI)
99 lines (92 loc) • 3.36 kB
text/typescript
import { CliTerseError } from '@alwaysai/alwayscli';
import { dirname, posix } from 'path';
import {
ModelId,
modelPackageCache,
downloadModelPackageToCache
} from '../model';
import { Spawner, RandomString, logger, stringifyError } from '../../util';
import { MODEL_JSON_FILE_NAME } from '../model/model-package-json-file';
import { ALWAYSAI_CLI_EXECUTABLE_NAME } from '../../constants';
import { APP_MODELS_DIRECTORY_NAME } from '../../paths';
export async function appInstallModel(
target: Spawner,
id: string,
version: number
) {
const { publisher, name } = ModelId.parse(id);
const destinationDir = posix.join(APP_MODELS_DIRECTORY_NAME, publisher, name);
let installedVersion: number | undefined = undefined;
try {
const parsed = await readModelJson(destinationDir);
installedVersion = parsed.version;
} catch (exception) {
logger.warn(
`Failed to read existing model config: ${stringifyError(exception)}`
);
// TODO finer-grained error handling
}
if (installedVersion !== version) {
const tmpId = RandomString();
const tmpDir = `${destinationDir}.${tmpId}.tmp`;
await target.mkdirp(tmpDir);
try {
if (!modelPackageCache.has(id, version)) {
await downloadModelPackageToCache(id, version);
}
try {
const modelPackageStream = modelPackageCache.read(id, version);
await target.untar(modelPackageStream, tmpDir);
} catch {
try {
await modelPackageCache.remove(id, version);
} catch {
throw new CliTerseError(
`Failed to install model due to corrupt cache. If the command continues to fail you may need to run ${ALWAYSAI_CLI_EXECUTABLE_NAME} model prune ${id} to remove the corrupt file`
);
}
}
const fileNames = await target.readdir(tmpDir);
// Sanity check
if (fileNames.length !== 1 || !fileNames[0]) {
throw new Error('Expected package to contain single directory');
}
// The model json file in the package does not have the version number,
// so we write it into the json file during install
await updateModelJson(posix.join(tmpDir, fileNames[0]), (modelJson) => ({
...modelJson,
version
}));
await target.rimraf(destinationDir);
await target.mkdirp(dirname(destinationDir));
await target.rename(
target.resolvePath(tmpDir, fileNames[0]),
destinationDir
);
} catch (exception) {
try {
// Attempt to delete new files since they may be corrupted
await target.rimraf(tmpDir);
await target.rimraf(destinationDir);
} finally {
// Do nothing
}
throw exception;
} finally {
await target.rimraf(tmpDir);
}
}
async function readModelJson(dir: string) {
const filePath = target.resolvePath(dir, MODEL_JSON_FILE_NAME);
const output = await target.readFile(filePath);
const parsed = JSON.parse(output);
return parsed;
}
async function updateModelJson(dir: string, updater: (current: any) => any) {
const parsed = await readModelJson(dir);
const updated = updater(parsed);
const filePath = target.resolvePath(dir, MODEL_JSON_FILE_NAME);
const serialized = JSON.stringify(updated, null, 2);
await target.writeFile(filePath, serialized);
}
}