pi-free 2.0.1 → 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 +179 -3
- package/README.md +495 -393
- package/config.ts +46 -54
- package/constants.ts +6 -0
- package/index.ts +39 -12
- package/lib/built-in-toggle.ts +63 -43
- 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/toggle-state.ts +86 -0
- package/lib/types.ts +101 -108
- package/package.json +8 -8
- package/provider-failover/benchmark-lookup.ts +637 -247
- package/provider-helper.ts +279 -260
- package/providers/cline/cline-auth.ts +473 -473
- package/providers/cline/cline-models.ts +129 -128
- package/providers/cline/cline.ts +311 -298
- package/providers/crofai/crofai.ts +170 -0
- package/providers/dynamic-built-in/index.ts +259 -308
- package/providers/kilo/kilo-auth.ts +155 -155
- package/providers/kilo/kilo-models.ts +2 -1
- package/providers/kilo/kilo.ts +263 -235
- package/providers/nvidia/nvidia.ts +476 -152
- package/providers/ollama/ollama.ts +130 -7
- 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 -368
- package/providers/modal/modal.ts +0 -44
package/config.ts
CHANGED
|
@@ -24,54 +24,45 @@ const _logger = createLogger("config");
|
|
|
24
24
|
|
|
25
25
|
interface PiFreeConfig {
|
|
26
26
|
nvidia_api_key?: string;
|
|
27
|
-
cloudflare_api_token?: string;
|
|
28
|
-
cloudflare_account_id?: string;
|
|
29
27
|
ollama_api_key?: string;
|
|
30
|
-
|
|
31
|
-
|
|
28
|
+
zenmux_api_key?: string;
|
|
29
|
+
crofai_api_key?: string;
|
|
32
30
|
mistral_api_key?: string;
|
|
33
31
|
groq_api_key?: string;
|
|
34
32
|
cerebras_api_key?: string;
|
|
35
33
|
xai_api_key?: string;
|
|
36
34
|
hf_token?: string;
|
|
37
|
-
openrouter_api_key?: string;
|
|
38
35
|
kilo_free_only?: boolean;
|
|
39
36
|
hidden_models?: string[];
|
|
40
37
|
free_only?: boolean;
|
|
41
38
|
kilo_show_paid?: boolean;
|
|
42
|
-
nvidia_show_paid?: boolean;
|
|
43
|
-
cloudflare_show_paid?: boolean;
|
|
44
39
|
ollama_show_paid?: boolean;
|
|
45
40
|
cline_show_paid?: boolean;
|
|
46
|
-
|
|
47
|
-
|
|
41
|
+
zenmux_show_paid?: boolean;
|
|
42
|
+
crofai_show_paid?: boolean;
|
|
48
43
|
openrouter_show_paid?: boolean;
|
|
49
44
|
opencode_show_paid?: boolean;
|
|
50
45
|
}
|
|
51
46
|
|
|
52
47
|
const CONFIG_TEMPLATE: PiFreeConfig = {
|
|
53
48
|
nvidia_api_key: "",
|
|
54
|
-
cloudflare_api_token: "",
|
|
55
|
-
cloudflare_account_id: "",
|
|
56
49
|
ollama_api_key: "",
|
|
57
|
-
|
|
58
|
-
|
|
50
|
+
zenmux_api_key: "",
|
|
51
|
+
crofai_api_key: "",
|
|
59
52
|
mistral_api_key: "",
|
|
60
53
|
groq_api_key: "",
|
|
61
54
|
cerebras_api_key: "",
|
|
62
55
|
xai_api_key: "",
|
|
63
56
|
hf_token: "",
|
|
64
|
-
|
|
57
|
+
|
|
65
58
|
kilo_free_only: false,
|
|
66
59
|
hidden_models: [],
|
|
67
60
|
free_only: true,
|
|
68
61
|
kilo_show_paid: false,
|
|
69
|
-
nvidia_show_paid: false,
|
|
70
|
-
cloudflare_show_paid: false,
|
|
71
62
|
ollama_show_paid: false,
|
|
72
63
|
cline_show_paid: false,
|
|
73
|
-
|
|
74
|
-
|
|
64
|
+
zenmux_show_paid: false,
|
|
65
|
+
crofai_show_paid: false,
|
|
75
66
|
openrouter_show_paid: false,
|
|
76
67
|
opencode_show_paid: false,
|
|
77
68
|
};
|
|
@@ -144,34 +135,22 @@ export function getKiloShowPaid(): boolean {
|
|
|
144
135
|
return resolveBool("KILO_SHOW_PAID", loadConfigFile().kilo_show_paid);
|
|
145
136
|
}
|
|
146
137
|
|
|
147
|
-
export function getNvidiaShowPaid(): boolean {
|
|
148
|
-
return resolveBool("NVIDIA_SHOW_PAID", loadConfigFile().nvidia_show_paid);
|
|
149
|
-
}
|
|
150
|
-
|
|
151
138
|
export function getClineShowPaid(): boolean {
|
|
152
139
|
return resolveBool("CLINE_SHOW_PAID", loadConfigFile().cline_show_paid);
|
|
153
140
|
}
|
|
154
141
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
return resolveBool("QWEN_SHOW_PAID", loadConfigFile().qwen_show_paid);
|
|
142
|
+
export function getZenmuxShowPaid(): boolean {
|
|
143
|
+
return resolveBool("ZENMUX_SHOW_PAID", loadConfigFile().zenmux_show_paid);
|
|
158
144
|
}
|
|
159
145
|
|
|
160
|
-
export function
|
|
161
|
-
return resolveBool("
|
|
146
|
+
export function getCrofaiShowPaid(): boolean {
|
|
147
|
+
return resolveBool("CROFAI_SHOW_PAID", loadConfigFile().crofai_show_paid);
|
|
162
148
|
}
|
|
163
149
|
|
|
164
150
|
export function getOllamaShowPaid(): boolean {
|
|
165
151
|
return resolveBool("OLLAMA_SHOW_PAID", loadConfigFile().ollama_show_paid);
|
|
166
152
|
}
|
|
167
153
|
|
|
168
|
-
export function getCloudflareShowPaid(): boolean {
|
|
169
|
-
return resolveBool(
|
|
170
|
-
"CLOUDFLARE_SHOW_PAID",
|
|
171
|
-
loadConfigFile().cloudflare_show_paid,
|
|
172
|
-
);
|
|
173
|
-
}
|
|
174
|
-
|
|
175
154
|
export function getOpenrouterShowPaid(): boolean {
|
|
176
155
|
return resolveBool(
|
|
177
156
|
"OPENROUTER_SHOW_PAID",
|
|
@@ -203,27 +182,16 @@ export function getNvidiaApiKey(): string | undefined {
|
|
|
203
182
|
return resolve("NVIDIA_API_KEY", loadConfigFile().nvidia_api_key);
|
|
204
183
|
}
|
|
205
184
|
|
|
206
|
-
export function
|
|
207
|
-
return resolve("
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
export function getOllamaApiKey(): string | undefined {
|
|
211
|
-
return resolve("OLLAMA_API_KEY", loadConfigFile().ollama_api_key);
|
|
185
|
+
export function getZenmuxApiKey(): string | undefined {
|
|
186
|
+
return resolve("ZENMUX_API_KEY", loadConfigFile().zenmux_api_key);
|
|
212
187
|
}
|
|
213
188
|
|
|
214
|
-
export function
|
|
215
|
-
return resolve("
|
|
189
|
+
export function getCrofaiApiKey(): string | undefined {
|
|
190
|
+
return resolve("CROFAI_API_KEY", loadConfigFile().crofai_api_key);
|
|
216
191
|
}
|
|
217
192
|
|
|
218
|
-
export function
|
|
219
|
-
return resolve(
|
|
220
|
-
"CLOUDFLARE_ACCOUNT_ID",
|
|
221
|
-
loadConfigFile().cloudflare_account_id,
|
|
222
|
-
);
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
export function getOpencodeApiKey(): string | undefined {
|
|
226
|
-
return resolve("OPENCODE_API_KEY", loadConfigFile().opencode_api_key);
|
|
193
|
+
export function getOllamaApiKey(): string | undefined {
|
|
194
|
+
return resolve("OLLAMA_API_KEY", loadConfigFile().ollama_api_key);
|
|
227
195
|
}
|
|
228
196
|
|
|
229
197
|
export function getMistralApiKey(): string | undefined {
|
|
@@ -246,18 +214,42 @@ export function getHfToken(): string | undefined {
|
|
|
246
214
|
return resolve("HF_TOKEN", loadConfigFile().hf_token);
|
|
247
215
|
}
|
|
248
216
|
|
|
217
|
+
/**
|
|
218
|
+
* OpenRouter key — pi's built-in provider reads from ~/.pi/agent/auth.json.
|
|
219
|
+
* pi-free only checks the env var to avoid stale keys from free.json.
|
|
220
|
+
*/
|
|
249
221
|
export function getOpenrouterApiKey(): string | undefined {
|
|
250
|
-
return
|
|
222
|
+
return process.env.OPENROUTER_API_KEY;
|
|
251
223
|
}
|
|
252
224
|
|
|
253
225
|
// =============================================================================
|
|
254
226
|
// Hidden models (re-reads config on every call)
|
|
255
227
|
// =============================================================================
|
|
256
228
|
|
|
257
|
-
|
|
229
|
+
/**
|
|
230
|
+
* Apply hidden models filter with provider scoping.
|
|
231
|
+
* Hidden models can be specified as:
|
|
232
|
+
* - "model-id" (global, applies to all providers - deprecated)
|
|
233
|
+
* - "provider/model-id" (provider-specific, preferred)
|
|
234
|
+
*/
|
|
235
|
+
export function applyHidden<T extends { id: string }>(
|
|
236
|
+
models: T[],
|
|
237
|
+
providerId?: string,
|
|
238
|
+
): T[] {
|
|
258
239
|
const hidden = new Set(loadConfigFile().hidden_models ?? []);
|
|
259
240
|
if (hidden.size === 0) return models;
|
|
260
|
-
|
|
241
|
+
|
|
242
|
+
return models.filter((m) => {
|
|
243
|
+
// Check provider-scoped ID (preferred format: "provider/model-id")
|
|
244
|
+
if (providerId && hidden.has(`${providerId}/${m.id}`)) {
|
|
245
|
+
return false;
|
|
246
|
+
}
|
|
247
|
+
// Check global ID (legacy format, still supported for backward compat)
|
|
248
|
+
if (hidden.has(m.id)) {
|
|
249
|
+
return false;
|
|
250
|
+
}
|
|
251
|
+
return true;
|
|
252
|
+
});
|
|
261
253
|
}
|
|
262
254
|
|
|
263
255
|
// =============================================================================
|
package/constants.ts
CHANGED
|
@@ -15,6 +15,8 @@ export const PROVIDER_OLLAMA = "ollama-cloud";
|
|
|
15
15
|
/** @deprecated Qwen provider is deprecated. The 1,000 req/day free tier is no longer available. */
|
|
16
16
|
export const PROVIDER_QWEN = "qwen";
|
|
17
17
|
export const PROVIDER_MODAL = "modal";
|
|
18
|
+
export const PROVIDER_ZENMUX = "zenmux";
|
|
19
|
+
export const PROVIDER_CROFAI = "crofai";
|
|
18
20
|
|
|
19
21
|
export const ALL_UNIQUE_PROVIDERS = [
|
|
20
22
|
PROVIDER_KILO,
|
|
@@ -24,6 +26,8 @@ export const ALL_UNIQUE_PROVIDERS = [
|
|
|
24
26
|
PROVIDER_QWEN,
|
|
25
27
|
PROVIDER_MODAL,
|
|
26
28
|
PROVIDER_OLLAMA,
|
|
29
|
+
PROVIDER_ZENMUX,
|
|
30
|
+
PROVIDER_CROFAI,
|
|
27
31
|
] as const;
|
|
28
32
|
|
|
29
33
|
// =============================================================================
|
|
@@ -38,6 +42,8 @@ export const BASE_URL_CLINE = "https://api.cline.bot/api/v1";
|
|
|
38
42
|
export const BASE_URL_MODAL = "https://api.us-west-2.modal.direct/v1";
|
|
39
43
|
export const BASE_URL_QWEN =
|
|
40
44
|
"https://dashscope.aliyuncs.com/compatible-mode/v1";
|
|
45
|
+
export const BASE_URL_ZENMUX = "https://zenmux.ai/api/v1";
|
|
46
|
+
export const BASE_URL_CROFAI = "https://crof.ai/v1";
|
|
41
47
|
|
|
42
48
|
/** Cline fetches free models from OpenRouter */
|
|
43
49
|
export const BASE_URL_OPENROUTER = "https://openrouter.ai/api/v1";
|
package/index.ts
CHANGED
|
@@ -9,8 +9,7 @@
|
|
|
9
9
|
* - Cline: Cline bot integration
|
|
10
10
|
* - NVIDIA: NVIDIA NIM hosting (free tier available)
|
|
11
11
|
* - Ollama Cloud: Ollama's cloud-hosted models with usage-based free tier
|
|
12
|
-
* -
|
|
13
|
-
* - Modal: Modal Labs hosting
|
|
12
|
+
* - ZenMux: Unified AI API gateway with 200+ models
|
|
14
13
|
*/
|
|
15
14
|
|
|
16
15
|
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
|
@@ -25,12 +24,11 @@ import {
|
|
|
25
24
|
} from "./lib/registry.ts";
|
|
26
25
|
// Import unique provider extensions (only providers NOT built into pi)
|
|
27
26
|
import cline from "./providers/cline/cline.ts";
|
|
28
|
-
import
|
|
27
|
+
import crofai from "./providers/crofai/crofai.ts";
|
|
29
28
|
import kilo from "./providers/kilo/kilo.ts";
|
|
30
|
-
import modal from "./providers/modal/modal.ts";
|
|
31
29
|
import nvidia from "./providers/nvidia/nvidia.ts";
|
|
32
30
|
import ollama from "./providers/ollama/ollama.ts";
|
|
33
|
-
import
|
|
31
|
+
import zenmux from "./providers/zenmux/zenmux.ts";
|
|
34
32
|
|
|
35
33
|
const _logger = createLogger("pi-free");
|
|
36
34
|
|
|
@@ -39,6 +37,39 @@ const _logger = createLogger("pi-free");
|
|
|
39
37
|
// =============================================================================
|
|
40
38
|
|
|
41
39
|
function setupGlobalCommands(pi: ExtensionAPI) {
|
|
40
|
+
// /toggle-free - Global free-only mode toggle
|
|
41
|
+
pi.registerCommand("toggle-free", {
|
|
42
|
+
description: "Toggle global free-only mode for all providers",
|
|
43
|
+
handler: async (_args, ctx) => {
|
|
44
|
+
const current = getGlobalFreeOnly();
|
|
45
|
+
const next = !current;
|
|
46
|
+
applyGlobalFilter(pi, next);
|
|
47
|
+
|
|
48
|
+
const registry = getProviderRegistry();
|
|
49
|
+
const providerCount = registry.size;
|
|
50
|
+
|
|
51
|
+
if (next) {
|
|
52
|
+
const totalFree = [...registry.values()].reduce(
|
|
53
|
+
(sum, e) => sum + e.stored.free.length,
|
|
54
|
+
0,
|
|
55
|
+
);
|
|
56
|
+
ctx.ui.notify(
|
|
57
|
+
`Free-only mode: ON (${totalFree} free models across ${providerCount} providers)`,
|
|
58
|
+
"info",
|
|
59
|
+
);
|
|
60
|
+
} else {
|
|
61
|
+
const totalAll = [...registry.values()].reduce(
|
|
62
|
+
(sum, e) => sum + (e.stored.all.length || e.stored.free.length),
|
|
63
|
+
0,
|
|
64
|
+
);
|
|
65
|
+
ctx.ui.notify(
|
|
66
|
+
`Free-only mode: OFF (all ${totalAll} models visible across ${providerCount} providers)`,
|
|
67
|
+
"info",
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
},
|
|
71
|
+
});
|
|
72
|
+
|
|
42
73
|
// /free-providers - Show free model counts by provider
|
|
43
74
|
pi.registerCommand("free-providers", {
|
|
44
75
|
description: "Show free/paid model counts for all pi-free providers",
|
|
@@ -106,19 +137,15 @@ export default async function (pi: ExtensionAPI) {
|
|
|
106
137
|
// Load all unique providers
|
|
107
138
|
// Each provider will register itself with the global toggle system
|
|
108
139
|
await Promise.allSettled([
|
|
109
|
-
cloudflare(pi),
|
|
110
|
-
modal(pi),
|
|
111
140
|
nvidia(pi),
|
|
112
141
|
kilo(pi),
|
|
113
142
|
ollama(pi),
|
|
114
|
-
// Qwen is deprecated
|
|
115
|
-
qwen(pi).catch((err) => {
|
|
116
|
-
_logger.warn("[pi-free] Qwen provider failed to load (deprecated)", err);
|
|
117
|
-
}),
|
|
118
143
|
cline(pi),
|
|
144
|
+
zenmux(pi),
|
|
145
|
+
crofai(pi),
|
|
119
146
|
]);
|
|
120
147
|
|
|
121
|
-
// Setup dynamic built-in providers (Mistral, Groq, Cerebras, xAI, Hugging Face
|
|
148
|
+
// Setup dynamic built-in providers (Mistral, Groq, Cerebras, xAI, Hugging Face)
|
|
122
149
|
// These only activate if the user has configured API keys (OpenRouter works without key too)
|
|
123
150
|
const { setupDynamicBuiltInProviders } = await import(
|
|
124
151
|
"./providers/dynamic-built-in/index.ts"
|
package/lib/built-in-toggle.ts
CHANGED
|
@@ -16,17 +16,10 @@ import type {
|
|
|
16
16
|
ExtensionAPI,
|
|
17
17
|
ProviderModelConfig,
|
|
18
18
|
} from "@mariozechner/pi-coding-agent";
|
|
19
|
-
import {
|
|
20
|
-
getOpencodeShowPaid,
|
|
21
|
-
getOpenrouterShowPaid,
|
|
22
|
-
saveConfig,
|
|
23
|
-
} from "../config.ts";
|
|
19
|
+
import { getOpencodeShowPaid, getOpenrouterShowPaid } from "../config.ts";
|
|
24
20
|
import { createLogger } from "./logger.ts";
|
|
25
|
-
import {
|
|
26
|
-
|
|
27
|
-
isFreeModel,
|
|
28
|
-
registerWithGlobalToggle,
|
|
29
|
-
} from "./registry.ts";
|
|
21
|
+
import { isFreeModel, registerWithGlobalToggle } from "./registry.ts";
|
|
22
|
+
import { createToggleState } from "./toggle-state.ts";
|
|
30
23
|
|
|
31
24
|
const _logger = createLogger("built-in-toggle");
|
|
32
25
|
|
|
@@ -49,10 +42,9 @@ const BUILT_IN_TOGGLE_PROVIDERS: BuiltInToggleConfig[] = [
|
|
|
49
42
|
// =============================================================================
|
|
50
43
|
|
|
51
44
|
interface BuiltInProviderState {
|
|
52
|
-
free: ProviderModelConfig[];
|
|
53
|
-
all: ProviderModelConfig[];
|
|
45
|
+
stored: { free: ProviderModelConfig[]; all: ProviderModelConfig[] };
|
|
54
46
|
reRegister: (models: ProviderModelConfig[]) => void;
|
|
55
|
-
|
|
47
|
+
toggleState: ReturnType<typeof createToggleState<ProviderModelConfig>>;
|
|
56
48
|
}
|
|
57
49
|
|
|
58
50
|
const providerStates = new Map<string, BuiltInProviderState>();
|
|
@@ -68,6 +60,7 @@ export function setupBuiltInProviderToggles(pi: ExtensionAPI): void {
|
|
|
68
60
|
for (const config of BUILT_IN_TOGGLE_PROVIDERS) {
|
|
69
61
|
registerToggleCommand(pi, config);
|
|
70
62
|
}
|
|
63
|
+
setupStatusBar(pi);
|
|
71
64
|
commandsRegistered = true;
|
|
72
65
|
}
|
|
73
66
|
|
|
@@ -87,7 +80,9 @@ export function setupBuiltInProviderToggles(pi: ExtensionAPI): void {
|
|
|
87
80
|
if (providerModels.length === 0) continue;
|
|
88
81
|
|
|
89
82
|
const allModels = providerModels.map(modelToProviderConfig);
|
|
90
|
-
const freeModels = allModels.filter(
|
|
83
|
+
const freeModels = allModels.filter((m) =>
|
|
84
|
+
isFreeModel({ ...m, provider: config.id }, allModels),
|
|
85
|
+
);
|
|
91
86
|
|
|
92
87
|
const baseUrl = providerModels[0].baseUrl;
|
|
93
88
|
const api = providerModels[0].api;
|
|
@@ -102,35 +97,29 @@ export function setupBuiltInProviderToggles(pi: ExtensionAPI): void {
|
|
|
102
97
|
});
|
|
103
98
|
};
|
|
104
99
|
|
|
100
|
+
const stored = { free: freeModels, all: allModels };
|
|
101
|
+
const toggleState = createToggleState<ProviderModelConfig>({
|
|
102
|
+
providerId: config.id,
|
|
103
|
+
initialShowPaid: config.getShowPaid(),
|
|
104
|
+
initialModels: stored,
|
|
105
|
+
});
|
|
106
|
+
|
|
105
107
|
providerStates.set(config.id, {
|
|
106
|
-
|
|
107
|
-
all: allModels,
|
|
108
|
+
stored,
|
|
108
109
|
reRegister,
|
|
109
|
-
|
|
110
|
+
toggleState,
|
|
110
111
|
});
|
|
111
112
|
|
|
112
|
-
|
|
113
|
-
registerWithGlobalToggle(
|
|
114
|
-
config.id,
|
|
115
|
-
{ free: freeModels, all: allModels },
|
|
116
|
-
reRegister,
|
|
117
|
-
true,
|
|
118
|
-
);
|
|
113
|
+
registerWithGlobalToggle(config.id, stored, reRegister, true);
|
|
119
114
|
|
|
120
115
|
_logger.info(
|
|
121
116
|
`[built-in-toggle] ${config.id}: captured ${allModels.length} models (${freeModels.length} free)`,
|
|
122
117
|
);
|
|
123
118
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
reRegister(freeModels);
|
|
129
|
-
_logger.info(
|
|
130
|
-
`[built-in-toggle] ${config.id}: applied free-only filter`,
|
|
131
|
-
);
|
|
132
|
-
}
|
|
133
|
-
}
|
|
119
|
+
const applied = toggleState.applyCurrent(reRegister);
|
|
120
|
+
_logger.info(
|
|
121
|
+
`[built-in-toggle] ${config.id}: applied ${applied.mode} mode with ${applied.models.length} models`,
|
|
122
|
+
);
|
|
134
123
|
}
|
|
135
124
|
});
|
|
136
125
|
}
|
|
@@ -156,21 +145,16 @@ function registerToggleCommand(
|
|
|
156
145
|
return;
|
|
157
146
|
}
|
|
158
147
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
// Persist preference
|
|
162
|
-
saveConfig({ [`${config.id}_show_paid`]: state.showPaid });
|
|
148
|
+
const applied = state.toggleState.toggle(state.reRegister);
|
|
163
149
|
|
|
164
|
-
if (
|
|
165
|
-
state.reRegister(state.all);
|
|
150
|
+
if (applied.mode === "all") {
|
|
166
151
|
ctx.ui.notify(
|
|
167
|
-
`${config.id}: showing all ${state.all.length} models`,
|
|
152
|
+
`${config.id}: showing all ${state.stored.all.length} models`,
|
|
168
153
|
"info",
|
|
169
154
|
);
|
|
170
155
|
} else {
|
|
171
|
-
state.reRegister(state.free);
|
|
172
156
|
ctx.ui.notify(
|
|
173
|
-
`${config.id}: showing ${state.free.length} free models`,
|
|
157
|
+
`${config.id}: showing ${state.stored.free.length} free models`,
|
|
174
158
|
"info",
|
|
175
159
|
);
|
|
176
160
|
}
|
|
@@ -197,6 +181,42 @@ function modelToProviderConfig(m: Model<Api>): ProviderModelConfig {
|
|
|
197
181
|
};
|
|
198
182
|
}
|
|
199
183
|
|
|
184
|
+
// =============================================================================
|
|
185
|
+
// Status bar for provider selection
|
|
186
|
+
// =============================================================================
|
|
187
|
+
|
|
188
|
+
function setupStatusBar(pi: ExtensionAPI): void {
|
|
189
|
+
pi.on("model_select", (_event, ctx) => {
|
|
190
|
+
const selected = _event.model?.provider;
|
|
191
|
+
|
|
192
|
+
// Clear status for all built-in toggle providers
|
|
193
|
+
for (const config of BUILT_IN_TOGGLE_PROVIDERS) {
|
|
194
|
+
if (selected !== config.id) {
|
|
195
|
+
ctx.ui.setStatus(`${config.id}-status`, undefined);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
if (!selected) return;
|
|
200
|
+
|
|
201
|
+
const state = providerStates.get(selected);
|
|
202
|
+
if (!state) return;
|
|
203
|
+
|
|
204
|
+
const free = state.stored.free.length;
|
|
205
|
+
const total = state.stored.all.length;
|
|
206
|
+
const paid = total - free;
|
|
207
|
+
const mode = state.toggleState.getCurrentMode();
|
|
208
|
+
let status: string;
|
|
209
|
+
if (paid === 0) {
|
|
210
|
+
status = `${selected}: ${free} free models`;
|
|
211
|
+
} else if (mode === "all") {
|
|
212
|
+
status = `${selected}: ${total} models (free + paid)`;
|
|
213
|
+
} else {
|
|
214
|
+
status = `${selected}: ${free} free \u00b7 ${paid} paid`;
|
|
215
|
+
}
|
|
216
|
+
ctx.ui.setStatus(`${selected}-status`, status);
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
|
|
200
220
|
function getApiKeyEnvForProvider(providerId: string): string {
|
|
201
221
|
const envMap: Record<string, string> = {
|
|
202
222
|
opencode: "OPENCODE_API_KEY",
|
package/lib/model-enhancer.ts
CHANGED
|
@@ -1,20 +1,20 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Model name enhancement helper
|
|
3
|
-
* Adds Coding Index scores to model names for display in /model
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import type { ProviderModelConfig } from "@mariozechner/pi-coding-agent";
|
|
7
|
-
import { enhanceModelNameWithCodingIndex } from "../provider-failover/benchmark-lookup.ts";
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* Enhance model names with Coding Index scores
|
|
11
|
-
* Use this before registering providers to show CI in /model list
|
|
12
|
-
*/
|
|
13
|
-
export function enhanceModelsWithCodingIndex(
|
|
14
|
-
models: ProviderModelConfig[],
|
|
15
|
-
): ProviderModelConfig[] {
|
|
16
|
-
return models.map((m) => ({
|
|
17
|
-
...m,
|
|
18
|
-
name: enhanceModelNameWithCodingIndex(m.name, m.id),
|
|
19
|
-
}));
|
|
20
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Model name enhancement helper
|
|
3
|
+
* Adds Coding Index scores to model names for display in /model
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { ProviderModelConfig } from "@mariozechner/pi-coding-agent";
|
|
7
|
+
import { enhanceModelNameWithCodingIndex } from "../provider-failover/benchmark-lookup.ts";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Enhance model names with Coding Index scores
|
|
11
|
+
* Use this before registering providers to show CI in /model list
|
|
12
|
+
*/
|
|
13
|
+
export function enhanceModelsWithCodingIndex(
|
|
14
|
+
models: ProviderModelConfig[],
|
|
15
|
+
): ProviderModelConfig[] {
|
|
16
|
+
return models.map((m) => ({
|
|
17
|
+
...m,
|
|
18
|
+
name: enhanceModelNameWithCodingIndex(m.name, m.id),
|
|
19
|
+
}));
|
|
20
|
+
}
|
package/lib/open-browser.ts
CHANGED
|
@@ -25,7 +25,7 @@ export function openBrowser(url: string): void {
|
|
|
25
25
|
"-NoProfile",
|
|
26
26
|
"-NonInteractive",
|
|
27
27
|
"-Command",
|
|
28
|
-
`Start-Process "${url.replace(/"/g,
|
|
28
|
+
`Start-Process "${url.replace(/[\\"]/g, "\\$&")}"`,
|
|
29
29
|
],
|
|
30
30
|
{ detached: true, shell: false, windowsHide: true },
|
|
31
31
|
).unref();
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { ProviderModelConfig } from "@mariozechner/pi-coding-agent";
|
|
2
|
+
|
|
3
|
+
export interface ProviderModelIdentity {
|
|
4
|
+
id: string;
|
|
5
|
+
name?: string;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export const DEEPSEEK_PROXY_COMPAT: NonNullable<ProviderModelConfig["compat"]> =
|
|
9
|
+
{
|
|
10
|
+
supportsStore: false,
|
|
11
|
+
supportsDeveloperRole: false,
|
|
12
|
+
supportsReasoningEffort: true,
|
|
13
|
+
requiresReasoningContentOnAssistantMessages: true,
|
|
14
|
+
thinkingFormat: "deepseek",
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export function isDeepSeekModel(model: ProviderModelIdentity): boolean {
|
|
18
|
+
const haystack = `${model.id} ${model.name ?? ""}`.toLowerCase();
|
|
19
|
+
return haystack.includes("deepseek");
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function isLikelyReasoningModel(model: ProviderModelIdentity): boolean {
|
|
23
|
+
const haystack = `${model.id} ${model.name ?? ""}`.toLowerCase();
|
|
24
|
+
return (
|
|
25
|
+
isDeepSeekModel(model) ||
|
|
26
|
+
haystack.includes("thinking") ||
|
|
27
|
+
haystack.includes("reasoning") ||
|
|
28
|
+
haystack.includes("reasoner") ||
|
|
29
|
+
haystack.includes("r1") ||
|
|
30
|
+
haystack.includes("qwq")
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* For gateway/proxy providers that mask the upstream DeepSeek base URL,
|
|
36
|
+
* add explicit compat so pi-ai preserves and replays reasoning_content.
|
|
37
|
+
*/
|
|
38
|
+
export function getProxyModelCompat(
|
|
39
|
+
model: ProviderModelIdentity,
|
|
40
|
+
): ProviderModelConfig["compat"] | undefined {
|
|
41
|
+
if (isDeepSeekModel(model)) {
|
|
42
|
+
return DEEPSEEK_PROXY_COMPAT;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return undefined;
|
|
46
|
+
}
|