opencode-pollinations-plugin 6.1.0-beta.8 → 6.2.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.
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 -293
  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 +10 -4
  108. package/dist/server/models-seed.d.ts +0 -18
  109. package/dist/server/models-seed.js +0 -55
@@ -0,0 +1,95 @@
1
+ import { tool } from '@opencode-ai/plugin/tool';
2
+ import { loadConfig, saveConfig } from '../../server/config.js';
3
+ import { emitStatusToast } from '../../server/toast.js';
4
+ import { t } from '../../locales/index.js';
5
+ export const polliConfigTool = tool({
6
+ description: `[CRITICAL TOOL FOR ASSISTANT] View or modify the Pollinations plugin configuration.
7
+ You must strictly understand the 3 INDEPENDENT categories of settings before explaining or changing them:
8
+
9
+ === 1. CHAT MODELS & FALLBACKS (Applies ONLY to conversational chat models) ===
10
+ - mode: Dictates fallback rules for the chat.
11
+ * 'manual': No automatic rules.
12
+ * 'alwaysfree': Free tiers first. If 'thresholdsTier' is reached -> fallbacks to Free Universe. NEVER uses Wallet.
13
+ * 'pro': Uses Wallet. If 'thresholdsWallet' is reached -> fallbacks to Free Universe.
14
+ - thresholdsTier: WARNING PERCENTAGE (e.g. 10 for 10%) that triggers chat fallback in 'alwaysfree' mode.
15
+ - thresholdsWallet: WARNING PERCENTAGE (e.g. 50 for 50%) that triggers chat fallback in 'pro' mode.
16
+ *Note: 'enter.agent' or 'free.agent' are fallback conversational models for logic reasoning, THEY DO NOT GENERATE IMAGES OR VIDEOS!*
17
+
18
+ === 2. TOOLS PROTECTION (Applies ONLY to independent 'polli_' tools like image, video, search) ===
19
+ - enablePaidTools: Allow tools to execute models that consume 'Wallet' pollen. If false, tools can only use models that consume 'Freetier' pollen.
20
+ - costConfirmationRequired: Safety lock for tools. If true, the user MUST manually confirm BEFORE executing ANY tool whose cost estimate exceeds the 'costThreshold'.
21
+ - costThreshold: USD/🌼 limit (cost of the tool execution) that triggers the confirmation lock.
22
+ - costEstimator: Shows live cost estimates IN TOOL OUTPUTS (false = Silent Mode).
23
+
24
+ === 3. UI & NOTIFICATIONS (General display) ===
25
+ - statusBar: Show/Hide the floating status bar notification in the OpenCode UI.
26
+
27
+ Use 'action=update' to change these. NEVER confuse Chat Mode with Tools Protection!`,
28
+ args: {
29
+ action: tool.schema.enum(['view', 'update'])
30
+ .describe('Action to perform: "view" to see current configuration, "update" to modify it.'),
31
+ mode: tool.schema.enum(['manual', 'alwaysfree', 'pro']).optional().describe('CHAT ONLY: Dictates automatic fallback rules (manual/alwaysfree/pro).'),
32
+ costEstimator: tool.schema.boolean().optional().describe('Set to true to show cost estimates auto. Set to false for "Manual Mode" (hide estimates).'),
33
+ statusBar: tool.schema.boolean().optional().describe('Enable/disable status bar visibility (true/false)'),
34
+ costConfirmationRequired: tool.schema.boolean().optional().describe('Safety Lock: Set to true to ask user confirmation before spending money. Set to false to spend automatically.'),
35
+ enablePaidTools: tool.schema.boolean().optional().describe('Allow execution of paid or premium models using user Wallet balance (true/false)'),
36
+ costThreshold: tool.schema.number().optional().describe('Cost threshold in USD/🌼 above which confirmation is required'),
37
+ thresholdsTier: tool.schema.number().optional().describe('Warning threshold PERCENTAGE (e.g. 10 for 10%) for Free Tier.'),
38
+ thresholdsWallet: tool.schema.number().optional().describe('Warning threshold PERCENTAGE (e.g. 50 for 50%) for Wallet balance.'),
39
+ lang: tool.schema.enum(['en', 'fr', 'es', 'de', 'it']).optional().describe('Plugin language for commands and toasts (en, fr, es, de, it).')
40
+ },
41
+ async execute(args, context) {
42
+ if (args.action === 'view') {
43
+ const current = loadConfig();
44
+ // Obfuscate API key for safety in logs/UI
45
+ const safeConfig = { ...current };
46
+ if (safeConfig.apiKey && safeConfig.apiKey.length > 10) {
47
+ safeConfig.apiKey = safeConfig.apiKey.substring(0, 5) + '...[REDACTED]';
48
+ }
49
+ return `Current Plugin Configuration:\n\n${JSON.stringify(safeConfig, null, 2)}`;
50
+ }
51
+ if (args.action === 'update') {
52
+ const currentConfig = loadConfig();
53
+ const updates = {};
54
+ if (args.mode !== undefined)
55
+ updates.mode = args.mode;
56
+ if (args.costEstimator !== undefined)
57
+ updates.costEstimator = args.costEstimator;
58
+ if (args.statusBar !== undefined)
59
+ updates.statusBar = args.statusBar;
60
+ if (args.costConfirmationRequired !== undefined)
61
+ updates.costConfirmationRequired = args.costConfirmationRequired;
62
+ if (args.enablePaidTools !== undefined)
63
+ updates.enablePaidTools = args.enablePaidTools;
64
+ if (args.costThreshold !== undefined)
65
+ updates.costThreshold = args.costThreshold;
66
+ if (args.lang !== undefined)
67
+ updates.lang = args.lang;
68
+ if (args.thresholdsTier !== undefined || args.thresholdsWallet !== undefined) {
69
+ updates.thresholds = { ...currentConfig.thresholds };
70
+ if (args.thresholdsTier !== undefined)
71
+ updates.thresholds.tier = args.thresholdsTier;
72
+ if (args.thresholdsWallet !== undefined)
73
+ updates.thresholds.wallet = args.thresholdsWallet;
74
+ }
75
+ if (Object.keys(updates).length === 0) {
76
+ return t('tools.config.no_values');
77
+ }
78
+ saveConfig(updates);
79
+ const newConfig = loadConfig();
80
+ if (newConfig.statusBar) {
81
+ const changedDetails = Object.keys(updates).map(k => {
82
+ const val = updates[k];
83
+ return `${k}=${typeof val === 'object' ? JSON.stringify(val) : val}`;
84
+ }).join(", ");
85
+ let toastMsg = t('toasts.config_updated');
86
+ if (changedDetails.length > 0) {
87
+ toastMsg += ` (${changedDetails})`;
88
+ }
89
+ emitStatusToast('info', toastMsg, 'Config Update');
90
+ }
91
+ return t('tools.config.success', { updates: JSON.stringify(updates, null, 2) });
92
+ }
93
+ return "Invalid action. Use 'view' or 'update'.";
94
+ }
95
+ });
@@ -0,0 +1,2 @@
1
+ import { type ToolDefinition } from '@opencode-ai/plugin/tool';
2
+ export declare const polliGenConfirmTool: ToolDefinition;
@@ -0,0 +1,48 @@
1
+ import { tool } from '@opencode-ai/plugin/tool';
2
+ import { getPendingRequest, removePendingRequest } from './cost-guard.js';
3
+ import { t } from '../../locales/index.js';
4
+ // Import tools to re-execute them
5
+ import { polliGenImageTool } from './gen_image.js';
6
+ import { polliGenVideoTool } from './gen_video.js';
7
+ import { polliGenAudioTool } from './gen_audio.js';
8
+ import { polliGenMusicTool } from './gen_music.js';
9
+ import { polliWebSearchTool } from './polli_web_search.js';
10
+ import { formatCost } from './shared.js';
11
+ export const polliGenConfirmTool = tool({
12
+ description: t('tools.polli_gen_confirm.desc'),
13
+ args: {
14
+ request_id: tool.schema.string().describe(t('tools.polli_gen_confirm.arg_request_id')),
15
+ action: tool.schema.enum(['confirm', 'cancel']).describe(t('tools.polli_gen_confirm.arg_action')),
16
+ },
17
+ async execute(args, context) {
18
+ const reqId = args.request_id;
19
+ const action = args.action;
20
+ const pendingReq = getPendingRequest(reqId);
21
+ if (!pendingReq) {
22
+ return t('tools.polli_gen_confirm.not_found', { reqId });
23
+ }
24
+ if (action === 'cancel') {
25
+ removePendingRequest(reqId);
26
+ return t('tools.polli_gen_confirm.cancelled', { reqId });
27
+ }
28
+ const toolRegistry = {
29
+ 'polli_gen_image': polliGenImageTool,
30
+ 'polli_gen_video': polliGenVideoTool,
31
+ 'polli_gen_audio': polliGenAudioTool,
32
+ 'polli_gen_music': polliGenMusicTool,
33
+ 'polli_web_search': polliWebSearchTool,
34
+ };
35
+ const targetTool = toolRegistry[pendingReq.toolName];
36
+ if (!targetTool) {
37
+ return t('tools.polli_gen_confirm.unknown_tool', { toolName: pendingReq.toolName });
38
+ }
39
+ // Add a bypass flag to arguments
40
+ const executionArgs = { ...pendingReq.args };
41
+ const CONFIRM_SYMBOL = Symbol.for('polli_confirmed');
42
+ executionArgs[CONFIRM_SYMBOL] = true;
43
+ context.metadata({ title: t('tools.polli_gen_confirm.toast_confirmed', { toolName: pendingReq.toolName, cost: formatCost(pendingReq.estimatedCost) }) });
44
+ // Execute original tool and clean up
45
+ removePendingRequest(reqId);
46
+ return await targetTool.execute(executionArgs, context);
47
+ }
48
+ });
@@ -0,0 +1,2 @@
1
+ import { type ToolDefinition } from '@opencode-ai/plugin/tool';
2
+ export declare const polliStatusTool: ToolDefinition;
@@ -0,0 +1,31 @@
1
+ import { tool } from '@opencode-ai/plugin/tool';
2
+ import { handleUsageCommand, handleInfosCommand, handlePricingCommand, handleModelsCommand } from '../../server/commands.js';
3
+ import { t } from '../../locales/index.js';
4
+ export const polliStatusTool = tool({
5
+ description: t('tools.polli_status.desc'),
6
+ args: {
7
+ info_type: tool.schema.enum(['usage', 'pricing', 'models', 'infos', 'all'])
8
+ .describe(t('tools.polli_status.arg_info_type'))
9
+ },
10
+ async execute(args, context) {
11
+ let results = [];
12
+ const type = args.info_type || 'all';
13
+ if (type === 'usage' || type === 'all') {
14
+ const res = await handleUsageCommand(['full']);
15
+ results.push(res.response || String(res.error));
16
+ }
17
+ if (type === 'pricing' || type === 'all') {
18
+ const res = await handlePricingCommand();
19
+ results.push(res.response || String(res.error));
20
+ }
21
+ if (type === 'models' || type === 'all') {
22
+ const res = await handleModelsCommand([]);
23
+ results.push(res.response || String(res.error));
24
+ }
25
+ if (type === 'infos' || type === 'all') {
26
+ const res = await handleInfosCommand();
27
+ results.push(res.response || String(res.error));
28
+ }
29
+ return results.join('\n\n======================================================\n\n');
30
+ }
31
+ });
@@ -0,0 +1,15 @@
1
+ /**
2
+ * polli_web_search Tool - Unified Web Search via Pollinations AI
3
+ *
4
+ * Replaces: deepsearch.ts + search_crawl_scrape.ts
5
+ *
6
+ * Three modes mapped to API models:
7
+ * - rapid: Fast web search (perplexity-fast or Google Search model)
8
+ * - medium: Standard web search with sources (perplexity-fast)
9
+ * - deep: Deep research with reasoning (perplexity-reasoning)
10
+ *
11
+ * Models are resolved dynamically from /text/models registry.
12
+ * Integrated with Cost Guard and Toast notifications.
13
+ */
14
+ import { type ToolDefinition } from '@opencode-ai/plugin/tool';
15
+ export declare const polliWebSearchTool: ToolDefinition;
@@ -0,0 +1,126 @@
1
+ /**
2
+ * polli_web_search Tool - Unified Web Search via Pollinations AI
3
+ *
4
+ * Replaces: deepsearch.ts + search_crawl_scrape.ts
5
+ *
6
+ * Three modes mapped to API models:
7
+ * - rapid: Fast web search (perplexity-fast or Google Search model)
8
+ * - medium: Standard web search with sources (perplexity-fast)
9
+ * - deep: Deep research with reasoning (perplexity-reasoning)
10
+ *
11
+ * Models are resolved dynamically from /text/models registry.
12
+ * Integrated with Cost Guard and Toast notifications.
13
+ */
14
+ import { tool } from '@opencode-ai/plugin/tool';
15
+ import { getApiKey, httpsPost, formatCost, extractCostFromHeaders, getTextModels, } from './shared.js';
16
+ import { loadConfig } from '../../server/config.js';
17
+ import { checkCostControl, isTokenBased } from './cost-guard.js';
18
+ import { emitStatusToast } from '../../server/toast.js';
19
+ import { t } from '../../locales/index.js';
20
+ // ─── Cost Estimation ────────────────────────────────────────────────────
21
+ function estimateSearchCost(model) {
22
+ // Les requêtes de test montrent qu'un web-search coûte environ 0.005
23
+ return 0.005;
24
+ }
25
+ // ─── Tool Definition ──────────────────────────────────────────────────────
26
+ export const polliWebSearchTool = tool({
27
+ description: t('tools.polli_web_search.desc'),
28
+ args: {
29
+ query: tool.schema.string().describe(t('tools.polli_web_search.arg_query')),
30
+ model: tool.schema.string().describe(t('tools.polli_web_search.arg_model')),
31
+ include_sources: tool.schema.boolean().optional()
32
+ .describe(t('tools.polli_web_search.arg_include_sources')),
33
+ recency: tool.schema.enum(['any', 'day', 'week', 'month']).optional()
34
+ .describe(t('tools.polli_web_search.arg_recency')),
35
+ },
36
+ async execute(args, context) {
37
+ const apiKey = getApiKey();
38
+ if (!apiKey) {
39
+ return t('tools.polli_web_search.req_key');
40
+ }
41
+ const model = args.model;
42
+ const includeSources = args.include_sources !== false;
43
+ // Verify model
44
+ const textModels = getTextModels();
45
+ const isBetaModel = !textModels[model];
46
+ if (isBetaModel) {
47
+ emitStatusToast('warning', t('tools.polli_web_search.warn_beta', { model }), '🌐 web_search');
48
+ }
49
+ // Cost Guard
50
+ const estimatedCost = estimateSearchCost(model);
51
+ const costCheck = checkCostControl('polli_web_search', args, model, estimatedCost, 'audio'); // text models use audio category for tokens
52
+ if (!costCheck.allowed) {
53
+ return costCheck.message || t('tools.polli_web_search.blocked');
54
+ }
55
+ // Emit start toast
56
+ const config = loadConfig();
57
+ const argsStr = config.gui?.logs === 'verbose' ? `\nParameters: ${JSON.stringify(args)}` : '';
58
+ emitStatusToast('info', `🌐 Web Research [${model}]: ${args.query.substring(0, 40)}...${argsStr}`, '🌐 polli_web_search');
59
+ // Metadata
60
+ context.metadata({ title: `🔎 Res: ${args.query.substring(0, 50)}...` });
61
+ try {
62
+ // Build recency hint
63
+ const recencyHints = {
64
+ any: t('tools.polli_web_search.recency_any'),
65
+ day: t('tools.polli_web_search.recency_day'),
66
+ week: t('tools.polli_web_search.recency_week'),
67
+ month: t('tools.polli_web_search.recency_month'),
68
+ };
69
+ const systemPrompt = `You are a specialized deep web research assistant.
70
+ ${recencyHints[args.recency || 'any']}
71
+ ${includeSources ? t('tools.polli_web_search.include_sources_prompt') : ''}`;
72
+ const { data: responseData, headers: responseHeaders } = await httpsPost('https://gen.pollinations.ai/v1/chat/completions', {
73
+ model: model,
74
+ messages: [
75
+ { role: 'system', content: systemPrompt },
76
+ { role: 'user', content: args.query },
77
+ ],
78
+ }, {
79
+ 'Authorization': `Bearer ${apiKey}`,
80
+ });
81
+ const jsonData = JSON.parse(responseData.toString());
82
+ const content = jsonData.choices?.[0]?.message?.content || 'No results found';
83
+ // Extract actual cost from headers
84
+ let actualCost = estimatedCost;
85
+ if (responseHeaders) {
86
+ const costTracking = extractCostFromHeaders(responseHeaders);
87
+ if (costTracking.costUsd !== undefined) {
88
+ actualCost = costTracking.costUsd;
89
+ }
90
+ }
91
+ // Build result
92
+ const lines = [];
93
+ // Inject costWarning at top if present
94
+ if (costCheck.message && !costCheck.allowed) {
95
+ lines.push(costCheck.message);
96
+ lines.push('');
97
+ }
98
+ lines.push(`🔎 Web Research Analytics`);
99
+ lines.push(`━━━━━━━━━━━━━━━━━━`);
100
+ lines.push(t('tools.polli_web_search.result_query', { query: args.query }));
101
+ lines.push(t('tools.polli_web_search.result_model', { model: model }));
102
+ if (args.recency && args.recency !== 'any') {
103
+ lines.push(t('tools.polli_web_search.result_recency', { recency: args.recency }));
104
+ }
105
+ if (isTokenBased('audio', model)) {
106
+ const maxCost = estimatedCost * 3;
107
+ lines.push(t('tools.polli_web_search.result_cost_max', { cost: formatCost(actualCost), maxCost: formatCost(maxCost) }));
108
+ }
109
+ else {
110
+ lines.push(t('tools.polli_web_search.result_cost', { cost: formatCost(actualCost) }));
111
+ }
112
+ lines.push('');
113
+ lines.push(content);
114
+ // Emit success toast
115
+ emitStatusToast('success', t('tools.polli_web_search.toast_success', { model }), '🌐 polli_web_search');
116
+ return lines.join('\n');
117
+ }
118
+ catch (err) {
119
+ emitStatusToast('error', t('tools.polli_web_search.toast_error', { error: err.message?.substring(0, 60) }), '🌐 polli_web_search');
120
+ if (err.message?.includes('402') || err.message?.includes('Payment')) {
121
+ return t('tools.polli_web_search.insufficient_pollen');
122
+ }
123
+ return t('tools.polli_web_search.error_prefix', { error: err.message });
124
+ }
125
+ },
126
+ });
@@ -0,0 +1,7 @@
1
+ /**
2
+ * search_crawl_scrape Tool - Web Search and Content Extraction
3
+ *
4
+ * Uses perplexity-fast for quick web search with sources
5
+ */
6
+ import { type ToolDefinition } from '@opencode-ai/plugin/tool';
7
+ export declare const searchCrawlScrapeTool: ToolDefinition;
@@ -0,0 +1,85 @@
1
+ /**
2
+ * search_crawl_scrape Tool - Web Search and Content Extraction
3
+ *
4
+ * Uses perplexity-fast for quick web search with sources
5
+ */
6
+ import { tool } from '@opencode-ai/plugin/tool';
7
+ import { getApiKey, httpsPost, } from './shared.js';
8
+ // ─── Tool Definition ──────────────────────────────────────────────────────
9
+ export const searchCrawlScrapeTool = tool({
10
+ description: `Search the web and extract information quickly.
11
+
12
+ **Model:** perplexity-fast
13
+
14
+ **Features:**
15
+ - Real-time web search
16
+ - Source citations
17
+ - Quick summaries
18
+ - Current information
19
+
20
+ **Use for:**
21
+ - Quick fact lookups
22
+ - Current news/events
23
+ - Documentation search
24
+ - General web queries
25
+
26
+ **Cost:** ~0.000001 🌻 per token (very cheap)`,
27
+ args: {
28
+ query: tool.schema.string().describe('Search query'),
29
+ include_sources: tool.schema.boolean().optional()
30
+ .describe('Include source URLs in response (default: true)'),
31
+ recency: tool.schema.enum(['any', 'day', 'week', 'month']).optional()
32
+ .describe('Filter by recency (default: any)'),
33
+ },
34
+ async execute(args, context) {
35
+ const apiKey = getApiKey();
36
+ if (!apiKey) {
37
+ return `❌ Web Search nécessite une clé API Pollinations.
38
+ 🔧 Connectez votre clé avec /pollinations connect`;
39
+ }
40
+ const model = 'perplexity-fast';
41
+ const includeSources = args.include_sources !== false;
42
+ // Build recency hint
43
+ const recencyHints = {
44
+ any: '',
45
+ day: 'Focus on information from the last 24 hours. ',
46
+ week: 'Focus on information from the last week. ',
47
+ month: 'Focus on information from the last month. ',
48
+ };
49
+ // Metadata
50
+ context.metadata({ title: `🔎 Search: ${args.query.substring(0, 40)}...` });
51
+ try {
52
+ const systemPrompt = `You are a web search assistant. Provide concise, accurate answers based on web search results.
53
+ ${recencyHints[args.recency || 'any']}
54
+ ${includeSources ? 'Always include source URLs at the end of your response.' : ''}`;
55
+ const { data } = await httpsPost('https://gen.pollinations.ai/v1/chat/completions', {
56
+ model: model,
57
+ messages: [
58
+ { role: 'system', content: systemPrompt },
59
+ { role: 'user', content: args.query },
60
+ ],
61
+ max_tokens: 2000,
62
+ }, {
63
+ 'Authorization': `Bearer ${apiKey}`,
64
+ });
65
+ const jsonData = JSON.parse(data.toString());
66
+ const content = jsonData.choices?.[0]?.message?.content || 'No results found';
67
+ // Format result
68
+ const lines = [
69
+ `🔎 Web Search Results`,
70
+ `━━━━━━━━━━━━━━━━━━`,
71
+ `Query: ${args.query}`,
72
+ `Model: ${model}`,
73
+ ``,
74
+ content,
75
+ ];
76
+ return lines.join('\n');
77
+ }
78
+ catch (err) {
79
+ if (err.message?.includes('402') || err.message?.includes('Payment')) {
80
+ return `❌ Crédits insuffisants.`;
81
+ }
82
+ return `❌ Erreur Web Search: ${err.message}`;
83
+ }
84
+ },
85
+ });
@@ -0,0 +1,181 @@
1
+ /**
2
+ * Shared utilities for Pollinations API tools
3
+ *
4
+ * Updated: 2026-02-18 - Sprint 2: Dynamic ModelRegistry integration
5
+ * Hardcoded model lists replaced by ModelRegistry lookups with static fallback.
6
+ */
7
+ export interface PollinationsConfig {
8
+ apiKey?: string;
9
+ mode?: string;
10
+ }
11
+ export interface ModelInfo {
12
+ name: string;
13
+ pricing: {
14
+ currency: string;
15
+ completionImageTokens?: number;
16
+ completionVideoSeconds?: number;
17
+ completionVideoTokens?: number;
18
+ completionAudioTokens?: number;
19
+ completionAudioSeconds?: number;
20
+ promptAudioSeconds?: number;
21
+ promptTextTokens?: number;
22
+ completionTextTokens?: number;
23
+ };
24
+ paid_only?: boolean;
25
+ input_modalities?: string[];
26
+ output_modalities?: string[];
27
+ description?: string;
28
+ }
29
+ export interface GenerationResult {
30
+ success: boolean;
31
+ url?: string;
32
+ localPath?: string;
33
+ cost: number;
34
+ model: string;
35
+ error?: string;
36
+ }
37
+ export interface CostTracking {
38
+ imageTokens?: number;
39
+ videoSeconds?: number;
40
+ videoTokens?: number;
41
+ costUsd?: number;
42
+ modelUsed?: string;
43
+ requestId?: string;
44
+ }
45
+ export declare function getApiKey(): string | undefined;
46
+ export declare function hasApiKey(): boolean;
47
+ /**
48
+ * FREE Image Models (DEPRECATED - image.pollinations.ai is dead)
49
+ */
50
+ export declare const FREE_IMAGE_MODELS: {};
51
+ /**
52
+ * Dynamic Paid Image Models accessor.
53
+ * Returns data from ModelRegistry if ready, otherwise falls back to static data.
54
+ *
55
+ * BACKWARD COMPATIBLE: Same shape as the old hardcoded PAID_IMAGE_MODELS
56
+ */
57
+ export declare function getPaidImageModels(): Record<string, {
58
+ desc: string;
59
+ cost: string;
60
+ t2i: boolean;
61
+ i2i: boolean;
62
+ params: string[];
63
+ notes?: string;
64
+ }>;
65
+ /**
66
+ * Dynamic Video Models accessor.
67
+ * BACKWARD COMPATIBLE: Same shape as old VIDEO_MODELS
68
+ */
69
+ export declare function getVideoModels(): Record<string, {
70
+ desc: string;
71
+ cost: string;
72
+ t2v: boolean;
73
+ i2v: boolean;
74
+ audio: boolean;
75
+ duration: [number, number];
76
+ aspectRatios: string[];
77
+ costHeader: string;
78
+ genTime: string;
79
+ }>;
80
+ /**
81
+ * Dynamic Audio Models accessor.
82
+ * BACKWARD COMPATIBLE: Same shape as old AUDIO_MODELS
83
+ */
84
+ export declare function getAudioModels(): Record<string, {
85
+ desc: string;
86
+ type: 'tts' | 'stt' | 'both';
87
+ endpoint: string;
88
+ params: string[];
89
+ voices?: string[];
90
+ notes?: string;
91
+ }>;
92
+ /**
93
+ * Text Model accessor
94
+ * Returns text models from registry
95
+ */
96
+ export declare function getTextModels(): Record<string, {
97
+ desc: string;
98
+ }>;
99
+ /**
100
+ * Music Model accessor (backward compatible)
101
+ */
102
+ export declare function getMusicModel(): Record<string, {
103
+ desc: string;
104
+ endpoint: string;
105
+ params: string[];
106
+ duration: [number, number];
107
+ }>;
108
+ export declare function httpsGet(url: string, headers?: Record<string, string>): Promise<{
109
+ data: Buffer;
110
+ headers: Record<string, string>;
111
+ }>;
112
+ export declare function httpsPost(url: string, body: any, headers?: Record<string, string>): Promise<{
113
+ data: Buffer;
114
+ headers: Record<string, string>;
115
+ }>;
116
+ /**
117
+ * Multipart POST for file uploads (STT)
118
+ */
119
+ export declare function httpsPostMultipart(url: string, fields: Record<string, string | Buffer>, headers?: Record<string, string>): Promise<{
120
+ data: Buffer;
121
+ headers: Record<string, string>;
122
+ }>;
123
+ /**
124
+ * @deprecated Use ModelRegistry.list() directly
125
+ */
126
+ export declare function fetchModels(type: 'image' | 'audio' | 'text'): Promise<ModelInfo[]>;
127
+ /**
128
+ * @deprecated Use ModelRegistry.get() directly
129
+ */
130
+ export declare function getModelInfo(type: 'image' | 'audio' | 'text', name: string): Promise<ModelInfo | undefined>;
131
+ /**
132
+ * Extract cost tracking from response headers
133
+ */
134
+ export declare function extractCostFromHeaders(headers: Record<string, string>): CostTracking;
135
+ /**
136
+ * Fetch current Enter balance (`/account/balance`) for Real Cost calculation.
137
+ */
138
+ export declare function fetchEnterBalance(): Promise<number | null>;
139
+ /**
140
+ * Check if cost estimator is enabled in config
141
+ */
142
+ export declare function isCostEstimatorEnabled(): boolean;
143
+ export declare function per1pollen(cost: number | null): string;
144
+ export declare function estimateImageCost(model: string): number;
145
+ export declare function estimateVideoCost(model: string, duration: number): number;
146
+ export declare function estimateTtsCost(textLength: number): number;
147
+ export declare function estimateMusicCost(duration: number): number;
148
+ /**
149
+ * Empêche le Path Traversal en s'assurant que le nom de fichier
150
+ * est restreint à son nom de base et ne contient pas de caractères malveillants.
151
+ */
152
+ export declare function sanitizeFilename(filename: string): string;
153
+ /**
154
+ * Valide qu'une URL est bien HTTP ou HTTPS et empêche les schémas dangereux (file://, javascript:).
155
+ */
156
+ export declare function validateHttpUrl(urlStr: string): boolean;
157
+ export declare function ensureDir(dir: string): void;
158
+ export declare function generateFilename(type: string, model: string, ext: string): string;
159
+ export declare function getDefaultOutputDir(type: string): string;
160
+ export declare function formatCost(cost: number): string;
161
+ export declare function formatFileSize(bytes: number): string;
162
+ /**
163
+ * Check if model supports Image-to-Image
164
+ */
165
+ export declare function supportsI2I(model: string): boolean;
166
+ /**
167
+ * Check if video model supports Image-to-Video
168
+ */
169
+ export declare function supportsI2V(model: string): boolean;
170
+ /**
171
+ * Check if video model requires Image-to-Video (no T2V)
172
+ */
173
+ export declare function requiresI2V(model: string): boolean;
174
+ /**
175
+ * Validate aspect ratio for video model
176
+ */
177
+ export declare function validateAspectRatio(model: string, ratio: string): boolean;
178
+ /**
179
+ * Get valid duration range for video model
180
+ */
181
+ export declare function getDurationRange(model: string): [number, number];