pi-free 2.0.2 → 2.0.5
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 +84 -12
- package/README.md +44 -97
- package/banner.svg +132 -0
- package/config.ts +24 -52
- package/constants.ts +6 -0
- package/index.ts +175 -148
- package/lib/built-in-toggle.ts +40 -1
- package/lib/model-detection.ts +176 -139
- package/lib/model-enhancer.ts +20 -20
- package/lib/open-browser.ts +1 -1
- package/lib/provider-compat.ts +46 -0
- package/lib/registry.ts +200 -144
- package/lib/types.ts +101 -108
- package/lib/util.ts +262 -256
- package/package.json +9 -8
- package/provider-failover/benchmark-lookup.ts +191 -140
- package/provider-helper.ts +19 -1
- package/providers/cline/cline-auth.ts +473 -473
- package/providers/cline/cline.ts +58 -14
- package/providers/crofai/crofai.ts +170 -0
- package/providers/dynamic-built-in/index.ts +260 -308
- package/providers/kilo/kilo-auth.ts +155 -155
- package/providers/kilo/kilo.ts +263 -235
- package/providers/nvidia/nvidia.ts +474 -415
- package/providers/ollama/ollama.ts +295 -280
- package/providers/opencode-session.ts +3 -4
- package/providers/qwen/qwen-models.ts +101 -101
- package/providers/qwen/qwen.ts +47 -49
- package/providers/zenmux/zenmux.ts +176 -0
- package/scripts/check-extensions.mjs +71 -55
- package/provider-factory.ts +0 -207
- package/providers/cloudflare/cloudflare.ts +0 -526
- package/providers/modal/modal.ts +0 -47
package/config.ts
CHANGED
|
@@ -24,54 +24,45 @@ const _logger = createLogger("config");
|
|
|
24
24
|
|
|
25
25
|
interface PiFreeConfig {
|
|
26
26
|
nvidia_api_key?: string;
|
|
27
|
-
cloudflare_api_token?: string;
|
|
28
|
-
cloudflare_account_id?: string;
|
|
29
27
|
ollama_api_key?: string;
|
|
30
|
-
|
|
31
|
-
|
|
28
|
+
zenmux_api_key?: string;
|
|
29
|
+
crofai_api_key?: string;
|
|
32
30
|
mistral_api_key?: string;
|
|
33
31
|
groq_api_key?: string;
|
|
34
32
|
cerebras_api_key?: string;
|
|
35
33
|
xai_api_key?: string;
|
|
36
34
|
hf_token?: string;
|
|
37
|
-
openrouter_api_key?: string;
|
|
38
35
|
kilo_free_only?: boolean;
|
|
39
36
|
hidden_models?: string[];
|
|
40
37
|
free_only?: boolean;
|
|
41
38
|
kilo_show_paid?: boolean;
|
|
42
|
-
nvidia_show_paid?: boolean;
|
|
43
|
-
cloudflare_show_paid?: boolean;
|
|
44
39
|
ollama_show_paid?: boolean;
|
|
45
40
|
cline_show_paid?: boolean;
|
|
46
|
-
|
|
47
|
-
|
|
41
|
+
zenmux_show_paid?: boolean;
|
|
42
|
+
crofai_show_paid?: boolean;
|
|
48
43
|
openrouter_show_paid?: boolean;
|
|
49
44
|
opencode_show_paid?: boolean;
|
|
50
45
|
}
|
|
51
46
|
|
|
52
47
|
const CONFIG_TEMPLATE: PiFreeConfig = {
|
|
53
48
|
nvidia_api_key: "",
|
|
54
|
-
cloudflare_api_token: "",
|
|
55
|
-
cloudflare_account_id: "",
|
|
56
49
|
ollama_api_key: "",
|
|
57
|
-
|
|
58
|
-
|
|
50
|
+
zenmux_api_key: "",
|
|
51
|
+
crofai_api_key: "",
|
|
59
52
|
mistral_api_key: "",
|
|
60
53
|
groq_api_key: "",
|
|
61
54
|
cerebras_api_key: "",
|
|
62
55
|
xai_api_key: "",
|
|
63
56
|
hf_token: "",
|
|
64
|
-
|
|
57
|
+
|
|
65
58
|
kilo_free_only: false,
|
|
66
59
|
hidden_models: [],
|
|
67
60
|
free_only: true,
|
|
68
61
|
kilo_show_paid: false,
|
|
69
|
-
nvidia_show_paid: false,
|
|
70
|
-
cloudflare_show_paid: false,
|
|
71
62
|
ollama_show_paid: false,
|
|
72
63
|
cline_show_paid: false,
|
|
73
|
-
|
|
74
|
-
|
|
64
|
+
zenmux_show_paid: false,
|
|
65
|
+
crofai_show_paid: false,
|
|
75
66
|
openrouter_show_paid: false,
|
|
76
67
|
opencode_show_paid: false,
|
|
77
68
|
};
|
|
@@ -144,34 +135,22 @@ export function getKiloShowPaid(): boolean {
|
|
|
144
135
|
return resolveBool("KILO_SHOW_PAID", loadConfigFile().kilo_show_paid);
|
|
145
136
|
}
|
|
146
137
|
|
|
147
|
-
export function getNvidiaShowPaid(): boolean {
|
|
148
|
-
return resolveBool("NVIDIA_SHOW_PAID", loadConfigFile().nvidia_show_paid);
|
|
149
|
-
}
|
|
150
|
-
|
|
151
138
|
export function getClineShowPaid(): boolean {
|
|
152
139
|
return resolveBool("CLINE_SHOW_PAID", loadConfigFile().cline_show_paid);
|
|
153
140
|
}
|
|
154
141
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
return resolveBool("QWEN_SHOW_PAID", loadConfigFile().qwen_show_paid);
|
|
142
|
+
export function getZenmuxShowPaid(): boolean {
|
|
143
|
+
return resolveBool("ZENMUX_SHOW_PAID", loadConfigFile().zenmux_show_paid);
|
|
158
144
|
}
|
|
159
145
|
|
|
160
|
-
export function
|
|
161
|
-
return resolveBool("
|
|
146
|
+
export function getCrofaiShowPaid(): boolean {
|
|
147
|
+
return resolveBool("CROFAI_SHOW_PAID", loadConfigFile().crofai_show_paid);
|
|
162
148
|
}
|
|
163
149
|
|
|
164
150
|
export function getOllamaShowPaid(): boolean {
|
|
165
151
|
return resolveBool("OLLAMA_SHOW_PAID", loadConfigFile().ollama_show_paid);
|
|
166
152
|
}
|
|
167
153
|
|
|
168
|
-
export function getCloudflareShowPaid(): boolean {
|
|
169
|
-
return resolveBool(
|
|
170
|
-
"CLOUDFLARE_SHOW_PAID",
|
|
171
|
-
loadConfigFile().cloudflare_show_paid,
|
|
172
|
-
);
|
|
173
|
-
}
|
|
174
|
-
|
|
175
154
|
export function getOpenrouterShowPaid(): boolean {
|
|
176
155
|
return resolveBool(
|
|
177
156
|
"OPENROUTER_SHOW_PAID",
|
|
@@ -203,27 +182,16 @@ export function getNvidiaApiKey(): string | undefined {
|
|
|
203
182
|
return resolve("NVIDIA_API_KEY", loadConfigFile().nvidia_api_key);
|
|
204
183
|
}
|
|
205
184
|
|
|
206
|
-
export function
|
|
207
|
-
return resolve("
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
export function getOllamaApiKey(): string | undefined {
|
|
211
|
-
return resolve("OLLAMA_API_KEY", loadConfigFile().ollama_api_key);
|
|
185
|
+
export function getZenmuxApiKey(): string | undefined {
|
|
186
|
+
return resolve("ZENMUX_API_KEY", loadConfigFile().zenmux_api_key);
|
|
212
187
|
}
|
|
213
188
|
|
|
214
|
-
export function
|
|
215
|
-
return resolve("
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
export function getCloudflareAccountId(): string | undefined {
|
|
219
|
-
return resolve(
|
|
220
|
-
"CLOUDFLARE_ACCOUNT_ID",
|
|
221
|
-
loadConfigFile().cloudflare_account_id,
|
|
222
|
-
);
|
|
189
|
+
export function getCrofaiApiKey(): string | undefined {
|
|
190
|
+
return resolve("CROFAI_API_KEY", loadConfigFile().crofai_api_key);
|
|
223
191
|
}
|
|
224
192
|
|
|
225
|
-
export function
|
|
226
|
-
return resolve("
|
|
193
|
+
export function getOllamaApiKey(): string | undefined {
|
|
194
|
+
return resolve("OLLAMA_API_KEY", loadConfigFile().ollama_api_key);
|
|
227
195
|
}
|
|
228
196
|
|
|
229
197
|
export function getMistralApiKey(): string | undefined {
|
|
@@ -246,8 +214,12 @@ export function getHfToken(): string | undefined {
|
|
|
246
214
|
return resolve("HF_TOKEN", loadConfigFile().hf_token);
|
|
247
215
|
}
|
|
248
216
|
|
|
217
|
+
/**
|
|
218
|
+
* OpenRouter key — pi's built-in provider reads from ~/.pi/agent/auth.json.
|
|
219
|
+
* pi-free only checks the env var to avoid stale keys from free.json.
|
|
220
|
+
*/
|
|
249
221
|
export function getOpenrouterApiKey(): string | undefined {
|
|
250
|
-
return
|
|
222
|
+
return process.env.OPENROUTER_API_KEY;
|
|
251
223
|
}
|
|
252
224
|
|
|
253
225
|
// =============================================================================
|
package/constants.ts
CHANGED
|
@@ -15,6 +15,8 @@ export const PROVIDER_OLLAMA = "ollama-cloud";
|
|
|
15
15
|
/** @deprecated Qwen provider is deprecated. The 1,000 req/day free tier is no longer available. */
|
|
16
16
|
export const PROVIDER_QWEN = "qwen";
|
|
17
17
|
export const PROVIDER_MODAL = "modal";
|
|
18
|
+
export const PROVIDER_ZENMUX = "zenmux";
|
|
19
|
+
export const PROVIDER_CROFAI = "crofai";
|
|
18
20
|
|
|
19
21
|
export const ALL_UNIQUE_PROVIDERS = [
|
|
20
22
|
PROVIDER_KILO,
|
|
@@ -24,6 +26,8 @@ export const ALL_UNIQUE_PROVIDERS = [
|
|
|
24
26
|
PROVIDER_QWEN,
|
|
25
27
|
PROVIDER_MODAL,
|
|
26
28
|
PROVIDER_OLLAMA,
|
|
29
|
+
PROVIDER_ZENMUX,
|
|
30
|
+
PROVIDER_CROFAI,
|
|
27
31
|
] as const;
|
|
28
32
|
|
|
29
33
|
// =============================================================================
|
|
@@ -38,6 +42,8 @@ export const BASE_URL_CLINE = "https://api.cline.bot/api/v1";
|
|
|
38
42
|
export const BASE_URL_MODAL = "https://api.us-west-2.modal.direct/v1";
|
|
39
43
|
export const BASE_URL_QWEN =
|
|
40
44
|
"https://dashscope.aliyuncs.com/compatible-mode/v1";
|
|
45
|
+
export const BASE_URL_ZENMUX = "https://zenmux.ai/api/v1";
|
|
46
|
+
export const BASE_URL_CROFAI = "https://crof.ai/v1";
|
|
41
47
|
|
|
42
48
|
/** Cline fetches free models from OpenRouter */
|
|
43
49
|
export const BASE_URL_OPENROUTER = "https://openrouter.ai/api/v1";
|
package/index.ts
CHANGED
|
@@ -1,148 +1,175 @@
|
|
|
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
|
-
* -
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
import
|
|
17
|
-
import {
|
|
18
|
-
import {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
import
|
|
28
|
-
import
|
|
29
|
-
import
|
|
30
|
-
import
|
|
31
|
-
import
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
// =============================================================================
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
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
|
+
* - ZenMux: Unified AI API gateway with 200+ models
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
|
16
|
+
import { setupBuiltInProviderToggles } from "./lib/built-in-toggle.ts";
|
|
17
|
+
import { createLogger } from "./lib/logger.ts";
|
|
18
|
+
import {
|
|
19
|
+
applyGlobalFilter,
|
|
20
|
+
getGlobalFreeOnly,
|
|
21
|
+
getProviderRegistry,
|
|
22
|
+
isFreeModel,
|
|
23
|
+
registerWithGlobalToggle,
|
|
24
|
+
} from "./lib/registry.ts";
|
|
25
|
+
// Import unique provider extensions (only providers NOT built into pi)
|
|
26
|
+
import cline from "./providers/cline/cline.ts";
|
|
27
|
+
import crofai from "./providers/crofai/crofai.ts";
|
|
28
|
+
import kilo from "./providers/kilo/kilo.ts";
|
|
29
|
+
import nvidia from "./providers/nvidia/nvidia.ts";
|
|
30
|
+
import ollama from "./providers/ollama/ollama.ts";
|
|
31
|
+
import zenmux from "./providers/zenmux/zenmux.ts";
|
|
32
|
+
|
|
33
|
+
const _logger = createLogger("pi-free");
|
|
34
|
+
|
|
35
|
+
// =============================================================================
|
|
36
|
+
// Global Commands
|
|
37
|
+
// =============================================================================
|
|
38
|
+
|
|
39
|
+
function setupGlobalCommands(pi: ExtensionAPI) {
|
|
40
|
+
// /toggle-free - Global free-only mode toggle
|
|
41
|
+
pi.registerCommand("toggle-free", {
|
|
42
|
+
description: "Toggle global free-only mode for all providers",
|
|
43
|
+
handler: async (_args, ctx) => {
|
|
44
|
+
const current = getGlobalFreeOnly();
|
|
45
|
+
const next = !current;
|
|
46
|
+
applyGlobalFilter(pi, next);
|
|
47
|
+
|
|
48
|
+
const registry = getProviderRegistry();
|
|
49
|
+
const providerCount = registry.size;
|
|
50
|
+
|
|
51
|
+
if (next) {
|
|
52
|
+
const totalFree = [...registry.values()].reduce(
|
|
53
|
+
(sum, e) => sum + e.stored.free.length,
|
|
54
|
+
0,
|
|
55
|
+
);
|
|
56
|
+
ctx.ui.notify(
|
|
57
|
+
`Free-only mode: ON (${totalFree} free models across ${providerCount} providers)`,
|
|
58
|
+
"info",
|
|
59
|
+
);
|
|
60
|
+
} else {
|
|
61
|
+
const totalAll = [...registry.values()].reduce(
|
|
62
|
+
(sum, e) => sum + (e.stored.all.length || e.stored.free.length),
|
|
63
|
+
0,
|
|
64
|
+
);
|
|
65
|
+
ctx.ui.notify(
|
|
66
|
+
`Free-only mode: OFF (all ${totalAll} models visible across ${providerCount} providers)`,
|
|
67
|
+
"info",
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
},
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
// /free-providers - Show free model counts by provider
|
|
74
|
+
pi.registerCommand("free-providers", {
|
|
75
|
+
description: "Show free/paid model counts for all pi-free providers",
|
|
76
|
+
handler: async (_args, ctx) => {
|
|
77
|
+
const lines = ["📊 Pi-Free Providers:", ""];
|
|
78
|
+
const registry = getProviderRegistry();
|
|
79
|
+
|
|
80
|
+
// Providers known to not expose pricing via API (all models show as "free")
|
|
81
|
+
// OpenRouter and OpenCode expose actual pricing
|
|
82
|
+
const noPricingApi = new Set([
|
|
83
|
+
"mistral",
|
|
84
|
+
"xai",
|
|
85
|
+
"huggingface",
|
|
86
|
+
"groq",
|
|
87
|
+
"cerebras",
|
|
88
|
+
]);
|
|
89
|
+
// Freemium providers - all models share a free tier quota
|
|
90
|
+
const freemiumProviders = new Set(["nvidia"]);
|
|
91
|
+
|
|
92
|
+
for (const [id, entry] of registry) {
|
|
93
|
+
const free = entry.stored.free.length;
|
|
94
|
+
const all = entry.stored.all.length || free;
|
|
95
|
+
const indicator = entry.hasKey ? "🔑" : "🆓";
|
|
96
|
+
const paid = all - free;
|
|
97
|
+
|
|
98
|
+
if (freemiumProviders.has(id)) {
|
|
99
|
+
// Freemium: all models share a free tier (e.g., 1,000 reqs/month)
|
|
100
|
+
lines.push(`${indicator} ${id}: ${all} models (freemium)`);
|
|
101
|
+
} else if (noPricingApi.has(id)) {
|
|
102
|
+
// Provider doesn't expose pricing - can't determine free vs paid
|
|
103
|
+
lines.push(
|
|
104
|
+
`${indicator} ${id}: ${all} models (pricing not exposed by API)`,
|
|
105
|
+
);
|
|
106
|
+
} else if (paid === 0 && free > 0) {
|
|
107
|
+
// All models are actually free
|
|
108
|
+
lines.push(`${indicator} ${id}: ${free} free models`);
|
|
109
|
+
} else {
|
|
110
|
+
// Mix of free and paid
|
|
111
|
+
lines.push(
|
|
112
|
+
`${indicator} ${id}: ${free} free / ${paid} paid (${all} total)`,
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (registry.size === 0) {
|
|
118
|
+
lines.push("(No providers registered yet)");
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
ctx.ui.notify(lines.join("\n"), "info");
|
|
122
|
+
},
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// =============================================================================
|
|
127
|
+
// Main Entry Point
|
|
128
|
+
// =============================================================================
|
|
129
|
+
|
|
130
|
+
export default async function (pi: ExtensionAPI) {
|
|
131
|
+
const globalFreeOnly = getGlobalFreeOnly();
|
|
132
|
+
_logger.info(`[pi-free] Initializing (global free-only: ${globalFreeOnly})`);
|
|
133
|
+
|
|
134
|
+
// Setup global commands first
|
|
135
|
+
setupGlobalCommands(pi);
|
|
136
|
+
|
|
137
|
+
// Load all unique providers
|
|
138
|
+
// Each provider will register itself with the global toggle system
|
|
139
|
+
await Promise.allSettled([
|
|
140
|
+
nvidia(pi),
|
|
141
|
+
kilo(pi),
|
|
142
|
+
ollama(pi),
|
|
143
|
+
cline(pi),
|
|
144
|
+
zenmux(pi),
|
|
145
|
+
crofai(pi),
|
|
146
|
+
]);
|
|
147
|
+
|
|
148
|
+
// Setup dynamic built-in providers (Mistral, Groq, Cerebras, xAI, Hugging Face)
|
|
149
|
+
// These only activate if the user has configured API keys (OpenRouter works without key too)
|
|
150
|
+
const { setupDynamicBuiltInProviders } = await import(
|
|
151
|
+
"./providers/dynamic-built-in/index.ts"
|
|
152
|
+
);
|
|
153
|
+
await setupDynamicBuiltInProviders(pi);
|
|
154
|
+
|
|
155
|
+
// Setup toggles for pi's built-in providers (e.g., OpenCode)
|
|
156
|
+
setupBuiltInProviderToggles(pi);
|
|
157
|
+
|
|
158
|
+
// Apply initial global filter if free-only mode is enabled
|
|
159
|
+
if (globalFreeOnly) {
|
|
160
|
+
_logger.info("[pi-free] Applying initial free-only filter");
|
|
161
|
+
applyGlobalFilter(pi, true);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const registry = getProviderRegistry();
|
|
165
|
+
_logger.info(`[pi-free] Loaded with ${registry.size} providers`);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Re-export registry helpers so consumers don't need deep imports
|
|
169
|
+
export {
|
|
170
|
+
applyGlobalFilter,
|
|
171
|
+
getGlobalFreeOnly,
|
|
172
|
+
getProviderRegistry,
|
|
173
|
+
isFreeModel,
|
|
174
|
+
registerWithGlobalToggle,
|
|
175
|
+
};
|
package/lib/built-in-toggle.ts
CHANGED
|
@@ -60,6 +60,7 @@ export function setupBuiltInProviderToggles(pi: ExtensionAPI): void {
|
|
|
60
60
|
for (const config of BUILT_IN_TOGGLE_PROVIDERS) {
|
|
61
61
|
registerToggleCommand(pi, config);
|
|
62
62
|
}
|
|
63
|
+
setupStatusBar(pi);
|
|
63
64
|
commandsRegistered = true;
|
|
64
65
|
}
|
|
65
66
|
|
|
@@ -79,7 +80,9 @@ export function setupBuiltInProviderToggles(pi: ExtensionAPI): void {
|
|
|
79
80
|
if (providerModels.length === 0) continue;
|
|
80
81
|
|
|
81
82
|
const allModels = providerModels.map(modelToProviderConfig);
|
|
82
|
-
const freeModels = allModels.filter(
|
|
83
|
+
const freeModels = allModels.filter((m) =>
|
|
84
|
+
isFreeModel({ ...m, provider: config.id }, allModels),
|
|
85
|
+
);
|
|
83
86
|
|
|
84
87
|
const baseUrl = providerModels[0].baseUrl;
|
|
85
88
|
const api = providerModels[0].api;
|
|
@@ -178,6 +181,42 @@ function modelToProviderConfig(m: Model<Api>): ProviderModelConfig {
|
|
|
178
181
|
};
|
|
179
182
|
}
|
|
180
183
|
|
|
184
|
+
// =============================================================================
|
|
185
|
+
// Status bar for provider selection
|
|
186
|
+
// =============================================================================
|
|
187
|
+
|
|
188
|
+
function setupStatusBar(pi: ExtensionAPI): void {
|
|
189
|
+
pi.on("model_select", (_event, ctx) => {
|
|
190
|
+
const selected = _event.model?.provider;
|
|
191
|
+
|
|
192
|
+
// Clear status for all built-in toggle providers
|
|
193
|
+
for (const config of BUILT_IN_TOGGLE_PROVIDERS) {
|
|
194
|
+
if (selected !== config.id) {
|
|
195
|
+
ctx.ui.setStatus(`${config.id}-status`, undefined);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
if (!selected) return;
|
|
200
|
+
|
|
201
|
+
const state = providerStates.get(selected);
|
|
202
|
+
if (!state) return;
|
|
203
|
+
|
|
204
|
+
const free = state.stored.free.length;
|
|
205
|
+
const total = state.stored.all.length;
|
|
206
|
+
const paid = total - free;
|
|
207
|
+
const mode = state.toggleState.getCurrentMode();
|
|
208
|
+
let status: string;
|
|
209
|
+
if (paid === 0) {
|
|
210
|
+
status = `${selected}: ${free} free models`;
|
|
211
|
+
} else if (mode === "all") {
|
|
212
|
+
status = `${selected}: ${total} models (free + paid)`;
|
|
213
|
+
} else {
|
|
214
|
+
status = `${selected}: ${free} free \u00b7 ${paid} paid`;
|
|
215
|
+
}
|
|
216
|
+
ctx.ui.setStatus(`${selected}-status`, status);
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
|
|
181
220
|
function getApiKeyEnvForProvider(providerId: string): string {
|
|
182
221
|
const envMap: Record<string, string> = {
|
|
183
222
|
opencode: "OPENCODE_API_KEY",
|