opencode-pollinations-plugin 6.2.7 → 6.3.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/README.de.md +9 -1
- package/README.es.md +9 -1
- package/README.fr.md +9 -1
- package/README.it.md +9 -1
- package/README.md +35 -16
- package/README.zh.md +9 -1
- package/dist/index.js +11 -7
- package/dist/locales/de.json +94 -6
- package/dist/locales/en.json +94 -6
- package/dist/locales/es.json +94 -6
- package/dist/locales/fr.json +94 -6
- package/dist/locales/index.js +3 -1
- package/dist/locales/it.json +94 -6
- package/dist/locales/zh.json +461 -0
- package/dist/server/commands.d.ts +8 -0
- package/dist/server/commands.js +203 -13
- package/dist/server/connect-response.js +1 -1
- package/dist/server/generate-config.js +4 -6
- package/dist/server/models/cache.js +1 -1
- package/dist/server/models/fetcher.js +24 -2
- package/dist/server/quota.js +29 -8
- package/dist/server/tier-info.d.ts +9 -4
- package/dist/server/tier-info.js +29 -18
- package/dist/tools/index.d.ts +2 -1
- package/dist/tools/index.js +14 -1
- package/dist/tools/pollinations/beta_discovery.d.ts +11 -4
- package/dist/tools/pollinations/beta_discovery.js +288 -136
- package/dist/tools/pollinations/gen_edit_image_free.d.ts +14 -0
- package/dist/tools/pollinations/gen_edit_image_free.js +146 -0
- package/dist/tools/pollinations/gen_video.js +2 -2
- package/dist/tools/pollinations/gen_video_free.d.ts +19 -0
- package/dist/tools/pollinations/gen_video_free.js +246 -0
- package/dist/tools/pollinations/polli_config.js +1 -1
- package/dist/tools/pollinations/polli_login.d.ts +13 -0
- package/dist/tools/pollinations/polli_login.js +30 -0
- package/dist/tools/pollinations/polli_quests.d.ts +3 -0
- package/dist/tools/pollinations/polli_quests.js +135 -0
- package/package.json +2 -2
- package/dist/server/index.d.ts +0 -2
- package/dist/server/index.js +0 -158
- package/dist/server/scripts/test_cost_endpoints.d.ts +0 -1
- package/dist/server/scripts/test_cost_endpoints.js +0 -61
- package/dist/server/scripts/test_dynamic_pricing.d.ts +0 -1
- package/dist/server/scripts/test_dynamic_pricing.js +0 -39
- package/dist/server/scripts/test_freetier_audit.d.ts +0 -11
- package/dist/server/scripts/test_freetier_audit.js +0 -215
- package/dist/server/scripts/test_parallel_cost.d.ts +0 -1
- package/dist/server/scripts/test_parallel_cost.js +0 -104
- package/dist/tools/pollinations/deepsearch.d.ts +0 -7
- package/dist/tools/pollinations/deepsearch.js +0 -80
- package/dist/tools/pollinations/search_crawl_scrape.d.ts +0 -7
- package/dist/tools/pollinations/search_crawl_scrape.js +0 -85
- package/dist/tools/pollinations/test_estimators.d.ts +0 -1
- package/dist/tools/pollinations/test_estimators.js +0 -22
package/dist/server/index.js
DELETED
|
@@ -1,158 +0,0 @@
|
|
|
1
|
-
import * as http from 'http';
|
|
2
|
-
import * as fs from 'fs';
|
|
3
|
-
import * as path from 'path';
|
|
4
|
-
import { getAggregatedModels } from './pollinations-api.js';
|
|
5
|
-
import { loadConfig, saveConfig } from './config.js';
|
|
6
|
-
import { handleChatCompletion } from './proxy.js';
|
|
7
|
-
import { createCommandHooks, setClientForCommands, checkKeyPermissions } from './commands.js';
|
|
8
|
-
import { fileURLToPath } from 'url';
|
|
9
|
-
const LOG_FILE = path.join(process.env.HOME || '/tmp', '.config/opencode/plugins/pollinations-v6.log');
|
|
10
|
-
// Simple file logger
|
|
11
|
-
function log(msg) {
|
|
12
|
-
const ts = new Date().toISOString();
|
|
13
|
-
try {
|
|
14
|
-
if (!fs.existsSync(path.dirname(LOG_FILE))) {
|
|
15
|
-
fs.mkdirSync(path.dirname(LOG_FILE), { recursive: true });
|
|
16
|
-
}
|
|
17
|
-
fs.appendFileSync(LOG_FILE, `[${ts}] ${msg}\n`);
|
|
18
|
-
}
|
|
19
|
-
catch (e) { }
|
|
20
|
-
}
|
|
21
|
-
const PORT = parseInt(process.env.POLLINATIONS_PORT || '10001', 10);
|
|
22
|
-
let serverInstance = null;
|
|
23
|
-
// --- SERVER LOGIC ---
|
|
24
|
-
function startServer() {
|
|
25
|
-
if (serverInstance)
|
|
26
|
-
return;
|
|
27
|
-
serverInstance = http.createServer(async (req, res) => {
|
|
28
|
-
log(`${req.method} ${req.url}`);
|
|
29
|
-
// CORS Headers
|
|
30
|
-
res.setHeader('Access-Control-Allow-Origin', '*');
|
|
31
|
-
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
|
|
32
|
-
res.setHeader('Access-Control-Allow-Headers', '*');
|
|
33
|
-
if (req.method === 'OPTIONS') {
|
|
34
|
-
res.writeHead(204);
|
|
35
|
-
res.end();
|
|
36
|
-
return;
|
|
37
|
-
}
|
|
38
|
-
// AUTH ENDPOINT
|
|
39
|
-
if (req.method === 'POST' && req.url === '/v1/auth') {
|
|
40
|
-
const chunks = [];
|
|
41
|
-
req.on('data', chunk => chunks.push(chunk));
|
|
42
|
-
req.on('end', async () => {
|
|
43
|
-
try {
|
|
44
|
-
const body = JSON.parse(Buffer.concat(chunks).toString());
|
|
45
|
-
if (body && body.apiKey) {
|
|
46
|
-
saveConfig({ apiKey: body.apiKey, mode: 'pro' });
|
|
47
|
-
log(`[AUTH] Key saved via Server Endpoint`);
|
|
48
|
-
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
49
|
-
res.end(JSON.stringify({ status: "ok" }));
|
|
50
|
-
}
|
|
51
|
-
else {
|
|
52
|
-
res.writeHead(400);
|
|
53
|
-
res.end(JSON.stringify({ error: "Missing apiKey" }));
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
catch (e) {
|
|
57
|
-
log(`[AUTH] Error: ${e}`);
|
|
58
|
-
res.writeHead(500);
|
|
59
|
-
res.end(JSON.stringify({ error: String(e) }));
|
|
60
|
-
}
|
|
61
|
-
});
|
|
62
|
-
return;
|
|
63
|
-
}
|
|
64
|
-
if (req.method === 'GET' && req.url === '/health') {
|
|
65
|
-
const config = loadConfig();
|
|
66
|
-
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
67
|
-
res.end(JSON.stringify({
|
|
68
|
-
status: "ok",
|
|
69
|
-
version: "v6.0.0-beta.99",
|
|
70
|
-
mode: config.mode,
|
|
71
|
-
hasKey: !!config.apiKey
|
|
72
|
-
}));
|
|
73
|
-
return;
|
|
74
|
-
}
|
|
75
|
-
if (req.method === 'GET' && req.url === '/v1/models') {
|
|
76
|
-
try {
|
|
77
|
-
const models = await getAggregatedModels();
|
|
78
|
-
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
79
|
-
res.end(JSON.stringify(models));
|
|
80
|
-
}
|
|
81
|
-
catch (e) {
|
|
82
|
-
log(`Error fetching models: ${e}`);
|
|
83
|
-
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
84
|
-
res.end(JSON.stringify({ error: "Failed to fetch models" }));
|
|
85
|
-
}
|
|
86
|
-
return;
|
|
87
|
-
}
|
|
88
|
-
if (req.method === 'POST' && req.url === '/v1/chat/completions') {
|
|
89
|
-
const chunks = [];
|
|
90
|
-
req.on('data', chunk => chunks.push(chunk));
|
|
91
|
-
req.on('end', async () => {
|
|
92
|
-
try {
|
|
93
|
-
const bodyRaw = Buffer.concat(chunks).toString();
|
|
94
|
-
await handleChatCompletion(req, res, bodyRaw);
|
|
95
|
-
}
|
|
96
|
-
catch (e) {
|
|
97
|
-
log(`Error in chat handler: ${e}`);
|
|
98
|
-
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
99
|
-
res.end(JSON.stringify({ error: "Internal Server Error in Chat Handler" }));
|
|
100
|
-
}
|
|
101
|
-
});
|
|
102
|
-
return;
|
|
103
|
-
}
|
|
104
|
-
res.writeHead(404);
|
|
105
|
-
res.end("Not Found");
|
|
106
|
-
});
|
|
107
|
-
// ANTI-ZOMBIE
|
|
108
|
-
try {
|
|
109
|
-
const { execSync } = require('child_process');
|
|
110
|
-
try {
|
|
111
|
-
console.log(`[POLLINATIONS] Checking port ${PORT}...`);
|
|
112
|
-
execSync(`fuser -k ${PORT}/tcp || true`);
|
|
113
|
-
console.log(`[POLLINATIONS] Port ${PORT} cleared.`);
|
|
114
|
-
}
|
|
115
|
-
catch (e) { }
|
|
116
|
-
}
|
|
117
|
-
catch (e) { }
|
|
118
|
-
// STARTUP CHECK
|
|
119
|
-
(async () => {
|
|
120
|
-
const config = loadConfig();
|
|
121
|
-
if (config.apiKey) {
|
|
122
|
-
try {
|
|
123
|
-
console.log('Pollinations Plugin: Verifying API Key on startup...');
|
|
124
|
-
const check = await checkKeyPermissions(config.apiKey);
|
|
125
|
-
if (!check.ok) {
|
|
126
|
-
console.warn(`Pollinations Plugin: Limited Key Detected on Startup (${check.reason}). Enforcing Manual Mode.`);
|
|
127
|
-
saveConfig({ apiKey: config.apiKey, mode: 'manual', keyHasAccessToProfile: false });
|
|
128
|
-
}
|
|
129
|
-
else {
|
|
130
|
-
if (config.keyHasAccessToProfile === false)
|
|
131
|
-
saveConfig({ apiKey: config.apiKey, keyHasAccessToProfile: true });
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
catch (e) {
|
|
135
|
-
console.error('Pollinations Plugin: Startup Check Failed:', e);
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
})();
|
|
139
|
-
serverInstance.listen(PORT, '127.0.0.1', () => {
|
|
140
|
-
const url = `http://127.0.0.1:${PORT}`;
|
|
141
|
-
log(`[SERVER] Started V6 (Plugin Mode) on port ${PORT}`);
|
|
142
|
-
console.log(`POLLINATIONS_V6_URL=${url}`);
|
|
143
|
-
});
|
|
144
|
-
}
|
|
145
|
-
// --- OPENCODE PLUGIN EXPORT ---
|
|
146
|
-
export const plugin = async ({ client }) => {
|
|
147
|
-
// 1. Inject Client for Command Handling
|
|
148
|
-
setClientForCommands(client);
|
|
149
|
-
// 2. Start Local Proxy Server
|
|
150
|
-
startServer();
|
|
151
|
-
// 3. Register Hooks (Commands, TUI, etc.)
|
|
152
|
-
return createCommandHooks();
|
|
153
|
-
};
|
|
154
|
-
// --- STANDALONE SUPPORT ---
|
|
155
|
-
// If run directly via `node dist/index.js`, start server immediately
|
|
156
|
-
if (process.argv[1] === fileURLToPath(import.meta.url)) {
|
|
157
|
-
startServer();
|
|
158
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
const API_KEY = process.env.POLLINATIONS_API_KEY || 'sk_eZbhgG1oJaaqSZKMvmy8nfVH9NNAGp0H';
|
|
2
|
-
async function logBalance(label) {
|
|
3
|
-
try {
|
|
4
|
-
const res = await fetch('https://gen.pollinations.ai/account/balance', {
|
|
5
|
-
headers: { 'Authorization': `Bearer ${API_KEY}` }
|
|
6
|
-
});
|
|
7
|
-
const bal = await res.json();
|
|
8
|
-
console.log(`\n[${label}] /account/balance :`);
|
|
9
|
-
console.log(JSON.stringify(bal, null, 2));
|
|
10
|
-
const resUsage = await fetch('https://gen.pollinations.ai/account/usage/daily', {
|
|
11
|
-
headers: { 'Authorization': `Bearer ${API_KEY}` }
|
|
12
|
-
});
|
|
13
|
-
const usg = await resUsage.json();
|
|
14
|
-
// Sum today stats
|
|
15
|
-
let costTier = 0;
|
|
16
|
-
let costPack = 0;
|
|
17
|
-
let req = 0;
|
|
18
|
-
if (usg.usage && usg.usage.length > 0) {
|
|
19
|
-
const todayDate = usg.usage[0].date;
|
|
20
|
-
for (const x of usg.usage) {
|
|
21
|
-
if (x.date === todayDate) {
|
|
22
|
-
req += x.requests;
|
|
23
|
-
if (x.meter_source === 'tier')
|
|
24
|
-
costTier += x.cost_usd;
|
|
25
|
-
else
|
|
26
|
-
costPack += x.cost_usd;
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
console.log(`[${label}] /account/usage/daily (Today sum) : Requests = ${req}, TierCost = ${costTier}, PackCost = ${costPack}`);
|
|
31
|
-
return { bal, usage: { costTier, costPack, req } };
|
|
32
|
-
}
|
|
33
|
-
catch (e) {
|
|
34
|
-
console.error("Error fetching balance/usage:", e.message);
|
|
35
|
-
return null;
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
async function run() {
|
|
39
|
-
console.log("=== COST ENDPOINTS PROBE ===");
|
|
40
|
-
// 1. Avant
|
|
41
|
-
const stateBefore = await logBalance("BEFORE GENERATION");
|
|
42
|
-
// 2. Générer Fake Image (Flux : censé être free tier ou cheap)
|
|
43
|
-
console.log("\n🎬 Génération d'une image (modèle 'flux')...");
|
|
44
|
-
const url = 'https://gen.pollinations.ai/image/a_beautiful_red_square_cost_test?model=flux&width=512&height=512&nologo=true';
|
|
45
|
-
const start = Date.now();
|
|
46
|
-
const res = await fetch(url, { headers: { 'Authorization': `Bearer ${API_KEY}` } });
|
|
47
|
-
const buffer = await res.arrayBuffer();
|
|
48
|
-
console.log(`✅ Image reçue en ${Date.now() - start}ms, taille: ${buffer.byteLength}B`);
|
|
49
|
-
console.log(`Headers Cost:`, {
|
|
50
|
-
'x-usage-cost-usd': res.headers.get('x-usage-cost-usd'),
|
|
51
|
-
'x-usage-completion-image-tokens': res.headers.get('x-usage-completion-image-tokens')
|
|
52
|
-
});
|
|
53
|
-
// 3. Immediatement Apres
|
|
54
|
-
await logBalance("IMMEDIATELY AFTER");
|
|
55
|
-
// 4. Attente 5 secondes
|
|
56
|
-
console.log("\n⏳ Attente 5 secondes pour propagation /usage...");
|
|
57
|
-
await new Promise(r => setTimeout(r, 5000));
|
|
58
|
-
await logBalance("5 SECONDS AFTER");
|
|
59
|
-
}
|
|
60
|
-
run();
|
|
61
|
-
export {};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
import * as https from 'https';
|
|
2
|
-
function fetchJson(url) {
|
|
3
|
-
return new Promise((resolve, reject) => {
|
|
4
|
-
https.get(url, (res) => {
|
|
5
|
-
let data = '';
|
|
6
|
-
res.on('data', chunk => data += chunk);
|
|
7
|
-
res.on('end', () => resolve(JSON.parse(data)));
|
|
8
|
-
}).on('error', reject);
|
|
9
|
-
});
|
|
10
|
-
}
|
|
11
|
-
async function auditDynamicPricing() {
|
|
12
|
-
console.log("=== AUDIT PRICING DYNAMIQUE (TINYBIRD) ===");
|
|
13
|
-
try {
|
|
14
|
-
console.log("Fetching /api/model-stats...");
|
|
15
|
-
const statsProxy = await fetchJson('https://enter.pollinations.ai/api/model-stats');
|
|
16
|
-
const stats = statsProxy.data || [];
|
|
17
|
-
console.log(`\nReçu ${stats.length} statistiques de prix moyens (average cost) depuis Tinybird:`);
|
|
18
|
-
// On affiche les 10 premiers pour l'exemple
|
|
19
|
-
console.log(stats.slice(0, 10).map((s) => `- ${s.model}: ${parseFloat(s.avg_cost_usd).toFixed(6)}$/req (${s.request_count} reqs)`).join('\n'));
|
|
20
|
-
console.log("\nFetching /image/models...");
|
|
21
|
-
const imageModels = await fetchJson('https://gen.pollinations.ai/image/models');
|
|
22
|
-
console.log("\nCroisement Models API vs Tinybird Stats:");
|
|
23
|
-
for (const m of imageModels.slice(0, 5)) {
|
|
24
|
-
const stat = stats.find((s) => s.model === m.name);
|
|
25
|
-
console.log(`\nModèle: ${m.name}`);
|
|
26
|
-
console.log(`- API Pricing Base:`, m.pricing);
|
|
27
|
-
if (stat) {
|
|
28
|
-
console.log(`- Tinybird Real Average Cost: ${stat.avg_cost_usd}$ (Basé sur le vrai usage!)`);
|
|
29
|
-
}
|
|
30
|
-
else {
|
|
31
|
-
console.log(`- Tinybird Stats: Inconnu / Pas d'usage récent`);
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
catch (e) {
|
|
36
|
-
console.error("Erreur lors de l'audit:", e.message);
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
auditDynamicPricing();
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* TEST FREETIER AUDIT — Quick & Dirty
|
|
3
|
-
* Objectif: Tester les modèles freetier Enter pour vérifier:
|
|
4
|
-
* - Latence de réponse
|
|
5
|
-
* - Headers de coût (x-usage-*)
|
|
6
|
-
* - Real cost via /account/balance diff
|
|
7
|
-
* - Erreurs et paramètres
|
|
8
|
-
*
|
|
9
|
-
* Usage: npx tsx src/server/scripts/test_freetier_audit.ts
|
|
10
|
-
*/
|
|
11
|
-
export {};
|
|
@@ -1,215 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* TEST FREETIER AUDIT — Quick & Dirty
|
|
3
|
-
* Objectif: Tester les modèles freetier Enter pour vérifier:
|
|
4
|
-
* - Latence de réponse
|
|
5
|
-
* - Headers de coût (x-usage-*)
|
|
6
|
-
* - Real cost via /account/balance diff
|
|
7
|
-
* - Erreurs et paramètres
|
|
8
|
-
*
|
|
9
|
-
* Usage: npx tsx src/server/scripts/test_freetier_audit.ts
|
|
10
|
-
*/
|
|
11
|
-
const API_KEY = 'sk_eZbhgG1oJaaqSZKMvmy8nfVH9NNAGp0H';
|
|
12
|
-
const BASE = 'https://gen.pollinations.ai';
|
|
13
|
-
// ─── HELPERS ────────────────────────────────────────────────────────
|
|
14
|
-
async function getBalance() {
|
|
15
|
-
const res = await fetch(`${BASE}/account/balance`, {
|
|
16
|
-
headers: { 'Authorization': `Bearer ${API_KEY}` }
|
|
17
|
-
});
|
|
18
|
-
const data = await res.json();
|
|
19
|
-
return data.balance;
|
|
20
|
-
}
|
|
21
|
-
async function getProfile() {
|
|
22
|
-
const res = await fetch(`${BASE}/account/profile`, {
|
|
23
|
-
headers: { 'Authorization': `Bearer ${API_KEY}` }
|
|
24
|
-
});
|
|
25
|
-
return await res.json();
|
|
26
|
-
}
|
|
27
|
-
function extractCostHeaders(headers) {
|
|
28
|
-
const cost = {};
|
|
29
|
-
headers.forEach((v, k) => {
|
|
30
|
-
if (k.startsWith('x-usage') || k.startsWith('x-cost') || k.startsWith('x-pollen') || k.startsWith('x-meter')) {
|
|
31
|
-
cost[k] = v;
|
|
32
|
-
}
|
|
33
|
-
});
|
|
34
|
-
return cost;
|
|
35
|
-
}
|
|
36
|
-
async function testText(model, prompt = 'Say hi in 5 words') {
|
|
37
|
-
const balBefore = await getBalance();
|
|
38
|
-
const start = Date.now();
|
|
39
|
-
const res = await fetch(`${BASE}/v1/chat/completions`, {
|
|
40
|
-
method: 'POST',
|
|
41
|
-
headers: {
|
|
42
|
-
'Authorization': `Bearer ${API_KEY}`,
|
|
43
|
-
'Content-Type': 'application/json'
|
|
44
|
-
},
|
|
45
|
-
body: JSON.stringify({
|
|
46
|
-
model,
|
|
47
|
-
messages: [{ role: 'user', content: prompt }],
|
|
48
|
-
max_tokens: 30,
|
|
49
|
-
stream: false
|
|
50
|
-
}),
|
|
51
|
-
signal: AbortSignal.timeout(20000)
|
|
52
|
-
});
|
|
53
|
-
const latency = Date.now() - start;
|
|
54
|
-
const costHeaders = extractCostHeaders(res.headers);
|
|
55
|
-
let error;
|
|
56
|
-
let extraInfo;
|
|
57
|
-
if (!res.ok) {
|
|
58
|
-
const errBody = await res.text();
|
|
59
|
-
error = `${res.status}: ${errBody.substring(0, 200)}`;
|
|
60
|
-
}
|
|
61
|
-
else {
|
|
62
|
-
const body = await res.json();
|
|
63
|
-
const usage = body.usage;
|
|
64
|
-
if (usage) {
|
|
65
|
-
extraInfo = `prompt=${usage.prompt_tokens} comp=${usage.completion_tokens} total=${usage.total_tokens}`;
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
// Wait 1s for balance to propagate
|
|
69
|
-
await new Promise(r => setTimeout(r, 1000));
|
|
70
|
-
const balAfter = await getBalance();
|
|
71
|
-
return {
|
|
72
|
-
name: `Text: ${model}`,
|
|
73
|
-
model, type: 'text', latencyMs: latency, status: res.status,
|
|
74
|
-
costHeaders, balanceBefore: balBefore, balanceAfter: balAfter,
|
|
75
|
-
realCost: Math.round((balBefore - balAfter) * 10000) / 10000,
|
|
76
|
-
error, extraInfo
|
|
77
|
-
};
|
|
78
|
-
}
|
|
79
|
-
async function testImage(model) {
|
|
80
|
-
const balBefore = await getBalance();
|
|
81
|
-
const start = Date.now();
|
|
82
|
-
const prompt = encodeURIComponent('a small red square test');
|
|
83
|
-
const res = await fetch(`${BASE}/image/${prompt}?model=${model}&width=256&height=256&nologo=true`, {
|
|
84
|
-
headers: { 'Authorization': `Bearer ${API_KEY}` },
|
|
85
|
-
redirect: 'follow',
|
|
86
|
-
signal: AbortSignal.timeout(40000)
|
|
87
|
-
});
|
|
88
|
-
const latency = Date.now() - start;
|
|
89
|
-
const costHeaders = extractCostHeaders(res.headers);
|
|
90
|
-
let error;
|
|
91
|
-
let extraInfo;
|
|
92
|
-
if (!res.ok) {
|
|
93
|
-
error = `${res.status}: ${(await res.text()).substring(0, 200)}`;
|
|
94
|
-
}
|
|
95
|
-
else {
|
|
96
|
-
const ct = res.headers.get('content-type') || 'unknown';
|
|
97
|
-
const buf = await res.arrayBuffer();
|
|
98
|
-
extraInfo = `content-type=${ct}, size=${buf.byteLength}B`;
|
|
99
|
-
}
|
|
100
|
-
await new Promise(r => setTimeout(r, 1000));
|
|
101
|
-
const balAfter = await getBalance();
|
|
102
|
-
return {
|
|
103
|
-
name: `Image: ${model}`,
|
|
104
|
-
model, type: 'image', latencyMs: latency, status: res.status,
|
|
105
|
-
costHeaders, balanceBefore: balBefore, balanceAfter: balAfter,
|
|
106
|
-
realCost: Math.round((balBefore - balAfter) * 10000) / 10000,
|
|
107
|
-
error, extraInfo
|
|
108
|
-
};
|
|
109
|
-
}
|
|
110
|
-
async function testAudio(model, input = 'Hello test') {
|
|
111
|
-
const balBefore = await getBalance();
|
|
112
|
-
const start = Date.now();
|
|
113
|
-
const res = await fetch(`${BASE}/v1/audio/speech`, {
|
|
114
|
-
method: 'POST',
|
|
115
|
-
headers: {
|
|
116
|
-
'Authorization': `Bearer ${API_KEY}`,
|
|
117
|
-
'Content-Type': 'application/json'
|
|
118
|
-
},
|
|
119
|
-
body: JSON.stringify({
|
|
120
|
-
model,
|
|
121
|
-
input,
|
|
122
|
-
voice: 'alloy',
|
|
123
|
-
response_format: 'mp3'
|
|
124
|
-
}),
|
|
125
|
-
signal: AbortSignal.timeout(30000)
|
|
126
|
-
});
|
|
127
|
-
const latency = Date.now() - start;
|
|
128
|
-
const costHeaders = extractCostHeaders(res.headers);
|
|
129
|
-
let error;
|
|
130
|
-
let extraInfo;
|
|
131
|
-
if (!res.ok) {
|
|
132
|
-
error = `${res.status}: ${(await res.text()).substring(0, 200)}`;
|
|
133
|
-
}
|
|
134
|
-
else {
|
|
135
|
-
const buf = await res.arrayBuffer();
|
|
136
|
-
extraInfo = `audio size=${buf.byteLength}B`;
|
|
137
|
-
}
|
|
138
|
-
await new Promise(r => setTimeout(r, 1000));
|
|
139
|
-
const balAfter = await getBalance();
|
|
140
|
-
return {
|
|
141
|
-
name: `Audio: ${model}`,
|
|
142
|
-
model, type: 'audio', latencyMs: latency, status: res.status,
|
|
143
|
-
costHeaders, balanceBefore: balBefore, balanceAfter: balAfter,
|
|
144
|
-
realCost: Math.round((balBefore - balAfter) * 10000) / 10000,
|
|
145
|
-
error, extraInfo
|
|
146
|
-
};
|
|
147
|
-
}
|
|
148
|
-
// ─── MAIN ───────────────────────────────────────────────────────────
|
|
149
|
-
async function main() {
|
|
150
|
-
console.log('═══════════════════════════════════════════');
|
|
151
|
-
console.log(' FREETIER AUDIT — Enter Universe Tests');
|
|
152
|
-
console.log('═══════════════════════════════════════════\n');
|
|
153
|
-
// Profile
|
|
154
|
-
const profile = await getProfile();
|
|
155
|
-
console.log(`👤 User: ${profile.name} | Tier: ${profile.tier} | Reset: ${profile.nextResetAt}`);
|
|
156
|
-
const startBalance = await getBalance();
|
|
157
|
-
console.log(`💰 Starting Balance: ${startBalance} pollen\n`);
|
|
158
|
-
const results = [];
|
|
159
|
-
// === TEXT FREETIER MODELS ===
|
|
160
|
-
console.log('── TEXT MODELS (FreeTier) ──');
|
|
161
|
-
const textModels = ['openai', 'mistral', 'gemini', 'qwen'];
|
|
162
|
-
for (const m of textModels) {
|
|
163
|
-
try {
|
|
164
|
-
console.log(` ⏳ Testing ${m}...`);
|
|
165
|
-
const r = await testText(m);
|
|
166
|
-
results.push(r);
|
|
167
|
-
console.log(` ${r.status === 200 ? '✅' : '❌'} ${r.name} | ${r.latencyMs}ms | cost=${r.realCost} | headers=${JSON.stringify(r.costHeaders)} | ${r.extraInfo || r.error || ''}`);
|
|
168
|
-
}
|
|
169
|
-
catch (e) {
|
|
170
|
-
console.log(` 💥 ${m}: CRASH - ${e.message}`);
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
// === IMAGE FREETIER MODELS ===
|
|
174
|
-
console.log('\n── IMAGE MODELS (FreeTier) ──');
|
|
175
|
-
const imageModels = ['flux', 'turbo'];
|
|
176
|
-
for (const m of imageModels) {
|
|
177
|
-
try {
|
|
178
|
-
console.log(` ⏳ Testing ${m}...`);
|
|
179
|
-
const r = await testImage(m);
|
|
180
|
-
results.push(r);
|
|
181
|
-
console.log(` ${r.status === 200 ? '✅' : '❌'} ${r.name} | ${r.latencyMs}ms | cost=${r.realCost} | headers=${JSON.stringify(r.costHeaders)} | ${r.extraInfo || r.error || ''}`);
|
|
182
|
-
}
|
|
183
|
-
catch (e) {
|
|
184
|
-
console.log(` 💥 ${m}: CRASH - ${e.message}`);
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
// === AUDIO TTS FREETIER ===
|
|
188
|
-
console.log('\n── AUDIO TTS (FreeTier) ──');
|
|
189
|
-
try {
|
|
190
|
-
console.log(` ⏳ Testing openai-audio (TTS)...`);
|
|
191
|
-
const r = await testAudio('openai-audio', 'This is a short test');
|
|
192
|
-
results.push(r);
|
|
193
|
-
console.log(` ${r.status === 200 ? '✅' : '❌'} ${r.name} | ${r.latencyMs}ms | cost=${r.realCost} | headers=${JSON.stringify(r.costHeaders)} | ${r.extraInfo || r.error || ''}`);
|
|
194
|
-
}
|
|
195
|
-
catch (e) {
|
|
196
|
-
console.log(` 💥 audio: CRASH - ${e.message}`);
|
|
197
|
-
}
|
|
198
|
-
// === SUMMARY ===
|
|
199
|
-
const endBalance = await getBalance();
|
|
200
|
-
console.log('\n═══════════════════════════════════════════');
|
|
201
|
-
console.log(' RÉSUMÉ');
|
|
202
|
-
console.log('═══════════════════════════════════════════');
|
|
203
|
-
console.log(`💰 Balance: ${startBalance} → ${endBalance} (dépensé: ${Math.round((startBalance - endBalance) * 10000) / 10000})`);
|
|
204
|
-
console.log(`📊 Tests réussis: ${results.filter(r => r.status === 200).length}/${results.length}`);
|
|
205
|
-
console.log(`⏱️ Latence moyenne: ${Math.round(results.filter(r => r.status === 200).reduce((a, r) => a + r.latencyMs, 0) / Math.max(1, results.filter(r => r.status === 200).length))}ms`);
|
|
206
|
-
console.log('\n── TABLEAU DÉTAILLÉ ──');
|
|
207
|
-
console.log('| Type | Model | Status | Latency | RealCost | Headers Cost | Info |');
|
|
208
|
-
console.log('|------|-------|--------|---------|----------|-------------|------|');
|
|
209
|
-
for (const r of results) {
|
|
210
|
-
const hStr = Object.entries(r.costHeaders).map(([k, v]) => `${k.replace('x-usage-', '').replace('x-', '')}=${v}`).join(', ') || 'none';
|
|
211
|
-
console.log(`| ${r.type} | ${r.model} | ${r.status} | ${r.latencyMs}ms | ${r.realCost} | ${hStr} | ${(r.extraInfo || r.error || '').substring(0, 50)} |`);
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
main().catch(e => console.error('FATAL:', e));
|
|
215
|
-
export {};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,104 +0,0 @@
|
|
|
1
|
-
import { loadConfig } from '../config.js';
|
|
2
|
-
function getApiKey() {
|
|
3
|
-
const config = loadConfig();
|
|
4
|
-
return config.apiKey || null;
|
|
5
|
-
}
|
|
6
|
-
async function getBalance(apiKey) {
|
|
7
|
-
try {
|
|
8
|
-
const resUsage = await fetch('https://gen.pollinations.ai/account/usage/daily', {
|
|
9
|
-
headers: { 'Authorization': `Bearer ${apiKey}` }
|
|
10
|
-
});
|
|
11
|
-
if (!resUsage.ok)
|
|
12
|
-
return null;
|
|
13
|
-
const data = await resUsage.json();
|
|
14
|
-
// On somme l'usage Freetier de ce jour précis (dernière date de l'API)
|
|
15
|
-
let sumTierCost = 0;
|
|
16
|
-
let sumRequests = 0;
|
|
17
|
-
if (data.usage && data.usage.length > 0) {
|
|
18
|
-
// Prendre uniquement les requêtes de la dernière journée (today)
|
|
19
|
-
const latestDate = data.usage[0].date;
|
|
20
|
-
for (const entry of data.usage) {
|
|
21
|
-
if (entry.date === latestDate) {
|
|
22
|
-
sumRequests += entry.requests;
|
|
23
|
-
if (entry.meter_source !== 'pack') {
|
|
24
|
-
sumTierCost += entry.cost_usd;
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
return {
|
|
30
|
-
tierCostUsd: sumTierCost,
|
|
31
|
-
totalRequests: sumRequests,
|
|
32
|
-
rawBody: data
|
|
33
|
-
};
|
|
34
|
-
}
|
|
35
|
-
catch (e) {
|
|
36
|
-
console.error("Erreur de fetch balance", e);
|
|
37
|
-
return null;
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
async function runTest() {
|
|
41
|
-
const apiKey = getApiKey();
|
|
42
|
-
if (!apiKey) {
|
|
43
|
-
console.error("Clé API introuvable !");
|
|
44
|
-
return;
|
|
45
|
-
}
|
|
46
|
-
console.log("=== POLLINATIONS PARALLEL COST TEST (FREETIER) ===");
|
|
47
|
-
console.log("Fetching daily usage footprint...");
|
|
48
|
-
let initialBalance = await getBalance(apiKey);
|
|
49
|
-
console.log("INITIAL:", {
|
|
50
|
-
tierCostUsd: initialBalance?.tierCostUsd,
|
|
51
|
-
totalRequests: initialBalance?.totalRequests
|
|
52
|
-
});
|
|
53
|
-
const NB_REQUESTS = 5;
|
|
54
|
-
console.log(`\nLaunching ${NB_REQUESTS} parallel requests to 'mistral' (FreeTier)...`);
|
|
55
|
-
const startTime = Date.now();
|
|
56
|
-
const promises = [];
|
|
57
|
-
for (let i = 0; i < NB_REQUESTS; i++) {
|
|
58
|
-
promises.push(fetch('https://text.pollinations.ai/openai', {
|
|
59
|
-
method: 'POST',
|
|
60
|
-
headers: {
|
|
61
|
-
'Authorization': `Bearer ${apiKey}`,
|
|
62
|
-
'Content-Type': 'application/json'
|
|
63
|
-
},
|
|
64
|
-
body: JSON.stringify({
|
|
65
|
-
model: 'mistral',
|
|
66
|
-
messages: [{ role: 'user', content: `Test parallel FREETIER count request #${i}. Reponds 'OK'.` }],
|
|
67
|
-
max_tokens: 10
|
|
68
|
-
})
|
|
69
|
-
}).then(res => res.text()).catch(e => "Error"));
|
|
70
|
-
}
|
|
71
|
-
await Promise.all(promises);
|
|
72
|
-
const duration = Date.now() - startTime;
|
|
73
|
-
console.log(`✅ ${NB_REQUESTS} free requests completed in ${duration}ms!`);
|
|
74
|
-
console.log("\nFetching Daily Stats immediately after completions...");
|
|
75
|
-
let quickBalance = await getBalance(apiKey);
|
|
76
|
-
console.log("IMMEDIATE STATS:", {
|
|
77
|
-
tierCostUsd: quickBalance?.tierCostUsd,
|
|
78
|
-
totalRequests: quickBalance?.totalRequests
|
|
79
|
-
});
|
|
80
|
-
console.log("\nWaiting 10 seconds for eventual API indexation...");
|
|
81
|
-
await new Promise(r => setTimeout(r, 10000));
|
|
82
|
-
let delayedBalance = await getBalance(apiKey);
|
|
83
|
-
console.log("DELAYED STATS (10s):", {
|
|
84
|
-
tierCostUsd: delayedBalance?.tierCostUsd,
|
|
85
|
-
totalRequests: delayedBalance?.totalRequests
|
|
86
|
-
});
|
|
87
|
-
if (initialBalance && quickBalance && delayedBalance) {
|
|
88
|
-
const deltaReqQuick = quickBalance.totalRequests - initialBalance.totalRequests;
|
|
89
|
-
const deltaReqDelayed = delayedBalance.totalRequests - initialBalance.totalRequests;
|
|
90
|
-
console.log("\n=== CONCLUSIONS ===");
|
|
91
|
-
console.log(`Requests indexed Immediately: +${deltaReqQuick}`);
|
|
92
|
-
console.log(`Requests indexed 10s later: +${deltaReqDelayed}`);
|
|
93
|
-
if (deltaReqQuick !== NB_REQUESTS && deltaReqDelayed === NB_REQUESTS) {
|
|
94
|
-
console.warn("⚠️ USAGE API IS EVENTUALLY CONSISTENT! Delay is required to see real quota drop.");
|
|
95
|
-
}
|
|
96
|
-
else if (deltaReqDelayed !== NB_REQUESTS) {
|
|
97
|
-
console.error(`❌ API did not index all ${NB_REQUESTS} requests. Only saw ${deltaReqDelayed}. Parallel drop bug!`);
|
|
98
|
-
}
|
|
99
|
-
else {
|
|
100
|
-
console.log("✅ Usage API is strongly consistent over parallel streams.");
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
runTest();
|