pi-free 1.0.8 → 2.0.0

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.
Files changed (63) hide show
  1. package/CHANGELOG.md +107 -1
  2. package/README.md +95 -46
  3. package/config.ts +165 -120
  4. package/constants.ts +22 -61
  5. package/index.ts +186 -0
  6. package/lib/json-persistence.ts +11 -10
  7. package/lib/logger.ts +2 -2
  8. package/lib/model-enhancer.ts +20 -20
  9. package/lib/open-browser.ts +41 -0
  10. package/lib/provider-cache.ts +106 -0
  11. package/lib/registry.ts +144 -0
  12. package/package.json +67 -82
  13. package/provider-factory.ts +25 -41
  14. package/provider-failover/benchmark-lookup.ts +247 -0
  15. package/provider-failover/benchmarks-chunk-0.ts +2010 -0
  16. package/provider-failover/benchmarks-chunk-1.ts +1988 -0
  17. package/provider-failover/benchmarks-chunk-2.ts +2010 -0
  18. package/provider-failover/benchmarks-chunk-3.ts +2010 -0
  19. package/provider-failover/benchmarks-chunk-4.ts +1969 -0
  20. package/provider-failover/hardcoded-benchmarks.ts +22 -10025
  21. package/provider-helper.ts +38 -37
  22. package/providers/{cline-auth.ts → cline/cline-auth.ts} +2 -2
  23. package/providers/cline/cline-models.ts +128 -0
  24. package/providers/{cline.ts → cline/cline.ts} +300 -257
  25. package/providers/cloudflare/cloudflare.ts +368 -0
  26. package/providers/dynamic-built-in/index.ts +513 -0
  27. package/providers/{kilo-auth.ts → kilo/kilo-auth.ts} +3 -20
  28. package/providers/{kilo-models.ts → kilo/kilo-models.ts} +2 -2
  29. package/providers/kilo/kilo.ts +235 -0
  30. package/providers/{modal.ts → modal/modal.ts} +4 -3
  31. package/providers/{nvidia.ts → nvidia/nvidia.ts} +152 -113
  32. package/providers/ollama/ollama.ts +172 -0
  33. package/providers/opencode-session.ts +34 -34
  34. package/providers/{qwen-auth.ts → qwen/qwen-auth.ts} +24 -40
  35. package/providers/{qwen-models.ts → qwen/qwen-models.ts} +101 -95
  36. package/providers/qwen/qwen.ts +202 -0
  37. package/provider-failover/auto-switch.ts +0 -350
  38. package/provider-failover/errors.ts +0 -275
  39. package/provider-failover/index.ts +0 -238
  40. package/providers/cline-models.ts +0 -77
  41. package/providers/factory.ts +0 -125
  42. package/providers/fireworks.ts +0 -49
  43. package/providers/go.ts +0 -216
  44. package/providers/kilo.ts +0 -146
  45. package/providers/mistral.ts +0 -144
  46. package/providers/ollama.ts +0 -113
  47. package/providers/openrouter.ts +0 -175
  48. package/providers/qwen.ts +0 -127
  49. package/providers/zen.ts +0 -371
  50. package/usage/commands.ts +0 -17
  51. package/usage/cumulative.ts +0 -193
  52. package/usage/formatters.ts +0 -115
  53. package/usage/index.ts +0 -46
  54. package/usage/limits.ts +0 -148
  55. package/usage/metrics.ts +0 -222
  56. package/usage/sessions.ts +0 -355
  57. package/usage/store.ts +0 -99
  58. package/usage/tracking.ts +0 -329
  59. package/usage/types.ts +0 -26
  60. package/usage/widget.ts +0 -90
  61. package/widget/data.ts +0 -113
  62. package/widget/format.ts +0 -26
  63. package/widget/render.ts +0 -117
