pi-free 2.0.0 → 2.0.1
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/CHANGELOG.md +37 -0
- package/README.md +393 -416
- package/config.ts +6 -2
- package/constants.ts +1 -0
- package/index.ts +6 -44
- package/lib/built-in-toggle.ts +206 -0
- package/package.json +67 -67
- package/provider-helper.ts +260 -260
- package/providers/cline/cline-models.ts +1 -1
- package/providers/cline/cline.ts +5 -7
- package/providers/dynamic-built-in/index.ts +432 -513
- package/providers/kilo/kilo.ts +5 -5
- package/providers/nvidia/nvidia.ts +1 -1
- package/providers/ollama/ollama.ts +3 -3
package/config.ts
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* 2. ~/.pi/free.json
|
|
7
7
|
*
|
|
8
8
|
* All exported values are getter functions so that runtime changes
|
|
9
|
-
* (e.g. after
|
|
9
|
+
* (e.g. after toggle-{provider}) are visible immediately.
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
@@ -112,7 +112,11 @@ function ensureConfigFile(): void {
|
|
|
112
112
|
export function loadConfigFile(): PiFreeConfig {
|
|
113
113
|
try {
|
|
114
114
|
return JSON.parse(readFileSync(CONFIG_PATH, "utf8")) as PiFreeConfig;
|
|
115
|
-
} catch {
|
|
115
|
+
} catch (err) {
|
|
116
|
+
_logger.warn("Could not parse config file — returning empty config", {
|
|
117
|
+
path: CONFIG_PATH,
|
|
118
|
+
error: err instanceof Error ? err.message : String(err),
|
|
119
|
+
});
|
|
116
120
|
return {};
|
|
117
121
|
}
|
|
118
122
|
}
|
package/constants.ts
CHANGED
package/index.ts
CHANGED
|
@@ -8,11 +8,13 @@
|
|
|
8
8
|
* - Kilo: OAuth-based free models
|
|
9
9
|
* - Cline: Cline bot integration
|
|
10
10
|
* - NVIDIA: NVIDIA NIM hosting (free tier available)
|
|
11
|
-
* -
|
|
11
|
+
* - Ollama Cloud: Ollama's cloud-hosted models with usage-based free tier
|
|
12
|
+
* - Qwen: OAuth-based Qwen access (deprecated)
|
|
12
13
|
* - Modal: Modal Labs hosting
|
|
13
14
|
*/
|
|
14
15
|
|
|
15
16
|
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
|
17
|
+
import { setupBuiltInProviderToggles } from "./lib/built-in-toggle.ts";
|
|
16
18
|
import { createLogger } from "./lib/logger.ts";
|
|
17
19
|
import {
|
|
18
20
|
applyGlobalFilter,
|
|
@@ -37,49 +39,6 @@ const _logger = createLogger("pi-free");
|
|
|
37
39
|
// =============================================================================
|
|
38
40
|
|
|
39
41
|
function setupGlobalCommands(pi: ExtensionAPI) {
|
|
40
|
-
// /free - Global toggle for ALL providers
|
|
41
|
-
pi.registerCommand("free", {
|
|
42
|
-
description: "Toggle free-only mode for ALL providers (on/off/status)",
|
|
43
|
-
handler: async (args, ctx) => {
|
|
44
|
-
const arg = args.trim().toLowerCase();
|
|
45
|
-
const registry = getProviderRegistry();
|
|
46
|
-
|
|
47
|
-
if (arg === "on" || arg === "true" || arg === "yes") {
|
|
48
|
-
applyGlobalFilter(pi, true);
|
|
49
|
-
ctx.ui.notify(
|
|
50
|
-
"✓ Free-only mode enabled - paid models hidden for all providers",
|
|
51
|
-
"info",
|
|
52
|
-
);
|
|
53
|
-
} else if (arg === "off" || arg === "false" || arg === "no") {
|
|
54
|
-
applyGlobalFilter(pi, false);
|
|
55
|
-
ctx.ui.notify(
|
|
56
|
-
"✓ Paid models enabled - all models visible for all providers",
|
|
57
|
-
"info",
|
|
58
|
-
);
|
|
59
|
-
} else if (arg === "status" || arg === "" || !arg) {
|
|
60
|
-
const available = await ctx.modelRegistry.getAvailable();
|
|
61
|
-
const freeCount = available.filter(isFreeModel).length;
|
|
62
|
-
const status = getGlobalFreeOnly() ? "enabled" : "disabled";
|
|
63
|
-
|
|
64
|
-
// Count by provider
|
|
65
|
-
const lines = [
|
|
66
|
-
`Free-only mode: ${status}`,
|
|
67
|
-
`${freeCount}/${available.length} models free`,
|
|
68
|
-
"",
|
|
69
|
-
];
|
|
70
|
-
for (const [id, entry] of registry) {
|
|
71
|
-
const free = entry.stored.free.length;
|
|
72
|
-
const all = entry.stored.all.length || free;
|
|
73
|
-
lines.push(`${id}: ${free}/${all} free`);
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
ctx.ui.notify(lines.join("\n"), "info");
|
|
77
|
-
} else {
|
|
78
|
-
ctx.ui.notify("Usage: /free [on|off|status]", "warning");
|
|
79
|
-
}
|
|
80
|
-
},
|
|
81
|
-
});
|
|
82
|
-
|
|
83
42
|
// /free-providers - Show free model counts by provider
|
|
84
43
|
pi.registerCommand("free-providers", {
|
|
85
44
|
description: "Show free/paid model counts for all pi-free providers",
|
|
@@ -166,6 +125,9 @@ export default async function (pi: ExtensionAPI) {
|
|
|
166
125
|
);
|
|
167
126
|
await setupDynamicBuiltInProviders(pi);
|
|
168
127
|
|
|
128
|
+
// Setup toggles for pi's built-in providers (e.g., OpenCode)
|
|
129
|
+
setupBuiltInProviderToggles(pi);
|
|
130
|
+
|
|
169
131
|
// Apply initial global filter if free-only mode is enabled
|
|
170
132
|
if (globalFreeOnly) {
|
|
171
133
|
_logger.info("[pi-free] Applying initial free-only filter");
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Built-in Provider Toggle Support
|
|
3
|
+
*
|
|
4
|
+
* Captures pi's built-in providers after session start and enables
|
|
5
|
+
* free/paid toggling for them via the global registry.
|
|
6
|
+
*
|
|
7
|
+
* Currently supports:
|
|
8
|
+
* - opencode (OpenCode / Zen gateway)
|
|
9
|
+
* - openrouter (OpenRouter)
|
|
10
|
+
*
|
|
11
|
+
* Usage: /toggle-opencode
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import type { Api, Model } from "@mariozechner/pi-ai";
|
|
15
|
+
import type {
|
|
16
|
+
ExtensionAPI,
|
|
17
|
+
ProviderModelConfig,
|
|
18
|
+
} from "@mariozechner/pi-coding-agent";
|
|
19
|
+
import {
|
|
20
|
+
getOpencodeShowPaid,
|
|
21
|
+
getOpenrouterShowPaid,
|
|
22
|
+
saveConfig,
|
|
23
|
+
} from "../config.ts";
|
|
24
|
+
import { createLogger } from "./logger.ts";
|
|
25
|
+
import {
|
|
26
|
+
getGlobalFreeOnly,
|
|
27
|
+
isFreeModel,
|
|
28
|
+
registerWithGlobalToggle,
|
|
29
|
+
} from "./registry.ts";
|
|
30
|
+
|
|
31
|
+
const _logger = createLogger("built-in-toggle");
|
|
32
|
+
|
|
33
|
+
// =============================================================================
|
|
34
|
+
// Configuration
|
|
35
|
+
// =============================================================================
|
|
36
|
+
|
|
37
|
+
interface BuiltInToggleConfig {
|
|
38
|
+
id: string;
|
|
39
|
+
getShowPaid: () => boolean;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const BUILT_IN_TOGGLE_PROVIDERS: BuiltInToggleConfig[] = [
|
|
43
|
+
{ id: "opencode", getShowPaid: getOpencodeShowPaid },
|
|
44
|
+
{ id: "openrouter", getShowPaid: getOpenrouterShowPaid },
|
|
45
|
+
];
|
|
46
|
+
|
|
47
|
+
// =============================================================================
|
|
48
|
+
// State
|
|
49
|
+
// =============================================================================
|
|
50
|
+
|
|
51
|
+
interface BuiltInProviderState {
|
|
52
|
+
free: ProviderModelConfig[];
|
|
53
|
+
all: ProviderModelConfig[];
|
|
54
|
+
reRegister: (models: ProviderModelConfig[]) => void;
|
|
55
|
+
showPaid: boolean;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const providerStates = new Map<string, BuiltInProviderState>();
|
|
59
|
+
let commandsRegistered = false;
|
|
60
|
+
|
|
61
|
+
// =============================================================================
|
|
62
|
+
// Setup
|
|
63
|
+
// =============================================================================
|
|
64
|
+
|
|
65
|
+
export function setupBuiltInProviderToggles(pi: ExtensionAPI): void {
|
|
66
|
+
// Register toggle commands once (available even before models load)
|
|
67
|
+
if (!commandsRegistered) {
|
|
68
|
+
for (const config of BUILT_IN_TOGGLE_PROVIDERS) {
|
|
69
|
+
registerToggleCommand(pi, config);
|
|
70
|
+
}
|
|
71
|
+
commandsRegistered = true;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Capture built-in models on session start and apply initial filter
|
|
75
|
+
pi.on("session_start", async (_event, ctx) => {
|
|
76
|
+
const available = ctx.modelRegistry.getAvailable();
|
|
77
|
+
|
|
78
|
+
for (const config of BUILT_IN_TOGGLE_PROVIDERS) {
|
|
79
|
+
if (providerStates.has(config.id)) {
|
|
80
|
+
// Already captured this session — skip to avoid re-registering
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const providerModels = available.filter(
|
|
85
|
+
(m: Model<Api>) => m.provider === config.id,
|
|
86
|
+
);
|
|
87
|
+
if (providerModels.length === 0) continue;
|
|
88
|
+
|
|
89
|
+
const allModels = providerModels.map(modelToProviderConfig);
|
|
90
|
+
const freeModels = allModels.filter(isFreeModel);
|
|
91
|
+
|
|
92
|
+
const baseUrl = providerModels[0].baseUrl;
|
|
93
|
+
const api = providerModels[0].api;
|
|
94
|
+
const apiKeyEnv = getApiKeyEnvForProvider(config.id);
|
|
95
|
+
|
|
96
|
+
const reRegister = (models: ProviderModelConfig[]) => {
|
|
97
|
+
pi.registerProvider(config.id, {
|
|
98
|
+
baseUrl,
|
|
99
|
+
apiKey: apiKeyEnv,
|
|
100
|
+
api,
|
|
101
|
+
models,
|
|
102
|
+
});
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
providerStates.set(config.id, {
|
|
106
|
+
free: freeModels,
|
|
107
|
+
all: allModels,
|
|
108
|
+
reRegister,
|
|
109
|
+
showPaid: config.getShowPaid(),
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
// Register with global free-only filter
|
|
113
|
+
registerWithGlobalToggle(
|
|
114
|
+
config.id,
|
|
115
|
+
{ free: freeModels, all: allModels },
|
|
116
|
+
reRegister,
|
|
117
|
+
true,
|
|
118
|
+
);
|
|
119
|
+
|
|
120
|
+
_logger.info(
|
|
121
|
+
`[built-in-toggle] ${config.id}: captured ${allModels.length} models (${freeModels.length} free)`,
|
|
122
|
+
);
|
|
123
|
+
|
|
124
|
+
// Respect global free-only setting at capture time
|
|
125
|
+
if (!getGlobalFreeOnly() && !config.getShowPaid()) {
|
|
126
|
+
// Default: show free only (same as other pi-free providers)
|
|
127
|
+
if (freeModels.length > 0) {
|
|
128
|
+
reRegister(freeModels);
|
|
129
|
+
_logger.info(
|
|
130
|
+
`[built-in-toggle] ${config.id}: applied free-only filter`,
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// =============================================================================
|
|
139
|
+
// Per-provider toggle command
|
|
140
|
+
// =============================================================================
|
|
141
|
+
|
|
142
|
+
function registerToggleCommand(
|
|
143
|
+
pi: ExtensionAPI,
|
|
144
|
+
config: BuiltInToggleConfig,
|
|
145
|
+
): void {
|
|
146
|
+
const commandName = `toggle-${config.id}`;
|
|
147
|
+
pi.registerCommand(commandName, {
|
|
148
|
+
description: `Toggle free/paid ${config.id} models`,
|
|
149
|
+
handler: async (_args, ctx) => {
|
|
150
|
+
const state = providerStates.get(config.id);
|
|
151
|
+
if (!state) {
|
|
152
|
+
ctx.ui.notify(
|
|
153
|
+
`${config.id}: models not loaded yet. Start a session first.`,
|
|
154
|
+
"warning",
|
|
155
|
+
);
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
state.showPaid = !state.showPaid;
|
|
160
|
+
|
|
161
|
+
// Persist preference
|
|
162
|
+
saveConfig({ [`${config.id}_show_paid`]: state.showPaid });
|
|
163
|
+
|
|
164
|
+
if (state.showPaid) {
|
|
165
|
+
state.reRegister(state.all);
|
|
166
|
+
ctx.ui.notify(
|
|
167
|
+
`${config.id}: showing all ${state.all.length} models`,
|
|
168
|
+
"info",
|
|
169
|
+
);
|
|
170
|
+
} else {
|
|
171
|
+
state.reRegister(state.free);
|
|
172
|
+
ctx.ui.notify(
|
|
173
|
+
`${config.id}: showing ${state.free.length} free models`,
|
|
174
|
+
"info",
|
|
175
|
+
);
|
|
176
|
+
}
|
|
177
|
+
},
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// =============================================================================
|
|
182
|
+
// Helpers
|
|
183
|
+
// =============================================================================
|
|
184
|
+
|
|
185
|
+
function modelToProviderConfig(m: Model<Api>): ProviderModelConfig {
|
|
186
|
+
return {
|
|
187
|
+
id: m.id,
|
|
188
|
+
name: m.name,
|
|
189
|
+
api: m.api,
|
|
190
|
+
reasoning: m.reasoning,
|
|
191
|
+
input: m.input,
|
|
192
|
+
cost: m.cost,
|
|
193
|
+
contextWindow: m.contextWindow,
|
|
194
|
+
maxTokens: m.maxTokens,
|
|
195
|
+
headers: m.headers,
|
|
196
|
+
compat: (m as any).compat,
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function getApiKeyEnvForProvider(providerId: string): string {
|
|
201
|
+
const envMap: Record<string, string> = {
|
|
202
|
+
opencode: "OPENCODE_API_KEY",
|
|
203
|
+
openrouter: "OPENROUTER_API_KEY",
|
|
204
|
+
};
|
|
205
|
+
return envMap[providerId] || `${providerId.toUpperCase()}_API_KEY`;
|
|
206
|
+
}
|
package/package.json
CHANGED
|
@@ -1,67 +1,67 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "pi-free",
|
|
3
|
-
"version": "2.0.
|
|
4
|
-
"type": "module",
|
|
5
|
-
"description": "AIO free models for PI: Kilo, Cline, Nvidia, Ollama Cloud and others",
|
|
6
|
-
"keywords": [
|
|
7
|
-
"pi-package",
|
|
8
|
-
"pi-extension",
|
|
9
|
-
"free-models",
|
|
10
|
-
"model-filter",
|
|
11
|
-
"nvidia-nim",
|
|
12
|
-
"kilo",
|
|
13
|
-
"cline",
|
|
14
|
-
"qwen",
|
|
15
|
-
"qwen-oauth",
|
|
16
|
-
"modal"
|
|
17
|
-
],
|
|
18
|
-
"license": "MIT",
|
|
19
|
-
"author": "Apostolos Mantzaris",
|
|
20
|
-
"homepage": "https://github.com/apmantza/pi-free#readme",
|
|
21
|
-
"bugs": {
|
|
22
|
-
"url": "https://github.com/apmantza/pi-free/issues"
|
|
23
|
-
},
|
|
24
|
-
"repository": {
|
|
25
|
-
"type": "git",
|
|
26
|
-
"url": "git+https://github.com/apmantza/pi-free.git"
|
|
27
|
-
},
|
|
28
|
-
"engines": {
|
|
29
|
-
"node": ">=20.0.0"
|
|
30
|
-
},
|
|
31
|
-
"files": [
|
|
32
|
-
"index.ts",
|
|
33
|
-
"providers/**/*.ts",
|
|
34
|
-
"lib/**/*.ts",
|
|
35
|
-
"provider-failover/**/*.ts",
|
|
36
|
-
"config.ts",
|
|
37
|
-
"constants.ts",
|
|
38
|
-
"provider-factory.ts",
|
|
39
|
-
"provider-helper.ts",
|
|
40
|
-
"README.md",
|
|
41
|
-
"LICENSE",
|
|
42
|
-
"CHANGELOG.md",
|
|
43
|
-
"scripts/check-extensions.mjs"
|
|
44
|
-
],
|
|
45
|
-
"scripts": {
|
|
46
|
-
"check": "node scripts/check-extensions.mjs",
|
|
47
|
-
"test": "vitest",
|
|
48
|
-
"test:ui": "vitest --ui",
|
|
49
|
-
"test:run": "vitest run"
|
|
50
|
-
},
|
|
51
|
-
"peerDependencies": {
|
|
52
|
-
"@mariozechner/pi-ai": "*",
|
|
53
|
-
"@mariozechner/pi-coding-agent": "*",
|
|
54
|
-
"@mariozechner/pi-tui": "*"
|
|
55
|
-
},
|
|
56
|
-
"devDependencies": {
|
|
57
|
-
"@vitest/ui": "^1.0.0",
|
|
58
|
-
"tsx": "^4.0.0",
|
|
59
|
-
"typescript": "^6.0.2",
|
|
60
|
-
"vitest": "^1.0.0"
|
|
61
|
-
},
|
|
62
|
-
"pi": {
|
|
63
|
-
"extensions": [
|
|
64
|
-
"./index.ts"
|
|
65
|
-
]
|
|
66
|
-
}
|
|
67
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "pi-free",
|
|
3
|
+
"version": "2.0.1",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "AIO free models for PI: Kilo, Cline, Nvidia, Ollama Cloud and others",
|
|
6
|
+
"keywords": [
|
|
7
|
+
"pi-package",
|
|
8
|
+
"pi-extension",
|
|
9
|
+
"free-models",
|
|
10
|
+
"model-filter",
|
|
11
|
+
"nvidia-nim",
|
|
12
|
+
"kilo",
|
|
13
|
+
"cline",
|
|
14
|
+
"qwen",
|
|
15
|
+
"qwen-oauth",
|
|
16
|
+
"modal"
|
|
17
|
+
],
|
|
18
|
+
"license": "MIT",
|
|
19
|
+
"author": "Apostolos Mantzaris",
|
|
20
|
+
"homepage": "https://github.com/apmantza/pi-free#readme",
|
|
21
|
+
"bugs": {
|
|
22
|
+
"url": "https://github.com/apmantza/pi-free/issues"
|
|
23
|
+
},
|
|
24
|
+
"repository": {
|
|
25
|
+
"type": "git",
|
|
26
|
+
"url": "git+https://github.com/apmantza/pi-free.git"
|
|
27
|
+
},
|
|
28
|
+
"engines": {
|
|
29
|
+
"node": ">=20.0.0"
|
|
30
|
+
},
|
|
31
|
+
"files": [
|
|
32
|
+
"index.ts",
|
|
33
|
+
"providers/**/*.ts",
|
|
34
|
+
"lib/**/*.ts",
|
|
35
|
+
"provider-failover/**/*.ts",
|
|
36
|
+
"config.ts",
|
|
37
|
+
"constants.ts",
|
|
38
|
+
"provider-factory.ts",
|
|
39
|
+
"provider-helper.ts",
|
|
40
|
+
"README.md",
|
|
41
|
+
"LICENSE",
|
|
42
|
+
"CHANGELOG.md",
|
|
43
|
+
"scripts/check-extensions.mjs"
|
|
44
|
+
],
|
|
45
|
+
"scripts": {
|
|
46
|
+
"check": "node scripts/check-extensions.mjs",
|
|
47
|
+
"test": "vitest",
|
|
48
|
+
"test:ui": "vitest --ui",
|
|
49
|
+
"test:run": "vitest run"
|
|
50
|
+
},
|
|
51
|
+
"peerDependencies": {
|
|
52
|
+
"@mariozechner/pi-ai": "*",
|
|
53
|
+
"@mariozechner/pi-coding-agent": "*",
|
|
54
|
+
"@mariozechner/pi-tui": "*"
|
|
55
|
+
},
|
|
56
|
+
"devDependencies": {
|
|
57
|
+
"@vitest/ui": "^1.0.0",
|
|
58
|
+
"tsx": "^4.0.0",
|
|
59
|
+
"typescript": "^6.0.2",
|
|
60
|
+
"vitest": "^1.0.0"
|
|
61
|
+
},
|
|
62
|
+
"pi": {
|
|
63
|
+
"extensions": [
|
|
64
|
+
"./index.ts"
|
|
65
|
+
]
|
|
66
|
+
}
|
|
67
|
+
}
|