pi-free 2.0.0 → 2.0.2

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,7 +1,7 @@
1
1
  /**
2
2
  * Shared provider setup helpers for pi-free-providers.
3
3
  * Extracts the common boilerplate pattern repeated across providers:
4
- * - /{provider}-toggle command to switch between free/paid models
4
+ * - toggle-{provider} command to switch between free/paid models
5
5
  * - model_select handler (clear status for other providers)
6
6
  * - turn_end handler (provider-specific error hook)
7
7
  * - before_agent_start handler (one-time ToS notice)
@@ -31,7 +31,7 @@ export interface ProviderSetupConfig {
31
31
  /** Initial mode - auto-detected from config at startup. */
32
32
  initialShowPaid?: boolean;
33
33
  /**
34
- * Called by /{provider}-toggle command to re-register
34
+ * Called by toggle-{provider} command to re-register
35
35
  * the provider with the given model set.
36
36
  */
37
37
  reRegister: (models: ProviderModelConfig[], stored: StoredModels) => void;
@@ -42,7 +42,7 @@ export interface ProviderSetupConfig {
42
42
  ui: { notify: (m: string, t: "info" | "warning" | "error") => void };
43
43
  },
44
44
  ) => Promise<boolean>;
45
- /** When true, skips creating the /{provider}-toggle command. Useful for providers with only one model. */
45
+ /** When true, skips creating the toggle-{provider} command. Useful for providers with only one model. */
46
46
  skipToggle?: boolean;
47
47
  }
48
48
 
@@ -79,10 +79,11 @@ export interface OpenAICompatibleConfig {
79
79
  */
80
80
  export function enhanceWithCI(
81
81
  models: ProviderModelConfig[],
82
+ providerId?: string,
82
83
  ): ProviderModelConfig[] {
83
84
  return models.map((m) => ({
84
85
  ...m,
85
- name: enhanceModelNameWithCodingIndex(m.name, m.id),
86
+ name: enhanceModelNameWithCodingIndex(m.name, m.id, providerId),
86
87
  }));
87
88
  }
88
89
 
@@ -105,7 +106,7 @@ export function registerOpenAICompatible(
105
106
  "User-Agent": "pi-free-providers",
106
107
  ...headers,
107
108
  },
108
- models: enhanceWithCI(models),
109
+ models: enhanceWithCI(models, providerId),
109
110
  ...(oauth && { oauth: oauth as any }),
110
111
  });
111
112
  }
@@ -144,7 +145,7 @@ export function createCtxReRegister(
144
145
  "User-Agent": "pi-free-providers",
145
146
  ...headers,
146
147
  },
147
- models: enhanceWithCI(models),
148
+ models: enhanceWithCI(models, providerId),
148
149
  ...(oauth && { oauth: oauth as any }),
149
150
  });
150
151
  };
