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.
Files changed (109) hide show
  1. package/README.de.md +130 -0
  2. package/README.es.md +130 -0
  3. package/README.fr.md +130 -0
  4. package/README.it.md +130 -0
  5. package/README.md +87 -73
  6. package/dist/index.js +52 -161
  7. package/dist/locales/de.json +374 -0
  8. package/dist/locales/en.json +373 -0
  9. package/dist/locales/es.json +374 -0
  10. package/dist/locales/fr.json +373 -0
  11. package/dist/locales/index.d.ts +1 -0
  12. package/dist/locales/index.js +37 -0
  13. package/dist/locales/it.json +374 -0
  14. package/dist/server/commands.d.ts +6 -0
  15. package/dist/server/commands.js +394 -125
  16. package/dist/server/config.d.ts +34 -23
  17. package/dist/server/config.js +200 -108
  18. package/dist/server/connect-response.d.ts +2 -0
  19. package/dist/server/connect-response.js +59 -0
  20. package/dist/server/generate-config.d.ts +3 -30
  21. package/dist/server/generate-config.js +164 -106
  22. package/dist/server/index.d.ts +2 -1
  23. package/dist/server/index.js +124 -149
  24. package/dist/server/logger.d.ts +8 -0
  25. package/dist/server/logger.js +38 -0
  26. package/dist/server/models/cache.d.ts +35 -0
  27. package/dist/server/models/cache.js +160 -0
  28. package/dist/server/models/fetcher.d.ts +18 -0
  29. package/dist/server/models/fetcher.js +194 -0
  30. package/dist/server/models/index.d.ts +6 -0
  31. package/dist/server/models/index.js +5 -0
  32. package/dist/server/models/manual.d.ts +15 -0
  33. package/dist/server/models/manual.js +92 -0
  34. package/dist/server/models/types.d.ts +55 -0
  35. package/dist/server/models/types.js +7 -0
  36. package/dist/server/models/worker.d.ts +22 -0
  37. package/dist/server/models/worker.js +174 -0
  38. package/dist/server/pollinations-api.d.ts +11 -0
  39. package/dist/server/pollinations-api.js +21 -8
  40. package/dist/server/proxy.js +222 -307
  41. package/dist/server/quota.d.ts +2 -0
  42. package/dist/server/quota.js +89 -86
  43. package/dist/server/scripts/pollinations_pricing.d.ts +8 -0
  44. package/dist/server/scripts/pollinations_pricing.js +246 -0
  45. package/dist/server/scripts/test_cost_endpoints.d.ts +1 -0
  46. package/dist/server/scripts/test_cost_endpoints.js +61 -0
  47. package/dist/server/scripts/test_dynamic_pricing.d.ts +1 -0
  48. package/dist/server/scripts/test_dynamic_pricing.js +39 -0
  49. package/dist/server/scripts/test_freetier_audit.d.ts +11 -0
  50. package/dist/server/scripts/test_freetier_audit.js +215 -0
  51. package/dist/server/scripts/test_parallel_cost.d.ts +1 -0
  52. package/dist/server/scripts/test_parallel_cost.js +104 -0
  53. package/dist/server/toast.d.ts +7 -1
  54. package/dist/server/toast.js +43 -10
  55. package/dist/tools/design/gen_diagram.d.ts +2 -0
  56. package/dist/tools/design/gen_diagram.js +94 -0
  57. package/dist/tools/design/gen_palette.d.ts +2 -0
  58. package/dist/tools/design/gen_palette.js +182 -0
  59. package/dist/tools/design/gen_qrcode.d.ts +2 -0
  60. package/dist/tools/design/gen_qrcode.js +50 -0
  61. package/dist/tools/ffmpeg.d.ts +24 -0
  62. package/dist/tools/ffmpeg.js +54 -0
  63. package/dist/tools/index.d.ts +25 -0
  64. package/dist/tools/index.js +86 -0
  65. package/dist/tools/pollinations/beta_discovery.d.ts +9 -0
  66. package/dist/tools/pollinations/beta_discovery.js +201 -0
  67. package/dist/tools/pollinations/cost-guard.d.ts +38 -0
  68. package/dist/tools/pollinations/cost-guard.js +136 -0
  69. package/dist/tools/pollinations/deepsearch.d.ts +7 -0
  70. package/dist/tools/pollinations/deepsearch.js +80 -0
  71. package/dist/tools/pollinations/gen_audio.d.ts +18 -0
  72. package/dist/tools/pollinations/gen_audio.js +220 -0
  73. package/dist/tools/pollinations/gen_image.d.ts +11 -0
  74. package/dist/tools/pollinations/gen_image.js +211 -0
  75. package/dist/tools/pollinations/gen_music.d.ts +14 -0
  76. package/dist/tools/pollinations/gen_music.js +157 -0
  77. package/dist/tools/pollinations/gen_video.d.ts +16 -0
  78. package/dist/tools/pollinations/gen_video.js +249 -0
  79. package/dist/tools/pollinations/polli_config.d.ts +2 -0
  80. package/dist/tools/pollinations/polli_config.js +95 -0
  81. package/dist/tools/pollinations/polli_gen_confirm.d.ts +2 -0
  82. package/dist/tools/pollinations/polli_gen_confirm.js +48 -0
  83. package/dist/tools/pollinations/polli_status.d.ts +2 -0
  84. package/dist/tools/pollinations/polli_status.js +31 -0
  85. package/dist/tools/pollinations/polli_web_search.d.ts +15 -0
  86. package/dist/tools/pollinations/polli_web_search.js +126 -0
  87. package/dist/tools/pollinations/search_crawl_scrape.d.ts +7 -0
  88. package/dist/tools/pollinations/search_crawl_scrape.js +85 -0
  89. package/dist/tools/pollinations/shared.d.ts +181 -0
  90. package/dist/tools/pollinations/shared.js +758 -0
  91. package/dist/tools/pollinations/test_estimators.d.ts +1 -0
  92. package/dist/tools/pollinations/test_estimators.js +22 -0
  93. package/dist/tools/pollinations/transcribe_audio.d.ts +13 -0
  94. package/dist/tools/pollinations/transcribe_audio.js +171 -0
  95. package/dist/tools/power/extract_audio.d.ts +2 -0
  96. package/dist/tools/power/extract_audio.js +179 -0
  97. package/dist/tools/power/extract_frames.d.ts +2 -0
  98. package/dist/tools/power/extract_frames.js +237 -0
  99. package/dist/tools/power/file_to_url.d.ts +2 -0
  100. package/dist/tools/power/file_to_url.js +217 -0
  101. package/dist/tools/power/remove_background.d.ts +2 -0
  102. package/dist/tools/power/remove_background.js +404 -0
  103. package/dist/tools/power/rmbg_keys.d.ts +2 -0
  104. package/dist/tools/power/rmbg_keys.js +79 -0
  105. package/dist/tools/shared.d.ts +30 -0
  106. package/dist/tools/shared.js +80 -0
  107. package/package.json +9 -3
  108. package/dist/server/models-seed.d.ts +0 -18
  109. 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();
@@ -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): void;
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
+ };
@@ -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
- dispatchToast('status', type, message, title || 'Pollinations Status');
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
- try {
66
- const logLine = `[${new Date(toast.timestamp).toISOString()}] [${toast.channel.toUpperCase()}] [${toast.type.toUpperCase()}] ${toast.message}`;
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,2 @@
1
+ import { type ToolDefinition } from '@opencode-ai/plugin/tool';
2
+ export declare const genDiagramTool: ToolDefinition;
@@ -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
+ });
@@ -0,0 +1,2 @@
1
+ import { type ToolDefinition } from '@opencode-ai/plugin/tool';
2
+ export declare const genPaletteTool: ToolDefinition;