opencode-pollinations-plugin 6.1.0-beta.9 → 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 -307
  41. package/dist/server/quota.d.ts +2 -0
  42. package/dist/server/quota.js +89 -86
  43. package/dist/server/scripts/pollinations_pricing.d.ts +8 -0
  44. package/dist/server/scripts/pollinations_pricing.js +246 -0
  45. package/dist/server/scripts/test_cost_endpoints.d.ts +1 -0
  46. package/dist/server/scripts/test_cost_endpoints.js +61 -0
  47. package/dist/server/scripts/test_dynamic_pricing.d.ts +1 -0
  48. package/dist/server/scripts/test_dynamic_pricing.js +39 -0
  49. package/dist/server/scripts/test_freetier_audit.d.ts +11 -0
  50. package/dist/server/scripts/test_freetier_audit.js +215 -0
  51. package/dist/server/scripts/test_parallel_cost.d.ts +1 -0
  52. package/dist/server/scripts/test_parallel_cost.js +104 -0
  53. package/dist/server/toast.d.ts +7 -1
  54. package/dist/server/toast.js +43 -10
  55. package/dist/tools/design/gen_diagram.d.ts +2 -0
  56. package/dist/tools/design/gen_diagram.js +94 -0
  57. package/dist/tools/design/gen_palette.d.ts +2 -0
  58. package/dist/tools/design/gen_palette.js +182 -0
  59. package/dist/tools/design/gen_qrcode.d.ts +2 -0
  60. package/dist/tools/design/gen_qrcode.js +50 -0
  61. package/dist/tools/ffmpeg.d.ts +24 -0
  62. package/dist/tools/ffmpeg.js +54 -0
  63. package/dist/tools/index.d.ts +25 -0
  64. package/dist/tools/index.js +86 -0
  65. package/dist/tools/pollinations/beta_discovery.d.ts +9 -0
  66. package/dist/tools/pollinations/beta_discovery.js +201 -0
  67. package/dist/tools/pollinations/cost-guard.d.ts +38 -0
  68. package/dist/tools/pollinations/cost-guard.js +136 -0
  69. package/dist/tools/pollinations/deepsearch.d.ts +7 -0
  70. package/dist/tools/pollinations/deepsearch.js +80 -0
  71. package/dist/tools/pollinations/gen_audio.d.ts +18 -0
  72. package/dist/tools/pollinations/gen_audio.js +220 -0
  73. package/dist/tools/pollinations/gen_image.d.ts +11 -0
  74. package/dist/tools/pollinations/gen_image.js +211 -0
  75. package/dist/tools/pollinations/gen_music.d.ts +14 -0
  76. package/dist/tools/pollinations/gen_music.js +157 -0
  77. package/dist/tools/pollinations/gen_video.d.ts +16 -0
  78. package/dist/tools/pollinations/gen_video.js +249 -0
  79. package/dist/tools/pollinations/polli_config.d.ts +2 -0
  80. package/dist/tools/pollinations/polli_config.js +95 -0
  81. package/dist/tools/pollinations/polli_gen_confirm.d.ts +2 -0
  82. package/dist/tools/pollinations/polli_gen_confirm.js +48 -0
  83. package/dist/tools/pollinations/polli_status.d.ts +2 -0
  84. package/dist/tools/pollinations/polli_status.js +31 -0
  85. package/dist/tools/pollinations/polli_web_search.d.ts +15 -0
  86. package/dist/tools/pollinations/polli_web_search.js +126 -0
  87. package/dist/tools/pollinations/search_crawl_scrape.d.ts +7 -0
  88. package/dist/tools/pollinations/search_crawl_scrape.js +85 -0
  89. package/dist/tools/pollinations/shared.d.ts +181 -0
  90. package/dist/tools/pollinations/shared.js +758 -0
  91. package/dist/tools/pollinations/test_estimators.d.ts +1 -0
  92. package/dist/tools/pollinations/test_estimators.js +22 -0
  93. package/dist/tools/pollinations/transcribe_audio.d.ts +13 -0
  94. package/dist/tools/pollinations/transcribe_audio.js +171 -0
  95. package/dist/tools/power/extract_audio.d.ts +2 -0
  96. package/dist/tools/power/extract_audio.js +179 -0
  97. package/dist/tools/power/extract_frames.d.ts +2 -0
  98. package/dist/tools/power/extract_frames.js +237 -0
  99. package/dist/tools/power/file_to_url.d.ts +2 -0
  100. package/dist/tools/power/file_to_url.js +217 -0
  101. package/dist/tools/power/remove_background.d.ts +2 -0
  102. package/dist/tools/power/remove_background.js +404 -0
  103. package/dist/tools/power/rmbg_keys.d.ts +2 -0
  104. package/dist/tools/power/rmbg_keys.js +79 -0
  105. package/dist/tools/shared.d.ts +30 -0
  106. package/dist/tools/shared.js +80 -0
  107. package/package.json +10 -4
  108. package/dist/server/models-seed.d.ts +0 -18
  109. package/dist/server/models-seed.js +0 -55
