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,175 +0,0 @@
1
- /**
2
- * OpenRouter Provider Extension
3
- *
4
- * Provides access to 29+ free models and 300+ paid models via OpenRouter.
5
- * Requires OPENROUTER_API_KEY (free account at https://openrouter.ai).
6
- *
7
- * By default only free (:free) models are shown.
8
- * Set OPENROUTER_OPENROUTER_SHOW_PAID=true to also include paid models.
9
- */
10
-
11
- import type {
12
- ExtensionAPI,
13
- ProviderModelConfig,
14
- } from "@mariozechner/pi-coding-agent";
15
- import {
16
- applyHidden,
17
- OPENROUTER_API_KEY as CONFIG_API_KEY,
18
- OPENROUTER_SHOW_PAID,
19
- PROVIDER_OPENROUTER,
20
- } from "../config.ts";
21
- import {
22
- BASE_URL_OPENROUTER,
23
- DEFAULT_FETCH_TIMEOUT_MS,
24
- DEFAULT_MIN_SIZE_B,
25
- } from "../constants.ts";
26
- import { fetchOpenRouterMetrics } from "../usage/metrics.ts";
27
- import {
28
- type StoredModels,
29
- setupProvider,
30
- createCtxReRegister,
31
- } from "../provider-helper.ts";
32
- import { createLogger } from "../lib/logger.ts";
33
- import { cleanModelName, isUsableModel, logWarning } from "../lib/util.ts";
34
- import { fetchOpenRouterModelsWithFree } from "./model-fetcher.ts";
35
-
36
- const _logger = createLogger("openrouter");
37
-
38
- const OPENROUTER_CONFIG = {
39
- providerId: PROVIDER_OPENROUTER,
40
- baseUrl: BASE_URL_OPENROUTER,
41
- apiKey: "OPENROUTER_API_KEY",
42
- headers: {
43
- "HTTP-Referer": "https://github.com/apmantza/pi-free",
44
- "X-Title": "Pi",
45
- },
46
- };
47
-
48
- // =============================================================================
49
- // Fetch
50
- // =============================================================================
51
-
52
- async function fetchOpenRouterModels(apiKey: string): Promise<{
53
- free: ProviderModelConfig[];
54
- all: ProviderModelConfig[];
55
- }> {
56
- const { free, all } = await fetchOpenRouterModelsWithFree({
57
- baseUrl: BASE_URL_OPENROUTER,
58
- apiKey,
59
- extraHeaders: {
60
- "HTTP-Referer": "https://github.com/apmantza/pi-free",
61
- "X-Title": "Pi",
62
- },
63
- });
64
-
65
- return { free: applyHidden(free), all: applyHidden(all) };
66
- }
67
-
68
- // =============================================================================
69
- // Extension Entry Point
70
- // =============================================================================
71
-
72
- export default async function (pi: ExtensionAPI) {
73
- const apiKey = CONFIG_API_KEY;
74
-
75
- // Shared model storage (references held by setupProvider for commands)
76
- const stored: StoredModels = { free: [], all: [] };
77
-
78
- // Re-registration function - will be set in session_start with ctx
79
- let reRegisterFn: (models: ProviderModelConfig[]) => void = () => {};
80
-
81
- // Wire up shared boilerplate (commands, model_select, turn_end)
82
- setupProvider(
83
- pi,
84
- {
85
- providerId: PROVIDER_OPENROUTER,
86
- initialShowPaid: OPENROUTER_SHOW_PAID,
87
- reRegister: (models) => reRegisterFn(models),
88
- },
89
- stored,
90
- );
91
-
92
- // Check in session_start if user already has auth for this provider
93
- // If yes: filter their models to free-only, use their key
94
- // If no: use our extension's key with filtered models
95
- pi.on("session_start", async (_event, ctx) => {
96
- const allModels = ctx.modelRegistry.getAll();
97
- const availableModels = ctx.modelRegistry.getAvailable();
98
- const existingModels = allModels.filter(
99
- (m) => m.provider === PROVIDER_OPENROUTER,
100
- );
101
- const hasExistingAuth = availableModels.some(
102
- (m) => m.provider === PROVIDER_OPENROUTER,
103
- );
104
-
105
- if (hasExistingAuth && existingModels.length > 0) {
106
- // User has existing auth - filter to free models, use their key
107
- const freeModels = existingModels
108
- .filter((m) => (m.cost?.input ?? 0) === 0)
109
- .filter((m) => isUsableModel(m.id, DEFAULT_MIN_SIZE_B))
110
- .map((m) => ({
111
- id: m.id,
112
- name: cleanModelName(m.name),
113
- reasoning: m.reasoning,
114
- input: m.input,
115
- cost: m.cost,
116
- contextWindow: m.contextWindow,
117
- maxTokens: m.maxTokens,
118
- }));
119
-
120
- if (freeModels.length === 0) {
121
- _logger.warn(
122
- "[openrouter] No free models available from existing auth",
123
- );
124
- return;
125
- }
126
-
127
- // Store for command toggle
128
- stored.free = freeModels;
129
- stored.all = existingModels;
130
-
131
- // Create re-register function using ctx
132
- reRegisterFn = createCtxReRegister(ctx as any, OPENROUTER_CONFIG);
133
- reRegisterFn(freeModels);
134
- return;
135
- }
136
-
137
- // User doesn't have existing auth — use our extension's key
138
- if (apiKey) {
139
- process.env.OPENROUTER_API_KEY = apiKey;
140
- } else {
141
- _logger.warn(
142
- "[openrouter] No API key found — set OPENROUTER_API_KEY or add openrouter_api_key to ~/.pi/free.json. Free key at https://openrouter.ai",
143
- );
144
- return;
145
- }
146
-
147
- let models: ProviderModelConfig[] = [];
148
- let fetchResult: {
149
- free: ProviderModelConfig[];
150
- all: ProviderModelConfig[];
151
- } | null = null;
152
-
153
- try {
154
- fetchResult = await fetchOpenRouterModels(apiKey);
155
- models = OPENROUTER_SHOW_PAID ? fetchResult.all : fetchResult.free;
156
- } catch (error) {
157
- logWarning("openrouter", "Failed to fetch models", error);
158
- }
159
-
160
- if (models.length === 0) return;
161
-
162
- // Store for command toggle
163
- if (fetchResult) {
164
- stored.free = fetchResult.free;
165
- stored.all = fetchResult.all;
166
- }
167
-
168
- // Create re-register function using ctx and register
169
- reRegisterFn = createCtxReRegister(ctx as any, OPENROUTER_CONFIG);
170
- reRegisterFn(models);
171
-
172
- // Fetch and cache metrics (used internally, not displayed)
173
- await fetchOpenRouterMetrics();
174
- });
175
- }
package/providers/qwen.ts DELETED
@@ -1,127 +0,0 @@
1
- /**
2
- * Qwen OAuth Provider Extension
3
- *
4
- * Provides free access to Qwen 3.6 Plus via OAuth device flow.
5
- * 1,000 free API calls/day — run /login qwen to authenticate.
6
- *
7
- * Usage:
8
- * pi install git:github.com/apmantza/pi-free
9
- * # Then /login qwen, select qwen model
10
- */
11
-
12
- import type { OAuthCredentials, Model, Api } from "@mariozechner/pi-ai";
13
- import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
14
- import { PROVIDER_QWEN, URL_QWEN_TOS } from "../constants.ts";
15
- import {
16
- enhanceWithCI,
17
- type StoredModels,
18
- setupProvider,
19
- createReRegister,
20
- } from "../provider-helper.ts";
21
- import { incrementRequestCount } from "../usage/metrics.ts";
22
- import { logWarning } from "../lib/util.ts";
23
- import { createLogger } from "../lib/logger.ts";
24
- import { loginQwen, refreshQwenToken, getQwenBaseUrl } from "./qwen-auth.ts";
25
- import { fetchQwenModels } from "./qwen-models.ts";
26
-
27
- const _logger = createLogger("qwen");
28
-
29
- // =============================================================================
30
- // Constants
31
- // =============================================================================
32
-
33
- // Mirrors qwen-code's DEFAULT_QWEN_BASE_URL (used when resource_url is absent).
34
- const DEFAULT_BASE_URL = "https://dashscope.aliyuncs.com/compatible-mode/v1";
35
-
36
- // Headers required by DashScope's OpenAI-compatible API for OAuth tokens.
37
- // Replicates DashScopeOpenAICompatibleProvider.buildHeaders() from qwen-code.
38
- const DASHSCOPE_HEADERS = {
39
- "X-DashScope-AuthType": "qwen-oauth",
40
- "X-DashScope-CacheControl": "enable",
41
- "X-DashScope-UserAgent": "QwenCode/0.0.5 (pi-free)",
42
- "Client-Code": "QwenCode",
43
- };
44
-
45
- // =============================================================================
46
- // Extension entry point
47
- // =============================================================================
48
-
49
- export default async function (pi: ExtensionAPI) {
50
- // Fetch static free-tier models
51
- let models = await fetchQwenModels().catch((err) => {
52
- logWarning("qwen", "Failed to load models at startup", err);
53
- return [];
54
- });
55
-
56
- if (models.length === 0) {
57
- logWarning("qwen", "No models available, skipping provider");
58
- return;
59
- }
60
-
61
- const stored: StoredModels = { free: models, all: models };
62
-
63
- // OAuth config for Qwen
64
- const oauthConfig = {
65
- name: "Qwen",
66
- login: loginQwen,
67
- refreshToken: refreshQwenToken,
68
- getApiKey: (cred: OAuthCredentials) => cred.access,
69
- modifyModels: (models: Model<Api>[], cred: OAuthCredentials) => {
70
- // Mirror qwen-code: resolve baseUrl from resource_url per-credential.
71
- // Chinese accounts → dashscope.aliyuncs.com/v1
72
- // International accounts → portal.qwen.ai/v1 (or custom endpoint)
73
- const baseUrl = getQwenBaseUrl(cred);
74
- _logger.info("Qwen OAuth modifyModels called", {
75
- baseUrl,
76
- resource_url: cred.resource_url,
77
- modelCount: models.length,
78
- });
79
- if (baseUrl === DEFAULT_BASE_URL) return models;
80
- return (models as Model<Api>[]).map((m) => ({ ...m, baseUrl }));
81
- },
82
- };
83
-
84
- // Register provider with OpenAI-compatible API
85
- function registerProvider(m = models) {
86
- pi.registerProvider(PROVIDER_QWEN, {
87
- baseUrl: DEFAULT_BASE_URL,
88
- apiKey: "QWEN_API_KEY",
89
- api: "openai-completions" as const,
90
- headers: {
91
- "User-Agent": "pi-free",
92
- ...DASHSCOPE_HEADERS,
93
- },
94
- models: enhanceWithCI(m),
95
- oauth: oauthConfig,
96
- });
97
- }
98
-
99
- registerProvider();
100
-
101
- // Wire up shared boilerplate (commands, model_select, turn_end, ToS)
102
- const reRegister = createReRegister(pi, {
103
- providerId: PROVIDER_QWEN,
104
- baseUrl: DEFAULT_BASE_URL,
105
- apiKey: "QWEN_API_KEY",
106
- oauth: oauthConfig as any,
107
- });
108
-
109
- setupProvider(
110
- pi,
111
- {
112
- providerId: PROVIDER_QWEN,
113
- tosUrl: URL_QWEN_TOS,
114
- initialShowPaid: false,
115
- reRegister: (m) => {
116
- reRegister(m);
117
- },
118
- },
119
- stored,
120
- );
121
-
122
- // Keep lightweight request counting for now (internal only).
123
- pi.on("turn_end", async (_event, ctx) => {
124
- if (ctx.model?.provider !== PROVIDER_QWEN) return;
125
- incrementRequestCount(PROVIDER_QWEN);
126
- });
127
- }
package/providers/zen.ts DELETED
@@ -1,371 +0,0 @@
1
- /**
2
- * OpenCode Zen Provider Extension
3
- *
4
- * Provides access to curated AI models via the OpenCode Zen gateway.
5
- * Free models are available immediately with no account needed.
6
- * Set OPENCODE_API_KEY (or opencode_api_key in ~/.pi/free.json) for paid access.
7
- *
8
- * Model list fetched directly from the Zen gateway — only returns models that
9
- * are actually deployed. Metadata (pricing, context) enriched from models.dev.
10
- */
11
-
12
- import type {
13
- ExtensionAPI,
14
- ProviderModelConfig,
15
- } from "@mariozechner/pi-coding-agent";
16
- import {
17
- applyHidden,
18
- OPENCODE_API_KEY as CONFIG_API_KEY,
19
- PROVIDER_ZEN,
20
- ZEN_SHOW_PAID,
21
- } from "../config.ts";
22
- import {
23
- BASE_URL_ZEN,
24
- DEFAULT_FETCH_TIMEOUT_MS,
25
- URL_ZEN_TOS,
26
- } from "../constants.ts";
27
- import {
28
- type StoredModels,
29
- setupProvider,
30
- createCtxReRegister,
31
- } from "../provider-helper.ts";
32
- import type { ZenGatewayModel } from "../lib/types.ts";
33
- import { fetchModelsDevMeta } from "./model-fetcher.ts";
34
- import { createOpenCodeSessionTracker } from "./opencode-session.ts";
35
- import { fetchWithRetry, logWarning } from "../lib/util.ts";
36
-
37
- const ZEN_CONFIG = {
38
- providerId: PROVIDER_ZEN,
39
- baseUrl: BASE_URL_ZEN,
40
- apiKey: "PI_FREE_ZEN_API_KEY",
41
- headers: {
42
- "X-Title": "Pi",
43
- "HTTP-Referer": "https://opencode.ai/",
44
- },
45
- };
46
-
47
- const session = createOpenCodeSessionTracker();
48
-
49
- // =============================================================================
50
- // Static fallback models (from Pi's built-in + OpenCode docs)
51
- // Used when /models API is unavailable
52
- // =============================================================================
53
-
54
- const _STATIC_ZEN_MODELS: ProviderModelConfig[] = [
55
- // Free models (from OpenCode Zen docs)
56
- {
57
- id: "big-pickle",
58
- name: "Big Pickle",
59
- reasoning: true,
60
- input: ["text"],
61
- cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
62
- contextWindow: 200000,
63
- maxTokens: 128000,
64
- },
65
- {
66
- id: "trinity-large-preview-free",
67
- name: "Trinity Large Preview Free",
68
- reasoning: false,
69
- input: ["text"],
70
- cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
71
- contextWindow: 128000,
72
- maxTokens: 16384,
73
- },
74
- {
75
- id: "minimax-m2.5-free",
76
- name: "MiniMax M2.5 Free",
77
- reasoning: true,
78
- input: ["text"],
79
- cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
80
- contextWindow: 200000,
81
- maxTokens: 16384,
82
- },
83
- {
84
- id: "mimo-v2-pro-free",
85
- name: "MiMo V2 Pro Free",
86
- reasoning: false,
87
- input: ["text"],
88
- cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
89
- contextWindow: 128000,
90
- maxTokens: 16384,
91
- },
92
- {
93
- id: "mimo-v2-omni-free",
94
- name: "MiMo V2 Omni Free",
95
- reasoning: false,
96
- input: ["text", "image"],
97
- cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
98
- contextWindow: 128000,
99
- maxTokens: 16384,
100
- },
101
- {
102
- id: "mimo-v2-flash-free",
103
- name: "MiMo V2 Flash Free",
104
- reasoning: false,
105
- input: ["text"],
106
- cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
107
- contextWindow: 128000,
108
- maxTokens: 16384,
109
- },
110
- {
111
- id: "nemotron-3-super-free",
112
- name: "Nemotron 3 Super Free",
113
- reasoning: false,
114
- input: ["text"],
115
- cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
116
- contextWindow: 128000,
117
- maxTokens: 16384,
118
- },
119
- // Paid models (available when show_paid: true and API key set)
120
- {
121
- id: "claude-3-5-haiku",
122
- name: "Claude Haiku 3.5",
123
- reasoning: false,
124
- input: ["text", "image"],
125
- cost: { input: 0.8, output: 4, cacheRead: 0, cacheWrite: 0 },
126
- contextWindow: 200000,
127
- maxTokens: 8192,
128
- },
129
- {
130
- id: "claude-haiku-4-5",
131
- name: "Claude Haiku 4.5",
132
- reasoning: true,
133
- input: ["text", "image"],
134
- cost: { input: 1, output: 5, cacheRead: 0, cacheWrite: 0 },
135
- contextWindow: 200000,
136
- maxTokens: 64000,
137
- },
138
- {
139
- id: "claude-opus-4-5",
140
- name: "Claude Opus 4.5",
141
- reasoning: true,
142
- input: ["text", "image"],
143
- cost: { input: 5, output: 25, cacheRead: 0, cacheWrite: 0 },
144
- contextWindow: 200000,
145
- maxTokens: 64000,
146
- },
147
- {
148
- id: "claude-sonnet-4-5",
149
- name: "Claude Sonnet 4.5",
150
- reasoning: true,
151
- input: ["text", "image"],
152
- cost: { input: 3, output: 15, cacheRead: 0, cacheWrite: 0 },
153
- contextWindow: 200000,
154
- maxTokens: 64000,
155
- },
156
- {
157
- id: "gemini-3-flash",
158
- name: "Gemini 3 Flash",
159
- reasoning: false,
160
- input: ["text", "image"],
161
- cost: { input: 0.5, output: 3, cacheRead: 0, cacheWrite: 0 },
162
- contextWindow: 128000,
163
- maxTokens: 16384,
164
- },
165
- {
166
- id: "gemini-3.1-pro",
167
- name: "Gemini 3.1 Pro",
168
- reasoning: false,
169
- input: ["text", "image"],
170
- cost: { input: 2, output: 12, cacheRead: 0, cacheWrite: 0 },
171
- contextWindow: 200000,
172
- maxTokens: 16384,
173
- },
174
- {
175
- id: "minimax-m2.5",
176
- name: "MiniMax M2.5",
177
- reasoning: true,
178
- input: ["text"],
179
- cost: { input: 0.3, output: 1.2, cacheRead: 0, cacheWrite: 0 },
180
- contextWindow: 200000,
181
- maxTokens: 16384,
182
- },
183
- ];
184
-
185
- // =============================================================================
186
- // Fetch helpers
187
- // =============================================================================
188
-
189
- // Models confirmed broken (always return empty content regardless of token budget).
190
- const ZEN_BROKEN_MODELS = new Set([
191
- "gpt-5-nano", // always returns empty content/choices
192
- "gpt-5.4-nano", // same family, same issue
193
- ]);
194
-
195
- /** Fetch the model list from the Zen gateway — authoritative for what's deployed. */
196
- async function fetchGatewayModels(token: string): Promise<string[]> {
197
- const response = await fetchWithRetry(
198
- `${BASE_URL_ZEN}/models`,
199
- {
200
- headers: {
201
- Authorization: `Bearer ${token}`,
202
- "User-Agent": "pi-free-providers",
203
- },
204
- },
205
- 3,
206
- 1000,
207
- DEFAULT_FETCH_TIMEOUT_MS,
208
- );
209
-
210
- if (!response.ok) {
211
- throw new Error(
212
- `Zen /models returned ${response.status} ${response.statusText}`,
213
- );
214
- }
215
-
216
- const json = (await response.json()) as { data?: ZenGatewayModel[] };
217
- return (json.data ?? [])
218
- .map((m) => m.id)
219
- .filter((id) => !ZEN_BROKEN_MODELS.has(id));
220
- }
221
-
222
- // =============================================================================
223
- // Main fetch
224
- // =============================================================================
225
-
226
- async function fetchZenModels(token: string): Promise<{
227
- all: ProviderModelConfig[];
228
- free: ProviderModelConfig[];
229
- useStaticFallback: boolean;
230
- }> {
231
- try {
232
- const [gatewayIds, meta] = await Promise.all([
233
- fetchGatewayModels(token),
234
- fetchModelsDevMeta("opencode"), // Fetch only opencode provider's models
235
- ]);
236
-
237
- const all: ProviderModelConfig[] = [];
238
- const free: ProviderModelConfig[] = [];
239
-
240
- for (const id of gatewayIds) {
241
- const m = meta[id];
242
-
243
- // Skip image-output models
244
- if (m?.modalities?.output?.includes("image")) continue;
245
-
246
- const config: ProviderModelConfig = {
247
- id,
248
- name: m?.name ?? id,
249
- reasoning: m?.reasoning ?? false,
250
- input: m?.modalities?.input?.includes("image")
251
- ? ["text", "image"]
252
- : ["text"],
253
- cost: {
254
- input: m?.cost?.input ?? 0,
255
- output: m?.cost?.output ?? 0,
256
- cacheRead: m?.cost?.cache_read ?? 0,
257
- cacheWrite: m?.cost?.cache_write ?? 0,
258
- },
259
- contextWindow: m?.limit?.context ?? 128_000,
260
- maxTokens: m?.limit?.output ?? 16_384,
261
- };
262
-
263
- all.push(config);
264
- if ((m?.cost?.input ?? 0) === 0) free.push(config);
265
- }
266
-
267
- return {
268
- all: applyHidden(all),
269
- free: applyHidden(free),
270
- useStaticFallback: false,
271
- };
272
- } catch (error) {
273
- // API failed - return special flag to skip registration
274
- // Pi's built-in OpenCode provider will be used instead
275
- logWarning(
276
- "zen",
277
- "API unavailable, letting Pi use built-in provider",
278
- error,
279
- );
280
- return { all: [], free: [], useStaticFallback: true };
281
- }
282
- }
283
-
284
- // =============================================================================
285
- // Extension Entry Point
286
- // =============================================================================
287
-
288
- export default async function (pi: ExtensionAPI) {
289
- const hasKey = !!CONFIG_API_KEY;
290
- const token = CONFIG_API_KEY ?? "public";
291
-
292
- // Use a private env var so we don't accidentally activate Pi's built-in
293
- // opencode provider, which also watches OPENCODE_API_KEY.
294
- const ZEN_KEY_VAR = "PI_FREE_ZEN_API_KEY";
295
-
296
- // Shared model storage (references held by setupProvider for commands)
297
- const stored: StoredModels = { free: [], all: [] };
298
-
299
- // Re-registration function - will be set in session_start with ctx
300
- let reRegisterFn: (models: ProviderModelConfig[]) => void = () => {};
301
-
302
- // Wire up shared boilerplate (commands, model_select, turn_end, ToS)
303
- setupProvider(
304
- pi,
305
- {
306
- providerId: PROVIDER_ZEN,
307
- tosUrl: URL_ZEN_TOS,
308
- hasKey,
309
- initialShowPaid: ZEN_SHOW_PAID,
310
- reRegister: (models) => reRegisterFn(models),
311
- },
312
- stored,
313
- );
314
-
315
- // Check in session_start if user already has auth for this provider
316
- // If they do, we still register with session headers for better reliability
317
- pi.on("session_start", async (_event, ctx) => {
318
- const availableModels = ctx.modelRegistry.getAvailable();
319
- const _hasExistingAuth = availableModels.some(
320
- (m) => m.provider === PROVIDER_ZEN,
321
- );
322
-
323
- // Set up the env var regardless - either for our use or to supplement existing auth
324
- process.env[ZEN_KEY_VAR] = token;
325
-
326
- let models: ProviderModelConfig[] = [];
327
- let _freeCount = 0;
328
- let useStaticFallback = false;
329
-
330
- try {
331
- const result = await fetchZenModels(token);
332
- models = hasKey && ZEN_SHOW_PAID ? result.all : result.free;
333
- _freeCount = result.free.length;
334
- useStaticFallback = result.useStaticFallback;
335
-
336
- // Store for command toggle
337
- stored.free = result.free;
338
- stored.all = result.all;
339
- } catch (error) {
340
- logWarning("zen", "Failed to fetch models", error);
341
- }
342
-
343
- // If API failed, don't register our provider - let Pi use its built-in
344
- if (useStaticFallback || models.length === 0) {
345
- return;
346
- }
347
-
348
- // Generate session ID for this session (used in headers)
349
- const sessionId = session.getSessionId();
350
-
351
- // Create re-register function with session headers
352
- const sessionConfig = {
353
- ...ZEN_CONFIG,
354
- headers: {
355
- ...ZEN_CONFIG.headers,
356
- "x-opencode-session": sessionId,
357
- "x-session-affinity": sessionId,
358
- },
359
- };
360
- reRegisterFn = createCtxReRegister(ctx as any, sessionConfig);
361
-
362
- // Register our filtered provider (CI enhancement handled by provider-helper)
363
- reRegisterFn(models);
364
- });
365
-
366
- // Update request count before each agent turn (for request ID generation)
367
- pi.on("before_agent_start", async (_event, ctx) => {
368
- if (ctx.model?.provider !== PROVIDER_ZEN) return;
369
- session.nextRequestId();
370
- });
371
- }
package/usage/commands.ts DELETED
@@ -1,17 +0,0 @@
1
- /**
2
- * Free tier usage commands
3
- *
4
- * Provides:
5
- * - /free-sessionusage: Current session breakdown
6
- * - /free-totalusage: Cumulative usage from disk
7
- *
8
- * NOTE: Commands temporarily disabled due to duplicate registration issues.
9
- * Use /kilo-sessionusage or /zen-sessionusage instead.
10
- */
11
-
12
- import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
13
-
14
- export function registerUsageCommands(_pi: ExtensionAPI): void {
15
- // Commands disabled - Pi shows duplicate registrations across providers
16
- // TODO: Find reliable way to register global commands once
17
- }