pi-free 2.0.2 → 2.0.5
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 +84 -12
- package/README.md +44 -97
- package/banner.svg +132 -0
- package/config.ts +24 -52
- package/constants.ts +6 -0
- package/index.ts +175 -148
- package/lib/built-in-toggle.ts +40 -1
- package/lib/model-detection.ts +176 -139
- package/lib/model-enhancer.ts +20 -20
- package/lib/open-browser.ts +1 -1
- package/lib/provider-compat.ts +46 -0
- package/lib/registry.ts +200 -144
- package/lib/types.ts +101 -108
- package/lib/util.ts +262 -256
- package/package.json +9 -8
- package/provider-failover/benchmark-lookup.ts +191 -140
- package/provider-helper.ts +19 -1
- package/providers/cline/cline-auth.ts +473 -473
- package/providers/cline/cline.ts +58 -14
- package/providers/crofai/crofai.ts +170 -0
- package/providers/dynamic-built-in/index.ts +260 -308
- package/providers/kilo/kilo-auth.ts +155 -155
- package/providers/kilo/kilo.ts +263 -235
- package/providers/nvidia/nvidia.ts +474 -415
- package/providers/ollama/ollama.ts +295 -280
- package/providers/opencode-session.ts +3 -4
- package/providers/qwen/qwen-models.ts +101 -101
- package/providers/qwen/qwen.ts +47 -49
- package/providers/zenmux/zenmux.ts +176 -0
- package/scripts/check-extensions.mjs +71 -55
- package/provider-factory.ts +0 -207
- package/providers/cloudflare/cloudflare.ts +0 -526
- package/providers/modal/modal.ts +0 -47
|
@@ -1,526 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Cloudflare Workers AI Provider Extension
|
|
3
|
-
*
|
|
4
|
-
* Provides access to Cloudflare's serverless GPU network with 30+ 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
|
-
|
|
22
|
-
import { existsSync, readFileSync } from "node:fs";
|
|
23
|
-
import { homedir } from "node:os";
|
|
24
|
-
import { join } from "node:path";
|
|
25
|
-
import type {
|
|
26
|
-
ExtensionAPI,
|
|
27
|
-
ProviderModelConfig,
|
|
28
|
-
} from "@mariozechner/pi-coding-agent";
|
|
29
|
-
import { DEFAULT_FETCH_TIMEOUT_MS } from "../../constants.ts";
|
|
30
|
-
import { createLogger } from "../../lib/logger.ts";
|
|
31
|
-
import { fetchWithRetry } from "../../lib/util.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
|
-
const CLOUDFLARE_COMPAT = {
|
|
85
|
-
supportsStore: false,
|
|
86
|
-
supportsDeveloperRole: false,
|
|
87
|
-
supportsReasoningEffort: false,
|
|
88
|
-
supportsStrictMode: false,
|
|
89
|
-
maxTokensField: "max_tokens" as const,
|
|
90
|
-
};
|
|
91
|
-
|
|
92
|
-
// =============================================================================
|
|
93
|
-
// Known non-chat model patterns (to filter out)
|
|
94
|
-
// =============================================================================
|
|
95
|
-
|
|
96
|
-
const NON_CHAT_PATTERNS = [
|
|
97
|
-
// Embeddings
|
|
98
|
-
/bge-/i,
|
|
99
|
-
/embed/i,
|
|
100
|
-
/embedding/i,
|
|
101
|
-
/pfnet\/plamo-embedding/i,
|
|
102
|
-
/qwen3-embedding/i,
|
|
103
|
-
// Image generation
|
|
104
|
-
/flux/i,
|
|
105
|
-
/stable-diffusion/i,
|
|
106
|
-
/dreamshaper/i,
|
|
107
|
-
/lucid-origin/i,
|
|
108
|
-
/phoenix/i,
|
|
109
|
-
// Speech/audio
|
|
110
|
-
/whisper/i,
|
|
111
|
-
/aura-/i,
|
|
112
|
-
/nova-/i,
|
|
113
|
-
/melotts/i,
|
|
114
|
-
// Translation (not chat)
|
|
115
|
-
/indictrans/i,
|
|
116
|
-
/m2m100/i,
|
|
117
|
-
// Vision-only models
|
|
118
|
-
/llava/i,
|
|
119
|
-
/detr-/i,
|
|
120
|
-
/resnet/i,
|
|
121
|
-
/unum\/uform/i,
|
|
122
|
-
// Code/SQL only
|
|
123
|
-
/sqlcoder/i,
|
|
124
|
-
// Classification/reranking
|
|
125
|
-
/reranker/i,
|
|
126
|
-
/distilbert/i,
|
|
127
|
-
// Safety/moderation
|
|
128
|
-
/llama-guard/i,
|
|
129
|
-
// Turn detection
|
|
130
|
-
/smart-turn/i,
|
|
131
|
-
];
|
|
132
|
-
|
|
133
|
-
// =============================================================================
|
|
134
|
-
// Fallback models (used if API fetch fails)
|
|
135
|
-
// =============================================================================
|
|
136
|
-
|
|
137
|
-
const FALLBACK_MODELS: ProviderModelConfig[] = [
|
|
138
|
-
{
|
|
139
|
-
id: "@cf/moonshotai/kimi-k2.5",
|
|
140
|
-
name: "Kimi K2.5",
|
|
141
|
-
reasoning: true,
|
|
142
|
-
input: ["text", "image"],
|
|
143
|
-
cost: { input: 0.6, output: 3.0, cacheRead: 0.1, cacheWrite: 0 },
|
|
144
|
-
contextWindow: 256000,
|
|
145
|
-
maxTokens: 8192,
|
|
146
|
-
compat: { ...CLOUDFLARE_COMPAT, requiresThinkingAsText: true },
|
|
147
|
-
},
|
|
148
|
-
{
|
|
149
|
-
id: "@cf/moonshotai/kimi-k2.6",
|
|
150
|
-
name: "Kimi K2.6",
|
|
151
|
-
reasoning: true,
|
|
152
|
-
input: ["text", "image"],
|
|
153
|
-
cost: { input: 0.8, output: 4.0, cacheRead: 0.1, cacheWrite: 0 },
|
|
154
|
-
contextWindow: 256000,
|
|
155
|
-
maxTokens: 8192,
|
|
156
|
-
compat: { ...CLOUDFLARE_COMPAT, requiresThinkingAsText: true },
|
|
157
|
-
},
|
|
158
|
-
{
|
|
159
|
-
id: "@cf/meta/llama-4-scout-17b-16e-instruct",
|
|
160
|
-
name: "Llama 4 Scout 17B",
|
|
161
|
-
reasoning: false,
|
|
162
|
-
input: ["text", "image"],
|
|
163
|
-
cost: { input: 0.27, output: 0.85, cacheRead: 0, cacheWrite: 0 },
|
|
164
|
-
contextWindow: 131072,
|
|
165
|
-
maxTokens: 8192,
|
|
166
|
-
compat: CLOUDFLARE_COMPAT,
|
|
167
|
-
},
|
|
168
|
-
{
|
|
169
|
-
id: "@cf/nvidia/nemotron-3-120b-a12b",
|
|
170
|
-
name: "Nemotron 3 Super 120B",
|
|
171
|
-
reasoning: true,
|
|
172
|
-
input: ["text"],
|
|
173
|
-
cost: { input: 0.5, output: 1.5, cacheRead: 0, cacheWrite: 0 },
|
|
174
|
-
contextWindow: 256000,
|
|
175
|
-
maxTokens: 8192,
|
|
176
|
-
compat: { ...CLOUDFLARE_COMPAT, requiresThinkingAsText: true },
|
|
177
|
-
},
|
|
178
|
-
{
|
|
179
|
-
id: "@cf/openai/gpt-oss-120b",
|
|
180
|
-
name: "GPT-OSS 120B",
|
|
181
|
-
reasoning: true,
|
|
182
|
-
input: ["text"],
|
|
183
|
-
cost: { input: 0.5, output: 1.5, cacheRead: 0, cacheWrite: 0 },
|
|
184
|
-
contextWindow: 128000,
|
|
185
|
-
maxTokens: 8192,
|
|
186
|
-
compat: { ...CLOUDFLARE_COMPAT, requiresThinkingAsText: true },
|
|
187
|
-
},
|
|
188
|
-
{
|
|
189
|
-
id: "@cf/openai/gpt-oss-20b",
|
|
190
|
-
name: "GPT-OSS 20B",
|
|
191
|
-
reasoning: true,
|
|
192
|
-
input: ["text"],
|
|
193
|
-
cost: { input: 0.2, output: 0.6, cacheRead: 0, cacheWrite: 0 },
|
|
194
|
-
contextWindow: 128000,
|
|
195
|
-
maxTokens: 8192,
|
|
196
|
-
compat: { ...CLOUDFLARE_COMPAT, requiresThinkingAsText: true },
|
|
197
|
-
},
|
|
198
|
-
{
|
|
199
|
-
id: "@cf/google/gemma-4-26b-a4b-it",
|
|
200
|
-
name: "Gemma 4 26B",
|
|
201
|
-
reasoning: true,
|
|
202
|
-
input: ["text", "image"],
|
|
203
|
-
cost: { input: 0.1, output: 0.3, cacheRead: 0, cacheWrite: 0 },
|
|
204
|
-
contextWindow: 256000,
|
|
205
|
-
maxTokens: 8192,
|
|
206
|
-
compat: { ...CLOUDFLARE_COMPAT, requiresThinkingAsText: true },
|
|
207
|
-
},
|
|
208
|
-
{
|
|
209
|
-
id: "@cf/google/gemma-3-12b-it",
|
|
210
|
-
name: "Gemma 3 12B",
|
|
211
|
-
reasoning: false,
|
|
212
|
-
input: ["text", "image"],
|
|
213
|
-
cost: { input: 0.345, output: 0.556, cacheRead: 0, cacheWrite: 0 },
|
|
214
|
-
contextWindow: 80000,
|
|
215
|
-
maxTokens: 8192,
|
|
216
|
-
compat: CLOUDFLARE_COMPAT,
|
|
217
|
-
},
|
|
218
|
-
{
|
|
219
|
-
id: "@cf/qwen/qwen3-30b-a3b-fp8",
|
|
220
|
-
name: "Qwen3 30B A3B",
|
|
221
|
-
reasoning: true,
|
|
222
|
-
input: ["text"],
|
|
223
|
-
cost: { input: 0.051, output: 0.34, cacheRead: 0, cacheWrite: 0 },
|
|
224
|
-
contextWindow: 32768,
|
|
225
|
-
maxTokens: 8192,
|
|
226
|
-
compat: { ...CLOUDFLARE_COMPAT, requiresThinkingAsText: true },
|
|
227
|
-
},
|
|
228
|
-
{
|
|
229
|
-
id: "@cf/qwen/qwen2.5-coder-32b-instruct",
|
|
230
|
-
name: "Qwen 2.5 Coder 32B",
|
|
231
|
-
reasoning: false,
|
|
232
|
-
input: ["text"],
|
|
233
|
-
cost: { input: 0.3, output: 0.3, cacheRead: 0, cacheWrite: 0 },
|
|
234
|
-
contextWindow: 32768,
|
|
235
|
-
maxTokens: 8192,
|
|
236
|
-
compat: CLOUDFLARE_COMPAT,
|
|
237
|
-
},
|
|
238
|
-
{
|
|
239
|
-
id: "@cf/qwen/qwq-32b",
|
|
240
|
-
name: "QwQ 32B (Reasoning)",
|
|
241
|
-
reasoning: true,
|
|
242
|
-
input: ["text"],
|
|
243
|
-
cost: { input: 0.3, output: 0.3, cacheRead: 0, cacheWrite: 0 },
|
|
244
|
-
contextWindow: 32768,
|
|
245
|
-
maxTokens: 8192,
|
|
246
|
-
compat: { ...CLOUDFLARE_COMPAT, requiresThinkingAsText: true },
|
|
247
|
-
},
|
|
248
|
-
{
|
|
249
|
-
id: "@cf/zai-org/glm-4.7-flash",
|
|
250
|
-
name: "GLM-4.7 Flash",
|
|
251
|
-
reasoning: false,
|
|
252
|
-
input: ["text"],
|
|
253
|
-
cost: { input: 0.06, output: 0.4, cacheRead: 0, cacheWrite: 0 },
|
|
254
|
-
contextWindow: 131072,
|
|
255
|
-
maxTokens: 8192,
|
|
256
|
-
compat: CLOUDFLARE_COMPAT,
|
|
257
|
-
},
|
|
258
|
-
{
|
|
259
|
-
id: "@cf/meta/llama-3.3-70b-instruct-fp8-fast",
|
|
260
|
-
name: "Llama 3.3 70B Fast",
|
|
261
|
-
reasoning: false,
|
|
262
|
-
input: ["text"],
|
|
263
|
-
cost: { input: 0.5, output: 0.5, cacheRead: 0, cacheWrite: 0 },
|
|
264
|
-
contextWindow: 131072,
|
|
265
|
-
maxTokens: 8192,
|
|
266
|
-
compat: CLOUDFLARE_COMPAT,
|
|
267
|
-
},
|
|
268
|
-
{
|
|
269
|
-
id: "@cf/meta/llama-3.1-405b-instruct",
|
|
270
|
-
name: "Llama 3.1 405B",
|
|
271
|
-
reasoning: false,
|
|
272
|
-
input: ["text"],
|
|
273
|
-
cost: { input: 2.0, output: 2.0, cacheRead: 0, cacheWrite: 0 },
|
|
274
|
-
contextWindow: 131072,
|
|
275
|
-
maxTokens: 8192,
|
|
276
|
-
compat: CLOUDFLARE_COMPAT,
|
|
277
|
-
},
|
|
278
|
-
{
|
|
279
|
-
id: "@cf/meta/llama-3.1-70b-instruct",
|
|
280
|
-
name: "Llama 3.1 70B",
|
|
281
|
-
reasoning: false,
|
|
282
|
-
input: ["text"],
|
|
283
|
-
cost: { input: 0.5, output: 0.5, cacheRead: 0, cacheWrite: 0 },
|
|
284
|
-
contextWindow: 131072,
|
|
285
|
-
maxTokens: 8192,
|
|
286
|
-
compat: CLOUDFLARE_COMPAT,
|
|
287
|
-
},
|
|
288
|
-
{
|
|
289
|
-
id: "@cf/meta/llama-3.2-11b-vision-instruct",
|
|
290
|
-
name: "Llama 3.2 11B Vision",
|
|
291
|
-
reasoning: false,
|
|
292
|
-
input: ["text", "image"],
|
|
293
|
-
cost: { input: 0.2, output: 0.2, cacheRead: 0, cacheWrite: 0 },
|
|
294
|
-
contextWindow: 128000,
|
|
295
|
-
maxTokens: 8192,
|
|
296
|
-
compat: CLOUDFLARE_COMPAT,
|
|
297
|
-
},
|
|
298
|
-
];
|
|
299
|
-
|
|
300
|
-
// =============================================================================
|
|
301
|
-
// Model metadata inference
|
|
302
|
-
// =============================================================================
|
|
303
|
-
|
|
304
|
-
interface CloudflareModel {
|
|
305
|
-
id: string;
|
|
306
|
-
name?: string;
|
|
307
|
-
description?: string;
|
|
308
|
-
task?: {
|
|
309
|
-
id?: string;
|
|
310
|
-
name?: string;
|
|
311
|
-
};
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
function isChatModel(modelId: string): boolean {
|
|
315
|
-
return !NON_CHAT_PATTERNS.some((pattern) => pattern.test(modelId));
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
function inferModelName(id: string): string {
|
|
319
|
-
// Extract the model name part after the last /
|
|
320
|
-
const namePart = id.split("/").pop() || id;
|
|
321
|
-
|
|
322
|
-
// Remove common suffixes
|
|
323
|
-
const clean = namePart
|
|
324
|
-
.replace(/-instruct$/i, "")
|
|
325
|
-
.replace(/-chat$/i, "")
|
|
326
|
-
.replace(/-it$/i, "")
|
|
327
|
-
.replace(/-awq$/i, " (AWQ)")
|
|
328
|
-
.replace(/-fp8$/i, " (FP8)")
|
|
329
|
-
.replace(/-fast$/i, " (Fast)")
|
|
330
|
-
.replace(/-lora$/i, " (LoRA)")
|
|
331
|
-
.replace(/-hf$/i, " (HF)")
|
|
332
|
-
.replace(/-v\d+\.\d+$/i, "");
|
|
333
|
-
|
|
334
|
-
// Convert to title case
|
|
335
|
-
return clean
|
|
336
|
-
.split("-")
|
|
337
|
-
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
|
|
338
|
-
.join(" ")
|
|
339
|
-
.replace(/\b(\d+(?:\.\d+)?)[bB]\b/g, "$1B");
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
function inferModelMetadata(id: string): Partial<ProviderModelConfig> {
|
|
343
|
-
const hasVision = /vision|multimodal|vl|llava/i.test(id);
|
|
344
|
-
const hasReasoning = /reason|r1|thinking|qwq|nemotron|oss/i.test(id);
|
|
345
|
-
|
|
346
|
-
// Default context windows by model family
|
|
347
|
-
let contextWindow = 32768;
|
|
348
|
-
let maxTokens = 4096;
|
|
349
|
-
|
|
350
|
-
if (/llama-3\.1|llama-3\.3|llama-4|gemma-4|kimi|nemotron/i.test(id)) {
|
|
351
|
-
contextWindow = 128000;
|
|
352
|
-
maxTokens = 8192;
|
|
353
|
-
}
|
|
354
|
-
if (/llama-3\.2-11b/i.test(id)) {
|
|
355
|
-
contextWindow = 128000;
|
|
356
|
-
maxTokens = 8192;
|
|
357
|
-
}
|
|
358
|
-
if (/gemma-3/i.test(id)) {
|
|
359
|
-
contextWindow = 80000;
|
|
360
|
-
maxTokens = 8192;
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
// Estimate costs based on model size (very rough approximation)
|
|
364
|
-
let inputCost = 0.1;
|
|
365
|
-
let outputCost = 0.3;
|
|
366
|
-
|
|
367
|
-
const sizeMatch = id.match(/(\d+)(?:\.\d+)?[bB]/);
|
|
368
|
-
if (sizeMatch) {
|
|
369
|
-
const size = parseInt(sizeMatch[1], 10);
|
|
370
|
-
if (size >= 100) {
|
|
371
|
-
inputCost = 0.5;
|
|
372
|
-
outputCost = 1.5;
|
|
373
|
-
} else if (size >= 70) {
|
|
374
|
-
inputCost = 0.5;
|
|
375
|
-
outputCost = 0.5;
|
|
376
|
-
} else if (size >= 30) {
|
|
377
|
-
inputCost = 0.3;
|
|
378
|
-
outputCost = 0.3;
|
|
379
|
-
} else if (size >= 8) {
|
|
380
|
-
inputCost = 0.2;
|
|
381
|
-
outputCost = 0.2;
|
|
382
|
-
}
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
// Override for specific known models
|
|
386
|
-
if (id.includes("llama-3.1-405b")) {
|
|
387
|
-
inputCost = 2.0;
|
|
388
|
-
outputCost = 2.0;
|
|
389
|
-
}
|
|
390
|
-
if (id.includes("kimi-k2.5")) {
|
|
391
|
-
inputCost = 0.6;
|
|
392
|
-
outputCost = 3.0;
|
|
393
|
-
}
|
|
394
|
-
if (id.includes("kimi-k2.6")) {
|
|
395
|
-
inputCost = 0.8;
|
|
396
|
-
outputCost = 4.0;
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
return {
|
|
400
|
-
name: inferModelName(id),
|
|
401
|
-
reasoning: hasReasoning,
|
|
402
|
-
input: hasVision ? (["text", "image"] as const) : (["text"] as const),
|
|
403
|
-
cost: { input: inputCost, output: outputCost, cacheRead: 0, cacheWrite: 0 },
|
|
404
|
-
contextWindow,
|
|
405
|
-
maxTokens,
|
|
406
|
-
compat: hasReasoning
|
|
407
|
-
? { ...CLOUDFLARE_COMPAT, requiresThinkingAsText: true }
|
|
408
|
-
: CLOUDFLARE_COMPAT,
|
|
409
|
-
};
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
// =============================================================================
|
|
413
|
-
// Dynamic model fetching
|
|
414
|
-
// =============================================================================
|
|
415
|
-
|
|
416
|
-
async function fetchCloudflareModels(
|
|
417
|
-
token: string,
|
|
418
|
-
accountId: string,
|
|
419
|
-
): Promise<ProviderModelConfig[]> {
|
|
420
|
-
const baseUrl = `https://api.cloudflare.com/client/v4/accounts/${accountId}`;
|
|
421
|
-
|
|
422
|
-
try {
|
|
423
|
-
const response = await fetchWithRetry(
|
|
424
|
-
`${baseUrl}/ai/models`,
|
|
425
|
-
{
|
|
426
|
-
headers: {
|
|
427
|
-
Authorization: `Bearer ${token}`,
|
|
428
|
-
"Content-Type": "application/json",
|
|
429
|
-
},
|
|
430
|
-
},
|
|
431
|
-
3,
|
|
432
|
-
1000,
|
|
433
|
-
DEFAULT_FETCH_TIMEOUT_MS,
|
|
434
|
-
);
|
|
435
|
-
|
|
436
|
-
if (!response.ok) {
|
|
437
|
-
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
438
|
-
}
|
|
439
|
-
|
|
440
|
-
const json = (await response.json()) as {
|
|
441
|
-
success?: boolean;
|
|
442
|
-
result?: CloudflareModel[];
|
|
443
|
-
errors?: Array<{ message: string }>;
|
|
444
|
-
};
|
|
445
|
-
|
|
446
|
-
if (!json.success || !json.result) {
|
|
447
|
-
throw new Error(
|
|
448
|
-
json.errors?.[0]?.message || "API returned unsuccessful response",
|
|
449
|
-
);
|
|
450
|
-
}
|
|
451
|
-
|
|
452
|
-
// Filter to chat/text generation models only
|
|
453
|
-
const chatModels = json.result.filter((m) => isChatModel(m.id));
|
|
454
|
-
|
|
455
|
-
// Map to ProviderModelConfig
|
|
456
|
-
const models = chatModels.map((m): ProviderModelConfig => {
|
|
457
|
-
const inferred = inferModelMetadata(m.id);
|
|
458
|
-
|
|
459
|
-
return {
|
|
460
|
-
id: m.id,
|
|
461
|
-
name: m.name || inferred.name || m.id,
|
|
462
|
-
reasoning: inferred.reasoning || false,
|
|
463
|
-
input: inferred.input || ["text"],
|
|
464
|
-
cost: inferred.cost || {
|
|
465
|
-
input: 0.1,
|
|
466
|
-
output: 0.3,
|
|
467
|
-
cacheRead: 0,
|
|
468
|
-
cacheWrite: 0,
|
|
469
|
-
},
|
|
470
|
-
contextWindow: inferred.contextWindow || 32768,
|
|
471
|
-
maxTokens: inferred.maxTokens || 4096,
|
|
472
|
-
compat: inferred.compat || CLOUDFLARE_COMPAT,
|
|
473
|
-
};
|
|
474
|
-
});
|
|
475
|
-
|
|
476
|
-
_logger.info(`[cloudflare] Fetched ${models.length} chat models from API`);
|
|
477
|
-
return models;
|
|
478
|
-
} catch (error) {
|
|
479
|
-
_logger.warn(
|
|
480
|
-
`[cloudflare] Failed to fetch models from API: ${error instanceof Error ? error.message : String(error)}`,
|
|
481
|
-
);
|
|
482
|
-
return [];
|
|
483
|
-
}
|
|
484
|
-
}
|
|
485
|
-
|
|
486
|
-
// =============================================================================
|
|
487
|
-
// Extension Entry Point
|
|
488
|
-
// =============================================================================
|
|
489
|
-
|
|
490
|
-
export default async function cloudflareProvider(pi: ExtensionAPI) {
|
|
491
|
-
const { token: apiToken, accountId } = getCloudflareAuth();
|
|
492
|
-
|
|
493
|
-
if (!apiToken) {
|
|
494
|
-
_logger.info(
|
|
495
|
-
"[cloudflare] CF_API_TOKEN or CLOUDFLARE_API_TOKEN not set. Provider will not be available.",
|
|
496
|
-
);
|
|
497
|
-
return;
|
|
498
|
-
}
|
|
499
|
-
|
|
500
|
-
if (!accountId) {
|
|
501
|
-
_logger.info(
|
|
502
|
-
"[cloudflare] CF_ACCOUNT_ID or CLOUDFLARE_ACCOUNT_ID not set. Provider will not be available.",
|
|
503
|
-
);
|
|
504
|
-
return;
|
|
505
|
-
}
|
|
506
|
-
|
|
507
|
-
// Try to fetch models dynamically, fall back to hardcoded list
|
|
508
|
-
let models = await fetchCloudflareModels(apiToken, accountId);
|
|
509
|
-
|
|
510
|
-
if (models.length === 0) {
|
|
511
|
-
_logger.info("[cloudflare] Using fallback model list");
|
|
512
|
-
models = FALLBACK_MODELS;
|
|
513
|
-
}
|
|
514
|
-
|
|
515
|
-
pi.registerProvider("cloudflare", {
|
|
516
|
-
baseUrl: `https://api.cloudflare.com/client/v4/accounts/${accountId}/ai/v1`,
|
|
517
|
-
apiKey: apiToken,
|
|
518
|
-
api: "openai-completions",
|
|
519
|
-
authHeader: true,
|
|
520
|
-
models,
|
|
521
|
-
});
|
|
522
|
-
|
|
523
|
-
_logger.info(
|
|
524
|
-
`[cloudflare] Provider registered with account: ${accountId.slice(0, 8)}... (${models.length} models)`,
|
|
525
|
-
);
|
|
526
|
-
}
|
package/providers/modal/modal.ts
DELETED
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Modal GLM Provider Extension
|
|
3
|
-
*
|
|
4
|
-
* Provides access to GLM models hosted on Modal's OpenAI-compatible endpoint.
|
|
5
|
-
* Requires MODAL_API_KEY (or modal_api_key in ~/.pi/free.json).
|
|
6
|
-
*
|
|
7
|
-
* Endpoint docs: https://modal.com/glm-5-endpoint
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
import type { ProviderModelConfig } from "@mariozechner/pi-coding-agent";
|
|
11
|
-
import { applyHidden, PROVIDER_MODAL } from "../../config.ts";
|
|
12
|
-
import { BASE_URL_MODAL, URL_MODAL_TOS } from "../../constants.ts";
|
|
13
|
-
import { createProvider } from "../../provider-factory.ts";
|
|
14
|
-
|
|
15
|
-
function getModalModels(): ProviderModelConfig[] {
|
|
16
|
-
return applyHidden(
|
|
17
|
-
[
|
|
18
|
-
{
|
|
19
|
-
id: "zai-org/GLM-5.1-FP8",
|
|
20
|
-
name: "GLM-5.1 FP8 (Modal)",
|
|
21
|
-
reasoning: true,
|
|
22
|
-
input: ["text"],
|
|
23
|
-
// Promotional/free-period pricing may change; keep conservative placeholders.
|
|
24
|
-
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
25
|
-
contextWindow: 128000,
|
|
26
|
-
maxTokens: 16384,
|
|
27
|
-
},
|
|
28
|
-
],
|
|
29
|
-
PROVIDER_MODAL,
|
|
30
|
-
);
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
export default function (pi: Parameters<typeof createProvider>[0]) {
|
|
34
|
-
return createProvider(pi, {
|
|
35
|
-
providerId: PROVIDER_MODAL,
|
|
36
|
-
baseUrl: BASE_URL_MODAL,
|
|
37
|
-
apiKeyEnvVar: "MODAL_API_KEY",
|
|
38
|
-
apiKeyConfigKey: "modal_api_key",
|
|
39
|
-
fetchModels: async () => getModalModels(),
|
|
40
|
-
tosUrl: URL_MODAL_TOS,
|
|
41
|
-
skipToggle: true, // Modal only has 1 model, no need for toggle
|
|
42
|
-
extraHeaders: {
|
|
43
|
-
"X-Title": "Pi",
|
|
44
|
-
"HTTP-Referer": "https://modal.com/",
|
|
45
|
-
},
|
|
46
|
-
});
|
|
47
|
-
}
|