pi-hide-providers 0.1.7 → 0.1.9

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 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 monkey-patches `modelRegistry.getAvailable()`, `getAll()`, and `find()` to filter out hidden models
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` unpatches the registry — all models return immediately
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
- → Monkey-patches modelRegistry:
122
- getAvailable() → original result filtered by isHidden()
123
- getAll() → original result filtered by isHidden()
124
- find(p, m) → returns undefined if isHidden(p, m)
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
- → Unpatches registry (restores original methods)
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 `modelRegistry.refresh()` because they wrap the original methods. On reload, the extension detects the registry is already patched and just updates the rules source.
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 `refresh()` | Monkey-patches `modelRegistry` methods (not an official SDK mechanism) |
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 modelRegistry accessor methods (getAvailable, getAll, find)
21
- * to filter out models matched by hide rules.
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 is the only mechanism that completely removes models from ALL lists:
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 modelRegistry.refresh() because our patches wrap the originals.
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(ctx.modelRegistry as unknown as PatchedRegistry, () => currentRules);
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 — unpatch registry (takes effect immediately)
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[PATCH_KEY]) {
306
- ctx.ui.notify("Registry is not patched. Nothing to reset.", "info");
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: registry unpatched — all models restored immediately.",
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 Unpatch registry — restore all models",
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: monkey-patches modelRegistry.getAvailable(),",
338
- " getAll(), and find() to filter out hidden models.",
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.__hide_providers_orig_getAll?.() ?? ctx.modelRegistry.getAll();
296
+ const allModels = getUnfilteredModels(registry);
362
297
 
363
298
  const models = (allModels as any[]).map((m: any) => ({
364
299
  provider: m.provider as string,
@@ -366,6 +301,48 @@ async function showHideSelector(
366
301
  name: (m.name ?? m.id) as string,
367
302
  }));
368
303
 
304
+ if (ctx.mode !== "tui") {
305
+ if (!ctx.hasUI) {
306
+ ctx.ui.notify("/hide-models requires a UI (TUI or GUI).", "error");
307
+ return;
308
+ }
309
+ if (models.length === 0) {
310
+ ctx.ui.notify("No models found.", "warning");
311
+ return;
312
+ }
313
+ const items = models.map((m) => {
314
+ const hidden = isHidden(currentRules, m.provider, m.id);
315
+ return `${m.name} (${formatRule({ provider: m.provider, model: m.id })}): ${hidden ? "hidden" : "visible"}`;
316
+ });
317
+ const pick = await ctx.ui.select("Hide models \u2014 pick a model to toggle", items);
318
+ if (pick === undefined) return;
319
+ const idx = items.indexOf(pick);
320
+ if (idx < 0) return;
321
+ const m = models[idx];
322
+ if (!m) return;
323
+ const matches = (r: HideRule) => r.provider === m.provider && r.model === m.id;
324
+ const hasExact = currentRules.some(matches);
325
+ const newRules = hasExact
326
+ ? currentRules.filter((r) => !matches(r))
327
+ : deduplicateRules([...currentRules, { provider: m.provider, model: m.id }]);
328
+ const configPath = writeConfig(ctx.cwd, { hide: newRules });
329
+ setRules(newRules);
330
+ if (newRules.length === 0) {
331
+ unpatchRegistry(registry);
332
+ ctx.ui.notify(
333
+ `Model ${m.name} visible. All models visible — filters removed (config: ${configPath}). Run /hide-models again for more.`,
334
+ "info",
335
+ );
336
+ } else {
337
+ patchRegistry(registry, () => newRules);
338
+ ctx.ui.notify(
339
+ `Model ${m.name} ${hasExact ? "visible" : "hidden"}. ${newRules.length} rule(s) active (config: ${configPath}). Run /hide-models again for more.`,
340
+ "info",
341
+ );
342
+ }
343
+ return;
344
+ }
345
+
369
346
  const result = await ctx.ui.custom<HideProviderSelectorResult>(
370
347
  (tui, theme, _kb, done) => {
371
348
  const selector = new HideProviderSelectorComponent(
@@ -414,32 +391,27 @@ async function showHideSelector(
414
391
  }
415
392
  }
416
393
 
417
- // Count total models using the original (unpatched) getAll.
394
+ // Count total models using the original unfiltered accessor.
418
395
  function countTotalFromUnpatched(ctx: ExtensionCommandContext): number {
419
396
  const registry = ctx.modelRegistry as unknown as PatchedRegistry;
420
- const all = registry.__hide_providers_orig_getAll?.();
421
- if (all) return all.length;
422
397
  try {
423
- return (ctx.modelRegistry.getAll() as any[]).length;
398
+ return getUnfilteredModels(registry).length;
424
399
  } catch {
425
400
  return 0;
426
401
  }
427
402
  }
428
403
 
429
- // Count hidden models using the original (unpatched) getAll.
404
+ // Count hidden models using the original unfiltered accessor.
430
405
  function hiddenFromUnpatched(
431
406
  ctx: ExtensionCommandContext,
432
407
  rules: ReadonlyArray<HideRule>,
433
408
  ): number {
434
409
  const registry = ctx.modelRegistry as unknown as PatchedRegistry;
435
- const all = registry.__hide_providers_orig_getAll?.();
436
- if (all) {
437
- return (all as any[]).filter((m: any) => isHidden(rules, m.provider, m.id)).length;
438
- }
439
410
  try {
440
- return (ctx.modelRegistry.getAll() as any[]).filter(
441
- (m: any) => isHidden(rules, m.provider, m.id),
442
- ).length;
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;
443
415
  } catch {
444
416
  return 0;
445
417
  }
@@ -461,13 +433,13 @@ function showStatus(
461
433
  }
462
434
 
463
435
  const registry = ctx.modelRegistry as unknown as PatchedRegistry;
464
- if (registry[PATCH_KEY]) {
436
+ if (isRegistryPatched(registry)) {
465
437
  lines.push("");
466
- lines.push("Status: PATCHED — getAvailable/getAll/find filter hidden models");
438
+ lines.push("Status: PATCHED — model accessors filter hidden models");
467
439
  }
468
440
 
469
441
  try {
470
- const all = registry.__hide_providers_orig_getAll?.() ?? [];
442
+ const all = getUnfilteredModels(registry);
471
443
  if (all.length > 0) {
472
444
  const hidden = (all as any[]).filter((m: any) => isHidden(rules, m.provider, m.id));
473
445
  lines.push("");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-hide-providers",
3
- "version": "0.1.7",
3
+ "version": "0.1.9",
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.6",
35
- "@earendil-works/pi-tui": "0.80.6",
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
+ }