pi-llama-cpp 0.13.0 → 0.15.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 +19 -18
- package/package.json +3 -3
- package/src/api/client.ts +25 -0
- package/src/constants.ts +2 -2
- package/src/enums/status.ts +0 -1
- package/src/index.ts +2 -1
- package/src/interfaces/endpoints/models.ts +1 -1
- package/src/interfaces/settings.ts +7 -1
- package/src/interfaces/sortBy.ts +4 -0
- package/src/managers/command/models.ts +254 -0
- package/src/managers/command.ts +34 -461
- package/src/managers/events.ts +2 -2
- package/src/managers/server.ts +34 -18
- package/src/managers/settings.ts +26 -90
- package/src/models/baseModel.ts +27 -10
- package/src/models/legacyModel.ts +2 -2
- package/src/models/routerModel.ts +2 -2
- package/src/models/singleModel.ts +1 -1
- package/src/server.ts +17 -38
- package/src/sse/client.ts +113 -59
- package/src/sse/fetch.ts +43 -0
- package/src/sse/manager.ts +9 -27
- package/src/sse/types.ts +0 -4
- package/src/ui/dialog/base.ts +118 -0
- package/src/ui/dialog/confirm.ts +45 -0
- package/src/ui/dialog/factory.ts +111 -0
- package/src/ui/dialog/input.ts +63 -0
- package/src/ui/dialog/options.ts +23 -0
- package/src/ui/editors/editorOptions.ts +50 -0
- package/src/ui/editors/itemBuilder.ts +43 -0
- package/src/ui/editors/listEditor.ts +300 -0
- package/src/ui/editors/override/entry.ts +24 -0
- package/src/ui/editors/override/entryEditor.ts +248 -0
- package/src/ui/editors/override/fields/base.ts +53 -0
- package/src/ui/editors/override/fields/capabilities.ts +36 -0
- package/src/ui/editors/override/fields/cost.ts +64 -0
- package/src/ui/editors/override/fields/index.ts +68 -0
- package/src/ui/editors/override/fields/numeric.ts +55 -0
- package/src/ui/editors/override/fields/pattern.ts +24 -0
- package/src/ui/editors/override/fields/reasoning.ts +33 -0
- package/src/ui/editors/override/itemBuilder.ts +39 -0
- package/src/ui/editors/override/overrideList.ts +127 -0
- package/src/ui/editors/server/builder.ts +60 -0
- package/src/ui/editors/server/fields.ts +84 -0
- package/src/ui/editors/server/itemBuilder.ts +64 -0
- package/src/ui/editors/server/serverEditor.ts +197 -0
- package/src/ui/editors/server/utils.ts +95 -0
- package/src/ui/editors/server/wizard.ts +110 -0
- package/src/ui/editors/settingField.ts +37 -0
- package/src/ui/editors/settingsListFactory.ts +33 -0
- package/src/ui/settings/index.ts +248 -0
- package/src/ui/strings.ts +25 -4
- package/src/utils/health.ts +2 -1
- package/src/utils/serverIds.ts +21 -0
- package/src/utils/settingsStore.ts +1 -1
- package/src/utils/urlResolver.ts +129 -0
- package/src/utils/urls.ts +33 -13
- package/tests/{commandManager.test.ts → command/commandManager.test.ts} +88 -12
- package/tests/{events.test.ts → events/events.test.ts} +6 -6
- package/tests/mocks.ts +2 -0
- package/tests/models/legacyModel.test.ts +103 -0
- package/tests/{routerModel.test.ts → models/routerModel.test.ts} +71 -72
- package/tests/{singleModel.test.ts → models/singleModel.test.ts} +17 -17
- package/tests/{health.test.ts → server/health.test.ts} +2 -2
- package/tests/{server.test.ts → server/server.test.ts} +5 -17
- package/tests/{serverManager.test.ts → server/serverManager.test.ts} +4 -4
- package/tests/{settings.test.ts → settings/settings.test.ts} +184 -141
- package/tests/{settingsStore.test.ts → settings/settingsStore.test.ts} +1 -1
- package/tests/{sseManager.test.ts → sse/sseManager.test.ts} +8 -26
- package/tests/{dialog.test.ts → ui/dialog.test.ts} +56 -4
- package/tests/{overrides.test.ts → ui/overrides.test.ts} +101 -111
- package/src/ui/dialog.ts +0 -290
- package/src/ui/overrideEntryEditor.ts +0 -119
- package/src/ui/overrideSettingsList.ts +0 -710
- package/src/ui/serverListEditor.ts +0 -59
- package/src/ui/serverSettingsList.ts +0 -513
- package/tests/legacyModel.test.ts +0 -97
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
2
|
-
import { SettingsStore } from "
|
|
2
|
+
import { SettingsStore } from "../../src/utils/settingsStore";
|
|
3
3
|
|
|
4
4
|
const mockReadFile = vi.hoisted(() => vi.fn());
|
|
5
5
|
const mockWriteFile = vi.hoisted(() => vi.fn());
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { describe, expect, it, vi } from "vitest";
|
|
2
|
-
import { SSEManager } from "
|
|
3
|
-
import { createMockServer } from "
|
|
2
|
+
import { SSEManager } from "../../src/sse/manager";
|
|
3
|
+
import { createMockServer } from "../mocks";
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Builds an SSEManager bound to a stub server, mirroring how
|
|
@@ -16,29 +16,15 @@ const createManager = (
|
|
|
16
16
|
pollingTimeout,
|
|
17
17
|
serverTimeout,
|
|
18
18
|
}),
|
|
19
|
-
"",
|
|
20
19
|
);
|
|
21
20
|
|
|
22
|
-
describe("SSEManager timeouts", async () => {
|
|
23
|
-
it("should expose the timeouts of its server", async () => {
|
|
24
|
-
const manager = createManager(5678, 1234);
|
|
25
|
-
|
|
26
|
-
expect(await manager.getPollingTimeout()).toBe(5678);
|
|
27
|
-
expect(await manager.getServerTimeout()).toBe(1234);
|
|
28
|
-
});
|
|
29
|
-
});
|
|
30
|
-
|
|
31
21
|
describe("SSEManager subscribeToStatus", () => {
|
|
32
22
|
it("should reject after the server's pollingTimeout, not the constant", async () => {
|
|
33
23
|
vi.useFakeTimers();
|
|
24
|
+
// Never-resolving fetch: the connection stays open but no events arrive
|
|
34
25
|
vi.stubGlobal(
|
|
35
|
-
"
|
|
36
|
-
|
|
37
|
-
onopen = null;
|
|
38
|
-
onerror = null;
|
|
39
|
-
onmessage = null;
|
|
40
|
-
close() {}
|
|
41
|
-
},
|
|
26
|
+
"fetch",
|
|
27
|
+
vi.fn(async () => new Promise<Response>(() => {})),
|
|
42
28
|
);
|
|
43
29
|
|
|
44
30
|
try {
|
|
@@ -60,14 +46,10 @@ describe("SSEManager subscribeToStatus", () => {
|
|
|
60
46
|
|
|
61
47
|
it("should resolve when a terminal status arrives before the timeout", async () => {
|
|
62
48
|
vi.useFakeTimers();
|
|
49
|
+
// Never-resolving fetch: events are dispatched manually below
|
|
63
50
|
vi.stubGlobal(
|
|
64
|
-
"
|
|
65
|
-
|
|
66
|
-
onopen = null;
|
|
67
|
-
onerror = null;
|
|
68
|
-
onmessage = null;
|
|
69
|
-
close() {}
|
|
70
|
-
},
|
|
51
|
+
"fetch",
|
|
52
|
+
vi.fn(async () => new Promise<Response>(() => {})),
|
|
71
53
|
);
|
|
72
54
|
|
|
73
55
|
try {
|
|
@@ -5,9 +5,10 @@ import {
|
|
|
5
5
|
type TUI,
|
|
6
6
|
} from "@earendil-works/pi-tui";
|
|
7
7
|
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
8
|
-
import { ConfirmDialog
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
8
|
+
import { ConfirmDialog } from "../../src/ui/dialog/confirm";
|
|
9
|
+
import { InputDialog } from "../../src/ui/dialog/input";
|
|
10
|
+
import { OverrideSettingsList } from "../../src/ui/editors/override/overrideList";
|
|
11
|
+
import { ServerSettingsList } from "../../src/ui/editors/server/serverEditor";
|
|
11
12
|
|
|
12
13
|
beforeEach(() => {
|
|
13
14
|
initTheme();
|
|
@@ -138,6 +139,7 @@ describe("ServerSettingsList add wizard", () => {
|
|
|
138
139
|
persist,
|
|
139
140
|
done,
|
|
140
141
|
onError,
|
|
142
|
+
authResolver: () => Promise.resolve("sk-placeholder"),
|
|
141
143
|
});
|
|
142
144
|
return { editor, persist, done, onError };
|
|
143
145
|
};
|
|
@@ -191,7 +193,7 @@ describe("OverrideEntryListEditor cost re-prefill", () => {
|
|
|
191
193
|
|
|
192
194
|
it("re-entering a cost field prefills the just-saved value", async () => {
|
|
193
195
|
const persist = vi.fn().mockResolvedValue(undefined);
|
|
194
|
-
const list =
|
|
196
|
+
const list = new OverrideSettingsList({
|
|
195
197
|
tui: createMockTui(),
|
|
196
198
|
theme: createMockTheme(),
|
|
197
199
|
keybindings: createKeybindings(),
|
|
@@ -226,3 +228,53 @@ describe("OverrideEntryListEditor cost re-prefill", () => {
|
|
|
226
228
|
expect(rendered).not.toContain("0.2");
|
|
227
229
|
});
|
|
228
230
|
});
|
|
231
|
+
|
|
232
|
+
describe("OverrideSettingsList escape navigation", () => {
|
|
233
|
+
const flush = () => new Promise((resolve) => setTimeout(resolve, 0));
|
|
234
|
+
|
|
235
|
+
const build = () => {
|
|
236
|
+
const done = vi.fn();
|
|
237
|
+
const list = new OverrideSettingsList({
|
|
238
|
+
tui: createMockTui(),
|
|
239
|
+
theme: createMockTheme(),
|
|
240
|
+
keybindings: createKeybindings(),
|
|
241
|
+
servers: [
|
|
242
|
+
{
|
|
243
|
+
url: "http://x:1",
|
|
244
|
+
overrides: { "gpt-*": { cost: { input: 0.2 } } },
|
|
245
|
+
},
|
|
246
|
+
] as never[],
|
|
247
|
+
persist: vi.fn().mockResolvedValue(undefined),
|
|
248
|
+
done,
|
|
249
|
+
onError: vi.fn(),
|
|
250
|
+
onChanged: vi.fn(),
|
|
251
|
+
});
|
|
252
|
+
return { list, done };
|
|
253
|
+
};
|
|
254
|
+
|
|
255
|
+
it("Esc from the field submenu then the entry list lands on the server list", async () => {
|
|
256
|
+
const { list, done } = build();
|
|
257
|
+
|
|
258
|
+
list.handleInput(ENTER); // drill into the server row (entry list)
|
|
259
|
+
list.handleInput(ENTER); // open the field submenu
|
|
260
|
+
expect(list.render(80).join("\n")).not.toContain("http://x:1");
|
|
261
|
+
|
|
262
|
+
list.handleInput(ESC); // field submenu → entry list
|
|
263
|
+
await flush();
|
|
264
|
+
expect(done).not.toHaveBeenCalled();
|
|
265
|
+
expect(list.render(80).join("\n")).not.toContain("http://x:1");
|
|
266
|
+
|
|
267
|
+
list.handleInput(ESC); // entry list → server list
|
|
268
|
+
await flush();
|
|
269
|
+
// The dialog must stay open, showing the server row again
|
|
270
|
+
expect(done).not.toHaveBeenCalled();
|
|
271
|
+
expect(list.render(80).join("\n")).toContain("http://x:1");
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
it("Esc from the server list closes the dialog", async () => {
|
|
275
|
+
const { list, done } = build();
|
|
276
|
+
|
|
277
|
+
list.handleInput(ESC);
|
|
278
|
+
expect(done).toHaveBeenCalledTimes(1);
|
|
279
|
+
});
|
|
280
|
+
});
|
|
@@ -1,18 +1,14 @@
|
|
|
1
1
|
import { beforeEach, describe, expect, it } from "vitest";
|
|
2
|
-
import { FALLBACK_CTX } from "
|
|
3
|
-
import type {
|
|
4
|
-
import { SingleModel } from "
|
|
5
|
-
import { Server } from "
|
|
2
|
+
import { FALLBACK_CTX } from "../../src/constants";
|
|
3
|
+
import type { ModelOverride } from "../../src/interfaces/settings";
|
|
4
|
+
import { SingleModel } from "../../src/models/singleModel";
|
|
5
|
+
import { Server } from "../../src/server";
|
|
6
|
+
import { OverrideEntry } from "../../src/ui/editors/override/entry";
|
|
6
7
|
import {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
removeOverrideEntry,
|
|
12
|
-
updateOverrideEntry,
|
|
13
|
-
} from "../src/ui/overrideEntryEditor";
|
|
14
|
-
import { overrideFieldValue } from "../src/ui/overrideSettingsList";
|
|
15
|
-
import { createMockServer, mockRpc } from "./mocks";
|
|
8
|
+
OverrideFields,
|
|
9
|
+
OverrideSummary,
|
|
10
|
+
} from "../../src/ui/editors/override/fields";
|
|
11
|
+
import { createMockServer, mockRpc } from "../mocks";
|
|
16
12
|
|
|
17
13
|
beforeEach(() => {
|
|
18
14
|
mockRpc.mockReset();
|
|
@@ -94,16 +90,18 @@ describe("BaseModel reasoning override", () => {
|
|
|
94
90
|
|
|
95
91
|
// ─── capabilities override ────────────────────────────────────────────────
|
|
96
92
|
|
|
97
|
-
describe("BaseModel capabilities override", () => {
|
|
93
|
+
describe("BaseModel capabilities override (via toProviderConfig)", () => {
|
|
98
94
|
it("should fully replace detection without contacting the server", async () => {
|
|
99
95
|
mockRpc.mockImplementation(() => {
|
|
100
96
|
throw new Error("override must not trigger a fetch");
|
|
101
97
|
});
|
|
102
98
|
|
|
103
|
-
const model = createModel({
|
|
104
|
-
|
|
99
|
+
const model = createModel({
|
|
100
|
+
test: { capabilities: ["text", "image"], contextSize: 8192 },
|
|
101
|
+
});
|
|
102
|
+
const { input } = await model.toProviderConfig();
|
|
105
103
|
|
|
106
|
-
expect(
|
|
104
|
+
expect(input).toEqual(["text", "image"]);
|
|
107
105
|
expect(mockRpc).not.toHaveBeenCalled();
|
|
108
106
|
});
|
|
109
107
|
|
|
@@ -111,41 +109,41 @@ describe("BaseModel capabilities override", () => {
|
|
|
111
109
|
mockDetection(8192);
|
|
112
110
|
|
|
113
111
|
const model = createModel({ other: { capabilities: ["text"] } });
|
|
114
|
-
const
|
|
112
|
+
const { input } = await model.toProviderConfig();
|
|
115
113
|
|
|
116
|
-
expect(
|
|
114
|
+
expect(input).toEqual(["text"]);
|
|
117
115
|
expect(mockRpc).toHaveBeenCalled();
|
|
118
116
|
});
|
|
119
117
|
});
|
|
120
118
|
|
|
121
119
|
// ─── contextSize override ─────────────────────────────────────────────────
|
|
122
120
|
|
|
123
|
-
describe("BaseModel
|
|
121
|
+
describe("BaseModel contextSize override (via toProviderConfig)", () => {
|
|
124
122
|
it("should return the overridden context size instead of the detected one", async () => {
|
|
125
123
|
mockDetection(8192);
|
|
126
124
|
|
|
127
125
|
const model = createModel({ test: { contextSize: 32768 } });
|
|
128
|
-
const
|
|
126
|
+
const { contextWindow } = await model.toProviderConfig();
|
|
129
127
|
|
|
130
|
-
expect(
|
|
128
|
+
expect(contextWindow).toBe(32768);
|
|
131
129
|
});
|
|
132
130
|
|
|
133
131
|
it("should treat a stored 0 as absent and autodetect", async () => {
|
|
134
132
|
mockDetection(8192);
|
|
135
133
|
|
|
136
134
|
const model = createModel({ test: { contextSize: 0 } });
|
|
137
|
-
const
|
|
135
|
+
const { contextWindow } = await model.toProviderConfig();
|
|
138
136
|
|
|
139
|
-
expect(
|
|
137
|
+
expect(contextWindow).toBe(8192);
|
|
140
138
|
});
|
|
141
139
|
|
|
142
140
|
it("should autodetect when no override matches", async () => {
|
|
143
141
|
mockDetection(8192);
|
|
144
142
|
|
|
145
143
|
const model = createModel({ other: { contextSize: 32768 } });
|
|
146
|
-
const
|
|
144
|
+
const { contextWindow } = await model.toProviderConfig();
|
|
147
145
|
|
|
148
|
-
expect(
|
|
146
|
+
expect(contextWindow).toBe(8192);
|
|
149
147
|
});
|
|
150
148
|
});
|
|
151
149
|
|
|
@@ -251,11 +249,14 @@ describe("toProviderConfig overrides", () => {
|
|
|
251
249
|
});
|
|
252
250
|
});
|
|
253
251
|
|
|
254
|
-
// ───
|
|
252
|
+
// ─── OverrideEntry.summary ────────────────────────────────────────────────────
|
|
253
|
+
|
|
254
|
+
describe("OverrideSummary.of", () => {
|
|
255
|
+
const summary = (override: ModelOverride): string =>
|
|
256
|
+
OverrideSummary.of(override);
|
|
255
257
|
|
|
256
|
-
describe("formatOverrideSummary", () => {
|
|
257
258
|
it("should render every configured field", () => {
|
|
258
|
-
const
|
|
259
|
+
const rendered = summary({
|
|
259
260
|
cost: { input: 0.2, output: 0.6, cacheRead: 0.01 },
|
|
260
261
|
capabilities: ["text", "image"],
|
|
261
262
|
reasoning: false,
|
|
@@ -263,122 +264,111 @@ describe("formatOverrideSummary", () => {
|
|
|
263
264
|
maxTokens: 4096,
|
|
264
265
|
});
|
|
265
266
|
|
|
266
|
-
expect(
|
|
267
|
-
expect(
|
|
268
|
-
expect(
|
|
269
|
-
expect(
|
|
270
|
-
expect(
|
|
271
|
-
expect(
|
|
272
|
-
expect(
|
|
267
|
+
expect(rendered).toContain("input: $0.2");
|
|
268
|
+
expect(rendered).toContain("output: $0.6");
|
|
269
|
+
expect(rendered).toContain("cache read: $0.01");
|
|
270
|
+
expect(rendered).toContain("capabilities: text,image");
|
|
271
|
+
expect(rendered).toContain("reasoning: false");
|
|
272
|
+
expect(rendered).toContain("contextSize: 32768");
|
|
273
|
+
expect(rendered).toContain("maxTokens: 4096");
|
|
273
274
|
});
|
|
274
275
|
|
|
275
276
|
it("should omit zero, undefined and empty fields", () => {
|
|
276
|
-
const
|
|
277
|
+
const rendered = summary({
|
|
277
278
|
cost: { input: 0, output: 0.6 },
|
|
278
279
|
reasoning: undefined,
|
|
279
280
|
});
|
|
280
281
|
|
|
281
|
-
expect(
|
|
282
|
-
expect(
|
|
283
|
-
expect(
|
|
284
|
-
expect(
|
|
285
|
-
expect(
|
|
286
|
-
expect(
|
|
282
|
+
expect(rendered).not.toContain("input:");
|
|
283
|
+
expect(rendered).toContain("output: $0.6");
|
|
284
|
+
expect(rendered).not.toContain("capabilities");
|
|
285
|
+
expect(rendered).not.toContain("reasoning");
|
|
286
|
+
expect(rendered).not.toContain("contextSize");
|
|
287
|
+
expect(rendered).not.toContain("maxTokens");
|
|
287
288
|
});
|
|
288
289
|
|
|
289
290
|
it("should render an em dash for an empty override", () => {
|
|
290
|
-
expect(
|
|
291
|
+
expect(summary({})).toBe("—");
|
|
291
292
|
});
|
|
292
293
|
});
|
|
293
294
|
|
|
294
|
-
// ───
|
|
295
|
-
|
|
296
|
-
describe("override entry list helpers", () => {
|
|
297
|
-
const servers = [
|
|
298
|
-
{ url: "http://a", overrides: { "a-1": { reasoning: false } } },
|
|
299
|
-
{ url: "http://b" },
|
|
300
|
-
] as LlamaServer[];
|
|
301
|
-
|
|
302
|
-
it("addOverrideEntry should append a pattern → override entry", () => {
|
|
303
|
-
const next = addOverrideEntry(servers, 0, "a-2", { contextSize: 4096 });
|
|
304
|
-
expect(next[0].overrides).toEqual({
|
|
305
|
-
"a-1": { reasoning: false },
|
|
306
|
-
"a-2": { contextSize: 4096 },
|
|
307
|
-
});
|
|
308
|
-
});
|
|
309
|
-
|
|
310
|
-
it("updateOverrideEntry should replace in place without reordering", () => {
|
|
311
|
-
const next = updateOverrideEntry(servers, 0, 0, "a-renamed", {
|
|
312
|
-
reasoning: true,
|
|
313
|
-
});
|
|
314
|
-
expect(Object.keys(next[0].overrides!)).toEqual(["a-renamed"]);
|
|
315
|
-
expect(next[0].overrides!["a-renamed"]).toEqual({ reasoning: true });
|
|
316
|
-
expect(next[1]).toBe(servers[1]);
|
|
317
|
-
});
|
|
295
|
+
// ─── CostField tests ──────────────────────────────────────────────────────
|
|
318
296
|
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
const
|
|
322
|
-
expect(
|
|
297
|
+
describe("CostField validation and apply", () => {
|
|
298
|
+
it("CostField.validate should accept blank, non-negative numbers; reject the rest", () => {
|
|
299
|
+
const cost = OverrideFields.byId("cost.input");
|
|
300
|
+
expect(cost.validate("")).toBe("0");
|
|
301
|
+
expect(cost.validate(" 0.5 ")).toBe("0.5");
|
|
302
|
+
expect(cost.validate("-1")).toBeNull();
|
|
303
|
+
expect(cost.validate("abc")).toBeNull();
|
|
304
|
+
expect(cost.validate("Infinity")).toBeNull();
|
|
323
305
|
});
|
|
324
306
|
|
|
325
|
-
it("
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
expect(
|
|
329
|
-
expect(parseCostValue("abc")).toBeNull();
|
|
330
|
-
expect(parseCostValue("Infinity")).toBeNull();
|
|
307
|
+
it("CostField.apply should set a positive value, keeping other fields", () => {
|
|
308
|
+
const override: ModelOverride = { cost: { input: 0.2 } };
|
|
309
|
+
OverrideFields.byId("cost.output").apply(override, "0.6");
|
|
310
|
+
expect(override.cost).toEqual({ input: 0.2, output: 0.6 });
|
|
331
311
|
});
|
|
332
312
|
|
|
333
|
-
it("
|
|
334
|
-
const
|
|
335
|
-
|
|
313
|
+
it("CostField.apply should remove the field on zero", () => {
|
|
314
|
+
const override: ModelOverride = { cost: { input: 0.2, output: 0.6 } };
|
|
315
|
+
OverrideFields.byId("cost.input").apply(override, "0");
|
|
316
|
+
expect(override.cost).toEqual({ output: 0.6 });
|
|
336
317
|
});
|
|
337
318
|
|
|
338
|
-
it("
|
|
339
|
-
const
|
|
340
|
-
|
|
341
|
-
|
|
319
|
+
it("CostField.apply should drop the whole cost object when the last field is zeroed", () => {
|
|
320
|
+
const single: ModelOverride = { cost: { input: 0.2 } };
|
|
321
|
+
OverrideFields.byId("cost.input").apply(single, "0");
|
|
322
|
+
expect(single.cost).toBeUndefined();
|
|
342
323
|
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
expect(
|
|
324
|
+
const empty: ModelOverride = {};
|
|
325
|
+
OverrideFields.byId("cost.input").apply(empty, "0");
|
|
326
|
+
expect(empty.cost).toBeUndefined();
|
|
346
327
|
});
|
|
347
328
|
|
|
348
|
-
it("
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
});
|
|
329
|
+
it("CostField.apply should treat an empty override as unset cost", () => {
|
|
330
|
+
const override: ModelOverride = {};
|
|
331
|
+
OverrideFields.byId("cost.input").apply(override, "0.5");
|
|
332
|
+
expect(override.cost).toEqual({ input: 0.5 });
|
|
352
333
|
});
|
|
353
334
|
});
|
|
354
335
|
|
|
355
|
-
describe("
|
|
336
|
+
describe("OverrideField.displayValue", () => {
|
|
337
|
+
const display = (id: string, override: ModelOverride): string =>
|
|
338
|
+
OverrideFields.byId(id).displayValue(new OverrideEntry("gpt-*", override));
|
|
339
|
+
|
|
356
340
|
it("formats cost fields, defaulting to 0", () => {
|
|
357
|
-
expect(
|
|
358
|
-
expect(
|
|
359
|
-
|
|
360
|
-
);
|
|
361
|
-
expect(overrideFieldValue("cost.cacheWrite", {})).toBe("0");
|
|
341
|
+
expect(display("cost.input", {})).toBe("0");
|
|
342
|
+
expect(display("cost.input", { cost: { input: 0.2 } })).toBe("0.2");
|
|
343
|
+
expect(display("cost.cacheWrite", {})).toBe("0");
|
|
362
344
|
});
|
|
363
345
|
|
|
364
346
|
it("formats capabilities and reasoning labels", () => {
|
|
365
|
-
expect(
|
|
366
|
-
expect(
|
|
367
|
-
|
|
368
|
-
)
|
|
369
|
-
expect(
|
|
370
|
-
expect(
|
|
347
|
+
expect(display("capabilities", {})).toBe("text");
|
|
348
|
+
expect(display("capabilities", { capabilities: ["text", "image"] })).toBe(
|
|
349
|
+
"text | image",
|
|
350
|
+
);
|
|
351
|
+
expect(display("reasoning", {})).toBe("true");
|
|
352
|
+
expect(display("reasoning", { reasoning: false })).toBe("false");
|
|
371
353
|
});
|
|
372
354
|
|
|
373
355
|
it("formats maxTokens/contextSize, defaulting to 0", () => {
|
|
374
|
-
expect(
|
|
375
|
-
expect(
|
|
376
|
-
expect(
|
|
377
|
-
"8192",
|
|
378
|
-
);
|
|
356
|
+
expect(display("maxTokens", {})).toBe("0");
|
|
357
|
+
expect(display("maxTokens", { maxTokens: 4096 })).toBe("4096");
|
|
358
|
+
expect(display("contextSize", { contextSize: 8192 })).toBe("8192");
|
|
379
359
|
});
|
|
380
360
|
|
|
381
|
-
it("returns
|
|
382
|
-
expect(
|
|
361
|
+
it("returns the entry's pattern for the pattern field", () => {
|
|
362
|
+
expect(
|
|
363
|
+
OverrideFields.byId("pattern").displayValue(
|
|
364
|
+
new OverrideEntry("gpt-*", {}),
|
|
365
|
+
),
|
|
366
|
+
).toBe("gpt-*");
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
it("throws for unknown fields", () => {
|
|
370
|
+
expect(() => OverrideFields.byId("other")).toThrow(
|
|
371
|
+
"Unknown override field: other",
|
|
372
|
+
);
|
|
383
373
|
});
|
|
384
374
|
});
|