pi-free 1.0.8 → 2.0.0

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 (63) hide show
  1. package/CHANGELOG.md +107 -1
  2. package/README.md +95 -46
  3. package/config.ts +165 -120
  4. package/constants.ts +22 -61
  5. package/index.ts +186 -0
  6. package/lib/json-persistence.ts +11 -10
  7. package/lib/logger.ts +2 -2
  8. package/lib/model-enhancer.ts +20 -20
  9. package/lib/open-browser.ts +41 -0
  10. package/lib/provider-cache.ts +106 -0
  11. package/lib/registry.ts +144 -0
  12. package/package.json +67 -82
  13. package/provider-factory.ts +25 -41
  14. package/provider-failover/benchmark-lookup.ts +247 -0
  15. package/provider-failover/benchmarks-chunk-0.ts +2010 -0
  16. package/provider-failover/benchmarks-chunk-1.ts +1988 -0
  17. package/provider-failover/benchmarks-chunk-2.ts +2010 -0
  18. package/provider-failover/benchmarks-chunk-3.ts +2010 -0
  19. package/provider-failover/benchmarks-chunk-4.ts +1969 -0
  20. package/provider-failover/hardcoded-benchmarks.ts +22 -10025
  21. package/provider-helper.ts +38 -37
  22. package/providers/{cline-auth.ts → cline/cline-auth.ts} +2 -2
  23. package/providers/cline/cline-models.ts +128 -0
  24. package/providers/{cline.ts → cline/cline.ts} +300 -257
  25. package/providers/cloudflare/cloudflare.ts +368 -0
  26. package/providers/dynamic-built-in/index.ts +513 -0
  27. package/providers/{kilo-auth.ts → kilo/kilo-auth.ts} +3 -20
  28. package/providers/{kilo-models.ts → kilo/kilo-models.ts} +2 -2
  29. package/providers/kilo/kilo.ts +235 -0
  30. package/providers/{modal.ts → modal/modal.ts} +4 -3
  31. package/providers/{nvidia.ts → nvidia/nvidia.ts} +152 -113
  32. package/providers/ollama/ollama.ts +172 -0
  33. package/providers/opencode-session.ts +34 -34
  34. package/providers/{qwen-auth.ts → qwen/qwen-auth.ts} +24 -40
  35. package/providers/{qwen-models.ts → qwen/qwen-models.ts} +101 -95
  36. package/providers/qwen/qwen.ts +202 -0
  37. package/provider-failover/auto-switch.ts +0 -350
  38. package/provider-failover/errors.ts +0 -275
  39. package/provider-failover/index.ts +0 -238
  40. package/providers/cline-models.ts +0 -77
  41. package/providers/factory.ts +0 -125
  42. package/providers/fireworks.ts +0 -49
  43. package/providers/go.ts +0 -216
  44. package/providers/kilo.ts +0 -146
  45. package/providers/mistral.ts +0 -144
  46. package/providers/ollama.ts +0 -113
  47. package/providers/openrouter.ts +0 -175
  48. package/providers/qwen.ts +0 -127
  49. package/providers/zen.ts +0 -371
  50. package/usage/commands.ts +0 -17
  51. package/usage/cumulative.ts +0 -193
  52. package/usage/formatters.ts +0 -115
  53. package/usage/index.ts +0 -46
  54. package/usage/limits.ts +0 -148
  55. package/usage/metrics.ts +0 -222
  56. package/usage/sessions.ts +0 -355
  57. package/usage/store.ts +0 -99
  58. package/usage/tracking.ts +0 -329
  59. package/usage/types.ts +0 -26
  60. package/usage/widget.ts +0 -90
  61. package/widget/data.ts +0 -113
  62. package/widget/format.ts +0 -26
  63. package/widget/render.ts +0 -117
