pi-free 2.0.9 → 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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-free",
3
- "version": "2.0.9",
3
+ "version": "2.0.10",
4
4
  "type": "module",
5
5
  "description": "AI model providers for Pi with free model filtering. Shows only $0 cost models by default. Supports Kilo (free OAuth), Cline (free), NVIDIA (freemium), ZenMux, CrofAI, Ollama Cloud, and more.",
6
6
  "keywords": [
@@ -41,6 +41,8 @@
41
41
  "LICENSE",
42
42
  "CHANGELOG.md",
43
43
  "banner.svg",
44
+ "banner.png",
45
+ "banner.jpg",
44
46
  "scripts/check-extensions.mjs"
45
47
  ],
46
48
  "scripts": {
@@ -1,99 +1,190 @@
1
- /**
2
- * CrofAI Provider Extension
3
- *
4
- * Provides access to CrofAI API - OpenAI-compatible LLM inference service.
5
- *
6
- * Setup:
7
- * 1. Get API key from https://ai.nahcrof.com
8
- * 2. Set CROFAI_API_KEY env var or add to ~/.pi/free.json
9
- *
10
- * Responds to global free-only filter.
11
- *
12
- * Usage:
13
- * pi install git:github.com/apmantza/pi-free
14
- * # Set CROFAI_API_KEY env var
15
- * # Models appear in /model selector
16
- */
17
-
18
- import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
19
- import { getCrofaiApiKey, getCrofaiShowPaid } from "../../config.ts";
20
- import { BASE_URL_CROFAI, PROVIDER_CROFAI } from "../../constants.ts";
21
- import { createLogger } from "../../lib/logger.ts";
22
- import { isFreeModel, registerWithGlobalToggle } from "../../lib/registry.ts";
23
- import { fetchOpenAICompatibleModels } from "../../lib/util.ts";
24
- import { createReRegister, setupProvider } from "../../provider-helper.ts";
25
-
26
- const _logger = createLogger("crofai");
27
-
28
- // =============================================================================
29
- // Extension Entry Point
30
- // =============================================================================
31
-
32
- export default async function crofaiProvider(pi: ExtensionAPI) {
33
- const apiKey = getCrofaiApiKey();
34
-
35
- if (!apiKey) {
36
- _logger.info(
37
- "[crofai] Skipping - CROFAI_API_KEY not set (env var or ~/.pi/free.json)",
38
- );
39
- return;
40
- }
41
-
42
- // Fetch models via shared OpenAI-compatible helper
43
- const allModels = await fetchOpenAICompatibleModels(
44
- "crofai",
45
- BASE_URL_CROFAI,
46
- apiKey,
47
- );
48
-
49
- if (allModels.length === 0) {
50
- _logger.warn("[crofai] No models available");
51
- return;
52
- }
53
-
54
- // Use isFreeModel with allModels for proper detection
55
- // CrofAI doesn't expose pricing (all costs are $0), so Route B will be used:
56
- // FREE only if "free" in name
57
- const freeModels = allModels.filter((m) =>
58
- isFreeModel({ ...m, provider: PROVIDER_CROFAI }, allModels),
59
- );
60
-
61
- const stored = { free: freeModels, all: allModels };
62
-
63
- _logger.info(
64
- `[crofai] Registered ${allModels.length} models (${freeModels.length} free)`,
65
- );
66
-
67
- // Create re-register function
68
- const reRegister = createReRegister(pi, {
69
- providerId: PROVIDER_CROFAI,
70
- baseUrl: BASE_URL_CROFAI,
71
- apiKey,
72
- });
73
-
74
- // Register with global toggle
75
- registerWithGlobalToggle(PROVIDER_CROFAI, stored, reRegister, true);
76
-
77
- // Setup provider with toggle command
78
- setupProvider(
79
- pi,
80
- {
81
- providerId: PROVIDER_CROFAI,
82
- initialShowPaid: getCrofaiShowPaid(),
83
- reRegister: (models, _stored) => {
84
- if (_stored) {
85
- stored.free = _stored.free;
86
- stored.all = _stored.all;
87
- }
88
- reRegister(models);
89
- },
90
- },
91
- stored,
92
- );
93
-
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);
99
- }
1
+ /**
2
+ * CrofAI Provider Extension
3
+ *
4
+ * Provides access to CrofAI API - OpenAI-compatible LLM inference service
5
+ * hosting DeepSeek, Qwen, and other open-source models.
6
+ *
7
+ * NOTE: CrofAI's /v1/models returns per-model context_length, max_completion_tokens,
8
+ * name, custom_reasoning, and reasoning_effort. Pricing is per-MILLION tokens.
9
+ *
10
+ * Setup:
11
+ * 1. Get API key from https://ai.nahcrof.com
12
+ * 2. Set CROFAI_API_KEY env var or add to ~/.pi/free.json
13
+ *
14
+ * Usage:
15
+ * pi install git:github.com/apmantza/pi-free
16
+ * # Set CROFAI_API_KEY env var
17
+ * # Models appear in /model selector
18
+ */
19
+
20
+ import type {
21
+ ExtensionAPI,
22
+ ProviderModelConfig,
23
+ } from "@earendil-works/pi-coding-agent";
24
+ import { getCrofaiApiKey, getCrofaiShowPaid } from "../../config.ts";
25
+ import {
26
+ BASE_URL_CROFAI,
27
+ DEFAULT_FETCH_TIMEOUT_MS,
28
+ PROVIDER_CROFAI,
29
+ } from "../../constants.ts";
30
+ import { createLogger } from "../../lib/logger.ts";
31
+ import {
32
+ getProxyModelCompat,
33
+ isLikelyReasoningModel,
34
+ } from "../../lib/provider-compat.ts";
35
+ import { isFreeModel, registerWithGlobalToggle } from "../../lib/registry.ts";
36
+ import { fetchWithRetry } from "../../lib/util.ts";
37
+ import { createReRegister, setupProvider } from "../../provider-helper.ts";
38
+
39
+ const _logger = createLogger("crofai");
40
+
41
+ // =============================================================================
42
+ // Types
43
+ // =============================================================================
44
+
45
+ interface CrofaiModel {
46
+ id: string;
47
+ name?: string;
48
+ context_length?: number;
49
+ max_completion_tokens?: number;
50
+ custom_reasoning?: boolean;
51
+ reasoning_effort?: boolean;
52
+ pricing?: {
53
+ prompt?: string;
54
+ completion?: string;
55
+ cache_prompt?: string;
56
+ };
57
+ }
58
+
59
+ // =============================================================================
60
+ // Fetch
61
+ // =============================================================================
62
+
63
+ function parseCrofaiPrice(priceStr: string | undefined): number {
64
+ if (priceStr === undefined) return 0;
65
+ const num = Number.parseFloat(priceStr);
66
+ if (Number.isNaN(num)) return 0;
67
+ // CrofAI pricing is per-MILLION tokens. Divide to get per-token (Pi convention).
68
+ return num / 1_000_000;
69
+ }
70
+
71
+ async function fetchCrofaiModels(
72
+ apiKey: string,
73
+ ): Promise<ProviderModelConfig[]> {
74
+ const response = await fetchWithRetry(
75
+ `${BASE_URL_CROFAI}/models`,
76
+ {
77
+ headers: {
78
+ Authorization: `Bearer ${apiKey}`,
79
+ "Content-Type": "application/json",
80
+ },
81
+ },
82
+ 3,
83
+ 1000,
84
+ DEFAULT_FETCH_TIMEOUT_MS,
85
+ );
86
+
87
+ if (!response.ok) {
88
+ throw new Error(
89
+ `CrofAI API error: ${response.status} ${response.statusText}`,
90
+ );
91
+ }
92
+
93
+ // CrofAI returns { data: [...] }
94
+ const json = (await response.json()) as {
95
+ data?: CrofaiModel[];
96
+ };
97
+ const models = json.data ?? [];
98
+
99
+ _logger.info(`[crofai] Fetched ${models.length} models`);
100
+
101
+ return models
102
+ .filter((m) => m.id)
103
+ .map((m): ProviderModelConfig => {
104
+ const name = m.name || m.id;
105
+ const reasoning =
106
+ m.custom_reasoning ?? isLikelyReasoningModel({ id: m.id, name });
107
+
108
+ return {
109
+ id: m.id,
110
+ name,
111
+ reasoning,
112
+ input: ["text"],
113
+ cost: {
114
+ input: parseCrofaiPrice(m.pricing?.prompt),
115
+ output: parseCrofaiPrice(m.pricing?.completion),
116
+ cacheRead: parseCrofaiPrice(m.pricing?.cache_prompt),
117
+ cacheWrite: 0,
118
+ },
119
+ contextWindow: m.context_length ?? 128_000,
120
+ maxTokens: m.max_completion_tokens ?? 16_384,
121
+ compat: getProxyModelCompat({ id: m.id, name }),
122
+ };
123
+ });
124
+ }
125
+
126
+ // =============================================================================
127
+ // Extension Entry Point
128
+ // =============================================================================
129
+
130
+ export default async function crofaiProvider(pi: ExtensionAPI) {
131
+ const apiKey = getCrofaiApiKey();
132
+
133
+ if (!apiKey) {
134
+ _logger.info(
135
+ "[crofai] Skipping - CROFAI_API_KEY not set (env var or ~/.pi/free.json)",
136
+ );
137
+ return;
138
+ }
139
+
140
+ // Fetch models
141
+ const allModels = await fetchCrofaiModels(apiKey);
142
+
143
+ if (allModels.length === 0) {
144
+ _logger.warn("[crofai] No models available");
145
+ return;
146
+ }
147
+
148
+ const freeModels = allModels.filter((m) =>
149
+ isFreeModel({ ...m, provider: PROVIDER_CROFAI }, allModels),
150
+ );
151
+
152
+ const stored = { free: freeModels, all: allModels };
153
+
154
+ _logger.info(
155
+ `[crofai] Registered ${allModels.length} models (${freeModels.length} free)`,
156
+ );
157
+
158
+ // Create re-register function
159
+ const reRegister = createReRegister(pi, {
160
+ providerId: PROVIDER_CROFAI,
161
+ baseUrl: BASE_URL_CROFAI,
162
+ apiKey,
163
+ });
164
+
165
+ // Register with global toggle
166
+ registerWithGlobalToggle(PROVIDER_CROFAI, stored, reRegister, true);
167
+
168
+ // Setup provider with toggle command
169
+ setupProvider(
170
+ pi,
171
+ {
172
+ providerId: PROVIDER_CROFAI,
173
+ initialShowPaid: getCrofaiShowPaid(),
174
+ reRegister: (models, _stored) => {
175
+ if (_stored) {
176
+ stored.free = _stored.free;
177
+ stored.all = _stored.all;
178
+ }
179
+ reRegister(models);
180
+ },
181
+ },
182
+ stored,
183
+ );
184
+
185
+ // Initial registration — respect persisted toggle state
186
+ const showPaid = getCrofaiShowPaid();
187
+ const initialModels =
188
+ showPaid && stored.all.length > 0 ? stored.all : freeModels;
189
+ reRegister(initialModels);
190
+ }
@@ -1,109 +1,206 @@
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 "@earendil-works/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
- }
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
+ * NOTE: DeepInfra's /v1/openai/models buries real model data in a "metadata"
8
+ * field (context_length, max_tokens, pricing, tags). We extract it here.
9
+ * Pricing is per-MILLION tokens.
10
+ *
11
+ * Free tier:
12
+ * - $5 one-time credit on signup (no credit card)
13
+ * - ~5M tokens, expires after 90 days
14
+ * - 60 RPM (varies by model)
15
+ *
16
+ * Paid: pay-per-token after credits exhaust
17
+ *
18
+ * Endpoint:
19
+ * Chat: https://api.deepinfra.com/v1/openai/chat/completions
20
+ *
21
+ * Setup:
22
+ * 1. Sign up at https://deepinfra.com/ (GitHub or email)
23
+ * 2. Get API key from https://deepinfra.com/dash/api_keys
24
+ * 3. Set DEEPINFRA_TOKEN env var (or add to ~/.pi/free.json)
25
+ *
26
+ * Usage:
27
+ * pi install git:github.com/apmantza/pi-free
28
+ * # Set DEEPINFRA_TOKEN env var
29
+ * # Models appear in /model selector as "deepinfra/meta-llama/..."
30
+ */
31
+
32
+ import type {
33
+ ExtensionAPI,
34
+ ProviderModelConfig,
35
+ } from "@earendil-works/pi-coding-agent";
36
+ import { getDeepinfraApiKey } from "../../config.ts";
37
+ import {
38
+ BASE_URL_DEEPINFRA,
39
+ DEFAULT_FETCH_TIMEOUT_MS,
40
+ PROVIDER_DEEPINFRA,
41
+ } from "../../constants.ts";
42
+ import { createLogger } from "../../lib/logger.ts";
43
+ import {
44
+ getProxyModelCompat,
45
+ isLikelyReasoningModel,
46
+ } from "../../lib/provider-compat.ts";
47
+ import { registerWithGlobalToggle } from "../../lib/registry.ts";
48
+ import { fetchWithRetry } from "../../lib/util.ts";
49
+ import { createReRegister, setupProvider } from "../../provider-helper.ts";
50
+
51
+ const _logger = createLogger("deepinfra");
52
+
53
+ // =============================================================================
54
+ // Types
55
+ // =============================================================================
56
+
57
+ interface DeepInfraModel {
58
+ id: string;
59
+ metadata?: {
60
+ context_length?: number;
61
+ max_tokens?: number;
62
+ description?: string;
63
+ pricing?: {
64
+ input_tokens?: number;
65
+ output_tokens?: number;
66
+ };
67
+ tags?: string[];
68
+ };
69
+ }
70
+
71
+ // =============================================================================
72
+ // Fetch
73
+ // =============================================================================
74
+
75
+ async function fetchDeepinfraModels(
76
+ apiKey: string,
77
+ ): Promise<ProviderModelConfig[]> {
78
+ const response = await fetchWithRetry(
79
+ `${BASE_URL_DEEPINFRA}/models`,
80
+ {
81
+ headers: {
82
+ Authorization: `Bearer ${apiKey}`,
83
+ "Content-Type": "application/json",
84
+ },
85
+ },
86
+ 3,
87
+ 1000,
88
+ DEFAULT_FETCH_TIMEOUT_MS,
89
+ );
90
+
91
+ if (!response.ok) {
92
+ throw new Error(
93
+ `DeepInfra API error: ${response.status} ${response.statusText}`,
94
+ );
95
+ }
96
+
97
+ const json = (await response.json()) as { data?: DeepInfraModel[] };
98
+ const models = json.data ?? [];
99
+
100
+ _logger.info(`[deepinfra] Fetched ${models.length} models`);
101
+
102
+ return models
103
+ .filter((m) => {
104
+ const id = m.id.toLowerCase();
105
+ // Filter out non-chat models
106
+ if (id.includes("embed")) return false;
107
+ if (id.includes("rerank")) return false;
108
+ if (id.includes("whisper")) return false;
109
+ if (id.includes("speech")) return false;
110
+ return true;
111
+ })
112
+ .map((m): ProviderModelConfig => {
113
+ const meta = m.metadata;
114
+ const name = m.id.split("/").pop() || m.id;
115
+
116
+ // Reasoning: check tags first, fall back to name heuristic
117
+ const reasoning =
118
+ meta?.tags?.includes("reasoning") ??
119
+ isLikelyReasoningModel({ id: m.id, name });
120
+
121
+ // Pricing is per-MILLION tokens. Divide to get per-token (Pi convention).
122
+ const inputCost = (meta?.pricing?.input_tokens ?? 0.3) / 1_000_000;
123
+ const outputCost = (meta?.pricing?.output_tokens ?? 0.9) / 1_000_000;
124
+
125
+ return {
126
+ id: m.id,
127
+ name,
128
+ reasoning,
129
+ input: ["text"],
130
+ cost: {
131
+ input: inputCost,
132
+ output: outputCost,
133
+ cacheRead: 0,
134
+ cacheWrite: 0,
135
+ },
136
+ contextWindow: meta?.context_length ?? 128_000,
137
+ maxTokens: meta?.max_tokens ?? 16_384,
138
+ compat: getProxyModelCompat({ id: m.id, name }),
139
+ };
140
+ });
141
+ }
142
+
143
+ // =============================================================================
144
+ // Extension Entry Point
145
+ // =============================================================================
146
+
147
+ export default async function deepinfraProvider(pi: ExtensionAPI) {
148
+ const apiKey = getDeepinfraApiKey();
149
+
150
+ if (!apiKey) {
151
+ _logger.info(
152
+ "[deepinfra] Skipping — DEEPINFRA_TOKEN not set. Sign up at https://deepinfra.com/",
153
+ );
154
+ return;
155
+ }
156
+
157
+ // Fetch models
158
+ const allModels = await fetchDeepinfraModels(apiKey);
159
+
160
+ if (allModels.length === 0) {
161
+ _logger.warn("[deepinfra] No chat models available");
162
+ return;
163
+ }
164
+
165
+ // DeepInfra is a trial credit provider — $5 one-time credit, no truly free models.
166
+ // All models are marked as paid. When free-only mode is ON, no models are shown.
167
+ // Toggle free-only OFF to see all models.
168
+ const freeModels: ProviderModelConfig[] = [];
169
+ const stored = { free: freeModels, all: allModels };
170
+
171
+ _logger.info(
172
+ `[deepinfra] Registered ${allModels.length} chat models (trial credit, 0 free)`,
173
+ );
174
+
175
+ // Create re-register function
176
+ const reRegister = createReRegister(pi, {
177
+ providerId: PROVIDER_DEEPINFRA,
178
+ baseUrl: BASE_URL_DEEPINFRA,
179
+ apiKey,
180
+ });
181
+
182
+ // Register with global toggle
183
+ registerWithGlobalToggle(PROVIDER_DEEPINFRA, stored, reRegister, true);
184
+
185
+ // Setup provider with toggle command
186
+ setupProvider(
187
+ pi,
188
+ {
189
+ providerId: PROVIDER_DEEPINFRA,
190
+ initialShowPaid: true, // trial credit: default to showing all models
191
+ tosUrl: "https://deepinfra.com/pricing",
192
+ reRegister: (models, _stored) => {
193
+ if (_stored) {
194
+ stored.free = _stored.free;
195
+ stored.all = _stored.all;
196
+ }
197
+ reRegister(models);
198
+ },
199
+ },
200
+ stored,
201
+ );
202
+
203
+ // Initial registration — DeepInfra is a trial-credit provider,
204
+ // so always show all models. Users see them immediately on setup.
205
+ reRegister(allModels);
206
+ }