pi-free 2.2.7 → 2.2.8

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,200 +0,0 @@
1
- /**
2
- * Qwen OAuth Provider Extension
3
- *
4
- * @deprecated This provider is deprecated. Qwen no longer offers the 1,000 free API calls/day tier.
5
- * The provider remains functional for existing authenticated users but new free tier registrations
6
- * are not supported. Consider using other free providers like Kilo, Cline, or NVIDIA instead.
7
- *
8
- * Original description (now outdated):
9
- * ~~Provides free access to Qwen 3.6 Plus via OAuth device flow.
10
- * 1,000 free API calls/day — run /login qwen to authenticate.~~
11
- */
12
-
13
- import type { Api, Model, OAuthCredentials } from "@earendil-works/pi-ai";
14
- import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
15
- import { PROVIDER_QWEN, URL_QWEN_TOS } from "../../constants.ts";
16
- import { createLogger } from "../../lib/logger.ts";
17
- import { logWarning } from "../../lib/util.ts";
18
- import {
19
- createReRegister,
20
- enhanceWithCI,
21
- type StoredModels,
22
- setupProvider,
23
- } from "../../provider-helper.ts";
24
- import { getQwenBaseUrl, loginQwen, refreshQwenToken } from "./qwen-auth.ts";
25
- import { fetchQwenModels } from "./qwen-models.ts";
26
-
27
- // =============================================================================
28
- // 401 detection patterns
29
- // =============================================================================
30
-
31
- /** Patterns that indicate an auth failure requiring token refresh. */
32
- const AUTH_ERROR_PATTERNS = [
33
- "invalid access token",
34
- "token expired",
35
- "401",
36
- "unauthorized",
37
- "authentication",
38
- ] as const;
39
-
40
- const _logger = createLogger("qwen");
41
-
42
- // =============================================================================
43
- // Constants
44
- // =============================================================================
45
-
46
- // Mirrors qwen-code's DEFAULT_QWEN_BASE_URL (used when resource_url is absent).
47
- const DEFAULT_BASE_URL = "https://dashscope.aliyuncs.com/compatible-mode/v1";
48
-
49
- // Headers required by DashScope's OpenAI-compatible API for OAuth tokens.
50
- // Replicates DashScopeOpenAICompatibleProvider.buildHeaders() from qwen-code.
51
- const DASHSCOPE_HEADERS = {
52
- "X-DashScope-AuthType": "qwen-oauth",
53
- "X-DashScope-CacheControl": "enable",
54
- "X-DashScope-UserAgent": "QwenCode/0.0.5 (pi-free)",
55
- "Client-Code": "QwenCode",
56
- };
57
-
58
- // =============================================================================
59
- // Extension entry point
60
- // =============================================================================
61
-
62
- export default async function qwenProvider(pi: ExtensionAPI) {
63
- // DEPRECATION WARNING
64
- _logger.warn(
65
- "Qwen provider is deprecated. The 1,000 req/day free tier is no longer available.",
66
- );
67
-
68
- // Fetch static free-tier models
69
- const models = await fetchQwenModels().catch((err) => {
70
- logWarning("qwen", "Failed to load models at startup", err);
71
- return [];
72
- });
73
-
74
- if (models.length === 0) {
75
- logWarning("qwen", "No models available, skipping provider");
76
- return;
77
- }
78
-
79
- const stored: StoredModels = { free: models, all: models };
80
-
81
- // OAuth config for Qwen
82
- const oauthConfig = {
83
- name: "Qwen",
84
- login: loginQwen,
85
- refreshToken: refreshQwenToken,
86
- getApiKey: (cred: OAuthCredentials) => cred.access,
87
- modifyModels: (models: Model<Api>[], cred: OAuthCredentials) => {
88
- // Mirror qwen-code: resolve baseUrl from resource_url per-credential.
89
- // Chinese accounts → dashscope.aliyuncs.com/v1
90
- // International accounts → portal.qwen.ai/v1 (or custom endpoint)
91
- const baseUrl = getQwenBaseUrl(cred);
92
- _logger.info("Qwen OAuth modifyModels called", {
93
- baseUrl,
94
- resource_url: cred.resource_url,
95
- modelCount: models.length,
96
- });
97
- if (baseUrl === DEFAULT_BASE_URL) return models;
98
- // modifyModels receives ALL models across providers — only patch Qwen ones.
99
- const nonQwen = models.filter((m) => m.provider !== PROVIDER_QWEN);
100
- const qwen = models
101
- .filter((m) => m.provider === PROVIDER_QWEN)
102
- .map((m) => ({ ...m, baseUrl }));
103
- return [...nonQwen, ...qwen];
104
- },
105
- };
106
-
107
- // Register provider with OpenAI-compatible API
108
- function registerProvider(m = models) {
109
- pi.registerProvider(PROVIDER_QWEN, {
110
- baseUrl: DEFAULT_BASE_URL,
111
- apiKey: "$QWEN_API_KEY",
112
- api: "openai-completions" as const,
113
- headers: {
114
- "User-Agent": "pi-free",
115
- ...DASHSCOPE_HEADERS,
116
- },
117
- models: enhanceWithCI(m),
118
- oauth: oauthConfig,
119
- });
120
- }
121
-
122
- registerProvider();
123
-
124
- // Wire up shared boilerplate (commands, model_select, turn_end, ToS)
125
- const reRegister = createReRegister(pi, {
126
- providerId: PROVIDER_QWEN,
127
- baseUrl: DEFAULT_BASE_URL,
128
- apiKey: "$QWEN_API_KEY",
129
- oauth: oauthConfig as any,
130
- });
131
-
132
- setupProvider(
133
- pi,
134
- {
135
- providerId: PROVIDER_QWEN,
136
- tosUrl: URL_QWEN_TOS,
137
- initialShowPaid: false,
138
- reRegister: (m) => {
139
- reRegister(m);
140
- },
141
- },
142
- stored,
143
- );
144
-
145
- // Request counting + 401 auth-error detection with forced token refresh.
146
- //
147
- // When Qwen returns a 401 / "invalid access token" error, the stored token
148
- // may have been revoked server-side even though its expiry hasn't been reached.
149
- // We force-expire the credential in auth storage so that pi-core's next
150
- // getApiKey() call will trigger a token refresh via refreshToken().
151
- // This mirrors qwen-code's executeWithCredentialManagement() retry logic.
152
- //
153
- function isQwenAuthError(msg: {
154
- role?: string;
155
- errorMessage?: string;
156
- }): boolean {
157
- if (msg.role !== "assistant" || !msg.errorMessage) return false;
158
- const errLower = msg.errorMessage.toLowerCase();
159
- return AUTH_ERROR_PATTERNS.some((p) => errLower.includes(p));
160
- }
161
-
162
- function forceExpireQwenToken(
163
- ctx: any,
164
- msg: { errorMessage?: string },
165
- ): void {
166
- _logger.warn("Qwen auth error detected, force-expiring token for refresh", {
167
- error: (msg.errorMessage ?? "").slice(0, 100),
168
- });
169
-
170
- try {
171
- const authStorage = (ctx as any).modelRegistry?.authStorage;
172
- if (authStorage) {
173
- const cred = authStorage.get(PROVIDER_QWEN);
174
- if (cred?.type === "oauth" && cred.expires > Date.now()) {
175
- authStorage.set(PROVIDER_QWEN, { ...cred, expires: 0 });
176
- _logger.info(
177
- "Qwen token force-expired; will refresh on next request",
178
- );
179
- }
180
- }
181
- ctx.ui.notify("Qwen: auth error detected, refreshing token…", "warning");
182
- } catch (e) {
183
- _logger.warn("Failed to force-expire Qwen token", {
184
- error: e instanceof Error ? e.message : String(e),
185
- });
186
- }
187
- }
188
-
189
- pi.on("turn_end", async (event, ctx) => {
190
- if (ctx.model?.provider !== PROVIDER_QWEN) return;
191
-
192
- const msg = (
193
- event as { message?: { role?: string; errorMessage?: string } }
194
- ).message;
195
-
196
- if (msg && isQwenAuthError(msg)) {
197
- forceExpireQwenToken(ctx, msg);
198
- }
199
- });
200
- }