@@ -0,0 +1,368 @@
1
+ /**
2
+ * Cloudflare Workers AI Provider Extension
3
+ *
4
+ * Provides access to Cloudflare's serverless GPU network with 18+ models.
5
+ * All models use Cloudflare's "Neurons" pricing system:
6
+ * - 10,000 Neurons per day FREE (resets daily at 00:00 UTC)
7
+ * - $0.011 per 1,000 Neurons beyond free allocation
8
+ *
9
+ * Setup:
10
+ * 1. Create API token at https://dash.cloudflare.com/profile/api-tokens
11
+ * with "Cloudflare AI" > "Read" permission
12
+ * 2. Get Account ID from https://dash.cloudflare.com (right sidebar)
13
+ * 3. Add credentials to ~/.pi/agent/auth.json or set env vars
14
+ *
15
+ * Auth (in order of priority):
16
+ * - Environment: CF_API_TOKEN and CF_ACCOUNT_ID
17
+ * - Config file: ~/.pi/agent/auth.json
18
+ * { "cloudflare-ai": { "access": "token", "account_id": "id" } }
19
+ * - Legacy: CLOUDFLARE_API_TOKEN and CLOUDFLARE_ACCOUNT_ID env vars
20
+ *
21
+ * Models can be customized via ~/.pi/cloudflare-models.json
22
+ */
23
+
24
+ import { existsSync, readFileSync } from "node:fs";
25
+ import { homedir } from "node:os";
26
+ import { join } from "node:path";
27
+ import type {
28
+ ExtensionAPI,
29
+ ProviderModelConfig,
30
+ } from "@mariozechner/pi-coding-agent";
31
+ import { createLogger } from "../../lib/logger.ts";
32
+
33
+ const _logger = createLogger("cloudflare");
34
+
35
+ // =============================================================================
36
+ // Auth Resolution
37
+ // =============================================================================
38
+
39
+ interface CloudflareAuth {
40
+ token?: string;
41
+ accountId?: string;
42
+ }
43
+
44
+ function getCloudflareAuth(): CloudflareAuth {
45
+ const result: CloudflareAuth = {};
46
+
47
+ // Check new env var names first
48
+ if (process.env.CF_API_TOKEN) result.token = process.env.CF_API_TOKEN;
49
+ if (process.env.CF_ACCOUNT_ID) result.accountId = process.env.CF_ACCOUNT_ID;
50
+
51
+ // Check legacy env var names
52
+ if (!result.token && process.env.CLOUDFLARE_API_TOKEN) {
53
+ result.token = process.env.CLOUDFLARE_API_TOKEN;
54
+ }
55
+ if (!result.accountId && process.env.CLOUDFLARE_ACCOUNT_ID) {
56
+ result.accountId = process.env.CLOUDFLARE_ACCOUNT_ID;
57
+ }
58
+
59
+ if (result.token && result.accountId) return result;
60
+
61
+ // Check ~/.pi/free.json (pi-free config)
62
+ try {
63
+ const configPath = join(homedir(), ".pi", "free.json");
64
+ if (existsSync(configPath)) {
65
+ const config = JSON.parse(readFileSync(configPath, "utf-8"));
66
+ if (!result.token && config.cloudflare_api_token) {
67
+ result.token = config.cloudflare_api_token;
68
+ }
69
+ if (!result.accountId && config.cloudflare_account_id) {
70
+ result.accountId = config.cloudflare_account_id;
71
+ }
72
+ }
73
+ } catch {
74
+ // Ignore config file errors
75
+ }
76
+
77
+ return result;
78
+ }
79
+
80
+ // =============================================================================
81
+ // Compatibility Settings
82
+ // =============================================================================
83
+
84
+ /**
85
+ * Cloudflare Workers AI compatibility settings.
86
+ * Prevents 413 Payload Too Large errors by disabling unsupported parameters.
87
+ */
88
+ const CLOUDFLARE_COMPAT: {
89
+ supportsStore?: boolean;
90
+ supportsDeveloperRole?: boolean;
91
+ supportsReasoningEffort?: boolean;
92
+ supportsStrictMode?: boolean;
93
+ maxTokensField?: "max_tokens" | "max_completion_tokens";
94
+ requiresThinkingAsText?: boolean;
95
+ } = {
96
+ supportsStore: false,
97
+ supportsDeveloperRole: false,
98
+ supportsReasoningEffort: false,
99
+ supportsStrictMode: false,
100
+ maxTokensField: "max_tokens",
101
+ };
102
+
103
+ // =============================================================================
104
+ // Default Models (18 models from Cloudflare Workers AI)
105
+ // =============================================================================
106
+
107
+ interface ModelConfig extends ProviderModelConfig {
108
+ compat?: { requiresThinkingAsText?: boolean };
109
+ _remove?: boolean;
110
+ }
111
+
112
+ const DEFAULT_MODELS: ModelConfig[] = [
113
+ // Frontier models
114
+ {
115
+ id: "@cf/moonshotai/kimi-k2.5",
116
+ name: "Kimi K2.5",
117
+ reasoning: true,
118
+ input: ["text", "image"],
119
+ cost: { input: 0.6, output: 3.0, cacheRead: 0.1, cacheWrite: 0 },
120
+ contextWindow: 256000,
121
+ maxTokens: 8192,
122
+ },
123
+ {
124
+ id: "@cf/meta/llama-4-scout-17b-16e-instruct",
125
+ name: "Llama 4 Scout 17B",
126
+ reasoning: false,
127
+ input: ["text", "image"],
128
+ cost: { input: 0.27, output: 0.85, cacheRead: 0, cacheWrite: 0 },
129
+ contextWindow: 131072,
130
+ maxTokens: 8192,
131
+ },
132
+ {
133
+ id: "@cf/nvidia/nemotron-3-120b-a12b",
134
+ name: "Nemotron 3 Super 120B",
135
+ reasoning: true,
136
+ input: ["text"],
137
+ cost: { input: 0.5, output: 1.5, cacheRead: 0, cacheWrite: 0 },
138
+ contextWindow: 256000,
139
+ maxTokens: 8192,
140
+ compat: { requiresThinkingAsText: true },
141
+ },
142
+ {
143
+ id: "@cf/google/gemma-4-26b-a4b-it",
144
+ name: "Gemma 4 26B",
145
+ reasoning: true,
146
+ input: ["text", "image"],
147
+ cost: { input: 0.1, output: 0.3, cacheRead: 0, cacheWrite: 0 },
148
+ contextWindow: 256000,
149
+ maxTokens: 8192,
150
+ compat: { requiresThinkingAsText: true },
151
+ },
152
+ {
153
+ id: "@cf/google/gemma-3-12b-it",
154
+ name: "Gemma 3 12B",
155
+ reasoning: false,
156
+ input: ["text", "image"],
157
+ cost: { input: 0.345, output: 0.556, cacheRead: 0, cacheWrite: 0 },
158
+ contextWindow: 80000,
159
+ maxTokens: 8192,
160
+ },
161
+ {
162
+ id: "@cf/qwen/qwen3-30b-a3b-fp8",
163
+ name: "Qwen3 30B A3B",
164
+ reasoning: true,
165
+ input: ["text"],
166
+ cost: { input: 0.051, output: 0.34, cacheRead: 0, cacheWrite: 0 },
167
+ contextWindow: 32768,
168
+ maxTokens: 8192,
169
+ compat: { requiresThinkingAsText: true },
170
+ },
171
+ {
172
+ id: "@cf/zai-org/glm-4.7-flash",
173
+ name: "GLM-4.7 Flash",
174
+ reasoning: false,
175
+ input: ["text"],
176
+ cost: { input: 0.06, output: 0.4, cacheRead: 0, cacheWrite: 0 },
177
+ contextWindow: 131072,
178
+ maxTokens: 8192,
179
+ },
180
+ // Popular models
181
+ {
182
+ id: "@cf/meta/llama-3.3-70b-instruct-fp8-fast",
183
+ name: "Llama 3.3 70B (Fast)",
184
+ reasoning: false,
185
+ input: ["text"],
186
+ cost: { input: 0.5, output: 0.5, cacheRead: 0, cacheWrite: 0 },
187
+ contextWindow: 131072,
188
+ maxTokens: 8192,
189
+ },
190
+ {
191
+ id: "@cf/meta/llama-3.1-8b-instruct",
192
+ name: "Llama 3.1 8B",
193
+ reasoning: false,
194
+ input: ["text"],
195
+ cost: { input: 0.1, output: 0.1, cacheRead: 0, cacheWrite: 0 },
196
+ contextWindow: 131072,
197
+ maxTokens: 8192,
198
+ },
199
+ {
200
+ id: "@cf/meta/llama-3.1-70b-instruct",
201
+ name: "Llama 3.1 70B",
202
+ reasoning: false,
203
+ input: ["text"],
204
+ cost: { input: 0.5, output: 0.5, cacheRead: 0, cacheWrite: 0 },
205
+ contextWindow: 131072,
206
+ maxTokens: 8192,
207
+ },
208
+ {
209
+ id: "@cf/meta/llama-3.1-405b-instruct",
210
+ name: "Llama 3.1 405B",
211
+ reasoning: false,
212
+ input: ["text"],
213
+ cost: { input: 2.0, output: 2.0, cacheRead: 0, cacheWrite: 0 },
214
+ contextWindow: 131072,
215
+ maxTokens: 8192,
216
+ },
217
+ {
218
+ id: "@cf/deepseek-ai/deepseek-r1-distill-qwen-32b",
219
+ name: "DeepSeek R1 Distill Qwen 32B",
220
+ reasoning: true,
221
+ input: ["text"],
222
+ cost: { input: 0.3, output: 0.3, cacheRead: 0, cacheWrite: 0 },
223
+ contextWindow: 32768,
224
+ maxTokens: 8192,
225
+ compat: { requiresThinkingAsText: true },
226
+ },
227
+ {
228
+ id: "@cf/deepseek-ai/deepseek-math-7b-instruct",
229
+ name: "DeepSeek Math 7B",
230
+ reasoning: true,
231
+ input: ["text"],
232
+ cost: { input: 0.1, output: 0.1, cacheRead: 0, cacheWrite: 0 },
233
+ contextWindow: 16384,
234
+ maxTokens: 4096,
235
+ },
236
+ // Mistral models
237
+ {
238
+ id: "@cf/mistral/mistral-small-3.1-24b-instruct",
239
+ name: "Mistral Small 3.1 24B",
240
+ reasoning: false,
241
+ input: ["text", "image"],
242
+ cost: { input: 0.3, output: 0.3, cacheRead: 0, cacheWrite: 0 },
243
+ contextWindow: 32768,
244
+ maxTokens: 8192,
245
+ },
246
+ {
247
+ id: "@cf/mistral/mistral-7b-instruct-v0.2-lora",
248
+ name: "Mistral 7B Instruct",
249
+ reasoning: false,
250
+ input: ["text"],
251
+ cost: { input: 0.1, output: 0.1, cacheRead: 0, cacheWrite: 0 },
252
+ contextWindow: 32768,
253
+ maxTokens: 4096,
254
+ },
255
+ {
256
+ id: "@cf/mistral/mixtral-8x7b-instruct-v0.1-awq",
257
+ name: "Mixtral 8x7B Instruct",
258
+ reasoning: false,
259
+ input: ["text"],
260
+ cost: { input: 0.3, output: 0.3, cacheRead: 0, cacheWrite: 0 },
261
+ contextWindow: 32768,
262
+ maxTokens: 4096,
263
+ },
264
+ // Qwen and Gemma
265
+ {
266
+ id: "@cf/qwen/qwen1.5-14b-chat-awq",
267
+ name: "Qwen 1.5 14B Chat",
268
+ reasoning: false,
269
+ input: ["text"],
270
+ cost: { input: 0.2, output: 0.2, cacheRead: 0, cacheWrite: 0 },
271
+ contextWindow: 32768,
272
+ maxTokens: 8192,
273
+ },
274
+ {
275
+ id: "@cf/google/gemma-2b-it-lora",
276
+ name: "Gemma 2B",
277
+ reasoning: false,
278
+ input: ["text"],
279
+ cost: { input: 0.05, output: 0.05, cacheRead: 0, cacheWrite: 0 },
280
+ contextWindow: 8192,
281
+ maxTokens: 2048,
282
+ },
283
+ {
284
+ id: "@cf/google/gemma-7b-it-lora",
285
+ name: "Gemma 7B",
286
+ reasoning: false,
287
+ input: ["text"],
288
+ cost: { input: 0.1, output: 0.1, cacheRead: 0, cacheWrite: 0 },
289
+ contextWindow: 8192,
290
+ maxTokens: 2048,
291
+ },
292
+ ];
293
+
294
+ // =============================================================================
295
+ // Model Customization (user overrides)
296
+ // =============================================================================
297
+
298
+ function getModels(): ProviderModelConfig[] {
299
+ // Apply Cloudflare compat settings to all default models
300
+ const defaults = DEFAULT_MODELS.map((m) => ({
301
+ ...m,
302
+ compat: { ...CLOUDFLARE_COMPAT, ...m.compat },
303
+ })) as ProviderModelConfig[];
304
+
305
+ // Check for user overrides
306
+ const overridePath = join(homedir(), ".pi", "cloudflare-models.json");
307
+ if (!existsSync(overridePath)) return defaults;
308
+
309
+ try {
310
+ const override = JSON.parse(
311
+ readFileSync(overridePath, "utf-8"),
312
+ ) as ModelConfig[];
313
+ const modelMap = new Map<string, any>(defaults.map((m) => [m.id, m]));
314
+
315
+ for (const model of override) {
316
+ if (model._remove) {
317
+ modelMap.delete(model.id);
318
+ } else {
319
+ // Apply Cloudflare compat settings to user overrides
320
+ model.compat = { ...CLOUDFLARE_COMPAT, ...model.compat };
321
+ modelMap.set(model.id, model);
322
+ }
323
+ }
324
+
325
+ return Array.from(modelMap.values()) as ProviderModelConfig[];
326
+ } catch (e) {
327
+ _logger.warn(
328
+ `[cloudflare] Failed to load ~/.pi/cloudflare-models.json: ${e instanceof Error ? e.message : String(e)}`,
329
+ );
330
+ return defaults;
331
+ }
332
+ }
333
+
334
+ // =============================================================================
335
+ // Extension Entry Point
336
+ // =============================================================================
337
+
338
+ export default async function cloudflareProvider(pi: ExtensionAPI) {
339
+ const { token: apiToken, accountId } = getCloudflareAuth();
340
+
341
+ if (!apiToken) {
342
+ _logger.info(
343
+ "[cloudflare] CF_API_TOKEN or CLOUDFLARE_API_TOKEN not set. Provider will not be available.",
344
+ );
345
+ return;
346
+ }
347
+
348
+ if (!accountId) {
349
+ _logger.info(
350
+ "[cloudflare] CF_ACCOUNT_ID or CLOUDFLARE_ACCOUNT_ID not set. Provider will not be available.",
351
+ );
352
+ return;
353
+ }
354
+
355
+ const models = getModels();
356
+
357
+ pi.registerProvider("cloudflare", {
358
+ baseUrl: `https://api.cloudflare.com/client/v4/accounts/${accountId}/ai/v1`,
359
+ apiKey: apiToken,
360
+ api: "openai-completions",
361
+ authHeader: true,
362
+ models,
363
+ });
364
+
365
+ _logger.info(
366
+ `[cloudflare] Provider registered with account: ${accountId.slice(0, 8)}... (${models.length} models)`,
367
+ );
368
+ }