pi-free 1.0.2 → 1.0.4

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 CHANGED
@@ -7,10 +7,37 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [1.0.4] - 2025-04-03
11
+
12
+ ### Fixed
13
+ - **All tests now passing** (127/127)
14
+ - Fixed mock paths in kilo.test.ts, zen.test.ts, ollama.test.ts
15
+ - Fixed createCtxReRegister mocks in zen.test.ts and openrouter.test.ts
16
+ - Fixed cline.test.ts to test actual provider re-registration behavior
17
+ - Added missing DEFAULT_MIN_SIZE_B constant to openrouter mock
18
+
19
+ ### Changed
20
+ - **Code quality improvements**
21
+ - Refactored usage modules to break circular dependency (limits.ts ↔ formatters.ts)
22
+ - Created usage/types.ts with shared interfaces (FreeTierLimit, FreeTierUsage)
23
+ - Bumped version to 1.0.4
24
+
25
+ ## [1.0.3] - 2025-04-03
26
+
27
+ ### Changed
28
+ - Updated package.json metadata (name, description, keywords, repository URL)
29
+ - Updated .npmignore for cleaner publishes
30
+
31
+ ## [1.0.0] - 2024-03-28
32
+
10
33
  ### Added
11
- - Structured logging with namespaced logger (lib/logger.ts)
12
- - LICENSE file (MIT)
13
- - CHANGELOG.md
34
+ - Initial release with 6 providers: Kilo, Zen, OpenRouter, NVIDIA, Cline, Fireworks
35
+ - Free tier usage tracking across all sessions
36
+ - Provider failover with model hopping
37
+ - Autocompact integration for rate limit recovery
38
+ - Usage widget with glimpseui
39
+ - Command toggles for free/all model filtering
40
+ - Hardcoded benchmark data from Artificial Analysis
14
41
 
15
42
  ### Changed
16
43
  - **Major refactoring**: Split free-tier-limits.ts into usage/* modules
@@ -46,14 +73,3 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
46
73
  - fetchWithRetry() now properly throws after exhausting retries
47
74
  - Auth error pattern matching now handles more message variants
48
75
  - Test isolation for free-tier-limits tests
49
-
50
- ## [1.0.0] - 2024-03-28
51
-
52
- ### Added
53
- - Initial release with 6 providers: Kilo, Zen, OpenRouter, NVIDIA, Cline, Fireworks
54
- - Free tier usage tracking across all sessions
55
- - Provider failover with model hopping
56
- - Autocompact integration for rate limit recovery
57
- - Usage widget with glimpseui
58
- - Command toggles for free/all model filtering
59
- - Hardcoded benchmark data from Artificial Analysis
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "pi-free",
3
- "version": "1.0.2",
3
+ "version": "1.0.4",
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
  }
@@ -3,25 +3,9 @@
3
3
  */
4
4
 
5
5
  import type { CumulativeUsageReport } from "./cumulative.ts";
6
- import {
7
- type FreeTierLimit,
8
- getFreeTierUsage,
9
- getLimitWarning,
10
- } from "./limits.ts";
6
+ import { getFreeTierUsage, getLimitWarning } from "./limits.ts";
11
7
  import type { SessionUsageReport } from "./tracking.ts";
12
-
13
- export interface FreeTierUsage {
14
- provider: string;
15
- requestsToday: number;
16
- requestsThisHour: number;
17
- requestsThisMonth?: number;
18
- limit: FreeTierLimit;
19
- remainingToday?: number;
20
- remainingThisHour?: number;
21
- remainingThisMonth?: number;
22
- percentUsed: number;
23
- status: "ok" | "warning" | "critical" | "unknown";
24
- }
8
+ // Types imported via limits.ts (which re-exports from types.ts)
25
9
 
26
10
  export function formatSessionUsage(report: SessionUsageReport): string {
27
11
  if (report.providers.length === 0) {
package/usage/limits.ts CHANGED
@@ -9,6 +9,7 @@
9
9
 
10
10
  import { createLogger } from "../lib/logger.ts";
11
11
  import { getDailyRequestCount } from "./metrics.ts";
12
+ import type { FreeTierLimit, FreeTierUsage } from "./types.ts";
12
13
 
13
14
  export {
14
15
  type CumulativeUsageReport,
@@ -30,6 +31,8 @@ export {
30
31
  resetUsageStats,
31
32
  type SessionUsageReport,
32
33
  } from "./tracking.ts";
34
+ // Re-export types for consumers
35
+ export type { FreeTierLimit, FreeTierUsage } from "./types.ts";
33
36
 
34
37
  const _logger = createLogger("free-tier");
35
38
 
@@ -37,14 +40,6 @@ const _logger = createLogger("free-tier");
37
40
  // Free Tier Limits Configuration
38
41
  // =============================================================================
39
42
 
40
- export interface FreeTierLimit {
41
- provider: string;
42
- requestsPerDay?: number;
43
- requestsPerHour?: number;
44
- requestsPerMonth?: number;
45
- description: string;
46
- }
47
-
48
43
  export const FREE_TIER_LIMITS: Record<string, FreeTierLimit> = {
49
44
  kilo: {
50
45
  provider: "kilo",
@@ -80,19 +75,6 @@ export const FREE_TIER_LIMITS: Record<string, FreeTierLimit> = {
80
75
  // Usage Status and Warnings
81
76
  // =============================================================================
82
77
 
83
- export interface FreeTierUsage {
84
- provider: string;
85
- requestsToday: number;
86
- requestsThisHour: number;
87
- requestsThisMonth?: number;
88
- limit: FreeTierLimit;
89
- remainingToday?: number;
90
- remainingThisHour?: number;
91
- remainingThisMonth?: number;
92
- percentUsed: number;
93
- status: "ok" | "warning" | "critical" | "unknown";
94
- }
95
-
96
78
  export function getFreeTierUsage(provider: string): FreeTierUsage {
97
79
  const limit = FREE_TIER_LIMITS[provider];
98
80
  if (!limit) {
package/usage/types.ts ADDED
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Shared types for usage tracking modules
3
+ *
4
+ * Extracted to break circular dependency between limits.ts and formatters.ts
5
+ */
6
+
7
+ export interface FreeTierLimit {
8
+ provider: string;
9
+ requestsPerDay?: number;
10
+ requestsPerHour?: number;
11
+ requestsPerMonth?: number;
12
+ description: string;
13
+ }
14
+
15
+ export interface FreeTierUsage {
16
+ provider: string;
17
+ requestsToday: number;
18
+ requestsThisHour: number;
19
+ requestsThisMonth?: number;
20
+ limit: FreeTierLimit;
21
+ remainingToday?: number;
22
+ remainingThisHour?: number;
23
+ remainingThisMonth?: number;
24
+ percentUsed: number;
25
+ status: "ok" | "warning" | "critical" | "unknown";
26
+ }
@@ -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
- }