opencode-pollinations-plugin 6.1.0-beta.9 ā 6.2.1
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 +130 -0
- package/README.es.md +130 -0
- package/README.fr.md +130 -0
- package/README.it.md +130 -0
- package/README.md +87 -73
- package/dist/index.js +52 -161
- package/dist/locales/de.json +374 -0
- package/dist/locales/en.json +373 -0
- package/dist/locales/es.json +374 -0
- package/dist/locales/fr.json +373 -0
- package/dist/locales/index.d.ts +1 -0
- package/dist/locales/index.js +37 -0
- package/dist/locales/it.json +374 -0
- package/dist/server/commands.d.ts +6 -0
- package/dist/server/commands.js +394 -125
- package/dist/server/config.d.ts +34 -23
- package/dist/server/config.js +200 -108
- package/dist/server/connect-response.d.ts +2 -0
- package/dist/server/connect-response.js +59 -0
- package/dist/server/generate-config.d.ts +3 -30
- package/dist/server/generate-config.js +164 -106
- package/dist/server/index.d.ts +2 -1
- package/dist/server/index.js +124 -149
- package/dist/server/logger.d.ts +8 -0
- package/dist/server/logger.js +38 -0
- package/dist/server/models/cache.d.ts +35 -0
- package/dist/server/models/cache.js +160 -0
- package/dist/server/models/fetcher.d.ts +18 -0
- package/dist/server/models/fetcher.js +194 -0
- package/dist/server/models/index.d.ts +6 -0
- package/dist/server/models/index.js +5 -0
- package/dist/server/models/manual.d.ts +15 -0
- package/dist/server/models/manual.js +92 -0
- package/dist/server/models/types.d.ts +55 -0
- package/dist/server/models/types.js +7 -0
- package/dist/server/models/worker.d.ts +22 -0
- package/dist/server/models/worker.js +174 -0
- package/dist/server/pollinations-api.d.ts +11 -0
- package/dist/server/pollinations-api.js +21 -8
- package/dist/server/proxy.js +222 -307
- package/dist/server/quota.d.ts +2 -0
- package/dist/server/quota.js +89 -86
- package/dist/server/scripts/pollinations_pricing.d.ts +8 -0
- package/dist/server/scripts/pollinations_pricing.js +246 -0
- package/dist/server/scripts/test_cost_endpoints.d.ts +1 -0
- package/dist/server/scripts/test_cost_endpoints.js +61 -0
- package/dist/server/scripts/test_dynamic_pricing.d.ts +1 -0
- package/dist/server/scripts/test_dynamic_pricing.js +39 -0
- package/dist/server/scripts/test_freetier_audit.d.ts +11 -0
- package/dist/server/scripts/test_freetier_audit.js +215 -0
- package/dist/server/scripts/test_parallel_cost.d.ts +1 -0
- package/dist/server/scripts/test_parallel_cost.js +104 -0
- package/dist/server/toast.d.ts +7 -1
- package/dist/server/toast.js +43 -10
- package/dist/tools/design/gen_diagram.d.ts +2 -0
- package/dist/tools/design/gen_diagram.js +94 -0
- package/dist/tools/design/gen_palette.d.ts +2 -0
- package/dist/tools/design/gen_palette.js +182 -0
- package/dist/tools/design/gen_qrcode.d.ts +2 -0
- package/dist/tools/design/gen_qrcode.js +50 -0
- package/dist/tools/ffmpeg.d.ts +24 -0
- package/dist/tools/ffmpeg.js +54 -0
- package/dist/tools/index.d.ts +25 -0
- package/dist/tools/index.js +86 -0
- package/dist/tools/pollinations/beta_discovery.d.ts +9 -0
- package/dist/tools/pollinations/beta_discovery.js +201 -0
- package/dist/tools/pollinations/cost-guard.d.ts +38 -0
- package/dist/tools/pollinations/cost-guard.js +136 -0
- package/dist/tools/pollinations/deepsearch.d.ts +7 -0
- package/dist/tools/pollinations/deepsearch.js +80 -0
- package/dist/tools/pollinations/gen_audio.d.ts +18 -0
- package/dist/tools/pollinations/gen_audio.js +220 -0
- package/dist/tools/pollinations/gen_image.d.ts +11 -0
- package/dist/tools/pollinations/gen_image.js +211 -0
- package/dist/tools/pollinations/gen_music.d.ts +14 -0
- package/dist/tools/pollinations/gen_music.js +157 -0
- package/dist/tools/pollinations/gen_video.d.ts +16 -0
- package/dist/tools/pollinations/gen_video.js +249 -0
- package/dist/tools/pollinations/polli_config.d.ts +2 -0
- package/dist/tools/pollinations/polli_config.js +95 -0
- package/dist/tools/pollinations/polli_gen_confirm.d.ts +2 -0
- package/dist/tools/pollinations/polli_gen_confirm.js +48 -0
- package/dist/tools/pollinations/polli_status.d.ts +2 -0
- package/dist/tools/pollinations/polli_status.js +31 -0
- package/dist/tools/pollinations/polli_web_search.d.ts +15 -0
- package/dist/tools/pollinations/polli_web_search.js +126 -0
- package/dist/tools/pollinations/search_crawl_scrape.d.ts +7 -0
- package/dist/tools/pollinations/search_crawl_scrape.js +85 -0
- package/dist/tools/pollinations/shared.d.ts +181 -0
- package/dist/tools/pollinations/shared.js +758 -0
- package/dist/tools/pollinations/test_estimators.d.ts +1 -0
- package/dist/tools/pollinations/test_estimators.js +22 -0
- package/dist/tools/pollinations/transcribe_audio.d.ts +13 -0
- package/dist/tools/pollinations/transcribe_audio.js +171 -0
- package/dist/tools/power/extract_audio.d.ts +2 -0
- package/dist/tools/power/extract_audio.js +179 -0
- package/dist/tools/power/extract_frames.d.ts +2 -0
- package/dist/tools/power/extract_frames.js +237 -0
- package/dist/tools/power/file_to_url.d.ts +2 -0
- package/dist/tools/power/file_to_url.js +217 -0
- package/dist/tools/power/remove_background.d.ts +2 -0
- package/dist/tools/power/remove_background.js +404 -0
- package/dist/tools/power/rmbg_keys.d.ts +2 -0
- package/dist/tools/power/rmbg_keys.js +79 -0
- package/dist/tools/shared.d.ts +30 -0
- package/dist/tools/shared.js +80 -0
- package/package.json +9 -3
- package/dist/server/models-seed.d.ts +0 -18
- package/dist/server/models-seed.js +0 -55
|
@@ -0,0 +1,215 @@
|
|
|
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 {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,104 @@
|
|
|
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();
|
package/dist/server/toast.d.ts
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
export declare function setGlobalClient(client: any): void;
|
|
2
2
|
export declare function emitLogToast(type: 'info' | 'warning' | 'error' | 'success', message: string, title?: string): void;
|
|
3
|
-
export declare function emitStatusToast(type: 'info' | 'warning' | 'error' | 'success', message: string, title?: string
|
|
3
|
+
export declare function emitStatusToast(type: 'info' | 'warning' | 'error' | 'success', message: string, title?: string, metadata?: {
|
|
4
|
+
filePath?: string;
|
|
5
|
+
params?: Record<string, any>;
|
|
6
|
+
}): void;
|
|
4
7
|
export declare function createToastHooks(client: any): {
|
|
5
8
|
'session.idle': ({ event }: any) => Promise<void>;
|
|
6
9
|
};
|
|
10
|
+
export declare function createToolHooks(client: any): {
|
|
11
|
+
'tool.execute.after': (input: any, output: any) => Promise<void>;
|
|
12
|
+
};
|
package/dist/server/toast.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import * as fs from 'fs';
|
|
2
1
|
import { loadConfig } from './config.js';
|
|
3
2
|
const toastQueue = [];
|
|
4
3
|
let globalClient = null;
|
|
@@ -20,16 +19,36 @@ export function emitLogToast(type, message, title) {
|
|
|
20
19
|
dispatchToast('log', type, message, title || 'Pollinations Log');
|
|
21
20
|
}
|
|
22
21
|
// 2. CANAL STATUS (Dashboard)
|
|
23
|
-
export function emitStatusToast(type, message, title) {
|
|
22
|
+
export function emitStatusToast(type, message, title, metadata) {
|
|
24
23
|
const config = loadConfig();
|
|
25
24
|
const verbosity = config.gui.status;
|
|
26
25
|
if (verbosity === 'none')
|
|
27
26
|
return;
|
|
28
|
-
// 'alert' logic handled by caller (proxy.ts) usually, but we can filter here too?
|
|
29
|
-
// Actually, 'all' sends everything. 'alert' sends only warnings/errors.
|
|
30
27
|
if (verbosity === 'alert' && type !== 'error' && type !== 'warning')
|
|
31
28
|
return;
|
|
32
|
-
|
|
29
|
+
let finalMessage = message;
|
|
30
|
+
if (metadata?.filePath) {
|
|
31
|
+
finalMessage += `\nš ${metadata.filePath}`;
|
|
32
|
+
}
|
|
33
|
+
if (type === 'success' || type === 'error') {
|
|
34
|
+
// En arrière-plan, essaye de récupérer le quota localement sans bloquer l'appel
|
|
35
|
+
import('./quota.js').then(({ getQuotaStatus, formatQuotaForToast }) => {
|
|
36
|
+
getQuotaStatus(false).then(quota => {
|
|
37
|
+
const quotaMsg = formatQuotaForToast
|
|
38
|
+
? formatQuotaForToast(quota)
|
|
39
|
+
: `š» Freetier: ${quota.tierRemaining.toFixed(2)}/${quota.tierLimit} | Wallet: $${quota.walletBalance.toFixed(2)}`;
|
|
40
|
+
finalMessage += `\n${quotaMsg}`;
|
|
41
|
+
dispatchToast('status', type, finalMessage, title || 'Pollinations Status');
|
|
42
|
+
}).catch(() => {
|
|
43
|
+
dispatchToast('status', type, finalMessage, title || 'Pollinations Status');
|
|
44
|
+
});
|
|
45
|
+
}).catch(() => {
|
|
46
|
+
dispatchToast('status', type, finalMessage, title || 'Pollinations Status');
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
dispatchToast('status', type, finalMessage, title || 'Pollinations Status');
|
|
51
|
+
}
|
|
33
52
|
}
|
|
34
53
|
// INTERNAL DISPATCHER
|
|
35
54
|
function dispatchToast(channel, type, message, title) {
|
|
@@ -61,12 +80,10 @@ function dispatchToast(channel, type, message, title) {
|
|
|
61
80
|
}
|
|
62
81
|
}
|
|
63
82
|
// === HELPERS ===
|
|
83
|
+
import { logToast } from './logger.js';
|
|
64
84
|
function logToastToFile(toast) {
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
fs.appendFileSync('/tmp/pollinations-toasts.log', logLine + '\n');
|
|
68
|
-
}
|
|
69
|
-
catch (e) { }
|
|
85
|
+
const logLine = `[${new Date(toast.timestamp).toISOString()}] [${toast.channel.toUpperCase()}] [${toast.type.toUpperCase()}] ${toast.message}`;
|
|
86
|
+
logToast(logLine);
|
|
70
87
|
}
|
|
71
88
|
export function createToastHooks(client) {
|
|
72
89
|
return {
|
|
@@ -76,3 +93,19 @@ export function createToastHooks(client) {
|
|
|
76
93
|
}
|
|
77
94
|
};
|
|
78
95
|
}
|
|
96
|
+
// 3. CANAL TOOLS (Natif)
|
|
97
|
+
export function createToolHooks(client) {
|
|
98
|
+
return {
|
|
99
|
+
'tool.execute.after': async (input, output) => {
|
|
100
|
+
// Check for metadata in the output
|
|
101
|
+
if (output.metadata && output.metadata.message) {
|
|
102
|
+
const meta = output.metadata;
|
|
103
|
+
const type = meta.type || 'info';
|
|
104
|
+
// If title is not in metadata, try to use the one from output or default
|
|
105
|
+
const title = meta.title || output.title || 'Pollinations Tool';
|
|
106
|
+
// Emit the toast
|
|
107
|
+
emitStatusToast(type, meta.message, title);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { tool } from '@opencode-ai/plugin/tool';
|
|
2
|
+
import * as https from 'https';
|
|
3
|
+
import * as fs from 'fs';
|
|
4
|
+
import * as path from 'path';
|
|
5
|
+
import { resolveOutputDir, TOOL_DIRS } from '../shared.js';
|
|
6
|
+
const MERMAID_INK_BASE = 'https://mermaid.ink';
|
|
7
|
+
/**
|
|
8
|
+
* Encode Mermaid code for mermaid.ink API
|
|
9
|
+
* Uses base64 encoding of the diagram definition
|
|
10
|
+
*/
|
|
11
|
+
function encodeMermaid(code) {
|
|
12
|
+
return Buffer.from(code, 'utf-8').toString('base64url');
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Fetch binary content from URL
|
|
16
|
+
*/
|
|
17
|
+
function fetchBinary(url) {
|
|
18
|
+
return new Promise((resolve, reject) => {
|
|
19
|
+
const req = https.get(url, { headers: { 'User-Agent': 'OpenCode-Pollinations-Plugin/6.0' } }, (res) => {
|
|
20
|
+
// Follow redirects
|
|
21
|
+
if (res.statusCode && res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
|
|
22
|
+
return fetchBinary(res.headers.location).then(resolve).catch(reject);
|
|
23
|
+
}
|
|
24
|
+
if (res.statusCode && res.statusCode >= 400) {
|
|
25
|
+
return reject(new Error(`HTTP ${res.statusCode}: ${res.statusMessage}`));
|
|
26
|
+
}
|
|
27
|
+
const chunks = [];
|
|
28
|
+
res.on('data', (chunk) => chunks.push(chunk));
|
|
29
|
+
res.on('end', () => resolve(Buffer.concat(chunks)));
|
|
30
|
+
});
|
|
31
|
+
req.on('error', reject);
|
|
32
|
+
req.setTimeout(15000, () => {
|
|
33
|
+
req.destroy();
|
|
34
|
+
reject(new Error('Timeout fetching diagram'));
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
export const genDiagramTool = tool({
|
|
39
|
+
description: `Render a Mermaid diagram to SVG or PNG image.
|
|
40
|
+
Uses mermaid.ink (free, no auth required). Supports all Mermaid syntax:
|
|
41
|
+
flowchart, sequenceDiagram, classDiagram, stateDiagram, erDiagram, gantt, pie, mindmap, timeline, etc.
|
|
42
|
+
The diagram code should be valid Mermaid syntax WITHOUT the \`\`\`mermaid fences.`,
|
|
43
|
+
args: {
|
|
44
|
+
code: tool.schema.string().describe('Mermaid diagram code (e.g. "graph LR; A-->B; B-->C")'),
|
|
45
|
+
format: tool.schema.enum(['svg', 'png']).optional().describe('Output format (default: svg)'),
|
|
46
|
+
theme: tool.schema.enum(['default', 'dark', 'forest', 'neutral']).optional().describe('Diagram theme (default: default)'),
|
|
47
|
+
filename: tool.schema.string().optional().describe('Custom filename (without extension). Auto-generated if omitted'),
|
|
48
|
+
output_path: tool.schema.string().optional().describe('Custom output directory. Default: ~/Downloads/pollinations/diagrams/'),
|
|
49
|
+
},
|
|
50
|
+
async execute(args, context) {
|
|
51
|
+
const format = args.format || 'svg';
|
|
52
|
+
const theme = args.theme || 'default';
|
|
53
|
+
const outputDir = resolveOutputDir(TOOL_DIRS.diagrams, args.output_path);
|
|
54
|
+
// Build mermaid.ink URL
|
|
55
|
+
// For themed rendering, we wrap with config
|
|
56
|
+
const themedCode = theme !== 'default'
|
|
57
|
+
? `%%{init: {'theme': '${theme}'}}%%\n${args.code}`
|
|
58
|
+
: args.code;
|
|
59
|
+
const encoded = encodeMermaid(themedCode);
|
|
60
|
+
const endpoint = format === 'svg' ? 'svg' : 'img';
|
|
61
|
+
const url = `${MERMAID_INK_BASE}/${endpoint}/${encoded}`;
|
|
62
|
+
// Generate filename
|
|
63
|
+
const safeName = args.filename
|
|
64
|
+
? args.filename.replace(/[^a-zA-Z0-9_-]/g, '_')
|
|
65
|
+
: `diagram_${Date.now()}`;
|
|
66
|
+
const filePath = path.join(outputDir, `${safeName}.${format}`);
|
|
67
|
+
try {
|
|
68
|
+
const data = await fetchBinary(url);
|
|
69
|
+
if (data.length < 50) {
|
|
70
|
+
return `ā Diagram Error: mermaid.ink returned empty/invalid response. Check your Mermaid syntax.`;
|
|
71
|
+
}
|
|
72
|
+
fs.writeFileSync(filePath, data);
|
|
73
|
+
const fileSizeKB = (data.length / 1024).toFixed(1);
|
|
74
|
+
// Extract diagram type from first line
|
|
75
|
+
const firstLine = args.code.trim().split('\n')[0].trim();
|
|
76
|
+
const diagramType = firstLine.replace(/[;\s{].*/g, '');
|
|
77
|
+
context.metadata({ title: `š Diagram: ${diagramType}` });
|
|
78
|
+
return [
|
|
79
|
+
`š Diagram Rendered`,
|
|
80
|
+
`āāāāāāāāāāāāāāāāāāā`,
|
|
81
|
+
`Type: ${diagramType}`,
|
|
82
|
+
`Theme: ${theme}`,
|
|
83
|
+
`Format: ${format.toUpperCase()}`,
|
|
84
|
+
`File: ${filePath}`,
|
|
85
|
+
`Weight: ${fileSizeKB} KB`,
|
|
86
|
+
`URL: ${url}`,
|
|
87
|
+
`Cost: Free (mermaid.ink)`,
|
|
88
|
+
].join('\n');
|
|
89
|
+
}
|
|
90
|
+
catch (err) {
|
|
91
|
+
return `ā Diagram Error: ${err.message}\nš” Verify your Mermaid syntax at https://mermaid.live`;
|
|
92
|
+
}
|
|
93
|
+
},
|
|
94
|
+
});
|