opencode-pollinations-plugin 6.1.0-beta.12 → 6.1.0-beta.22

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 (73) hide show
  1. package/README.md +11 -6
  2. package/dist/index.js +40 -10
  3. package/dist/server/commands.d.ts +4 -0
  4. package/dist/server/commands.js +296 -12
  5. package/dist/server/config.d.ts +5 -0
  6. package/dist/server/config.js +163 -35
  7. package/dist/server/connect-response.d.ts +2 -0
  8. package/dist/server/connect-response.js +141 -0
  9. package/dist/server/generate-config.js +10 -24
  10. package/dist/server/logger.d.ts +8 -0
  11. package/dist/server/logger.js +36 -0
  12. package/dist/server/models/cache.d.ts +35 -0
  13. package/dist/server/models/cache.js +160 -0
  14. package/dist/server/models/fetcher.d.ts +18 -0
  15. package/dist/server/models/fetcher.js +150 -0
  16. package/dist/server/models/index.d.ts +6 -0
  17. package/dist/server/models/index.js +5 -0
  18. package/dist/server/models/manual.d.ts +15 -0
  19. package/dist/server/models/manual.js +92 -0
  20. package/dist/server/models/types.d.ts +55 -0
  21. package/dist/server/models/types.js +7 -0
  22. package/dist/server/models/worker.d.ts +21 -0
  23. package/dist/server/models/worker.js +97 -0
  24. package/dist/server/pollinations-api.js +1 -8
  25. package/dist/server/proxy.js +52 -27
  26. package/dist/server/quota.d.ts +2 -8
  27. package/dist/server/quota.js +47 -89
  28. package/dist/server/scripts/pollinations_pricing.d.ts +8 -0
  29. package/dist/server/scripts/pollinations_pricing.js +246 -0
  30. package/dist/server/scripts/test_cost_endpoints.d.ts +1 -0
  31. package/dist/server/scripts/test_cost_endpoints.js +61 -0
  32. package/dist/server/scripts/test_dynamic_pricing.d.ts +1 -0
  33. package/dist/server/scripts/test_dynamic_pricing.js +39 -0
  34. package/dist/server/scripts/test_freetier_audit.d.ts +11 -0
  35. package/dist/server/scripts/test_freetier_audit.js +215 -0
  36. package/dist/server/scripts/test_parallel_cost.d.ts +1 -0
  37. package/dist/server/scripts/test_parallel_cost.js +104 -0
  38. package/dist/server/toast.d.ts +4 -1
  39. package/dist/server/toast.js +27 -10
  40. package/dist/tools/ffmpeg.d.ts +24 -0
  41. package/dist/tools/ffmpeg.js +54 -0
  42. package/dist/tools/index.d.ts +10 -8
  43. package/dist/tools/index.js +27 -25
  44. package/dist/tools/pollinations/beta_discovery.d.ts +9 -0
  45. package/dist/tools/pollinations/beta_discovery.js +197 -0
  46. package/dist/tools/pollinations/cost-guard.d.ts +38 -0
  47. package/dist/tools/pollinations/cost-guard.js +141 -0
  48. package/dist/tools/pollinations/gen_audio.d.ts +1 -1
  49. package/dist/tools/pollinations/gen_audio.js +65 -23
  50. package/dist/tools/pollinations/gen_image.d.ts +5 -7
  51. package/dist/tools/pollinations/gen_image.js +146 -160
  52. package/dist/tools/pollinations/gen_music.d.ts +1 -1
  53. package/dist/tools/pollinations/gen_music.js +57 -16
  54. package/dist/tools/pollinations/gen_video.d.ts +1 -1
  55. package/dist/tools/pollinations/gen_video.js +99 -65
  56. package/dist/tools/pollinations/polli_gen_confirm.d.ts +2 -0
  57. package/dist/tools/pollinations/polli_gen_confirm.js +48 -0
  58. package/dist/tools/pollinations/polli_status.d.ts +2 -0
  59. package/dist/tools/pollinations/polli_status.js +31 -0
  60. package/dist/tools/pollinations/polli_web_search.d.ts +15 -0
  61. package/dist/tools/pollinations/polli_web_search.js +164 -0
  62. package/dist/tools/pollinations/shared.d.ts +34 -39
  63. package/dist/tools/pollinations/shared.js +300 -89
  64. package/dist/tools/pollinations/test_estimators.d.ts +1 -0
  65. package/dist/tools/pollinations/test_estimators.js +22 -0
  66. package/dist/tools/pollinations/transcribe_audio.d.ts +5 -9
  67. package/dist/tools/pollinations/transcribe_audio.js +31 -72
  68. package/dist/tools/power/extract_audio.js +26 -27
  69. package/dist/tools/power/extract_frames.js +24 -27
  70. package/dist/tools/power/remove_background.js +2 -1
  71. package/dist/tools/power/rmbg_keys.js +2 -1
  72. package/dist/tools/shared.js +9 -3
  73. package/package.json +2 -2