@@ -1,31 +1,16 @@
1
- /**
2
- * generate-config.ts - v6.0 Simplified
3
- *
4
- * Single endpoint: gen.pollinations.ai/text/models
5
- * No more Free tier, no cache ETag, no prefixes
6
- */
7
1
  import * as https from 'https';
8
2
  import * as fs from 'fs';
9
- import { loadConfig } from './config.js';
3
+ import * as path from 'path';
4
+ import { loadConfig, CONFIG_FILE } from './config.js';
5
+ import { log as logSystem } from './logger.js';
10
6
  // --- LOGGING ---
11
- const LOG_FILE = '/tmp/opencode_pollinations_config.log';
12
7
  function log(msg) {
13
- try {
14
- const ts = new Date().toISOString();
15
- if (!fs.existsSync(LOG_FILE))
16
- fs.writeFileSync(LOG_FILE, '');
17
- fs.appendFileSync(LOG_FILE, `[ConfigGen v6.0] ${ts} ${msg}\n`);
18
- }
19
- catch (e) { }
8
+ logSystem(`[ConfigGen] ${msg}`);
20
9
  }
21
- // --- NETWORK HELPER ---
10
+ // Fetch Helper
22
11
  function fetchJson(url, headers = {}) {
23
12
  return new Promise((resolve, reject) => {
24
- const finalHeaders = {
25
- ...headers,
26
- 'User-Agent': 'Mozilla/5.0 (compatible; OpenCode-Pollinations/6.0; +https://opencode.ai)'
27
- };
28
- const req = https.get(url, { headers: finalHeaders }, (res) => {
13
+ const req = https.get(url, { headers }, (res) => {
29
14
  let data = '';
30
15
  res.on('data', chunk => data += chunk);
31
16
  res.on('end', () => {
@@ -35,7 +20,7 @@ function fetchJson(url, headers = {}) {
35
20
  }
36
21
  catch (e) {
37
22
  log(`JSON Parse Error for ${url}: ${e}`);
38
- resolve([]);
23
+ resolve([]); // Fail safe -> empty list
39
24
  }
40
25
  });
41
26
  });
@@ -43,132 +28,205 @@ function fetchJson(url, headers = {}) {
43
28
  log(`Network Error for ${url}: ${e.message}`);
44
29
  reject(e);
45
30
  });
46
- req.setTimeout(15000, () => {
31
+ req.setTimeout(5000, () => {
47
32
  req.destroy();
48
33
  reject(new Error('Timeout'));
49
34
  });
50
35
  });
51
36
  }
52
- // --- GENERATOR LOGIC ---
53
- export async function generatePollinationsConfig(forceApiKey) {
37
+ function formatName(id, censored = true) {
38
+ let clean = id.replace(/^pollinations\//, '').replace(/-/g, ' ');
39
+ clean = clean.replace(/\b\w/g, l => l.toUpperCase());
40
+ if (!censored)
41
+ clean += " (Uncensored)";
42
+ return clean;
43
+ }
44
+ // --- MAIN GENERATOR logic ---
45
+ // --- MAIN GENERATOR logic ---
46
+ export async function generatePollinationsConfig(forceApiKey, forceStrict = false) {
54
47
  const config = loadConfig();
55
48
  const modelsOutput = [];
49
+ log(`Starting Configuration (V5.1.22 Hot-Reload)...`);
50
+ // Use forced key (from Hook) or cached key
56
51
  const effectiveKey = forceApiKey || config.apiKey;
57
- log(`Starting Configuration v6.0...`);
58
- log(`API Key present: ${!!effectiveKey}`);
59
- // 1. ALWAYS add "connect" placeholder model
52
+ // 0. CONNECT MODEL (Always present, first in list)
60
53
  modelsOutput.push({
61
- id: 'connect',
62
- name: '🔑 Connect your Pollinations Account',
63
- modalities: { input: ['text'], output: ['text'] },
64
- tool_call: false
54
+ id: 'pollinations/connect',
55
+ name: '🌸 Pollinations Guide & Connexion',
56
+ object: 'model',
57
+ variants: {}
65
58
  });
66
- // 2. If no API key, return only connect placeholder
67
- if (!effectiveKey || effectiveKey.length < 5 || effectiveKey === 'dummy') {
68
- log('No API key configured. Returning connect placeholder only.');
69
- return modelsOutput;
70
- }
71
- // 3. Fetch models from Enterprise endpoint
59
+ // 1. FREE UNIVERSE
72
60
  try {
73
- log('Fetching models from gen.pollinations.ai/text/models...');
74
- const rawList = await fetchJson('https://gen.pollinations.ai/text/models', {
75
- 'Authorization': `Bearer ${effectiveKey}`
76
- });
77
- const modelsList = Array.isArray(rawList) ? rawList : (rawList.data || []);
78
- log(`Received ${modelsList.length} models from API`);
79
- modelsList.forEach((m) => {
80
- // Skip models without tools support (except nomnom - special handling)
81
- if (m.tools === false && m.name !== 'nomnom') {
82
- log(`Skipping ${m.name} (no tools)`);
83
- return;
84
- }
85
- const mapped = mapModel(m);
61
+ // Switch to main models endpoint (User provided curl confirms it has 'description')
62
+ const freeList = await fetchJson('https://text.pollinations.ai/models');
63
+ const list = Array.isArray(freeList) ? freeList : (freeList.data || []);
64
+ list.forEach((m) => {
65
+ const mapped = mapModel(m, 'free/', '');
86
66
  modelsOutput.push(mapped);
87
67
  });
88
- log(`Total models registered: ${modelsOutput.length}`);
89
- log(`Model IDs: ${modelsOutput.map(m => m.id).join(', ')}`);
68
+ log(`Fetched ${modelsOutput.length} Free models.`);
90
69
  }
91
70
  catch (e) {
92
- log(`Error fetching models: ${e.message}`);
93
- // Return connect placeholder only on error
71
+ log(`Error fetching Free models: ${e}`);
72
+ // Fallback Robust (Offline support)
73
+ modelsOutput.push({ id: "free/mistral", name: "Mistral Nemo (Fallback)", object: "model", variants: {} });
74
+ modelsOutput.push({ id: "free/openai", name: "OpenAI (Fallback)", object: "model", variants: {} });
75
+ modelsOutput.push({ id: "free/gemini", name: "Gemini Flash (Fallback)", object: "model", variants: {} });
76
+ }
77
+ // ALIAS Removed for Clean Config
78
+ // const hasGeminiAlias = modelsOutput.find(m => m.id === 'pollinations/free/gemini');
79
+ // if (!hasGeminiAlias) {
80
+ // modelsOutput.push({ id: "pollinations/free/gemini", name: "[Free] Gemini Flash (Alias)", object: "model", variants: {} });
81
+ // }
82
+ // 2. ENTERPRISE UNIVERSE
83
+ if (effectiveKey && effectiveKey.length > 5 && effectiveKey !== 'dummy') {
84
+ try {
85
+ // Use /text/models for full metadata (input_modalities, tools, reasoning, pricing)
86
+ const enterListRaw = await fetchJson('https://gen.pollinations.ai/text/models', {
87
+ 'Authorization': `Bearer ${effectiveKey}`
88
+ });
89
+ const enterList = Array.isArray(enterListRaw) ? enterListRaw : (enterListRaw.data || []);
90
+ const paidModels = [];
91
+ enterList.forEach((m) => {
92
+ if (m.tools === false)
93
+ return;
94
+ const mapped = mapModel(m, 'enter/', '');
95
+ modelsOutput.push(mapped);
96
+ if (m.paid_only) {
97
+ paidModels.push(mapped.id.replace('enter/', '')); // Store bare ID "gemini-large"
98
+ }
99
+ });
100
+ log(`Total models (Free+Pro): ${modelsOutput.length}`);
101
+ // Save Paid Models List for Proxy
102
+ try {
103
+ const paidListPath = path.join(config.gui ? path.dirname(CONFIG_FILE) : '/tmp', 'pollinations-paid-models.json');
104
+ // Ensure dir exists (re-use config dir logic from config.ts if possible, or just assume it exists since config loaded)
105
+ if (fs.existsSync(path.dirname(paidListPath))) {
106
+ fs.writeFileSync(paidListPath, JSON.stringify(paidModels));
107
+ }
108
+ }
109
+ catch (e) {
110
+ log(`Error saving paid models list: ${e}`);
111
+ }
112
+ }
113
+ catch (e) {
114
+ log(`Error fetching Enterprise models: ${e}`);
115
+ // STRICT MODE (Validation): Do not return fake fallback models.
116
+ if (forceStrict)
117
+ throw e;
118
+ // Fallback Robust for Enterprise (User has Key but discovery failed)
119
+ modelsOutput.push({ id: "enter/gpt-4o", name: "GPT-4o (Fallback)", object: "model", variants: {} });
120
+ // ...
121
+ modelsOutput.push({ id: "enter/claude-3-5-sonnet", name: "Claude 3.5 Sonnet (Fallback)", object: "model", variants: {} });
122
+ modelsOutput.push({ id: "enter/deepseek-reasoner", name: "DeepSeek R1 (Fallback)", object: "model", variants: {} });
123
+ }
94
124
  }
95
125
  return modelsOutput;
96
126
  }
97
- // --- UTILS ---
127
+ // --- CAPABILITY ICONS ---
98
128
  function getCapabilityIcons(raw) {
99
129
  const icons = [];
100
- // Vision: check both input_modalities and legacy vision flag
101
- if (raw.input_modalities?.includes('image') || raw.vision === true) {
130
+ // Vision: accepts images
131
+ if (raw.input_modalities?.includes('image'))
102
132
  icons.push('👁️');
103
- }
104
- // Audio input
105
- if (raw.input_modalities?.includes('audio') || raw.audio === true) {
133
+ // Audio Input
134
+ if (raw.input_modalities?.includes('audio'))
106
135
  icons.push('🎙️');
107
- }
108
- // Audio output
109
- if (raw.output_modalities?.includes('audio')) {
136
+ // Audio Output
137
+ if (raw.output_modalities?.includes('audio'))
110
138
  icons.push('🔊');
111
- }
112
- // Reasoning
113
- if (raw.reasoning === true) {
139
+ // Reasoning capability
140
+ if (raw.reasoning === true)
114
141
  icons.push('🧠');
115
- }
116
- // Search capability
117
- if (raw.description?.toLowerCase().includes('search') || raw.name?.includes('search')) {
142
+ // Web Search (from description)
143
+ if (raw.description?.toLowerCase().includes('search') ||
144
+ raw.name?.includes('search') ||
145
+ raw.name?.includes('perplexity')) {
118
146
  icons.push('🔍');
119
147
  }
120
- // Tools
121
- if (raw.tools === true) {
122
- icons.push('🛠️');
123
- }
124
- // Paid only
125
- if (raw.paid_only === true) {
126
- icons.push('💎');
127
- }
148
+ // Tool/Function calling
149
+ if (raw.tools === true)
150
+ icons.push('💻');
128
151
  return icons.length > 0 ? ` ${icons.join('')}` : '';
129
152
  }
130
- function formatName(id) {
131
- let clean = id.replace(/-/g, ' ');
132
- clean = clean.replace(/\b\w/g, l => l.toUpperCase());
133
- return clean;
134
- }
135
- function mapModel(raw) {
136
- const rawId = raw.name;
137
- // Build display name
153
+ // --- MAPPING ENGINE ---
154
+ function mapModel(raw, prefix, namePrefix) {
155
+ const rawId = raw.id || raw.name;
156
+ const fullId = prefix + rawId; // ex: "free/gemini" or "enter/nomnom" (prefix passed is "enter/")
138
157
  let baseName = raw.description;
139
158
  if (!baseName || baseName === rawId) {
140
- baseName = formatName(rawId);
159
+ baseName = formatName(rawId, raw.censored !== false);
141
160
  }
142
- // Truncate after first " - "
161
+ // CLEANUP: Simple Truncation Rule (Requested by User)
162
+ // "Start from left, find ' - ', delete everything after."
143
163
  if (baseName && baseName.includes(' - ')) {
144
164
  baseName = baseName.split(' - ')[0].trim();
145
165
  }
166
+ // Gérer les icônes pour paid_only et modèles FREE
167
+ let paidPrefix = '';
168
+ let freeSuffix = '';
169
+ if (raw.paid_only) {
170
+ paidPrefix = '💎 '; // Icône diamant devant les modèles payants
171
+ }
172
+ if (prefix === 'free/') {
173
+ freeSuffix = ' (free)'; // Suffixe pour l'univers FREE
174
+ }
175
+ // Get capability icons from API metadata
146
176
  const capabilityIcons = getCapabilityIcons(raw);
147
- const finalName = `${baseName}${capabilityIcons}`;
148
- // Determine modalities for OpenCode
149
- const inputMods = raw.input_modalities || ['text'];
150
- const outputMods = raw.output_modalities || ['text'];
177
+ const finalName = `${paidPrefix}${baseName}${capabilityIcons}${freeSuffix}`;
151
178
  const modelObj = {
152
- id: rawId, // No prefix! Direct model ID
179
+ id: fullId,
153
180
  name: finalName,
181
+ object: 'model',
182
+ variants: {},
183
+ // Declare modalities for OpenCode vision support
154
184
  modalities: {
155
- input: inputMods,
156
- output: outputMods
157
- },
158
- tool_call: raw.tools === true && rawId !== 'nomnom' // NomNom: no tools
185
+ input: raw.input_modalities || ['text'],
186
+ output: raw.output_modalities || ['text']
187
+ }
159
188
  };
160
- // Model-specific limits
189
+ // --- ENRICHISSEMENT ---
190
+ if (raw.reasoning === true || rawId.includes('thinking') || rawId.includes('reasoning')) {
191
+ modelObj.variants = { ...modelObj.variants, high_reasoning: { options: { reasoningEffort: "high", budgetTokens: 16000 } } };
192
+ }
193
+ if (rawId.includes('gemini') && !rawId.includes('fast')) {
194
+ if (!modelObj.variants.high_reasoning && (rawId === 'gemini' || rawId === 'gemini-large')) {
195
+ modelObj.variants.high_reasoning = { options: { reasoningEffort: "high", budgetTokens: 16000 } };
196
+ }
197
+ }
198
+ if (rawId.includes('claude') || rawId.includes('mistral') || rawId.includes('llama')) {
199
+ modelObj.variants.safe_tokens = { options: { maxTokens: 8000 } };
200
+ }
201
+ // NOVA FIX: Bedrock limit ~10k (User reported error > 10000)
202
+ // We MUST set the limit on the model object itself so OpenCode respects it by default.
161
203
  if (rawId.includes('nova')) {
162
- modelObj.limit = { output: 8000, context: 128000 };
204
+ modelObj.limit = {
205
+ output: 8000,
206
+ context: 128000 // Nova Micro/Lite/Pro usually 128k
207
+ };
208
+ // Also keep variant just in case
209
+ modelObj.variants.bedrock_safe = { options: { maxTokens: 8000 } };
210
+ }
211
+ // BEDROCK/ENTERPRISE LIMITS (Chickytutor only)
212
+ if (rawId.includes('chickytutor')) {
213
+ modelObj.limit = {
214
+ output: 8192,
215
+ context: 128000
216
+ };
163
217
  }
164
- if (rawId === 'nomnom') {
165
- modelObj.limit = { output: 2048, context: 32768 };
166
- modelObj.tool_call = false; // NomNom is a router, no external tools
218
+ // NOMNOM FIX: User reported error if max_tokens is missing.
219
+ // Also it is a 'Gemini-scrape' model, so we treat it similar to Gemini but with strict limit.
220
+ if (rawId.includes('nomnom') || rawId.includes('scrape')) {
221
+ modelObj.limit = {
222
+ output: 2048, // User used 1500 successfully
223
+ context: 32768
224
+ };
167
225
  }
168
- if (rawId.includes('chicky') || rawId.includes('mistral')) {
169
- modelObj.limit = { output: 4096, context: 8192 };
170
- modelObj.options = { maxTokens: 4096 };
226
+ if (rawId.includes('fast') || rawId.includes('flash') || rawId.includes('lite')) {
227
+ if (!rawId.includes('gemini')) {
228
+ modelObj.variants.speed = { options: { thinking: { disabled: true } } };
229
+ }
171
230
  }
172
- log(`[Mapped] ${modelObj.id} → ${modelObj.name} | tools=${modelObj.tool_call} | modalities=${JSON.stringify(modelObj.modalities)}`);
173
231
  return modelObj;
174
232
  }
@@ -1 +1,2 @@
1
- export {};
1
+ import type { Plugin } from '@opencode-ai/plugin';
2
+ export declare const plugin: Plugin;
@@ -4,7 +4,9 @@ import * as path from 'path';
4
4
  import { getAggregatedModels } from './pollinations-api.js';
5
5
  import { loadConfig, saveConfig } from './config.js';
6
6
  import { handleChatCompletion } from './proxy.js';
7
- const LOG_FILE = path.join(process.env.HOME || '/tmp', '.config/opencode/plugins/pollinations-v3.log');
7
+ import { createCommandHooks, setClientForCommands, checkKeyPermissions } from './commands.js';
8
+ import { fileURLToPath } from 'url';
9
+ const LOG_FILE = path.join(process.env.HOME || '/tmp', '.config/opencode/plugins/pollinations-v6.log');
8
10
  // Simple file logger
9
11
  function log(msg) {
10
12
  const ts = new Date().toISOString();
@@ -14,170 +16,143 @@ function log(msg) {
14
16
  }
15
17
  fs.appendFileSync(LOG_FILE, `[${ts}] ${msg}\n`);
16
18
  }
17
- catch (e) {
18
- // silent fail
19
- }
20
- }
21
- // CRASH GUARD
22
- const CRASH_LOG = '/tmp/opencode_pollinations_crash.log';
23
- process.on('uncaughtException', (err) => {
24
- try {
25
- const msg = `[CRASH] Uncaught Exception: ${err.message}\n${err.stack}\n`;
26
- fs.appendFileSync(CRASH_LOG, msg);
27
- console.error(msg);
28
- }
29
19
  catch (e) { }
30
- process.exit(1);
31
- });
32
- process.on('unhandledRejection', (reason, promise) => {
33
- try {
34
- const msg = `[CRASH] Unhandled Rejection: ${reason}\n`;
35
- fs.appendFileSync(CRASH_LOG, msg);
36
- }
37
- catch (e) { }
38
- });
39
- const server = http.createServer(async (req, res) => {
40
- log(`${req.method} ${req.url}`);
41
- // CORS Headers
42
- res.setHeader('Access-Control-Allow-Origin', '*');
43
- res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
44
- res.setHeader('Access-Control-Allow-Headers', '*');
45
- if (req.method === 'OPTIONS') {
46
- res.writeHead(204);
47
- res.end();
20
+ }
21
+ const PORT = parseInt(process.env.POLLINATIONS_PORT || '10001', 10);
22
+ let serverInstance = null;
23
+ // --- SERVER LOGIC ---
24
+ function startServer() {
25
+ if (serverInstance)
48
26
  return;
49
- }
50
- // AUTH ENDPOINT (Kept for compatibility, though Native Auth is preferred)
51
- if (req.method === 'POST' && req.url === '/v1/auth') {
52
- const chunks = [];
53
- req.on('data', chunk => chunks.push(chunk));
54
- req.on('end', async () => {
55
- try {
56
- const body = JSON.parse(Buffer.concat(chunks).toString());
57
- if (body && body.apiKey) {
58
- saveConfig({ apiKey: body.apiKey, mode: 'pro' });
59
- log(`[AUTH] Key saved via Server Endpoint`);
60
- res.writeHead(200, { 'Content-Type': 'application/json' });
61
- res.end(JSON.stringify({ status: "ok" }));
27
+ serverInstance = http.createServer(async (req, res) => {
28
+ log(`${req.method} ${req.url}`);
29
+ // CORS Headers
30
+ res.setHeader('Access-Control-Allow-Origin', '*');
31
+ res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
32
+ res.setHeader('Access-Control-Allow-Headers', '*');
33
+ if (req.method === 'OPTIONS') {
34
+ res.writeHead(204);
35
+ res.end();
36
+ return;
37
+ }
38
+ // AUTH ENDPOINT
39
+ if (req.method === 'POST' && req.url === '/v1/auth') {
40
+ const chunks = [];
41
+ req.on('data', chunk => chunks.push(chunk));
42
+ req.on('end', async () => {
43
+ try {
44
+ const body = JSON.parse(Buffer.concat(chunks).toString());
45
+ if (body && body.apiKey) {
46
+ saveConfig({ apiKey: body.apiKey, mode: 'pro' });
47
+ log(`[AUTH] Key saved via Server Endpoint`);
48
+ res.writeHead(200, { 'Content-Type': 'application/json' });
49
+ res.end(JSON.stringify({ status: "ok" }));
50
+ }
51
+ else {
52
+ res.writeHead(400);
53
+ res.end(JSON.stringify({ error: "Missing apiKey" }));
54
+ }
62
55
  }
63
- else {
64
- res.writeHead(400);
65
- res.end(JSON.stringify({ error: "Missing apiKey" }));
56
+ catch (e) {
57
+ log(`[AUTH] Error: ${e}`);
58
+ res.writeHead(500);
59
+ res.end(JSON.stringify({ error: String(e) }));
66
60
  }
67
- }
68
- catch (e) {
69
- log(`[AUTH] Error: ${e}`);
70
- res.writeHead(500);
71
- res.end(JSON.stringify({ error: String(e) }));
72
- }
73
- });
74
- return;
75
- }
76
- if (req.method === 'GET' && req.url === '/health') {
77
- const config = loadConfig();
78
- res.writeHead(200, { 'Content-Type': 'application/json' });
79
- res.end(JSON.stringify({
80
- status: "ok",
81
- version: "v3.0.0-phase3",
82
- mode: config.mode,
83
- hasKey: !!config.apiKey
84
- }));
85
- return;
86
- }
87
- if (req.method === 'GET' && req.url === '/v1/models') {
88
- try {
89
- const models = await getAggregatedModels();
90
- res.writeHead(200, { 'Content-Type': 'application/json' });
91
- res.end(JSON.stringify(models));
61
+ });
62
+ return;
92
63
  }
93
- catch (e) {
94
- log(`Error fetching models: ${e}`);
95
- res.writeHead(500, { 'Content-Type': 'application/json' });
96
- res.end(JSON.stringify({ error: "Failed to fetch models" }));
64
+ if (req.method === 'GET' && req.url === '/health') {
65
+ const config = loadConfig();
66
+ res.writeHead(200, { 'Content-Type': 'application/json' });
67
+ res.end(JSON.stringify({
68
+ status: "ok",
69
+ version: "v6.0.0-beta.99",
70
+ mode: config.mode,
71
+ hasKey: !!config.apiKey
72
+ }));
73
+ return;
97
74
  }
98
- return;
99
- }
100
- if (req.method === 'POST' && req.url === '/v1/chat/completions') {
101
- // Accumulate body for the proxy
102
- const chunks = [];
103
- req.on('data', chunk => chunks.push(chunk));
104
- req.on('end', async () => {
75
+ if (req.method === 'GET' && req.url === '/v1/models') {
105
76
  try {
106
- const bodyRaw = Buffer.concat(chunks).toString();
107
- await handleChatCompletion(req, res, bodyRaw);
77
+ const models = await getAggregatedModels();
78
+ res.writeHead(200, { 'Content-Type': 'application/json' });
79
+ res.end(JSON.stringify(models));
108
80
  }
109
81
  catch (e) {
110
- log(`Error in chat handler: ${e}`);
82
+ log(`Error fetching models: ${e}`);
111
83
  res.writeHead(500, { 'Content-Type': 'application/json' });
112
- res.end(JSON.stringify({ error: "Internal Server Error in Chat Handler" }));
84
+ res.end(JSON.stringify({ error: "Failed to fetch models" }));
113
85
  }
114
- });
115
- return;
116
- }
117
- res.writeHead(404);
118
- res.end("Not Found");
119
- });
120
- const PORT = parseInt(process.env.POLLINATIONS_PORT || '10001', 10);
121
- // ANTI-ZOMBIE (Visible Logs Restored)
122
- try {
123
- const { execSync } = require('child_process');
124
- try {
125
- console.log(`[POLLINATIONS] Checking port ${PORT}...`);
126
- execSync(`fuser -k ${PORT}/tcp || true`);
127
- console.log(`[POLLINATIONS] Port ${PORT} cleared.`);
128
- }
129
- catch (e) {
130
- console.log(`[POLLINATIONS] Port check skipped (cmd missing?)`);
131
- }
132
- }
133
- catch (e) { }
134
- // LIFECYCLE DEBUG (Sync Write)
135
- const LIFE_LOG = '/tmp/POLLI_LIFECYCLE.log';
136
- const LOC_LOG = '/tmp/POLLI_LOCATION.log'; // NEW: Track source location
137
- try {
138
- fs.appendFileSync(LIFE_LOG, `[${new Date().toISOString()}] [STARTUP] PID:${process.pid} Initializing...\n`);
139
- fs.writeFileSync(LOC_LOG, `[${new Date().toISOString()}] RUNNING FROM: ${__filename}\n`);
140
- }
141
- catch (e) { }
142
- process.on('exit', (code) => {
86
+ return;
87
+ }
88
+ if (req.method === 'POST' && req.url === '/v1/chat/completions') {
89
+ const chunks = [];
90
+ req.on('data', chunk => chunks.push(chunk));
91
+ req.on('end', async () => {
92
+ try {
93
+ const bodyRaw = Buffer.concat(chunks).toString();
94
+ await handleChatCompletion(req, res, bodyRaw);
95
+ }
96
+ catch (e) {
97
+ log(`Error in chat handler: ${e}`);
98
+ res.writeHead(500, { 'Content-Type': 'application/json' });
99
+ res.end(JSON.stringify({ error: "Internal Server Error in Chat Handler" }));
100
+ }
101
+ });
102
+ return;
103
+ }
104
+ res.writeHead(404);
105
+ res.end("Not Found");
106
+ });
107
+ // ANTI-ZOMBIE
143
108
  try {
144
- fs.appendFileSync(LIFE_LOG, `[${new Date().toISOString()}] [EXIT] PID:${process.pid} Exiting with code ${code}\n`);
109
+ const { execSync } = require('child_process');
110
+ try {
111
+ console.log(`[POLLINATIONS] Checking port ${PORT}...`);
112
+ execSync(`fuser -k ${PORT}/tcp || true`);
113
+ console.log(`[POLLINATIONS] Port ${PORT} cleared.`);
114
+ }
115
+ catch (e) { }
145
116
  }
146
117
  catch (e) { }
147
- });
148
- // STARTUP CHECK: Re-validate Key (in case of upgrade/config drift)
149
- import { checkKeyPermissions } from './commands.js';
150
- (async () => {
151
- const config = loadConfig();
152
- if (config.apiKey) {
153
- try {
154
- console.log('Pollinations Plugin: Verifying API Key on startup...');
155
- const check = await checkKeyPermissions(config.apiKey);
156
- if (!check.ok) {
157
- console.warn(`Pollinations Plugin: Limited Key Detected on Startup (${check.reason}). Enforcing Manual Mode.`);
158
- saveConfig({
159
- apiKey: config.apiKey,
160
- mode: 'manual',
161
- keyHasAccessToProfile: false
162
- });
163
- }
164
- else {
165
- if (config.keyHasAccessToProfile === false) {
166
- saveConfig({ apiKey: config.apiKey, keyHasAccessToProfile: true });
118
+ // STARTUP CHECK
119
+ (async () => {
120
+ const config = loadConfig();
121
+ if (config.apiKey) {
122
+ try {
123
+ console.log('Pollinations Plugin: Verifying API Key on startup...');
124
+ const check = await checkKeyPermissions(config.apiKey);
125
+ if (!check.ok) {
126
+ console.warn(`Pollinations Plugin: Limited Key Detected on Startup (${check.reason}). Enforcing Manual Mode.`);
127
+ saveConfig({ apiKey: config.apiKey, mode: 'manual', keyHasAccessToProfile: false });
128
+ }
129
+ else {
130
+ if (config.keyHasAccessToProfile === false)
131
+ saveConfig({ apiKey: config.apiKey, keyHasAccessToProfile: true });
167
132
  }
168
133
  }
134
+ catch (e) {
135
+ console.error('Pollinations Plugin: Startup Check Failed:', e);
136
+ }
169
137
  }
170
- catch (e) {
171
- console.error('Pollinations Plugin: Startup Check Failed:', e);
172
- }
173
- }
174
- server.listen(PORT, '127.0.0.1', () => {
138
+ })();
139
+ serverInstance.listen(PORT, '127.0.0.1', () => {
175
140
  const url = `http://127.0.0.1:${PORT}`;
176
- log(`[SERVER] Started V3 Phase 3 (Auth Enabled) on port ${PORT}`);
177
- try {
178
- fs.appendFileSync(LIFE_LOG, `[${new Date().toISOString()}] [LISTEN] PID:${process.pid} Listening on ${PORT}\n`);
179
- }
180
- catch (e) { }
181
- console.log(`POLLINATIONS_V3_URL=${url}`);
141
+ log(`[SERVER] Started V6 (Plugin Mode) on port ${PORT}`);
142
+ console.log(`POLLINATIONS_V6_URL=${url}`);
182
143
  });
183
- })();
144
+ }
145
+ // --- OPENCODE PLUGIN EXPORT ---
146
+ export const plugin = async ({ client }) => {
147
+ // 1. Inject Client for Command Handling
148
+ setClientForCommands(client);
149
+ // 2. Start Local Proxy Server
150
+ startServer();
151
+ // 3. Register Hooks (Commands, TUI, etc.)
152
+ return createCommandHooks();
153
+ };
154
+ // --- STANDALONE SUPPORT ---
155
+ // If run directly via `node dist/index.js`, start server immediately
156
+ if (process.argv[1] === fileURLToPath(import.meta.url)) {
157
+ startServer();
158
+ }
@@ -0,0 +1,8 @@
1
+ export declare function log(msg: string, file?: string): void;
2
+ export declare function logApi(msg: string): void;
3
+ export declare function logToast(msg: string): void;
4
+ export declare const LOG_FILES: {
5
+ main: string;
6
+ api: string;
7
+ toast: string;
8
+ };