opencode-pollinations-plugin 5.1.18 → 5.1.19

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/dist/index.js CHANGED
@@ -4,37 +4,27 @@ import { execSync } from 'child_process';
4
4
  import { generatePollinationsConfig } from './server/generate-config.js';
5
5
  import { loadConfig } from './server/config.js';
6
6
  import { handleChatCompletion } from './server/proxy.js';
7
- import { createToastHooks, setGlobalClient, emitStatusToast } from './server/toast.js';
7
+ import { createToastHooks, setGlobalClient } from './server/toast.js';
8
8
  import { createStatusHooks } from './server/status.js';
9
9
  import { createCommandHooks } from './server/commands.js';
10
- const LIFE_LOG = '/tmp/POLLI_LIFECYCLE.log';
11
- const LOC_LOG = '/tmp/POLLI_LOCATION.log';
10
+ const LOG_FILE = '/tmp/opencode_pollinations_v4.log';
12
11
  function log(msg) {
13
12
  try {
14
- fs.appendFileSync(LIFE_LOG, `[${new Date().toISOString()}] ${msg}\n`);
13
+ fs.appendFileSync(LOG_FILE, `[${new Date().toISOString()}] ${msg}\n`);
15
14
  }
16
15
  catch (e) { }
17
16
  }
18
17
  const TRACKING_PORT = 10001;
19
- // IMMEDIATE PROBE
20
- try {
21
- fs.writeFileSync(LOC_LOG, `[${new Date().toISOString()}] ENTRY_POINT_RUNNING: ${__filename}\n`);
22
- log(`[STARTUP] Initializing Plugin v5.1.16...`);
23
- }
24
- catch (e) { }
25
18
  // === ANTI-ZOMBIE ATOMIC CLEANUP ===
26
19
  try {
27
20
  log(`[Init] Checking port ${TRACKING_PORT} for zombies...`);
28
21
  execSync(`fuser -k ${TRACKING_PORT}/tcp || true`);
29
22
  log(`[Init] Port ${TRACKING_PORT} cleaned.`);
30
- execSync('sleep 1');
31
23
  }
32
24
  catch (e) {
33
25
  log(`[Init] Zombie cleanup warning: ${e}`);
34
26
  }
35
27
  // === GESTION DU CYCLE DE VIE PROXY ===
