pi-free 2.0.8 → 2.0.10

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/index.ts CHANGED
@@ -1,239 +1,242 @@
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
- * - Codestral: Mistral's code-focused model via codestral.mistral.ai (free tier)
14
- * - DeepInfra: AI inference cloud ($5 trial credit)
15
- * - SambaNova: Fast inference on RDU hardware (free tier, no credit card)
16
- * - LLM7: AI gateway (free default/fast selectors)
17
- */
18
-
19
- import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
20
- import { setupBuiltInProviderToggles } from "./lib/built-in-toggle.ts";
21
- import { createLogger } from "./lib/logger.ts";
22
- import {
23
- processQuotaResponse,
24
- formatQuotaStatus,
25
- } from "./lib/quota-monitor.ts";
26
- import {
27
- applyGlobalFilter,
28
- getGlobalFreeOnly,
29
- getProviderRegistry,
30
- isFreeModel,
31
- registerWithGlobalToggle,
32
- } from "./lib/registry.ts";
33
- // Import unique provider extensions (only providers NOT built into pi)
34
- import cline from "./providers/cline/cline.ts";
35
- import codestral from "./providers/codestral/codestral.ts";
36
- import crofai from "./providers/crofai/crofai.ts";
37
- import kilo from "./providers/kilo/kilo.ts";
38
- import llm7 from "./providers/llm7/llm7.ts";
39
- import deepinfra from "./providers/deepinfra/deepinfra.ts";
40
- import sambanova from "./providers/sambanova/sambanova.ts";
41
- import nvidia from "./providers/nvidia/nvidia.ts";
42
- import ollama from "./providers/ollama/ollama.ts";
43
- import zenmux from "./providers/zenmux/zenmux.ts";
44
-
45
- const _logger = createLogger("pi-free");
46
-
47
- // =============================================================================
48
- // Global Commands
49
- // =============================================================================
50
-
51
- function setupGlobalCommands(pi: ExtensionAPI) {
52
- // /toggle-free - Global free-only mode toggle
53
- pi.registerCommand("toggle-free", {
54
- description: "Toggle global free-only mode for all providers",
55
- handler: async (_args, ctx) => {
56
- const current = getGlobalFreeOnly();
57
- const next = !current;
58
- applyGlobalFilter(pi, next);
59
-
60
- const registry = getProviderRegistry();
61
- const providerCount = registry.size;
62
-
63
- if (next) {
64
- const totalFree = [...registry.values()].reduce(
65
- (sum, e) => sum + e.stored.free.length,
66
- 0,
67
- );
68
- ctx.ui.notify(
69
- `Free-only mode: ON (${totalFree} free models across ${providerCount} providers)`,
70
- "info",
71
- );
72
- } else {
73
- const totalAll = [...registry.values()].reduce(
74
- (sum, e) => sum + (e.stored.all.length || e.stored.free.length),
75
- 0,
76
- );
77
- ctx.ui.notify(
78
- `Free-only mode: OFF (all ${totalAll} models visible across ${providerCount} providers)`,
79
- "info",
80
- );
81
- }
82
- },
83
- });
84
-
85
- // /free-providers - Show free model counts by provider
86
- pi.registerCommand("free-providers", {
87
- description: "Show free/paid model counts for all pi-free providers",
88
- handler: async (_args, ctx) => {
89
- const lines = ["📊 Pi-Free Providers:", ""];
90
- const registry = getProviderRegistry();
91
-
92
- // Providers known to not expose pricing via API (all models show as "free")
93
- // OpenRouter and OpenCode expose actual pricing
94
- const noPricingApi = new Set([
95
- "mistral",
96
- "xai",
97
- "huggingface",
98
- "groq",
99
- "cerebras",
100
- ]);
101
- // Freemium providers - all models share a free tier quota
102
- const freemiumProviders = new Set([
103
- "nvidia",
104
- "sambanova",
105
- "ollama-cloud",
106
- ]);
107
- // Trial credit providers - one-time credits, otherwise paid
108
- const trialCreditProviders = new Set(["deepinfra"]);
109
-
110
- for (const [id, entry] of registry) {
111
- const free = entry.stored.free.length;
112
- const all = entry.stored.all.length || free;
113
- const indicator = entry.hasKey ? "🔑" : "🆓";
114
- const paid = all - free;
115
-
116
- if (freemiumProviders.has(id)) {
117
- // Freemium: all models share a free tier (e.g., 1,000 reqs/month)
118
- lines.push(`${indicator} ${id}: ${all} models (freemium)`);
119
- } else if (trialCreditProviders.has(id)) {
120
- // Trial credit: one-time credits, otherwise paid
121
- lines.push(`${indicator} ${id}: ${all} models ($5 trial credit)`);
122
- } else if (noPricingApi.has(id)) {
123
- // Provider doesn't expose pricing - can't determine free vs paid
124
- lines.push(
125
- `${indicator} ${id}: ${all} models (pricing not exposed by API)`,
126
- );
127
- } else if (paid === 0 && free > 0) {
128
- // All models are actually free
129
- lines.push(`${indicator} ${id}: ${free} free models`);
130
- } else {
131
- // Mix of free and paid
132
- lines.push(
133
- `${indicator} ${id}: ${free} free / ${paid} paid (${all} total)`,
134
- );
135
- }
136
- }
137
-
138
- if (registry.size === 0) {
139
- lines.push("(No providers registered yet)");
140
- }
141
-
142
- ctx.ui.notify(lines.join("\n"), "info");
143
- },
144
- });
145
- }
146
-
147
- // =============================================================================
148
- // Quota Monitoring
149
- // =============================================================================
150
-
151
- function setupQuotaMonitoring(pi: ExtensionAPI) {
152
- // Capture rate-limit headers from every provider response
153
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
154
- (pi as any).on(
155
- "after_provider_response",
156
- (event: { status: number; headers: Record<string, string> }, ctx: any) => {
157
- const providerId = ctx.model?.provider;
158
- if (!providerId) return;
159
-
160
- processQuotaResponse(providerId, event.headers);
161
-
162
- // Update status bar with quota for the active provider
163
- const status = formatQuotaStatus(providerId);
164
- if (status) {
165
- ctx.ui.setStatus("quota", status);
166
- }
167
- },
168
- );
169
-
170
- // Clear quota status when switching away from a provider
171
- pi.on("model_select", (_event, ctx) => {
172
- const providerId = ctx.model?.provider;
173
- if (!providerId) {
174
- ctx.ui.setStatus("quota", undefined);
175
- return;
176
- }
177
- // Show cached quota on provider switch (if still fresh)
178
- const status = formatQuotaStatus(providerId);
179
- ctx.ui.setStatus("quota", status);
180
- });
181
- }
182
-
183
- // =============================================================================
184
- // Main Entry Point
185
- // =============================================================================
186
-
187
- export default async function piFreeEntry(pi: ExtensionAPI) {
188
- const globalFreeOnly = getGlobalFreeOnly();
189
- _logger.info(`[pi-free] Initializing (global free-only: ${globalFreeOnly})`);
190
-
191
- // Setup global commands first
192
- setupGlobalCommands(pi);
193
-
194
- // Setup quota monitoring (passive, no extra API calls)
195
- setupQuotaMonitoring(pi);
196
-
197
- // Load all unique providers
198
- // Each provider will register itself with the global toggle system
199
- await Promise.allSettled([
200
- nvidia(pi),
201
- kilo(pi),
202
- ollama(pi),
203
- cline(pi),
204
- zenmux(pi),
205
- crofai(pi),
206
- codestral(pi),
207
- llm7(pi),
208
- deepinfra(pi),
209
- sambanova(pi),
210
- ]);
211
-
212
- // Setup dynamic built-in providers (Mistral, Groq, Cerebras, xAI, Hugging Face)
213
- // These only activate if the user has configured API keys (OpenRouter works without key too)
214
- const { setupDynamicBuiltInProviders } = await import(
215
- "./providers/dynamic-built-in/index.ts"
216
- );
217
- await setupDynamicBuiltInProviders(pi);
218
-
219
- // Setup toggles for pi's built-in providers (e.g., OpenCode)
220
- setupBuiltInProviderToggles(pi);
221
-
222
- // Apply initial global filter if free-only mode is enabled
223
- if (globalFreeOnly) {
224
- _logger.info("[pi-free] Applying initial free-only filter");
225
- applyGlobalFilter(pi, true);
226
- }
227
-
228
- const registry = getProviderRegistry();
229
- _logger.info(`[pi-free] Loaded with ${registry.size} providers`);
230
- }
231
-
232
- // Re-export registry helpers so consumers don't need deep imports
233
- export {
234
- applyGlobalFilter,
235
- getGlobalFreeOnly,
236
- getProviderRegistry,
237
- isFreeModel,
238
- registerWithGlobalToggle,
239
- };
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
+ * - Codestral: Mistral's code-focused model via codestral.mistral.ai (free tier)
14
+ * - DeepInfra: AI inference cloud ($5 trial credit)
15
+ * - SambaNova: Fast inference on RDU hardware (free tier, no credit card)
16
+ * - Together: Fast inference on 200+ open-source models ($1 trial credit)
17
+ * - LLM7: AI gateway (free default/fast selectors)
18
+ */
19
+
20
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
21
+ import { setupBuiltInProviderToggles } from "./lib/built-in-toggle.ts";
22
+ import { createLogger } from "./lib/logger.ts";
23
+ import {
24
+ processQuotaResponse,
25
+ formatQuotaStatus,
26
+ } from "./lib/quota-monitor.ts";
27
+ import {
28
+ applyGlobalFilter,
29
+ getGlobalFreeOnly,
30
+ getProviderRegistry,
31
+ isFreeModel,
32
+ registerWithGlobalToggle,
33
+ } from "./lib/registry.ts";
34
+ // Import unique provider extensions (only providers NOT built into pi)
35
+ import cline from "./providers/cline/cline.ts";
36
+ import codestral from "./providers/codestral/codestral.ts";
37
+ import crofai from "./providers/crofai/crofai.ts";
38
+ import kilo from "./providers/kilo/kilo.ts";
39
+ import llm7 from "./providers/llm7/llm7.ts";
40
+ import deepinfra from "./providers/deepinfra/deepinfra.ts";
41
+ import sambanova from "./providers/sambanova/sambanova.ts";
42
+ import together from "./providers/together/together.ts";
43
+ import nvidia from "./providers/nvidia/nvidia.ts";
44
+ import ollama from "./providers/ollama/ollama.ts";
45
+ import zenmux from "./providers/zenmux/zenmux.ts";
46
+
47
+ const _logger = createLogger("pi-free");
48
+
49
+ // =============================================================================
50
+ // Global Commands
51
+ // =============================================================================
52
+
53
+ function setupGlobalCommands(pi: ExtensionAPI) {
54
+ // /toggle-free - Global free-only mode toggle
55
+ pi.registerCommand("toggle-free", {
56
+ description: "Toggle global free-only mode for all providers",
57
+ handler: async (_args, ctx) => {
58
+ const current = getGlobalFreeOnly();
59
+ const next = !current;
60
+ applyGlobalFilter(pi, next);
61
+
62
+ const registry = getProviderRegistry();
63
+ const providerCount = registry.size;
64
+
65
+ if (next) {
66
+ const totalFree = [...registry.values()].reduce(
67
+ (sum, e) => sum + e.stored.free.length,
68
+ 0,
69
+ );
70
+ ctx.ui.notify(
71
+ `Free-only mode: ON (${totalFree} free models across ${providerCount} providers)`,
72
+ "info",
73
+ );
74
+ } else {
75
+ const totalAll = [...registry.values()].reduce(
76
+ (sum, e) => sum + (e.stored.all.length || e.stored.free.length),
77
+ 0,
78
+ );
79
+ ctx.ui.notify(
80
+ `Free-only mode: OFF (all ${totalAll} models visible across ${providerCount} providers)`,
81
+ "info",
82
+ );
83
+ }
84
+ },
85
+ });
86
+
87
+ // /free-providers - Show free model counts by provider
88
+ pi.registerCommand("free-providers", {
89
+ description: "Show free/paid model counts for all pi-free providers",
90
+ handler: async (_args, ctx) => {
91
+ const lines = ["📊 Pi-Free Providers:", ""];
92
+ const registry = getProviderRegistry();
93
+
94
+ // Providers known to not expose pricing via API (all models show as "free")
95
+ // OpenRouter and OpenCode expose actual pricing
96
+ const noPricingApi = new Set([
97
+ "mistral",
98
+ "xai",
99
+ "huggingface",
100
+ "groq",
101
+ "cerebras",
102
+ ]);
103
+ // Freemium providers - all models share a free tier quota
104
+ const freemiumProviders = new Set([
105
+ "nvidia",
106
+ "sambanova",
107
+ "ollama-cloud",
108
+ ]);
109
+ // Trial credit providers - one-time credits, otherwise paid
110
+ const trialCreditProviders = new Set(["deepinfra"]);
111
+
112
+ for (const [id, entry] of registry) {
113
+ const free = entry.stored.free.length;
114
+ const all = entry.stored.all.length || free;
115
+ const indicator = entry.hasKey ? "🔑" : "🆓";
116
+ const paid = all - free;
117
+
118
+ if (freemiumProviders.has(id)) {
119
+ // Freemium: all models share a free tier (e.g., 1,000 reqs/month)
120
+ lines.push(`${indicator} ${id}: ${all} models (freemium)`);
121
+ } else if (trialCreditProviders.has(id)) {
122
+ // Trial credit: one-time credits, otherwise paid
123
+ lines.push(`${indicator} ${id}: ${all} models ($5 trial credit)`);
124
+ } else if (noPricingApi.has(id)) {
125
+ // Provider doesn't expose pricing - can't determine free vs paid
126
+ lines.push(
127
+ `${indicator} ${id}: ${all} models (pricing not exposed by API)`,
128
+ );
129
+ } else if (paid === 0 && free > 0) {
130
+ // All models are actually free
131
+ lines.push(`${indicator} ${id}: ${free} free models`);
132
+ } else {
133
+ // Mix of free and paid
134
+ lines.push(
135
+ `${indicator} ${id}: ${free} free / ${paid} paid (${all} total)`,
136
+ );
137
+ }
138
+ }
139
+
140
+ if (registry.size === 0) {
141
+ lines.push("(No providers registered yet)");
142
+ }
143
+
144
+ ctx.ui.notify(lines.join("\n"), "info");
145
+ },
146
+ });
147
+ }
148
+
149
+ // =============================================================================
150
+ // Quota Monitoring
151
+ // =============================================================================
152
+
153
+ function setupQuotaMonitoring(pi: ExtensionAPI) {
154
+ // Capture rate-limit headers from every provider response
155
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
156
+ (pi as any).on(
157
+ "after_provider_response",
158
+ (event: { status: number; headers: Record<string, string> }, ctx: any) => {
159
+ const providerId = ctx.model?.provider;
160
+ if (!providerId) return;
161
+
162
+ processQuotaResponse(providerId, event.headers);
163
+
164
+ // Update status bar with quota for the active provider
165
+ const status = formatQuotaStatus(providerId);
166
+ if (status) {
167
+ ctx.ui.setStatus("quota", status);
168
+ }
169
+ },
170
+ );
171
+
172
+ // Clear quota status when switching away from a provider
173
+ pi.on("model_select", (_event, ctx) => {
174
+ const providerId = ctx.model?.provider;
175
+ if (!providerId) {
176
+ ctx.ui.setStatus("quota", undefined);
177
+ return;
178
+ }
179
+ // Show cached quota on provider switch (if still fresh)
180
+ const status = formatQuotaStatus(providerId);
181
+ ctx.ui.setStatus("quota", status);
182
+ });
183
+ }
184
+
185
+ // =============================================================================
186
+ // Main Entry Point
187
+ // =============================================================================
188
+
189
+ export default async function piFreeEntry(pi: ExtensionAPI) {
190
+ const globalFreeOnly = getGlobalFreeOnly();
191
+ _logger.info(`[pi-free] Initializing (global free-only: ${globalFreeOnly})`);
192
+
193
+ // Setup global commands first
194
+ setupGlobalCommands(pi);
195
+
196
+ // Setup quota monitoring (passive, no extra API calls)
197
+ setupQuotaMonitoring(pi);
198
+
199
+ // Load all unique providers
200
+ // Each provider will register itself with the global toggle system
201
+ await Promise.allSettled([
202
+ nvidia(pi),
203
+ kilo(pi),
204
+ ollama(pi),
205
+ cline(pi),
206
+ zenmux(pi),
207
+ crofai(pi),
208
+ codestral(pi),
209
+ llm7(pi),
210
+ deepinfra(pi),
211
+ sambanova(pi),
212
+ together(pi),
213
+ ]);
214
+
215
+ // Setup dynamic built-in providers (Mistral, Groq, Cerebras, xAI, Hugging Face)
216
+ // These only activate if the user has configured API keys (OpenRouter works without key too)
217
+ const { setupDynamicBuiltInProviders } = await import(
218
+ "./providers/dynamic-built-in/index.ts"
219
+ );
220
+ await setupDynamicBuiltInProviders(pi);
221
+
222
+ // Setup toggles for pi's built-in providers (e.g., OpenCode)
223
+ setupBuiltInProviderToggles(pi);
224
+
225
+ // Apply initial global filter if free-only mode is enabled
226
+ if (globalFreeOnly) {
227
+ _logger.info("[pi-free] Applying initial free-only filter");
228
+ applyGlobalFilter(pi, true);
229
+ }
230
+
231
+ const registry = getProviderRegistry();
232
+ _logger.info(`[pi-free] Loaded with ${registry.size} providers`);
233
+ }
234
+
235
+ // Re-export registry helpers so consumers don't need deep imports
236
+ export {
237
+ applyGlobalFilter,
238
+ getGlobalFreeOnly,
239
+ getProviderRegistry,
240
+ isFreeModel,
241
+ registerWithGlobalToggle,
242
+ };
@@ -11,11 +11,11 @@
11
11
  * Usage: /toggle-opencode
