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 +30 -14
- package/package.json +27 -7
- package/usage/formatters.ts +2 -18
- package/usage/limits.ts +3 -21
- package/usage/types.ts +26 -0
- 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/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
|
-
-
|
|
12
|
-
-
|
|
13
|
-
-
|
|
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.
|
|
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
|
}
|
package/usage/formatters.ts
CHANGED
|
@@ -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
|
-
}
|