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.
- package/CHANGELOG.md +107 -1
- package/README.md +95 -46
- package/config.ts +165 -120
- package/constants.ts +22 -61
- package/index.ts +186 -0
- package/lib/json-persistence.ts +11 -10
- package/lib/logger.ts +2 -2
- package/lib/model-enhancer.ts +20 -20
- package/lib/open-browser.ts +41 -0
- package/lib/provider-cache.ts +106 -0
- package/lib/registry.ts +144 -0
- package/package.json +67 -82
- package/provider-factory.ts +25 -41
- package/provider-failover/benchmark-lookup.ts +247 -0
- package/provider-failover/benchmarks-chunk-0.ts +2010 -0
- package/provider-failover/benchmarks-chunk-1.ts +1988 -0
- package/provider-failover/benchmarks-chunk-2.ts +2010 -0
- package/provider-failover/benchmarks-chunk-3.ts +2010 -0
- package/provider-failover/benchmarks-chunk-4.ts +1969 -0
- package/provider-failover/hardcoded-benchmarks.ts +22 -10025
- package/provider-helper.ts +38 -37
- package/providers/{cline-auth.ts → cline/cline-auth.ts} +2 -2
- package/providers/cline/cline-models.ts +128 -0
- package/providers/{cline.ts → cline/cline.ts} +300 -257
- package/providers/cloudflare/cloudflare.ts +368 -0
- package/providers/dynamic-built-in/index.ts +513 -0
- package/providers/{kilo-auth.ts → kilo/kilo-auth.ts} +3 -20
- package/providers/{kilo-models.ts → kilo/kilo-models.ts} +2 -2
- package/providers/kilo/kilo.ts +235 -0
- package/providers/{modal.ts → modal/modal.ts} +4 -3
- package/providers/{nvidia.ts → nvidia/nvidia.ts} +152 -113
- package/providers/ollama/ollama.ts +172 -0
- package/providers/opencode-session.ts +34 -34
- package/providers/{qwen-auth.ts → qwen/qwen-auth.ts} +24 -40
- package/providers/{qwen-models.ts → qwen/qwen-models.ts} +101 -95
- package/providers/qwen/qwen.ts +202 -0
- package/provider-failover/auto-switch.ts +0 -350
- package/provider-failover/errors.ts +0 -275
- package/provider-failover/index.ts +0 -238
- package/providers/cline-models.ts +0 -77
- package/providers/factory.ts +0 -125
- package/providers/fireworks.ts +0 -49
- package/providers/go.ts +0 -216
- package/providers/kilo.ts +0 -146
- package/providers/mistral.ts +0 -144
- package/providers/ollama.ts +0 -113
- package/providers/openrouter.ts +0 -175
- package/providers/qwen.ts +0 -127
- package/providers/zen.ts +0 -371
- package/usage/commands.ts +0 -17
- package/usage/cumulative.ts +0 -193
- package/usage/formatters.ts +0 -115
- package/usage/index.ts +0 -46
- package/usage/limits.ts +0 -148
- package/usage/metrics.ts +0 -222
- package/usage/sessions.ts +0 -355
- package/usage/store.ts +0 -99
- package/usage/tracking.ts +0 -329
- package/usage/types.ts +0 -26
- package/usage/widget.ts +0 -90
- package/widget/data.ts +0 -113
- package/widget/format.ts +0 -26
- package/widget/render.ts +0 -117
package/providers/openrouter.ts
DELETED
|
@@ -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
|
-
}
|