pi-hide-providers 0.1.8 → 0.1.10
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 +10 -10
- package/hide-providers.ts +38 -108
- package/package.json +3 -3
- package/src/model-filter.ts +178 -0
- package/src/provider-selector.ts +16 -5
package/README.md
CHANGED
|
@@ -26,9 +26,9 @@ Pi has `enabledModels` in `settings.json` as an allowlist, but maintaining it ma
|
|
|
26
26
|
`pi-hide-providers` gives you a blocklist that **completely removes** models from all lists — not an allowlist, not a scoped subset:
|
|
27
27
|
|
|
28
28
|
- Define hide rules in a config file (`~/.pi/agent/hide-providers.json` or `.pi/hide-providers.json`)
|
|
29
|
-
- On session start, the extension
|
|
29
|
+
- On session start, the extension patches pi’s underlying `ModelRuntime` accessors (with a `ModelRegistry` fallback for older pi versions) to filter out hidden models
|
|
30
30
|
- The `/model` selector, `Ctrl+P` cycling, `--list-models`, and session restoration all see only visible models
|
|
31
|
-
- `/hide-models reset`
|
|
31
|
+
- `/hide-models reset` removes the runtime/registry patch — all models return immediately
|
|
32
32
|
- Changes via `/hide-models add` and `/hide-models remove` take effect immediately (no reload needed)
|
|
33
33
|
- Interactive `/hide-models` TUI lists visible models first in user-toggle order, then hidden models in their original registry order, and keeps the cursor at the same index after each toggle — matching pi core's `/scoped-models` UX
|
|
34
34
|
- `/hide-models` subcommands for adding, removing, and inspecting rules
|
|
@@ -118,33 +118,33 @@ pi -e ./hide-providers.ts
|
|
|
118
118
|
```
|
|
119
119
|
Session starts
|
|
120
120
|
→ Extension reads hide-providers.json
|
|
121
|
-
→
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
121
|
+
→ Patches ModelRuntime (current pi) or ModelRegistry (older pi):
|
|
122
|
+
available model reads → original result filtered by isHidden()
|
|
123
|
+
all model reads → original result filtered by isHidden()
|
|
124
|
+
model lookup(p, m) → returns undefined if isHidden(p, m)
|
|
125
125
|
→ All downstream consumers see only visible models:
|
|
126
126
|
/model selector, Ctrl+P, --list-models, session restoration
|
|
127
127
|
|
|
128
128
|
/hide-models add or /hide-models remove:
|
|
129
129
|
→ Config updated on disk
|
|
130
130
|
→ currentRules updated in memory
|
|
131
|
-
→ Patched methods read latest rules via closure
|
|
131
|
+
→ Patched runtime/registry methods read latest rules via closure
|
|
132
132
|
→ Changes take effect immediately (no reload)
|
|
133
133
|
|
|
134
134
|
/hide-models reset:
|
|
135
|
-
→
|
|
135
|
+
→ Restores the original model accessors
|
|
136
136
|
→ All models return immediately
|
|
137
137
|
```
|
|
138
138
|
|
|
139
139
|
The SDK doesn't provide a mechanism to remove models from the registry — `registerProvider({ models: [] })` is treated as "no models to register" (override-only), not "remove all models." Monkey-patching the accessor methods is the only way to completely remove models from all lists without touching `settings.json`.
|
|
140
140
|
|
|
141
|
-
The patches survive
|
|
141
|
+
The patches survive model catalog refreshes because they wrap the runtime accessors rather than a model snapshot. On reload, the extension detects an existing patch and updates its rules source.
|
|
142
142
|
|
|
143
143
|
## Comparison with Alternatives
|
|
144
144
|
|
|
145
145
|
| Approach | Pros | Cons |
|
|
146
146
|
|----------|------|------|
|
|
147
|
-
| **pi-hide-providers** (this) | Blocklist — completely removes models from all lists; no `settings.json` writes; changes take effect immediately; survives
|
|
147
|
+
| **pi-hide-providers** (this) | Blocklist — completely removes models from all lists; no `settings.json` writes; changes take effect immediately; survives catalog refresh | Monkey-patches internal model accessors (not an official SDK mechanism) |
|
|
148
148
|
| `enabledModels` in `settings.json` (manual) | Built-in, no extension needed | Allowlist — must list every model you want individually; no blocklist support; clobbers settings with hundreds of entries |
|
|
149
149
|
| `--models` CLI flag | Per-session scoping | Must pass every time; no persistence |
|
|
150
150
|
| `pi.unregisterProvider()` | Restores built-in models after override | Only works for providers registered via `pi.registerProvider()`; can't hide entire providers (empty models array is a no-op) |
|
package/hide-providers.ts
CHANGED
|
@@ -11,18 +11,25 @@ import {
|
|
|
11
11
|
deduplicateRules,
|
|
12
12
|
} from "./src/index.js";
|
|
13
13
|
import { HideProviderSelectorComponent, type HideProviderSelectorResult } from "./src/provider-selector.js";
|
|
14
|
+
import {
|
|
15
|
+
getUnfilteredModels,
|
|
16
|
+
isRegistryPatched,
|
|
17
|
+
patchRegistry,
|
|
18
|
+
type PatchedRegistry,
|
|
19
|
+
unpatchRegistry,
|
|
20
|
+
} from "./src/model-filter.js";
|
|
14
21
|
import { readFileSync, writeFileSync, existsSync, mkdirSync } from "node:fs";
|
|
15
22
|
import { join } from "node:path";
|
|
16
23
|
|
|
17
24
|
/**
|
|
18
25
|
* pi-hide-providers — hide providers and models from pi's model selector.
|
|
19
26
|
*
|
|
20
|
-
* Strategy: monkey-patches
|
|
21
|
-
* to filter
|
|
27
|
+
* Strategy: monkey-patches the ModelRuntime accessors used by current pi, with
|
|
28
|
+
* a ModelRegistry fallback for older releases, to filter models matched by hide rules.
|
|
22
29
|
*
|
|
23
|
-
* This
|
|
30
|
+
* This completely removes models from ALL lists:
|
|
24
31
|
* the /model selector, Ctrl+P cycling, --list-models CLI, and session restoration.
|
|
25
|
-
* It survives
|
|
32
|
+
* It survives catalog refreshes because the patches wrap accessors, not snapshots.
|
|
26
33
|
* No settings.json is touched — no 250+ entry explosion, no allowlist semantics.
|
|
27
34
|
*/
|
|
28
35
|
|
|
@@ -69,85 +76,6 @@ function writeConfig(cwd: string, config: HideProvidersConfig): string {
|
|
|
69
76
|
return path;
|
|
70
77
|
}
|
|
71
78
|
|
|
72
|
-
// Monkey-patching helpers
|
|
73
|
-
|
|
74
|
-
const PATCH_KEY = "__hide_providers_patched";
|
|
75
|
-
|
|
76
|
-
interface PatchedRegistry {
|
|
77
|
-
[PATCH_KEY]: boolean;
|
|
78
|
-
getAvailable(): unknown[];
|
|
79
|
-
getAll(): unknown[];
|
|
80
|
-
find(provider: string, modelId: string): unknown | undefined;
|
|
81
|
-
__hide_providers_get_rules: () => HideRule[];
|
|
82
|
-
__hide_providers_orig_getAvailable: () => unknown[];
|
|
83
|
-
__hide_providers_orig_getAll: () => unknown[];
|
|
84
|
-
__hide_providers_orig_find: (provider: string, modelId: string) => unknown | undefined;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
// Patch a model registry to filter out hidden models.
|
|
88
|
-
// If already patched (e.g. after reload), just updates the rules source.
|
|
89
|
-
function patchRegistry(
|
|
90
|
-
registry: PatchedRegistry,
|
|
91
|
-
getRules: () => HideRule[],
|
|
92
|
-
): void {
|
|
93
|
-
if (registry[PATCH_KEY]) {
|
|
94
|
-
registry.__hide_providers_get_rules = getRules;
|
|
95
|
-
return;
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
registry[PATCH_KEY] = true;
|
|
99
|
-
registry.__hide_providers_get_rules = getRules;
|
|
100
|
-
|
|
101
|
-
// Save originals
|
|
102
|
-
registry.__hide_providers_orig_getAvailable = registry.getAvailable.bind(registry);
|
|
103
|
-
registry.__hide_providers_orig_getAll = registry.getAll.bind(registry);
|
|
104
|
-
registry.__hide_providers_orig_find = registry.find.bind(registry);
|
|
105
|
-
|
|
106
|
-
// Patch getAvailable — used by model selector, Ctrl+P cycle, resolveModelScope
|
|
107
|
-
registry.getAvailable = function (this: PatchedRegistry) {
|
|
108
|
-
const rules = this.__hide_providers_get_rules();
|
|
109
|
-
const all = this.__hide_providers_orig_getAvailable();
|
|
110
|
-
return all.filter(
|
|
111
|
-
(m: any) => !isHidden(rules, m.provider, m.id),
|
|
112
|
-
);
|
|
113
|
-
};
|
|
114
|
-
|
|
115
|
-
// Patch getAll — used by --list-models and CLI model resolution
|
|
116
|
-
registry.getAll = function (this: PatchedRegistry) {
|
|
117
|
-
const rules = this.__hide_providers_get_rules();
|
|
118
|
-
const all = this.__hide_providers_orig_getAll();
|
|
119
|
-
return all.filter(
|
|
120
|
-
(m: any) => !isHidden(rules, m.provider, m.id),
|
|
121
|
-
);
|
|
122
|
-
};
|
|
123
|
-
|
|
124
|
-
// Patch find — used by session restoration. Hides hidden models from being restored.
|
|
125
|
-
registry.find = function (
|
|
126
|
-
this: PatchedRegistry,
|
|
127
|
-
provider: string,
|
|
128
|
-
modelId: string,
|
|
129
|
-
) {
|
|
130
|
-
const rules = this.__hide_providers_get_rules();
|
|
131
|
-
if (isHidden(rules, provider, modelId)) return undefined;
|
|
132
|
-
return this.__hide_providers_orig_find(provider, modelId);
|
|
133
|
-
};
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
// Restore original methods on a patched registry.
|
|
137
|
-
function unpatchRegistry(registry: PatchedRegistry): void {
|
|
138
|
-
if (!registry[PATCH_KEY]) return;
|
|
139
|
-
|
|
140
|
-
registry.getAvailable = registry.__hide_providers_orig_getAvailable;
|
|
141
|
-
registry.getAll = registry.__hide_providers_orig_getAll;
|
|
142
|
-
registry.find = registry.__hide_providers_orig_find;
|
|
143
|
-
|
|
144
|
-
delete (registry as any)[PATCH_KEY];
|
|
145
|
-
delete (registry as any).__hide_providers_get_rules;
|
|
146
|
-
delete (registry as any).__hide_providers_orig_getAvailable;
|
|
147
|
-
delete (registry as any).__hide_providers_orig_getAll;
|
|
148
|
-
delete (registry as any).__hide_providers_orig_find;
|
|
149
|
-
}
|
|
150
|
-
|
|
151
79
|
// Extension
|
|
152
80
|
|
|
153
81
|
export default function (pi: ExtensionAPI) {
|
|
@@ -157,8 +85,11 @@ export default function (pi: ExtensionAPI) {
|
|
|
157
85
|
const config = readConfig(ctx.cwd);
|
|
158
86
|
currentRules = config.hide;
|
|
159
87
|
|
|
88
|
+
const registry = ctx.modelRegistry as unknown as PatchedRegistry;
|
|
160
89
|
if (currentRules.length > 0) {
|
|
161
|
-
patchRegistry(
|
|
90
|
+
patchRegistry(registry, () => currentRules);
|
|
91
|
+
} else {
|
|
92
|
+
unpatchRegistry(registry);
|
|
162
93
|
}
|
|
163
94
|
});
|
|
164
95
|
|
|
@@ -232,6 +163,7 @@ async function handleHideCommand(
|
|
|
232
163
|
const updated = deduplicateRules([...currentRules, rule]);
|
|
233
164
|
const configPath = writeConfig(ctx.cwd, { hide: updated });
|
|
234
165
|
setRules(updated);
|
|
166
|
+
patchRegistry(ctx.modelRegistry as unknown as PatchedRegistry, () => updated);
|
|
235
167
|
ctx.ui.notify(
|
|
236
168
|
`Added: ${formatRule(rule)} (config: ${configPath}). Changes take effect immediately.`,
|
|
237
169
|
"info",
|
|
@@ -269,6 +201,9 @@ async function handleHideCommand(
|
|
|
269
201
|
|
|
270
202
|
writeConfig(ctx.cwd, { hide: updated });
|
|
271
203
|
setRules(updated);
|
|
204
|
+
const registry = ctx.modelRegistry as unknown as PatchedRegistry;
|
|
205
|
+
if (updated.length > 0) patchRegistry(registry, () => updated);
|
|
206
|
+
else unpatchRegistry(registry);
|
|
272
207
|
ctx.ui.notify(
|
|
273
208
|
`Removed: ${key}. Changes take effect immediately.`,
|
|
274
209
|
"info",
|
|
@@ -299,17 +234,17 @@ async function handleHideCommand(
|
|
|
299
234
|
return;
|
|
300
235
|
}
|
|
301
236
|
|
|
302
|
-
// /hide-models reset —
|
|
237
|
+
// /hide-models reset — remove model filters (takes effect immediately)
|
|
303
238
|
if (subcommand === "reset") {
|
|
304
239
|
const registry = ctx.modelRegistry as unknown as PatchedRegistry;
|
|
305
|
-
if (!registry
|
|
306
|
-
ctx.ui.notify("
|
|
240
|
+
if (!isRegistryPatched(registry)) {
|
|
241
|
+
ctx.ui.notify("Model accessors are not patched. Nothing to reset.", "info");
|
|
307
242
|
return;
|
|
308
243
|
}
|
|
309
244
|
|
|
310
245
|
unpatchRegistry(registry);
|
|
311
246
|
ctx.ui.notify(
|
|
312
|
-
"Reset:
|
|
247
|
+
"Reset: model filters removed — all models restored immediately.",
|
|
313
248
|
"info",
|
|
314
249
|
);
|
|
315
250
|
return;
|
|
@@ -326,7 +261,7 @@ async function handleHideCommand(
|
|
|
326
261
|
" /hide-models add <rule> Add a hide rule (e.g. ollama, openrouter/cheap-model)",
|
|
327
262
|
" /hide-models remove <rule> Remove a hide rule",
|
|
328
263
|
" /hide-models apply Show current hide state",
|
|
329
|
-
" /hide-models reset
|
|
264
|
+
" /hide-models reset Remove model filters — restore all models",
|
|
330
265
|
" /hide-models help This message",
|
|
331
266
|
"",
|
|
332
267
|
"Rule formats:",
|
|
@@ -334,8 +269,8 @@ async function handleHideCommand(
|
|
|
334
269
|
' "provider/*" Hide entire provider (explicit)',
|
|
335
270
|
' "provider/model-id" Hide specific model',
|
|
336
271
|
"",
|
|
337
|
-
"Mechanism:
|
|
338
|
-
"
|
|
272
|
+
"Mechanism: patches ModelRuntime model accessors (or ModelRegistry",
|
|
273
|
+
" on older pi versions) to filter out hidden models.",
|
|
339
274
|
" Takes effect immediately. No settings.json modifications.",
|
|
340
275
|
" Survives refresh().",
|
|
341
276
|
].join("\n"),
|
|
@@ -358,7 +293,7 @@ async function showHideSelector(
|
|
|
358
293
|
): Promise<void> {
|
|
359
294
|
// Get all models from the unpatched registry (so we see everything)
|
|
360
295
|
const registry = ctx.modelRegistry as unknown as PatchedRegistry;
|
|
361
|
-
const allModels = registry
|
|
296
|
+
const allModels = getUnfilteredModels(registry);
|
|
362
297
|
|
|
363
298
|
const models = (allModels as any[]).map((m: any) => ({
|
|
364
299
|
provider: m.provider as string,
|
|
@@ -395,7 +330,7 @@ async function showHideSelector(
|
|
|
395
330
|
if (newRules.length === 0) {
|
|
396
331
|
unpatchRegistry(registry);
|
|
397
332
|
ctx.ui.notify(
|
|
398
|
-
`Model ${m.name} visible. All models visible —
|
|
333
|
+
`Model ${m.name} visible. All models visible — filters removed (config: ${configPath}). Run /hide-models again for more.`,
|
|
399
334
|
"info",
|
|
400
335
|
);
|
|
401
336
|
} else {
|
|
@@ -456,32 +391,27 @@ async function showHideSelector(
|
|
|
456
391
|
}
|
|
457
392
|
}
|
|
458
393
|
|
|
459
|
-
// Count total models using the original
|
|
394
|
+
// Count total models using the original unfiltered accessor.
|
|
460
395
|
function countTotalFromUnpatched(ctx: ExtensionCommandContext): number {
|
|
461
396
|
const registry = ctx.modelRegistry as unknown as PatchedRegistry;
|
|
462
|
-
const all = registry.__hide_providers_orig_getAll?.();
|
|
463
|
-
if (all) return all.length;
|
|
464
397
|
try {
|
|
465
|
-
return (
|
|
398
|
+
return getUnfilteredModels(registry).length;
|
|
466
399
|
} catch {
|
|
467
400
|
return 0;
|
|
468
401
|
}
|
|
469
402
|
}
|
|
470
403
|
|
|
471
|
-
// Count hidden models using the original
|
|
404
|
+
// Count hidden models using the original unfiltered accessor.
|
|
472
405
|
function hiddenFromUnpatched(
|
|
473
406
|
ctx: ExtensionCommandContext,
|
|
474
407
|
rules: ReadonlyArray<HideRule>,
|
|
475
408
|
): number {
|
|
476
409
|
const registry = ctx.modelRegistry as unknown as PatchedRegistry;
|
|
477
|
-
const all = registry.__hide_providers_orig_getAll?.();
|
|
478
|
-
if (all) {
|
|
479
|
-
return (all as any[]).filter((m: any) => isHidden(rules, m.provider, m.id)).length;
|
|
480
|
-
}
|
|
481
410
|
try {
|
|
482
|
-
return (
|
|
483
|
-
|
|
484
|
-
|
|
411
|
+
return getUnfilteredModels(registry).filter((model) => {
|
|
412
|
+
const m = model as { provider: string; id: string };
|
|
413
|
+
return isHidden(rules, m.provider, m.id);
|
|
414
|
+
}).length;
|
|
485
415
|
} catch {
|
|
486
416
|
return 0;
|
|
487
417
|
}
|
|
@@ -503,13 +433,13 @@ function showStatus(
|
|
|
503
433
|
}
|
|
504
434
|
|
|
505
435
|
const registry = ctx.modelRegistry as unknown as PatchedRegistry;
|
|
506
|
-
if (registry
|
|
436
|
+
if (isRegistryPatched(registry)) {
|
|
507
437
|
lines.push("");
|
|
508
|
-
lines.push("Status: PATCHED —
|
|
438
|
+
lines.push("Status: PATCHED — model accessors filter hidden models");
|
|
509
439
|
}
|
|
510
440
|
|
|
511
441
|
try {
|
|
512
|
-
const all = registry
|
|
442
|
+
const all = getUnfilteredModels(registry);
|
|
513
443
|
if (all.length > 0) {
|
|
514
444
|
const hidden = (all as any[]).filter((m: any) => isHidden(rules, m.provider, m.id));
|
|
515
445
|
lines.push("");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-hide-providers",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.10",
|
|
4
4
|
"description": "Hide providers and models from pi's model selector — filter the /model list and Ctrl+P cycling via a configurable blocklist",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"author": "Tom X Nguyen",
|
|
@@ -31,8 +31,8 @@
|
|
|
31
31
|
"README.md"
|
|
32
32
|
],
|
|
33
33
|
"devDependencies": {
|
|
34
|
-
"@earendil-works/pi-coding-agent": "0.80.
|
|
35
|
-
"@earendil-works/pi-tui": "0.80.
|
|
34
|
+
"@earendil-works/pi-coding-agent": "0.80.10",
|
|
35
|
+
"@earendil-works/pi-tui": "0.80.10",
|
|
36
36
|
"@types/node": "25.9.1",
|
|
37
37
|
"@vitest/coverage-v8": "4.1.7",
|
|
38
38
|
"knip": "6.14.1",
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import { isHidden, type HideRule } from "./index.js";
|
|
2
|
+
|
|
3
|
+
interface ModelLike {
|
|
4
|
+
provider: string;
|
|
5
|
+
id: string;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
interface ModelRuntimeLike {
|
|
9
|
+
getModels(providerId?: string): readonly unknown[];
|
|
10
|
+
getAvailableSnapshot(): readonly unknown[];
|
|
11
|
+
getAvailable?(providerId?: string): Promise<readonly unknown[]>;
|
|
12
|
+
getModel(provider: string, modelId: string): unknown | undefined;
|
|
13
|
+
__hide_providers_runtime_patched?: boolean;
|
|
14
|
+
__hide_providers_get_rules?: () => HideRule[];
|
|
15
|
+
__hide_providers_orig_getModels?: (providerId?: string) => readonly unknown[];
|
|
16
|
+
__hide_providers_orig_getAvailableSnapshot?: () => readonly unknown[];
|
|
17
|
+
__hide_providers_orig_getAvailable?: (providerId?: string) => Promise<readonly unknown[]>;
|
|
18
|
+
__hide_providers_orig_getModel?: (provider: string, modelId: string) => unknown | undefined;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface PatchedRegistry {
|
|
22
|
+
__hide_providers_patched?: boolean;
|
|
23
|
+
runtime?: ModelRuntimeLike;
|
|
24
|
+
getAvailable(): unknown[];
|
|
25
|
+
getAll(): unknown[];
|
|
26
|
+
find(provider: string, modelId: string): unknown | undefined;
|
|
27
|
+
__hide_providers_get_rules?: () => HideRule[];
|
|
28
|
+
__hide_providers_orig_getAvailable?: () => unknown[];
|
|
29
|
+
__hide_providers_orig_getAll?: () => unknown[];
|
|
30
|
+
__hide_providers_orig_find?: (provider: string, modelId: string) => unknown | undefined;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function filterModels(models: readonly unknown[], rules: ReadonlyArray<HideRule>): unknown[] {
|
|
34
|
+
return models.filter((model) => {
|
|
35
|
+
const candidate = model as ModelLike;
|
|
36
|
+
return !isHidden(rules, candidate.provider, candidate.id);
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function getRuntime(registry: PatchedRegistry): ModelRuntimeLike | undefined {
|
|
41
|
+
const runtime = registry.runtime;
|
|
42
|
+
return runtime &&
|
|
43
|
+
typeof runtime.getModels === "function" &&
|
|
44
|
+
typeof runtime.getAvailableSnapshot === "function" &&
|
|
45
|
+
typeof runtime.getModel === "function"
|
|
46
|
+
? runtime
|
|
47
|
+
: undefined;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function patchRuntime(runtime: ModelRuntimeLike, getRules: () => HideRule[]): void {
|
|
51
|
+
if (runtime.__hide_providers_runtime_patched) {
|
|
52
|
+
runtime.__hide_providers_get_rules = getRules;
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
runtime.__hide_providers_runtime_patched = true;
|
|
57
|
+
runtime.__hide_providers_get_rules = getRules;
|
|
58
|
+
runtime.__hide_providers_orig_getModels = runtime.getModels.bind(runtime);
|
|
59
|
+
runtime.__hide_providers_orig_getAvailableSnapshot = runtime.getAvailableSnapshot.bind(runtime);
|
|
60
|
+
runtime.__hide_providers_orig_getModel = runtime.getModel.bind(runtime);
|
|
61
|
+
if (runtime.getAvailable) {
|
|
62
|
+
runtime.__hide_providers_orig_getAvailable = runtime.getAvailable.bind(runtime);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
runtime.getModels = function (this: ModelRuntimeLike, providerId?: string) {
|
|
66
|
+
return filterModels(
|
|
67
|
+
this.__hide_providers_orig_getModels!(providerId),
|
|
68
|
+
this.__hide_providers_get_rules!(),
|
|
69
|
+
);
|
|
70
|
+
};
|
|
71
|
+
runtime.getAvailableSnapshot = function (this: ModelRuntimeLike) {
|
|
72
|
+
return filterModels(
|
|
73
|
+
this.__hide_providers_orig_getAvailableSnapshot!(),
|
|
74
|
+
this.__hide_providers_get_rules!(),
|
|
75
|
+
);
|
|
76
|
+
};
|
|
77
|
+
if (runtime.__hide_providers_orig_getAvailable) {
|
|
78
|
+
runtime.getAvailable = async function (this: ModelRuntimeLike, providerId?: string) {
|
|
79
|
+
return filterModels(
|
|
80
|
+
await this.__hide_providers_orig_getAvailable!(providerId),
|
|
81
|
+
this.__hide_providers_get_rules!(),
|
|
82
|
+
);
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
runtime.getModel = function (this: ModelRuntimeLike, provider: string, modelId: string) {
|
|
86
|
+
if (isHidden(this.__hide_providers_get_rules!(), provider, modelId)) return undefined;
|
|
87
|
+
return this.__hide_providers_orig_getModel!(provider, modelId);
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function unpatchRuntime(runtime: ModelRuntimeLike): void {
|
|
92
|
+
if (!runtime.__hide_providers_runtime_patched) return;
|
|
93
|
+
|
|
94
|
+
runtime.getModels = runtime.__hide_providers_orig_getModels!;
|
|
95
|
+
runtime.getAvailableSnapshot = runtime.__hide_providers_orig_getAvailableSnapshot!;
|
|
96
|
+
runtime.getModel = runtime.__hide_providers_orig_getModel!;
|
|
97
|
+
if (runtime.__hide_providers_orig_getAvailable) {
|
|
98
|
+
runtime.getAvailable = runtime.__hide_providers_orig_getAvailable;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
delete runtime.__hide_providers_runtime_patched;
|
|
102
|
+
delete runtime.__hide_providers_get_rules;
|
|
103
|
+
delete runtime.__hide_providers_orig_getModels;
|
|
104
|
+
delete runtime.__hide_providers_orig_getAvailableSnapshot;
|
|
105
|
+
delete runtime.__hide_providers_orig_getAvailable;
|
|
106
|
+
delete runtime.__hide_providers_orig_getModel;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export function patchRegistry(registry: PatchedRegistry, getRules: () => HideRule[]): void {
|
|
110
|
+
const runtime = getRuntime(registry);
|
|
111
|
+
if (runtime) patchRuntime(runtime, getRules);
|
|
112
|
+
|
|
113
|
+
if (registry.__hide_providers_patched) {
|
|
114
|
+
registry.__hide_providers_get_rules = getRules;
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Current pi internals read ModelRuntime directly. Patching its compatibility
|
|
119
|
+
// ModelRegistry facade too would only double-filter results. Older pi versions
|
|
120
|
+
// do not expose the runtime, so retain the original registry patch as fallback.
|
|
121
|
+
if (runtime) return;
|
|
122
|
+
|
|
123
|
+
registry.__hide_providers_patched = true;
|
|
124
|
+
registry.__hide_providers_get_rules = getRules;
|
|
125
|
+
registry.__hide_providers_orig_getAvailable = registry.getAvailable.bind(registry);
|
|
126
|
+
registry.__hide_providers_orig_getAll = registry.getAll.bind(registry);
|
|
127
|
+
registry.__hide_providers_orig_find = registry.find.bind(registry);
|
|
128
|
+
|
|
129
|
+
registry.getAvailable = function (this: PatchedRegistry) {
|
|
130
|
+
return filterModels(
|
|
131
|
+
this.__hide_providers_orig_getAvailable!(),
|
|
132
|
+
this.__hide_providers_get_rules!(),
|
|
133
|
+
);
|
|
134
|
+
};
|
|
135
|
+
registry.getAll = function (this: PatchedRegistry) {
|
|
136
|
+
return filterModels(
|
|
137
|
+
this.__hide_providers_orig_getAll!(),
|
|
138
|
+
this.__hide_providers_get_rules!(),
|
|
139
|
+
);
|
|
140
|
+
};
|
|
141
|
+
registry.find = function (this: PatchedRegistry, provider: string, modelId: string) {
|
|
142
|
+
if (isHidden(this.__hide_providers_get_rules!(), provider, modelId)) return undefined;
|
|
143
|
+
return this.__hide_providers_orig_find!(provider, modelId);
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export function unpatchRegistry(registry: PatchedRegistry): void {
|
|
148
|
+
const runtime = getRuntime(registry);
|
|
149
|
+
if (runtime) unpatchRuntime(runtime);
|
|
150
|
+
|
|
151
|
+
if (!registry.__hide_providers_patched) return;
|
|
152
|
+
registry.getAvailable = registry.__hide_providers_orig_getAvailable!;
|
|
153
|
+
registry.getAll = registry.__hide_providers_orig_getAll!;
|
|
154
|
+
registry.find = registry.__hide_providers_orig_find!;
|
|
155
|
+
|
|
156
|
+
delete registry.__hide_providers_patched;
|
|
157
|
+
delete registry.__hide_providers_get_rules;
|
|
158
|
+
delete registry.__hide_providers_orig_getAvailable;
|
|
159
|
+
delete registry.__hide_providers_orig_getAll;
|
|
160
|
+
delete registry.__hide_providers_orig_find;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
export function isRegistryPatched(registry: PatchedRegistry): boolean {
|
|
164
|
+
return Boolean(
|
|
165
|
+
registry.__hide_providers_patched || getRuntime(registry)?.__hide_providers_runtime_patched,
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
export function getUnfilteredModels(registry: PatchedRegistry): unknown[] {
|
|
170
|
+
const runtime = getRuntime(registry);
|
|
171
|
+
if (runtime?.__hide_providers_orig_getModels) {
|
|
172
|
+
return [...runtime.__hide_providers_orig_getModels()];
|
|
173
|
+
}
|
|
174
|
+
if (registry.__hide_providers_orig_getAll) {
|
|
175
|
+
return registry.__hide_providers_orig_getAll();
|
|
176
|
+
}
|
|
177
|
+
return registry.getAll();
|
|
178
|
+
}
|
package/src/provider-selector.ts
CHANGED
|
@@ -23,6 +23,8 @@ import {
|
|
|
23
23
|
matchesKey,
|
|
24
24
|
Spacer,
|
|
25
25
|
Text,
|
|
26
|
+
truncateToWidth,
|
|
27
|
+
wrapTextWithAnsi,
|
|
26
28
|
} from "@earendil-works/pi-tui";
|
|
27
29
|
import type { Theme } from "@earendil-works/pi-coding-agent";
|
|
28
30
|
import { DynamicBorder, keyText } from "@earendil-works/pi-coding-agent";
|
|
@@ -135,11 +137,20 @@ export class HideProviderSelectorComponent implements Component {
|
|
|
135
137
|
|
|
136
138
|
lines.push(...new DynamicBorder((s) => this.theme.fg("accent", s)).render(width));
|
|
137
139
|
lines.push("");
|
|
138
|
-
lines.push(this.theme.fg("accent", this.theme.bold("Hide Provider Configuration")));
|
|
139
140
|
lines.push(
|
|
140
|
-
|
|
141
|
-
"
|
|
142
|
-
|
|
141
|
+
truncateToWidth(
|
|
142
|
+
this.theme.fg("accent", this.theme.bold("Hide Provider Configuration")),
|
|
143
|
+
width,
|
|
144
|
+
"",
|
|
145
|
+
),
|
|
146
|
+
);
|
|
147
|
+
lines.push(
|
|
148
|
+
...wrapTextWithAnsi(
|
|
149
|
+
this.theme.fg(
|
|
150
|
+
"muted",
|
|
151
|
+
`Select providers or models to hide from the model selector.`,
|
|
152
|
+
),
|
|
153
|
+
width,
|
|
143
154
|
),
|
|
144
155
|
);
|
|
145
156
|
lines.push("");
|
|
@@ -150,7 +161,7 @@ export class HideProviderSelectorComponent implements Component {
|
|
|
150
161
|
lines.push(...this.footerText.render(width));
|
|
151
162
|
lines.push(...new DynamicBorder((s) => this.theme.fg("accent", s)).render(width));
|
|
152
163
|
|
|
153
|
-
return lines;
|
|
164
|
+
return lines.map((line) => truncateToWidth(line, width, ""));
|
|
154
165
|
}
|
|
155
166
|
|
|
156
167
|
handleInput(data: string): void {
|