@@ -169,14 +170,14 @@ export function setupProvider(
169
170
 
170
171
  // Wrap reRegister to automatically add CI scores to all models
171
172
  const reRegister = (models: ProviderModelConfig[], _s: StoredModels) => {
172
- const enhanced = enhanceWithCI(models);
173
+ const enhanced = enhanceWithCI(models, providerId);
173
174
  config.reRegister(enhanced, _s);
174
175
  };
175
176
 
176
177
  // ── Single toggle command (skip if requested) ──────────────────────
177
178
 
178
179
  if (!config.skipToggle) {
179
- pi.registerCommand(`${providerId}-toggle`, {
180
+ pi.registerCommand(`toggle-${providerId}`, {
180
181
  description: `Toggle between free and all ${providerId} models`,
181
182
  handler: async (_args, ctx) => {
182
183
  // Toggle the mode
@@ -1,128 +1,129 @@
1
- /**
2
- * Cline model fetching.
3
- *
4
- * Fetches ALL models from OpenRouter (Cline's gateway).
5
- * Free/paid filtering is handled by the global /free toggle.
6
- */
7
-
8
- import { applyHidden } from "../../config.ts";
9
- import {
10
- BASE_URL_OPENROUTER,
11
- DEFAULT_FETCH_TIMEOUT_MS,
12
- DEFAULT_MIN_SIZE_B,
13
- } from "../../constants.ts";
14
- import type { ProviderModelConfig } from "../../lib/types.ts";
15
- import {
16
- cleanModelName,
17
- fetchWithRetry,
18
- isUsableModel,
19
- } from "../../lib/util.ts";
20
-
21
- interface OpenRouterRaw {
22
- id: string;
23
- name: string;
24
- context_length?: number;
25
- supported_parameters?: string[];
26
- architecture?: { input_modalities?: string[]; output_modalities?: string[] };
27
- top_provider?: { max_completion_tokens?: number | null };
28
- pricing?: { prompt?: string; completion?: string };
29
- }
30
-
31
- function extractNameFromId(id: string): string {
32
- const part = id.split("/")[1] ?? id;
33
- return part
34
- .split(/[-_]/)
35
- .map((w) => w.charAt(0).toUpperCase() + w.slice(1))
36
- .join(" ");
37
- }
38
-
39
- /**
40
- * Parse pricing string to cost per million tokens.
41
- * OpenRouter returns pricing as string (e.g., "0.0001" or "0").
42
- */
43
- function parsePricing(pricingStr: string | undefined): number {
44
- if (!pricingStr || pricingStr === "0") return 0;
45
- const parsed = parseFloat(pricingStr);
46
- return isNaN(parsed) ? 0 : parsed * 1_000_000; // Convert to per-million
47
- }
48
-
49
- /**
50
- * Check if a model is free (both prompt and completion pricing is 0).
51
- */
52
- function isFreeModel(info: OpenRouterRaw): boolean {
53
- return info.pricing?.prompt === "0" && info.pricing?.completion === "0";
54
- }
55
-
56
- /**
57
- * Fetch ALL models from OpenRouter.
58
- * @param freeOnly - If true, return only free models
59
- */
60
- export async function fetchClineModels(
61
- freeOnly = false,
62
- ): Promise<ProviderModelConfig[]> {
63
- const response = await fetchWithRetry(
64
- `${BASE_URL_OPENROUTER}/models`,
65
- {},
66
- 3,
67
- 1000,
68
- DEFAULT_FETCH_TIMEOUT_MS,
69
- );
70
-
71
- if (!response.ok)
72
- throw new Error(`Failed to fetch OpenRouter models: ${response.status}`);
73
-
74
- const json = (await response.json()) as { data?: OpenRouterRaw[] };
75
-
76
- // Filter to usable models (chat-capable, size threshold)
77
- let usableModels = (json.data ?? []).filter((m) =>
78
- isUsableModel(m.id, DEFAULT_MIN_SIZE_B),
79
- );
80
-
81
- // If freeOnly, filter to free models
82
- if (freeOnly) {
83
- usableModels = usableModels.filter(isFreeModel);
84
- }
85
-
86
- const models: ProviderModelConfig[] = [];
87
- for (const info of usableModels) {
88
- const isReasoning = !!(
89
- info.supported_parameters?.includes("include_reasoning") ||
90
- info.supported_parameters?.includes("reasoning")
91
- );
92
- const hasImage =
93
- info.architecture?.input_modalities?.includes("image") ?? false;
94
-
95
- // Calculate cost per million tokens
96
- const inputCost = parsePricing(info.pricing?.prompt);
97
- const outputCost = parsePricing(info.pricing?.completion);
98
- const isFree = inputCost === 0 && outputCost === 0;
99
-
100
- const cleanName = info.name
101
- ? cleanModelName(info.name)
102
- : extractNameFromId(info.id);
103
-
104
- models.push({
105
- id: info.id,
106
- name: `${cleanName} (Cline)${isFree ? "" : " 💰"}`,
107
- reasoning: isReasoning,
108
- input: hasImage ? ["text", "image"] : ["text"],
109
- cost: {
110
- input: inputCost,
111
- output: outputCost,
112
- cacheRead: 0,
113
- cacheWrite: 0,
114
- },
115
- contextWindow: info.context_length ?? 128_000,
116
- maxTokens: info.top_provider?.max_completion_tokens ?? 8_192,
117
- });
118
- }
119
-
120
- return applyHidden(models);
121
- }
122
-
123
- /**
124
- * Fetch only free models (backward compatibility).
125
- */
126
- export async function fetchClineFreeModels(): Promise<ProviderModelConfig[]> {
127
- return fetchClineModels(true);
128
- }
1
+ /**
2
+ * Cline model fetching.
3
+ *
4
+ * Fetches ALL models from OpenRouter (Cline's gateway).
5
+ * Free/paid filtering is handled by the global free-only filter.
6
+ */
7
+
8
+ import { applyHidden } from "../../config.ts";
9
+ import {
10
+ BASE_URL_OPENROUTER,
11
+ DEFAULT_FETCH_TIMEOUT_MS,
12
+ DEFAULT_MIN_SIZE_B,
13
+ PROVIDER_CLINE,
14
+ } from "../../constants.ts";
15
+ import type { ProviderModelConfig } from "../../lib/types.ts";
16
+ import {
17
+ cleanModelName,
18
+ fetchWithRetry,
19
+ isUsableModel,
20
+ } from "../../lib/util.ts";
21
+
22
+ interface OpenRouterRaw {
23
+ id: string;
24
+ name: string;
25
+ context_length?: number;
26
+ supported_parameters?: string[];
27
+ architecture?: { input_modalities?: string[]; output_modalities?: string[] };
28
+ top_provider?: { max_completion_tokens?: number | null };
29
+ pricing?: { prompt?: string; completion?: string };
30
+ }
31
+
32
+ function extractNameFromId(id: string): string {
33
+ const part = id.split("/")[1] ?? id;
34
+ return part
35
+ .split(/[-_]/)
36
+ .map((w) => w.charAt(0).toUpperCase() + w.slice(1))
37
+ .join(" ");
38
+ }
39
+
40
+ /**
41
+ * Parse pricing string to cost per million tokens.
42
+ * OpenRouter returns pricing as string (e.g., "0.0001" or "0").
43
+ */
44
+ function parsePricing(pricingStr: string | undefined): number {
45
+ if (!pricingStr || pricingStr === "0") return 0;
46
+ const parsed = parseFloat(pricingStr);
47
+ return isNaN(parsed) ? 0 : parsed * 1_000_000; // Convert to per-million
48
+ }
49
+
50
+ /**
51
+ * Check if a model is free (both prompt and completion pricing is 0).
52
+ */
53
+ function isFreeModel(info: OpenRouterRaw): boolean {
54
+ return info.pricing?.prompt === "0" && info.pricing?.completion === "0";
55
+ }
56
+
57
+ /**
58
+ * Fetch ALL models from OpenRouter.
59
+ * @param freeOnly - If true, return only free models
60
+ */
61
+ export async function fetchClineModels(
62
+ freeOnly = false,
63
+ ): Promise<ProviderModelConfig[]> {
64
+ const response = await fetchWithRetry(
65
+ `${BASE_URL_OPENROUTER}/models`,
66
+ {},
67
+ 3,
68
+ 1000,
69
+ DEFAULT_FETCH_TIMEOUT_MS,
70
+ );
71
+
72
+ if (!response.ok)
73
+ throw new Error(`Failed to fetch OpenRouter models: ${response.status}`);
74
+
75
+ const json = (await response.json()) as { data?: OpenRouterRaw[] };
76
+
77
+ // Filter to usable models (chat-capable, size threshold)
78
+ let usableModels = (json.data ?? []).filter((m) =>
79
+ isUsableModel(m.id, DEFAULT_MIN_SIZE_B),
80
+ );
81
+
82
+ // If freeOnly, filter to free models
83
+ if (freeOnly) {
84
+ usableModels = usableModels.filter(isFreeModel);
85
+ }
86
+
87
+ const models: ProviderModelConfig[] = [];
88
+ for (const info of usableModels) {
89
+ const isReasoning = !!(
90
+ info.supported_parameters?.includes("include_reasoning") ||
91
+ info.supported_parameters?.includes("reasoning")
92
+ );
93
+ const hasImage =
94
+ info.architecture?.input_modalities?.includes("image") ?? false;
95
+
96
+ // Calculate cost per million tokens
97
+ const inputCost = parsePricing(info.pricing?.prompt);
98
+ const outputCost = parsePricing(info.pricing?.completion);
99
+ const isFree = inputCost === 0 && outputCost === 0;
100
+
101
+ const cleanName = info.name
102
+ ? cleanModelName(info.name)
103
+ : extractNameFromId(info.id);
104
+
105
+ models.push({
106
+ id: info.id,
107
+ name: `${cleanName} (Cline)${isFree ? "" : " 💰"}`,
108
+ reasoning: isReasoning,
109
+ input: hasImage ? ["text", "image"] : ["text"],
110
+ cost: {
111
+ input: inputCost,
112
+ output: outputCost,
113
+ cacheRead: 0,
114
+ cacheWrite: 0,
115
+ },
116
+ contextWindow: info.context_length ?? 128_000,
117
+ maxTokens: info.top_provider?.max_completion_tokens ?? 8_192,
118
+ });
119
+ }
120
+
121
+ return applyHidden(models, PROVIDER_CLINE);
122
+ }
123
+
124
+ /**
125
+ * Fetch only free models (backward compatibility).
126
+ */
127
+ export async function fetchClineFreeModels(): Promise<ProviderModelConfig[]> {
128
+ return fetchClineModels(true);
129
+ }