Skip to content
This repository was archived by the owner on Jul 4, 2025. It is now read-only.

Commit dd551e0

Browse files
authored
feat: ps command support (#621)
1 parent c086432 commit dd551e0

File tree

14 files changed

+205
-64
lines changed

14 files changed

+205
-64
lines changed

cortex-js/src/command.module.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ import { AssistantsModule } from './usecases/assistants/assistants.module';
2424
import { CliUsecasesModule } from './infrastructure/commanders/usecases/cli.usecases.module';
2525
import { MessagesModule } from './usecases/messages/messages.module';
2626
import { FileManagerModule } from './file-manager/file-manager.module';
27+
import { PSCommand } from './infrastructure/commanders/ps.command';
28+
import { KillCommand } from './infrastructure/commanders/kill.command';
2729

2830
@Module({
2931
imports: [
@@ -48,6 +50,8 @@ import { FileManagerModule } from './file-manager/file-manager.module';
4850
ServeCommand,
4951
ChatCommand,
5052
InitCommand,
53+
PSCommand,
54+
KillCommand,
5155

5256
// Questions
5357
InitRunModeQuestions,

cortex-js/src/infrastructure/commanders/cortex-command.commander.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import { ModelsCommand } from './models.command';
55
import { InitCommand } from './init.command';
66
import { RunCommand } from './shortcuts/run.command';
77
import { ModelPullCommand } from './models/model-pull.command';
8+
import { PSCommand } from './ps.command';
9+
import { KillCommand } from './kill.command';
810

911
@RootCommand({
1012
subCommands: [
@@ -14,6 +16,8 @@ import { ModelPullCommand } from './models/model-pull.command';
1416
InitCommand,
1517
RunCommand,
1618
ModelPullCommand,
19+
PSCommand,
20+
KillCommand,
1721
],
1822
description: 'Cortex CLI',
1923
})
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { CommandRunner, SubCommand } from 'nest-commander';
2+
import { CortexUsecases } from '@/usecases/cortex/cortex.usecases';
3+
4+
@SubCommand({
5+
name: 'kill',
6+
description: 'Kill running cortex processes',
7+
})
8+
export class KillCommand extends CommandRunner {
9+
constructor(private readonly usecases: CortexUsecases) {
10+
super();
11+
}
12+
async run(): Promise<void> {
13+
this.usecases.stopCortex().then(console.log);
14+
}
15+
}
Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
1-
import { CommandRunner, SubCommand } from 'nest-commander';
1+
import { CommandRunner, SubCommand, Option } from 'nest-commander';
22
import { exit } from 'node:process';
33
import { ModelsCliUsecases } from '../usecases/models.cli.usecases';
44
import { CortexUsecases } from '@/usecases/cortex/cortex.usecases';
55

6+
type ModelStartOptions = {
7+
attach: boolean;
8+
};
69
@SubCommand({ name: 'start', description: 'Start a model by ID.' })
710
export class ModelStartCommand extends CommandRunner {
811
constructor(
@@ -12,13 +15,26 @@ export class ModelStartCommand extends CommandRunner {
1215
super();
1316
}
1417

15-
async run(input: string[]): Promise<void> {
18+
async run(input: string[], options: ModelStartOptions): Promise<void> {
1619
if (input.length === 0) {
1720
console.error('Model ID is required');
1821
exit(1);
1922
}
2023

21-
await this.cortexUsecases.startCortex();
22-
await this.modelsCliUsecases.startModel(input[0]);
24+
await this.cortexUsecases
25+
.startCortex(options.attach)
26+
.then(() => this.modelsCliUsecases.startModel(input[0]))
27+
.then(console.log)
28+
.then(() => !options.attach && process.exit(0));
29+
}
30+
31+
@Option({
32+
flags: '-a, --attach',
33+
description: 'Attach to interactive chat session',
34+
defaultValue: false,
35+
name: 'attach',
36+
})
37+
parseAttach() {
38+
return true;
2339
}
2440
}

cortex-js/src/infrastructure/commanders/models/model-stop.command.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,9 @@ export class ModelStopCommand extends CommandRunner {
1818
exit(1);
1919
}
2020

21-
await this.modelsCliUsecases.stopModel(input[0]);
22-
await this.cortexUsecases.stopCortex();
21+
await this.modelsCliUsecases
22+
.stopModel(input[0])
23+
.then(() => this.cortexUsecases.stopCortex())
24+
.then(console.log);
2325
}
2426
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { CommandRunner, SubCommand } from 'nest-commander';
2+
import { PSCliUsecases } from './usecases/ps.cli.usecases';
3+
4+
@SubCommand({
5+
name: 'ps',
6+
description: 'Show running models and their status',
7+
})
8+
export class PSCommand extends CommandRunner {
9+
constructor(private readonly usecases: PSCliUsecases) {
10+
super();
11+
}
12+
async run(): Promise<void> {
13+
return this.usecases.getModels().then(console.table);
14+
}
15+
}

cortex-js/src/infrastructure/commanders/shortcuts/run.command.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,9 @@ export class RunCommand extends CommandRunner {
3030
const modelId = input[0];
3131

3232
await this.cortexUsecases.startCortex(
33+
false,
3334
defaultCortexCppHost,
3435
defaultCortexCppPort,
35-
false,
3636
);
3737
await this.modelsUsecases.startModel(modelId);
3838
await this.chatCliUsecases.chat(modelId, option?.threadId);

cortex-js/src/infrastructure/commanders/usecases/cli.usecases.module.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { ThreadsModule } from '@/usecases/threads/threads.module';
1010
import { AssistantsModule } from '@/usecases/assistants/assistants.module';
1111
import { MessagesModule } from '@/usecases/messages/messages.module';
1212
import { FileManagerModule } from '@/file-manager/file-manager.module';
13+
import { PSCliUsecases } from './ps.cli.usecases';
1314

1415
@Module({
1516
imports: [
@@ -22,7 +23,12 @@ import { FileManagerModule } from '@/file-manager/file-manager.module';
2223
MessagesModule,
2324
FileManagerModule,
2425
],
25-
providers: [InitCliUsecases, ModelsCliUsecases, ChatCliUsecases],
26-
exports: [InitCliUsecases, ModelsCliUsecases, ChatCliUsecases],
26+
providers: [
27+
InitCliUsecases,
28+
ModelsCliUsecases,
29+
ChatCliUsecases,
30+
PSCliUsecases,
31+
],
32+
exports: [InitCliUsecases, ModelsCliUsecases, ChatCliUsecases, PSCliUsecases],
2733
})
2834
export class CliUsecasesModule {}

cortex-js/src/infrastructure/commanders/usecases/models.cli.usecases.ts

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import {
2424
import { ModelTokenizer } from '../types/model-tokenizer.interface';
2525
import { HttpService } from '@nestjs/axios';
2626
import { firstValueFrom } from 'rxjs';
27+
import { StartModelSuccessDto } from '@/infrastructure/dtos/models/start-model-success.dto';
2728

2829
const AllQuantizations = [
2930
'Q3_K_S',
@@ -61,18 +62,25 @@ export class ModelsCliUsecases {
6162
* Start a model by ID
6263
* @param modelId
6364
*/
64-
async startModel(modelId: string): Promise<void> {
65-
await this.getModelOrStop(modelId);
66-
await this.modelsUsecases.startModel(modelId);
65+
async startModel(modelId: string): Promise<StartModelSuccessDto> {
66+
return this.getModelOrStop(modelId)
67+
.then(() => this.modelsUsecases.startModel(modelId))
68+
.catch(() => {
69+
return {
70+
modelId: modelId,
71+
message: 'Model not found',
72+
};
73+
});
6774
}
6875

6976
/**
7077
* Stop a model by ID
7178
* @param modelId
7279
*/
7380
async stopModel(modelId: string): Promise<void> {
74-
await this.getModelOrStop(modelId);
75-
await this.modelsUsecases.stopModel(modelId);
81+
return this.getModelOrStop(modelId)
82+
.then(() => this.modelsUsecases.stopModel(modelId))
83+
.then();
7684
}
7785

7886
/**
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
import { Injectable } from '@nestjs/common';
2+
import { defaultCortexCppHost, defaultCortexCppPort } from 'constant';
3+
4+
interface ModelStat {
5+
modelId: string;
6+
engine?: string;
7+
duration?: string;
8+
status: string;
9+
vram?: string;
10+
ram?: string;
11+
}
12+
interface ModelStatResponse {
13+
object: string;
14+
data: any;
15+
}
16+
@Injectable()
17+
export class PSCliUsecases {
18+
/**
19+
* Get models running in the Cortex C++ server
20+
* @param host Cortex host address
21+
* @param port Cortex port address
22+
*/
23+
async getModels(
24+
host: string = defaultCortexCppHost,
25+
port: number = defaultCortexCppPort,
26+
): Promise<ModelStat[]> {
27+
return new Promise<ModelStat[]>((resolve, reject) =>
28+
fetch(`http://${host}:${port}/inferences/server/models`)
29+
.then((res) => {
30+
if (res.ok) {
31+
res
32+
.json()
33+
.then(({ data }: ModelStatResponse) => {
34+
if (data && Array.isArray(data) && data.length > 0) {
35+
resolve(
36+
data.map((e) => {
37+
const startTime = e.start_time ?? new Date();
38+
const currentTime = new Date();
39+
const duration =
40+
currentTime.getTime() - new Date(startTime).getTime();
41+
return {
42+
modelId: e.id,
43+
engine: e.engine ?? 'llama.cpp', // TODO: get engine from model when it's ready
44+
status: 'running',
45+
duration: this.formatDuration(duration),
46+
ram: e.ram ?? '-',
47+
vram: e.vram ?? '-',
48+
};
49+
}),
50+
);
51+
} else reject();
52+
})
53+
.catch(reject);
54+
} else reject();
55+
})
56+
.catch(reject),
57+
).catch(() => []);
58+
}
59+
60+
private formatDuration(milliseconds: number): string {
61+
const days = Math.floor(milliseconds / (1000 * 60 * 60 * 24));
62+
const hours = Math.floor(
63+
(milliseconds % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60),
64+
);
65+
const minutes = Math.floor((milliseconds % (1000 * 60 * 60)) / (1000 * 60));
66+
const seconds = Math.floor((milliseconds % (1000 * 60)) / 1000);
67+
68+
let formattedDuration = '';
69+
70+
if (days > 0) {
71+
formattedDuration += `${days}d `;
72+
}
73+
if (hours > 0) {
74+
formattedDuration += `${hours}h `;
75+
}
76+
if (minutes > 0) {
77+
formattedDuration += `${minutes}m `;
78+
}
79+
if (seconds > 0) {
80+
formattedDuration += `${seconds}s `;
81+
}
82+
83+
return formattedDuration.trim();
84+
}
85+
}

0 commit comments

Comments
 (0)