pi-llama-cpp 0.11.0 → 0.12.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 +174 -58
- package/package.json +4 -4
- package/src/interfaces/server.ts +7 -0
- package/src/interfaces/settings.ts +65 -0
- package/src/managers/command.ts +108 -31
- package/src/managers/events.ts +2 -2
- package/src/managers/server.ts +12 -9
- package/src/managers/settings.ts +120 -45
- package/src/models/baseModel.ts +37 -7
- package/src/models/routerModel.ts +2 -1
- package/src/server.ts +38 -4
- package/src/sse/manager.ts +9 -8
- package/src/ui/dialog.ts +287 -0
- package/src/ui/overrideEntryEditor.ts +119 -0
- package/src/ui/overrideSettingsList.ts +682 -0
- package/src/ui/serverListEditor.ts +0 -449
- package/src/ui/serverSettingsList.ts +466 -0
- package/src/ui/strings.ts +127 -0
- package/src/utils/settingsStore.ts +2 -6
- package/tests/commandManager.test.ts +106 -16
- package/tests/dialog.test.ts +186 -0
- package/tests/events.test.ts +12 -9
- package/tests/legacyModel.test.ts +4 -19
- package/tests/mocks.ts +12 -8
- package/tests/overrides.test.ts +352 -0
- package/tests/server.test.ts +12 -12
- package/tests/serverManager.test.ts +24 -58
- package/tests/settings.test.ts +489 -43
- package/tests/settingsStore.test.ts +0 -19
- package/tests/singleModel.test.ts +32 -0
- package/tests/sseManager.test.ts +4 -4
- package/tests/serverListEditor.test.ts +0 -637
package/src/managers/command.ts
CHANGED
|
@@ -14,7 +14,8 @@ import { Mode } from "../enums/mode";
|
|
|
14
14
|
import { Status } from "../enums/status";
|
|
15
15
|
import { LlamaSettings } from "../interfaces/settings";
|
|
16
16
|
import { BaseModel } from "../models/baseModel";
|
|
17
|
-
import {
|
|
17
|
+
import { createOverrideSettingsList } from "../ui/overrideSettingsList";
|
|
18
|
+
import { ServerSettingsList } from "../ui/serverSettingsList";
|
|
18
19
|
import { errorMessage } from "../utils/errors";
|
|
19
20
|
import { EventManager } from "./events";
|
|
20
21
|
import { ServerManager } from "./server";
|
|
@@ -64,15 +65,20 @@ const ARGUMENT_COMPLETIONS: AutocompleteItem[] = [
|
|
|
64
65
|
label: "unload",
|
|
65
66
|
description: "Unload all models",
|
|
66
67
|
},
|
|
68
|
+
{
|
|
69
|
+
value: "settings",
|
|
70
|
+
label: "settings",
|
|
71
|
+
description: "Configure llamaSettings",
|
|
72
|
+
},
|
|
67
73
|
{
|
|
68
74
|
value: "servers",
|
|
69
75
|
label: "servers",
|
|
70
76
|
description: "Manage llama.cpp server URLs",
|
|
71
77
|
},
|
|
72
78
|
{
|
|
73
|
-
value: "
|
|
74
|
-
label: "
|
|
75
|
-
description: "
|
|
79
|
+
value: "overrides",
|
|
80
|
+
label: "overrides",
|
|
81
|
+
description: "Manage llama.cpp model overrides",
|
|
76
82
|
},
|
|
77
83
|
];
|
|
78
84
|
|
|
@@ -96,17 +102,17 @@ const parseMs = (value: string): number =>
|
|
|
96
102
|
* Builds the `SettingsList` items for `/models settings` from the current
|
|
97
103
|
* (merged) values of the scalar `llamaSettings` fields.
|
|
98
104
|
*/
|
|
99
|
-
export const buildSettingsItems = (
|
|
105
|
+
export const buildSettingsItems = async (
|
|
100
106
|
settings: LlamaSettingsManager,
|
|
101
|
-
): SettingItem[] => {
|
|
102
|
-
const { pollingTimeout, serverTimeout } = settings.resolveTimeouts();
|
|
107
|
+
): Promise<SettingItem[]> => {
|
|
108
|
+
const { pollingTimeout, serverTimeout } = await settings.resolveTimeouts();
|
|
103
109
|
|
|
104
110
|
return [
|
|
105
111
|
{
|
|
106
112
|
id: Options.REACT_TO_MODEL_SELECT,
|
|
107
113
|
label: "React to model selection",
|
|
108
114
|
description: "Load the model when you pick it in Pi (immediate)",
|
|
109
|
-
currentValue: settings.resolveReactToModelSelect() ? "on" : "off",
|
|
115
|
+
currentValue: (await settings.resolveReactToModelSelect()) ? "on" : "off",
|
|
110
116
|
values: ["on", "off"],
|
|
111
117
|
},
|
|
112
118
|
{
|
|
@@ -114,14 +120,14 @@ export const buildSettingsItems = (
|
|
|
114
120
|
label: "Autoload on message",
|
|
115
121
|
description:
|
|
116
122
|
"Auto-load the selected model when you send a message (immediate)",
|
|
117
|
-
currentValue: settings.resolveAutoloadOnMessage() ? "on" : "off",
|
|
123
|
+
currentValue: (await settings.resolveAutoloadOnMessage()) ? "on" : "off",
|
|
118
124
|
values: ["on", "off"],
|
|
119
125
|
},
|
|
120
126
|
{
|
|
121
127
|
id: Options.SORT_BY,
|
|
122
128
|
label: "Sort models by",
|
|
123
129
|
description: "Order of models in /models (next open)",
|
|
124
|
-
currentValue: settings.resolveSortBy(),
|
|
130
|
+
currentValue: await settings.resolveSortBy(),
|
|
125
131
|
values: [...SORT_VALUES],
|
|
126
132
|
},
|
|
127
133
|
{
|
|
@@ -208,9 +214,17 @@ export class CommandManager {
|
|
|
208
214
|
return;
|
|
209
215
|
}
|
|
210
216
|
|
|
211
|
-
// Servers editor:
|
|
217
|
+
// Servers editor: re-registers providers after editing so changes
|
|
218
|
+
// (add / remove / URL / id / name) apply immediately
|
|
212
219
|
if (args === "servers") {
|
|
213
|
-
await this.runServersEditor(ctx);
|
|
220
|
+
await this.runServersEditor(ctx, pi);
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// Overrides editor: re-registers providers after editing so new
|
|
225
|
+
// overrides take effect on the next request
|
|
226
|
+
if (args === "overrides") {
|
|
227
|
+
await this.runOverridesEditor(ctx, pi);
|
|
214
228
|
return;
|
|
215
229
|
}
|
|
216
230
|
|
|
@@ -223,17 +237,15 @@ export class CommandManager {
|
|
|
223
237
|
}
|
|
224
238
|
|
|
225
239
|
if (args === "unload") {
|
|
226
|
-
await
|
|
227
|
-
|
|
228
|
-
);
|
|
240
|
+
const models = await this.serverManager.getAllModels();
|
|
241
|
+
await Promise.all(models.map((model) => model.unload()));
|
|
229
242
|
ctx.ui.notify(`Unloaded all ${PROVIDER_NAME} models`, "info");
|
|
230
243
|
return;
|
|
231
244
|
}
|
|
232
245
|
|
|
233
246
|
if (args === "info") {
|
|
234
|
-
const
|
|
235
|
-
|
|
236
|
-
);
|
|
247
|
+
const models = await this.serverManager.getAllModels();
|
|
248
|
+
const infos = await Promise.all(models.map((model) => model.getInfo()));
|
|
237
249
|
ctx.ui.notify(ctx.ui.theme.fg("accent", infos.join("\n")), "info");
|
|
238
250
|
return;
|
|
239
251
|
}
|
|
@@ -248,7 +260,9 @@ export class CommandManager {
|
|
|
248
260
|
*
|
|
249
261
|
* Writes go to the global `~/.pi/agent/settings.json` via
|
|
250
262
|
* `LlamaSettingsManager.setLlamaSetting()`; write errors are notified
|
|
251
|
-
* and leave the dialog open with values unchanged.
|
|
263
|
+
* and leave the dialog open with values unchanged. These settings
|
|
264
|
+
* (reactToModelSelect, autoloadOnMessage, sortBy, timeouts) do not
|
|
265
|
+
* require provider re-registration.
|
|
252
266
|
*/
|
|
253
267
|
private async runSettingsMenu(ctx: ExtensionCommandContext): Promise<void> {
|
|
254
268
|
if (ctx.mode !== "tui") {
|
|
@@ -259,7 +273,7 @@ export class CommandManager {
|
|
|
259
273
|
return;
|
|
260
274
|
}
|
|
261
275
|
|
|
262
|
-
const items = buildSettingsItems(this.settings);
|
|
276
|
+
const items = await buildSettingsItems(this.settings);
|
|
263
277
|
|
|
264
278
|
await ctx.ui.custom<void>(
|
|
265
279
|
(_tui, _theme, _kb, done) =>
|
|
@@ -282,16 +296,19 @@ export class CommandManager {
|
|
|
282
296
|
|
|
283
297
|
/**
|
|
284
298
|
* Runs the interactive servers editor for `llamaSettings.servers`.
|
|
285
|
-
* Enter
|
|
286
|
-
* d deletes (after
|
|
299
|
+
* Enter on a server row drills into its field-edit submenu (URL/id/name);
|
|
300
|
+
* a adds a new server (inline Input), d deletes (after confirmation);
|
|
301
|
+
* Esc closes.
|
|
287
302
|
*
|
|
288
303
|
* Writes go to the global `~/.pi/agent/settings.json` via
|
|
289
304
|
* `LlamaSettingsManager.setLlamaSetting()`; write errors are notified and
|
|
290
|
-
* the editor stays open with the pre-mutation list.
|
|
291
|
-
*
|
|
292
|
-
* scanned — run `/models` to see them.
|
|
305
|
+
* the editor stays open with the pre-mutation list. After closing,
|
|
306
|
+
* providers are re-registered so server changes apply immediately.
|
|
293
307
|
*/
|
|
294
|
-
private async runServersEditor(
|
|
308
|
+
private async runServersEditor(
|
|
309
|
+
ctx: ExtensionCommandContext,
|
|
310
|
+
pi: ExtensionAPI,
|
|
311
|
+
): Promise<void> {
|
|
295
312
|
if (ctx.mode !== "tui") {
|
|
296
313
|
ctx.ui.notify(
|
|
297
314
|
"/models servers requires an interactive session (TUI)",
|
|
@@ -300,22 +317,79 @@ export class CommandManager {
|
|
|
300
317
|
return;
|
|
301
318
|
}
|
|
302
319
|
|
|
303
|
-
const servers = this.settings.
|
|
320
|
+
const servers = await this.settings.getLlamaServers();
|
|
304
321
|
|
|
305
322
|
await ctx.ui.custom<void>(
|
|
306
323
|
(tui, theme, keybindings, done) =>
|
|
307
|
-
new
|
|
324
|
+
new ServerSettingsList({
|
|
308
325
|
tui,
|
|
309
326
|
theme,
|
|
310
327
|
keybindings,
|
|
311
328
|
servers,
|
|
312
329
|
persist: (next) => this.settings.setLlamaSetting("servers", next),
|
|
313
|
-
done: () =>
|
|
330
|
+
done: () => {
|
|
331
|
+
done(undefined);
|
|
332
|
+
// Re-register providers so the updated server list takes effect
|
|
333
|
+
this.serverManager.update(pi);
|
|
334
|
+
},
|
|
314
335
|
onError: (message) => ctx.ui.notify(message, "error"),
|
|
315
336
|
}),
|
|
316
337
|
);
|
|
317
338
|
}
|
|
318
339
|
|
|
340
|
+
/**
|
|
341
|
+
* Runs the interactive overrides editor for
|
|
342
|
+
* `llamaSettings.servers[].overrides`: a SettingsList of servers drilling
|
|
343
|
+
* down into each server's override entries (one row per pattern, with
|
|
344
|
+
* add/delete support). Within a server's entry list: Enter drills into
|
|
345
|
+
* the field-edit submenu; a adds, d deletes (after confirmation).
|
|
346
|
+
*
|
|
347
|
+
* Fields use a mix of finite (Enter to cycle) and infinite (Enter to
|
|
348
|
+
* type) editing:
|
|
349
|
+
*
|
|
350
|
+
* - Pattern / costs (input, output, cacheRead, cacheWrite): infinite —
|
|
351
|
+
* Enter opens an Input for typing.
|
|
352
|
+
* - Capabilities: finite — Enter cycles between `text` and `text | image`.
|
|
353
|
+
* - Reasoning: finite — Enter cycles between `true` and `false`.
|
|
354
|
+
*
|
|
355
|
+
* Servers themselves are not managed here — use `/models servers`.
|
|
356
|
+
*
|
|
357
|
+
* Writes go to the global `~/.pi/agent/settings.json` via
|
|
358
|
+
* `LlamaSettingsManager.setLlamaSetting()`; write errors are notified and
|
|
359
|
+
* leave the values unchanged. After closing, providers are
|
|
360
|
+
* re-registered so new overrides take effect on the next request.
|
|
361
|
+
*/
|
|
362
|
+
private async runOverridesEditor(
|
|
363
|
+
ctx: ExtensionCommandContext,
|
|
364
|
+
pi: ExtensionAPI,
|
|
365
|
+
): Promise<void> {
|
|
366
|
+
if (ctx.mode !== "tui") {
|
|
367
|
+
ctx.ui.notify(
|
|
368
|
+
"/models overrides requires an interactive session (TUI)",
|
|
369
|
+
"warning",
|
|
370
|
+
);
|
|
371
|
+
return;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
const servers = await this.settings.getLlamaServers();
|
|
375
|
+
await ctx.ui.custom<void>((tui, theme, keybindings, done) =>
|
|
376
|
+
createOverrideSettingsList({
|
|
377
|
+
tui,
|
|
378
|
+
theme,
|
|
379
|
+
keybindings,
|
|
380
|
+
servers,
|
|
381
|
+
persist: (next) => this.settings.setLlamaSetting("servers", next),
|
|
382
|
+
done: () => {
|
|
383
|
+
done(undefined);
|
|
384
|
+
// Re-register providers so the updated overrides take effect
|
|
385
|
+
this.serverManager.update(pi);
|
|
386
|
+
},
|
|
387
|
+
onError: (message) => ctx.ui.notify(message, "error"),
|
|
388
|
+
onChanged: () => {}, // no per-change notification needed
|
|
389
|
+
}),
|
|
390
|
+
);
|
|
391
|
+
}
|
|
392
|
+
|
|
319
393
|
/**
|
|
320
394
|
* Notifies the user that a server is unreachable.
|
|
321
395
|
*/
|
|
@@ -332,7 +406,7 @@ export class CommandManager {
|
|
|
332
406
|
): Promise<void> {
|
|
333
407
|
const event = await this.modelSelectionHandler(
|
|
334
408
|
ctx,
|
|
335
|
-
this.serverManager.getAllModels(),
|
|
409
|
+
await this.serverManager.getAllModels(),
|
|
336
410
|
);
|
|
337
411
|
|
|
338
412
|
if (!event) return;
|
|
@@ -522,7 +596,10 @@ export class CommandManager {
|
|
|
522
596
|
: [Action.SWITCH, ...base],
|
|
523
597
|
[Status.LOADING]: [...base],
|
|
524
598
|
[Status.FAILED]: [Action.RETRY, ...base],
|
|
525
|
-
[Status.SLEEPING]:
|
|
599
|
+
[Status.SLEEPING]:
|
|
600
|
+
model.mode === Mode.ROUTER
|
|
601
|
+
? [Action.SWITCH, Action.UNLOAD, ...base]
|
|
602
|
+
: [Action.SWITCH, ...base],
|
|
526
603
|
[Status.UNLOADED]: [Action.LOAD_AND_SWITCH, Action.LOAD, ...base],
|
|
527
604
|
[Status.UNAUTHORIZED]: [...base],
|
|
528
605
|
};
|
package/src/managers/events.ts
CHANGED
|
@@ -47,7 +47,7 @@ export class EventManager {
|
|
|
47
47
|
*/
|
|
48
48
|
async onModelSelect(event: ModelSelectEvent, ctx: ExtensionContext) {
|
|
49
49
|
// Check if the model_select event should be used
|
|
50
|
-
if (!this.settings.resolveReactToModelSelect()) return;
|
|
50
|
+
if (!(await this.settings.resolveReactToModelSelect())) return;
|
|
51
51
|
|
|
52
52
|
for (const { providerId, models } of this.serverManager.servers) {
|
|
53
53
|
if (event.model.provider !== providerId) continue;
|
|
@@ -72,7 +72,7 @@ export class EventManager {
|
|
|
72
72
|
* @param model The model to potentially auto-load
|
|
73
73
|
*/
|
|
74
74
|
private async autoLoadIfNeeded(model: BaseModel): Promise<void> {
|
|
75
|
-
if (!this.settings.resolveAutoloadOnMessage()) return;
|
|
75
|
+
if (!(await this.settings.resolveAutoloadOnMessage())) return;
|
|
76
76
|
|
|
77
77
|
const status = await model.getStatus();
|
|
78
78
|
if (status !== Status.UNLOADED) return;
|
package/src/managers/server.ts
CHANGED
|
@@ -30,7 +30,7 @@ export class ServerManager {
|
|
|
30
30
|
*/
|
|
31
31
|
async initialize(pi: ExtensionAPI) {
|
|
32
32
|
// Register the providers with the configured server timeout
|
|
33
|
-
const { serverTimeout } = this.settings.resolveTimeouts();
|
|
33
|
+
const { serverTimeout } = await this.settings.resolveTimeouts();
|
|
34
34
|
await this.update(pi, serverTimeout);
|
|
35
35
|
}
|
|
36
36
|
|
|
@@ -51,7 +51,7 @@ export class ServerManager {
|
|
|
51
51
|
// (add / remove / URL / id / name) apply on the next scan
|
|
52
52
|
const fresh: Server[] = [];
|
|
53
53
|
const seen = new Set<string>(); // dedupe repeated URLs (same providerId)
|
|
54
|
-
for (const server of this.settings.resolveServers()) {
|
|
54
|
+
for (const server of await this.settings.resolveServers()) {
|
|
55
55
|
if (seen.has(server.providerId)) continue;
|
|
56
56
|
seen.add(server.providerId);
|
|
57
57
|
fresh.push(server);
|
|
@@ -171,16 +171,20 @@ export class ServerManager {
|
|
|
171
171
|
|
|
172
172
|
/**
|
|
173
173
|
* Returns all models from all servers, sorted by the configured sort mode.
|
|
174
|
+
* Servers maintain their order from `llamaSettings`; sorting only applies
|
|
175
|
+
* to models within each server.
|
|
174
176
|
*
|
|
175
177
|
* @returns Flat array of all models across all servers
|
|
176
178
|
*/
|
|
177
|
-
getAllModels(): BaseModel[] {
|
|
178
|
-
const sortBy = this.settings.resolveSortBy();
|
|
179
|
-
const allModels = this.servers.flatMap((s) => s.models);
|
|
179
|
+
async getAllModels(): Promise<BaseModel[]> {
|
|
180
|
+
const sortBy = await this.settings.resolveSortBy();
|
|
180
181
|
|
|
181
|
-
if (sortBy === "api")
|
|
182
|
+
if (sortBy === "api") {
|
|
183
|
+
return this.servers.flatMap((s) => s.models);
|
|
184
|
+
}
|
|
182
185
|
|
|
183
|
-
|
|
186
|
+
const sorter = ServerManager.SORTERS[sortBy];
|
|
187
|
+
return this.servers.flatMap((s) => [...s.models].sort(sorter));
|
|
184
188
|
}
|
|
185
189
|
|
|
186
190
|
private static sortByIdAsc(a: BaseModel, b: BaseModel): number {
|
|
@@ -204,8 +208,7 @@ export class ServerManager {
|
|
|
204
208
|
}
|
|
205
209
|
|
|
206
210
|
/**
|
|
207
|
-
* Comparators for
|
|
208
|
-
* order (short-circuited in {@link ServerManager.getAllModels}).
|
|
211
|
+
* Comparators for sorting models within each server.
|
|
209
212
|
*/
|
|
210
213
|
private static readonly SORTERS: Record<
|
|
211
214
|
Exclude<SortBy, "api">,
|
package/src/managers/settings.ts
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import { ApiKeyCredential, ModelThinkingLevel } from "@earendil-works/pi-ai";
|
|
2
2
|
import {
|
|
3
|
+
getAgentDir,
|
|
3
4
|
readStoredCredential,
|
|
4
5
|
SettingsManager,
|
|
5
6
|
} from "@earendil-works/pi-coding-agent";
|
|
7
|
+
import { access } from "node:fs/promises";
|
|
8
|
+
import { join } from "node:path";
|
|
6
9
|
import {
|
|
7
10
|
API_KEY_PLACEHOLDER,
|
|
8
11
|
AUTOLOAD_ON_MESSAGE,
|
|
@@ -15,7 +18,11 @@ import {
|
|
|
15
18
|
THINKING_BUDGETS,
|
|
16
19
|
type SortBy,
|
|
17
20
|
} from "../constants";
|
|
18
|
-
import {
|
|
21
|
+
import {
|
|
22
|
+
LlamaServer,
|
|
23
|
+
LlamaSettings,
|
|
24
|
+
ModelOverride,
|
|
25
|
+
} from "../interfaces/settings";
|
|
19
26
|
import { Server } from "../server";
|
|
20
27
|
import { SettingsStore } from "../utils/settingsStore";
|
|
21
28
|
import { isValidServerUrl, normalizeUrl } from "../utils/urls";
|
|
@@ -23,7 +30,22 @@ import { isValidServerUrl, normalizeUrl } from "../utils/urls";
|
|
|
23
30
|
export class LlamaSettingsManager {
|
|
24
31
|
private settingsManager = SettingsManager.create(process.cwd());
|
|
25
32
|
|
|
26
|
-
|
|
33
|
+
private globalStore = new SettingsStore(join(getAgentDir(), "settings.json"));
|
|
34
|
+
private projectStore = new SettingsStore(
|
|
35
|
+
join(process.cwd(), ".pi", "settings.json"),
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Check if project settings file exists in the current working directory.
|
|
40
|
+
*/
|
|
41
|
+
private async hasProjectSettings(): Promise<boolean> {
|
|
42
|
+
try {
|
|
43
|
+
await access(join(process.cwd(), ".pi", "settings.json"));
|
|
44
|
+
return true;
|
|
45
|
+
} catch {
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
27
49
|
|
|
28
50
|
/** Warnings collected during URL resolution (dropped invalid entries). */
|
|
29
51
|
private warnings: string[] = [];
|
|
@@ -38,29 +60,31 @@ export class LlamaSettingsManager {
|
|
|
38
60
|
}
|
|
39
61
|
|
|
40
62
|
/**
|
|
41
|
-
*
|
|
63
|
+
* Reloads settings from disk and returns merged project/global settings.
|
|
64
|
+
* Project settings override global settings.
|
|
42
65
|
*/
|
|
43
|
-
private
|
|
44
|
-
|
|
66
|
+
private async getMergedSettings(): Promise<Record<string, any>> {
|
|
67
|
+
await this.settingsManager.reload();
|
|
68
|
+
return {
|
|
45
69
|
...this.settingsManager.getGlobalSettings(),
|
|
46
70
|
...this.settingsManager.getProjectSettings(),
|
|
47
71
|
} as Record<string, any>;
|
|
48
|
-
return merged;
|
|
49
72
|
}
|
|
50
73
|
|
|
51
74
|
/**
|
|
52
|
-
* Convenience
|
|
75
|
+
* Convenience method for the `llamaSettings` key.
|
|
76
|
+
* Reloads settings from disk before reading.
|
|
53
77
|
*/
|
|
54
|
-
|
|
55
|
-
return this.
|
|
78
|
+
async getLlamaSettings(): Promise<LlamaSettings> {
|
|
79
|
+
return (await this.getMergedSettings())[SETTINGS_KEY] ?? {};
|
|
56
80
|
}
|
|
57
81
|
|
|
58
82
|
/**
|
|
59
|
-
* Convenience
|
|
60
|
-
* global, per-key merge)
|
|
83
|
+
* Convenience method for the merged `servers` list (project overrides
|
|
84
|
+
* global, per-key merge). Reloads settings from disk before reading.
|
|
61
85
|
*/
|
|
62
|
-
|
|
63
|
-
return this.
|
|
86
|
+
async getLlamaServers(): Promise<LlamaServer[]> {
|
|
87
|
+
return (await this.getLlamaSettings()).servers ?? [];
|
|
64
88
|
}
|
|
65
89
|
|
|
66
90
|
/**
|
|
@@ -73,14 +97,14 @@ export class LlamaSettingsManager {
|
|
|
73
97
|
*
|
|
74
98
|
* @returns The list of URLs to use
|
|
75
99
|
*/
|
|
76
|
-
resolveUrls(): string[] {
|
|
100
|
+
async resolveUrls(): Promise<string[]> {
|
|
77
101
|
let response = this.resolveEnvUrls();
|
|
78
102
|
if (response.length > 0) return response;
|
|
79
103
|
|
|
80
|
-
response = this.resolveServerUrls();
|
|
104
|
+
response = await this.resolveServerUrls();
|
|
81
105
|
if (response.length > 0) return response;
|
|
82
106
|
|
|
83
|
-
response = this.resolveLegacyUrls();
|
|
107
|
+
response = await this.resolveLegacyUrls();
|
|
84
108
|
if (response.length > 0) return response;
|
|
85
109
|
|
|
86
110
|
return [LLAMA_SERVER_URL];
|
|
@@ -101,22 +125,24 @@ export class LlamaSettingsManager {
|
|
|
101
125
|
/**
|
|
102
126
|
* Resolves the llama-server URLs from `llamaSettings.servers`.
|
|
103
127
|
* Settings are merged, prioritizing project over global settings.
|
|
128
|
+
* Reloads settings from disk before reading.
|
|
104
129
|
*
|
|
105
130
|
* @returns A list of detected URLs
|
|
106
131
|
*/
|
|
107
|
-
private resolveServerUrls(): string[] {
|
|
108
|
-
const { servers = [] } = this.
|
|
132
|
+
private async resolveServerUrls(): Promise<string[]> {
|
|
133
|
+
const { servers = [] } = await this.getLlamaSettings();
|
|
109
134
|
return servers.map((s) => this.parseUrls(s.url)).flat();
|
|
110
135
|
}
|
|
111
136
|
|
|
112
137
|
/**
|
|
113
|
-
* Resolves the llama-server URLs from `
|
|
138
|
+
* Resolves the llama-server URLs from `llamaServerUrl` legacy key.
|
|
114
139
|
* Settings are merged, prioritizing project over global settings.
|
|
140
|
+
* Reloads settings from disk before reading.
|
|
115
141
|
*
|
|
116
142
|
* @returns A list of detected URLs
|
|
117
143
|
*/
|
|
118
|
-
private resolveLegacyUrls(): string[] {
|
|
119
|
-
const { llamaServerUrl = null } = this.
|
|
144
|
+
private async resolveLegacyUrls(): Promise<string[]> {
|
|
145
|
+
const { llamaServerUrl = null } = await this.getMergedSettings();
|
|
120
146
|
if (!llamaServerUrl) return [];
|
|
121
147
|
|
|
122
148
|
return this.parseUrls(llamaServerUrl);
|
|
@@ -147,26 +173,53 @@ export class LlamaSettingsManager {
|
|
|
147
173
|
});
|
|
148
174
|
}
|
|
149
175
|
|
|
176
|
+
/**
|
|
177
|
+
* Resolves the override map for a given server URL.
|
|
178
|
+
*
|
|
179
|
+
* Reads the `overrides` field from the matching server config and returns
|
|
180
|
+
* a map of model ID → override. Returns an empty object when the server
|
|
181
|
+
* has no `overrides` defined.
|
|
182
|
+
*
|
|
183
|
+
* @param serverUrl - The URL of the server to resolve overrides for
|
|
184
|
+
* @returns A map of model ID to override configuration (partial fields,
|
|
185
|
+
* fallbacks applied at consumption time)
|
|
186
|
+
*/
|
|
187
|
+
async resolveServerOverrides(
|
|
188
|
+
serverUrl: string,
|
|
189
|
+
): Promise<Record<string, ModelOverride>> {
|
|
190
|
+
const serverConfig = (await this.getLlamaSettings()).servers?.find(
|
|
191
|
+
(s: { url: string }) => s.url === serverUrl,
|
|
192
|
+
);
|
|
193
|
+
return serverConfig?.overrides ?? {};
|
|
194
|
+
}
|
|
195
|
+
|
|
150
196
|
/**
|
|
151
197
|
* Resolves the servers that this extension will use.
|
|
152
198
|
* Uses `resolveUrls()` as the source of truth for URLs (env > settings >
|
|
153
|
-
* legacy > default), then applies `id`/`name` from
|
|
154
|
-
* as overrides when available.
|
|
199
|
+
* legacy > default), then applies `id`/`name`/`overrides` from
|
|
200
|
+
* `llamaSettings.servers` as overrides when available.
|
|
201
|
+
* Reloads settings from disk before reading.
|
|
155
202
|
*
|
|
156
203
|
* @returns A list of Server objects
|
|
157
204
|
*/
|
|
158
|
-
resolveServers(): Server[] {
|
|
159
|
-
const urls = this.resolveUrls();
|
|
160
|
-
const serverConfigs = this.
|
|
205
|
+
async resolveServers(): Promise<Server[]> {
|
|
206
|
+
const urls = await this.resolveUrls();
|
|
207
|
+
const serverConfigs = (await this.getLlamaSettings()).servers ?? [];
|
|
161
208
|
|
|
162
|
-
|
|
209
|
+
const servers: Server[] = [];
|
|
210
|
+
for (const url of urls) {
|
|
163
211
|
const config = serverConfigs.find((s) => s.url === url);
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
212
|
+
const overrides = await this.resolveServerOverrides(url);
|
|
213
|
+
servers.push(
|
|
214
|
+
new Server(this, {
|
|
215
|
+
baseUrl: url,
|
|
216
|
+
customId: config?.id,
|
|
217
|
+
customName: config?.name,
|
|
218
|
+
overrides,
|
|
219
|
+
}),
|
|
220
|
+
);
|
|
221
|
+
}
|
|
222
|
+
return servers;
|
|
170
223
|
}
|
|
171
224
|
|
|
172
225
|
/**
|
|
@@ -206,8 +259,11 @@ export class LlamaSettingsManager {
|
|
|
206
259
|
*
|
|
207
260
|
* @returns `true` if the extension should load the model on model_select
|
|
208
261
|
*/
|
|
209
|
-
resolveReactToModelSelect(): boolean {
|
|
210
|
-
return
|
|
262
|
+
async resolveReactToModelSelect(): Promise<boolean> {
|
|
263
|
+
return (
|
|
264
|
+
(await this.getLlamaSettings()).reactToModelSelect ??
|
|
265
|
+
REACT_TO_MODEL_SELECT
|
|
266
|
+
);
|
|
211
267
|
}
|
|
212
268
|
|
|
213
269
|
/**
|
|
@@ -215,8 +271,10 @@ export class LlamaSettingsManager {
|
|
|
215
271
|
*
|
|
216
272
|
* @returns `true` if the extension should auto-load models
|
|
217
273
|
*/
|
|
218
|
-
resolveAutoloadOnMessage(): boolean {
|
|
219
|
-
return
|
|
274
|
+
async resolveAutoloadOnMessage(): Promise<boolean> {
|
|
275
|
+
return (
|
|
276
|
+
(await this.getLlamaSettings()).autoloadOnMessage ?? AUTOLOAD_ON_MESSAGE
|
|
277
|
+
);
|
|
220
278
|
}
|
|
221
279
|
|
|
222
280
|
/**
|
|
@@ -224,10 +282,14 @@ export class LlamaSettingsManager {
|
|
|
224
282
|
*
|
|
225
283
|
* @returns Object with polling and server timeout values
|
|
226
284
|
*/
|
|
227
|
-
resolveTimeouts(): {
|
|
285
|
+
async resolveTimeouts(): Promise<{
|
|
286
|
+
pollingTimeout: number;
|
|
287
|
+
serverTimeout: number;
|
|
288
|
+
}> {
|
|
289
|
+
const llamaSettings = await this.getLlamaSettings();
|
|
228
290
|
return {
|
|
229
|
-
pollingTimeout:
|
|
230
|
-
serverTimeout:
|
|
291
|
+
pollingTimeout: llamaSettings.pollingTimeout ?? POLLING_TIMEOUT,
|
|
292
|
+
serverTimeout: llamaSettings.serverTimeout ?? SERVER_TIMEOUT,
|
|
231
293
|
};
|
|
232
294
|
}
|
|
233
295
|
|
|
@@ -236,13 +298,16 @@ export class LlamaSettingsManager {
|
|
|
236
298
|
*
|
|
237
299
|
* @returns The sort order: "asc", "desc", "asc-name", "desc-name", or "api"
|
|
238
300
|
*/
|
|
239
|
-
resolveSortBy(): SortBy {
|
|
240
|
-
return this.
|
|
301
|
+
async resolveSortBy(): Promise<SortBy> {
|
|
302
|
+
return (await this.getLlamaSettings()).sortBy ?? SORT_BY;
|
|
241
303
|
}
|
|
242
304
|
|
|
243
305
|
/**
|
|
244
|
-
* Persists one llamaSettings field to
|
|
245
|
-
*
|
|
306
|
+
* Persists one llamaSettings field to settings and reloads the in-memory
|
|
307
|
+
* settings so resolvers see the change immediately.
|
|
308
|
+
*
|
|
309
|
+
* When `scope` is `"auto"` (default), writes to the project `.pi/settings.json`
|
|
310
|
+
* if it exists, otherwise to global `~/.pi/agent/settings.json`.
|
|
246
311
|
*
|
|
247
312
|
* Rejects if the file can't be read (e.g. invalid JSON) or written —
|
|
248
313
|
* in-memory state stays consistent (reload only on success).
|
|
@@ -250,8 +315,18 @@ export class LlamaSettingsManager {
|
|
|
250
315
|
async setLlamaSetting<K extends keyof LlamaSettings>(
|
|
251
316
|
key: K,
|
|
252
317
|
value: LlamaSettings[K],
|
|
318
|
+
scope: "auto" | "global" | "project" = "auto",
|
|
253
319
|
): Promise<void> {
|
|
254
|
-
|
|
320
|
+
const store =
|
|
321
|
+
scope === "auto"
|
|
322
|
+
? (await this.hasProjectSettings())
|
|
323
|
+
? this.projectStore
|
|
324
|
+
: this.globalStore
|
|
325
|
+
: scope === "project"
|
|
326
|
+
? this.projectStore
|
|
327
|
+
: this.globalStore;
|
|
328
|
+
|
|
329
|
+
await store.updateKey(SETTINGS_KEY, (current) => {
|
|
255
330
|
const merged =
|
|
256
331
|
typeof current === "object" && current !== null
|
|
257
332
|
? (current as Record<string, unknown>)
|