@@ -1,49 +0,0 @@
1
- /**
2
- * Fireworks AI Provider Extension
3
- *
4
- * Provides access to Fireworks AI hosted models via api.fireworks.ai.
5
- * Uses OpenAI-compatible API - requires FIREWORKS_API_KEY.
6
- * Get a key at: https://app.fireworks.ai/settings/users/api-keys
7
- *
8
- * All models are credit-based (no free tier).
9
- * Set FIREWORKS_SHOW_PAID=true to enable.
10
- */
11
-
12
- import type { ProviderModelConfig } from "@mariozechner/pi-coding-agent";
13
- import { PROVIDER_FIREWORKS } from "../config.ts";
14
- import { BASE_URL_FIREWORKS } from "../constants.ts";
15
- import { createProvider } from "../provider-factory.ts";
16
-
17
- // =============================================================================
18
- // Static model list (Fireworks doesn't have a models API)
19
- // =============================================================================
20
-
21
- function getFireworksModels(): ProviderModelConfig[] {
22
- return [
23
- {
24
- id: "accounts/fireworks/routers/kimi-k2p5-turbo",
25
- name: "Kimi K2.5 Turbo",
26
- reasoning: true,
27
- input: ["text"],
28
- // Actual pricing (per 1M tokens): $0.50 input, $2.00 output
29
- cost: { input: 0.0005, output: 0.002, cacheRead: 0, cacheWrite: 0 },
30
- contextWindow: 262144,
31
- maxTokens: 131072,
32
- },
33
- ];
34
- }
35
-
36
- // =============================================================================
37
- // Extension Entry Point
38
- // =============================================================================
39
-
40
- export default function (pi: Parameters<typeof createProvider>[0]) {
41
- return createProvider(pi, {
42
- providerId: PROVIDER_FIREWORKS,
43
- baseUrl: BASE_URL_FIREWORKS,
44
- apiKeyEnvVar: "FIREWORKS_API_KEY",
45
- apiKeyConfigKey: "fireworks_api_key",
46
- showPaidFlag: "FIREWORKS_SHOW_PAID",
47
- fetchModels: async () => getFireworksModels(),
48
- });
49
- }
package/providers/go.ts DELETED
@@ -1,216 +0,0 @@
1
- /**
2
- * OpenCode Go Provider Extension
3
- *
4
- * Provides access to curated open coding models via the OpenCode Go gateway.
5
- * Requires an OpenCode Go subscription ($5 first month, then $10/month).
6
- * Set OPENCODE_GO_API_KEY (or opencode_go_api_key in ~/.pi/free.json) for access.
7
- *
8
- * Models available:
9
- * - GLM-5
10
- * - Kimi K2.5
11
- * - MiMo-V2-Pro
12
- * - MiMo-V2-Omni
13
- * - MiniMax M2.7
14
- * - MiniMax M2.5
15
- *
16
- * See: https://opencode.ai/docs/go
17
- */
18
-
19
- import type {
20
- ExtensionAPI,
21
- ProviderModelConfig,
22
- } from "@mariozechner/pi-coding-agent";
23
- import {
24
- applyHidden,
25
- GO_SHOW_PAID,
26
- OPENCODE_API_KEY,
27
- OPENCODE_GO_API_KEY as CONFIG_API_KEY,
28
- PROVIDER_GO,
29
- } from "../config.ts";
30
- import {
31
- BASE_URL_GO,
32
- URL_GO_TOS,
33
- } from "../constants.ts";
34
- import {
35
- type StoredModels,
36
- setupProvider,
37
- createCtxReRegister,
38
- } from "../provider-helper.ts";
39
- import { createOpenCodeSessionTracker } from "./opencode-session.ts";
40
-
41
- const GO_CONFIG = {
42
- providerId: PROVIDER_GO,
43
- baseUrl: BASE_URL_GO,
44
- apiKey: "PI_FREE_GO_API_KEY",
45
- headers: {
46
- "X-Title": "Pi",
47
- "HTTP-Referer": "https://opencode.ai/",
48
- },
49
- };
50
-
51
- const session = createOpenCodeSessionTracker();
52
-
53
- // =============================================================================
54
- // Static fallback models (from OpenCode Go docs)
55
- // Used when /models API is unavailable
56
- // =============================================================================
57
-
58
- const STATIC_GO_MODELS: ProviderModelConfig[] = [
59
- {
60
- id: "glm-5",
61
- name: "GLM-5",
62
- reasoning: true,
63
- input: ["text"],
64
- cost: { input: 0.5, output: 2, cacheRead: 0, cacheWrite: 0 },
65
- contextWindow: 128000,
66
- maxTokens: 16384,
67
- },
68
- {
69
- id: "kimi-k2.5",
70
- name: "Kimi K2.5",
71
- reasoning: true,
72
- input: ["text"],
73
- cost: { input: 0.3, output: 1.5, cacheRead: 0, cacheWrite: 0 },
74
- contextWindow: 128000,
75
- maxTokens: 16384,
76
- },
77
- {
78
- id: "mimo-v2-pro",
79
- name: "MiMo V2 Pro",
80
- reasoning: false,
81
- input: ["text"],
82
- cost: { input: 0.4, output: 1.2, cacheRead: 0, cacheWrite: 0 },
83
- contextWindow: 128000,
84
- maxTokens: 16384,
85
- },
86
- {
87
- id: "mimo-v2-omni",
88
- name: "MiMo V2 Omni",
89
- reasoning: false,
90
- input: ["text", "image"],
91
- cost: { input: 0.25, output: 0.8, cacheRead: 0, cacheWrite: 0 },
92
- contextWindow: 128000,
93
- maxTokens: 16384,
94
- },
95
- {
96
- id: "minimax-m2.7",
97
- name: "MiniMax M2.7",
98
- reasoning: true,
99
- input: ["text"],
100
- cost: { input: 0.1, output: 0.4, cacheRead: 0, cacheWrite: 0 },
101
- contextWindow: 200000,
102
- maxTokens: 16384,
103
- },
104
- {
105
- id: "minimax-m2.5",
106
- name: "MiniMax M2.5",
107
- reasoning: true,
108
- input: ["text"],
109
- cost: { input: 0.05, output: 0.2, cacheRead: 0, cacheWrite: 0 },
110
- contextWindow: 200000,
111
- maxTokens: 16384,
112
- },
113
- ];
114
-
115
- // =============================================================================
116
- // Fetch helpers
117
- // =============================================================================
118
-
119
- // OpenCode Go does not have a /models endpoint.
120
- // Models are only accessible via the chat completions API.
121
- // Static models are used directly (see below).
122
-
123
- // =============================================================================
124
- // Main fetch
125
- // =============================================================================
126
-
127
- /**
128
- * OpenCode Go does not have a /models endpoint.
129
- * Models are only accessible via chat completions.
130
- * So we use the static fallback models directly.
131
- */
132
- async function fetchGoModels(_token: string): Promise<{
133
- all: ProviderModelConfig[];
134
- useStaticFallback: boolean;
135
- }> {
136
- // Go has no /models API - use static models
137
- return { all: applyHidden(STATIC_GO_MODELS), useStaticFallback: true };
138
- }
139
-
140
- // =============================================================================
141
- // Extension Entry Point
142
- // =============================================================================
143
-
144
- export default async function (pi: ExtensionAPI) {
145
- const token = CONFIG_API_KEY ?? OPENCODE_API_KEY ?? "";
146
- const hasKey = !!token;
147
-
148
- // Go requires an API key (no free tier)
149
- if (!hasKey) {
150
- pi.on("session_start", async () => {
151
- // No key - don't register provider
152
- });
153
- return;
154
- }
155
-
156
- // Use a private env var so we don't conflict with other providers
157
- const GO_KEY_VAR = "PI_FREE_GO_API_KEY";
158
-
159
- // Shared model storage
160
- const stored: StoredModels = { free: [], all: [] };
161
-
162
- // Re-registration function
163
- let reRegisterFn: (models: ProviderModelConfig[]) => void = () => {};
164
-
165
- // Wire up shared boilerplate
166
- setupProvider(
167
- pi,
168
- {
169
- providerId: PROVIDER_GO,
170
- tosUrl: URL_GO_TOS,
171
- hasKey,
172
- initialShowPaid: GO_SHOW_PAID,
173
- reRegister: (models) => reRegisterFn(models),
174
- },
175
- stored,
176
- );
177
-
178
- // Register provider on session start
179
- pi.on("session_start", async (_event, ctx) => {
180
- // Set up the env var
181
- process.env[GO_KEY_VAR] = token;
182
-
183
- // OpenCode Go has no /models endpoint - use static fallback
184
- const models = applyHidden(STATIC_GO_MODELS);
185
-
186
- stored.all = models;
187
- stored.free = models; // Go has no free tier
188
-
189
- if (models.length === 0) {
190
- return;
191
- }
192
-
193
- // Generate session ID for this session (used in headers)
194
- const sessionId = session.getSessionId();
195
-
196
- // Create re-register function with session headers (same as zen)
197
- const sessionConfig = {
198
- ...GO_CONFIG,
199
- headers: {
200
- ...GO_CONFIG.headers,
201
- "x-opencode-session": sessionId,
202
- "x-session-affinity": sessionId,
203
- },
204
- };
205
- reRegisterFn = createCtxReRegister(ctx as any, sessionConfig);
206
-
207
- // Register our provider
208
- reRegisterFn(models);
209
- });
210
-
211
- // Update request count before each agent turn (for request ID generation)
212
- pi.on("before_agent_start", async (_event, ctx) => {
213
- if (ctx.model?.provider !== PROVIDER_GO) return;
214
- session.nextRequestId();
215
- });
216
- }
package/providers/kilo.ts DELETED
@@ -1,146 +0,0 @@
1
- /**
2
- * Kilo Provider Extension
3
- *
4
- * Provides access to 300+ AI models via the Kilo Gateway (OpenRouter-compatible).
5
- * Free models available immediately; /login kilo for full access.
6
- *
7
- * Usage:
8
- * pi install git:github.com/apmantza/pi-free
9
- * # Then /login kilo, or set KILO_API_KEY=...
10
- */
11
-
12
- import type { Api, Model, OAuthCredentials } from "@mariozechner/pi-ai";
13
- import type {
14
- ExtensionAPI,
15
- ProviderModelConfig,
16
- } from "@mariozechner/pi-coding-agent";
17
- import { KILO_FREE_ONLY, KILO_SHOW_PAID, PROVIDER_KILO } from "../config.ts";
18
- import { URL_KILO_TOS } from "../constants.ts";
19
- import {
20
- enhanceWithCI,
21
- type StoredModels,
22
- setupProvider,
23
- createReRegister,
24
- createCtxReRegister,
25
- } from "../provider-helper.ts";
26
- import { cleanModelName, logWarning } from "../lib/util.ts";
27
- import { loginKilo, refreshKiloToken } from "./kilo-auth.ts";
28
- import { fetchKiloModels, KILO_GATEWAY_BASE } from "./kilo-models.ts";
29
-
30
- const KILO_PROVIDER_CONFIG = {
31
- providerId: PROVIDER_KILO,
32
- baseUrl: KILO_GATEWAY_BASE,
33
- apiKey: "KILO_API_KEY",
34
- headers: {
35
- "X-KILOCODE-EDITORNAME": "Pi",
36
- },
37
- };
38
-
39
- export default async function (pi: ExtensionAPI) {
40
- let freeModels: ProviderModelConfig[] = [];
41
- try {
42
- freeModels = await fetchKiloModels({ freeOnly: true });
43
- } catch (error) {
44
- logWarning("kilo", "Failed to fetch free models at startup", error);
45
- }
46
-
47
- let cachedAllModels: ProviderModelConfig[] = [];
48
- let showPaidModels = KILO_SHOW_PAID;
49
-
50
- // Shared model storage for setupProvider commands
51
- const stored: StoredModels = { free: freeModels, all: [] };
52
-
53
- // OAuth config for Kilo (shared across registrations)
54
- const oauthConfig = {
55
- name: "Kilo",
56
- login: async (callbacks: any) => {
57
- const cred = await loginKilo(callbacks);
58
- try {
59
- cachedAllModels = await fetchKiloModels({ token: cred.access });
60
- stored.all = cachedAllModels;
61
- } catch (error) {
62
- logWarning("kilo", "Failed to fetch models after login", error);
63
- }
64
- return cred;
65
- },
66
- refreshToken: refreshKiloToken,
67
- getApiKey: (cred: OAuthCredentials) => cred.access,
68
- modifyModels: (models: Model<Api>[], _cred: OAuthCredentials) => {
69
- if (!showPaidModels || KILO_FREE_ONLY || cachedAllModels.length === 0) {
70
- return models;
71
- }
72
- const template = models.find((m) => m.provider === PROVIDER_KILO);
73
- if (!template) return models;
74
- const nonKilo = models.filter((m) => m.provider !== PROVIDER_KILO);
75
- const fullModels = cachedAllModels.map((m) => ({
76
- ...template,
77
- id: m.id,
78
- name: cleanModelName(m.name),
79
- reasoning: m.reasoning,
80
- input: m.input,
81
- cost: m.cost,
82
- contextWindow: m.contextWindow,
83
- maxTokens: m.maxTokens,
84
- }));
85
- return [...nonKilo, ...fullModels];
86
- },
87
- };
88
-
89
- // Register initial provider
90
- pi.registerProvider(PROVIDER_KILO, {
91
- baseUrl: KILO_GATEWAY_BASE,
92
- apiKey: "KILO_API_KEY",
93
- api: "openai-completions" as const,
94
- headers: {
95
- "X-KILOCODE-EDITORNAME": "Pi",
96
- "User-Agent": "pi-free-providers",
97
- },
98
- models: enhanceWithCI(freeModels),
99
- oauth: oauthConfig,
100
- });
101
-
102
- // Wire up shared boilerplate (commands, model_select, turn_end, ToS)
103
- const reRegister = createReRegister(pi, {
104
- ...KILO_PROVIDER_CONFIG,
105
- oauth: oauthConfig as any,
106
- });
107
- setupProvider(
108
- pi,
109
- {
110
- providerId: PROVIDER_KILO,
111
- tosUrl: URL_KILO_TOS,
112
- initialShowPaid: KILO_SHOW_PAID,
113
- reRegister: (models) => {
114
- showPaidModels = models === stored.all;
115
- reRegister(models);
116
- },
117
- },
118
- stored,
119
- );
120
-
121
- // Usage widget temporarily deprecated.
122
-
123
- // ── Kilo-specific: events ────────────────────────────────────────────
124
-
125
- pi.on("session_start", async (_event, ctx) => {
126
- const cred = ctx.modelRegistry.authStorage.get(PROVIDER_KILO);
127
-
128
- if (cred?.type === "oauth") {
129
- try {
130
- cachedAllModels = await fetchKiloModels({ token: cred.access });
131
- stored.all = cachedAllModels;
132
- if (cachedAllModels.length > 0) {
133
- const ctxReRegister = createCtxReRegister(ctx as any, {
134
- ...KILO_PROVIDER_CONFIG,
135
- oauth: oauthConfig as any,
136
- });
137
- const modelsToShow =
138
- showPaidModels && !KILO_FREE_ONLY ? cachedAllModels : freeModels;
139
- ctxReRegister(modelsToShow);
140
- }
141
- } catch (error) {
142
- logWarning("kilo", "Failed to fetch models at session start", error);
143
- }
144
- }
145
- });
146
- }
@@ -1,144 +0,0 @@
1
- /**
2
- * Mistral AI Provider Extension
3
- *
4
- * Provides access to Mistral's models via api.mistral.ai.
5
- * Free models available without account; paid models require MISTRAL_API_KEY.
6
- * Get a key at: https://console.mistral.ai/api-keys
7
- *
8
- * By default only free models are shown.
9
- * Set MISTRAL_SHOW_PAID=true to also include paid models.
10
- *
11
- * Note: Mistral has stricter field requirements than OpenAI —
12
- * only whitelisted fields are sent to avoid 400 errors.
13
- */
14
-
15
- import type { ProviderModelConfig } from "@mariozechner/pi-coding-agent";
16
- import { applyHidden, MISTRAL_SHOW_PAID, PROVIDER_MISTRAL } from "../config.ts";
17
- import { BASE_URL_MISTRAL } from "../constants.ts";
18
- import { createProvider } from "../provider-factory.ts";
19
-
20
- // =============================================================================
21
- // Static model list
22
- // =============================================================================
23
-
24
- function getMistralModels(): ProviderModelConfig[] {
25
- return applyHidden([
26
- // Free models
27
- {
28
- id: "mistral-small-latest",
29
- name: "Mistral Small",
30
- reasoning: false,
31
- input: ["text"],
32
- cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
33
- contextWindow: 32768,
34
- maxTokens: 32768,
35
- },
36
- {
37
- id: "open-mistral-nemo",
38
- name: "Mistral Nemo (Open)",
39
- reasoning: false,
40
- input: ["text"],
41
- cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
42
- contextWindow: 128000,
43
- maxTokens: 128000,
44
- },
45
- // Paid models
46
- {
47
- id: "mistral-large-latest",
48
- name: "Mistral Large",
49
- reasoning: true,
50
- input: ["text"],
51
- cost: { input: 0.002, output: 0.006, cacheRead: 0, cacheWrite: 0 },
52
- contextWindow: 128000,
53
- maxTokens: 128000,
54
- },
55
- {
56
- id: "ministral-3b-latest",
57
- name: "Ministral 3B",
58
- reasoning: false,
59
- input: ["text"],
60
- cost: { input: 0.00004, output: 0.00004, cacheRead: 0, cacheWrite: 0 },
61
- contextWindow: 128000,
62
- maxTokens: 128000,
63
- },
64
- {
65
- id: "ministral-8b-latest",
66
- name: "Ministral 8B",
67
- reasoning: false,
68
- input: ["text"],
69
- cost: { input: 0.0001, output: 0.0001, cacheRead: 0, cacheWrite: 0 },
70
- contextWindow: 128000,
71
- maxTokens: 128000,
72
- },
73
- {
74
- id: "codestral-latest",
75
- name: "Codestral",
76
- reasoning: false,
77
- input: ["text"],
78
- cost: { input: 0.0003, output: 0.0009, cacheRead: 0, cacheWrite: 0 },
79
- contextWindow: 256000,
80
- maxTokens: 256000,
81
- },
82
- ]);
83
- }
84
-
85
- // =============================================================================
86
- // Mistral-specific request filtering
87
- // =============================================================================
88
-
89
- const MISTRAL_ALLOWED_FIELDS = new Set([
90
- "model",
91
- "messages",
92
- "temperature",
93
- "max_tokens",
94
- "stream",
95
- "tools",
96
- "tool_choice",
97
- "stop",
98
- "top_p",
99
- "presence_penalty",
100
- "frequency_penalty",
101
- ]);
102
-
103
- function isMistralPayload(payload: Record<string, unknown>): boolean {
104
- const modelId = payload.model as string | undefined;
105
- return !!modelId && (modelId.includes("mistral") || modelId.includes("nemo"));
106
- }
107
-
108
- function filterMistralPayload(
109
- payload: Record<string, unknown>,
110
- ): Record<string, unknown> {
111
- const filtered: Record<string, unknown> = {};
112
- for (const [key, value] of Object.entries(payload)) {
113
- if (MISTRAL_ALLOWED_FIELDS.has(key)) {
114
- filtered[key] = value;
115
- }
116
- }
117
- return filtered;
118
- }
119
-
120
- // =============================================================================
121
- // Extension Entry Point
122
- // =============================================================================
123
-
124
- export default function (pi: Parameters<typeof createProvider>[0]) {
125
- const allModels = getMistralModels();
126
- const models = MISTRAL_SHOW_PAID
127
- ? allModels
128
- : allModels.filter((m) => (m.cost?.input ?? 0) === 0);
129
-
130
- // Override fetchModels to use our already-fetched models
131
- return createProvider(pi, {
132
- providerId: PROVIDER_MISTRAL,
133
- baseUrl: BASE_URL_MISTRAL,
134
- apiKeyEnvVar: "MISTRAL_API_KEY",
135
- apiKeyConfigKey: "mistral_api_key",
136
- fetchModels: async () => models,
137
- beforeProviderRequest: (payload) => {
138
- if (isMistralPayload(payload)) {
139
- return filterMistralPayload(payload);
140
- }
141
- return undefined;
142
- },
143
- });
144
- }
@@ -1,113 +0,0 @@
1
- /**
2
- * Ollama Cloud Provider Extension
3
- *
4
- * Provides access to Ollama's cloud-hosted models via ollama.com/v1.
5
- * Free tier available with usage limits (resets every 5 hours + 7 days).
6
- * Requires OLLAMA_API_KEY from https://ollama.com/settings/keys
7
- *
8
- * Set OLLAMA_SHOW_PAID=true to enable (required since Ollama has usage limits).
9
- */
10
-
11
- import type { ProviderModelConfig } from "@mariozechner/pi-coding-agent";
12
- import { OLLAMA_API_KEY, PROVIDER_OLLAMA } from "../config.ts";
13
- import { BASE_URL_OLLAMA, DEFAULT_FETCH_TIMEOUT_MS } from "../constants.ts";
14
- import { fetchWithRetry } from "../lib/util.ts";
15
- import { createProvider } from "../provider-factory.ts";
16
-
17
- // =============================================================================
18
- // Fetch + map
19
- // =============================================================================
20
-
21
- async function fetchOllamaModels(): Promise<ProviderModelConfig[]> {
22
- const apiKey = OLLAMA_API_KEY;
23
- if (!apiKey) return [];
24
-
25
- const response = await fetchWithRetry(
26
- `${BASE_URL_OLLAMA}/models`,
27
- {
28
- headers: {
29
- Authorization: `Bearer ${apiKey}`,
30
- "User-Agent": "pi-free-providers",
31
- },
32
- },
33
- 3,
34
- 1000,
35
- DEFAULT_FETCH_TIMEOUT_MS,
36
- );
37
-
38
- if (!response.ok) {
39
- throw new Error(
40
- `Failed to fetch Ollama models: ${response.status} ${response.statusText}`,
41
- );
42
- }
43
-
44
- const json = (await response.json()) as {
45
- data?: Array<{
46
- id: string;
47
- object?: string;
48
- created?: number;
49
- owned_by?: string;
50
- }>;
51
- };
52
- const models = json.data ?? [];
53
-
54
- // Filter out small models (< 30B) to keep list focused
55
- return models
56
- .filter((m) => {
57
- const sizeMatch = m.id.match(/:(\d+)([bmt])/i);
58
- if (sizeMatch) {
59
- const size = parseInt(sizeMatch[1], 10);
60
- const unit = sizeMatch[2].toLowerCase();
61
- if (unit === "b" && size < 30) return false;
62
- }
63
- return true;
64
- })
65
- .map(mapOllamaModel);
66
- }
67
-
68
- function mapOllamaModel(m: { id: string }): ProviderModelConfig {
69
- // Extract context window from parameter size
70
- let contextWindow = 131072; // Default 128k
71
- const sizeMatch = m.id.match(/:(\d+)([bmt])/i);
72
- if (sizeMatch) {
73
- const size = parseInt(sizeMatch[1], 10);
74
- const unit = sizeMatch[2].toLowerCase();
75
- if (unit === "b" && size >= 100) {
76
- contextWindow = 200000;
77
- }
78
- }
79
-
80
- // Clean up name for display
81
- const displayName = m.id
82
- .replace(/:/g, " ")
83
- .replace(/-/g, " ")
84
- .split(" ")
85
- .filter((w) => w.length > 0)
86
- .map((w) => w.charAt(0).toUpperCase() + w.slice(1))
87
- .join(" ");
88
-
89
- return {
90
- id: m.id,
91
- name: displayName,
92
- reasoning: m.id.includes("deepseek") || m.id.includes("r1"),
93
- input: ["text"],
94
- cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
95
- contextWindow,
96
- maxTokens: Math.min(contextWindow / 2, 131072),
97
- };
98
- }
99
-
100
- // =============================================================================
101
- // Extension Entry Point
102
- // =============================================================================
103
-
104
- export default function (pi: Parameters<typeof createProvider>[0]) {
105
- return createProvider(pi, {
106
- providerId: PROVIDER_OLLAMA,
107
- baseUrl: BASE_URL_OLLAMA,
108
- apiKeyEnvVar: "OLLAMA_API_KEY",
109
- apiKeyConfigKey: "ollama_api_key",
110
- showPaidFlag: "OLLAMA_SHOW_PAID",
111
- fetchModels: fetchOllamaModels,
112
- });
113
- }