opencode-pollinations-plugin 6.4.9 → 6.5.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 +67 -54
- package/README.es.md +79 -66
- package/README.fr.md +70 -57
- package/README.it.md +78 -65
- package/README.md +33 -29
- package/README.zh.md +77 -64
- package/dist/locales/de.json +82 -47
- package/dist/locales/en.json +84 -49
- package/dist/locales/es.json +82 -47
- package/dist/locales/fr.json +81 -46
- package/dist/locales/it.json +82 -47
- package/dist/locales/zh.json +82 -47
- package/dist/server/commands.js +118 -178
- package/dist/server/config.d.ts +22 -6
- package/dist/server/config.js +45 -5
- package/dist/server/connect-response.js +7 -7
- package/dist/server/generate-config.js +2 -2
- package/dist/server/models/cache.d.ts +23 -10
- package/dist/server/models/cache.js +46 -24
- package/dist/server/models/fetcher.js +2 -0
- package/dist/server/models/types.d.ts +2 -0
- package/dist/server/models/worker.js +4 -4
- package/dist/server/proxy.d.ts +6 -0
- package/dist/server/proxy.js +318 -218
- package/dist/server/quota.d.ts +23 -32
- package/dist/server/quota.js +44 -184
- package/dist/server/scripts/pollinations_pricing.js +6 -3
- package/dist/server/status.js +1 -2
- package/dist/server/toast.js +1 -1
- package/dist/tools/index.d.ts +2 -1
- package/dist/tools/index.js +3 -1
- package/dist/tools/pollinations/artifact-core.d.ts +53 -0
- package/dist/tools/pollinations/artifact-core.js +159 -0
- package/dist/tools/pollinations/beta_discovery.js +2 -1
- package/dist/tools/pollinations/cost-guard.d.ts +2 -2
- package/dist/tools/pollinations/error-parser.d.ts +38 -0
- package/dist/tools/pollinations/error-parser.js +112 -0
- package/dist/tools/pollinations/gen_3d.d.ts +17 -0
- package/dist/tools/pollinations/gen_3d.js +207 -0
- package/dist/tools/pollinations/gen_image.js +29 -10
- package/dist/tools/pollinations/gen_music.js +3 -2
- package/dist/tools/pollinations/gen_video.js +13 -2
- package/dist/tools/pollinations/polli_config.js +15 -21
- package/dist/tools/pollinations/polli_gen_confirm.js +2 -0
- package/dist/tools/pollinations/shared.d.ts +1 -1
- package/dist/tools/pollinations/shared.js +53 -142
- package/dist/tools/pollinations/timeout-policy.d.ts +80 -0
- package/dist/tools/pollinations/timeout-policy.js +124 -0
- package/dist/tools/pollinations/tool-capability-registry.d.ts +51 -0
- package/dist/tools/pollinations/tool-capability-registry.js +215 -0
- package/dist/tools/pollinations/transcribe_audio.js +5 -24
- package/package.json +64 -62
- package/dist/server/tier-info.d.ts +0 -36
- package/dist/server/tier-info.js +0 -107
package/dist/server/commands.js
CHANGED
|
@@ -1,33 +1,11 @@
|
|
|
1
1
|
import * as https from 'https';
|
|
2
2
|
import { loadConfig, saveConfig, saveKeyToAuthJson } from './config.js';
|
|
3
|
-
import { getQuotaStatus, fetchUsageForPeriod } from './quota.js';
|
|
3
|
+
import { getQuotaStatus, fetchUsageForPeriod, calculateResetInfo } from './quota.js';
|
|
4
4
|
import { emitStatusToast } from './toast.js';
|
|
5
5
|
import { generatePollinationsConfig } from './generate-config.js';
|
|
6
6
|
import { ModelRegistry } from './models/index.js';
|
|
7
7
|
import { t } from '../locales/index.js';
|
|
8
|
-
import { formatTierTable } from './tier-info.js';
|
|
9
8
|
import { buildQuestsReport } from '../tools/pollinations/polli_quests.js';
|
|
10
|
-
// GET helper for /account/* JSON endpoints (used for quest-reward reconstruction).
|
|
11
|
-
function fetchAccountJson(path, apiKey) {
|
|
12
|
-
return new Promise((resolve, reject) => {
|
|
13
|
-
const req = https.request({
|
|
14
|
-
hostname: 'gen.pollinations.ai', path, method: 'GET',
|
|
15
|
-
headers: { 'Authorization': `Bearer ${apiKey}`, 'User-Agent': 'opencode-pollinations-plugin' },
|
|
16
|
-
}, (res) => {
|
|
17
|
-
let data = '';
|
|
18
|
-
res.on('data', c => data += c);
|
|
19
|
-
res.on('end', () => { try {
|
|
20
|
-
resolve(JSON.parse(data));
|
|
21
|
-
}
|
|
22
|
-
catch (e) {
|
|
23
|
-
reject(e);
|
|
24
|
-
} });
|
|
25
|
-
});
|
|
26
|
-
req.on('error', reject);
|
|
27
|
-
req.setTimeout(10000, () => { req.destroy(); reject(new Error('Timeout')); });
|
|
28
|
-
req.end();
|
|
29
|
-
});
|
|
30
|
-
}
|
|
31
9
|
function checkEndpoint(ep, key) {
|
|
32
10
|
return new Promise((resolve) => {
|
|
33
11
|
const req = https.request({
|
|
@@ -87,27 +65,10 @@ function formatTokens(tokens) {
|
|
|
87
65
|
return `${(tokens / 1_000).toFixed(1)}K`;
|
|
88
66
|
return tokens.toString();
|
|
89
67
|
}
|
|
90
|
-
function formatDuration(ms) {
|
|
91
|
-
const hours = Math.floor(ms / (1000 * 60 * 60));
|
|
92
|
-
const minutes = Math.floor((ms % (1000 * 60 * 60)) / (1000 * 60));
|
|
93
|
-
return `${hours}h ${minutes}m`;
|
|
94
|
-
}
|
|
95
|
-
function progressBar(value, max) {
|
|
96
|
-
const percentage = max > 0 ? Math.round((value / max) * 10) : 0;
|
|
97
|
-
const filled = '█'.repeat(percentage);
|
|
98
|
-
const empty = '░'.repeat(10 - percentage);
|
|
99
|
-
return `\`${filled}${empty}\` (${(value / max * 100).toFixed(0)}%)`;
|
|
100
|
-
}
|
|
101
68
|
function parseUsageTimestamp(timestamp) {
|
|
102
69
|
return new Date(timestamp.replace(' ', 'T') + 'Z');
|
|
103
70
|
}
|
|
104
|
-
function
|
|
105
|
-
// Hourly quota system (reset at :00). The "current period" is the last hour,
|
|
106
|
-
// matching the tier window computed in quota.ts. (Previously 24h — stale daily model.)
|
|
107
|
-
const lastReset = new Date(nextResetAt.getTime() - 60 * 60 * 1000);
|
|
108
|
-
return lastReset;
|
|
109
|
-
}
|
|
110
|
-
function calculateCurrentPeriodStats(usage, lastReset, tierLimit) {
|
|
71
|
+
function calculateCurrentPeriodStats(usage, lastReset) {
|
|
111
72
|
let tierUsed = 0;
|
|
112
73
|
let packUsed = 0;
|
|
113
74
|
let totalRequests = 0;
|
|
@@ -136,7 +97,7 @@ function calculateCurrentPeriodStats(usage, lastReset, tierLimit) {
|
|
|
136
97
|
}
|
|
137
98
|
return {
|
|
138
99
|
tierUsed,
|
|
139
|
-
tierRemaining:
|
|
100
|
+
tierRemaining: 0,
|
|
140
101
|
packUsed,
|
|
141
102
|
totalRequests,
|
|
142
103
|
inputTokens,
|
|
@@ -203,50 +164,57 @@ async function handleModeCommand(args) {
|
|
|
203
164
|
response: t('commands.mode.current', { mode: config.mode })
|
|
204
165
|
};
|
|
205
166
|
}
|
|
206
|
-
|
|
167
|
+
// v6.5 Quest/Paid modes + legacy aliases (alwaysfree → quest, pro → paid).
|
|
168
|
+
const LEGACY_MODE_ALIASES = {
|
|
169
|
+
'alwaysfree': 'quest',
|
|
170
|
+
'pro': 'paid',
|
|
171
|
+
};
|
|
172
|
+
const resolvedMode = LEGACY_MODE_ALIASES[mode] || mode;
|
|
173
|
+
if (!['manual', 'quest', 'quest_only', 'paid'].includes(resolvedMode)) {
|
|
207
174
|
return {
|
|
208
175
|
handled: true,
|
|
209
176
|
error: t('commands.mode.invalid', { mode })
|
|
210
177
|
};
|
|
211
178
|
}
|
|
212
179
|
const checkConfig = loadConfig();
|
|
213
|
-
// JIT VERIFICATION for
|
|
214
|
-
if (
|
|
215
|
-
const checkConfig = loadConfig(); // Reload to be sure
|
|
180
|
+
// JIT VERIFICATION for Quest/Paid modes (requires a valid key)
|
|
181
|
+
if (resolvedMode === 'quest' || resolvedMode === 'quest_only' || resolvedMode === 'paid') {
|
|
216
182
|
const key = checkConfig.apiKey;
|
|
217
183
|
if (!key) {
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
if (mode === 'pro')
|
|
221
|
-
return { handled: true, error: t('commands.mode.pro_requires_key') };
|
|
222
|
-
}
|
|
223
|
-
emitStatusToast('info', t('commands.mode.verifying'), 'Mode Pro');
|
|
224
|
-
try {
|
|
225
|
-
// Force verify permissions NOW
|
|
226
|
-
const check = await checkKeyPermissions(key);
|
|
227
|
-
if (!check.ok) {
|
|
228
|
-
saveConfig({ mode: 'manual', keyHasAccessToProfile: false });
|
|
229
|
-
return {
|
|
230
|
-
handled: true,
|
|
231
|
-
error: t('commands.mode.denied', { status: check.status || '?', reason: check.reason || '?' })
|
|
232
|
-
};
|
|
184
|
+
if (resolvedMode === 'paid' || resolvedMode === 'quest_only') {
|
|
185
|
+
return { handled: true, error: t('commands.mode.key_required', { mode: resolvedMode }) };
|
|
233
186
|
}
|
|
234
|
-
// Valid -> Ensure flag is true
|
|
235
|
-
saveConfig({ keyHasAccessToProfile: true });
|
|
236
187
|
}
|
|
237
|
-
|
|
238
|
-
|
|
188
|
+
else {
|
|
189
|
+
emitStatusToast('info', t('commands.mode.verifying'), 'Mode');
|
|
190
|
+
try {
|
|
191
|
+
// Force verify permissions NOW
|
|
192
|
+
const check = await checkKeyPermissions(key);
|
|
193
|
+
if (!check.ok) {
|
|
194
|
+
saveConfig({ mode: 'manual', keyHasAccessToProfile: false });
|
|
195
|
+
return {
|
|
196
|
+
handled: true,
|
|
197
|
+
error: t('commands.mode.denied', { status: check.status || '?', reason: check.reason || '?' })
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
// Valid -> Ensure flag is true
|
|
201
|
+
saveConfig({ keyHasAccessToProfile: true });
|
|
202
|
+
}
|
|
203
|
+
catch (e) {
|
|
204
|
+
return { handled: true, error: t('commands.mode.verify_error', { error: e.message }) };
|
|
205
|
+
}
|
|
239
206
|
}
|
|
240
207
|
}
|
|
241
|
-
// Allow switch
|
|
242
|
-
saveConfig({ mode:
|
|
208
|
+
// Allow switch
|
|
209
|
+
saveConfig({ mode: resolvedMode });
|
|
243
210
|
const config = loadConfig();
|
|
244
211
|
if (config.gui.status !== 'none') {
|
|
245
|
-
emitStatusToast('success', t('commands.mode.success', { mode }), 'Pollinations Config');
|
|
212
|
+
emitStatusToast('success', t('commands.mode.success', { mode: resolvedMode }), 'Pollinations Config');
|
|
246
213
|
}
|
|
214
|
+
const aliasNote = LEGACY_MODE_ALIASES[mode] ? ` ${t('commands.mode.legacy_alias', { legacy: mode, mode: resolvedMode })}` : '';
|
|
247
215
|
return {
|
|
248
216
|
handled: true,
|
|
249
|
-
response: t('commands.mode.success', { mode })
|
|
217
|
+
response: t('commands.mode.success', { mode: resolvedMode }) + aliasNote
|
|
250
218
|
};
|
|
251
219
|
}
|
|
252
220
|
export async function handleUsageCommand(args) {
|
|
@@ -254,70 +222,31 @@ export async function handleUsageCommand(args) {
|
|
|
254
222
|
try {
|
|
255
223
|
const quota = await getQuotaStatus(true);
|
|
256
224
|
const config = loadConfig();
|
|
257
|
-
const
|
|
258
|
-
const timeUntilReset = quota.nextResetAt.getTime() - Date.now();
|
|
259
|
-
const durationStr = formatDuration(Math.max(0, timeUntilReset));
|
|
225
|
+
const resetInfo = calculateResetInfo();
|
|
260
226
|
let response = t('commands.usage.title', { mode: config.mode.toUpperCase() });
|
|
261
227
|
response += t('commands.usage.resources');
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
// Reconstructed Quest/Paid split by CROSS-REFERENCING exact data (no magic field needed):
|
|
266
|
-
// • claimed quest rewards (tier bucket) ← /account/quests (exact)
|
|
267
|
-
// • tier consumption since the first claim ← /account/usage meter_source=='tier' (exact)
|
|
268
|
-
// • current-hour floor remaining (≤ tierLimit) ← quota (the only fuzzy term, ignored)
|
|
269
|
-
// Quest remaining ≈ claimedQuestTier − tierConsumedSinceClaim (+ floor); Paid = Total − Quest.
|
|
270
|
-
const total = quota.tierRemaining + quota.walletBalance; // true balance
|
|
271
|
-
let claimedQuestTier = 0;
|
|
272
|
-
let firstClaimMs = Infinity;
|
|
273
|
-
let tierConsumedSinceClaim = 0;
|
|
274
|
-
if (config.apiKey && config.keyHasAccessToProfile !== false) {
|
|
275
|
-
try {
|
|
276
|
-
const qres = await fetchAccountJson('/account/quests', config.apiKey);
|
|
277
|
-
for (const q of (qres?.quests || [])) {
|
|
278
|
-
const r = q.reward;
|
|
279
|
-
if (r && r.claimedAt && r.balanceBucket === 'tier') {
|
|
280
|
-
claimedQuestTier += (r.pollenAmount || 0);
|
|
281
|
-
const cms = new Date(r.claimedAt).getTime();
|
|
282
|
-
if (!isNaN(cms) && cms < firstClaimMs)
|
|
283
|
-
firstClaimMs = cms;
|
|
284
|
-
}
|
|
285
|
-
}
|
|
286
|
-
// Sum tier-metered spend since the first claim (what actually ate the Quest stash).
|
|
287
|
-
if (claimedQuestTier > 0 && isFinite(firstClaimMs)) {
|
|
288
|
-
const ures = await fetchAccountJson('/account/usage?limit=100', config.apiKey);
|
|
289
|
-
for (const e of (ures?.usage || [])) {
|
|
290
|
-
const ts = new Date(String(e.timestamp).replace(' ', 'T') + (String(e.timestamp).includes('Z') ? '' : 'Z')).getTime();
|
|
291
|
-
if (e.meter_source === 'tier' && !isNaN(ts) && ts >= firstClaimMs) {
|
|
292
|
-
tierConsumedSinceClaim += (e.cost_usd || 0);
|
|
293
|
-
}
|
|
294
|
-
}
|
|
295
|
-
}
|
|
296
|
-
}
|
|
297
|
-
catch { /* fall back gracefully if unreachable */ }
|
|
298
|
-
}
|
|
299
|
-
// Quest stash = claimed − consumed, plus the current hourly floor still available.
|
|
300
|
-
const questPollen = Math.max(0, claimedQuestTier - tierConsumedSinceClaim) + quota.tierRemaining;
|
|
301
|
-
const paidPollen = Math.max(0, total - questPollen);
|
|
228
|
+
const quest = quota.questBalance;
|
|
229
|
+
const paid = quota.walletBalance;
|
|
230
|
+
const total = quota.totalBalance || (quest + paid);
|
|
302
231
|
response += t('commands.usage.split', {
|
|
303
|
-
quest:
|
|
304
|
-
paid:
|
|
232
|
+
quest: quest.toFixed(2),
|
|
233
|
+
paid: paid.toFixed(2),
|
|
305
234
|
total: total.toFixed(2),
|
|
306
235
|
});
|
|
307
|
-
response += t('commands.usage.
|
|
236
|
+
response += t('commands.usage.quest_note');
|
|
308
237
|
if (isFull && config.apiKey) {
|
|
309
238
|
if (config.keyHasAccessToProfile === false) {
|
|
310
239
|
response += t('commands.usage.restricted_key');
|
|
311
240
|
}
|
|
312
241
|
else {
|
|
313
|
-
const lastReset =
|
|
242
|
+
const lastReset = resetInfo.lastReset;
|
|
314
243
|
const usageData = await fetchUsageForPeriod(config.apiKey, lastReset);
|
|
315
244
|
if (usageData && usageData.length > 0) {
|
|
316
|
-
const stats = calculateCurrentPeriodStats(usageData, lastReset
|
|
245
|
+
const stats = calculateCurrentPeriodStats(usageData, lastReset);
|
|
317
246
|
response += t('commands.usage.period_detail', { time: lastReset.toLocaleTimeString() });
|
|
318
247
|
response += t('commands.usage.total_reqs', { reqs: stats.totalRequests, inTok: formatTokens(stats.inputTokens), outTok: formatTokens(stats.outputTokens) });
|
|
319
248
|
// Exact consumption split by meter_source (tier = Quest Pollen, pack = Paid).
|
|
320
|
-
// This is the ONLY reliable split the API exposes
|
|
249
|
+
// This is the ONLY reliable split the API exposes.
|
|
321
250
|
response += t('commands.usage.source_split', {
|
|
322
251
|
tier: formatPollen(stats.tierUsed),
|
|
323
252
|
pack: formatPollen(stats.packUsed),
|
|
@@ -650,20 +579,23 @@ ${t('commands.config.intro')}
|
|
|
650
579
|
${t('commands.config.table_headers')}
|
|
651
580
|
${t('commands.config.table_divider')}
|
|
652
581
|
| **apiKey** | \`${k}\` | ${t('commands.config.api_key_role')} | \`/poll connect <key>\` |
|
|
653
|
-
| **mode** | \`${config.mode}\` | ${t('commands.config.mode_role')} | \`/poll mode <
|
|
582
|
+
| **mode** | \`${config.mode}\` | ${t('commands.config.mode_role')} | \`/poll mode <quest/quest_only/paid/manual>\` |
|
|
654
583
|
| **enablePaidTools**| \`${config.enablePaidTools ?? true}\` | ${t('commands.config.enablePaidTools_role')} | \`/poll config enablePaidTools <true/false>\` |
|
|
655
584
|
| **costConfirmationRequired**| \`${config.costConfirmationRequired ?? true}\` | ${t('commands.config.costConfirmationRequired_role')} | \`/poll config costConfirmationRequired <true/false>\` |
|
|
656
585
|
| **costThreshold**| \`${config.costThreshold ?? 0.15} 🌻\` | ${t('commands.config.costThreshold_role')} | \`/poll config costThreshold <X>\` |
|
|
657
586
|
| **cost_estimator**| \`${config.costEstimator ?? true}\` | ${t('commands.config.cost_estimator_role')} | \`/poll config cost_estimator <true/false>\` |
|
|
658
|
-
| **refillOverride**| \`${config.refillOverride ?? 'auto (déduit)'}\` | Quest Pollen refill horaire | \`/poll config refillOverride <0.01/0.15/0.4/0.8/10>\` |
|
|
659
|
-
| **questStashInFreeMode**| \`${config.questStashInFreeMode ?? true}\` | Compte le stash dans alwaysfree | \`/poll config questStashInFreeMode <true/false>\` |
|
|
660
587
|
| **fallbacks.free.main** | \`${config.fallbacks?.free?.main || 'free/mistral'}\` | ${t('commands.config.fallback_main_role')} | \`/poll fallback <main> <agent>\` |
|
|
661
588
|
| **fallbacks.free.agent** | \`${config.fallbacks?.free?.agent || 'free/openai-fast'}\`| ${t('commands.config.fallback_agent_role')} | \`/poll fallback <main> <agent>\` |
|
|
662
589
|
| **fallbacks.enter.agent** | \`${config.fallbacks?.enter?.agent || 'free/openai-fast'}\`| ${t('commands.config.fallback_enter_role')} | *${t('commands.config.managed_auto')}* |
|
|
663
590
|
| **status_gui** | \`${config.gui?.status || 'all'}\` | ${t('commands.config.status_gui_role')} | \`/poll config status_gui <all/alert/none>\` |
|
|
664
591
|
| **logs_gui** | \`${config.gui?.logs || 'error'}\` | ${t('commands.config.logs_gui_role')} | \`/poll config logs_gui <verbose/error/none>\` |
|
|
665
|
-
| **
|
|
666
|
-
| **threshold_wallet** | \`${config.thresholds?.wallet
|
|
592
|
+
| **threshold_quest** | \`${config.thresholds?.quest ?? 0.05} 🌻\` | ${t('commands.config.threshold_quest_role')} | \`/poll config threshold_quest <pollen>\` |
|
|
593
|
+
| **threshold_wallet** | \`${config.thresholds?.wallet ?? 0.5} 🌻\` | ${t('commands.config.threshold_wallet_role')} | \`/poll config threshold_wallet <pollen>\` |
|
|
594
|
+
| **timeouts.default** | \`${config.timeouts?.default ?? 300}s\` | ${t('commands.config.timeouts_default_role')} | \`/poll config timeouts.default <s>\` |
|
|
595
|
+
| **timeouts.longRunning** | \`${config.timeouts?.longRunning ?? 900}s\` | ${t('commands.config.timeouts_long_role')} | \`/poll config timeouts.longRunning <s>\` |
|
|
596
|
+
| **timeouts.max** | \`${config.timeouts?.max ?? 3600}s\` | ${t('commands.config.timeouts_max_role')} | \`/poll config timeouts.max <s>\` |
|
|
597
|
+
| **timeouts.video** | \`${config.timeouts?.capabilities?.video ?? 1800}s\` | ${t('commands.config.timeouts_video_role')} | \`/poll config timeouts.video <s>\` |
|
|
598
|
+
| **timeouts.threeD** | \`${config.timeouts?.capabilities?.threeD ?? 1800}s\` | ${t('commands.config.timeouts_3d_role')} | \`/poll config timeouts.threeD <s>\` |
|
|
667
599
|
| **status_bar** | \`${config.statusBar ?? true}\` | ${t('commands.config.status_bar_role')} | \`/poll config status_bar <true/false>\` |
|
|
668
600
|
| **lang** | \`${config.lang || 'en'}\` | ${t('commands.config.lang_role')} | \`/poll config lang <en/fr/es/de/it>\` |`;
|
|
669
601
|
return {
|
|
@@ -706,23 +638,63 @@ ${t('commands.config.table_divider')}
|
|
|
706
638
|
saveConfig({ gui: { ...config.gui, logs: value } });
|
|
707
639
|
return { handled: true, response: `✅ logs_gui = ${value}` };
|
|
708
640
|
}
|
|
709
|
-
if (key === '
|
|
710
|
-
const threshold =
|
|
711
|
-
if (isNaN(threshold) || threshold < 0
|
|
712
|
-
return { handled: true, error: 'Valeur
|
|
641
|
+
if (key === 'threshold_quest' && value) {
|
|
642
|
+
const threshold = parseFloat(value);
|
|
643
|
+
if (isNaN(threshold) || threshold < 0) {
|
|
644
|
+
return { handled: true, error: 'Valeur numérique positive requise (en pollen). Ex: 0.05' };
|
|
713
645
|
}
|
|
714
646
|
const config = loadConfig();
|
|
715
|
-
saveConfig({ thresholds: { ...config.thresholds,
|
|
716
|
-
return { handled: true, response: `✅
|
|
647
|
+
saveConfig({ thresholds: { ...config.thresholds, quest: threshold } });
|
|
648
|
+
return { handled: true, response: `✅ threshold_quest = ${threshold} 🌻` };
|
|
717
649
|
}
|
|
718
650
|
if (key === 'threshold_wallet' && value) {
|
|
719
|
-
const threshold =
|
|
720
|
-
if (isNaN(threshold) || threshold < 0
|
|
721
|
-
return { handled: true, error: 'Valeur
|
|
651
|
+
const threshold = parseFloat(value);
|
|
652
|
+
if (isNaN(threshold) || threshold < 0) {
|
|
653
|
+
return { handled: true, error: 'Valeur numérique positive requise (en pollen). Ex: 0.5' };
|
|
722
654
|
}
|
|
723
655
|
const config = loadConfig();
|
|
724
656
|
saveConfig({ thresholds: { ...config.thresholds, wallet: threshold } });
|
|
725
|
-
return { handled: true, response: `✅ threshold_wallet = ${threshold}
|
|
657
|
+
return { handled: true, response: `✅ threshold_wallet = ${threshold} 🌻` };
|
|
658
|
+
}
|
|
659
|
+
// v6.5 timeout hierarchy: timeouts.default / longRunning / max /
|
|
660
|
+
// timeouts.<capability> (image|video|audio|threeD|realtime|embed) /
|
|
661
|
+
// timeouts.model.<name> / reset
|
|
662
|
+
if (key.startsWith('timeouts') && value) {
|
|
663
|
+
const parts = key.split('.');
|
|
664
|
+
const config = loadConfig();
|
|
665
|
+
const timeouts = { ...(config.timeouts || {}) };
|
|
666
|
+
if (parts.length === 2 && ['default', 'longRunning', 'max'].includes(parts[1])) {
|
|
667
|
+
const seconds = parseFloat(value);
|
|
668
|
+
if (isNaN(seconds) || seconds < 1 || seconds > 3600) {
|
|
669
|
+
return { handled: true, error: 'Valeur en secondes requise (1-3600)' };
|
|
670
|
+
}
|
|
671
|
+
timeouts[parts[1]] = seconds;
|
|
672
|
+
saveConfig({ timeouts });
|
|
673
|
+
return { handled: true, response: `✅ timeouts.${parts[1]} = ${seconds}s` };
|
|
674
|
+
}
|
|
675
|
+
const CAP_KEYS = ['image', 'video', 'audio', 'threeD', 'realtime', 'embed'];
|
|
676
|
+
if (parts.length === 2 && CAP_KEYS.includes(parts[1])) {
|
|
677
|
+
const seconds = parseFloat(value);
|
|
678
|
+
if (isNaN(seconds) || seconds < 10 || seconds > 3600) {
|
|
679
|
+
return { handled: true, error: 'Valeur en secondes requise (10-3600)' };
|
|
680
|
+
}
|
|
681
|
+
timeouts.capabilities = { ...(timeouts.capabilities || {}), [parts[1]]: seconds };
|
|
682
|
+
saveConfig({ timeouts });
|
|
683
|
+
return { handled: true, response: `✅ timeouts.${parts[1]} = ${seconds}s` };
|
|
684
|
+
}
|
|
685
|
+
if (parts.length === 3 && parts[1] === 'model') {
|
|
686
|
+
const seconds = parseFloat(value);
|
|
687
|
+
if (isNaN(seconds) || seconds < 10 || seconds > 3600) {
|
|
688
|
+
return { handled: true, error: 'Valeur en secondes requise (10-3600)' };
|
|
689
|
+
}
|
|
690
|
+
timeouts.overrides = { ...(timeouts.overrides || {}), [parts[2]]: seconds };
|
|
691
|
+
saveConfig({ timeouts });
|
|
692
|
+
return { handled: true, response: `✅ timeouts.model.${parts[2]} = ${seconds}s` };
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
if (key === 'timeouts.reset') {
|
|
696
|
+
saveConfig({ timeouts: undefined });
|
|
697
|
+
return { handled: true, response: '✅ timeouts = defaults (reset)' };
|
|
726
698
|
}
|
|
727
699
|
if (key === 'status_bar' && value) {
|
|
728
700
|
const enabled = value === 'true';
|
|
@@ -753,29 +725,9 @@ ${t('commands.config.table_divider')}
|
|
|
753
725
|
saveConfig({ costConfirmationRequired: enabled });
|
|
754
726
|
return { handled: true, response: `✅ costConfirmationRequired = ${enabled}` };
|
|
755
727
|
}
|
|
756
|
-
if (key === 'refillOverride' && value) {
|
|
757
|
-
if (value === 'auto') {
|
|
758
|
-
saveConfig({ refillOverride: undefined });
|
|
759
|
-
return { handled: true, response: '✅ refillOverride = auto (déduction automatique)' };
|
|
760
|
-
}
|
|
761
|
-
const override = parseFloat(value);
|
|
762
|
-
if (isNaN(override) || ![0.01, 0.15, 0.4, 0.8, 10].includes(override)) {
|
|
763
|
-
return { handled: true, error: 'Valeurs supportées: auto, 0.01, 0.15, 0.4, 0.8, 10' };
|
|
764
|
-
}
|
|
765
|
-
saveConfig({ refillOverride: override });
|
|
766
|
-
return { handled: true, response: `✅ refillOverride = ${override} 🌻/h` };
|
|
767
|
-
}
|
|
768
|
-
if (key === 'questStashInFreeMode' && value) {
|
|
769
|
-
if (value !== 'true' && value !== 'false') {
|
|
770
|
-
return { handled: true, error: 'Valeurs supportées: true, false' };
|
|
771
|
-
}
|
|
772
|
-
const enabled = value === 'true';
|
|
773
|
-
saveConfig({ questStashInFreeMode: enabled });
|
|
774
|
-
return { handled: true, response: `✅ questStashInFreeMode = ${enabled}${enabled ? ' (le stash Quest compte comme free)' : ' (seul le refill horaire compte)'}` };
|
|
775
|
-
}
|
|
776
728
|
return {
|
|
777
729
|
handled: true,
|
|
778
|
-
error: `Clé inconnue: ${key}. Clés: status_gui, logs_gui,
|
|
730
|
+
error: `Clé inconnue: ${key}. Clés: status_gui, logs_gui, threshold_quest, threshold_wallet, status_bar, cost_estimator, enablePaidTools, costThreshold, costConfirmationRequired, lang`
|
|
779
731
|
};
|
|
780
732
|
}
|
|
781
733
|
function handleHelpCommand() {
|
|
@@ -784,15 +736,13 @@ function handleHelpCommand() {
|
|
|
784
736
|
{ key: 'lang', values: 'en, fr, es, de, it, zh', i18n: 'commands.help.config.lang' },
|
|
785
737
|
{ key: 'status_gui', values: 'none, alert, all', i18n: 'commands.help.config.status_gui' },
|
|
786
738
|
{ key: 'logs_gui', values: 'none, error, verbose', i18n: 'commands.help.config.logs_gui' },
|
|
787
|
-
{ key: '
|
|
788
|
-
{ key: 'threshold_wallet', values: '0
|
|
739
|
+
{ key: 'threshold_quest', values: 'pollen (e.g. 0.05)', i18n: 'commands.help.config.threshold_quest' },
|
|
740
|
+
{ key: 'threshold_wallet', values: 'pollen (e.g. 0.5)', i18n: 'commands.help.config.threshold_wallet' },
|
|
789
741
|
{ key: 'status_bar', values: 'true/false', i18n: 'commands.help.config.status_bar' },
|
|
790
742
|
{ key: 'cost_estimator', values: 'true/false', i18n: 'commands.help.config.cost_estimator' },
|
|
791
743
|
{ key: 'enablePaidTools', values: 'true/false', i18n: 'commands.help.config.enablePaidTools' },
|
|
792
744
|
{ key: 'costThreshold', values: 'number (pollen)', i18n: 'commands.help.config.costThreshold' },
|
|
793
745
|
{ key: 'costConfirmationRequired', values: 'true/false', i18n: 'commands.help.config.costConfirmationRequired' },
|
|
794
|
-
{ key: 'refillOverride', values: '0.01, 0.15, 0.4, 0.8, 10, auto', i18n: 'commands.help.config.refillOverride' },
|
|
795
|
-
{ key: 'questStashInFreeMode', values: 'true/false', i18n: 'commands.help.config.questStashInFreeMode' },
|
|
796
746
|
];
|
|
797
747
|
const configSection = configKeys.map(k => ` - \`${k.key}\`: ${t(k.i18n)} (\`${k.values}\`)`).join('\n');
|
|
798
748
|
const help = `
|
|
@@ -810,12 +760,16 @@ ${t('commands.help.models_pricing')}
|
|
|
810
760
|
}
|
|
811
761
|
// === MODELS & PRICING COMMANDS ===
|
|
812
762
|
function parseNameDesc(m) {
|
|
763
|
+
const displayName = m.title || m.description || m.name;
|
|
813
764
|
const fullDesc = m.description || m.name;
|
|
814
|
-
|
|
765
|
+
if (m.title && m.description) {
|
|
766
|
+
return { nom: m.title, desc: m.description };
|
|
767
|
+
}
|
|
768
|
+
const parts = displayName.split(" - ");
|
|
815
769
|
if (parts.length > 1) {
|
|
816
770
|
return { nom: parts[0].trim(), desc: parts.slice(1).join(" - ").trim() };
|
|
817
771
|
}
|
|
818
|
-
return { nom:
|
|
772
|
+
return { nom: displayName, desc: "" };
|
|
819
773
|
}
|
|
820
774
|
export async function handleModelsCommand(args) {
|
|
821
775
|
const filter = args[0]; // optional: image, video, audio, text
|
|
@@ -956,17 +910,7 @@ function buildOutputCost(m) {
|
|
|
956
910
|
export async function handleInfosCommand() {
|
|
957
911
|
const config = loadConfig();
|
|
958
912
|
let name = "Developer";
|
|
959
|
-
let tier = "anonymous";
|
|
960
|
-
let tierEmoji = '👤';
|
|
961
913
|
if (config.apiKey) {
|
|
962
|
-
try {
|
|
963
|
-
const quota = await getQuotaStatus(true);
|
|
964
|
-
tier = quota.tier || 'anonymous';
|
|
965
|
-
tierEmoji = quota.tierEmoji || '👤';
|
|
966
|
-
}
|
|
967
|
-
catch (e) {
|
|
968
|
-
// Ignorer l'erreur réseau et garder les valeurs par défaut
|
|
969
|
-
}
|
|
970
914
|
try {
|
|
971
915
|
const res = await fetch('https://gen.pollinations.ai/account/profile', {
|
|
972
916
|
headers: { 'Authorization': `Bearer ${config.apiKey}` }
|
|
@@ -981,9 +925,8 @@ export async function handleInfosCommand() {
|
|
|
981
925
|
// Ignorer
|
|
982
926
|
}
|
|
983
927
|
}
|
|
984
|
-
//
|
|
985
|
-
|
|
986
|
-
const tierTable = formatTierTable(userLang);
|
|
928
|
+
// v6.5: Quest/Paid page (the old tier/refill table was removed upstream —
|
|
929
|
+
// hourly refills no longer exist).
|
|
987
930
|
const response = `${t('commands.infos.title', { name })}
|
|
988
931
|
${t('commands.infos.features_title')}
|
|
989
932
|
${t('commands.infos.features_free')}
|
|
@@ -992,16 +935,13 @@ ${t('commands.infos.features_pro')}
|
|
|
992
935
|
|
|
993
936
|
${t('commands.infos.features_config')}
|
|
994
937
|
|
|
995
|
-
${t('commands.infos.tiers_title', { emoji: tierEmoji, tier: tier.toUpperCase() })}
|
|
996
938
|
${t('commands.infos.get_started')}
|
|
997
939
|
|
|
998
940
|
${t('commands.infos.about')}
|
|
999
941
|
|
|
1000
|
-
${t('commands.infos.
|
|
1001
|
-
|
|
1002
|
-
${tierTable}
|
|
942
|
+
${t('commands.infos.quest_paid_title')}
|
|
1003
943
|
|
|
1004
|
-
${t('commands.infos.
|
|
944
|
+
${t('commands.infos.quest_paid_body')}
|
|
1005
945
|
|
|
1006
946
|
${t('commands.infos.quests')}
|
|
1007
947
|
|
package/dist/server/config.d.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
export declare function getConfigDir(): string;
|
|
2
2
|
export declare const CONFIG_DIR: string;
|
|
3
3
|
export declare const CONFIG_FILE: string;
|
|
4
|
+
export type BillingMode = 'quest' | 'quest_only' | 'paid' | 'manual';
|
|
4
5
|
export interface PollinationsConfigV5 {
|
|
5
6
|
version: string | number;
|
|
6
|
-
mode:
|
|
7
|
+
mode: BillingMode;
|
|
7
8
|
apiKey?: string;
|
|
8
9
|
keyHasAccessToProfile?: boolean;
|
|
9
10
|
gui: {
|
|
@@ -11,7 +12,7 @@ export interface PollinationsConfigV5 {
|
|
|
11
12
|
logs: 'none' | 'error' | 'verbose';
|
|
12
13
|
};
|
|
13
14
|
thresholds: {
|
|
14
|
-
|
|
15
|
+
quest: number;
|
|
15
16
|
wallet: number;
|
|
16
17
|
};
|
|
17
18
|
fallbacks: {
|
|
@@ -28,14 +29,22 @@ export interface PollinationsConfigV5 {
|
|
|
28
29
|
costConfirmationRequired: boolean;
|
|
29
30
|
statusBar: boolean;
|
|
30
31
|
costEstimator: boolean;
|
|
32
|
+
lang?: string;
|
|
33
|
+
timeouts?: {
|
|
34
|
+
default?: number;
|
|
35
|
+
longRunning?: number;
|
|
36
|
+
max?: number;
|
|
37
|
+
capabilities?: Record<string, number>;
|
|
38
|
+
overrides?: Record<string, number>;
|
|
39
|
+
};
|
|
31
40
|
refillOverride?: number;
|
|
32
41
|
questStashInFreeMode?: boolean;
|
|
33
|
-
lang?: string;
|
|
34
42
|
}
|
|
43
|
+
export declare function migrateV65Config(raw: any): any;
|
|
35
44
|
export declare function loadConfig(): PollinationsConfigV5;
|
|
36
45
|
export declare function saveConfig(updates: Partial<PollinationsConfigV5>): {
|
|
37
46
|
version: string;
|
|
38
|
-
mode:
|
|
47
|
+
mode: BillingMode;
|
|
39
48
|
apiKey?: string;
|
|
40
49
|
keyHasAccessToProfile?: boolean;
|
|
41
50
|
gui: {
|
|
@@ -43,7 +52,7 @@ export declare function saveConfig(updates: Partial<PollinationsConfigV5>): {
|
|
|
43
52
|
logs: "none" | "error" | "verbose";
|
|
44
53
|
};
|
|
45
54
|
thresholds: {
|
|
46
|
-
|
|
55
|
+
quest: number;
|
|
47
56
|
wallet: number;
|
|
48
57
|
};
|
|
49
58
|
fallbacks: {
|
|
@@ -60,9 +69,16 @@ export declare function saveConfig(updates: Partial<PollinationsConfigV5>): {
|
|
|
60
69
|
costConfirmationRequired: boolean;
|
|
61
70
|
statusBar: boolean;
|
|
62
71
|
costEstimator: boolean;
|
|
72
|
+
lang?: string;
|
|
73
|
+
timeouts?: {
|
|
74
|
+
default?: number;
|
|
75
|
+
longRunning?: number;
|
|
76
|
+
max?: number;
|
|
77
|
+
capabilities?: Record<string, number>;
|
|
78
|
+
overrides?: Record<string, number>;
|
|
79
|
+
};
|
|
63
80
|
refillOverride?: number;
|
|
64
81
|
questStashInFreeMode?: boolean;
|
|
65
|
-
lang?: string;
|
|
66
82
|
};
|
|
67
83
|
export declare function saveKeyToAuthJson(key: string): boolean;
|
|
68
84
|
export declare function migrateLegacyConfig(): void;
|
package/dist/server/config.js
CHANGED
|
@@ -69,11 +69,11 @@ catch (e) {
|
|
|
69
69
|
}
|
|
70
70
|
const DEFAULT_CONFIG_V5 = {
|
|
71
71
|
version: PKG_VERSION,
|
|
72
|
-
mode: '
|
|
72
|
+
mode: 'quest',
|
|
73
73
|
gui: { status: 'alert', logs: 'none' },
|
|
74
|
-
thresholds: {
|
|
74
|
+
thresholds: { quest: 0.05, wallet: 0.5 },
|
|
75
75
|
fallbacks: {
|
|
76
|
-
free: { main: 'free/
|
|
76
|
+
free: { main: 'free/openai-fast', agent: 'free/openai-fast' },
|
|
77
77
|
enter: { agent: 'free/openai-fast' }
|
|
78
78
|
},
|
|
79
79
|
enablePaidTools: false,
|
|
@@ -82,9 +82,49 @@ const DEFAULT_CONFIG_V5 = {
|
|
|
82
82
|
keyHasAccessToProfile: true, // Default true for legacy keys
|
|
83
83
|
statusBar: true,
|
|
84
84
|
costEstimator: true, // Show cost estimates by default
|
|
85
|
-
questStashInFreeMode: true, // Count quest stash as free in alwaysfree
|
|
86
85
|
lang: 'en', // Default language is English
|
|
87
86
|
};
|
|
87
|
+
// v6.5 migration: legacy mode/threshold names → Quest/Paid semantics.
|
|
88
|
+
// alwaysfree → quest (QUEST_PREFERRED), pro → paid (PAID_ALLOWED).
|
|
89
|
+
// Legacy tier percentage thresholds are replaced by absolute pollen floors.
|
|
90
|
+
export function migrateV65Config(raw) {
|
|
91
|
+
if (!raw || typeof raw !== 'object')
|
|
92
|
+
return raw;
|
|
93
|
+
const legacyModeMap = {
|
|
94
|
+
'alwaysfree': 'quest',
|
|
95
|
+
'pro': 'paid',
|
|
96
|
+
'manual': 'manual',
|
|
97
|
+
'quest': 'quest',
|
|
98
|
+
'quest_only': 'quest_only',
|
|
99
|
+
'paid': 'paid',
|
|
100
|
+
};
|
|
101
|
+
if (raw.mode && legacyModeMap[raw.mode]) {
|
|
102
|
+
const mapped = legacyModeMap[raw.mode];
|
|
103
|
+
if (mapped !== raw.mode) {
|
|
104
|
+
logConfig(`Migrating legacy mode '${raw.mode}' → '${mapped}'`);
|
|
105
|
+
raw.mode = mapped;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
if (raw.thresholds && typeof raw.thresholds === 'object') {
|
|
109
|
+
if (raw.thresholds.tier !== undefined && raw.thresholds.quest === undefined) {
|
|
110
|
+
// Old percentage-of-tier threshold cannot be converted to a
|
|
111
|
+
// meaningful absolute floor: use the v6.5 default.
|
|
112
|
+
logConfig('Migrating legacy thresholds.tier → thresholds.quest (default 0.05)');
|
|
113
|
+
raw.thresholds.quest = 0.05;
|
|
114
|
+
delete raw.thresholds.tier;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
// Purge dead refill concepts from persisted config.
|
|
118
|
+
if ('refillOverride' in raw) {
|
|
119
|
+
logConfig('Removing deprecated refillOverride');
|
|
120
|
+
delete raw.refillOverride;
|
|
121
|
+
}
|
|
122
|
+
if ('questStashInFreeMode' in raw) {
|
|
123
|
+
logConfig('Removing deprecated questStashInFreeMode');
|
|
124
|
+
delete raw.questStashInFreeMode;
|
|
125
|
+
}
|
|
126
|
+
return raw;
|
|
127
|
+
}
|
|
88
128
|
import { log as logSystem } from './logger.js';
|
|
89
129
|
function logConfig(msg) {
|
|
90
130
|
logSystem(`[Config] ${msg}`);
|
|
@@ -225,7 +265,7 @@ function readConfigFromDisk() {
|
|
|
225
265
|
// Let's REMOVE this auto-downgrade too to be strictly "Decoupled".
|
|
226
266
|
// If user is in PRO without key, they get "Missing Key" error, which is correct.
|
|
227
267
|
}
|
|
228
|
-
return { ...config, version: PKG_VERSION };
|
|
268
|
+
return migrateV65Config({ ...config, version: PKG_VERSION });
|
|
229
269
|
}
|
|
230
270
|
export function saveConfig(updates) {
|
|
231
271
|
try {
|
|
@@ -4,21 +4,21 @@ export async function buildConnectResponse(config) {
|
|
|
4
4
|
const hasKey = !!config.apiKey;
|
|
5
5
|
const mode = config.mode;
|
|
6
6
|
let name = "Developer";
|
|
7
|
-
let
|
|
8
|
-
let
|
|
7
|
+
let questEmoji = '🎁';
|
|
8
|
+
let questText = 'Quest/Paid';
|
|
9
9
|
if (hasKey) {
|
|
10
10
|
try {
|
|
11
|
-
//
|
|
11
|
+
// v6.5: Quest/Paid semantics (tier/refill model removed upstream).
|
|
12
12
|
const quota = await getQuotaStatus(true);
|
|
13
|
-
|
|
14
|
-
tierEmoji = quota.tierEmoji || '👤';
|
|
13
|
+
questText = `Quest ~${quota.questBalance.toFixed(2)} | Paid ~${quota.walletBalance.toFixed(2)}`;
|
|
15
14
|
}
|
|
16
15
|
catch (e) {
|
|
17
16
|
// Ignorer l'erreur réseau et garder les valeurs par défaut
|
|
18
17
|
}
|
|
19
18
|
try {
|
|
20
19
|
const res = await fetch('https://gen.pollinations.ai/account/profile', {
|
|
21
|
-
headers: { 'Authorization': `Bearer ${config.apiKey}` }
|
|
20
|
+
headers: { 'Authorization': `Bearer ${config.apiKey}` },
|
|
21
|
+
signal: AbortSignal.timeout(5000),
|
|
22
22
|
});
|
|
23
23
|
if (res.ok) {
|
|
24
24
|
const data = await res.json();
|
|
@@ -33,7 +33,7 @@ export async function buildConnectResponse(config) {
|
|
|
33
33
|
if (hasKey) {
|
|
34
34
|
return `${t('connect_response.title_key', { name, mode })}
|
|
35
35
|
|
|
36
|
-
> **Your
|
|
36
|
+
> **Your Pollen:** ${questEmoji} ${questText}
|
|
37
37
|
|
|
38
38
|
---
|
|
39
39
|
|