pi-free 1.0.9 → 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 +138 -0
- package/README.md +393 -367
- package/config.ts +170 -121
- package/constants.ts +23 -61
- package/index.ts +148 -0
- package/lib/built-in-toggle.ts +206 -0
- package/lib/json-persistence.ts +11 -10
- package/lib/logger.ts +2 -2
- package/lib/model-enhancer.ts +20 -20
- package/lib/open-browser.ts +41 -0
- package/lib/provider-cache.ts +106 -0
- package/lib/registry.ts +144 -0
- package/package.json +8 -23
- package/provider-factory.ts +25 -41
- package/provider-failover/benchmark-lookup.ts +247 -0
- package/provider-failover/benchmarks-chunk-0.ts +2010 -0
- package/provider-failover/benchmarks-chunk-1.ts +1988 -0
- package/provider-failover/benchmarks-chunk-2.ts +2010 -0
- package/provider-failover/benchmarks-chunk-3.ts +2010 -0
- package/provider-failover/benchmarks-chunk-4.ts +1969 -0
- package/provider-failover/hardcoded-benchmarks.ts +22 -10025
- package/provider-helper.ts +260 -259
- package/providers/{cline-auth.ts → cline/cline-auth.ts} +2 -2
- package/providers/cline/cline-models.ts +128 -0
- package/providers/{cline.ts → cline/cline.ts} +298 -257
- package/providers/cloudflare/cloudflare.ts +368 -0
- package/providers/dynamic-built-in/index.ts +432 -0
- package/providers/{kilo-auth.ts → kilo/kilo-auth.ts} +3 -20
- package/providers/{kilo-models.ts → kilo/kilo-models.ts} +2 -2
- package/providers/kilo/kilo.ts +235 -0
- package/providers/{modal.ts → modal/modal.ts} +4 -3
- package/providers/{nvidia.ts → nvidia/nvidia.ts} +152 -113
- package/providers/ollama/ollama.ts +172 -0
- package/providers/opencode-session.ts +34 -34
- package/providers/{qwen-auth.ts → qwen/qwen-auth.ts} +24 -40
- package/providers/{qwen-models.ts → qwen/qwen-models.ts} +101 -95
- package/providers/{qwen.ts → qwen/qwen.ts} +83 -13
- package/provider-failover/auto-switch.ts +0 -350
- package/provider-failover/errors.ts +0 -275
- package/provider-failover/index.ts +0 -238
- package/providers/cline-models.ts +0 -77
- package/providers/factory.ts +0 -125
- package/providers/fireworks.ts +0 -49
- package/providers/go.ts +0 -216
- package/providers/kilo.ts +0 -146
- package/providers/mistral.ts +0 -144
- package/providers/ollama.ts +0 -113
- package/providers/openrouter.ts +0 -175
- package/providers/zen.ts +0 -371
- package/usage/commands.ts +0 -17
- package/usage/cumulative.ts +0 -193
- package/usage/formatters.ts +0 -115
- package/usage/index.ts +0 -46
- package/usage/limits.ts +0 -148
- package/usage/metrics.ts +0 -222
- package/usage/sessions.ts +0 -355
- package/usage/store.ts +0 -99
- package/usage/tracking.ts +0 -329
- package/usage/types.ts +0 -26
- package/usage/widget.ts +0 -90
- package/widget/data.ts +0 -113
- package/widget/format.ts +0 -26
- package/widget/render.ts +0 -117
package/config.ts
CHANGED
|
@@ -5,67 +5,75 @@
|
|
|
5
5
|
* 1. Environment variable
|
|
6
6
|
* 2. ~/.pi/free.json
|
|
7
7
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* OPENROUTER_SHOW_PAID=true or openrouter_show_paid: true
|
|
11
|
-
* NVIDIA_SHOW_PAID=true or nvidia_show_paid: true
|
|
12
|
-
* FIREWORKS_SHOW_PAID=true or fireworks_show_paid: true
|
|
13
|
-
* CLINE_SHOW_PAID=true or cline_show_paid: true
|
|
14
|
-
* GO_SHOW_PAID=true or go_show_paid: true
|
|
15
|
-
* OLLAMA_SHOW_PAID=true or ollama_show_paid: true
|
|
16
|
-
*
|
|
17
|
-
* PI_FREE_KILO_FREE_ONLY=true — restrict Kilo to free models even after login.
|
|
8
|
+
* All exported values are getter functions so that runtime changes
|
|
9
|
+
* (e.g. after toggle-{provider}) are visible immediately.
|
|
18
10
|
*/
|
|
19
11
|
|
|
20
12
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
21
13
|
import { join } from "node:path";
|
|
14
|
+
import {
|
|
15
|
+
PROVIDER_CLINE,
|
|
16
|
+
PROVIDER_KILO,
|
|
17
|
+
PROVIDER_MODAL,
|
|
18
|
+
PROVIDER_NVIDIA,
|
|
19
|
+
PROVIDER_QWEN,
|
|
20
|
+
} from "./constants.ts";
|
|
22
21
|
import { createLogger } from "./lib/logger.ts";
|
|
23
22
|
|
|
24
23
|
const _logger = createLogger("config");
|
|
25
24
|
|
|
26
25
|
interface PiFreeConfig {
|
|
27
|
-
openrouter_api_key?: string;
|
|
28
26
|
nvidia_api_key?: string;
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
fireworks_api_key?: string;
|
|
32
|
-
mistral_api_key?: string;
|
|
27
|
+
cloudflare_api_token?: string;
|
|
28
|
+
cloudflare_account_id?: string;
|
|
33
29
|
ollama_api_key?: string;
|
|
34
30
|
modal_api_key?: string;
|
|
31
|
+
opencode_api_key?: string;
|
|
32
|
+
mistral_api_key?: string;
|
|
33
|
+
groq_api_key?: string;
|
|
34
|
+
cerebras_api_key?: string;
|
|
35
|
+
xai_api_key?: string;
|
|
36
|
+
hf_token?: string;
|
|
37
|
+
openrouter_api_key?: string;
|
|
35
38
|
kilo_free_only?: boolean;
|
|
36
39
|
hidden_models?: string[];
|
|
37
|
-
|
|
40
|
+
free_only?: boolean;
|
|
38
41
|
kilo_show_paid?: boolean;
|
|
39
|
-
openrouter_show_paid?: boolean;
|
|
40
42
|
nvidia_show_paid?: boolean;
|
|
41
|
-
|
|
42
|
-
cline_show_paid?: boolean;
|
|
43
|
-
zen_show_paid?: boolean;
|
|
44
|
-
go_show_paid?: boolean;
|
|
45
|
-
mistral_show_paid?: boolean;
|
|
43
|
+
cloudflare_show_paid?: boolean;
|
|
46
44
|
ollama_show_paid?: boolean;
|
|
45
|
+
cline_show_paid?: boolean;
|
|
46
|
+
qwen_show_paid?: boolean;
|
|
47
|
+
modal_show_paid?: boolean;
|
|
48
|
+
openrouter_show_paid?: boolean;
|
|
49
|
+
opencode_show_paid?: boolean;
|
|
47
50
|
}
|
|
48
51
|
|
|
49
52
|
const CONFIG_TEMPLATE: PiFreeConfig = {
|
|
50
|
-
openrouter_api_key: "",
|
|
51
53
|
nvidia_api_key: "",
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
fireworks_api_key: "",
|
|
55
|
-
mistral_api_key: "",
|
|
54
|
+
cloudflare_api_token: "",
|
|
55
|
+
cloudflare_account_id: "",
|
|
56
56
|
ollama_api_key: "",
|
|
57
57
|
modal_api_key: "",
|
|
58
|
+
opencode_api_key: "",
|
|
59
|
+
mistral_api_key: "",
|
|
60
|
+
groq_api_key: "",
|
|
61
|
+
cerebras_api_key: "",
|
|
62
|
+
xai_api_key: "",
|
|
63
|
+
hf_token: "",
|
|
64
|
+
openrouter_api_key: "",
|
|
58
65
|
kilo_free_only: false,
|
|
59
66
|
hidden_models: [],
|
|
67
|
+
free_only: true,
|
|
60
68
|
kilo_show_paid: false,
|
|
61
|
-
openrouter_show_paid: false,
|
|
62
69
|
nvidia_show_paid: false,
|
|
63
|
-
|
|
64
|
-
cline_show_paid: false,
|
|
65
|
-
zen_show_paid: false,
|
|
66
|
-
go_show_paid: false,
|
|
67
|
-
mistral_show_paid: false,
|
|
70
|
+
cloudflare_show_paid: false,
|
|
68
71
|
ollama_show_paid: false,
|
|
72
|
+
cline_show_paid: false,
|
|
73
|
+
qwen_show_paid: false,
|
|
74
|
+
modal_show_paid: false,
|
|
75
|
+
openrouter_show_paid: false,
|
|
76
|
+
opencode_show_paid: false,
|
|
69
77
|
};
|
|
70
78
|
|
|
71
79
|
const PI_DIR = join(process.env.HOME || process.env.USERPROFILE || "", ".pi");
|
|
@@ -75,7 +83,6 @@ function ensureConfigFile(): void {
|
|
|
75
83
|
try {
|
|
76
84
|
mkdirSync(PI_DIR, { recursive: true });
|
|
77
85
|
if (existsSync(CONFIG_PATH)) {
|
|
78
|
-
// Merge: add any new template keys without touching existing values
|
|
79
86
|
const existing = JSON.parse(
|
|
80
87
|
readFileSync(CONFIG_PATH, "utf8"),
|
|
81
88
|
) as PiFreeConfig;
|
|
@@ -102,25 +109,26 @@ function ensureConfigFile(): void {
|
|
|
102
109
|
}
|
|
103
110
|
}
|
|
104
111
|
|
|
105
|
-
function loadConfigFile(): PiFreeConfig {
|
|
112
|
+
export function loadConfigFile(): PiFreeConfig {
|
|
106
113
|
try {
|
|
107
114
|
return JSON.parse(readFileSync(CONFIG_PATH, "utf8")) as PiFreeConfig;
|
|
108
|
-
} 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
|
+
});
|
|
109
120
|
return {};
|
|
110
121
|
}
|
|
111
122
|
}
|
|
112
123
|
|
|
113
124
|
ensureConfigFile();
|
|
114
|
-
const file = loadConfigFile();
|
|
115
125
|
|
|
116
126
|
// Resolve each value: env var takes priority over config file.
|
|
117
|
-
// Treat empty strings in the config file as unset.
|
|
118
127
|
function resolve(envKey: string, fileVal?: string): string | undefined {
|
|
119
128
|
return process.env[envKey] || (fileVal?.trim() ? fileVal : undefined);
|
|
120
129
|
}
|
|
121
130
|
|
|
122
131
|
// Resolve boolean flag: env var takes priority, then config file.
|
|
123
|
-
// If neither is set, defaults to false (free-only mode).
|
|
124
132
|
function resolveBool(envKey: string, fileVal?: boolean): boolean {
|
|
125
133
|
const envValue = process.env[envKey];
|
|
126
134
|
if (envValue === "true") return true;
|
|
@@ -128,104 +136,134 @@ function resolveBool(envKey: string, fileVal?: boolean): boolean {
|
|
|
128
136
|
return fileVal === true;
|
|
129
137
|
}
|
|
130
138
|
|
|
131
|
-
//
|
|
132
|
-
//
|
|
133
|
-
|
|
139
|
+
// =============================================================================
|
|
140
|
+
// Per-provider paid-model flags (getters so toggles reflect immediately)
|
|
141
|
+
// =============================================================================
|
|
134
142
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
file.kilo_show_paid,
|
|
139
|
-
);
|
|
143
|
+
export function getKiloShowPaid(): boolean {
|
|
144
|
+
return resolveBool("KILO_SHOW_PAID", loadConfigFile().kilo_show_paid);
|
|
145
|
+
}
|
|
140
146
|
|
|
141
|
-
export
|
|
142
|
-
"
|
|
143
|
-
|
|
144
|
-
);
|
|
147
|
+
export function getNvidiaShowPaid(): boolean {
|
|
148
|
+
return resolveBool("NVIDIA_SHOW_PAID", loadConfigFile().nvidia_show_paid);
|
|
149
|
+
}
|
|
145
150
|
|
|
146
|
-
export
|
|
147
|
-
"
|
|
148
|
-
|
|
149
|
-
);
|
|
151
|
+
export function getClineShowPaid(): boolean {
|
|
152
|
+
return resolveBool("CLINE_SHOW_PAID", loadConfigFile().cline_show_paid);
|
|
153
|
+
}
|
|
150
154
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
+
/** @deprecated Qwen provider is deprecated. */
|
|
156
|
+
export function getQwenShowPaid(): boolean {
|
|
157
|
+
return resolveBool("QWEN_SHOW_PAID", loadConfigFile().qwen_show_paid);
|
|
158
|
+
}
|
|
155
159
|
|
|
156
|
-
export
|
|
157
|
-
"
|
|
158
|
-
|
|
159
|
-
);
|
|
160
|
+
export function getModalShowPaid(): boolean {
|
|
161
|
+
return resolveBool("MODAL_SHOW_PAID", loadConfigFile().modal_show_paid);
|
|
162
|
+
}
|
|
160
163
|
|
|
161
|
-
export
|
|
164
|
+
export function getOllamaShowPaid(): boolean {
|
|
165
|
+
return resolveBool("OLLAMA_SHOW_PAID", loadConfigFile().ollama_show_paid);
|
|
166
|
+
}
|
|
162
167
|
|
|
163
|
-
export
|
|
168
|
+
export function getCloudflareShowPaid(): boolean {
|
|
169
|
+
return resolveBool(
|
|
170
|
+
"CLOUDFLARE_SHOW_PAID",
|
|
171
|
+
loadConfigFile().cloudflare_show_paid,
|
|
172
|
+
);
|
|
173
|
+
}
|
|
164
174
|
|
|
165
|
-
export
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
)
|
|
175
|
+
export function getOpenrouterShowPaid(): boolean {
|
|
176
|
+
return resolveBool(
|
|
177
|
+
"OPENROUTER_SHOW_PAID",
|
|
178
|
+
loadConfigFile().openrouter_show_paid,
|
|
179
|
+
);
|
|
180
|
+
}
|
|
169
181
|
|
|
170
|
-
export
|
|
171
|
-
"
|
|
172
|
-
|
|
173
|
-
);
|
|
182
|
+
export function getOpencodeShowPaid(): boolean {
|
|
183
|
+
return resolveBool("OPENCODE_SHOW_PAID", loadConfigFile().opencode_show_paid);
|
|
184
|
+
}
|
|
174
185
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
);
|
|
186
|
+
// =============================================================================
|
|
187
|
+
// Global free-only mode
|
|
188
|
+
// =============================================================================
|
|
179
189
|
|
|
180
|
-
|
|
190
|
+
export function getFreeOnly(): boolean {
|
|
191
|
+
return resolveBool("PI_FREE_ONLY", loadConfigFile().free_only);
|
|
192
|
+
}
|
|
181
193
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
return models.filter((m) => !HIDDEN.has(m.id));
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
export const OPENROUTER_API_KEY = resolve(
|
|
189
|
-
"OPENROUTER_API_KEY",
|
|
190
|
-
file.openrouter_api_key,
|
|
191
|
-
);
|
|
192
|
-
export const NVIDIA_API_KEY = resolve("NVIDIA_API_KEY", file.nvidia_api_key);
|
|
193
|
-
export const OPENCODE_API_KEY = resolve(
|
|
194
|
-
"OPENCODE_API_KEY",
|
|
195
|
-
file.opencode_api_key,
|
|
196
|
-
);
|
|
197
|
-
export const OPENCODE_GO_API_KEY = resolve(
|
|
198
|
-
"OPENCODE_GO_API_KEY",
|
|
199
|
-
file.opencode_go_api_key,
|
|
200
|
-
);
|
|
201
|
-
export const FIREWORKS_API_KEY = resolve(
|
|
202
|
-
"FIREWORKS_API_KEY",
|
|
203
|
-
file.fireworks_api_key,
|
|
204
|
-
);
|
|
205
|
-
export const MISTRAL_API_KEY = resolve("MISTRAL_API_KEY", file.mistral_api_key);
|
|
206
|
-
export const OLLAMA_API_KEY = resolve("OLLAMA_API_KEY", file.ollama_api_key);
|
|
207
|
-
export const MODAL_API_KEY = resolve("MODAL_API_KEY", file.modal_api_key);
|
|
194
|
+
export function getKiloFreeOnly(): boolean {
|
|
195
|
+
return resolveBool("PI_FREE_KILO_FREE_ONLY", loadConfigFile().kilo_free_only);
|
|
196
|
+
}
|
|
208
197
|
|
|
209
|
-
//
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
198
|
+
// =============================================================================
|
|
199
|
+
// API Keys (getters so runtime config changes are visible)
|
|
200
|
+
// =============================================================================
|
|
201
|
+
|
|
202
|
+
export function getNvidiaApiKey(): string | undefined {
|
|
203
|
+
return resolve("NVIDIA_API_KEY", loadConfigFile().nvidia_api_key);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
export function getModalApiKey(): string | undefined {
|
|
207
|
+
return resolve("MODAL_API_KEY", loadConfigFile().modal_api_key);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
export function getOllamaApiKey(): string | undefined {
|
|
211
|
+
return resolve("OLLAMA_API_KEY", loadConfigFile().ollama_api_key);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
export function getCloudflareApiToken(): string | undefined {
|
|
215
|
+
return resolve("CLOUDFLARE_API_TOKEN", loadConfigFile().cloudflare_api_token);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
export function getCloudflareAccountId(): string | undefined {
|
|
219
|
+
return resolve(
|
|
220
|
+
"CLOUDFLARE_ACCOUNT_ID",
|
|
221
|
+
loadConfigFile().cloudflare_account_id,
|
|
222
|
+
);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
export function getOpencodeApiKey(): string | undefined {
|
|
226
|
+
return resolve("OPENCODE_API_KEY", loadConfigFile().opencode_api_key);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
export function getMistralApiKey(): string | undefined {
|
|
230
|
+
return resolve("MISTRAL_API_KEY", loadConfigFile().mistral_api_key);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
export function getGroqApiKey(): string | undefined {
|
|
234
|
+
return resolve("GROQ_API_KEY", loadConfigFile().groq_api_key);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
export function getCerebrasApiKey(): string | undefined {
|
|
238
|
+
return resolve("CEREBRAS_API_KEY", loadConfigFile().cerebras_api_key);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
export function getXaiApiKey(): string | undefined {
|
|
242
|
+
return resolve("XAI_API_KEY", loadConfigFile().xai_api_key);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
export function getHfToken(): string | undefined {
|
|
246
|
+
return resolve("HF_TOKEN", loadConfigFile().hf_token);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
export function getOpenrouterApiKey(): string | undefined {
|
|
250
|
+
return resolve("OPENROUTER_API_KEY", loadConfigFile().openrouter_api_key);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// =============================================================================
|
|
254
|
+
// Hidden models (re-reads config on every call)
|
|
255
|
+
// =============================================================================
|
|
256
|
+
|
|
257
|
+
export function applyHidden<T extends { id: string }>(models: T[]): T[] {
|
|
258
|
+
const hidden = new Set(loadConfigFile().hidden_models ?? []);
|
|
259
|
+
if (hidden.size === 0) return models;
|
|
260
|
+
return models.filter((m) => !hidden.has(m.id));
|
|
261
|
+
}
|
|
223
262
|
|
|
224
263
|
// =============================================================================
|
|
225
|
-
//
|
|
264
|
+
// Persistence
|
|
226
265
|
// =============================================================================
|
|
227
266
|
|
|
228
|
-
/** Save updated config values to ~/.pi/free.json */
|
|
229
267
|
export function saveConfig(updates: Partial<PiFreeConfig>): void {
|
|
230
268
|
try {
|
|
231
269
|
const existing = loadConfigFile();
|
|
@@ -243,7 +281,18 @@ export function saveConfig(updates: Partial<PiFreeConfig>): void {
|
|
|
243
281
|
}
|
|
244
282
|
}
|
|
245
283
|
|
|
246
|
-
/** Get current config values (for checking state) */
|
|
247
284
|
export function getConfig(): PiFreeConfig {
|
|
248
285
|
return loadConfigFile();
|
|
249
286
|
}
|
|
287
|
+
|
|
288
|
+
// =============================================================================
|
|
289
|
+
// Re-export provider names for consistency
|
|
290
|
+
// =============================================================================
|
|
291
|
+
|
|
292
|
+
export {
|
|
293
|
+
PROVIDER_CLINE,
|
|
294
|
+
PROVIDER_KILO,
|
|
295
|
+
PROVIDER_MODAL,
|
|
296
|
+
PROVIDER_NVIDIA,
|
|
297
|
+
PROVIDER_QWEN,
|
|
298
|
+
};
|
package/constants.ts
CHANGED
|
@@ -4,33 +4,26 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
// =============================================================================
|
|
7
|
-
// Provider names (
|
|
7
|
+
// Provider names (unique providers NOT built into pi)
|
|
8
8
|
// =============================================================================
|
|
9
9
|
|
|
10
10
|
export const PROVIDER_KILO = "kilo";
|
|
11
|
-
export const PROVIDER_ZEN = "zen";
|
|
12
|
-
export const PROVIDER_GO = "go";
|
|
13
|
-
export const PROVIDER_OPENROUTER = "openrouter";
|
|
14
|
-
export const PROVIDER_NVIDIA = "nvidia";
|
|
15
11
|
export const PROVIDER_CLINE = "cline";
|
|
16
|
-
export const
|
|
17
|
-
export const
|
|
18
|
-
export const
|
|
12
|
+
export const PROVIDER_NVIDIA = "nvidia";
|
|
13
|
+
export const PROVIDER_CLOUDFLARE = "cloudflare";
|
|
14
|
+
export const PROVIDER_OLLAMA = "ollama-cloud";
|
|
15
|
+
/** @deprecated Qwen provider is deprecated. The 1,000 req/day free tier is no longer available. */
|
|
19
16
|
export const PROVIDER_QWEN = "qwen";
|
|
20
17
|
export const PROVIDER_MODAL = "modal";
|
|
21
18
|
|
|
22
|
-
export const
|
|
19
|
+
export const ALL_UNIQUE_PROVIDERS = [
|
|
23
20
|
PROVIDER_KILO,
|
|
24
|
-
PROVIDER_ZEN,
|
|
25
|
-
PROVIDER_GO,
|
|
26
|
-
PROVIDER_OPENROUTER,
|
|
27
|
-
PROVIDER_NVIDIA,
|
|
28
21
|
PROVIDER_CLINE,
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
PROVIDER_OLLAMA,
|
|
22
|
+
PROVIDER_NVIDIA,
|
|
23
|
+
/** @deprecated Qwen free tier no longer available */
|
|
32
24
|
PROVIDER_QWEN,
|
|
33
25
|
PROVIDER_MODAL,
|
|
26
|
+
PROVIDER_OLLAMA,
|
|
34
27
|
] as const;
|
|
35
28
|
|
|
36
29
|
// =============================================================================
|
|
@@ -38,14 +31,16 @@ export const ALL_PROVIDERS = [
|
|
|
38
31
|
// =============================================================================
|
|
39
32
|
|
|
40
33
|
export const BASE_URL_KILO = "https://api.kilo.ai/api/gateway";
|
|
41
|
-
export const BASE_URL_ZEN = "https://opencode.ai/zen/v1";
|
|
42
|
-
export const BASE_URL_GO = "https://opencode.ai/zen/go/v1";
|
|
43
|
-
export const BASE_URL_OPENROUTER = "https://openrouter.ai/api/v1";
|
|
44
34
|
export const BASE_URL_NVIDIA = "https://integrate.api.nvidia.com/v1";
|
|
35
|
+
export const BASE_URL_CLOUDFLARE = "https://api.cloudflare.com/client/v4";
|
|
36
|
+
export const BASE_URL_OLLAMA = "https://ollama.com/v1"; // OpenAI-compatible API endpoint
|
|
45
37
|
export const BASE_URL_CLINE = "https://api.cline.bot/api/v1";
|
|
46
|
-
export const BASE_URL_FIREWORKS = "https://api.fireworks.ai/inference/v1";
|
|
47
|
-
export const BASE_URL_OLLAMA = "https://ollama.com/v1";
|
|
48
38
|
export const BASE_URL_MODAL = "https://api.us-west-2.modal.direct/v1";
|
|
39
|
+
export const BASE_URL_QWEN =
|
|
40
|
+
"https://dashscope.aliyuncs.com/compatible-mode/v1";
|
|
41
|
+
|
|
42
|
+
/** Cline fetches free models from OpenRouter */
|
|
43
|
+
export const BASE_URL_OPENROUTER = "https://openrouter.ai/api/v1";
|
|
49
44
|
|
|
50
45
|
// =============================================================================
|
|
51
46
|
// External URLs
|
|
@@ -53,12 +48,9 @@ export const BASE_URL_MODAL = "https://api.us-west-2.modal.direct/v1";
|
|
|
53
48
|
|
|
54
49
|
export const URL_MODELS_DEV = "https://models.dev/api.json";
|
|
55
50
|
export const URL_KILO_TOS = "https://kilo.ai/terms";
|
|
56
|
-
export const URL_ZEN_TOS = "https://opencode.ai/terms";
|
|
57
|
-
export const URL_GO_TOS = "https://opencode.ai/terms";
|
|
58
51
|
export const URL_CLINE_TOS = "https://cline.bot/tos";
|
|
59
52
|
export const URL_QWEN_TOS = "https://terms.alicloud.com/";
|
|
60
53
|
export const URL_MODAL_TOS = "https://modal.com/terms";
|
|
61
|
-
export const BASE_URL_QWEN = "https://dashscope.aliyuncs.com/compatible-mode/v1";
|
|
62
54
|
|
|
63
55
|
// =============================================================================
|
|
64
56
|
// Cline auth
|
|
@@ -77,47 +69,17 @@ export const DEFAULT_MIN_SIZE_B = 30; // Default minimum model size for filterin
|
|
|
77
69
|
// Timeouts (milliseconds)
|
|
78
70
|
// =============================================================================
|
|
79
71
|
|
|
80
|
-
|
|
72
|
+
/** Timeout for fetch operations */
|
|
81
73
|
export const DEFAULT_FETCH_TIMEOUT_MS: number = 10_000;
|
|
82
74
|
|
|
83
|
-
export interface TestConfig {
|
|
84
|
-
timeout: number;
|
|
85
|
-
retries: number;
|
|
86
|
-
label: string;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
// LSP test - fixed - added missing property
|
|
90
|
-
export const testConfig: TestConfig = {
|
|
91
|
-
timeout: 5000,
|
|
92
|
-
retries: 3,
|
|
93
|
-
label: "test",
|
|
94
|
-
};
|
|
95
|
-
|
|
96
|
-
// LSP test - fixed return type
|
|
97
|
-
export function calculateTimeout(base: number): number {
|
|
98
|
-
return base * 2;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
// LSP test - unused variable (should show hint/warning if configured)
|
|
102
|
-
export function unusedParamTest(required: string, _unused: number): string {
|
|
103
|
-
return required.toUpperCase();
|
|
104
|
-
}
|
|
105
75
|
export const KILO_POLL_INTERVAL_MS = 3_000;
|
|
106
76
|
export const KILO_TOKEN_EXPIRATION_MS = 365 * 24 * 60 * 60 * 1000; // 1 year
|
|
107
77
|
|
|
108
78
|
// =============================================================================
|
|
109
|
-
//
|
|
79
|
+
// Removed providers (now built into pi):
|
|
80
|
+
// - openrouter: use pi's built-in with OPENROUTER_API_KEY
|
|
81
|
+
// - zen/opencode: use pi's built-in with OPENCODE_API_KEY
|
|
82
|
+
// - go/opencode-go: use pi's built-in with OPENCODE_API_KEY
|
|
83
|
+
// - mistral: use pi's built-in with MISTRAL_API_KEY
|
|
84
|
+
// - ollama: add to ~/.pi/agent/models.json as custom provider
|
|
110
85
|
// =============================================================================
|
|
111
|
-
|
|
112
|
-
export const PROVIDER_GROQ = "groq";
|
|
113
|
-
export const PROVIDER_TOGETHER = "together";
|
|
114
|
-
export const PROVIDER_DEEPINFRA = "deepinfra";
|
|
115
|
-
export const PROVIDER_PERPLEXITY = "perplexity";
|
|
116
|
-
export const PROVIDER_XAI = "xai";
|
|
117
|
-
|
|
118
|
-
export const BASE_URL_GROQ = "https://api.groq.com/openai/v1";
|
|
119
|
-
export const BASE_URL_TOGETHER = "https://api.together.xyz/v1";
|
|
120
|
-
export const BASE_URL_DEEPINFRA = "https://api.deepinfra.com/v1/openai";
|
|
121
|
-
export const BASE_URL_MISTRAL = "https://api.mistral.ai/v1";
|
|
122
|
-
export const BASE_URL_PERPLEXITY = "https://api.perplexity.ai";
|
|
123
|
-
export const BASE_URL_XAI = "https://api.x.ai/v1";
|
package/index.ts
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pi-Free Providers Index
|
|
3
|
+
*
|
|
4
|
+
* Provides free model filtering for ALL providers (built-in + extension)
|
|
5
|
+
* plus unique free/paid providers not covered by pi's built-in providers.
|
|
6
|
+
*
|
|
7
|
+
* Unique providers:
|
|
8
|
+
* - Kilo: OAuth-based free models
|
|
9
|
+
* - Cline: Cline bot integration
|
|
10
|
+
* - NVIDIA: NVIDIA NIM hosting (free tier available)
|
|
11
|
+
* - Ollama Cloud: Ollama's cloud-hosted models with usage-based free tier
|
|
12
|
+
* - Qwen: OAuth-based Qwen access (deprecated)
|
|
13
|
+
* - Modal: Modal Labs hosting
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
|
17
|
+
import { setupBuiltInProviderToggles } from "./lib/built-in-toggle.ts";
|
|
18
|
+
import { createLogger } from "./lib/logger.ts";
|
|
19
|
+
import {
|
|
20
|
+
applyGlobalFilter,
|
|
21
|
+
getGlobalFreeOnly,
|
|
22
|
+
getProviderRegistry,
|
|
23
|
+
isFreeModel,
|
|
24
|
+
registerWithGlobalToggle,
|
|
25
|
+
} from "./lib/registry.ts";
|
|
26
|
+
// Import unique provider extensions (only providers NOT built into pi)
|
|
27
|
+
import cline from "./providers/cline/cline.ts";
|
|
28
|
+
import cloudflare from "./providers/cloudflare/cloudflare.ts";
|
|
29
|
+
import kilo from "./providers/kilo/kilo.ts";
|
|
30
|
+
import modal from "./providers/modal/modal.ts";
|
|
31
|
+
import nvidia from "./providers/nvidia/nvidia.ts";
|
|
32
|
+
import ollama from "./providers/ollama/ollama.ts";
|
|
33
|
+
import qwen from "./providers/qwen/qwen.ts";
|
|
34
|
+
|
|
35
|
+
const _logger = createLogger("pi-free");
|
|
36
|
+
|
|
37
|
+
// =============================================================================
|
|
38
|
+
// Global Commands
|
|
39
|
+
// =============================================================================
|
|
40
|
+
|
|
41
|
+
function setupGlobalCommands(pi: ExtensionAPI) {
|
|
42
|
+
// /free-providers - Show free model counts by provider
|
|
43
|
+
pi.registerCommand("free-providers", {
|
|
44
|
+
description: "Show free/paid model counts for all pi-free providers",
|
|
45
|
+
handler: async (_args, ctx) => {
|
|
46
|
+
const lines = ["📊 Pi-Free Providers:", ""];
|
|
47
|
+
const registry = getProviderRegistry();
|
|
48
|
+
|
|
49
|
+
// Providers known to not expose pricing via API (all models show as "free")
|
|
50
|
+
// OpenRouter and OpenCode expose actual pricing
|
|
51
|
+
const noPricingApi = new Set([
|
|
52
|
+
"mistral",
|
|
53
|
+
"xai",
|
|
54
|
+
"huggingface",
|
|
55
|
+
"groq",
|
|
56
|
+
"cerebras",
|
|
57
|
+
]);
|
|
58
|
+
// Freemium providers - all models share a free tier quota
|
|
59
|
+
const freemiumProviders = new Set(["nvidia"]);
|
|
60
|
+
|
|
61
|
+
for (const [id, entry] of registry) {
|
|
62
|
+
const free = entry.stored.free.length;
|
|
63
|
+
const all = entry.stored.all.length || free;
|
|
64
|
+
const indicator = entry.hasKey ? "🔑" : "🆓";
|
|
65
|
+
const paid = all - free;
|
|
66
|
+
|
|
67
|
+
if (freemiumProviders.has(id)) {
|
|
68
|
+
// Freemium: all models share a free tier (e.g., 1,000 reqs/month)
|
|
69
|
+
lines.push(`${indicator} ${id}: ${all} models (freemium)`);
|
|
70
|
+
} else if (noPricingApi.has(id)) {
|
|
71
|
+
// Provider doesn't expose pricing - can't determine free vs paid
|
|
72
|
+
lines.push(
|
|
73
|
+
`${indicator} ${id}: ${all} models (pricing not exposed by API)`,
|
|
74
|
+
);
|
|
75
|
+
} else if (paid === 0 && free > 0) {
|
|
76
|
+
// All models are actually free
|
|
77
|
+
lines.push(`${indicator} ${id}: ${free} free models`);
|
|
78
|
+
} else {
|
|
79
|
+
// Mix of free and paid
|
|
80
|
+
lines.push(
|
|
81
|
+
`${indicator} ${id}: ${free} free / ${paid} paid (${all} total)`,
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (registry.size === 0) {
|
|
87
|
+
lines.push("(No providers registered yet)");
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
ctx.ui.notify(lines.join("\n"), "info");
|
|
91
|
+
},
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// =============================================================================
|
|
96
|
+
// Main Entry Point
|
|
97
|
+
// =============================================================================
|
|
98
|
+
|
|
99
|
+
export default async function (pi: ExtensionAPI) {
|
|
100
|
+
const globalFreeOnly = getGlobalFreeOnly();
|
|
101
|
+
_logger.info(`[pi-free] Initializing (global free-only: ${globalFreeOnly})`);
|
|
102
|
+
|
|
103
|
+
// Setup global commands first
|
|
104
|
+
setupGlobalCommands(pi);
|
|
105
|
+
|
|
106
|
+
// Load all unique providers
|
|
107
|
+
// Each provider will register itself with the global toggle system
|
|
108
|
+
await Promise.allSettled([
|
|
109
|
+
cloudflare(pi),
|
|
110
|
+
modal(pi),
|
|
111
|
+
nvidia(pi),
|
|
112
|
+
kilo(pi),
|
|
113
|
+
ollama(pi),
|
|
114
|
+
// Qwen is deprecated
|
|
115
|
+
qwen(pi).catch((err) => {
|
|
116
|
+
_logger.warn("[pi-free] Qwen provider failed to load (deprecated)", err);
|
|
117
|
+
}),
|
|
118
|
+
cline(pi),
|
|
119
|
+
]);
|
|
120
|
+
|
|
121
|
+
// Setup dynamic built-in providers (Mistral, Groq, Cerebras, xAI, Hugging Face, OpenRouter)
|
|
122
|
+
// These only activate if the user has configured API keys (OpenRouter works without key too)
|
|
123
|
+
const { setupDynamicBuiltInProviders } = await import(
|
|
124
|
+
"./providers/dynamic-built-in/index.ts"
|
|
125
|
+
);
|
|
126
|
+
await setupDynamicBuiltInProviders(pi);
|
|
127
|
+
|
|
128
|
+
// Setup toggles for pi's built-in providers (e.g., OpenCode)
|
|
129
|
+
setupBuiltInProviderToggles(pi);
|
|
130
|
+
|
|
131
|
+
// Apply initial global filter if free-only mode is enabled
|
|
132
|
+
if (globalFreeOnly) {
|
|
133
|
+
_logger.info("[pi-free] Applying initial free-only filter");
|
|
134
|
+
await applyGlobalFilter(pi, true);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const registry = getProviderRegistry();
|
|
138
|
+
_logger.info(`[pi-free] Loaded with ${registry.size} providers`);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Re-export registry helpers so consumers don't need deep imports
|
|
142
|
+
export {
|
|
143
|
+
applyGlobalFilter,
|
|
144
|
+
getGlobalFreeOnly,
|
|
145
|
+
getProviderRegistry,
|
|
146
|
+
isFreeModel,
|
|
147
|
+
registerWithGlobalToggle,
|
|
148
|
+
};
|