pi-llama-cpp 0.12.0 → 0.14.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.
Files changed (68) hide show
  1. package/README.md +20 -9
  2. package/package.json +3 -3
  3. package/src/api/client.ts +25 -0
  4. package/src/constants.ts +8 -5
  5. package/src/enums/status.ts +0 -1
  6. package/src/interfaces/endpoints/models.ts +1 -1
  7. package/src/interfaces/settings.ts +3 -2
  8. package/src/interfaces/sortBy.ts +4 -0
  9. package/src/managers/command/models.ts +247 -0
  10. package/src/managers/command.ts +29 -459
  11. package/src/managers/events.ts +2 -2
  12. package/src/managers/server.ts +29 -5
  13. package/src/managers/settings.ts +12 -88
  14. package/src/models/baseModel.ts +27 -10
  15. package/src/models/legacyModel.ts +2 -2
  16. package/src/models/routerModel.ts +2 -2
  17. package/src/models/singleModel.ts +1 -1
  18. package/src/server.ts +35 -48
  19. package/src/sse/client.ts +113 -59
  20. package/src/sse/fetch.ts +43 -0
  21. package/src/sse/manager.ts +9 -27
  22. package/src/sse/types.ts +0 -4
  23. package/src/ui/dialog/base.ts +118 -0
  24. package/src/ui/dialog/confirm.ts +45 -0
  25. package/src/ui/dialog/factory.ts +111 -0
  26. package/src/ui/dialog/input.ts +63 -0
  27. package/src/ui/dialog/options.ts +23 -0
  28. package/src/ui/editors/editorOptions.ts +43 -0
  29. package/src/ui/editors/itemBuilder.ts +47 -0
  30. package/src/ui/editors/listEditor.ts +291 -0
  31. package/src/ui/editors/override/entry.ts +24 -0
  32. package/src/ui/editors/override/entryEditor.ts +166 -0
  33. package/src/ui/editors/override/fields.ts +294 -0
  34. package/src/ui/editors/override/handlers.ts +118 -0
  35. package/src/ui/editors/override/itemBuilder.ts +42 -0
  36. package/src/ui/editors/override/overrideList.ts +127 -0
  37. package/src/ui/editors/server/builder.ts +60 -0
  38. package/src/ui/editors/server/fields.ts +84 -0
  39. package/src/ui/editors/server/handlers.ts +32 -0
  40. package/src/ui/editors/server/itemBuilder.ts +68 -0
  41. package/src/ui/editors/server/serverEditor.ts +161 -0
  42. package/src/ui/editors/server/utils.ts +45 -0
  43. package/src/ui/editors/server/wizard.ts +110 -0
  44. package/src/ui/editors/settingField.ts +37 -0
  45. package/src/ui/editors/settingsListFactory.ts +33 -0
  46. package/src/ui/settings/index.ts +237 -0
  47. package/src/ui/strings.ts +8 -4
  48. package/src/utils/health.ts +48 -0
  49. package/src/utils/serverIds.ts +21 -0
  50. package/src/utils/settingsStore.ts +1 -1
  51. package/src/utils/urlResolver.ts +129 -0
  52. package/src/utils/urls.ts +33 -13
  53. package/tests/commandManager.test.ts +38 -7
  54. package/tests/dialog.test.ts +95 -2
  55. package/tests/health.test.ts +116 -0
  56. package/tests/legacyModel.test.ts +34 -28
  57. package/tests/overrides.test.ts +123 -68
  58. package/tests/routerModel.test.ts +67 -68
  59. package/tests/server.test.ts +47 -16
  60. package/tests/serverManager.test.ts +4 -4
  61. package/tests/settings.test.ts +10 -8
  62. package/tests/singleModel.test.ts +12 -12
  63. package/tests/sseManager.test.ts +6 -24
  64. package/src/ui/dialog.ts +0 -287
  65. package/src/ui/overrideEntryEditor.ts +0 -119
  66. package/src/ui/overrideSettingsList.ts +0 -682
  67. package/src/ui/serverListEditor.ts +0 -32
  68. package/src/ui/serverSettingsList.ts +0 -466
