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

Commit 828b679

Browse files
authored
fix: #644 - model start attach mode can just work once (#851)
1 parent 4a59eb2 commit 828b679

File tree

10 files changed

+77
-42
lines changed

10 files changed

+77
-42
lines changed

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

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,7 @@ export class InitCommand extends CommandRunner {
3636

3737
async run(passedParams: string[], options?: InitOptions): Promise<void> {
3838
if (options?.silent) {
39-
const installationOptions =
40-
await this.initUsecases.defaultInstallationOptions();
41-
await this.initUsecases.installEngine(installationOptions);
39+
await this.initUsecases.installEngine(undefined);
4240
} else {
4341
options = await this.inquirerService.ask(
4442
'init-run-mode-questions',

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

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -60,11 +60,7 @@ export class ModelPullCommand extends CommandRunner {
6060
!existsSync(join(await this.fileService.getCortexCppEnginePath(), engine))
6161
) {
6262
console.log('\n');
63-
await this.initUsecases.installEngine(
64-
await this.initUsecases.defaultInstallationOptions(),
65-
'latest',
66-
engine,
67-
);
63+
await this.initUsecases.installEngine(undefined, 'latest', engine);
6864
}
6965
this.telemetryUsecases.sendEvent(
7066
[

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

Lines changed: 47 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import { CortexUsecases } from '@/usecases/cortex/cortex.usecases';
1111
import { SetCommandContext } from '../decorators/CommandContext';
1212
import { ContextService } from '@/infrastructure/services/context/context.service';
1313
import { InitCliUsecases } from '../usecases/init.cli.usecases';
14-
import { existsSync } from 'node:fs';
14+
import { createReadStream, existsSync, statSync, watchFile } from 'node:fs';
1515
import { FileManagerService } from '@/infrastructure/services/file-manager/file-manager.service';
1616
import { join } from 'node:path';
1717
import { Engines } from '../types/engine.interface';
@@ -61,7 +61,9 @@ export class ModelStartCommand extends CommandRunner {
6161
!Array.isArray(existingModel.files) ||
6262
/^(http|https):\/\/[^/]+\/.*/.test(existingModel.files[0])
6363
) {
64-
checkingSpinner.fail(`Model ${modelId} not found on filesystem.\nPlease try 'cortex pull ${modelId}' first.`);
64+
checkingSpinner.fail(
65+
`Model ${modelId} not found on filesystem.\nPlease try 'cortex pull ${modelId}' first.`,
66+
);
6567
process.exit(1);
6668
}
6769

@@ -74,18 +76,19 @@ export class ModelStartCommand extends CommandRunner {
7476
!existsSync(join(await this.fileService.getCortexCppEnginePath(), engine))
7577
) {
7678
const engineSpinner = ora('Installing engine...').start();
77-
await this.initUsecases.installEngine(
78-
await this.initUsecases.defaultInstallationOptions(),
79-
'latest',
80-
engine,
81-
);
79+
await this.initUsecases.installEngine(undefined, 'latest', engine);
8280
engineSpinner.succeed();
8381
}
82+
83+
// Attached - stdout logs
84+
if (options.attach) {
85+
this.attachLogWatch();
86+
}
87+
8488
await this.cortexUsecases
85-
.startCortex(options.attach)
89+
.startCortex()
8690
.then(() => this.modelsCliUsecases.startModel(modelId, options.preset))
87-
.then(console.log)
88-
.then(() => !options.attach && process.exit(0));
91+
.then(() => options.attach && ora('Model is running...').start());
8992
}
9093

9194
modelInquiry = async () => {
@@ -120,4 +123,38 @@ export class ModelStartCommand extends CommandRunner {
120123
parseTemplate(value: string) {
121124
return value;
122125
}
126+
127+
/**
128+
* Attach to the log file and watch for changes
129+
*/
130+
private async attachLogWatch() {
131+
const logPath = await this.fileService.getLogPath();
132+
const initialSize = statSync(logPath).size;
133+
const logStream = createReadStream(logPath, {
134+
start: initialSize,
135+
encoding: 'utf-8',
136+
autoClose: false,
137+
});
138+
logStream.on('data', (chunk) => {
139+
console.log(chunk);
140+
});
141+
watchFile(logPath, (curr, prev) => {
142+
// Check if the file size has increased
143+
if (curr.size > prev.size) {
144+
// Calculate the position to start reading from
145+
const position = prev.size;
146+
147+
// Create a new read stream from the updated position
148+
const updateStream = createReadStream(logPath, {
149+
encoding: 'utf8',
150+
start: position,
151+
});
152+
153+
// Read the newly written content
154+
updateStream.on('data', (chunk) => {
155+
console.log(chunk);
156+
});
157+
}
158+
});
159+
}
123160
}

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

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -88,16 +88,12 @@ export class RunCommand extends CommandRunner {
8888
!existsSync(join(await this.fileService.getCortexCppEnginePath(), engine))
8989
) {
9090
const engineSpinner = ora('Installing engine...').start();
91-
await this.initUsecases.installEngine(
92-
await this.initUsecases.defaultInstallationOptions(),
93-
'latest',
94-
engine,
95-
);
91+
await this.initUsecases.installEngine(undefined, 'latest', engine);
9692
engineSpinner.succeed('Engine installed');
9793
}
9894

9995
return this.cortexUsecases
100-
.startCortex(false)
96+
.startCortex()
10197
.then(() => this.modelsCliUsecases.startModel(modelId, options.preset))
10298
.then(() => this.chatCliUsecases.chat(modelId, options.threadId));
10399
}

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

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,11 +51,15 @@ export class InitCliUsecases {
5151
* @param version
5252
*/
5353
installEngine = async (
54-
options: InitOptions,
54+
options?: InitOptions,
5555
version: string = 'latest',
5656
engine: string = 'default',
5757
force: boolean = true,
5858
): Promise<any> => {
59+
// Use default option if not defined
60+
if (!options) {
61+
options = await this.defaultInstallationOptions();
62+
}
5963
const configs = await this.fileManagerService.getConfig();
6064

6165
if (configs.initialized && !force) return;
@@ -271,7 +275,7 @@ export class InitCliUsecases {
271275
private detectInstructions = (): Promise<
272276
'AVX' | 'AVX2' | 'AVX512' | undefined
273277
> => {
274-
const cpuInstruction = cpuInfo.cpuInfo()[0]?? 'AVX'
278+
const cpuInstruction = cpuInfo.cpuInfo()[0] ?? 'AVX';
275279
console.log(cpuInstruction, 'CPU instructions detected');
276280
return Promise.resolve(cpuInstruction);
277281
};

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,11 +41,11 @@ export class ModelsCliUsecases {
4141
.catch(async (e) => {
4242
console.error('Model start failed with reason:', e.message);
4343

44-
printLastErrorLines(await this.fileService.getDataFolderPath(), 5);
44+
printLastErrorLines(await this.fileService.getLogPath());
4545

4646
console.log(
4747
'For more information, please check the logs at: %s',
48-
join(await this.fileService.getDataFolderPath(), 'cortex.log'),
48+
await this.fileService.getLogPath(),
4949
);
5050
process.exit(1);
5151
});

cortex-js/src/infrastructure/services/file-manager/file-manager.service.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -249,6 +249,14 @@ export class FileManagerService {
249249
return join(await this.getDataFolderPath(), 'cortex-cpp', 'engines');
250250
}
251251

252+
/**
253+
* Get log path
254+
* @returns the path to the cortex engines folder
255+
*/
256+
async getLogPath(): Promise<string> {
257+
return join(await this.getDataFolderPath(), 'cortex.log');
258+
}
259+
252260
async createFolderIfNotExistInDataFolder(folderName: string): Promise<void> {
253261
const dataFolderPath = await this.getDataFolderPath();
254262
const folderPath = join(dataFolderPath, folderName);

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

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,7 @@ export class CortexUsecases {
2929
* @param attach
3030
* @returns
3131
*/
32-
async startCortex(
33-
attach: boolean = false,
34-
): Promise<CortexOperationSuccessfullyDto> {
32+
async startCortex(): Promise<CortexOperationSuccessfullyDto> {
3533
const configs = await this.fileManagerService.getConfig();
3634
const host = configs.cortexCppHost;
3735
const port = configs.cortexCppPort;
@@ -56,14 +54,11 @@ export class CortexUsecases {
5654
'cortex-cpp',
5755
);
5856

59-
const writer = openSync(
60-
join(await this.fileManagerService.getDataFolderPath(), 'cortex.log'),
61-
'a+',
62-
);
57+
const writer = openSync(await this.fileManagerService.getLogPath(), 'a+');
6358

6459
// go up one level to get the binary folder, have to also work on windows
6560
this.cortexProcess = spawn(cortexCppPath, args, {
66-
detached: !attach,
61+
detached: true,
6762
cwd: cortexCppFolderPath,
6863
stdio: [0, writer, writer],
6964
env: {
@@ -143,9 +138,9 @@ export class CortexUsecases {
143138

144139
/**
145140
* Check whether the Cortex CPP is healthy
146-
* @param host
147-
* @param port
148-
* @returns
141+
* @param host
142+
* @param port
143+
* @returns
149144
*/
150145
healthCheck(host: string, port: number): Promise<boolean> {
151146
return fetch(CORTEX_CPP_HEALTH_Z_URL(host, port))

cortex-js/src/usecases/models/models.usecases.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,7 @@ export class ModelsUsecases {
227227
};
228228
this.eventEmitter.emit('model.event', modelEvent);
229229
if (e.code === AxiosError.ERR_BAD_REQUEST) {
230+
loadingModelSpinner.succeed('Model loaded');
230231
return {
231232
message: 'Model already loaded',
232233
modelId,

cortex-js/src/utils/logs.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,12 @@ import { createInterface } from 'readline';
88
* @param numLines
99
*/
1010
export async function printLastErrorLines(
11-
dataFolderPath: string,
12-
numLines: number = 5,
11+
logPath: string,
12+
numLines: number = 10,
1313
): Promise<void> {
1414
const errorLines: string[] = [];
1515

16-
const fileStream = createReadStream(join(dataFolderPath, 'cortex.log'));
16+
const fileStream = createReadStream(logPath);
1717
const rl = createInterface({
1818
input: fileStream,
1919
crlfDelay: Infinity,

0 commit comments

Comments
 (0)