pi-free 2.0.2 → 2.0.4
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/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-enhancer.ts +20 -20
- package/lib/open-browser.ts +1 -1
- package/lib/provider-compat.ts +46 -0
- package/lib/registry.ts +193 -144
- package/lib/types.ts +101 -108
- package/lib/util.ts +256 -256
- package/package.json +8 -8
- package/provider-failover/benchmark-lookup.ts +2 -2
- package/provider-helper.ts +19 -1
- package/providers/cline/cline-auth.ts +473 -473
- package/providers/cline/cline.ts +31 -4
- package/providers/crofai/crofai.ts +170 -0
- package/providers/dynamic-built-in/index.ts +258 -308
- package/providers/kilo/kilo-auth.ts +155 -155
- package/providers/kilo/kilo.ts +263 -235
- package/providers/nvidia/nvidia.ts +476 -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/zenmux/zenmux.ts +176 -0
- package/scripts/check-extensions.mjs +64 -55
- package/provider-factory.ts +0 -207
- package/providers/cloudflare/cloudflare.ts +0 -526
- package/providers/modal/modal.ts +0 -47
package/providers/cline/cline.ts
CHANGED
|
@@ -18,7 +18,7 @@ import type { OAuthCredentials } from "@mariozechner/pi-ai";
|
|
|
18
18
|
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
|
19
19
|
import { getClineShowPaid } from "../../config.ts";
|
|
20
20
|
import { BASE_URL_CLINE, PROVIDER_CLINE } from "../../constants.ts";
|
|
21
|
-
import { registerWithGlobalToggle } from "../../lib/registry.ts";
|
|
21
|
+
import { isFreeModel, registerWithGlobalToggle } from "../../lib/registry.ts";
|
|
22
22
|
import { createToggleState } from "../../lib/toggle-state.ts";
|
|
23
23
|
import { logWarning } from "../../lib/util.ts";
|
|
24
24
|
import { enhanceWithCI } from "../../provider-helper.ts";
|
|
@@ -132,7 +132,7 @@ function extractTaskBody(content: unknown): string {
|
|
|
132
132
|
if (!Array.isArray(content)) return "";
|
|
133
133
|
for (const p of content as any[]) {
|
|
134
134
|
if (p?.type !== "text" || typeof p?.text !== "string") continue;
|
|
135
|
-
const m = p.text.match(/<task
|
|
135
|
+
const m = p.text.match(/<task>([\s\S]*?)<\/task>/);
|
|
136
136
|
if (m?.[1]) return m[1].trim();
|
|
137
137
|
}
|
|
138
138
|
return "";
|
|
@@ -198,7 +198,9 @@ export default async function (pi: ExtensionAPI) {
|
|
|
198
198
|
logWarning("cline", "Failed to fetch models at startup", err);
|
|
199
199
|
return [];
|
|
200
200
|
});
|
|
201
|
-
let freeModels = allModels.filter((m) =>
|
|
201
|
+
let freeModels = allModels.filter((m) =>
|
|
202
|
+
isFreeModel({ ...m, provider: PROVIDER_CLINE }, allModels),
|
|
203
|
+
);
|
|
202
204
|
const stored = { free: freeModels, all: allModels };
|
|
203
205
|
const toggleState = createToggleState({
|
|
204
206
|
providerId: PROVIDER_CLINE,
|
|
@@ -246,6 +248,29 @@ export default async function (pi: ExtensionAPI) {
|
|
|
246
248
|
},
|
|
247
249
|
});
|
|
248
250
|
|
|
251
|
+
// ── Status bar for provider selection ─────────────────────────
|
|
252
|
+
|
|
253
|
+
pi.on("model_select", (_event, ctx) => {
|
|
254
|
+
if (_event.model?.provider !== PROVIDER_CLINE) {
|
|
255
|
+
ctx.ui.setStatus(`${PROVIDER_CLINE}-status`, undefined);
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
const free = stored.free.length;
|
|
260
|
+
const total = stored.all.length;
|
|
261
|
+
const paid = total - free;
|
|
262
|
+
const mode = toggleState.getCurrentMode();
|
|
263
|
+
let status: string;
|
|
264
|
+
if (paid === 0) {
|
|
265
|
+
status = `cline: ${free} free models`;
|
|
266
|
+
} else if (mode === "all") {
|
|
267
|
+
status = `cline: ${total} models (free + paid)`;
|
|
268
|
+
} else {
|
|
269
|
+
status = `cline: ${free} free \u00b7 ${paid} paid`;
|
|
270
|
+
}
|
|
271
|
+
ctx.ui.setStatus(`${PROVIDER_CLINE}-status`, status);
|
|
272
|
+
});
|
|
273
|
+
|
|
249
274
|
pi.on("before_agent_start", async (_event, ctx) => {
|
|
250
275
|
if (ctx.model?.provider !== PROVIDER_CLINE) return;
|
|
251
276
|
_currentTaskId = generateUlid();
|
|
@@ -263,7 +288,9 @@ export default async function (pi: ExtensionAPI) {
|
|
|
263
288
|
const fresh = await fetchClineModels(false);
|
|
264
289
|
if (fresh.length > 0) {
|
|
265
290
|
allModels = fresh;
|
|
266
|
-
freeModels = allModels.filter((m) =>
|
|
291
|
+
freeModels = allModels.filter((m) =>
|
|
292
|
+
isFreeModel({ ...m, provider: PROVIDER_CLINE }, allModels),
|
|
293
|
+
);
|
|
267
294
|
stored.all = allModels;
|
|
268
295
|
stored.free = freeModels;
|
|
269
296
|
toggleState.setModels(stored);
|
|
@@ -0,0 +1,170 @@
|
|
|
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 {
|
|
19
|
+
ExtensionAPI,
|
|
20
|
+
ProviderModelConfig,
|
|
21
|
+
} from "@mariozechner/pi-coding-agent";
|
|
22
|
+
import { getCrofaiApiKey, getCrofaiShowPaid } from "../../config.ts";
|
|
23
|
+
import {
|
|
24
|
+
BASE_URL_CROFAI,
|
|
25
|
+
DEFAULT_FETCH_TIMEOUT_MS,
|
|
26
|
+
PROVIDER_CROFAI,
|
|
27
|
+
} from "../../constants.ts";
|
|
28
|
+
import { createLogger } from "../../lib/logger.ts";
|
|
29
|
+
import {
|
|
30
|
+
getProxyModelCompat,
|
|
31
|
+
isLikelyReasoningModel,
|
|
32
|
+
} from "../../lib/provider-compat.ts";
|
|
33
|
+
import { isFreeModel, registerWithGlobalToggle } from "../../lib/registry.ts";
|
|
34
|
+
import { fetchWithRetry } from "../../lib/util.ts";
|
|
35
|
+
import { createReRegister, setupProvider } from "../../provider-helper.ts";
|
|
36
|
+
|
|
37
|
+
const _logger = createLogger("crofai");
|
|
38
|
+
|
|
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
|
+
// =============================================================================
|
|
107
|
+
// Extension Entry Point
|
|
108
|
+
// =============================================================================
|
|
109
|
+
|
|
110
|
+
export default async function crofaiProvider(pi: ExtensionAPI) {
|
|
111
|
+
const apiKey = getCrofaiApiKey();
|
|
112
|
+
|
|
113
|
+
if (!apiKey) {
|
|
114
|
+
_logger.info(
|
|
115
|
+
"[crofai] Skipping - CROFAI_API_KEY not set (env var or ~/.pi/free.json)",
|
|
116
|
+
);
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Fetch models
|
|
121
|
+
const allModels = await fetchCrofaiModels(apiKey);
|
|
122
|
+
|
|
123
|
+
if (allModels.length === 0) {
|
|
124
|
+
_logger.warn("[crofai] No models available");
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Use isFreeModel with allModels for proper detection
|
|
129
|
+
// CrofAI doesn't expose pricing (all costs are $0), so Route B will be used:
|
|
130
|
+
// FREE only if "free" in name
|
|
131
|
+
const freeModels = allModels.filter((m) =>
|
|
132
|
+
isFreeModel({ ...m, provider: PROVIDER_CROFAI }, allModels),
|
|
133
|
+
);
|
|
134
|
+
|
|
135
|
+
const stored = { free: freeModels, all: allModels };
|
|
136
|
+
|
|
137
|
+
_logger.info(
|
|
138
|
+
`[crofai] Registered ${allModels.length} models (${freeModels.length} free)`,
|
|
139
|
+
);
|
|
140
|
+
|
|
141
|
+
// Create re-register function
|
|
142
|
+
const reRegister = createReRegister(pi, {
|
|
143
|
+
providerId: PROVIDER_CROFAI,
|
|
144
|
+
baseUrl: BASE_URL_CROFAI,
|
|
145
|
+
apiKey,
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
// Register with global toggle
|
|
149
|
+
registerWithGlobalToggle(PROVIDER_CROFAI, stored, reRegister, true);
|
|
150
|
+
|
|
151
|
+
// Setup provider with toggle command
|
|
152
|
+
setupProvider(
|
|
153
|
+
pi,
|
|
154
|
+
{
|
|
155
|
+
providerId: PROVIDER_CROFAI,
|
|
156
|
+
initialShowPaid: getCrofaiShowPaid(),
|
|
157
|
+
reRegister: (models, _stored) => {
|
|
158
|
+
if (_stored) {
|
|
159
|
+
stored.free = _stored.free;
|
|
160
|
+
stored.all = _stored.all;
|
|
161
|
+
}
|
|
162
|
+
reRegister(models);
|
|
163
|
+
},
|
|
164
|
+
},
|
|
165
|
+
stored,
|
|
166
|
+
);
|
|
167
|
+
|
|
168
|
+
// Initial registration
|
|
169
|
+
reRegister(freeModels);
|
|
170
|
+
}
|