@@ -1,466 +0,0 @@
1
- import {
2
- getSettingsListTheme,
3
- type Theme,
4
- } from "@earendil-works/pi-coding-agent";
5
- import type {
6
- Component,
7
- Focusable,
8
- KeybindingsManager,
9
- TUI,
10
- } from "@earendil-works/pi-tui";
11
- import { SettingsList, type SettingItem } from "@earendil-works/pi-tui";
12
- import type { LlamaServer } from "../interfaces/settings";
13
- import { ConfirmDialog, InputDialog } from "./dialog";
14
- import { formatServerSuffix, normalizeServerUrl } from "./serverListEditor";
15
- import { fieldMessage, FIELDS, HINTS, TITLES } from "./strings";
16
-
17
- /**
18
- * Options for the SettingsList-based server editor.
19
- */
20
- export interface ServerSettingsListOptions {
21
- /** TUI instance, used to request re-renders after async persists */
22
- tui: TUI;
23
- /** Theme for dialogs (from the ctx.ui.custom factory) */
24
- theme: Theme;
25
- /** App keybindings manager (injected by ctx.ui.custom) */
26
- keybindings: KeybindingsManager;
27
- /**
28
- * Snapshot of the merged `llamaSettings.servers` to edit. Replaced
29
- * in place (`options.servers = next`) after every successful persist
30
- * so the rebuilt rows reflect the adopted list.
31
- */
32
- servers: LlamaServer[];
33
- /** Persists a new server list; a rejection keeps the current list */
34
- persist: (next: LlamaServer[]) => Promise<void>;
35
- /** Closes the editor (called on Esc in the server list) */
36
- done: () => void;
37
- /** Notifies about persistence errors */
38
- onError: (message: string) => void;
39
- }
40
-
41
- /**
42
- * Builds the SettingItem for one server's editable fields (URL, id, name).
43
- * Each field is infinite → opens an `InputDialog` submenu; commits are
44
- * reported by the containing `SettingsList`'s `onChange` (field id → value).
45
- */
46
- const buildServerFieldItems = (
47
- server: LlamaServer,
48
- theme: Theme,
49
- tui: TUI,
50
- ): SettingItem[] => [
51
- {
52
- id: "url",
53
- label: FIELDS.serverUrl.label,
54
- description: FIELDS.serverUrl.description,
55
- currentValue: server.url,
56
- submenu: InputDialog.inputSubmenu(
57
- theme,
58
- tui,
59
- TITLES.edit(FIELDS.serverUrl.label),
60
- fieldMessage(FIELDS.serverUrl),
61
- FIELDS.serverUrl.placeholder,
62
- server.url,
63
- normalizeServerUrl,
64
- ),
65
- },
66
- {
67
- id: "id",
68
- label: FIELDS.providerId.label,
69
- description: FIELDS.providerId.description,
70
- currentValue: server.id ?? "",
71
- submenu: InputDialog.inputSubmenu(
72
- theme,
73
- tui,
74
- TITLES.edit(FIELDS.providerId.label),
75
- fieldMessage(FIELDS.providerId),
76
- FIELDS.providerId.placeholder,
77
- server.id ?? "",
78
- ),
79
- },
80
- {
81
- id: "name",
82
- label: FIELDS.displayName.label,
83
- description: FIELDS.displayName.description,
84
- currentValue: server.name ?? "",
85
- submenu: InputDialog.inputSubmenu(
86
- theme,
87
- tui,
88
- TITLES.edit(FIELDS.displayName.label),
89
- fieldMessage(FIELDS.displayName),
90
- FIELDS.displayName.placeholder,
91
- server.name ?? "",
92
- ),
93
- },
94
- ];
95
-
96
- /**
97
- * Builds the SettingItem for one server row in the top-level list.
98
- * Enter drills into the server's field-edit submenu.
99
- */
100
- const buildServerRow = (
101
- server: LlamaServer,
102
- index: number,
103
- theme: Theme,
104
- tui: TUI,
105
- onChange: (field: string, value: string) => void,
106
- ): SettingItem => ({
107
- id: `server-${index}`,
108
- label: server.url,
109
- description: HINTS.serverRow,
110
- currentValue: formatServerSuffix(server),
111
- submenu: (_cv, done) => {
112
- // Rebuild the field items on open so they prefill with current values
113
- const items = buildServerFieldItems(server, theme, tui);
114
- return new SettingsList(
115
- items,
116
- Math.min(items.length + 2, 15),
117
- getSettingsListTheme(),
118
- // Field commits arrive here: done(value) from the input submenus
119
- (field, value) => onChange(field, value),
120
- done,
121
- );
122
- },
123
- });
124
-
125
- /**
126
- * Wrapper around a `SettingsList` of servers that adds `a` (add) and `d`
127
- * (delete) support at the list level.
128
- *
129
- * - **a** opens the add wizard — a sequence of framed `InputDialog`s
130
- * (URL → optional ID → optional name, mirroring `/login`'s sequential
131
- * prompts). Esc at any step aborts without persisting; the server is
132
- * saved only after the final step.
133
- * - **d** opens a `ConfirmDialog` (Delete/Cancel select list).
134
- * - **Enter** on a server row drills into its field-edit submenu.
135
- * - **Esc** closes the editor.
136
- */
137
- export class ServerSettingsList implements Component, Focusable {
138
- private settingsList: SettingsList;
139
- private mode: "list" | "wizard" | "confirm" = "list";
140
- private wizardStep = 0;
141
- private wizardUrl = "";
142
- private wizardId = "";
143
- private wizardDialog: InputDialog | undefined;
144
- private confirmDialog: ConfirmDialog | undefined;
145
- private isFocused = false;
146
-
147
- constructor(private readonly options: ServerSettingsListOptions) {
148
- this.settingsList = this.buildSettingsList();
149
- }
150
-
151
- /**
152
- * Builds the top-level `SettingsList` from the current
153
- * `options.servers`. Called on construction and rebuilt after every
154
- * successful persist so added/deleted/edited servers are reflected
155
- * immediately (the initial snapshot is not mutated by `persist`).
156
- */
157
- private buildSettingsList(): SettingsList {
158
- const { theme, tui } = this.options;
159
- const items: SettingItem[] = this.options.servers.map((server, i) =>
160
- buildServerRow(server, i, theme, tui, (field, value) =>
161
- this.handleFieldChange(field, value),
162
- ),
163
- );
164
-
165
- return new SettingsList(
166
- items,
167
- Math.min(items.length + 2, 15),
168
- getSettingsListTheme(),
169
- () => {
170
- // Top-level onChange is a no-op (servers don't change at this level)
171
- },
172
- () => {
173
- this.options.done();
174
- },
175
- );
176
- }
177
-
178
- // -- Focusable -----------------------------------------------------------
179
-
180
- get focused(): boolean {
181
- return this.isFocused;
182
- }
183
-
184
- set focused(value: boolean) {
185
- this.isFocused = value;
186
- if (this.wizardDialog) this.wizardDialog.focused = value;
187
- if (this.confirmDialog) this.confirmDialog.focused = value;
188
- }
189
-
190
- // -- Component -----------------------------------------------------------
191
-
192
- invalidate(): void {
193
- this.settingsList.invalidate();
194
- this.wizardDialog?.invalidate();
195
- this.confirmDialog?.invalidate();
196
- }
197
-
198
- handleInput(data: string): void {
199
- if (this.mode === "wizard") {
200
- this.wizardDialog?.handleInput(data);
201
- return;
202
- }
203
-
204
- if (this.mode === "confirm") {
205
- this.confirmDialog?.handleInput(data);
206
- return;
207
- }
208
-
209
- const kb = this.options.keybindings;
210
- if (kb.matches(data, "tui.select.cancel")) {
211
- this.options.done();
212
- return;
213
- }
214
- // Track selection for add/delete
215
- if (kb.matches(data, "tui.select.up")) {
216
- this._selectedIndex =
217
- this._selectedIndex === 0
218
- ? this.options.servers.length - 1
219
- : this._selectedIndex - 1;
220
- }
221
- if (kb.matches(data, "tui.select.down")) {
222
- this._selectedIndex =
223
- this._selectedIndex === this.options.servers.length - 1
224
- ? 0
225
- : this._selectedIndex + 1;
226
- }
227
- if (data === "a") {
228
- this.beginWizard();
229
- return;
230
- }
231
- if (data === "d") {
232
- this.beginConfirm();
233
- return;
234
- }
235
- // Delegate to SettingsList (it handles up/down internally for rendering)
236
- this.settingsList.handleInput(data);
237
- }
238
-
239
- render(width: number): string[] {
240
- if (this.mode === "wizard") {
241
- return this.wizardDialog?.render(width) ?? [];
242
- }
243
-
244
- if (this.mode === "confirm") {
245
- return this.confirmDialog?.render(width) ?? [];
246
- }
247
-
248
- // List mode: the shortcuts live in the rows' description; only when the
249
- // list is empty (no rows → no description) show them as the hint line
250
- const settingsLines = this.settingsList.render(width);
251
- if (this.options.servers.length === 0) {
252
- settingsLines[settingsLines.length - 1] = getSettingsListTheme().hint(
253
- HINTS.emptyServers,
254
- );
255
- }
256
- return settingsLines;
257
- }
258
-
259
- // -- helpers ---------------------------------------------------------------
260
-
261
- private selectedIndex(): number {
262
- // Manual tracker for add/delete targeting, kept in sync with the
263
- // SettingsList's cursor by rebuildList (SettingsList doesn't expose
264
- // a getter)
265
- return this._selectedIndex;
266
- }
267
-
268
- private _selectedIndex = 0;
269
-
270
- /**
271
- * Rebuilds the server list and places the cursor on `targetIndex`
272
- * (clamped to the last row). A fresh SettingsList starts its cursor
273
- * at index 0, so `selectItem` is used to restore the position.
274
- */
275
- private rebuildList(targetIndex: number): void {
276
- this.settingsList = this.buildSettingsList();
277
- const count = this.options.servers.length;
278
- if (count === 0) {
279
- this._selectedIndex = 0;
280
- return;
281
- }
282
- const target = Math.min(targetIndex, count - 1);
283
- this._selectedIndex = target;
284
- this.settingsList.selectItem(`server-${target}`);
285
- }
286
-
287
- // -- add wizard ---------------------------------------------------------------
288
-
289
- /**
290
- * Opens the add wizard at step 1/3 (URL). Esc at any step cancels the
291
- * whole wizard; the server is persisted only after the final step.
292
- */
293
- private beginWizard(): void {
294
- this.mode = "wizard";
295
- this.wizardStep = 0;
296
- this.wizardUrl = "";
297
- this.wizardId = "";
298
- this.showWizardStep();
299
- this.options.tui.requestRender();
300
- }
301
-
302
- private showWizardStep(): void {
303
- const { theme, tui } = this.options;
304
- const step = this.wizardStep;
305
-
306
- const dialog =
307
- step === 0
308
- ? new InputDialog({
309
- theme,
310
- tui,
311
- title: TITLES.addServerStep(1),
312
- message: fieldMessage(FIELDS.serverUrl),
313
- placeholder: FIELDS.serverUrl.placeholder,
314
- validate: normalizeServerUrl,
315
- onSubmit: (value) => this.wizardSubmit(value),
316
- onCancel: () => this.cancelWizard(),
317
- })
318
- : step === 1
319
- ? new InputDialog({
320
- theme,
321
- tui,
322
- title: TITLES.addServerStep(2),
323
- message: fieldMessage(FIELDS.providerId, "(optional)"),
324
- placeholder: FIELDS.providerId.placeholder,
325
- onSubmit: (value) => this.wizardSubmit(value),
326
- onCancel: () => this.cancelWizard(),
327
- })
328
- : new InputDialog({
329
- theme,
330
- tui,
331
- title: TITLES.addServerStep(3),
332
- message: fieldMessage(FIELDS.displayName, "(optional)"),
333
- placeholder: FIELDS.displayName.placeholder,
334
- onSubmit: (value) => this.wizardSubmit(value),
335
- onCancel: () => this.cancelWizard(),
336
- });
337
-
338
- this.wizardDialog = dialog;
339
- dialog.focused = this.isFocused;
340
- }
341
-
342
- private wizardSubmit(value: string): void {
343
- if (this.wizardStep === 0) {
344
- this.wizardUrl = value;
345
- this.wizardStep = 1;
346
- this.showWizardStep();
347
- } else if (this.wizardStep === 1) {
348
- this.wizardId = value;
349
- this.wizardStep = 2;
350
- this.showWizardStep();
351
- } else {
352
- // Final step (display name): commit the collected fields
353
- this.wizardDialog = undefined;
354
- void this.saveNewServer(this.wizardUrl, this.wizardId, value);
355
- return;
356
- }
357
- this.options.tui.requestRender();
358
- }
359
-
360
- private cancelWizard(): void {
361
- this.wizardDialog = undefined;
362
- this.mode = "list";
363
- this.options.tui.requestRender();
364
- }
365
-
366
- private async saveNewServer(
367
- url: string,
368
- id: string,
369
- name: string,
370
- ): Promise<void> {
371
- const next = [
372
- ...this.options.servers,
373
- {
374
- url,
375
- ...(id.length > 0 ? { id } : {}),
376
- ...(name.length > 0 ? { name } : {}),
377
- },
378
- ];
379
- try {
380
- await this.options.persist(next);
381
- // Adopt the persisted list and rebuild so the new row appears;
382
- // the new server is appended last — put the cursor on it
383
- this.options.servers = next;
384
- this.rebuildList(next.length - 1);
385
- this.mode = "list";
386
- } catch (err) {
387
- this.options.onError(String(err));
388
- this.mode = "list";
389
- }
390
- this.options.tui.requestRender();
391
- }
392
-
393
- // -- delete ------------------------------------------------------------------
394
-
395
- private beginConfirm(): void {
396
- const server = this.options.servers[this.selectedIndex()];
397
- if (!server) return;
398
-
399
- this.mode = "confirm";
400
- this.confirmDialog = new ConfirmDialog({
401
- theme: this.options.theme,
402
- tui: this.options.tui,
403
- title: TITLES.deleteServer,
404
- message: `Delete "${server.url}"?`,
405
- onConfirm: () => {
406
- this.confirmDialog = undefined;
407
- void this.deleteSelected();
408
- },
409
- onCancel: () => {
410
- this.confirmDialog = undefined;
411
- this.mode = "list";
412
- this.options.tui.requestRender();
413
- },
414
- });
415
- this.confirmDialog.focused = this.isFocused;
416
- this.options.tui.requestRender();
417
- }
418
-
419
- private async deleteSelected(): Promise<void> {
420
- const idx = this.selectedIndex();
421
- const servers = this.options.servers;
422
- const next = servers.filter((_, i) => i !== idx);
423
- try {
424
- await this.options.persist(next);
425
- // Adopt the persisted list and rebuild so the row disappears;
426
- // the server that followed the deleted one now sits at the same
427
- // index (rebuildList clamps when the last row was deleted)
428
- this.options.servers = next;
429
- this.rebuildList(idx);
430
- this.mode = "list";
431
- } catch (err) {
432
- this.options.onError(String(err));
433
- this.mode = "list";
434
- }
435
- this.options.tui.requestRender();
436
- }
437
-
438
- private async handleFieldChange(field: string, value: string): Promise<void> {
439
- const idx = this._selectedIndex;
440
- const server = this.options.servers[idx];
441
- if (!server) return;
442
-
443
- const next = this.options.servers.map((s, i) => {
444
- if (i !== idx) return s;
445
- if (field === "url") return { ...s, url: value };
446
- if (field === "id") {
447
- const { id, ...rest } = s;
448
- return value.length === 0 ? rest : { ...rest, id: value };
449
- }
450
- const { name, ...rest } = s;
451
- return value.length === 0 ? rest : { ...rest, name: value };
452
- });
453
-
454
- try {
455
- await this.options.persist(next);
456
- // URL/id/name changes alter row labels → rebuild the list;
457
- // keep the cursor on the edited server
458
- this.options.servers = next;
459
- this.rebuildList(idx);
460
- this.options.tui.requestRender();
461
- } catch (err) {
462
- this.options.onError(String(err));
463
- this.options.tui.requestRender();
464
- }
465
- }
466
- }