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.
@@ -1,235 +1,263 @@
1
- /**
2
- * Kilo Provider Extension
3
- *
4
- * Provides access to 300+ AI models via the Kilo Gateway (OpenRouter-compatible).
5
- * Fetches ALL models at startup (like Cline/OpenRouter), defaults to free-only view.
6
- * Run /login kilo or use /toggle-kilo to access paid models.
7
- *
8
- * Responds to global free-only filter for free/paid model filtering.
9
- *
10
- * Usage:
11
- * pi install git:github.com/apmantza/pi-free
12
- * # Free models visible immediately; /login kilo for paid access
13
- */
14
-
15
- import type { Api, Model, OAuthCredentials } from "@mariozechner/pi-ai";
16
- import type {
17
- ExtensionAPI,
18
- ProviderModelConfig,
19
- } from "@mariozechner/pi-coding-agent";
20
- import {
21
- getKiloFreeOnly,
22
- getKiloShowPaid,
23
- PROVIDER_KILO,
24
- } from "../../config.ts";
25
- import { URL_KILO_TOS } from "../../constants.ts";
26
- import { isFreeModel, registerWithGlobalToggle } from "../../lib/registry.ts";
27
- import { cleanModelName, logWarning } from "../../lib/util.ts";
28
- import {
29
- createCtxReRegister,
30
- createReRegister,
31
- enhanceWithCI,
32
- type StoredModels,
33
- } from "../../provider-helper.ts";
34
- import { loginKilo, refreshKiloToken } from "./kilo-auth.ts";
35
- import { fetchKiloModels, KILO_GATEWAY_BASE } from "./kilo-models.ts";
36
-
37
- const KILO_PROVIDER_CONFIG = {
38
- providerId: PROVIDER_KILO,
39
- baseUrl: KILO_GATEWAY_BASE,
40
- apiKey: "KILO_API_KEY",
41
- headers: {
42
- "X-KILOCODE-EDITORNAME": "Pi",
43
- },
44
- };
45
-
46
- export default async function (pi: ExtensionAPI) {
47
- // Try to fetch ALL models at startup (like Cline/OpenRouter)
48
- // If no API key, this will return free models only
49
- let allModels: ProviderModelConfig[] = [];
50
- let freeModels: ProviderModelConfig[] = [];
51
-
52
- try {
53
- // Fetch all models (returns free-only if no auth, all if auth available)
54
- allModels = await fetchKiloModels({ freeOnly: false });
55
- // Derive free list from cost
56
- freeModels = allModels.filter(isFreeModel);
57
- } catch (error) {
58
- logWarning("kilo", "Failed to fetch models at startup", error);
59
- // Fallback: try to fetch just free models
60
- try {
61
- freeModels = await fetchKiloModels({ freeOnly: true });
62
- } catch (e) {
63
- logWarning("kilo", "Failed to fetch free models", e);
64
- }
65
- }
66
-
67
- // State tracking
68
- const kiloShowPaid = getKiloShowPaid();
69
- const kiloFreeOnly = getKiloFreeOnly();
70
- let showPaidModels = kiloShowPaid;
71
- let currentModels = kiloShowPaid && !kiloFreeOnly ? allModels : freeModels;
72
-
73
- // Shared model storage for global toggle
74
- const stored: StoredModels = { free: freeModels, all: allModels };
75
-
76
- // Create re-register function
77
- const reRegister = createReRegister(pi, {
78
- ...KILO_PROVIDER_CONFIG,
79
- });
80
-
81
- // Register with global toggle system
82
- registerWithGlobalToggle(
83
- PROVIDER_KILO,
84
- stored,
85
- reRegister,
86
- !!process.env.KILO_API_KEY,
87
- );
88
-
89
- // OAuth config for Kilo
90
- const oauthConfig = {
91
- name: "Kilo",
92
- login: async (callbacks: any) => {
93
- const cred = await loginKilo(callbacks);
94
- try {
95
- // Fetch all models with the new token
96
- const newModels = await fetchKiloModels({
97
- token: cred.access,
98
- freeOnly: false,
99
- });
100
- allModels = newModels;
101
- stored.all = allModels;
102
- freeModels = allModels.filter(isFreeModel);
103
- stored.free = freeModels;
104
-
105
- // Update global toggle registration with new lists
106
- const globalReRegister = createReRegister(pi, {
107
- ...KILO_PROVIDER_CONFIG,
108
- });
109
- registerWithGlobalToggle(PROVIDER_KILO, stored, globalReRegister, true);
110
-
111
- // If paid mode is enabled, show all models
112
- if (showPaidModels && !kiloFreeOnly) {
113
- currentModels = allModels;
114
- globalReRegister(allModels);
115
- }
116
- } catch (error) {
117
- logWarning("kilo", "Failed to fetch models after login", error);
118
- }
119
- return cred;
120
- },
121
- refreshToken: refreshKiloToken,
122
- getApiKey: (cred: OAuthCredentials) => cred.access,
123
- modifyModels: (models: Model<Api>[], _cred: OAuthCredentials) => {
124
- if (!showPaidModels || kiloFreeOnly || allModels.length === 0) {
125
- return models;
126
- }
127
- const template = models.find((m) => m.provider === PROVIDER_KILO);
128
- if (!template) return models;
129
- const nonKilo = models.filter((m) => m.provider !== PROVIDER_KILO);
130
- const fullModels = allModels.map((m) => ({
131
- ...template,
132
- id: m.id,
133
- name: cleanModelName(m.name),
134
- reasoning: m.reasoning,
135
- input: m.input,
136
- cost: m.cost,
137
- contextWindow: m.contextWindow,
138
- maxTokens: m.maxTokens,
139
- }));
140
- return [...nonKilo, ...fullModels];
141
- },
142
- };
143
-
144
- // Register initial provider (default to free models)
145
- pi.registerProvider(PROVIDER_KILO, {
146
- baseUrl: KILO_GATEWAY_BASE,
147
- apiKey: "KILO_API_KEY",
148
- api: "openai-completions" as const,
149
- headers: {
150
- "X-KILOCODE-EDITORNAME": "Pi",
151
- "User-Agent": "pi-free-providers",
152
- },
153
- models: enhanceWithCI(currentModels),
154
- oauth: oauthConfig,
155
- });
156
-
157
- // Registration complete - models registered silently (use LOG_LEVEL=info to see details)
158
-
159
- // Per-provider toggle command
160
- pi.registerCommand("toggle-kilo", {
161
- description: "Toggle between free and all Kilo models",
162
- handler: async (_args, ctx) => {
163
- showPaidModels = !showPaidModels;
164
-
165
- // Determine which models to show
166
- const modelsToShow =
167
- showPaidModels && allModels.length > 0 ? allModels : freeModels;
168
-
169
- currentModels = modelsToShow;
170
- reRegister(modelsToShow);
171
-
172
- const freeCount = freeModels.length;
173
- const paidCount = allModels.length - freeCount;
174
-
175
- if (showPaidModels && allModels.length > 0) {
176
- ctx.ui.notify(
177
- `kilo: showing all ${allModels.length} models (${freeCount} free, ${paidCount} paid)`,
178
- "info",
179
- );
180
- } else {
181
- ctx.ui.notify(
182
- `kilo: showing ${freeCount} free models (${paidCount} paid hidden)`,
183
- "info",
184
- );
185
- }
186
- },
187
- });
188
-
189
- // ToS notice on first use
190
- let tosShown = false;
191
- pi.on("model_select", async (_event, ctx) => {
192
- if (tosShown || ctx.model?.provider !== PROVIDER_KILO) return;
193
- tosShown = true;
194
- const cred = ctx.modelRegistry.authStorage.get(PROVIDER_KILO);
195
- if (cred?.type === "oauth") return;
196
- const paidCount = allModels.length - freeModels.length;
197
- if (paidCount > 0) {
198
- ctx.ui.notify(
199
- `Kilo: ${freeModels.length} free models shown. Use /toggle-kilo or /login kilo for ${paidCount} paid models. Terms: ${URL_KILO_TOS}`,
200
- "info",
201
- );
202
- }
203
- });
204
-
205
- // Refresh models on session start if authenticated
206
- pi.on("session_start", async (_event, ctx) => {
207
- const cred = ctx.modelRegistry.authStorage.get(PROVIDER_KILO);
208
-
209
- if (cred?.type === "oauth") {
210
- try {
211
- const newModels = await fetchKiloModels({
212
- token: cred.access,
213
- freeOnly: false,
214
- });
215
- allModels = newModels;
216
- stored.all = allModels;
217
- freeModels = allModels.filter(isFreeModel);
218
- stored.free = freeModels;
219
-
220
- // Update global toggle registration
221
- const ctxReRegister = createCtxReRegister(ctx as any, {
222
- ...KILO_PROVIDER_CONFIG,
223
- });
224
- registerWithGlobalToggle(PROVIDER_KILO, stored, ctxReRegister, true);
225
-
226
- // Apply current view mode
227
- if (showPaidModels && !getKiloFreeOnly()) {
228
- ctxReRegister(allModels);
229
- }
230
- } catch (error) {
231
- logWarning("kilo", "Failed to refresh models at session start", error);
232
- }
233
- }
234
- });
235
- }
1
+ /**
2
+ * Kilo Provider Extension
3
+ *
4
+ * Provides access to 300+ AI models via the Kilo Gateway (OpenRouter-compatible).
5
+ * Fetches ALL models at startup (like Cline/OpenRouter), defaults to free-only view.
6
+ * Run /login kilo or use /toggle-kilo to access paid models.
7
+ *
8
+ * Responds to global free-only filter for free/paid model filtering.
9
+ *
10
+ * Usage:
11
+ * pi install git:github.com/apmantza/pi-free
12
+ * # Free models visible immediately; /login kilo for paid access
13
+ */
14
+
15
+ import type { Api, Model, OAuthCredentials } from "@mariozechner/pi-ai";
16
+ import type {
17
+ ExtensionAPI,
18
+ ProviderModelConfig,
19
+ } from "@mariozechner/pi-coding-agent";
20
+ import {
21
+ getKiloFreeOnly,
22
+ getKiloShowPaid,
23
+ PROVIDER_KILO,
24
+ saveConfig,
25
+ } from "../../config.ts";
26
+ import { URL_KILO_TOS } from "../../constants.ts";
27
+ import { isFreeModel, registerWithGlobalToggle } from "../../lib/registry.ts";
28
+ import { cleanModelName, logWarning } from "../../lib/util.ts";
29
+ import {
30
+ createCtxReRegister,
31
+ createReRegister,
32
+ enhanceWithCI,
33
+ type StoredModels,
34
+ } from "../../provider-helper.ts";
35
+ import { loginKilo, refreshKiloToken } from "./kilo-auth.ts";
36
+ import { fetchKiloModels, KILO_GATEWAY_BASE } from "./kilo-models.ts";
37
+
38
+ const KILO_PROVIDER_CONFIG = {
39
+ providerId: PROVIDER_KILO,
40
+ baseUrl: KILO_GATEWAY_BASE,
41
+ apiKey: "KILO_API_KEY",
42
+ headers: {
43
+ "X-KILOCODE-EDITORNAME": "Pi",
44
+ },
45
+ };
46
+
47
+ export default async function (pi: ExtensionAPI) {
48
+ // Try to fetch ALL models at startup (like Cline/OpenRouter)
49
+ // If no API key, this will return free models only
50
+ let allModels: ProviderModelConfig[] = [];
51
+ let freeModels: ProviderModelConfig[] = [];
52
+
53
+ try {
54
+ // Fetch all models (returns free-only if no auth, all if auth available)
55
+ allModels = await fetchKiloModels({ freeOnly: false });
56
+ // Derive free list using isFreeModel with allModels for detection
57
+ freeModels = allModels.filter((m) =>
58
+ isFreeModel({ ...m, provider: PROVIDER_KILO }, allModels),
59
+ );
60
+ } catch (error) {
61
+ logWarning("kilo", "Failed to fetch models at startup", error);
62
+ // Fallback: try to fetch just free models
63
+ try {
64
+ freeModels = await fetchKiloModels({ freeOnly: true });
65
+ } catch (e) {
66
+ logWarning("kilo", "Failed to fetch free models", e);
67
+ }
68
+ }
69
+
70
+ // State tracking
71
+ const kiloShowPaid = getKiloShowPaid();
72
+ const kiloFreeOnly = getKiloFreeOnly();
73
+ let showPaidModels = kiloShowPaid;
74
+ let currentModels = kiloShowPaid && !kiloFreeOnly ? allModels : freeModels;
75
+
76
+ // Shared model storage for global toggle
77
+ const stored: StoredModels = { free: freeModels, all: allModels };
78
+
79
+ // Create re-register function
80
+ const reRegister = createReRegister(pi, {
81
+ ...KILO_PROVIDER_CONFIG,
82
+ });
83
+
84
+ // Register with global toggle system
85
+ registerWithGlobalToggle(
86
+ PROVIDER_KILO,
87
+ stored,
88
+ reRegister,
89
+ !!process.env.KILO_API_KEY,
90
+ );
91
+
92
+ // OAuth config for Kilo
93
+ const oauthConfig = {
94
+ name: "Kilo",
95
+ login: async (callbacks: any) => {
96
+ const cred = await loginKilo(callbacks);
97
+ try {
98
+ // Fetch all models with the new token
99
+ const newModels = await fetchKiloModels({
100
+ token: cred.access,
101
+ freeOnly: false,
102
+ });
103
+ allModels = newModels;
104
+ stored.all = allModels;
105
+ freeModels = allModels.filter((m) =>
106
+ isFreeModel({ ...m, provider: PROVIDER_KILO }, allModels),
107
+ );
108
+ stored.free = freeModels;
109
+
110
+ // Update global toggle registration with new lists
111
+ const globalReRegister = createReRegister(pi, {
112
+ ...KILO_PROVIDER_CONFIG,
113
+ });
114
+ registerWithGlobalToggle(PROVIDER_KILO, stored, globalReRegister, true);
115
+
116
+ // If paid mode is enabled, show all models
117
+ if (showPaidModels && !kiloFreeOnly) {
118
+ currentModels = allModels;
119
+ globalReRegister(allModels);
120
+ }
121
+ } catch (error) {
122
+ logWarning("kilo", "Failed to fetch models after login", error);
123
+ }
124
+ return cred;
125
+ },
126
+ refreshToken: refreshKiloToken,
127
+ getApiKey: (cred: OAuthCredentials) => cred.access,
128
+ modifyModels: (models: Model<Api>[], _cred: OAuthCredentials) => {
129
+ if (!showPaidModels || kiloFreeOnly || allModels.length === 0) {
130
+ return models;
131
+ }
132
+ const template = models.find((m) => m.provider === PROVIDER_KILO);
133
+ if (!template) return models;
134
+ const nonKilo = models.filter((m) => m.provider !== PROVIDER_KILO);
135
+ const fullModels = allModels.map((m) => ({
136
+ ...template,
137
+ id: m.id,
138
+ name: cleanModelName(m.name),
139
+ reasoning: m.reasoning,
140
+ input: m.input,
141
+ cost: m.cost,
142
+ contextWindow: m.contextWindow,
143
+ maxTokens: m.maxTokens,
144
+ }));
145
+ return [...nonKilo, ...fullModels];
146
+ },
147
+ };
148
+
149
+ // Register initial provider (default to free models)
150
+ pi.registerProvider(PROVIDER_KILO, {
151
+ baseUrl: KILO_GATEWAY_BASE,
152
+ apiKey: "KILO_API_KEY",
153
+ api: "openai-completions" as const,
154
+ headers: {
155
+ "X-KILOCODE-EDITORNAME": "Pi",
156
+ "User-Agent": "pi-free-providers",
157
+ },
158
+ models: enhanceWithCI(currentModels),
159
+ oauth: oauthConfig,
160
+ });
161
+
162
+ // Registration complete - models registered silently (use LOG_LEVEL=info to see details)
163
+
164
+ // Per-provider toggle command
165
+ pi.registerCommand("toggle-kilo", {
166
+ description: "Toggle between free and all Kilo models",
167
+ handler: async (_args, ctx) => {
168
+ showPaidModels = !showPaidModels;
169
+ saveConfig({ kilo_show_paid: showPaidModels });
170
+
171
+ // Determine which models to show
172
+ const modelsToShow =
173
+ showPaidModels && allModels.length > 0 ? allModels : freeModels;
174
+
175
+ currentModels = modelsToShow;
176
+ reRegister(modelsToShow);
177
+
178
+ const freeCount = freeModels.length;
179
+ const paidCount = allModels.length - freeCount;
180
+
181
+ if (showPaidModels && allModels.length > 0) {
182
+ ctx.ui.notify(
183
+ `kilo: showing all ${allModels.length} models (${freeCount} free, ${paidCount} paid)`,
184
+ "info",
185
+ );
186
+ } else {
187
+ ctx.ui.notify(
188
+ `kilo: showing ${freeCount} free models (${paidCount} paid hidden)`,
189
+ "info",
190
+ );
191
+ }
192
+ },
193
+ });
194
+
195
+ // Status bar + ToS notice on provider selection
196
+ let tosShown = false;
197
+ pi.on("model_select", async (_event, ctx) => {
198
+ if (ctx.model?.provider !== PROVIDER_KILO) {
199
+ ctx.ui.setStatus(`${PROVIDER_KILO}-status`, undefined);
200
+ return;
201
+ }
202
+
203
+ // Build status line
204
+ const free = freeModels.length;
205
+ const total = allModels.length;
206
+ const paid = total - free;
207
+ let status: string;
208
+ if (paid === 0) {
209
+ status = `kilo: ${free} free models`;
210
+ } else if (showPaidModels) {
211
+ status = `kilo: ${total} models (free + paid)`;
212
+ } else {
213
+ status = `kilo: ${free} free \u00b7 ${paid} paid`;
214
+ }
215
+ ctx.ui.setStatus(`${PROVIDER_KILO}-status`, status);
216
+
217
+ // ToS notice (once)
218
+ if (tosShown) return;
219
+ tosShown = true;
220
+ const cred = ctx.modelRegistry.authStorage.get(PROVIDER_KILO);
221
+ if (cred?.type === "oauth") return;
222
+ const paidCount = allModels.length - freeModels.length;
223
+ if (paidCount > 0) {
224
+ ctx.ui.notify(
225
+ `Kilo: ${freeModels.length} free models shown. Use /toggle-kilo or /login kilo for ${paidCount} paid models. Terms: ${URL_KILO_TOS}`,
226
+ "info",
227
+ );
228
+ }
229
+ });
230
+
231
+ // Refresh models on session start if authenticated
232
+ pi.on("session_start", async (_event, ctx) => {
233
+ const cred = ctx.modelRegistry.authStorage.get(PROVIDER_KILO);
234
+
235
+ if (cred?.type === "oauth") {
236
+ try {
237
+ const newModels = await fetchKiloModels({
238
+ token: cred.access,
239
+ freeOnly: false,
240
+ });
241
+ allModels = newModels;
242
+ stored.all = allModels;
243
+ freeModels = allModels.filter((m) =>
244
+ isFreeModel({ ...m, provider: PROVIDER_KILO }, allModels),
245
+ );
246
+ stored.free = freeModels;
247
+
248
+ // Update global toggle registration
249
+ const ctxReRegister = createCtxReRegister(ctx as any, {
250
+ ...KILO_PROVIDER_CONFIG,
251
+ });
252
+ registerWithGlobalToggle(PROVIDER_KILO, stored, ctxReRegister, true);
253
+
254
+ // Apply current view mode
255
+ if (showPaidModels && !getKiloFreeOnly()) {
256
+ ctxReRegister(allModels);
257
+ }
258
+ } catch (error) {
259
+ logWarning("kilo", "Failed to refresh models at session start", error);
260
+ }
261
+ }
262
+ });
263
+ }