pi-free 2.0.6 → 2.0.8

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.
Files changed (36) hide show
  1. package/CHANGELOG.md +540 -421
  2. package/README.md +572 -495
  3. package/config.ts +58 -11
  4. package/constants.ts +12 -0
  5. package/index.ts +66 -2
  6. package/lib/model-detection.ts +1 -0
  7. package/lib/model-enhancer.ts +20 -20
  8. package/lib/open-browser.ts +1 -1
  9. package/lib/quota-monitor.ts +123 -0
  10. package/lib/types.ts +101 -101
  11. package/lib/util.ts +460 -351
  12. package/package.json +68 -68
  13. package/provider-failover/benchmark-lookup.ts +743 -702
  14. package/provider-failover/benchmarks-chunk-0.ts +48 -48
  15. package/provider-failover/benchmarks-chunk-1.ts +44 -44
  16. package/provider-failover/benchmarks-chunk-2.ts +39 -39
  17. package/provider-failover/benchmarks-chunk-3.ts +41 -41
  18. package/provider-failover/benchmarks-chunk-4.ts +33 -33
  19. package/providers/cline/cline-auth.ts +473 -473
  20. package/providers/cline/cline-models.ts +2 -2
  21. package/providers/cline/cline.ts +1 -1
  22. package/providers/codestral/codestral.ts +139 -0
  23. package/providers/crofai/crofai.ts +14 -85
  24. package/providers/deepinfra/deepinfra.ts +109 -0
  25. package/providers/kilo/kilo-auth.ts +155 -155
  26. package/providers/kilo/kilo.ts +1 -1
  27. package/providers/llm7/llm7.ts +156 -0
  28. package/providers/model-fetcher.ts +2 -2
  29. package/providers/nvidia/nvidia.ts +4 -4
  30. package/providers/ollama/ollama.ts +1 -1
  31. package/providers/opencode-session.ts +1 -1
  32. package/providers/qwen/qwen-models.ts +101 -101
  33. package/providers/qwen/qwen.ts +1 -1
  34. package/providers/sambanova/sambanova.ts +109 -0
  35. package/providers/zenmux/zenmux.ts +5 -2
  36. package/scripts/check-extensions.mjs +6 -4