12
12
  */
13
13
 
14
- import type { Api, Model } from "@mariozechner/pi-ai";
14
+ import type { Api, Model } from "@earendil-works/pi-ai";
15
15
  import type {
16
16
  ExtensionAPI,
17
17
  ProviderModelConfig,
18
- } from "@mariozechner/pi-coding-agent";
18
+ } from "@earendil-works/pi-coding-agent";
19
19
  import { getOpencodeShowPaid, getOpenrouterShowPaid } from "../config.ts";
20
20
  import { createLogger } from "./logger.ts";
21
21
  import { isFreeModel, registerWithGlobalToggle } from "./registry.ts";
@@ -4,7 +4,7 @@
4
4
  * Used for failover when providers hit rate limits.
5
5
  */
6
6
 
7
- import type { Model } from "@mariozechner/pi-ai";
7
+ import type { Model } from "@earendil-works/pi-ai";
8
8
  import type { ProviderModelConfig } from "./types.ts";
9
9
 
10
10
  export interface ModelInfo {
@@ -1,20 +1,20 @@
1
- /**
2
- * Model name enhancement helper
3
- * Adds Coding Index scores to model names for display in /model
4
- */
5
-
6
- import type { ProviderModelConfig } from "@mariozechner/pi-coding-agent";
7
- import { enhanceModelNameWithCodingIndex } from "../provider-failover/benchmark-lookup.ts";
8
-
9
- /**
10
- * Enhance model names with Coding Index scores
11
- * Use this before registering providers to show CI in /model list
12
- */
13
- export function enhanceModelsWithCodingIndex(
14
- models: ProviderModelConfig[],
15
- ): ProviderModelConfig[] {
16
- return models.map((m) => ({
17
- ...m,
18
- name: enhanceModelNameWithCodingIndex(m.name, m.id),
19
- }));
20
- }
1
+ /**
2
+ * Model name enhancement helper
3
+ * Adds Coding Index scores to model names for display in /model
4
+ */
5
+
6
+ import type { ProviderModelConfig } from "@earendil-works/pi-coding-agent";
7
+ import { enhanceModelNameWithCodingIndex } from "../provider-failover/benchmark-lookup.ts";
8
+
9
+ /**
10
+ * Enhance model names with Coding Index scores
11
+ * Use this before registering providers to show CI in /model list
12
+ */
13
+ export function enhanceModelsWithCodingIndex(
14
+ models: ProviderModelConfig[],
15
+ ): ProviderModelConfig[] {
16
+ return models.map((m) => ({
17
+ ...m,
18
+ name: enhanceModelNameWithCodingIndex(m.name, m.id),
19
+ }));
20
+ }
@@ -1,4 +1,4 @@
1
- import type { ProviderModelConfig } from "@mariozechner/pi-coding-agent";
1
+ import type { ProviderModelConfig } from "@earendil-works/pi-coding-agent";
2
2
 
3
3
  export interface ProviderModelIdentity {
4
4
  id: string;
package/lib/registry.ts CHANGED
@@ -8,7 +8,7 @@
8
8
  import type {
9
9
  ExtensionAPI,
10
10
  ProviderModelConfig,
11
- } from "@mariozechner/pi-coding-agent";
11
+ } from "@earendil-works/pi-coding-agent";
12
12
  import { getFreeOnly, saveConfig } from "../config.ts";
13
13
  import { createLogger } from "./logger.ts";
14
14