pi-hide-providers 0.1.19 → 0.1.21
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 +6 -0
- package/hide-providers.ts +26 -1
- package/package.json +7 -3
- package/src/method-patch.ts +22 -0
- package/src/model-filter.ts +44 -154
package/README.md
CHANGED
|
@@ -179,3 +179,9 @@ npm run lint:dead # Dead code detection (knip)
|
|
|
179
179
|
## License
|
|
180
180
|
|
|
181
181
|
MIT
|
|
182
|
+
|
|
183
|
+
## Pi 0.99 compatibility (0.1.21)
|
|
184
|
+
|
|
185
|
+
Filters the actual 0.99 ModelRuntime and scoped-selector fallback entries. Auth/cancellation arguments are forwarded; filtering composes with model sorting and restores on shutdown. Custom screens propagate IME focus.
|
|
186
|
+
|
|
187
|
+
Tested with Pi 0.99.0. Host-provided Pi packages are wildcard peers, not bundled dependencies; development uses exact 0.99.0 versions.
|
package/hide-providers.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ExtensionAPI, ExtensionCommandContext } from "@earendil-works/pi-coding-agent";
|
|
2
|
-
import { getAgentDir, CONFIG_DIR_NAME } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import { getAgentDir, CONFIG_DIR_NAME, ModelSelectorComponent } from "@earendil-works/pi-coding-agent";
|
|
3
3
|
import {
|
|
4
4
|
HIDE_COMMAND_DESCRIPTION,
|
|
5
5
|
CONFIG_FILENAME,
|
|
@@ -76,16 +76,39 @@ function writeConfig(cwd: string, config: HideProvidersConfig): string {
|
|
|
76
76
|
return path;
|
|
77
77
|
}
|
|
78
78
|
|
|
79
|
+
import { installMethodPatch } from "./src/method-patch.js";
|
|
80
|
+
|
|
79
81
|
// Extension
|
|
80
82
|
|
|
81
83
|
export default function (pi: ExtensionAPI) {
|
|
82
84
|
let currentRules: HideRule[] = [];
|
|
85
|
+
let activeRegistry: PatchedRegistry | undefined;
|
|
86
|
+
let restoreSelector: (() => void) | undefined;
|
|
87
|
+
pi.on("session_shutdown", () => {
|
|
88
|
+
if (activeRegistry) unpatchRegistry(activeRegistry);
|
|
89
|
+
activeRegistry = undefined;
|
|
90
|
+
restoreSelector?.();
|
|
91
|
+
restoreSelector = undefined;
|
|
92
|
+
});
|
|
83
93
|
|
|
84
94
|
pi.on("session_start", async (_event, ctx) => {
|
|
85
95
|
const config = readConfig(ctx.cwd);
|
|
86
96
|
currentRules = config.hide;
|
|
87
97
|
|
|
88
98
|
const registry = ctx.modelRegistry as unknown as PatchedRegistry;
|
|
99
|
+
if (activeRegistry && activeRegistry !== registry) unpatchRegistry(activeRegistry);
|
|
100
|
+
activeRegistry = registry;
|
|
101
|
+
if (ctx.mode === "tui" && !restoreSelector) {
|
|
102
|
+
restoreSelector = installMethodPatch(ModelSelectorComponent.prototype, "loadModelsFromSnapshot", function (original, ...args) {
|
|
103
|
+
// Pi keeps unavailable scoped models as fallback items. Filter that
|
|
104
|
+
// input as well as the runtime snapshot, without destroying the scope.
|
|
105
|
+
const scoped = this.scopedModels;
|
|
106
|
+
this.scopedModels = scoped.filter((item: { model: { provider: string; id: string } }) =>
|
|
107
|
+
!isHidden(currentRules, item.model.provider, item.model.id));
|
|
108
|
+
try { return original.apply(this, args); }
|
|
109
|
+
finally { this.scopedModels = scoped; }
|
|
110
|
+
});
|
|
111
|
+
}
|
|
89
112
|
if (currentRules.length > 0) {
|
|
90
113
|
patchRegistry(registry, () => currentRules);
|
|
91
114
|
} else {
|
|
@@ -353,6 +376,8 @@ async function showHideSelector(
|
|
|
353
376
|
);
|
|
354
377
|
|
|
355
378
|
return {
|
|
379
|
+
get focused() { return selector.focused; },
|
|
380
|
+
set focused(value: boolean) { selector.focused = value; },
|
|
356
381
|
render(width: number) {
|
|
357
382
|
return selector.render(width);
|
|
358
383
|
},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-hide-providers",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.21",
|
|
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",
|
|
@@ -38,8 +38,8 @@
|
|
|
38
38
|
"lint:dead": "knip --no-gitignore"
|
|
39
39
|
},
|
|
40
40
|
"devDependencies": {
|
|
41
|
-
"@earendil-works/pi-coding-agent": "0.
|
|
42
|
-
"@earendil-works/pi-tui": "0.
|
|
41
|
+
"@earendil-works/pi-coding-agent": "0.99.0",
|
|
42
|
+
"@earendil-works/pi-tui": "0.99.0",
|
|
43
43
|
"@types/node": "25.9.1",
|
|
44
44
|
"@vitest/coverage-v8": "4.1.7",
|
|
45
45
|
"knip": "6.14.1",
|
|
@@ -50,5 +50,9 @@
|
|
|
50
50
|
"extensions": [
|
|
51
51
|
"./hide-providers.ts"
|
|
52
52
|
]
|
|
53
|
+
},
|
|
54
|
+
"peerDependencies": {
|
|
55
|
+
"@earendil-works/pi-coding-agent": "*",
|
|
56
|
+
"@earendil-works/pi-tui": "*"
|
|
53
57
|
}
|
|
54
58
|
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
// Cooperating model extensions may unload in either order.
|
|
2
|
+
const PATCH = Symbol.for("pi.model-accessor.patch.v1");
|
|
3
|
+
type Method = (this: any, ...args: any[]) => any;
|
|
4
|
+
type Wrapped = Method & { [PATCH]?: { active: boolean; original: Method } };
|
|
5
|
+
function unwrap(method: Wrapped): Method {
|
|
6
|
+
while (method[PATCH] && !method[PATCH]!.active) method = method[PATCH]!.original;
|
|
7
|
+
return method;
|
|
8
|
+
}
|
|
9
|
+
export function installMethodPatch(target: any, name: string, handler: (this: any, original: Method, ...args: any[]) => any): () => void {
|
|
10
|
+
const original = target[name] as Method;
|
|
11
|
+
if (typeof original !== "function") throw new Error(`Pi model contract missing: ${name}`);
|
|
12
|
+
const state = { active: true, original };
|
|
13
|
+
const wrapped: Wrapped = function (...args) {
|
|
14
|
+
return state.active ? handler.call(this, original, ...args) : original.apply(this, args);
|
|
15
|
+
};
|
|
16
|
+
wrapped[PATCH] = state;
|
|
17
|
+
target[name] = wrapped;
|
|
18
|
+
return () => {
|
|
19
|
+
state.active = false;
|
|
20
|
+
if (target[name] === wrapped) target[name] = unwrap(original);
|
|
21
|
+
};
|
|
22
|
+
}
|
package/src/model-filter.ts
CHANGED
|
@@ -1,178 +1,68 @@
|
|
|
1
1
|
import { isHidden, type HideRule } from "./index.js";
|
|
2
|
-
|
|
3
|
-
interface ModelLike {
|
|
4
|
-
provider: string;
|
|
5
|
-
id: string;
|
|
6
|
-
}
|
|
2
|
+
import { installMethodPatch } from "./method-patch.js";
|
|
7
3
|
|
|
8
4
|
interface ModelRuntimeLike {
|
|
9
5
|
getModels(providerId?: string): readonly unknown[];
|
|
10
6
|
getAvailableSnapshot(): readonly unknown[];
|
|
11
|
-
getAvailable?(providerId?: string): Promise<readonly unknown[]>;
|
|
7
|
+
getAvailable?(providerId?: string, options?: any): Promise<readonly unknown[]>;
|
|
12
8
|
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
9
|
}
|
|
20
|
-
|
|
21
10
|
export interface PatchedRegistry {
|
|
22
|
-
__hide_providers_patched?: boolean;
|
|
23
11
|
runtime?: ModelRuntimeLike;
|
|
24
12
|
getAvailable(): unknown[];
|
|
25
13
|
getAll(): unknown[];
|
|
26
14
|
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
15
|
}
|
|
32
|
-
|
|
33
|
-
|
|
16
|
+
interface PatchState {
|
|
17
|
+
getRules: () => HideRule[];
|
|
18
|
+
all: () => readonly unknown[];
|
|
19
|
+
restores: Array<() => void>;
|
|
20
|
+
}
|
|
21
|
+
const patches = new WeakMap<object, PatchState>();
|
|
22
|
+
function target(registry: PatchedRegistry) { return registry.runtime ?? registry; }
|
|
23
|
+
function filter(models: readonly unknown[], rules: HideRule[]): unknown[] {
|
|
34
24
|
return models.filter((model) => {
|
|
35
|
-
const
|
|
36
|
-
return !isHidden(rules,
|
|
25
|
+
const { provider, id } = model as { provider: string; id: string };
|
|
26
|
+
return !isHidden(rules, provider, id);
|
|
37
27
|
});
|
|
38
28
|
}
|
|
39
|
-
|
|
40
|
-
|
|
29
|
+
export function patchRegistry(registry: PatchedRegistry, getRules: () => HideRule[]): void {
|
|
30
|
+
const object = target(registry);
|
|
31
|
+
const existing = patches.get(object);
|
|
32
|
+
if (existing) { existing.getRules = getRules; return; }
|
|
41
33
|
const runtime = registry.runtime;
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
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);
|
|
34
|
+
const state: PatchState = {
|
|
35
|
+
getRules,
|
|
36
|
+
all: runtime ? runtime.getModels.bind(runtime) : registry.getAll.bind(registry),
|
|
37
|
+
restores: [],
|
|
88
38
|
};
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
function
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
39
|
+
try {
|
|
40
|
+
for (const name of runtime ? ["getModels", "getAvailableSnapshot"] : ["getAll", "getAvailable"]) {
|
|
41
|
+
state.restores.push(installMethodPatch(object, name, function (original, ...args) {
|
|
42
|
+
return filter(original.apply(this, args), state.getRules());
|
|
43
|
+
}));
|
|
44
|
+
}
|
|
45
|
+
if (runtime?.getAvailable) state.restores.push(installMethodPatch(object, "getAvailable", async function (original, ...args) {
|
|
46
|
+
// Forward provider and AuthOperationOptions (including cancellation).
|
|
47
|
+
return filter(await original.apply(this, args), state.getRules());
|
|
48
|
+
}));
|
|
49
|
+
state.restores.push(installMethodPatch(object, runtime ? "getModel" : "find", function (original, provider, id, ...rest) {
|
|
50
|
+
return isHidden(state.getRules(), provider, id) ? undefined : original.call(this, provider, id, ...rest);
|
|
51
|
+
}));
|
|
52
|
+
patches.set(object, state);
|
|
53
|
+
} catch (error) {
|
|
54
|
+
for (const restore of state.restores.reverse()) restore();
|
|
55
|
+
throw error;
|
|
99
56
|
}
|
|
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
57
|
}
|
|
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
58
|
export function unpatchRegistry(registry: PatchedRegistry): void {
|
|
148
|
-
const
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
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;
|
|
59
|
+
const object = target(registry);
|
|
60
|
+
const state = patches.get(object);
|
|
61
|
+
if (!state) return;
|
|
62
|
+
for (const restore of state.restores.reverse()) restore();
|
|
63
|
+
patches.delete(object);
|
|
161
64
|
}
|
|
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
|
-
|
|
65
|
+
export function isRegistryPatched(registry: PatchedRegistry): boolean { return patches.has(target(registry)); }
|
|
169
66
|
export function getUnfilteredModels(registry: PatchedRegistry): unknown[] {
|
|
170
|
-
|
|
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();
|
|
67
|
+
return [...(patches.get(target(registry))?.all() ?? registry.getAll())];
|
|
178
68
|
}
|