@@ -43,8 +43,8 @@ function extractNameFromId(id: string): string {
43
43
  */
44
44
  function parsePricing(pricingStr: string | undefined): number {
45
45
  if (!pricingStr || pricingStr === "0") return 0;
46
- const parsed = parseFloat(pricingStr);
47
- return isNaN(parsed) ? 0 : parsed * 1_000_000; // Convert to per-million
46
+ const parsed = Number.parseFloat(pricingStr);
47
+ return Number.isNaN(parsed) ? 0 : parsed * 1_000_000; // Convert to per-million
48
48
  }
49
49
 
50
50
  /**
@@ -210,7 +210,7 @@ function shapeMessagesForCline(messages: any[]): any[] {
210
210
  // Extension entry point
211
211
  // =============================================================================
212
212
 
213
- export default async function (pi: ExtensionAPI) {
213
+ export default async function clineProvider(pi: ExtensionAPI) {
214
214
  let allModels = await fetchClineModels(false).catch((err) => {
215
215
  logWarning("cline", "Failed to fetch models at startup", err);
216
216
  return [];
@@ -0,0 +1,139 @@
1
+ /**
2
+ * Codestral Provider Extension
3
+ *
4
+ * Codestral is Mistral AI's code-focused model. This provider registers it
5
+ * through the Codestral-specific endpoint (codestral.mistral.ai) using
6
+ * the Mistral SDK (api: "mistral-conversations") — separate from the built-in
7
+ * "mistral" provider which uses api.mistral.ai.
8
+ *
9
+ * NOTE: Do NOT use api: "openai-completions" here. Codestral's API is
10
+ * Mistral-format (camelCase fields, maxTokens, no stream_options/store).
11
+ * The OpenAI completions adapter sends OpenAI-specific fields that Mistral
12
+ * rejects with HTTP 422 "Extra inputs are not permitted".
13
+ *
14
+ * Free tier (Experiment plan):
15
+ * - 2 req/min, 500K tokens/min, 1B tokens/month
16
+ * - No credit card — phone verification only
17
+ * - Sign up at https://console.mistral.ai/codestral
18
+ *
19
+ * Endpoints:
20
+ * Chat: https://codestral.mistral.ai/v1/chat/completions
21
+ * FIM: https://codestral.mistral.ai/v1/fim/completions (not used by pi)
22
+ *
23
+ * Setup:
24
+ * 1. Get API key from https://console.mistral.ai/codestral
25
+ * 2. Set CODESTRAL_API_KEY env var (or MISTRAL_API_KEY as fallback)
26
+ *
27
+ * Usage:
28
+ * pi install git:github.com/apmantza/pi-free
29
+ * # Set CODESTRAL_API_KEY env var
30
+ * # Models appear in /model selector as "codestral/codestral-latest"
31
+ */
32
+
33
+ import type {
34
+ ExtensionAPI,
35
+ ProviderModelConfig,
36
+ } from "@mariozechner/pi-coding-agent";
37
+ import {
38
+ getCodestralApiKey,
39
+ getCodestralShowPaid,
40
+ getMistralApiKey,
41
+ } from "../../config.ts";
42
+ import { BASE_URL_CODESTRAL, PROVIDER_CODESTRAL } from "../../constants.ts";
43
+ import { createLogger } from "../../lib/logger.ts";
44
+ import { registerWithGlobalToggle } from "../../lib/registry.ts";
45
+ import { enhanceWithCI, setupProvider } from "../../provider-helper.ts";
46
+
47
+ const _logger = createLogger("codestral");
48
+
49
+ // =============================================================================
50
+ // Model Definition
51
+ // =============================================================================
52
+
53
+ const CODESTRAL_MODEL: ProviderModelConfig = {
54
+ id: "codestral-latest",
55
+ name: "Codestral",
56
+ reasoning: false,
57
+ input: ["text"],
58
+ cost: {
59
+ input: 0.3,
60
+ output: 0.9,
61
+ cacheRead: 0,
62
+ cacheWrite: 0,
63
+ },
64
+ contextWindow: 256_000,
65
+ maxTokens: 4_096,
66
+ };
67
+
68
+ // =============================================================================
69
+ // Extension Entry Point
70
+ // =============================================================================
71
+
72
+ export default async function codestralProvider(pi: ExtensionAPI) {
73
+ // Try CODESTRAL_API_KEY first, fall back to MISTRAL_API_KEY
74
+ const apiKey = getCodestralApiKey() || getMistralApiKey();
75
+
76
+ if (!apiKey) {
77
+ _logger.info(
78
+ "[codestral] Skipping — neither CODESTRAL_API_KEY nor MISTRAL_API_KEY set",
79
+ );
80
+ return;
81
+ }
82
+
83
+ const keySource = getCodestralApiKey()
84
+ ? "CODESTRAL_API_KEY"
85
+ : "MISTRAL_API_KEY";
86
+ _logger.info(`[codestral] Using key from ${keySource}`);
87
+
88
+ const allModels = [CODESTRAL_MODEL];
89
+ const freeModels = allModels; // All $0.30/$0.90 — still accessible via Experiment free tier
90
+ const stored = { free: freeModels, all: allModels };
91
+
92
+ // Re-register function — uses mistral-conversations API (Mistral SDK)
93
+ // NOT openai-completions: Codestral uses the same API format as Mistral
94
+ // and rejects OpenAI-specific fields (stream_options, store, max_completion_tokens) with 422.
95
+ const reRegister = (models: typeof freeModels) => {
96
+ pi.registerProvider(PROVIDER_CODESTRAL, {
97
+ baseUrl: BASE_URL_CODESTRAL,
98
+ apiKey,
99
+ api: "mistral-conversations" as const,
100
+ models: enhanceWithCI(models, PROVIDER_CODESTRAL),
101
+ });
102
+ };
103
+
104
+ // Register with global toggle
105
+ registerWithGlobalToggle(PROVIDER_CODESTRAL, stored, reRegister, true);
106
+
107
+ // Setup provider (toggle command, status bar, error handling)
108
+ setupProvider(
109
+ pi,
110
+ {
111
+ providerId: PROVIDER_CODESTRAL,
112
+ initialShowPaid: getCodestralShowPaid(),
113
+ skipToggle: true, // Only one model, no toggle needed
114
+ reRegister: (models) => {
115
+ stored.free = models;
116
+ stored.all = models;
117
+ reRegister(models);
118
+ },
119
+ },
120
+ stored,
121
+ );
122
+
123
+ // Initial registration — uses mistral-conversations API (Mistral SDK)
124
+ reRegister(freeModels);
125
+
126
+ _logger.info(`[codestral] Registered codestral-latest via ${keySource}`);
127
+
128
+ // Status bar
129
+ pi.on("model_select", (_event, ctx) => {
130
+ if (_event.model?.provider !== PROVIDER_CODESTRAL) {
131
+ ctx.ui.setStatus(`${PROVIDER_CODESTRAL}-status`, undefined);
132
+ return;
133
+ }
134
+ ctx.ui.setStatus(
135
+ `${PROVIDER_CODESTRAL}-status`,
136
+ `codestral: 1 model (free tier) 🔑`,
137
+ );
138
+ });
139
+ }
@@ -15,94 +15,16 @@
15
15
  * # Models appear in /model selector
16
16
  */
17
17
 
18
- import type {
19
- ExtensionAPI,
20
- ProviderModelConfig,
21
- } from "@mariozechner/pi-coding-agent";
18
+ import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
22
19
  import { getCrofaiApiKey, getCrofaiShowPaid } from "../../config.ts";
23
- import {
24
- BASE_URL_CROFAI,
25
- DEFAULT_FETCH_TIMEOUT_MS,
26
- PROVIDER_CROFAI,
27
- } from "../../constants.ts";
20
+ import { BASE_URL_CROFAI, PROVIDER_CROFAI } from "../../constants.ts";
28
21
  import { createLogger } from "../../lib/logger.ts";
29
- import {
30
- getProxyModelCompat,
31
- isLikelyReasoningModel,
32
- } from "../../lib/provider-compat.ts";
33
22
  import { isFreeModel, registerWithGlobalToggle } from "../../lib/registry.ts";
34
- import { fetchWithRetry } from "../../lib/util.ts";
23
+ import { fetchOpenAICompatibleModels } from "../../lib/util.ts";
35
24
  import { createReRegister, setupProvider } from "../../provider-helper.ts";
36
25
 
37
26
  const _logger = createLogger("crofai");
38
27
 
39
- // =============================================================================
40
- // Fetch CrofAI models
41
- // =============================================================================
42
-
43
- interface CrofaiModel {
44
- id: string;
45
- object?: string;
46
- created?: number;
47
- owned_by?: string;
48
- }
49
-
50
- async function fetchCrofaiModels(
51
- apiKey: string,
52
- ): Promise<ProviderModelConfig[]> {
53
- _logger.info("[crofai] Fetching models from CrofAI API...");
54
-
55
- try {
56
- const response = await fetchWithRetry(
57
- `${BASE_URL_CROFAI}/models`,
58
- {
59
- headers: {
60
- Authorization: `Bearer ${apiKey}`,
61
- "Content-Type": "application/json",
62
- },
63
- },
64
- 3,
65
- 1000,
66
- DEFAULT_FETCH_TIMEOUT_MS,
67
- );
68
-
69
- if (!response.ok) {
70
- throw new Error(`CrofAI API error: ${response.status}`);
71
- }
72
-
73
- const data = (await response.json()) as { data?: CrofaiModel[] };
74
- const models = data.data ?? [];
75
-
76
- _logger.info(`[crofai] Fetched ${models.length} models`);
77
-
78
- return models
79
- .filter((m) => m.id) // Filter out any empty entries
80
- .map((m) => {
81
- const name = m.id.split("/").pop() || m.id;
82
- return {
83
- id: m.id,
84
- name,
85
- reasoning: isLikelyReasoningModel({ id: m.id, name }),
86
- input: ["text"],
87
- cost: {
88
- input: 0, // CrofAI doesn't expose pricing via API
89
- output: 0,
90
- cacheRead: 0,
91
- cacheWrite: 0,
92
- },
93
- contextWindow: 128000, // Default, varies by model
94
- maxTokens: 4096,
95
- compat: getProxyModelCompat({ id: m.id, name }),
96
- } satisfies ProviderModelConfig;
97
- });
98
- } catch (error) {
99
- _logger.error("[crofai] Failed to fetch models:", {
100
- error: error instanceof Error ? error.message : String(error),
101
- });
102
- return [];
103
- }
104
- }
105
-
106
28
  // =============================================================================
107
29
  // Extension Entry Point
108
30
  // =============================================================================
@@ -117,8 +39,12 @@ export default async function crofaiProvider(pi: ExtensionAPI) {
117
39
  return;
118
40
  }
119
41
 
120
- // Fetch models
121
- const allModels = await fetchCrofaiModels(apiKey);
42
+ // Fetch models via shared OpenAI-compatible helper
43
+ const allModels = await fetchOpenAICompatibleModels(
44
+ "crofai",
45
+ BASE_URL_CROFAI,
46
+ apiKey,
47
+ );
122
48
 
123
49
  if (allModels.length === 0) {
124
50
  _logger.warn("[crofai] No models available");
@@ -165,6 +91,9 @@ export default async function crofaiProvider(pi: ExtensionAPI) {
165
91
  stored,
166
92
  );
167
93
 
168
- // Initial registration
169
- reRegister(freeModels);
94
+ // Initial registration — respect persisted toggle state
95
+ const showPaid = getCrofaiShowPaid();
96
+ const initialModels =
97
+ showPaid && stored.all.length > 0 ? stored.all : freeModels;
98
+ reRegister(initialModels);
170
99
  }
@@ -0,0 +1,109 @@
1
+ /**
2
+ * DeepInfra Provider Extension
3
+ *
4
+ * DeepInfra is an AI inference cloud with an OpenAI-compatible API for
5
+ * 100+ open-source models (Llama, DeepSeek, Mistral, Qwen, Mixtral, etc.).
6
+ *
7
+ * Free tier:
8
+ * - $5 one-time credit on signup (no credit card)
9
+ * - ~5M tokens, expires after 90 days
10
+ * - 60 RPM (varies by model)
11
+ *
12
+ * Paid: pay-per-token after credits exhaust
13
+ *
14
+ * Endpoint:
15
+ * Chat: https://api.deepinfra.com/v1/openai/chat/completions
16
+ *
17
+ * Setup:
18
+ * 1. Sign up at https://deepinfra.com/ (GitHub or email)
19
+ * 2. Get API key from https://deepinfra.com/dash/api_keys
20
+ * 3. Set DEEPINFRA_TOKEN env var (or add to ~/.pi/free.json)
21
+ *
22
+ * Usage:
23
+ * pi install git:github.com/apmantza/pi-free
24
+ * # Set DEEPINFRA_TOKEN env var
25
+ * # Models appear in /model selector as "deepinfra/meta-llama/..."
26
+ */
27
+
28
+ import type {
29
+ ExtensionAPI,
30
+ ProviderModelConfig,
31
+ } from "@mariozechner/pi-coding-agent";
32
+ import { getDeepinfraApiKey } from "../../config.ts";
33
+ import { BASE_URL_DEEPINFRA, PROVIDER_DEEPINFRA } from "../../constants.ts";
34
+ import { createLogger } from "../../lib/logger.ts";
35
+ import { registerWithGlobalToggle } from "../../lib/registry.ts";
36
+ import { fetchOpenAICompatibleModels } from "../../lib/util.ts";
37
+ import { createReRegister, setupProvider } from "../../provider-helper.ts";
38
+
39
+ const _logger = createLogger("deepinfra");
40
+
41
+ // =============================================================================
42
+ // Extension Entry Point
43
+ // =============================================================================
44
+
45
+ export default async function deepinfraProvider(pi: ExtensionAPI) {
46
+ const apiKey = getDeepinfraApiKey();
47
+
48
+ if (!apiKey) {
49
+ _logger.info(
50
+ "[deepinfra] Skipping — DEEPINFRA_TOKEN not set. Sign up at https://deepinfra.com/",
51
+ );
52
+ return;
53
+ }
54
+
55
+ // Fetch models via shared OpenAI-compatible helper
56
+ const allModels = await fetchOpenAICompatibleModels(
57
+ "deepinfra",
58
+ BASE_URL_DEEPINFRA,
59
+ apiKey,
60
+ { cost: { input: 0.3, output: 0.9 } },
61
+ );
62
+
63
+ if (allModels.length === 0) {
64
+ _logger.warn("[deepinfra] No models available");
65
+ return;
66
+ }
67
+
68
+ // DeepInfra is a trial credit provider — $5 one-time credit, no truly free models.
69
+ // All models are marked as paid. When free-only mode is ON, no models are shown.
70
+ // Toggle free-only OFF to see all models.
71
+ const freeModels: ProviderModelConfig[] = [];
72
+ const stored = { free: freeModels, all: allModels };
73
+
74
+ _logger.info(
75
+ `[deepinfra] Registered ${allModels.length} models (trial credit, 0 free)`,
76
+ );
77
+
78
+ // Create re-register function
79
+ const reRegister = createReRegister(pi, {
80
+ providerId: PROVIDER_DEEPINFRA,
81
+ baseUrl: BASE_URL_DEEPINFRA,
82
+ apiKey,
83
+ });
84
+
85
+ // Register with global toggle
86
+ registerWithGlobalToggle(PROVIDER_DEEPINFRA, stored, reRegister, true);
87
+
88
+ // Setup provider with toggle command
89
+ setupProvider(
90
+ pi,
91
+ {
92
+ providerId: PROVIDER_DEEPINFRA,
93
+ initialShowPaid: true, // trial credit: default to showing all models
94
+ tosUrl: "https://deepinfra.com/pricing",
95
+ reRegister: (models, _stored) => {
96
+ if (_stored) {
97
+ stored.free = _stored.free;
98
+ stored.all = _stored.all;
99
+ }
100
+ reRegister(models);
101
+ },
102
+ },
103
+ stored,
104
+ );
105
+
106
+ // Initial registration — DeepInfra is a trial-credit provider,
107
+ // so always show all models. Users see them immediately on setup.
108
+ reRegister(allModels);
109
+ }