Skip to content
This repository was archived by the owner on Jul 4, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion cortex-js/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
"preuninstall": "node ./uninstall.js"
},
"dependencies": {
"@cortexso/cortex.js": "^0.1.5",
"@cortexso/cortex.js": "^0.1.7",
"@nestjs/axios": "^3.0.2",
"@nestjs/common": "^10.0.0",
"@nestjs/config": "^3.2.2",
Expand Down
1 change: 1 addition & 0 deletions cortex-js/src/domain/models/huggingface.interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export interface HuggingFaceRepoSibling {
downloadUrl?: string;
fileSize?: number;
quantization?: Quantization;
lfs?: { oid?: string };
}
export interface HuggingFaceRepoData {
id: string;
Expand Down
3 changes: 3 additions & 0 deletions cortex-js/src/infrastructure/commanders/base.command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,4 +34,7 @@ export abstract class BaseCommand extends CommandRunner {
}
await this.runCommand(passedParam, options);
}
protected setCortex(cortex: CortexClient) {
this.cortex = cortex;
}
}
16 changes: 14 additions & 2 deletions cortex-js/src/infrastructure/commanders/chat.command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import { Cortex } from '@cortexso/cortex.js';
import { ChatClient } from './services/chat-client';
import { downloadProgress } from '@/utils/download-progress';
import { DownloadType } from '@/domain/models/download.interface';
import { CortexClient } from './services/cortex.client';
import { checkRequiredVersion } from '@/utils/model-check';