36
- // CONFIG WATCHER (Hot Reload)
37
- // CONFIG WATCHER REMOVED (Legacy)
38
28
  const startProxy = () => {
39
29
  return new Promise((resolve) => {
40
30
  const server = http.createServer(async (req, res) => {
@@ -52,29 +42,13 @@ const startProxy = () => {
52
42
  res.writeHead(200, { 'Content-Type': 'application/json' });
53
43
  res.end(JSON.stringify({
54
44
  status: "ok",
55
- version: "v5.1.17",
56
- mode: config.mode,
57
- pid: process.pid
45
+ version: "v4.0.5",
46
+ mode: config.mode
58
47
  }));
59
48
  return;
60
49
  }
61
- // RESTORED: Models Endpoint
62
- if (req.method === 'GET' && req.url === '/v1/models') {
63
- try {
64
- const { getAggregatedModels } = await import('./server/pollinations-api.js');
65
- const models = await getAggregatedModels();
66
- res.writeHead(200, { 'Content-Type': 'application/json' });
67
- res.end(JSON.stringify(models));
68
- log(`[Proxy] Served ${models.data?.length || 0} models`);
69
- }
70
- catch (e) {
71
- log(`[Proxy] Error fetching models: ${e}`);
72
- res.writeHead(500);
73
- res.end(JSON.stringify({ error: String(e) }));
74
- }
75
- return;
76
- }
77
50
  // SUPPORT FLEXIBLE DES PATHS
51
+ // Le SDK peut envoyer /v1/chat/completions ou juste /chat/completions
78
52
  if (req.method === 'POST' && (req.url === '/v1/chat/completions' || req.url === '/chat/completions')) {
79
53
  const chunks = [];
80
54
  req.on('data', chunk => chunks.push(chunk));
@@ -82,10 +56,9 @@ const startProxy = () => {
82
56
  try {
83
57
  const bodyRaw = Buffer.concat(chunks).toString();
84
58
  await handleChatCompletion(req, res, bodyRaw);
85
- log(`[Proxy] Chat Completion Success`);
86
59
  }
87
60
  catch (e) {
88
- log(`[Proxy] Chat Error: ${e}`);
61
+ log(`Error: ${e}`);
89
62
  if (!res.headersSent) {
90
63
  res.writeHead(500);
91
64
  res.end(JSON.stringify({ error: String(e) }));
@@ -99,12 +72,11 @@ const startProxy = () => {
99
72
  res.end("Not Found");
100
73
  });
101
74
  server.listen(TRACKING_PORT, '127.0.0.1', () => {
102
- log(`[Proxy] Started V5.1.17 on port ${TRACKING_PORT}`);
75
+ log(`[Proxy] Started V4 on port ${TRACKING_PORT}`);
103
76
  resolve(TRACKING_PORT);
104
77
  });
105
78
  server.on('error', (e) => {
106
79
  log(`[Proxy] Fatal Error: ${e}`);
107
- // Retry logic could go here, but for now log is key
108
80
  resolve(0);
109
81
  });
110
82
  });
@@ -128,12 +100,7 @@ export const PollinationsPlugin = async (ctx) => {
128
100
  return {
129
101
  async config(config) {
130
102
  log("[Hook] config() called");
131
- // Extract API Key from incoming config to ensure Hot Reload
132
- const incomingKey = config.provider?.pollinations?.options?.apiKey ||
133
- config.provider?.pollinations_enter?.options?.apiKey;
134
- if (incomingKey)
135
- log(`[Hook] Detected API Key update.`);
136
- const modelsArray = await generatePollinationsConfig(incomingKey);
103
+ const modelsArray = await generatePollinationsConfig();
137
104
  const modelsObj = {};
138
105
  for (const m of modelsArray) {
139
106
  // Ensure ID is relative for mapping ("free/gemini")
@@ -152,7 +119,6 @@ export const PollinationsPlugin = async (ctx) => {
152
119
  models: modelsObj
153
120
  };
154
121
  log(`[Hook] Registered ${Object.keys(modelsObj).length} models.`);
155
- emitStatusToast('info', `${Object.keys(modelsObj).length} Models Loaded`, 'Pollinations');
156
122
  },
157
123
  ...toastHooks,
158
124
  ...createStatusHooks(ctx.client), // New Status Bar Logic
@@ -1,5 +1,3 @@
1
- import * as fs from 'fs';
2
- import * as path from 'path';
3
1
  import { loadConfig, saveConfig } from './config.js';
4
2
  import { getQuotaStatus } from './quota.js';
5
3
  import { emitStatusToast } from './toast.js';
@@ -95,8 +93,6 @@ export async function handleCommand(command) {
95
93
  return handleFallbackCommand(args);
96
94
  case 'config':
97
95
  return handleConfigCommand(args);
98
- case 'debug':
99
- return handleDebugCommand();
100
96
  case 'help':
101
97
  return handleHelpCommand();
102
98
  default:
@@ -271,26 +267,6 @@ function handleConfigCommand(args) {
271
267
  error: `Clé inconnue: ${key}. Clés: status_gui, logs_gui, threshold_tier, threshold_wallet, status_bar`
272
268
  };
273
269
  }
274
- function handleDebugCommand() {
275
- const info = {
276
- pid: process.pid,
277
- execPath: process.execPath,
278
- cwd: process.cwd(),
279
- home: process.env.HOME || 'undefined',
280
- node: process.version,
281
- uid: process.getuid ? process.getuid() : 'unknown',
282
- gid: process.getgid ? process.getgid() : 'unknown',
283
- tmp: '/tmp',
284
- port_env: process.env.POLLINATIONS_PORT || 'default(10001)',
285
- log_file_check: fs.existsSync(path.join(process.env.HOME || '', '.config/opencode/plugins/pollinations-v3.log')),
286
- tmp_log_check: fs.existsSync('/tmp/POLLI_LIFECYCLE.log')
287
- };
288
- return {
289
- handled: true,
290
- response: `### 🐞 Debug Probe v5.1.16\n` +
291
- `\`\`\`json\n${JSON.stringify(info, null, 2)}\n\`\`\``
292
- };
293
- }
294
270
  function handleHelpCommand() {
295
271
  const help = `
296
272
  ### 🌸 Pollinations Plugin - Commandes V5
@@ -25,7 +25,7 @@ export interface PollinationsConfigV5 {
25
25
  export declare function subscribeToConfigChange(callback: () => void): void;
26
26
  export declare function loadConfig(): PollinationsConfigV5;
27
27
  export declare function saveConfig(updates: Partial<PollinationsConfigV5>): {
28
- version: string;
28
+ version: number;
29
29
  mode: "manual" | "alwaysfree" | "pro";
30
30
  apiKey?: string;
31
31
  gui: {
@@ -172,15 +172,13 @@ function readConfigFromDisk() {
172
172
  if (!keyFound && config.mode === 'pro') {
173
173
  config.mode = 'manual';
174
174
  }
175
- // CRITICAL: Always enforce the runtime version, never trust the file cache for versioning
176
- config.version = PKG_VERSION;
177
175
  return config;
178
176
  }
179
177
  export function saveConfig(updates) {
180
178
  try {
181
179
  // We must base updates on current state (even if cached is slightly old, we refresh first?)
182
180
  const current = readConfigFromDisk(); // Read disk for safety before write
183
- const updated = { ...current, ...updates, version: PKG_VERSION };
181
+ const updated = { ...current, ...updates, version: 5 };
184
182
  if (!fs.existsSync(CONFIG_DIR_POLLI)) {
185
183
  fs.mkdirSync(CONFIG_DIR_POLLI, { recursive: true });
186
184
  }
@@ -9,5 +9,5 @@ interface OpenCodeModel {
9
9
  output?: number;
10
10
  };
11
11
  }
12
- export declare function generatePollinationsConfig(forceApiKey?: string): Promise<OpenCodeModel[]>;
12
+ export declare function generatePollinationsConfig(): Promise<OpenCodeModel[]>;
13
13
  export {};
@@ -48,12 +48,10 @@ function formatName(id, censored = true) {
48
48
  return clean;
49
49
  }
50
50
  // --- MAIN GENERATOR logic ---
51
- export async function generatePollinationsConfig(forceApiKey) {
51
+ export async function generatePollinationsConfig() {
52
52
  const config = loadConfig();
53
53
  const modelsOutput = [];
54
- // Use forced key (from Hook) or cached key
55
- const effectiveKey = forceApiKey || config.apiKey;
56
- log(`Starting Configuration (V5.1.18 Hot-Reload)...`);
54
+ log(`Starting Configuration (V4.5 Clean Dynamic)...`);
57
55
  // 1. FREE UNIVERSE
58
56
  try {
59
57
  // Switch to main models endpoint (User provided curl confirms it has 'description')
@@ -73,10 +71,10 @@ export async function generatePollinationsConfig(forceApiKey) {
73
71
  modelsOutput.push({ id: "free/gemini", name: "[Free] Gemini Flash (Fallback)", object: "model", variants: {} });
74
72
  }
75
73
  // 2. ENTERPRISE UNIVERSE
76
- if (effectiveKey && effectiveKey.length > 5 && effectiveKey !== 'dummy') {
74
+ if (config.apiKey && config.apiKey.length > 5 && config.apiKey !== 'dummy') {
77
75
  try {
78
76
  const enterListRaw = await fetchJson('https://gen.pollinations.ai/text/models', {
79
- 'Authorization': `Bearer ${effectiveKey}`
77
+ 'Authorization': `Bearer ${config.apiKey}`
80
78
  });
81
79
  const enterList = Array.isArray(enterListRaw) ? enterListRaw : (enterListRaw.data || []);
82
80
  enterList.forEach((m) => {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "opencode-pollinations-plugin",
3
3
  "displayName": "Pollinations AI (V5.1)",
4
- "version": "5.1.18",
4
+ "version": "5.1.19",
5
5
  "description": "Native Pollinations.ai Provider Plugin for OpenCode",
6
6
  "publisher": "pollinations",
7
7
  "repository": {
@@ -28,7 +28,6 @@
28
28
  ],
29
29
  "scripts": {
30
30
  "build": "tsc",
31
- "prepare": "npm run build",
32
31
  "package": "npx vsce package"
33
32
  },
34
33
  "contributes": {
@@ -1,8 +0,0 @@
1
- export interface RoutingDecision {
2
- targetUrl: string;
3
- actualModel: string;
4
- authHeader?: string;
5
- fallbackUsed: boolean;
6
- fallbackReason?: string;
7
- }
8
- export declare function resolveRouting(requestedModel: string, isAgent?: boolean): Promise<RoutingDecision>;
@@ -1,122 +0,0 @@
1
- import { loadConfig } from './config.js';
2
- import { getQuotaStatus } from './quota.js';
3
- import { emitToast } from './toast.js';
4
- // === MAIN ROUTER ===
5
- export async function resolveRouting(requestedModel, isAgent = false) {
6
- const config = loadConfig();
7
- // Normalisation de l'ID modèle pour l'analyse
8
- // Peut arriver sous forme: "pollinations/free/gemini" OU "free/gemini"
9
- const isEnterprise = requestedModel.includes('/enter/') || requestedModel.startsWith('enter/');
10
- const isFree = requestedModel.includes('/free/') || requestedModel.startsWith('free/');
11
- // Extraction du "baseModel" (ex: "gemini", "openai")
12
- let baseModel = requestedModel;
13
- baseModel = baseModel.replace(/^pollinations\//, ''); // Remove plugin prefix
14
- baseModel = baseModel.replace(/^(enter|free)\//, ''); // Remove tier prefix
15
- // === MODE MANUAL ===
16
- if (config.mode === 'manual') {
17
- if (isEnterprise && config.apiKey) {
18
- return {
19
- targetUrl: 'https://gen.pollinations.ai/v1/chat/completions',
20
- actualModel: baseModel,
21
- authHeader: `Bearer ${config.apiKey}`,
22
- fallbackUsed: false
23
- };
24
- }
25
- // Default Free
26
- return {
27
- targetUrl: 'https://text.pollinations.ai/openai/chat/completions',
28
- actualModel: baseModel,
29
- authHeader: undefined,
30
- fallbackUsed: false
31
- };
32
- }
33
- // === MODES INTELLIGENTS ===
34
- if (!config.apiKey) {
35
- return {
36
- targetUrl: 'https://text.pollinations.ai/openai/chat/completions',
37
- actualModel: baseModel,
38
- authHeader: undefined,
39
- fallbackUsed: false
40
- };
41
- }
42
- const quota = await getQuotaStatus();
43
- handleQuotaAlerts(quota, config);
44
- // === ALWAYSFREE ===
45
- if (config.mode === 'alwaysfree') {
46
- if (isEnterprise) {
47
- if (quota.tierRemaining > 0) {
48
- return {
49
- targetUrl: 'https://gen.pollinations.ai/v1/chat/completions',
50
- actualModel: baseModel,
51
- authHeader: `Bearer ${config.apiKey}`,
52
- fallbackUsed: false
53
- };
54
- }
55
- else {
56
- const fallbackModel = isAgent ? config.fallbackModels.agent : config.fallbackModels.main;
57
- emitToast('warning', `Quota Free épuisé 🛑 → Relai sur ${fallbackModel} gratuit 🔀`, 'Mode AlwaysFree');
58
- return {
59
- targetUrl: 'https://text.pollinations.ai/openai/chat/completions',
60
- actualModel: fallbackModel,
61
- authHeader: undefined,
62
- fallbackUsed: true,
63
- fallbackReason: 'tier_exhausted'
64
- };
65
- }
66
- }
67
- // Free
68
- return {
69
- targetUrl: 'https://text.pollinations.ai/openai/chat/completions',
70
- actualModel: baseModel,
71
- authHeader: undefined,
72
- fallbackUsed: false
73
- };
74
- }
75
- // === PRO ===
76
- if (config.mode === 'pro') {
77
- if (isEnterprise) {
78
- if (quota.canUseEnterprise) {
79
- if (quota.isUsingWallet) {
80
- emitToast('info', `Tier épuisé → Utilisation du Wallet ($${quota.walletBalance.toFixed(2)})`, 'Mode Pro');
81
- }
82
- return {
83
- targetUrl: 'https://gen.pollinations.ai/v1/chat/completions',
84
- actualModel: baseModel,
85
- authHeader: `Bearer ${config.apiKey}`,
86
- fallbackUsed: false
87
- };
88
- }
89
- else {
90
- const fallbackModel = isAgent ? config.fallbackModels.agent : config.fallbackModels.main;
91
- emitToast('error', `💸 Wallet épuisé ! Fallback sur ${fallbackModel}`, 'Mode Pro');
92
- return {
93
- targetUrl: 'https://text.pollinations.ai/openai/chat/completions',
94
- actualModel: fallbackModel,
95
- authHeader: undefined,
96
- fallbackUsed: true,
97
- fallbackReason: 'wallet_exhausted'
98
- };
99
- }
100
- }
101
- // Free
102
- return {
103
- targetUrl: 'https://text.pollinations.ai/openai/chat/completions',
104
- actualModel: baseModel,
105
- authHeader: undefined,
106
- fallbackUsed: false
107
- };
108
- }
109
- // Default
110
- return {
111
- targetUrl: 'https://text.pollinations.ai/openai/chat/completions',
112
- actualModel: baseModel,
113
- authHeader: undefined,
114
- fallbackUsed: false
115
- };
116
- }
117
- function handleQuotaAlerts(quota, config) {
118
- if (quota.needsAlert && quota.tierLimit > 0) {
119
- const tierPercent = Math.round((quota.tierRemaining / quota.tierLimit) * 100);
120
- emitToast('warning', `⚠️ Quota Tier à ${tierPercent}%`, 'Alerte Quota');
121
- }
122
- }