pi-free 2.2.1 → 2.2.2

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 CHANGED
@@ -7,6 +7,22 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [2.2.2] - 2026-06-19
11
+
12
+ ### Refactored
13
+
14
+ - `config.ts`: Introduced `PROVIDER_META` table that pairs each
15
+ provider's ID, env-var prefix, and typed config key. `getProviderShowPaid`
16
+ now delegates to a generic `resolveShowPaidForProvider` resolver
17
+ instead of a 17-case switch. New `PROVIDER_OPENROUTER`, `PROVIDER_OPENCODE`,
18
+ and `PROVIDER_FASTROUTER` constants added to `constants.ts` and used
19
+ in the table (DRYKISS DRY/Architecture findings).
20
+ - `index.ts`: Wrap dynamic built-in provider import in `try/catch`
21
+ with full error+stack logging to both `~/.pi/free.log` and stderr
22
+ (DRYKISS Resilience finding).
23
+ - `providers/bai/bai.ts`: Improve startup-failure message to point users
24
+ at `~/.pi/free.log` and their API key (DRYKISS Resilience finding).
25
+
10
26
  ## [2.2.1] - 2026-06-19
11
27
 
12
28
  ### Security
package/config.ts CHANGED
@@ -11,11 +11,33 @@
11
11
 
12
12
  import { chmodSync, existsSync, readFileSync, writeFileSync } from "node:fs";
13
13
  import { join } from "node:path";
