pi-cliproxyapi 0.2.0 → 0.3.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 +34 -23
- package/index.ts +3 -3
- package/package.json +1 -1
- package/src/commands.ts +12 -143
- package/src/fetch-models.ts +2 -5
- package/src/log.ts +17 -4
- package/src/ui-hub/hub.ts +264 -0
- package/src/ui-hub/index.ts +50 -0
- package/src/ui-hub/shell.ts +119 -0
- package/src/ui-hub/types.ts +16 -0
- package/src/ui-hub/view-diagnostics.ts +108 -0
- package/src/ui-hub/view-models.ts +515 -0
- package/src/ui-hub/view-usage.ts +131 -0
- package/src/ui-picker/mutate.ts +34 -0
- package/src/ui-setup.ts +1 -1
- package/src/ui-usage.ts +1 -1
- package/src/ui-overlay.ts +0 -235
- package/src/ui-picker/index.ts +0 -35
- package/src/ui-picker/picker-component.ts +0 -432
- package/src/ui-picker/picker.ts +0 -247
package/src/ui-picker/picker.ts
DELETED
|
@@ -1,247 +0,0 @@
|
|
|
1
|
-
// Three-panel picker: state, navigation, render, input dispatch.
|
|
2
|
-
//
|
|
3
|
-
// Layout (drawn by render()):
|
|
4
|
-
// \u250c\u2500 providers \u2500\u252c\u2500 assigned to <prov> \u2500\u2510
|
|
5
|
-
// \u2502 list ... \u2502 list ... \u2502
|
|
6
|
-
// \u2502 \u251c\u2500 available pool \u2500\u2500\u2500\u2500\u2500\u2524
|
|
7
|
-
// \u2502 \u2502 grouped by owned_by \u2502
|
|
8
|
-
// \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2534\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518
|
|
9
|
-
|
|
10
|
-
import type { ExtensionCommandContext } from "@earendil-works/pi-coding-agent";
|
|
11
|
-
import {
|
|
12
|
-
type Component,
|
|
13
|
-
getKeybindings,
|
|
14
|
-
matchesKey,
|
|
15
|
-
visibleWidth,
|
|
16
|
-
} from "@earendil-works/pi-tui";
|
|
17
|
-
|
|
18
|
-
import type { ProxyConfig } from "../config.ts";
|
|
19
|
-
import type { Discovery } from "../fetch-models.ts";
|
|
20
|
-
import { buildCatalog } from "./catalog.ts";
|
|
21
|
-
import {
|
|
22
|
-
apiCompatible,
|
|
23
|
-
assignedIdsFor,
|
|
24
|
-
attachModel,
|
|
25
|
-
detachModel,
|
|
26
|
-
groupPoolByOwnedBy,
|
|
27
|
-
poolFor,
|
|
28
|
-
} from "./mutate.ts";
|
|
29
|
-
import { collectProviders } from "./providers.ts";
|
|
30
|
-
import { confirmRemoveProvider } from "./prompt-confirm.ts";
|
|
31
|
-
import { promptNewProviderName } from "./prompt-name.ts";
|
|
32
|
-
import { pad } from "./render-text.ts";
|
|
33
|
-
import {
|
|
34
|
-
renderEmpty,
|
|
35
|
-
renderModelRow,
|
|
36
|
-
renderNewProviderRow,
|
|
37
|
-
renderPanelHeader,
|
|
38
|
-
renderProviderRow,
|
|
39
|
-
renderSubheader,
|
|
40
|
-
} from "./rows.ts";
|
|
41
|
-
import type {
|
|
42
|
-
CatalogIndex,
|
|
43
|
-
OverlayTui,
|
|
44
|
-
PanelId,
|
|
45
|
-
ProviderEntry,
|
|
46
|
-
Theme,
|
|
47
|
-
} from "./types.ts";
|
|
48
|
-
|
|
49
|
-
interface RightRow {
|
|
50
|
-
kind: "header" | "sub" | "model" | "empty";
|
|
51
|
-
label?: string;
|
|
52
|
-
// model fields
|
|
53
|
-
id?: string;
|
|
54
|
-
side?: "assigned" | "pool";
|
|
55
|
-
compatWarn?: boolean;
|
|
56
|
-
/** index into the linear cursor list (skipping non-cursor rows). */
|
|
57
|
-
cursorIdx?: number;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
export function buildPicker(
|
|
61
|
-
tui: OverlayTui,
|
|
62
|
-
theme: Theme,
|
|
63
|
-
cfg: ProxyConfig,
|
|
64
|
-
discovery: Discovery,
|
|
65
|
-
ctx: ExtensionCommandContext,
|
|
66
|
-
done: (v: ProxyConfig | null) => void,
|
|
67
|
-
): Component & { handleInput(data: string): void } {
|
|
68
|
-
const catalog: CatalogIndex = buildCatalog(discovery);
|
|
69
|
-
let providers: ProviderEntry[] = collectProviders(cfg, catalog);
|
|
70
|
-
|
|
71
|
-
let focus: PanelId = "providers";
|
|
72
|
-
let providerCursor = 0;
|
|
73
|
-
let assignedCursor = 0;
|
|
74
|
-
let poolCursor = 0;
|
|
75
|
-
let providerScroll = 0;
|
|
76
|
-
let assignedScroll = 0;
|
|
77
|
-
let poolScroll = 0;
|
|
78
|
-
const finish = (result: ProxyConfig | null): void => {
|
|
79
|
-
done(result);
|
|
80
|
-
};
|
|
81
|
-
|
|
82
|
-
const selectedProvider = (): ProviderEntry | null => {
|
|
83
|
-
if (providers.length === 0) return null;
|
|
84
|
-
const idx = Math.max(0, Math.min(providerCursor, providers.length - 1));
|
|
85
|
-
return providers[idx] ?? null;
|
|
86
|
-
};
|
|
87
|
-
|
|
88
|
-
const refresh = (): void => {
|
|
89
|
-
providers = collectProviders(cfg, catalog);
|
|
90
|
-
const maxProv = Math.max(0, providers.length); // +1 for "+ new" pseudo row
|
|
91
|
-
if (providerCursor > maxProv) providerCursor = maxProv;
|
|
92
|
-
const prov = selectedProvider();
|
|
93
|
-
if (prov) {
|
|
94
|
-
const aLen = assignedIdsFor(cfg, prov).length;
|
|
95
|
-
const pLen = poolFor(cfg, prov, catalog).length;
|
|
96
|
-
if (assignedCursor >= aLen) assignedCursor = Math.max(0, aLen - 1);
|
|
97
|
-
if (poolCursor >= pLen) poolCursor = Math.max(0, pLen - 1);
|
|
98
|
-
}
|
|
99
|
-
};
|
|
100
|
-
|
|
101
|
-
const ensureVisible = (
|
|
102
|
-
cursor: number,
|
|
103
|
-
scroll: number,
|
|
104
|
-
visible: number,
|
|
105
|
-
): number => {
|
|
106
|
-
if (cursor < scroll) return cursor;
|
|
107
|
-
if (cursor >= scroll + visible) return cursor - visible + 1;
|
|
108
|
-
return Math.max(0, scroll);
|
|
109
|
-
};
|
|
110
|
-
|
|
111
|
-
const onTab = (back: boolean): void => {
|
|
112
|
-
const order: PanelId[] = ["providers", "assigned", "pool"];
|
|
113
|
-
const i = order.indexOf(focus);
|
|
114
|
-
focus = back
|
|
115
|
-
? order[(i - 1 + order.length) % order.length]!
|
|
116
|
-
: order[(i + 1) % order.length]!;
|
|
117
|
-
};
|
|
118
|
-
|
|
119
|
-
const moveCursor = (delta: number): void => {
|
|
120
|
-
if (focus === "providers") {
|
|
121
|
-
const total = providers.length + 1; // +1 for "+ new" pseudo row
|
|
122
|
-
providerCursor = Math.max(0, Math.min(providerCursor + delta, total - 1));
|
|
123
|
-
return;
|
|
124
|
-
}
|
|
125
|
-
const prov = selectedProvider();
|
|
126
|
-
if (!prov) return;
|
|
127
|
-
if (focus === "assigned") {
|
|
128
|
-
const total = assignedIdsFor(cfg, prov).length;
|
|
129
|
-
if (total === 0) return;
|
|
130
|
-
assignedCursor = Math.max(0, Math.min(assignedCursor + delta, total - 1));
|
|
131
|
-
return;
|
|
132
|
-
}
|
|
133
|
-
const total = poolFor(cfg, prov, catalog).length;
|
|
134
|
-
if (total === 0) return;
|
|
135
|
-
poolCursor = Math.max(0, Math.min(poolCursor + delta, total - 1));
|
|
136
|
-
};
|
|
137
|
-
|
|
138
|
-
const onActivate = async (): Promise<void> => {
|
|
139
|
-
if (focus === "providers") {
|
|
140
|
-
if (providerCursor === providers.length) {
|
|
141
|
-
const name = await promptNewProviderName(ctx, cfg.proxy.providerPrefix);
|
|
142
|
-
if (name && !cfg.customProviders[name]) {
|
|
143
|
-
cfg.customProviders[name] = { api: "openai-completions", models: [] };
|
|
144
|
-
refresh();
|
|
145
|
-
providerCursor = providers.findIndex(
|
|
146
|
-
(p) => p.kind === "custom" && p.name === name,
|
|
147
|
-
);
|
|
148
|
-
if (providerCursor < 0) providerCursor = providers.length - 1;
|
|
149
|
-
focus = "pool";
|
|
150
|
-
}
|
|
151
|
-
tui.requestRender();
|
|
152
|
-
return;
|
|
153
|
-
}
|
|
154
|
-
focus = "pool";
|
|
155
|
-
return;
|
|
156
|
-
}
|
|
157
|
-
const prov = selectedProvider();
|
|
158
|
-
if (!prov) return;
|
|
159
|
-
if (focus === "assigned") {
|
|
160
|
-
const ids = assignedIdsFor(cfg, prov);
|
|
161
|
-
const id = ids[assignedCursor];
|
|
162
|
-
if (!id) return;
|
|
163
|
-
detachModel(cfg, prov, id);
|
|
164
|
-
refresh();
|
|
165
|
-
return;
|
|
166
|
-
}
|
|
167
|
-
const ids = poolFor(cfg, prov, catalog);
|
|
168
|
-
const id = ids[poolCursor];
|
|
169
|
-
if (!id) return;
|
|
170
|
-
const m = catalog.byId.get(id);
|
|
171
|
-
if (!m) return;
|
|
172
|
-
attachModel(cfg, prov, m);
|
|
173
|
-
refresh();
|
|
174
|
-
};
|
|
175
|
-
|
|
176
|
-
const onDelete = async (): Promise<void> => {
|
|
177
|
-
if (focus !== "providers") return;
|
|
178
|
-
const prov = selectedProvider();
|
|
179
|
-
if (!prov || prov.kind !== "custom") return;
|
|
180
|
-
const ok = await confirmRemoveProvider(ctx, prov.name);
|
|
181
|
-
if (!ok) {
|
|
182
|
-
tui.requestRender();
|
|
183
|
-
return;
|
|
184
|
-
}
|
|
185
|
-
delete cfg.customProviders[prov.name];
|
|
186
|
-
refresh();
|
|
187
|
-
if (providerCursor >= providers.length)
|
|
188
|
-
providerCursor = Math.max(0, providers.length - 1);
|
|
189
|
-
tui.requestRender();
|
|
190
|
-
};
|
|
191
|
-
|
|
192
|
-
// Render and input split into the second half of the file.
|
|
193
|
-
return assembleComponent({
|
|
194
|
-
tui,
|
|
195
|
-
theme,
|
|
196
|
-
cfg,
|
|
197
|
-
catalog,
|
|
198
|
-
getProviders: () => providers,
|
|
199
|
-
getFocus: () => focus,
|
|
200
|
-
setFocus: (f: PanelId) => {
|
|
201
|
-
focus = f;
|
|
202
|
-
},
|
|
203
|
-
getProviderCursor: () => providerCursor,
|
|
204
|
-
getAssignedCursor: () => assignedCursor,
|
|
205
|
-
getPoolCursor: () => poolCursor,
|
|
206
|
-
getProviderScroll: () => providerScroll,
|
|
207
|
-
setProviderScroll: (v: number) => {
|
|
208
|
-
providerScroll = v;
|
|
209
|
-
},
|
|
210
|
-
getAssignedScroll: () => assignedScroll,
|
|
211
|
-
setAssignedScroll: (v: number) => {
|
|
212
|
-
assignedScroll = v;
|
|
213
|
-
},
|
|
214
|
-
getPoolScroll: () => poolScroll,
|
|
215
|
-
setPoolScroll: (v: number) => {
|
|
216
|
-
poolScroll = v;
|
|
217
|
-
},
|
|
218
|
-
selectedProvider,
|
|
219
|
-
moveCursor,
|
|
220
|
-
onTab,
|
|
221
|
-
onActivate,
|
|
222
|
-
onDelete,
|
|
223
|
-
ensureVisible,
|
|
224
|
-
finish,
|
|
225
|
-
apiCompatible,
|
|
226
|
-
poolGrouper: (ids: string[]) => groupPoolByOwnedBy(ids, catalog),
|
|
227
|
-
assignedIdsFor: (p: ProviderEntry) => assignedIdsFor(cfg, p),
|
|
228
|
-
poolFor: (p: ProviderEntry) => poolFor(cfg, p, catalog),
|
|
229
|
-
});
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
// The component assembly (render + input) lives next-door to keep this file
|
|
233
|
-
// readable. We import it lazily to dodge circular imports.
|
|
234
|
-
import { assembleComponent } from "./picker-component.ts";
|
|
235
|
-
// Re-export helpers used by the component file (avoid duplicate imports).
|
|
236
|
-
export {
|
|
237
|
-
pad,
|
|
238
|
-
renderEmpty,
|
|
239
|
-
renderModelRow,
|
|
240
|
-
renderNewProviderRow,
|
|
241
|
-
renderPanelHeader,
|
|
242
|
-
renderProviderRow,
|
|
243
|
-
renderSubheader,
|
|
244
|
-
visibleWidth,
|
|
245
|
-
};
|
|
246
|
-
export type { RightRow };
|
|
247
|
-
export { matchesKey, getKeybindings };
|