type ChatOptions = {
threadId?: string;
Expand Down Expand Up @@ -96,8 +96,20 @@ export class ChatCommand extends BaseCommand {
await downloadProgress(this.cortex, undefined, DownloadType.Engine);
}

const { version: engineVersion } =
await this.cortex.engines.retrieve(engine);
if (
existingModel.engine_version &&
!checkRequiredVersion(existingModel.engine_version, engineVersion)
) {
console.log(
`Model engine version ${existingModel.engine_version} is not compatible with engine version ${engineVersion}`,
);
process.exit(1);
}

if (!message) options.attach = true;
this.telemetryUsecases.sendEvent(
void this.telemetryUsecases.sendEvent(
[
{
name: EventName.CHAT,
Expand Down
1 change: 1 addition & 0 deletions cortex-js/src/infrastructure/commanders/engines.command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ export class EnginesCommand extends BaseCommand {
) {
const commandInstance = this.moduleRef.get(commandClass, { strict: false });
if (commandInstance) {
commandInstance.setCortex(this.cortex);
await commandInstance.runCommand(params, options);
} else {
console.error('Command not found.');
Expand Down
1 change: 1 addition & 0 deletions cortex-js/src/infrastructure/commanders/models.command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ export class ModelsCommand extends BaseCommand {
) {
const commandInstance = this.moduleRef.get(commandClass, { strict: false });
if (commandInstance) {
commandInstance.setCortex(this.cortex);
await commandInstance.runCommand(params, options);
} else {
console.error('Command not found.');
Expand Down
7 changes: 6 additions & 1 deletion cortex-js/src/infrastructure/commanders/run.command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import ora from 'ora';
import { existsSync } from 'fs';
import { join } from 'path';
import { Engines } from './types/engine.interface';
import { checkModelCompatibility } from '@/utils/model-check';
import { checkModelCompatibility, checkRequiredVersion } from '@/utils/model-check';
import { BaseCommand } from './base.command';
import { isRemoteEngine } from '@/utils/normalize-model-id';
import { ChatClient } from './services/chat-client';
Expand Down Expand Up @@ -98,6 +98,11 @@ export class RunCommand extends BaseCommand {
await this.cortex.engines.init(engine);
await downloadProgress(this.cortex, undefined, DownloadType.Engine);
}
const { version: engineVersion } = await this.cortex.engines.retrieve(engine);
if(existingModel.engine_version && !checkRequiredVersion(existingModel.engine_version, engineVersion)) {
console.log(`Model engine version ${existingModel.engine_version} is not compatible with engine version ${engineVersion}`);
process.exit(1);
}

const startingSpinner = ora('Loading model...').start();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { Presets, SingleBar } from 'cli-progress';
import { createWriteStream, unlinkSync } from 'node:fs';
import { basename } from 'node:path';
import { firstValueFrom } from 'rxjs';
import crypto from 'crypto';

@Injectable()
export class DownloadManagerService {
Expand Down Expand Up @@ -51,7 +52,13 @@ export class DownloadManagerService {
downloadId: string,
title: string,
downloadType: DownloadType,
urlToDestination: Record<string, string>,
urlToDestination: Record<
string,
{
destination: string;
checksum?: string;
}
>,
finishedCallback?: () => Promise<void>,
inSequence: boolean = true,
) {
Expand All @@ -65,7 +72,7 @@ export class DownloadManagerService {

const downloadItems: DownloadItem[] = Object.keys(urlToDestination).map(
(url) => {
const destination = urlToDestination[url];
const { destination, checksum } = urlToDestination[url];
const downloadItem: DownloadItem = {
id: destination,
time: {
Expand All @@ -78,6 +85,7 @@ export class DownloadManagerService {
},
progress: 0,
status: DownloadStatus.Downloading,
checksum,
};

return downloadItem;
Expand Down Expand Up @@ -119,15 +127,15 @@ export class DownloadManagerService {
if (!inSequence) {
Promise.all(
Object.keys(urlToDestination).map((url) => {
const destination = urlToDestination[url];
return this.downloadFile(downloadId, url, destination);
const { destination, checksum } = urlToDestination[url];
return this.downloadFile(downloadId, url, destination, checksum);
}),
).then(callBack);
} else {
// Download model file in sequence
for (const url of Object.keys(urlToDestination)) {
const destination = urlToDestination[url];
await this.downloadFile(downloadId, url, destination);
const { destination, checksum } = urlToDestination[url];
await this.downloadFile(downloadId, url, destination, checksum);
}
return callBack();
}
Expand All @@ -137,7 +145,14 @@ export class DownloadManagerService {
downloadId: string,
url: string,
destination: string,
checksum?: string,
) {
console.log('Downloading', {
downloadId,
url,
destination,
checksum,
});
const controller = new AbortController();
// adding to abort controllers
this.abortControllers[downloadId][destination] = controller;
Expand All @@ -155,6 +170,7 @@ export class DownloadManagerService {
}

const writer = createWriteStream(destination);
const hash = crypto.createHash('sha256');
const totalBytes = Number(response.headers['content-length']);

// update download state
Expand Down Expand Up @@ -214,8 +230,23 @@ export class DownloadManagerService {
const downloadItem = currentDownloadState?.children.find(
(downloadItem) => downloadItem.id === destination,
);
const isFileBroken = checksum && checksum === hash.digest('hex');
if (downloadItem) {
downloadItem.status = DownloadStatus.Downloaded;
downloadItem.status = isFileBroken
? DownloadStatus.Error
: DownloadStatus.Downloaded;
if (isFileBroken) {
downloadItem.error = 'Checksum is not matched';
this.handleError(
new Error('Checksum is not matched'),
downloadId,
destination,
);
}
}
if (isFileBroken) {
currentDownloadState.status = DownloadStatus.Error;
currentDownloadState.error = 'Checksum is not matched';
}
this.eventEmitter.emit('download.event', this.allDownloadStates);
} finally {
Expand All @@ -234,6 +265,7 @@ export class DownloadManagerService {
});

response.data.on('data', (chunk: any) => {
hash.update(chunk);
resetTimeout();
transferredBytes += chunk.length;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ const writeAsync = promisify(write);

@Injectable()
export class FileManagerService {
private configFile = '.cortexrc';
private cortexDirectoryName = 'cortex';
private modelFolderName = 'models';
private presetFolderName = 'presets';
Expand All @@ -44,7 +43,10 @@ export class FileManagerService {
*/
async getConfig(dataFolderPath?: string): Promise<Config & object> {
const homeDir = os.homedir();
const configPath = join(homeDir, this.configFile);
const configPath = join(
homeDir,
this.getConfigFileName(this.configProfile),
);
const config = this.defaultConfig();
const dataFolderPathUsed = dataFolderPath || config.dataFolderPath;
if (!existsSync(configPath) || !existsSync(dataFolderPathUsed)) {
Expand All @@ -55,8 +57,7 @@ export class FileManagerService {

try {
const content = await promises.readFile(configPath, 'utf8');
const configs = yaml.load(content) as Record<string, Config>;
const config = configs?.[this.configProfile] ?? {};
const config = yaml.load(content) as Config & object;
return {
...this.defaultConfig(),
...config,
Expand All @@ -72,17 +73,20 @@ export class FileManagerService {

async writeConfigFile(config: Config & object): Promise<void> {
const homeDir = os.homedir();
const configPath = join(homeDir, this.configFile);
const configPath = join(
homeDir,
this.getConfigFileName(this.configProfile),
);

// write config to file as yaml
if (!existsSync(configPath)) {
await promises.writeFile(configPath, '', 'utf8');
}
const content = await promises.readFile(configPath, 'utf8');
const currentConfig = yaml.load(content) as Record<string, Config>;
const currentConfig = yaml.load(content) as Config & object;
const configString = yaml.dump({
...currentConfig,
[this.configProfile]: config,
...config,
});
await promises.writeFile(configPath, configString, 'utf8');
}
Expand Down Expand Up @@ -345,12 +349,17 @@ export class FileManagerService {
*/
getServerConfig(): { host: string; port: number } {
const homeDir = os.homedir();
const configPath = join(homeDir, this.configFile);
const configPath = join(
homeDir,
this.getConfigFileName(this.configProfile),
);
let config = this.defaultConfig();
try {
const content = readFileSync(configPath, 'utf8');
const configs = (yaml.load(content) as Record<string, Config>) ?? {};
config = configs?.[this.configProfile] ?? config;
const currentConfig = (yaml.load(content) as Config & object) ?? {};
if (currentConfig) {
config = currentConfig;
}
} catch {}
return {
host: config.apiServerHost ?? '127.0.0.1',
Expand All @@ -366,15 +375,22 @@ export class FileManagerService {
}
public profileConfigExists(profile: string): boolean {
const homeDir = os.homedir();
const configPath = join(homeDir, this.configFile);
const configPath = join(homeDir, this.getConfigFileName(profile));
try {
const content = readFileSync(configPath, 'utf8');
const configs = (yaml.load(content) as Record<string, Config>) ?? {};
return !!configs[profile];
const config = yaml.load(content) as Config & object;
return !!config;
} catch {
return false;
}
}

private getConfigFileName(configProfile: string): string {
if (configProfile === 'default') {
return '.cortexrc';
}
return `.${configProfile}rc`;
}
}

export const fileManagerService = new FileManagerService();
3 changes: 1 addition & 2 deletions cortex-js/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,10 @@ import { getApp } from './app';
import chalk from 'chalk';

async function bootstrap() {
const app = await getApp();
// getting port from env
const host = process.env.CORTEX_JS_HOST || defaultCortexJsHost;
const port = process.env.CORTEX_JS_PORT || defaultCortexJsPort;

const app = await getApp(host, Number(port));
try {
await app.listen(port, host);
console.log(chalk.blue(`Started server at http://${host}:${port}`));
Expand Down
4 changes: 2 additions & 2 deletions cortex-js/src/usecases/engines/engines.usecase.ts
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,7 @@ export class EnginesUsecases {
url,
'Cuda Toolkit Dependencies',
DownloadType.Engine,
{ [url]: destination },
{ [url]: { destination } },
async () => {
try {
await decompress(
Expand Down Expand Up @@ -274,7 +274,7 @@ export class EnginesUsecases {
toDownloadAsset.browser_download_url,
engine,
DownloadType.Engine,
{ [toDownloadAsset.browser_download_url]: destination },
{ [toDownloadAsset.browser_download_url]: { destination } },
// On completed - post processing
async () => {
try {
Expand Down
34 changes: 27 additions & 7 deletions cortex-js/src/usecases/models/models.usecases.ts
Original file line number Diff line number Diff line change
Expand Up @@ -399,14 +399,34 @@ export class ModelsUsecases {
}

// Start downloading the model
const toDownloads: Record<string, string> = files
const toDownloads: Record<
string,
{
destination: string;
checksum?: string;
}
> = files
.filter((e) => this.validFileDownload(e))
.reduce((acc: Record<string, string>, file) => {
if (file.downloadUrl)
acc[file.downloadUrl] = join(modelFolder, file.rfilename);
return acc;
}, {});

.reduce(
(
acc: Record<
string,
{
destination: string;
checksum?: string;
}
>,
file,
) => {
if (file.downloadUrl)
acc[file.downloadUrl] = {
destination: join(modelFolder, file.rfilename),
checksum: file.lfs.oid,
};
return acc;
},
{},
);
return this.downloadManagerService.submitDownloadRequest(
modelId,
modelId,
Expand Down
Loading