@@ -0,0 +1,11 @@
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 {};
@@ -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,9 @@
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
  };
@@ -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 {
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Check if ffmpeg is available in the system PATH
3
+ */
4
+ export declare function hasSystemFFmpeg(): boolean;
5
+ /**
6
+ * Check if ffprobe is available in the system PATH
7
+ */
8
+ export declare function hasSystemFFprobe(): boolean;
9
+ /**
10
+ * Get cross-platform installation instructions
11
+ */
12
+ export declare function getFFmpegInstallInstructions(): string;
13
+ /**
14
+ * Helper to run ffmpeg commands safely
15
+ */
16
+ export declare function runFFmpeg(args: string[], options?: {
17
+ timeout?: number;
18
+ }): void;
19
+ /**
20
+ * Helper to run ffprobe commands safely and return stdout
21
+ */
22
+ export declare function runFFprobe(args: string[], options?: {
23
+ timeout?: number;
24
+ }): string;
@@ -0,0 +1,54 @@
1
+ import { spawnSync } from 'child_process';
2
+ /**
3
+ * Check if ffmpeg is available in the system PATH
4
+ */
5
+ export function hasSystemFFmpeg() {
6
+ const result = spawnSync('ffmpeg', ['-version'], { stdio: 'ignore' });
7
+ return result.status === 0 && !result.error;
8
+ }
9
+ /**
10
+ * Check if ffprobe is available in the system PATH
11
+ */
12
+ export function hasSystemFFprobe() {
13
+ const result = spawnSync('ffprobe', ['-version'], { stdio: 'ignore' });
14
+ return result.status === 0 && !result.error;
15
+ }
16
+ /**
17
+ * Get cross-platform installation instructions
18
+ */
19
+ export function getFFmpegInstallInstructions() {
20
+ const platform = process.platform;
21
+ const instructions = {
22
+ linux: 'sudo apt install ffmpeg (Debian/Ubuntu)\nsudo dnf install ffmpeg (Fedora)',
23
+ darwin: 'brew install ffmpeg',
24
+ win32: 'choco install ffmpeg (Chocolatey)\nwinget install ffmpeg (WinGet)\nOu télécharger sur https://ffmpeg.org/download.html',
25
+ };
26
+ return instructions[platform] || 'Voir https://ffmpeg.org/download.html';
27
+ }
28
+ /**
29
+ * Helper to run ffmpeg commands safely
30
+ */
31
+ export function runFFmpeg(args, options = {}) {
32
+ const result = spawnSync('ffmpeg', args, {
33
+ stdio: 'ignore',
34
+ timeout: options.timeout || 120000,
35
+ });
36
+ if (result.error)
37
+ throw result.error;
38
+ if (result.status !== 0)
39
+ throw new Error(`FFmpeg failed with code ${result.status}`);
40
+ }
41
+ /**
42
+ * Helper to run ffprobe commands safely and return stdout
43
+ */
44
+ export function runFFprobe(args, options = {}) {
45
+ const result = spawnSync('ffprobe', args, {
46
+ encoding: 'utf-8',
47
+ timeout: options.timeout || 15000,
48
+ });
49
+ if (result.error)
50
+ throw result.error;
51
+ if (result.status !== 0)
52
+ throw new Error(`FFprobe failed: ${result.stderr}`);
53
+ return result.stdout;
54
+ }
@@ -6,17 +6,19 @@
6
6
  *
7
7
  * Tools are injected ONCE at plugin init. Restart needed after /poll connect.
8
8
  */
9
- import { genImageTool } from './pollinations/gen_image.js';
10
- import { genVideoTool } from './pollinations/gen_video.js';
11
- import { genAudioTool } from './pollinations/gen_audio.js';
12
- import { transcribeAudioTool } from './pollinations/transcribe_audio.js';
13
- import { genMusicTool } from './pollinations/gen_music.js';
14
- import { deepsearchTool } from './pollinations/deepsearch.js';
15
- import { searchCrawlScrapeTool } from './pollinations/search_crawl_scrape.js';
9
+ import { polliGenImageTool } from './pollinations/gen_image.js';
10
+ import { polliGenVideoTool } from './pollinations/gen_video.js';
11
+ import { polliGenAudioTool } from './pollinations/gen_audio.js';
12
+ import { polliSttTool } from './pollinations/transcribe_audio.js';
13
+ import { polliGenMusicTool } from './pollinations/gen_music.js';
14
+ import { polliWebSearchTool } from './pollinations/polli_web_search.js';
15
+ import { polliBetaDiscoveryTool } from './pollinations/beta_discovery.js';
16
+ import { polliGenConfirmTool } from './pollinations/polli_gen_confirm.js';
17
+ import { polliStatusTool } from './pollinations/polli_status.js';
16
18
  /**
17
19
  * Build the tool registry based on user's access level
18
20
  *
19
21
  * @returns Record<string, Tool> to be spread into the plugin's tool: {} property
20
22
  */
21
23
  export declare function createToolRegistry(): Record<string, any>;
22
- export { genImageTool, genVideoTool, genAudioTool, transcribeAudioTool, genMusicTool, deepsearchTool, searchCrawlScrapeTool, };
24
+ export { polliGenImageTool, polliGenVideoTool, polliGenAudioTool, polliSttTool, polliGenMusicTool, polliWebSearchTool, polliBetaDiscoveryTool, polliGenConfirmTool, polliStatusTool };
@@ -17,21 +17,16 @@ import { extractFramesTool } from './power/extract_frames.js';
17
17
  import { extractAudioTool } from './power/extract_audio.js';
18
18
  import { rmbgKeysTool } from './power/rmbg_keys.js';
19
19
  // === ENTER TOOLS (Require API key) ===
20
- import { genImageTool } from './pollinations/gen_image.js';
21
- import { genVideoTool } from './pollinations/gen_video.js';
22
- import { genAudioTool } from './pollinations/gen_audio.js';
23
- import { transcribeAudioTool } from './pollinations/transcribe_audio.js';
24
- import { genMusicTool } from './pollinations/gen_music.js';
25
- import { deepsearchTool } from './pollinations/deepsearch.js';
26
- import { searchCrawlScrapeTool } from './pollinations/search_crawl_scrape.js';
27
- import * as fs from 'fs';
28
- const LOG_FILE = '/tmp/opencode_pollinations_v4.log';
29
- function log(msg) {
30
- try {
31
- fs.appendFileSync(LOG_FILE, `[${new Date().toISOString()}] [Tools] ${msg}\n`);
32
- }
33
- catch { }
34
- }
20
+ import { polliGenImageTool } from './pollinations/gen_image.js';
21
+ import { polliGenVideoTool } from './pollinations/gen_video.js';
22
+ import { polliGenAudioTool } from './pollinations/gen_audio.js';
23
+ import { polliSttTool } from './pollinations/transcribe_audio.js';
24
+ import { polliGenMusicTool } from './pollinations/gen_music.js';
25
+ import { polliWebSearchTool } from './pollinations/polli_web_search.js';
26
+ import { polliBetaDiscoveryTool } from './pollinations/beta_discovery.js';
27
+ import { polliGenConfirmTool } from './pollinations/polli_gen_confirm.js';
28
+ import { polliStatusTool } from './pollinations/polli_status.js';
29
+ import { log } from '../server/logger.js';
35
30
  /**
36
31
  * Detect if a valid API key is present
37
32
  */
@@ -59,23 +54,30 @@ export function createToolRegistry() {
59
54
  tools['extract_audio'] = extractAudioTool;
60
55
  tools['rmbg_keys'] = rmbgKeysTool;
61
56
  log(`Free tools injected: ${Object.keys(tools).length}`);
62
- // === ENTER UNIVERSE: Only with valid API key (+7 tools) ===
57
+ // === ENTER UNIVERSE: Only with valid API key (+6 tools) ===
63
58
  if (keyPresent) {
64
59
  // Pollinations media tools
65
- tools['gen_image'] = genImageTool;
66
- tools['gen_video'] = genVideoTool;
67
- tools['gen_audio'] = genAudioTool;
68
- tools['transcribe_audio'] = transcribeAudioTool;
69
- tools['gen_music'] = genMusicTool;
70
- // Search tools
71
- tools['deepsearch'] = deepsearchTool;
72
- tools['search_crawl_scrape'] = searchCrawlScrapeTool;
60
+ tools['polli_gen_image'] = polliGenImageTool;
61
+ tools['polli_gen_video'] = polliGenVideoTool;
62
+ tools['polli_gen_audio'] = polliGenAudioTool;
63
+ tools['polli_stt'] = polliSttTool;
64
+ tools['polli_gen_music'] = polliGenMusicTool;
65
+ // Unified search tool
66
+ tools['polli_web_search'] = polliWebSearchTool;
67
+ // Cost Guard Confirmation tool
68
+ tools['polli_gen_confirm'] = polliGenConfirmTool;
69
+ // Model API discovery & diagnostics
70
+ tools['polli_beta_discovery'] = polliBetaDiscoveryTool;
71
+ // Plugin Status / Info / Pricing helper map
72
+ tools['polli_status'] = polliStatusTool;
73
73
  log(`Enter tools injected (key detected). Total: ${Object.keys(tools).length}`);
74
74
  }
75
75
  else {
76
+ // En mode gratuit, on ajoute quand meme polli_status mais restraint (il dira manque une clé pour full profile etc)
77
+ tools['polli_status'] = polliStatusTool;
76
78
  log(`Enter tools SKIPPED (no key). Total: ${Object.keys(tools).length}`);
77
79
  }
78
80
  return tools;
79
81
  }
80
82
  // Re-export for convenience
81
- export { genImageTool, genVideoTool, genAudioTool, transcribeAudioTool, genMusicTool, deepsearchTool, searchCrawlScrapeTool, };
83
+ export { polliGenImageTool, polliGenVideoTool, polliGenAudioTool, polliSttTool, polliGenMusicTool, polliWebSearchTool, polliBetaDiscoveryTool, polliGenConfirmTool, polliStatusTool };
@@ -0,0 +1,9 @@
1
+ /**
2
+ * beta_discovery Tool (API Explorer V3 - Hybrid Probe)
3
+ *
4
+ * Combines reading the official OpenAPI Specification with active
5
+ * blackbox probing (triggering HTTP 400/422 ValidationErrors) to
6
+ * discover hidden or undocumented enums and parameters.
7
+ */
8
+ import { type ToolDefinition } from '@opencode-ai/plugin/tool';
9
+ export declare const polliBetaDiscoveryTool: ToolDefinition;