pi-free 1.0.2 → 1.0.3
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/package.json +27 -7
- package/provider-failover/errors.ts +0 -275
- package/provider-failover/hardcoded-benchmarks.ts +0 -9911
- package/provider-failover/index.ts +0 -194
- package/widget/data.ts +0 -113
- package/widget/format.ts +0 -26
- package/widget/render.ts +0 -117
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-free",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.3",
|
|
4
4
|
"type": "module",
|
|
5
|
-
"description": "AIO Free AI models for Pi",
|
|
5
|
+
"description": "AIO Free AI models for Pi - Access free models from Kilo, Zen, OpenRouter, NVIDIA, Cline, Mistral, and Ollama",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"pi-package",
|
|
8
8
|
"pi-extension",
|
|
@@ -10,7 +10,6 @@
|
|
|
10
10
|
"ai-providers",
|
|
11
11
|
"openrouter",
|
|
12
12
|
"nvidia-nim",
|
|
13
|
-
"opencode",
|
|
14
13
|
"kilo",
|
|
15
14
|
"cline",
|
|
16
15
|
"ollama",
|
|
@@ -23,11 +22,36 @@
|
|
|
23
22
|
"bugs": {
|
|
24
23
|
"url": "https://github.com/apmantza/pi-free/issues"
|
|
25
24
|
},
|
|
25
|
+
"repository": {
|
|
26
|
+
"type": "git",
|
|
27
|
+
"url": "git+https://github.com/apmantza/pi-free.git"
|
|
28
|
+
},
|
|
29
|
+
"engines": {
|
|
30
|
+
"node": ">=20.0.0"
|
|
31
|
+
},
|
|
32
|
+
"files": [
|
|
33
|
+
"providers/**/*.ts",
|
|
34
|
+
"lib/**/*.ts",
|
|
35
|
+
"usage/**/*.ts",
|
|
36
|
+
"config.ts",
|
|
37
|
+
"constants.ts",
|
|
38
|
+
"provider-factory.ts",
|
|
39
|
+
"provider-helper.ts",
|
|
40
|
+
"README.md",
|
|
41
|
+
"LICENSE",
|
|
42
|
+
"CHANGELOG.md"
|
|
43
|
+
],
|
|
26
44
|
"scripts": {
|
|
27
45
|
"test": "vitest",
|
|
28
46
|
"test:ui": "vitest --ui",
|
|
29
47
|
"test:run": "vitest run"
|
|
30
48
|
},
|
|
49
|
+
"peerDependencies": {
|
|
50
|
+
"@mariozechner/pi-ai": "*",
|
|
51
|
+
"@mariozechner/pi-coding-agent": "*",
|
|
52
|
+
"@mariozechner/pi-tui": "*",
|
|
53
|
+
"@sinclair/typebox": "*"
|
|
54
|
+
},
|
|
31
55
|
"devDependencies": {
|
|
32
56
|
"vitest": "^1.0.0",
|
|
33
57
|
"@vitest/ui": "^1.0.0",
|
|
@@ -44,9 +68,5 @@
|
|
|
44
68
|
"./providers/mistral.ts",
|
|
45
69
|
"./providers/ollama.ts"
|
|
46
70
|
]
|
|
47
|
-
},
|
|
48
|
-
"repository": {
|
|
49
|
-
"type": "git",
|
|
50
|
-
"url": "git+https://github.com/apmantza/pi-free.git"
|
|
51
71
|
}
|
|
52
72
|
}
|
|
@@ -1,275 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Error classification for provider failover
|
|
3
|
-
* Detects 429 rate limits, capacity errors, and other provider-specific errors
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { createLogger } from "../lib/logger.ts";
|
|
7
|
-
import { getFreeTierUsage, getLimitWarning } from "../usage/limits.ts";
|
|
8
|
-
|
|
9
|
-
const _logger = createLogger("failover");
|
|
10
|
-
|
|
11
|
-
export type ErrorType =
|
|
12
|
-
| "rate_limit" // 429, quota exceeded
|
|
13
|
-
| "capacity" // No capacity, overloaded
|
|
14
|
-
| "auth" // Invalid key, unauthorized
|
|
15
|
-
| "network" // Timeout, connection error
|
|
16
|
-
| "unknown"; // Unclassified
|
|
17
|
-
|
|
18
|
-
export interface ClassifiedError {
|
|
19
|
-
type: ErrorType;
|
|
20
|
-
provider?: string;
|
|
21
|
-
model?: string;
|
|
22
|
-
statusCode?: number;
|
|
23
|
-
message: string;
|
|
24
|
-
retryable: boolean;
|
|
25
|
-
retryAfterMs?: number; // Server-suggested retry delay
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
// Pattern matching for various provider error messages
|
|
29
|
-
const RATE_LIMIT_PATTERNS = [
|
|
30
|
-
/429/i,
|
|
31
|
-
/rate.?limit/i,
|
|
32
|
-
/too.?many.?requests/i,
|
|
33
|
-
/quota.*exceeded/i,
|
|
34
|
-
/insufficient.*quota/i,
|
|
35
|
-
/billing.*quota/i,
|
|
36
|
-
/limit.*exceeded/i,
|
|
37
|
-
/throttled/i,
|
|
38
|
-
/ratelimit/i,
|
|
39
|
-
];
|
|
40
|
-
|
|
41
|
-
const CAPACITY_PATTERNS = [
|
|
42
|
-
/no.*capacity/i,
|
|
43
|
-
/overloaded/i,
|
|
44
|
-
/engine.*overloaded/i,
|
|
45
|
-
/temporarily.*unavailable/i,
|
|
46
|
-
/service.*unavailable/i,
|
|
47
|
-
/503/i,
|
|
48
|
-
/529/i, // Cloudflare origin is overloaded
|
|
49
|
-
/busy/i,
|
|
50
|
-
];
|
|
51
|
-
|
|
52
|
-
const AUTH_PATTERNS = [
|
|
53
|
-
/401/i,
|
|
54
|
-
/403/i,
|
|
55
|
-
/unauthorized/i,
|
|
56
|
-
/invalid.*key/i,
|
|
57
|
-
/invalid.*token/i,
|
|
58
|
-
/authentication/i,
|
|
59
|
-
/api.*key.*invalid/i,
|
|
60
|
-
/key.*not.*valid/i,
|
|
61
|
-
/invalid.*api.*key/i,
|
|
62
|
-
/invalid.*auth/i,
|
|
63
|
-
];
|
|
64
|
-
|
|
65
|
-
const NETWORK_PATTERNS = [
|
|
66
|
-
/timeout/i,
|
|
67
|
-
/etimedout/i,
|
|
68
|
-
/enetunreach/i,
|
|
69
|
-
/econnreset/i,
|
|
70
|
-
/connection.*refused/i,
|
|
71
|
-
/fetch.*failed/i,
|
|
72
|
-
/network.*error/i,
|
|
73
|
-
/abort/i,
|
|
74
|
-
/signal/i,
|
|
75
|
-
];
|
|
76
|
-
|
|
77
|
-
/**
|
|
78
|
-
* Extract HTTP status code from error object or message
|
|
79
|
-
*/
|
|
80
|
-
function extractStatusCode(error: unknown): number | undefined {
|
|
81
|
-
// Check for statusCode property
|
|
82
|
-
if (
|
|
83
|
-
typeof error === "object" &&
|
|
84
|
-
error !== null &&
|
|
85
|
-
"statusCode" in error &&
|
|
86
|
-
typeof error.statusCode === "number"
|
|
87
|
-
) {
|
|
88
|
-
return error.statusCode;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
// Check for status property
|
|
92
|
-
if (
|
|
93
|
-
typeof error === "object" &&
|
|
94
|
-
error !== null &&
|
|
95
|
-
"status" in error &&
|
|
96
|
-
typeof error.status === "number"
|
|
97
|
-
) {
|
|
98
|
-
return error.status;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
// Extract from message
|
|
102
|
-
const message = String(error);
|
|
103
|
-
const match = message.match(/\b(\d{3})\b/);
|
|
104
|
-
if (match) {
|
|
105
|
-
const code = Number.parseInt(match[1], 10);
|
|
106
|
-
if (code >= 400 && code < 600) return code;
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
return undefined;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
/**
|
|
113
|
-
* Extract retry-after hint from error
|
|
114
|
-
*/
|
|
115
|
-
function extractRetryAfter(error: unknown): number | undefined {
|
|
116
|
-
const message = String(error);
|
|
117
|
-
|
|
118
|
-
// Look for "retry after X seconds/minutes"
|
|
119
|
-
const secondsMatch = message.match(/retry.?after\s+(\d+)\s*s/i);
|
|
120
|
-
if (secondsMatch) {
|
|
121
|
-
return Number.parseInt(secondsMatch[1], 10) * 1000;
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
const minutesMatch = message.match(/retry.?after\s+(\d+)\s*m/i);
|
|
125
|
-
if (minutesMatch) {
|
|
126
|
-
return Number.parseInt(minutesMatch[1], 10) * 60 * 1000;
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
// Check for retry_after property
|
|
130
|
-
if (
|
|
131
|
-
typeof error === "object" &&
|
|
132
|
-
error !== null &&
|
|
133
|
-
"retry_after" in error &&
|
|
134
|
-
typeof error.retry_after === "number"
|
|
135
|
-
) {
|
|
136
|
-
return error.retry_after * 1000;
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
return undefined;
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
/**
|
|
143
|
-
* Classify an error to determine if it's a 429/capacity issue
|
|
144
|
-
*/
|
|
145
|
-
export function classifyError(error: unknown): ClassifiedError {
|
|
146
|
-
const message = String(error);
|
|
147
|
-
const statusCode = extractStatusCode(error);
|
|
148
|
-
const retryAfterMs = extractRetryAfter(error);
|
|
149
|
-
|
|
150
|
-
// Check status code first
|
|
151
|
-
if (statusCode === 429) {
|
|
152
|
-
return {
|
|
153
|
-
type: "rate_limit",
|
|
154
|
-
statusCode,
|
|
155
|
-
message,
|
|
156
|
-
retryable: true,
|
|
157
|
-
retryAfterMs: retryAfterMs ?? 60000, // Default 1 min
|
|
158
|
-
};
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
if (statusCode === 503 || statusCode === 529) {
|
|
162
|
-
return {
|
|
163
|
-
type: "capacity",
|
|
164
|
-
statusCode,
|
|
165
|
-
message,
|
|
166
|
-
retryable: true,
|
|
167
|
-
retryAfterMs: retryAfterMs ?? 30000, // Default 30 sec
|
|
168
|
-
};
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
if (statusCode === 401 || statusCode === 403) {
|
|
172
|
-
return {
|
|
173
|
-
type: "auth",
|
|
174
|
-
statusCode,
|
|
175
|
-
message,
|
|
176
|
-
retryable: false,
|
|
177
|
-
};
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
// Check patterns in message
|
|
181
|
-
if (RATE_LIMIT_PATTERNS.some((p) => p.test(message))) {
|
|
182
|
-
return {
|
|
183
|
-
type: "rate_limit",
|
|
184
|
-
statusCode,
|
|
185
|
-
message,
|
|
186
|
-
retryable: true,
|
|
187
|
-
retryAfterMs: retryAfterMs ?? 60000,
|
|
188
|
-
};
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
if (CAPACITY_PATTERNS.some((p) => p.test(message))) {
|
|
192
|
-
return {
|
|
193
|
-
type: "capacity",
|
|
194
|
-
statusCode,
|
|
195
|
-
message,
|
|
196
|
-
retryable: true,
|
|
197
|
-
retryAfterMs: retryAfterMs ?? 30000,
|
|
198
|
-
};
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
if (AUTH_PATTERNS.some((p) => p.test(message))) {
|
|
202
|
-
return {
|
|
203
|
-
type: "auth",
|
|
204
|
-
statusCode,
|
|
205
|
-
message,
|
|
206
|
-
retryable: false,
|
|
207
|
-
};
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
if (NETWORK_PATTERNS.some((p) => p.test(message))) {
|
|
211
|
-
return {
|
|
212
|
-
type: "network",
|
|
213
|
-
statusCode,
|
|
214
|
-
message,
|
|
215
|
-
retryable: true,
|
|
216
|
-
retryAfterMs: 5000, // Short retry for network
|
|
217
|
-
};
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
// Unknown error - assume retryable but with caution
|
|
221
|
-
return {
|
|
222
|
-
type: "unknown",
|
|
223
|
-
statusCode,
|
|
224
|
-
message,
|
|
225
|
-
retryable: statusCode ? statusCode >= 500 : true,
|
|
226
|
-
retryAfterMs: 10000,
|
|
227
|
-
};
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
/**
|
|
231
|
-
* Check if error is specifically a rate limit (429)
|
|
232
|
-
*/
|
|
233
|
-
export function isRateLimit(error: unknown): boolean {
|
|
234
|
-
return classifyError(error).type === "rate_limit";
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
/**
|
|
238
|
-
* Check if error is capacity-related (provider overloaded)
|
|
239
|
-
*/
|
|
240
|
-
export function isCapacityError(error: unknown): boolean {
|
|
241
|
-
return classifyError(error).type === "capacity";
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
/**
|
|
245
|
-
* Log error classification for debugging
|
|
246
|
-
*/
|
|
247
|
-
export function logErrorClassification(
|
|
248
|
-
_error: unknown,
|
|
249
|
-
classified: ClassifiedError,
|
|
250
|
-
): void {
|
|
251
|
-
_logger.info(`Error classified: ${classified.type}`, {
|
|
252
|
-
statusCode: classified.statusCode,
|
|
253
|
-
retryable: classified.retryable,
|
|
254
|
-
retryAfterMs: classified.retryAfterMs,
|
|
255
|
-
message: classified.message.slice(0, 100),
|
|
256
|
-
});
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
/**
|
|
260
|
-
* Log free tier usage when rate limit occurs
|
|
261
|
-
* Helps users understand their quota consumption
|
|
262
|
-
*/
|
|
263
|
-
export function logFreeTierUsage(provider: string): void {
|
|
264
|
-
const usage = getFreeTierUsage(provider);
|
|
265
|
-
const warning = getLimitWarning(provider);
|
|
266
|
-
|
|
267
|
-
if (warning) {
|
|
268
|
-
_logger.warn(`Free tier warning: ${warning}`, { provider });
|
|
269
|
-
} else {
|
|
270
|
-
_logger.info(`${provider} usage`, {
|
|
271
|
-
requestsToday: usage.requestsToday,
|
|
272
|
-
limit: usage.limit.description,
|
|
273
|
-
});
|
|
274
|
-
}
|
|
275
|
-
}
|