pi-llama-cpp 0.9.2 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +97 -22
- package/package.json +3 -2
- package/src/constants.ts +13 -3
- package/src/index.ts +2 -8
- package/src/interfaces/settings.ts +60 -0
- package/src/managers/events.ts +28 -8
- package/src/managers/server.ts +6 -4
- package/src/managers/settings.ts +206 -0
- package/src/models/baseModel.ts +32 -9
- package/src/models/legacyModel.ts +2 -2
- package/src/models/routerModel.ts +3 -3
- package/src/server.ts +33 -9
- package/src/sse/manager.ts +2 -1
- package/tests/events.test.ts +145 -9
- package/tests/mocks.ts +1 -1
- package/tests/server.test.ts +46 -0
- package/tests/settings.test.ts +636 -0
- package/tests/sseManager.test.ts +21 -0
- package/src/resolver.ts +0 -149
- package/tests/resolver.test.ts +0 -184
package/src/models/baseModel.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ProviderModelConfig } from "@earendil-works/pi-coding-agent";
|
|
2
|
-
import {
|
|
2
|
+
import { FALLBACK_CTX, POLLING_INTERVAL } from "../constants";
|
|
3
3
|
import { Mode } from "../enums/mode";
|
|
4
4
|
import { Status } from "../enums/status";
|
|
5
5
|
import { DataProperty } from "../interfaces/endpoints/models";
|
|
@@ -130,9 +130,9 @@ export abstract class BaseModel {
|
|
|
130
130
|
const { data } = await this.server.fetchModels();
|
|
131
131
|
const { n_ctx } = data.find((m) => m.id === this.id)?.meta!;
|
|
132
132
|
|
|
133
|
-
return n_ctx ??
|
|
133
|
+
return n_ctx ?? FALLBACK_CTX;
|
|
134
134
|
} catch {
|
|
135
|
-
return
|
|
135
|
+
return FALLBACK_CTX;
|
|
136
136
|
}
|
|
137
137
|
}
|
|
138
138
|
|
|
@@ -202,11 +202,31 @@ export abstract class BaseModel {
|
|
|
202
202
|
await this.server.postRequest("load", this.id);
|
|
203
203
|
|
|
204
204
|
if (await this.server.sseManager.probeSSE()) {
|
|
205
|
-
|
|
206
|
-
|
|
205
|
+
try {
|
|
206
|
+
const { status, exit_code } =
|
|
207
|
+
await this.server.sseManager.subscribeToStatus(this.id);
|
|
207
208
|
|
|
208
|
-
|
|
209
|
-
|
|
209
|
+
if (status === "failed" || (status === "unloaded" && exit_code !== 0)) {
|
|
210
|
+
throw new Error(`Model loading failed: ${this.id}`);
|
|
211
|
+
}
|
|
212
|
+
} catch (err) {
|
|
213
|
+
// A real failed status from the server stays fatal.
|
|
214
|
+
if (
|
|
215
|
+
err instanceof Error &&
|
|
216
|
+
err.message.startsWith("Model loading failed")
|
|
217
|
+
) {
|
|
218
|
+
throw err;
|
|
219
|
+
}
|
|
220
|
+
// An SSE timeout or connection failure is not proof the load failed:
|
|
221
|
+
// subscribeToStatus timers are only cleared by a terminal event on
|
|
222
|
+
// the same SSE connection, so a timer orphaned by a server restart
|
|
223
|
+
// or reconnect can fire during a later, healthy load. Check the
|
|
224
|
+
// model's real status over HTTP before surfacing an error.
|
|
225
|
+
await this.pollStatus();
|
|
226
|
+
const finalStatus = await this.getStatus();
|
|
227
|
+
if (finalStatus !== Status.LOADED && finalStatus !== Status.SLEEPING) {
|
|
228
|
+
throw new Error(`Model loading failed: ${this.id}`);
|
|
229
|
+
}
|
|
210
230
|
}
|
|
211
231
|
} else {
|
|
212
232
|
await this.pollStatus();
|
|
@@ -224,14 +244,17 @@ export abstract class BaseModel {
|
|
|
224
244
|
* Polls llama-server to check when the model is loaded
|
|
225
245
|
*
|
|
226
246
|
* @param startTime The initial polling timestamp
|
|
227
|
-
* @param timeout The maximum amount of ms before timeout. Defaults to
|
|
247
|
+
* @param timeout The maximum amount of ms before timeout. Defaults to server's pollingTimeout
|
|
228
248
|
* @param interval The polling interval. Defaults to POLLING_INTERVAL
|
|
229
249
|
*/
|
|
230
250
|
async pollStatus(
|
|
231
251
|
startTime: number = Date.now(),
|
|
232
|
-
timeout
|
|
252
|
+
timeout?: number,
|
|
233
253
|
interval: number = POLLING_INTERVAL,
|
|
234
254
|
): Promise<void> {
|
|
255
|
+
if (timeout === undefined) {
|
|
256
|
+
timeout = this.server.pollingTimeout;
|
|
257
|
+
}
|
|
235
258
|
while ((await this.getStatus()) === Status.LOADING) {
|
|
236
259
|
// Force a timeout if we wasted too much time polling
|
|
237
260
|
if (Date.now() - startTime > timeout) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { FALLBACK_CTX } from "../constants";
|
|
2
2
|
import { Mode } from "../enums/mode";
|
|
3
3
|
import { SingleModel } from "./singleModel";
|
|
4
4
|
|
|
@@ -25,7 +25,7 @@ export class LegacyModel extends SingleModel {
|
|
|
25
25
|
const [{ max_model_len }] = data;
|
|
26
26
|
const contextSize = max_model_len === 0 ? n_ctx : max_model_len;
|
|
27
27
|
|
|
28
|
-
return contextSize ??
|
|
28
|
+
return contextSize ?? FALLBACK_CTX;
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
/**
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { FALLBACK_CTX, POLLING_INTERVAL } from "../constants";
|
|
2
2
|
import { Mode } from "../enums/mode";
|
|
3
3
|
import { Status } from "../enums/status";
|
|
4
4
|
import { BaseModel } from "./baseModel";
|
|
@@ -41,7 +41,7 @@ export class RouterModel extends BaseModel {
|
|
|
41
41
|
}
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
-
const timeout =
|
|
44
|
+
const timeout = this.server.pollingTimeout - elapsed;
|
|
45
45
|
return await super.pollStatus(startTime, timeout);
|
|
46
46
|
}
|
|
47
47
|
|
|
@@ -60,7 +60,7 @@ export class RouterModel extends BaseModel {
|
|
|
60
60
|
const response =
|
|
61
61
|
this.extractFrom("--ctx-size") ??
|
|
62
62
|
this.extractFrom("--fit-ctx") ??
|
|
63
|
-
|
|
63
|
+
FALLBACK_CTX;
|
|
64
64
|
|
|
65
65
|
return response;
|
|
66
66
|
}
|
package/src/server.ts
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
import { ApiClient } from "./api/client";
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
API_KEY_PLACEHOLDER,
|
|
4
|
+
POLLING_TIMEOUT,
|
|
5
|
+
PROVIDER_NAME,
|
|
6
|
+
PROVIDER_PREFIX,
|
|
7
|
+
SERVER_TIMEOUT,
|
|
8
|
+
} from "./constants";
|
|
3
9
|
import { Mode } from "./enums/mode";
|
|
4
10
|
import { ServerStatus } from "./enums/serverStatus";
|
|
5
11
|
import { HealthEndpoint } from "./interfaces/endpoints/health";
|
|
@@ -8,20 +14,25 @@ import {
|
|
|
8
14
|
PropsEndpoint,
|
|
9
15
|
PropsModelEndpoint,
|
|
10
16
|
} from "./interfaces/endpoints/props";
|
|
17
|
+
import { settings } from "./managers/settings";
|
|
11
18
|
import { BaseModel } from "./models/baseModel";
|
|
12
19
|
import { LegacyModel } from "./models/legacyModel";
|
|
13
20
|
import { RouterModel } from "./models/routerModel";
|
|
14
21
|
import { SingleModel } from "./models/singleModel";
|
|
15
|
-
import { ConfigResolver } from "./resolver";
|
|
16
22
|
import { SSEManager } from "./sse/manager";
|
|
17
23
|
|
|
18
24
|
export class Server {
|
|
19
25
|
public readonly models: BaseModel[] = [];
|
|
20
|
-
private configResolver = new ConfigResolver();
|
|
21
26
|
private apiClient!: ApiClient;
|
|
22
27
|
private sse!: SSEManager;
|
|
23
28
|
|
|
24
|
-
constructor(
|
|
29
|
+
constructor(
|
|
30
|
+
readonly baseUrl: string,
|
|
31
|
+
private readonly customId?: string,
|
|
32
|
+
private readonly customName?: string,
|
|
33
|
+
readonly serverTimeout: number = SERVER_TIMEOUT,
|
|
34
|
+
readonly pollingTimeout: number = POLLING_TIMEOUT,
|
|
35
|
+
) {}
|
|
25
36
|
|
|
26
37
|
/**
|
|
27
38
|
* Provides access to the SSE manager for direct subscriptions.
|
|
@@ -32,24 +43,37 @@ export class Server {
|
|
|
32
43
|
|
|
33
44
|
/**
|
|
34
45
|
* Generates a unique provider ID from a server URL.
|
|
46
|
+
* Uses custom ID if provided, otherwise falls back to URL-based ID.
|
|
35
47
|
*/
|
|
36
48
|
get providerId(): string {
|
|
37
|
-
return `${PROVIDER_PREFIX}=${this.baseUrl}`;
|
|
49
|
+
return this.customId ?? `${PROVIDER_PREFIX}=${this.baseUrl}`;
|
|
38
50
|
}
|
|
39
51
|
|
|
40
52
|
/**
|
|
41
53
|
* Generates a human-readable provider name from a server URL.
|
|
54
|
+
* Uses custom name as suffix if provided.
|
|
42
55
|
*/
|
|
43
56
|
get providerName(): string {
|
|
57
|
+
if (this.customName) {
|
|
58
|
+
return `${PROVIDER_NAME} (${this.customName})`;
|
|
59
|
+
}
|
|
44
60
|
return `${PROVIDER_NAME} (${this.baseUrl})`;
|
|
45
61
|
}
|
|
46
62
|
|
|
47
63
|
/**
|
|
48
|
-
* Retrieves the API key from the resolver
|
|
64
|
+
* Retrieves the API key from the config resolver.
|
|
65
|
+
* Tries custom ID first, then falls back to URL-based ID.
|
|
66
|
+
*
|
|
49
67
|
* @returns The API key
|
|
50
68
|
*/
|
|
51
|
-
|
|
52
|
-
|
|
69
|
+
getApiKey(): string {
|
|
70
|
+
// Try custom ID first
|
|
71
|
+
if (this.customId) {
|
|
72
|
+
const key = settings.resolveApiKey(this.customId);
|
|
73
|
+
if (key !== API_KEY_PLACEHOLDER) return key;
|
|
74
|
+
}
|
|
75
|
+
// Fall back to URL-based ID
|
|
76
|
+
return settings.resolveApiKey(`${PROVIDER_PREFIX}=${this.baseUrl}`);
|
|
53
77
|
}
|
|
54
78
|
|
|
55
79
|
/**
|
|
@@ -59,7 +83,7 @@ export class Server {
|
|
|
59
83
|
async initialize() {
|
|
60
84
|
const apiKey = await this.getApiKey();
|
|
61
85
|
this.apiClient = new ApiClient(this.baseUrl, apiKey);
|
|
62
|
-
this.sse = new SSEManager(this.baseUrl, apiKey);
|
|
86
|
+
this.sse = new SSEManager(this.baseUrl, apiKey, this.serverTimeout);
|
|
63
87
|
const { data } = await this.fetchModels();
|
|
64
88
|
const mode = await this.detectServerMode();
|
|
65
89
|
|
package/src/sse/manager.ts
CHANGED
|
@@ -27,6 +27,7 @@ export class SSEManager {
|
|
|
27
27
|
constructor(
|
|
28
28
|
private readonly baseUrl: string,
|
|
29
29
|
private readonly apiKey: string,
|
|
30
|
+
readonly serverTimeout: number = SERVER_TIMEOUT,
|
|
30
31
|
) {}
|
|
31
32
|
|
|
32
33
|
/**
|
|
@@ -52,7 +53,7 @@ export class SSEManager {
|
|
|
52
53
|
}
|
|
53
54
|
const response = await fetch(url, {
|
|
54
55
|
method: "GET",
|
|
55
|
-
signal: AbortSignal.timeout(
|
|
56
|
+
signal: AbortSignal.timeout(this.serverTimeout),
|
|
56
57
|
});
|
|
57
58
|
this.sseSupported =
|
|
58
59
|
response.ok &&
|
package/tests/events.test.ts
CHANGED
|
@@ -1,7 +1,25 @@
|
|
|
1
1
|
import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
|
2
|
-
import {
|
|
2
|
+
import { THINKING_BUDGETS } from "../src/constants";
|
|
3
|
+
import { Status } from "../src/enums/status";
|
|
3
4
|
import { createMockModel, createMockServer } from "./mocks";
|
|
4
5
|
|
|
6
|
+
// Mock settings — reactToModelSelect and autoloadOnMessage
|
|
7
|
+
const mockSettings = {
|
|
8
|
+
resolveReactToModelSelect: vi.fn(() => true),
|
|
9
|
+
resolveAutoloadOnMessage: vi.fn(() => false),
|
|
10
|
+
resolveThinkingLevel: vi.fn(() => "medium"),
|
|
11
|
+
resolveThinkingBudgets: vi.fn(() => ({ ...THINKING_BUDGETS })),
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
// Wire resolveThinkingBudgets to use the SettingsManager mock when set
|
|
15
|
+
mockSettings.resolveThinkingBudgets.mockImplementation(() => {
|
|
16
|
+
const userBudgets = mockSettingsManager.getThinkingBudgets();
|
|
17
|
+
if (userBudgets) {
|
|
18
|
+
return { ...THINKING_BUDGETS, ...userBudgets };
|
|
19
|
+
}
|
|
20
|
+
return { ...THINKING_BUDGETS };
|
|
21
|
+
});
|
|
22
|
+
|
|
5
23
|
// Create a mutable mock object shared across tests
|
|
6
24
|
const mockSettingsManager = {
|
|
7
25
|
getDefaultThinkingLevel: vi.fn(() => "medium"),
|
|
@@ -19,6 +37,10 @@ vi.mock("@earendil-works/pi-coding-agent", async (importOriginal) => {
|
|
|
19
37
|
};
|
|
20
38
|
});
|
|
21
39
|
|
|
40
|
+
vi.mock("../src/managers/settings", () => ({
|
|
41
|
+
settings: mockSettings,
|
|
42
|
+
}));
|
|
43
|
+
|
|
22
44
|
let EventManager: typeof import("../src/managers/events").EventManager;
|
|
23
45
|
|
|
24
46
|
beforeAll(async () => {
|
|
@@ -117,7 +139,7 @@ describe("EventManager.onBeforeProviderRequest", () => {
|
|
|
117
139
|
|
|
118
140
|
expect(result.messages).toEqual([{ role: "user", content: "test" }]);
|
|
119
141
|
expect(result.temperature).toBe(0.7);
|
|
120
|
-
expect(result.thinking_budget_tokens).toBe(
|
|
142
|
+
expect(result.thinking_budget_tokens).toBe(THINKING_BUDGETS.low);
|
|
121
143
|
});
|
|
122
144
|
});
|
|
123
145
|
|
|
@@ -194,9 +216,7 @@ describe("EventManager.onBeforeProviderRequest", () => {
|
|
|
194
216
|
)) as Record<string, unknown>;
|
|
195
217
|
|
|
196
218
|
// medium uses default since user only overrode low
|
|
197
|
-
expect(result.thinking_budget_tokens).toBe(
|
|
198
|
-
DEFAULT_THINKING_BUDGETS.medium,
|
|
199
|
-
);
|
|
219
|
+
expect(result.thinking_budget_tokens).toBe(THINKING_BUDGETS.medium);
|
|
200
220
|
});
|
|
201
221
|
});
|
|
202
222
|
|
|
@@ -223,9 +243,7 @@ describe("EventManager.onBeforeProviderRequest", () => {
|
|
|
223
243
|
)) as Record<string, unknown>;
|
|
224
244
|
|
|
225
245
|
// Should fall back to default since "medium" is not in user budgets
|
|
226
|
-
expect(result.thinking_budget_tokens).toBe(
|
|
227
|
-
DEFAULT_THINKING_BUDGETS.medium,
|
|
228
|
-
);
|
|
246
|
+
expect(result.thinking_budget_tokens).toBe(THINKING_BUDGETS.medium);
|
|
229
247
|
});
|
|
230
248
|
|
|
231
249
|
it("should not allow overriding 'off' — thinking stays disabled", async () => {
|
|
@@ -290,7 +308,125 @@ describe("EventManager.onBeforeProviderRequest", () => {
|
|
|
290
308
|
ctx,
|
|
291
309
|
)) as Record<string, unknown>;
|
|
292
310
|
|
|
293
|
-
expect(result.thinking_budget_tokens).toBe(
|
|
311
|
+
expect(result.thinking_budget_tokens).toBe(THINKING_BUDGETS.high);
|
|
312
|
+
});
|
|
313
|
+
});
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
describe("EventManager.onModelSelect", () => {
|
|
317
|
+
beforeEach(() => {
|
|
318
|
+
mockSettings.resolveReactToModelSelect.mockReturnValue(true);
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
it("should load the model when reactToModelSelect is true", async () => {
|
|
322
|
+
const server = createMockServer({
|
|
323
|
+
models: ["model-a"].map((id) => createMockModel(id)),
|
|
324
|
+
});
|
|
325
|
+
const eventManager = new EventManager([server]);
|
|
326
|
+
const ctx = createMockCtx();
|
|
327
|
+
|
|
328
|
+
const event = {
|
|
329
|
+
model: { provider: server.providerId, id: "model-a" },
|
|
330
|
+
} as any;
|
|
331
|
+
|
|
332
|
+
await eventManager.onModelSelect(event, ctx);
|
|
333
|
+
|
|
334
|
+
expect(ctx.ui.notify).toHaveBeenCalledWith("Loading model-a...", "info");
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
it("should return early when reactToModelSelect is false", async () => {
|
|
338
|
+
mockSettings.resolveReactToModelSelect.mockReturnValue(false);
|
|
339
|
+
|
|
340
|
+
const server = createMockServer({
|
|
341
|
+
models: ["model-a"].map((id) => createMockModel(id)),
|
|
294
342
|
});
|
|
343
|
+
const eventManager = new EventManager([server]);
|
|
344
|
+
const ctx = createMockCtx();
|
|
345
|
+
|
|
346
|
+
const event = {
|
|
347
|
+
model: { provider: server.providerId, id: "model-a" },
|
|
348
|
+
} as any;
|
|
349
|
+
|
|
350
|
+
await eventManager.onModelSelect(event, ctx);
|
|
351
|
+
|
|
352
|
+
expect(ctx.ui.notify).not.toHaveBeenCalled();
|
|
353
|
+
});
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
describe("EventManager.autoLoadIfNeeded", () => {
|
|
357
|
+
it("should load the model when autoloadOnMessage is true and model is UNLOADED", async () => {
|
|
358
|
+
mockSettings.resolveAutoloadOnMessage.mockReturnValue(true);
|
|
359
|
+
|
|
360
|
+
const server = createMockServer({
|
|
361
|
+
models: [
|
|
362
|
+
createMockModel("model-a", {
|
|
363
|
+
getStatus: vi.fn().mockResolvedValue(Status.UNLOADED),
|
|
364
|
+
load: vi.fn().mockResolvedValue(undefined),
|
|
365
|
+
}),
|
|
366
|
+
],
|
|
367
|
+
});
|
|
368
|
+
const eventManager = new EventManager([server]);
|
|
369
|
+
const model = server.models[0];
|
|
370
|
+
|
|
371
|
+
await (eventManager as any).autoLoadIfNeeded(model);
|
|
372
|
+
|
|
373
|
+
expect(model.load).toHaveBeenCalled();
|
|
374
|
+
});
|
|
375
|
+
|
|
376
|
+
it("should not load the model when autoloadOnMessage is false", async () => {
|
|
377
|
+
mockSettings.resolveAutoloadOnMessage.mockReturnValue(false);
|
|
378
|
+
|
|
379
|
+
const server = createMockServer({
|
|
380
|
+
models: [
|
|
381
|
+
createMockModel("model-a", {
|
|
382
|
+
getStatus: vi.fn().mockResolvedValue(Status.UNLOADED),
|
|
383
|
+
load: vi.fn().mockResolvedValue(undefined),
|
|
384
|
+
}),
|
|
385
|
+
],
|
|
386
|
+
});
|
|
387
|
+
const eventManager = new EventManager([server]);
|
|
388
|
+
const model = server.models[0];
|
|
389
|
+
|
|
390
|
+
await (eventManager as any).autoLoadIfNeeded(model);
|
|
391
|
+
|
|
392
|
+
expect(model.load).not.toHaveBeenCalled();
|
|
393
|
+
});
|
|
394
|
+
|
|
395
|
+
it("should not load the model when model is already LOADED", async () => {
|
|
396
|
+
mockSettings.resolveAutoloadOnMessage.mockReturnValue(true);
|
|
397
|
+
|
|
398
|
+
const server = createMockServer({
|
|
399
|
+
models: [
|
|
400
|
+
createMockModel("model-a", {
|
|
401
|
+
getStatus: vi.fn().mockResolvedValue(Status.LOADED),
|
|
402
|
+
load: vi.fn().mockResolvedValue(undefined),
|
|
403
|
+
}),
|
|
404
|
+
],
|
|
405
|
+
});
|
|
406
|
+
const eventManager = new EventManager([server]);
|
|
407
|
+
const model = server.models[0];
|
|
408
|
+
|
|
409
|
+
await (eventManager as any).autoLoadIfNeeded(model);
|
|
410
|
+
|
|
411
|
+
expect(model.load).not.toHaveBeenCalled();
|
|
412
|
+
});
|
|
413
|
+
|
|
414
|
+
it("should not load the model when model is SLEEPING", async () => {
|
|
415
|
+
mockSettings.resolveAutoloadOnMessage.mockReturnValue(true);
|
|
416
|
+
|
|
417
|
+
const server = createMockServer({
|
|
418
|
+
models: [
|
|
419
|
+
createMockModel("model-a", {
|
|
420
|
+
getStatus: vi.fn().mockResolvedValue(Status.SLEEPING),
|
|
421
|
+
load: vi.fn().mockResolvedValue(undefined),
|
|
422
|
+
}),
|
|
423
|
+
],
|
|
424
|
+
});
|
|
425
|
+
const eventManager = new EventManager([server]);
|
|
426
|
+
const model = server.models[0];
|
|
427
|
+
|
|
428
|
+
await (eventManager as any).autoLoadIfNeeded(model);
|
|
429
|
+
|
|
430
|
+
expect(model.load).not.toHaveBeenCalled();
|
|
295
431
|
});
|
|
296
432
|
});
|
package/tests/mocks.ts
CHANGED
|
@@ -17,7 +17,7 @@ export const createMockServer = (
|
|
|
17
17
|
const server: Partial<Server> = {
|
|
18
18
|
baseUrl: "http://127.0.0.1:8080",
|
|
19
19
|
models,
|
|
20
|
-
getApiKey: () =>
|
|
20
|
+
getApiKey: () => overrides.apiKey ?? "",
|
|
21
21
|
fetchModels: () => mockRpc("/v1/models"),
|
|
22
22
|
fetchModelProps: (modelId: string) =>
|
|
23
23
|
mockRpc(`/props?model=${modelId}&autoload=false`),
|
package/tests/server.test.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { beforeEach, describe, expect, it } from "vitest";
|
|
2
|
+
import { POLLING_TIMEOUT, SERVER_TIMEOUT } from "../src/constants";
|
|
2
3
|
import { ServerStatus } from "../src/enums/serverStatus";
|
|
3
4
|
import { Server } from "../src/server";
|
|
4
5
|
import { createMockServer, mockRpc } from "./mocks";
|
|
@@ -146,6 +147,51 @@ describe("Server postRequest", () => {
|
|
|
146
147
|
});
|
|
147
148
|
});
|
|
148
149
|
|
|
150
|
+
describe("Server timeouts", () => {
|
|
151
|
+
it("should use default timeouts when not provided", () => {
|
|
152
|
+
const server = new Server("http://127.0.0.1:8080");
|
|
153
|
+
|
|
154
|
+
expect(server.serverTimeout).toBe(SERVER_TIMEOUT);
|
|
155
|
+
expect(server.pollingTimeout).toBe(POLLING_TIMEOUT);
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
it("should accept custom serverTimeout", () => {
|
|
159
|
+
const server = new Server(
|
|
160
|
+
"http://127.0.0.1:8080",
|
|
161
|
+
undefined,
|
|
162
|
+
undefined,
|
|
163
|
+
3000,
|
|
164
|
+
);
|
|
165
|
+
|
|
166
|
+
expect(server.serverTimeout).toBe(3000);
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
it("should accept custom pollingTimeout", () => {
|
|
170
|
+
const server = new Server(
|
|
171
|
+
"http://127.0.0.1:8080",
|
|
172
|
+
undefined,
|
|
173
|
+
undefined,
|
|
174
|
+
1000,
|
|
175
|
+
120000,
|
|
176
|
+
);
|
|
177
|
+
|
|
178
|
+
expect(server.pollingTimeout).toBe(120000);
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
it("should accept both custom timeouts", () => {
|
|
182
|
+
const server = new Server(
|
|
183
|
+
"http://127.0.0.1:8080",
|
|
184
|
+
"my-id",
|
|
185
|
+
"My Server",
|
|
186
|
+
2000,
|
|
187
|
+
90000,
|
|
188
|
+
);
|
|
189
|
+
|
|
190
|
+
expect(server.serverTimeout).toBe(2000);
|
|
191
|
+
expect(server.pollingTimeout).toBe(90000);
|
|
192
|
+
});
|
|
193
|
+
});
|
|
194
|
+
|
|
149
195
|
describe("Server isReady", () => {
|
|
150
196
|
it("should return READY when health status is ok", async () => {
|
|
151
197
|
mockRpc.mockResolvedValueOnce({ status: "ok" });
|