14
+ import {
15
+ PROVIDER_BAI,
16
+ PROVIDER_CLINE,
17
+ PROVIDER_FASTROUTER,
18
+ PROVIDER_KILO,
19
+ PROVIDER_OLLAMA,
20
+ PROVIDER_OPENCODE,
21
+ PROVIDER_OPENROUTER,
22
+ PROVIDER_ROUTEWAY,
23
+ PROVIDER_TOKENROUTER,
24
+ PROVIDER_ZENMUX,
25
+ PROVIDER_CROFAI,
26
+ PROVIDER_CODESTRAL,
27
+ PROVIDER_LLM7,
28
+ PROVIDER_DEEPINFRA,
29
+ PROVIDER_SAMBANOVA,
30
+ PROVIDER_TOGETHER,
31
+ PROVIDER_NOVITA,
32
+ } from "./constants.ts";
14
33
  export {
15
34
  PROVIDER_BAI,
16
35
  PROVIDER_CLINE,
36
+ PROVIDER_FASTROUTER,
17
37
  PROVIDER_KILO,
18
38
  PROVIDER_MODAL,
39
+ PROVIDER_OPENCODE,
40
+ PROVIDER_OPENROUTER,
19
41
  PROVIDER_QWEN,
20
42
  PROVIDER_ROUTEWAY,
21
43
  PROVIDER_TOKENROUTER,
@@ -219,6 +241,56 @@ function resolveBool(envKey: string, fileVal?: boolean): boolean {
219
241
  return fileVal === true;
220
242
  }
221
243
 
244
+ // =============================================================================
245
+ // Per-provider metadata table
246
+ // Adding a new provider only requires a single entry here plus the
247
+ // corresponding field in the PiFreeConfig interface and CONFIG_TEMPLATE.
248
+ // Each entry pairs the provider ID with its env-var prefix (used for both
249
+ // the API key and show_paid flag) and the typed key on PiFreeConfig.
250
+ // =============================================================================
251
+
252
+ interface ProviderMeta {
253
+ id: string;
254
+ /** Env var prefix, e.g. "KILO" => KILO_SHOW_PAID and KILO_API_KEY */
255
+ prefix: string;
256
+ /** Typed accessor returning the show_paid value from PiFreeConfig */
257
+ showPaidKey: keyof PiFreeConfig;
258
+ }
259
+
260
+ const PROVIDER_META: readonly ProviderMeta[] = [
261
+ { id: PROVIDER_KILO, prefix: "KILO", showPaidKey: "kilo_show_paid" },
262
+ { id: PROVIDER_CLINE, prefix: "CLINE", showPaidKey: "cline_show_paid" },
263
+ { id: PROVIDER_ZENMUX, prefix: "ZENMUX", showPaidKey: "zenmux_show_paid" },
264
+ { id: PROVIDER_CROFAI, prefix: "CROFAI", showPaidKey: "crofai_show_paid" },
265
+ { id: PROVIDER_CODESTRAL, prefix: "CODESTRAL", showPaidKey: "codestral_show_paid" },
266
+ { id: PROVIDER_LLM7, prefix: "LLM7", showPaidKey: "llm7_show_paid" },
267
+ { id: PROVIDER_DEEPINFRA, prefix: "DEEPINFRA", showPaidKey: "deepinfra_show_paid" },
268
+ { id: PROVIDER_SAMBANOVA, prefix: "SAMBANOVA", showPaidKey: "sambanova_show_paid" },
269
+ { id: PROVIDER_TOGETHER, prefix: "TOGETHER", showPaidKey: "together_show_paid" },
270
+ { id: PROVIDER_NOVITA, prefix: "NOVITA", showPaidKey: "novita_show_paid" },
271
+ { id: PROVIDER_ROUTEWAY, prefix: "ROUTEWAY", showPaidKey: "routeway_show_paid" },
272
+ { id: PROVIDER_TOKENROUTER, prefix: "TOKENROUTER", showPaidKey: "tokenrouter_show_paid" },
273
+ { id: PROVIDER_BAI, prefix: "BAI", showPaidKey: "bai_show_paid" },
274
+ { id: PROVIDER_FASTROUTER, prefix: "FASTROUTER", showPaidKey: "fastrouter_show_paid" },
275
+ { id: PROVIDER_OLLAMA, prefix: "OLLAMA", showPaidKey: "ollama_show_paid" },
276
+ { id: PROVIDER_OPENROUTER, prefix: "OPENROUTER", showPaidKey: "openrouter_show_paid" },
277
+ { id: PROVIDER_OPENCODE, prefix: "OPENCODE", showPaidKey: "opencode_show_paid" },
278
+ ];
279
+
280
+ const PROVIDER_META_BY_ID = new Map(PROVIDER_META.map((m) => [m.id, m]));
281
+
282
+ /**
283
+ * Generic show_paid resolver backed by PROVIDER_META. Returns false
284
+ * for unknown provider IDs (matches the previous switch default).
285
+ */
286
+ function resolveShowPaidForProvider(providerId: string): boolean {
287
+ const meta = PROVIDER_META_BY_ID.get(providerId);
288
+ if (!meta) return false;
289
+ const cfg = loadConfigFile();
290
+ const fileVal = cfg[meta.showPaidKey];
291
+ return resolveBool(`${meta.prefix}_SHOW_PAID`, fileVal as boolean | undefined);
292
+ }
293
+
222
294
  // =============================================================================
223
295
  // Per-provider paid-model flags (getters so toggles reflect immediately)
224
296
  // =============================================================================
@@ -310,44 +382,7 @@ export function getOpencodeShowPaid(): boolean {
310
382
  }
311
383
 
312
384
  export function getProviderShowPaid(providerId: string): boolean {
313
- switch (providerId) {
314
- case "kilo":
315
- return getKiloShowPaid();
316
- case "cline":
317
- return getClineShowPaid();
318
- case "zenmux":
319
- return getZenmuxShowPaid();
320
- case "crofai":
321
- return getCrofaiShowPaid();
322
- case "codestral":
323
- return getCodestralShowPaid();
324
- case "llm7":
325
- return getLlm7ShowPaid();
326
- case "deepinfra":
327
- return getDeepinfraShowPaid();
328
- case "sambanova":
329
- return getSambanovaShowPaid();
330
- case "together":
331
- return getTogetherShowPaid();
332
- case "novita":
333
- return getNovitaShowPaid();
334
- case "routeway":
335
- return getRoutewayShowPaid();
336
- case "tokenrouter":
337
- return getTokenrouterShowPaid();
338
- case "bai":
339
- return getBaiShowPaid();
340
- case "fastrouter":
341
- return getFastrouterShowPaid();
342
- case "ollama-cloud":
343
- return getOllamaShowPaid();
344
- case "openrouter":
345
- return getOpenrouterShowPaid();
346
- case "opencode":
347
- return getOpencodeShowPaid();
348
- default:
349
- return false;
350
- }
385
+ return resolveShowPaidForProvider(providerId);
351
386
  }
352
387
 
353
388
  // =============================================================================
package/constants.ts CHANGED
@@ -26,6 +26,11 @@ export const PROVIDER_ROUTEWAY = "routeway";
26
26
  export const PROVIDER_TOKENROUTER = "tokenrouter";
27
27
  export const PROVIDER_BAI = "bai";
28
28
 
29
+ // Built-in pi providers that pi-free wraps with toggles
30
+ export const PROVIDER_OPENROUTER = "openrouter";
31
+ export const PROVIDER_OPENCODE = "opencode";
32
+ export const PROVIDER_FASTROUTER = "fastrouter";
33
+
29
34
  export const ALL_UNIQUE_PROVIDERS = [
30
35
  PROVIDER_KILO,
31
36
  PROVIDER_CLINE,
package/index.ts CHANGED
@@ -379,10 +379,22 @@ export default async function piFreeEntry(pi: ExtensionAPI) {
379
379
 
380
380
  // Setup dynamic built-in providers (Mistral, Groq, Cerebras, xAI, Hugging Face,
381
381
  // OpenRouter/OpenCode from Pi auth, and FastRouter public model discovery)
382
- const { setupDynamicBuiltInProviders } = await import(
383
- "./providers/dynamic-built-in/index.ts"
384
- );
385
- await setupDynamicBuiltInProviders(pi);
382
+ try {
383
+ const { setupDynamicBuiltInProviders } = await import(
384
+ "./providers/dynamic-built-in/index.ts"
385
+ );
386
+ await setupDynamicBuiltInProviders(pi);
387
+ } catch (err) {
388
+ // Dynamic providers are a best-effort enhancement — if the import
389
+ // or init fails (e.g. upstream API change), continue with the
390
+ // already-registered static providers rather than failing the whole
391
+ // extension load. Log full error (message + stack) to the structured
392
+ // log so the user can investigate, but never block startup.
393
+ _logger.error("[pi-free] Dynamic built-in providers failed to load", {
394
+ error: err instanceof Error ? err.message : String(err),
395
+ stack: err instanceof Error ? err.stack : undefined,
396
+ });
397
+ }
386
398
 
387
399
  // Setup toggles for pi's built-in providers (e.g., OpenCode)
388
400
  setupBuiltInProviderToggles(pi);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-free",
3
- "version": "2.2.1",
3
+ "version": "2.2.2",
4
4
  "type": "module",
5
5
  "description": "AI model providers for Pi with free model filtering and dynamic model fetching",
6
6
  "keywords": [
@@ -184,7 +184,12 @@ export default async function baiProvider(pi: ExtensionAPI) {
184
184
  const allModels = await fetchBaiModels(apiKey);
185
185
 
186
186
  if (allModels.length === 0) {
187
- _logger.warn("[bai] No text chat models available");
187
+ // Either the API failed (already logged inside fetchBaiModels) or
188
+ // the API returned zero text chat models. We can't tell the user
189
+ // which, but we can give a hint to check the log / their key.
190
+ _logger.warn(
191
+ "[bai] No text chat models available — verify BAI_API_KEY is valid and see ~/.pi/free.log for details",
192
+ );
188
193
  return;
189
194
  }
190
195