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

Commit 480f1a0

Browse files
authored
feat: add cortex serve detach option - add health check and stop api endpoints (#693)
1 parent fa82078 commit 480f1a0

File tree

12 files changed

+137
-15
lines changed

12 files changed

+137
-15
lines changed

cortex-js/src/app.module.ts

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,14 @@ import { AppLoggerMiddleware } from './infrastructure/middlewares/app.logger.mid
1717
import { EventEmitterModule } from '@nestjs/event-emitter';
1818
import { DownloadManagerModule } from './download-manager/download-manager.module';
1919
import { EventsController } from './infrastructure/controllers/events.controller';
20+
import { AppController } from './infrastructure/controllers/app.controller';
21+
import { AssistantsController } from './infrastructure/controllers/assistants.controller';
22+
import { ChatController } from './infrastructure/controllers/chat.controller';
23+
import { EmbeddingsController } from './infrastructure/controllers/embeddings.controller';
24+
import { ModelsController } from './infrastructure/controllers/models.controller';
25+
import { ThreadsController } from './infrastructure/controllers/threads.controller';
26+
import { StatusController } from './infrastructure/controllers/status.controller';
27+
import { ProcessController } from './infrastructure/controllers/process.controller';
2028

2129
@Module({
2230
imports: [
@@ -40,7 +48,17 @@ import { EventsController } from './infrastructure/controllers/events.controller
4048
ModelRepositoryModule,
4149
DownloadManagerModule,
4250
],
43-
controllers: [EventsController],
51+
controllers: [
52+
AppController,
53+
AssistantsController,
54+
ChatController,
55+
EmbeddingsController,
56+
ModelsController,
57+
ThreadsController,
58+
StatusController,
59+
ProcessController,
60+
EventsController,
61+
],
4462
providers: [SeedService],
4563
})
4664
export class AppModule implements NestModule {

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

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,12 @@ export class PSCommand extends CommandRunner {
1010
super();
1111
}
1212
async run(): Promise<void> {
13-
return this.usecases.getModels().then(console.table);
13+
return this.usecases
14+
.getModels()
15+
.then(console.table)
16+
.then(() => this.usecases.isAPIServerOnline())
17+
.then((isOnline) => {
18+
if (isOnline) console.log('API server is online');
19+
});
1420
}
1521
}

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

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { spawn } from 'child_process';
22
import {
3+
CORTEX_JS_STOP_API_SERVER_URL,
34
defaultCortexJsHost,
45
defaultCortexJsPort,
56
} from '@/infrastructure/constants/cortex';
@@ -9,6 +10,7 @@ import { join } from 'path';
910
type ServeOptions = {
1011
host?: string;
1112
port?: number;
13+
attach: boolean;
1214
};
1315

1416
@SubCommand({
@@ -20,7 +22,25 @@ export class ServeCommand extends CommandRunner {
2022
const host = options?.host || defaultCortexJsHost;
2123
const port = options?.port || defaultCortexJsPort;
2224

23-
spawn(
25+
if (_input[0] === 'stop') {
26+
return this.stopServer().then(() => console.log('API server stopped'));
27+
} else {
28+
return this.startServer(host, port, options);
29+
}
30+
}
31+
32+
private async stopServer() {
33+
return fetch(CORTEX_JS_STOP_API_SERVER_URL(), {
34+
method: 'DELETE',
35+
}).catch(() => {});
36+
}
37+
38+
private async startServer(
39+
host: string,
40+
port: number,
41+
options: ServeOptions = { attach: true },
42+
) {
43+
const serveProcess = spawn(
2444
'node',
2545
process.env.TEST
2646
? [join(__dirname, '../../../dist/src/main.js')]
@@ -32,10 +52,14 @@ export class ServeCommand extends CommandRunner {
3252
CORTEX_JS_PORT: port.toString(),
3353
NODE_ENV: 'production',
3454
},
35-
stdio: 'inherit',
36-
detached: false,
55+
stdio: options?.attach ? 'inherit' : 'ignore',
56+
detached: true,
3757
},
3858
);
59+
if (!options?.attach) {
60+
serveProcess.unref();
61+
console.log('Started server at http://%s:%d', host, port);
62+
}
3963
}
4064

4165
@Option({
@@ -53,4 +77,14 @@ export class ServeCommand extends CommandRunner {
5377
parsePort(value: string) {
5478
return parseInt(value, 10);
5579
}
80+
81+
@Option({
82+
flags: '-a, --attach',
83+
description: 'Attach to interactive chat session',
84+
defaultValue: false,
85+
name: 'attach',
86+
})
87+
parseAttach() {
88+
return true;
89+
}
5690
}

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

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
import { HttpStatus, Injectable } from '@nestjs/common';
22
import {
33
CORTEX_CPP_MODELS_URL,
4+
CORTEX_JS_HEALTH_URL,
45
defaultCortexCppHost,
56
defaultCortexCppPort,
7+
defaultCortexJsHost,
8+
defaultCortexJsPort,
69
} from '@/infrastructure/constants/cortex';
710
import { HttpService } from '@nestjs/axios';
811
import { firstValueFrom } from 'rxjs';
@@ -56,6 +59,23 @@ export class PSCliUsecases {
5659
).catch(() => []);
5760
}
5861

62+
/**
63+
* Check if the Cortex API server is online
64+
* @param host Cortex host address
65+
* @param port Cortex port address
66+
* @returns
67+
*/
68+
async isAPIServerOnline(
69+
host: string = defaultCortexJsHost,
70+
port: number = defaultCortexJsPort,
71+
): Promise<boolean> {
72+
return firstValueFrom(
73+
this.httpService.get(CORTEX_JS_HEALTH_URL(host, port)),
74+
)
75+
.then((res) => res.status === HttpStatus.OK)
76+
.catch(() => false);
77+
}
78+
5979
private formatDuration(milliseconds: number): string {
6080
const days = Math.floor(milliseconds / (1000 * 60 * 60 * 24));
6181
const hours = Math.floor(

cortex-js/src/infrastructure/constants/cortex.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,16 @@ export const CORTEX_CPP_MODELS_URL = (
2828
port: number = defaultCortexCppPort,
2929
) => `http://${host}:${port}/inferences/server/models`;
3030

31+
export const CORTEX_JS_HEALTH_URL = (
32+
host: string = defaultCortexJsHost,
33+
port: number = defaultCortexJsPort,
34+
) => `http://${host}:${port}/health`;
35+
36+
export const CORTEX_JS_STOP_API_SERVER_URL = (
37+
host: string = defaultCortexJsHost,
38+
port: number = defaultCortexJsPort,
39+
) => `http://${host}:${port}/process`;
40+
3141
// INITIALIZATION
3242
export const CORTEX_RELEASES_URL =
3343
'https://api.github.com/repos/janhq/cortex/releases';
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { Controller, Delete } from '@nestjs/common';
2+
import { ApiOperation, ApiTags } from '@nestjs/swagger';
3+
4+
@ApiTags('Processes')
5+
@Controller('process')
6+
export class ProcessController {
7+
constructor() {}
8+
9+
@ApiOperation({
10+
summary: 'Terminate service',
11+
description: 'Terminate service endpoint',
12+
})
13+
@Delete()
14+
async delete() {
15+
process.exit(0);
16+
}
17+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { Controller, HttpCode, Get } from '@nestjs/common';
2+
import { ApiOperation, ApiTags, ApiResponse } from '@nestjs/swagger';
3+
4+
@ApiTags('Status')
5+
@Controller('health')
6+
export class StatusController {
7+
constructor() {}
8+
9+
@ApiOperation({
10+
summary: 'Health check',
11+
description: 'Health check endpoint.',
12+
})
13+
@HttpCode(200)
14+
@ApiResponse({
15+
status: 200,
16+
description: 'Ok',
17+
})
18+
@Get()
19+
async get() {
20+
return 'OK';
21+
}
22+
}

cortex-js/src/main.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ async function bootstrap() {
7070
const port = process.env.CORTEX_JS_PORT || defaultCortexJsPort;
7171

7272
await app.listen(port, host);
73-
console.log(`Server running on http://${host}:${port}`);
73+
console.log(`Started server at http://${host}:${port}`);
7474
}
7575

7676
const buildSwagger = (app: INestApplication<any>) => {

cortex-js/src/usecases/assistants/assistants.module.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
11
import { Module } from '@nestjs/common';
2-
import { AssistantsController } from '@/infrastructure/controllers/assistants.controller';
32
import { AssistantsUsecases } from './assistants.usecases';
43
import { DatabaseModule } from '@/infrastructure/database/database.module';
54

65
@Module({
76
imports: [DatabaseModule],
8-
controllers: [AssistantsController],
7+
controllers: [],
98
providers: [AssistantsUsecases],
109
exports: [AssistantsUsecases],
1110
})

cortex-js/src/usecases/chat/chat.module.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,13 @@
11
import { Module } from '@nestjs/common';
2-
import { ChatController } from '@/infrastructure/controllers/chat.controller';
32
import { ChatUsecases } from './chat.usecases';
43
import { DatabaseModule } from '@/infrastructure/database/database.module';
54
import { ExtensionModule } from '@/infrastructure/repositories/extensions/extension.module';
65
import { ModelRepositoryModule } from '@/infrastructure/repositories/models/model.module';
76
import { HttpModule } from '@nestjs/axios';
8-
import { EmbeddingsController } from '@/infrastructure/controllers/embeddings.controller';
97

108
@Module({
119
imports: [DatabaseModule, ExtensionModule, ModelRepositoryModule, HttpModule],
12-
controllers: [ChatController, EmbeddingsController],
10+
controllers: [],
1311
providers: [ChatUsecases],
1412
exports: [ChatUsecases],
1513
})

0 commit comments

Comments
 (0)