opencode-pollinations-plugin 6.0.0-beta.25 → 6.0.0-beta.3

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.md CHANGED
@@ -1,4 +1,4 @@
1
- # 🌸 Pollinations AI Plugin for OpenCode (v5.6.0)
1
+ # 🌸 Pollinations AI Plugin for OpenCode (v5.9.0)
2
2
 
3
3
  <div align="center">
4
4
  <img src="https://avatars.githubusercontent.com/u/88394740?s=400&v=4" alt="Pollinations.ai Logo" width="200">
@@ -10,11 +10,9 @@
10
10
 
11
11
  <div align="center">
12
12
 
13
- ![Version](https://img.shields.io/badge/version-5.8.4--beta.15-orange.svg)
13
+ ![Version](https://img.shields.io/badge/version-5.6.0-blue.svg)
14
14
  ![License](https://img.shields.io/badge/license-MIT-green.svg)
15
- ![Status](https://img.shields.io/badge/status-Beta-yellow.svg)
16
-
17
- [📜 View Changelog](./CHANGELOG.md) | [🛣️ Roadmap](./ROADMAP.md)
15
+ ![Status](https://img.shields.io/badge/status-Stable-success.svg)
18
16
 
19
17
  </div>
20
18
 
@@ -136,8 +134,9 @@ OpenCode uses NPM as its registry. To publish:
136
134
 
137
135
  ### 1. The Basics (Free Mode)
138
136
  Just type in the chat. You are in **Manual Mode** by default.
139
- - Model: `openai` (GPT-4o Mini equivalent)
140
- - Model: `mistral` (Mistral Nemo)
137
+ - Model: `openai-fast` (GPT-OSS 20b)
138
+ - Model: `mistral` (Mistral Small 3.1)
139
+ - ...
141
140
 
142
141
  ### 🔑 Configuration (API Key)
143
142
 
@@ -157,6 +156,7 @@ Just type in the chat. You are in **Manual Mode** by default.
157
156
 
158
157
  ## 🔗 Links
159
158
 
159
+ - **Sign up Pollinations Beta (more and best free tiers access and paids models)**: [pollinations.ai](https://enter.pollinations.ai)
160
160
  - **Pollinations Website**: [pollinations.ai](https://pollinations.ai)
161
161
  - **Discord Community**: [Join us!](https://discord.gg/pollinations-ai-885844321461485618)
162
162
  - **OpenCode Ecosystem**: [opencode.ai](https://opencode.ai/docs/ecosystem#plugins)
package/dist/index.js CHANGED
@@ -1,11 +1,12 @@
1
1
  import * as http from 'http';
2
2
  import * as fs from 'fs';
3
3
  import { generatePollinationsConfig } from './server/generate-config.js';
4
- import { loadConfig, saveConfig } from './server/config.js';
4
+ import { loadConfig } from './server/config.js';
5
5
  import { handleChatCompletion } from './server/proxy.js';
6
6
  import { createToastHooks, setGlobalClient } from './server/toast.js';
7
7
  import { createStatusHooks } from './server/status.js';
8
8
  import { createCommandHooks } from './server/commands.js';
9
+ import { createToolRegistry } from './tools/index.js';
9
10
  import { createRequire } from 'module';
10
11
  const require = createRequire(import.meta.url);
11
12
  const LOG_FILE = '/tmp/opencode_pollinations_v4.log';
@@ -15,17 +16,12 @@ function log(msg) {
15
16
  }
16
17
  catch (e) { }
17
18
  }
18
- // === PROXY SERVER (Singleton with Fixed Port) ===
19
- const DEFAULT_PORT = 18888;
20
- const GLOBAL_SERVER_KEY = '__POLLINATIONS_PROXY_SERVER__';
19
+ // Port killing removed: Using dynamic ports.
21
20
  const startProxy = () => {
22
- // Check if server exists in global scope (survives module reloads)
23
- if (global[GLOBAL_SERVER_KEY]) {
24
- log(`[Proxy] Reusing existing global server on port ${DEFAULT_PORT}`);
25
- return Promise.resolve(DEFAULT_PORT);
26
- }
27
21
  return new Promise((resolve) => {
28
22
  const server = http.createServer(async (req, res) => {
23
+ // ... (Request Handling) ...
24
+ // We reuse the existing logic structure but simplified startup
29
25
  log(`[Proxy] Request: ${req.method} ${req.url}`);
30
26
  res.setHeader('Access-Control-Allow-Origin', '*');
31
27
  res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
@@ -67,156 +63,37 @@ const startProxy = () => {
67
63
  res.writeHead(404);
68
64
  res.end("Not Found");
69
65
  });
70
- // Try fixed port first, fallback to dynamic if occupied
71
- const tryListen = (port, fallbackToDynamic) => {
72
- server.listen(port, '127.0.0.1', () => {
73
- // @ts-ignore
74
- const assignedPort = server.address().port;
75
- global[GLOBAL_SERVER_KEY] = server;
76
- log(`[Proxy] Started v${require('../package.json').version} on port ${assignedPort}${port === 0 ? ' (dynamic fallback)' : ''}`);
77
- resolve(assignedPort);
78
- });
79
- server.on('error', (e) => {
80
- if (e.code === 'EADDRINUSE' && fallbackToDynamic) {
81
- log(`[Proxy] Port ${port} in use, falling back to dynamic port`);
82
- server.removeAllListeners('error');
83
- tryListen(0, false); // Try dynamic port
84
- }
85
- else {
86
- log(`[Proxy] Fatal Error: ${e}`);
87
- resolve(0);
88
- }
89
- });
90
- };
91
- tryListen(DEFAULT_PORT, true);
66
+ // Listen on random port (0) to avoid conflicts (CLI/IDE)
67
+ server.listen(0, '127.0.0.1', () => {
68
+ // @ts-ignore
69
+ const assignedPort = server.address().port;
70
+ log(`[Proxy] Started v${require('../package.json').version} (Dynamic Port) on port ${assignedPort}`);
71
+ resolve(assignedPort);
72
+ });
73
+ server.on('error', (e) => {
74
+ log(`[Proxy] Fatal Error: ${e}`);
75
+ resolve(0);
76
+ });
92
77
  });
93
78
  };
94
- // === AUTH HOOK: Native /connect Integration ===
95
- // Auth Hook moved inside plugin to access context
96
79
  // === PLUGIN EXPORT ===
97
80
  export const PollinationsPlugin = async (ctx) => {
98
81
  log(`Plugin Initializing v${require('../package.json').version}...`);
99
- // START PROXY on fixed port
82
+ // START PROXY
100
83
  const port = await startProxy();
101
84
  const localBaseUrl = `http://127.0.0.1:${port}/v1`;
102
85
  setGlobalClient(ctx.client);
103
86
  const toastHooks = createToastHooks(ctx.client);
104
87
  const commandHooks = createCommandHooks();
105
- // Helper: Refresh provider config (for hot-reload after /connect)
106
- let isRefreshing = false;
107
- const refreshProviderConfig = async () => {
108
- if (isRefreshing)
109
- return;
110
- isRefreshing = true;
111
- try {
112
- log('[Event] Refreshing provider config after auth update...');
113
- const modelsArray = await generatePollinationsConfig();
114
- const modelsObj = {};
115
- for (const m of modelsArray) {
116
- modelsObj[m.id] = m;
117
- }
118
- const version = require('../package.json').version;
119
- // CRITICAL: Fetch current config first to avoid overwriting other providers
120
- let currentConfig = {};
121
- try {
122
- // Try to fetch existing config to preserve other providers
123
- const response = await ctx.client.fetch('/config');
124
- if (response.ok) {
125
- currentConfig = await response.json();
126
- }
127
- }
128
- catch (err) {
129
- log(`[Event] Warning: Could not fetch current config: ${err}`);
130
- }
131
- // Safe Merge
132
- if (!currentConfig.provider)
133
- currentConfig.provider = {};
134
- currentConfig.provider.pollinations = {
135
- id: 'openai',
136
- name: `Pollinations AI (v${version})`,
137
- options: {
138
- baseURL: localBaseUrl,
139
- apiKey: 'plugin-managed',
140
- },
141
- models: modelsObj
142
- };
143
- // Use Server API to update config with the MERGED object
144
- await ctx.client.fetch('/config', {
145
- method: 'PATCH',
146
- headers: { 'Content-Type': 'application/json' },
147
- body: JSON.stringify({
148
- provider: currentConfig.provider
149
- })
150
- });
151
- log(`[Event] Provider config refreshed with ${Object.keys(modelsObj).length} models.`);
152
- }
153
- catch (e) {
154
- log(`[Event] Failed to refresh provider config: ${e}`);
155
- }
156
- finally {
157
- // Debounce: prevent another refresh for 5 seconds
158
- setTimeout(() => { isRefreshing = false; }, 5000);
159
- }
160
- };
88
+ // Build tool registry (conditional on API key presence)
89
+ const toolRegistry = createToolRegistry();
90
+ log(`[Tools] ${Object.keys(toolRegistry).length} tools registered`);
161
91
  return {
162
- // AUTH HOOK: Native /connect integration (INLINED)
163
- auth: {
164
- provider: 'pollinations',
165
- loader: async (auth, provider) => {
166
- log('[AuthHook] loader() called');
167
- try {
168
- const authData = await auth();
169
- if (authData && 'key' in authData) {
170
- const k = authData.key;
171
- saveConfig({ apiKey: k });
172
- return { apiKey: k };
173
- }
174
- }
175
- catch (e) {
176
- log(`[AuthHook] loader error: ${e}`);
177
- }
178
- const config = loadConfig();
179
- if (config.apiKey)
180
- return { apiKey: config.apiKey };
181
- return {};
182
- },
183
- methods: [{
184
- type: 'api',
185
- label: 'Pollinations API Key',
186
- prompts: [{
187
- type: 'text',
188
- key: 'apiKey',
189
- message: 'Enter Pollinations API Key (starts with sk_)',
190
- validate: (v) => (!v || v.length < 10) ? 'Invalid key' : undefined
191
- }],
192
- authorize: async (inputs) => {
193
- log(`[AuthHook] authorize() called`);
194
- if (!inputs?.apiKey)
195
- return { type: 'failed' };
196
- try {
197
- const r = await fetch('https://gen.pollinations.ai/text/models', {
198
- headers: { 'Authorization': `Bearer ${inputs.apiKey}` }
199
- });
200
- if (r.ok) {
201
- log('[AuthHook] Success. Saving & Refreshing Config...');
202
- saveConfig({ apiKey: inputs.apiKey });
203
- // CRITICAL: Refresh config IMMEDIATELY after successful auth
204
- await refreshProviderConfig();
205
- return { type: 'success', key: inputs.apiKey };
206
- }
207
- }
208
- catch (e) {
209
- log(`[AuthHook] Auth error: ${e}`);
210
- }
211
- return { type: 'failed' };
212
- }
213
- }]
214
- },
215
- // Event hook removed (logic moved to authorize)
216
- event: async ({ event }) => { },
92
+ tool: toolRegistry,
217
93
  async config(config) {
218
94
  log("[Hook] config() called");
219
- // Generate models based on current auth state
95
+ // STARTUP only - No complex hot reload logic
96
+ // The user must restart OpenCode to refresh this list if they change keys.
220
97
  const modelsArray = await generatePollinationsConfig();
221
98
  const modelsObj = {};
222
99
  for (const m of modelsArray) {
@@ -224,14 +101,12 @@ export const PollinationsPlugin = async (ctx) => {
224
101
  }
225
102
  if (!config.provider)
226
103
  config.provider = {};
104
+ // Dynamic Provider Name
227
105
  const version = require('../package.json').version;
228
106
  config.provider['pollinations'] = {
229
- id: 'openai',
107
+ id: 'pollinations',
230
108
  name: `Pollinations AI (v${version})`,
231
- options: {
232
- baseURL: localBaseUrl,
233
- apiKey: 'plugin-managed', // Key is managed by auth hook
234
- },
109
+ options: { baseURL: localBaseUrl },
235
110
  models: modelsObj
236
111
  };
237
112
  log(`[Hook] Registered ${Object.keys(modelsObj).length} models.`);
@@ -303,15 +303,15 @@ async function handleConnectCommand(args) {
303
303
  // 1. Universal Validation (No Syntax Check) - Functional Check
304
304
  emitStatusToast('info', 'Vérification de la clé...', 'Pollinations Config');
305
305
  try {
306
- const models = await generatePollinationsConfig(key);
307
- // 2. Check if we got real models (not just connect placeholder)
308
- const realModels = models.filter(m => m.id !== 'connect');
309
- if (realModels.length > 0) {
306
+ const models = await generatePollinationsConfig(key, true);
307
+ // 2. Check if we got Enterprise models
308
+ const enterpriseModels = models.filter(m => m.id.startsWith('enter/'));
309
+ if (enterpriseModels.length > 0) {
310
310
  // SUCCESS
311
311
  saveConfig({ apiKey: key }); // Don't force mode 'pro'. Let user decide.
312
312
  const masked = key.substring(0, 6) + '...';
313
313
  // Count Paid Only models found
314
- const diamondCount = realModels.filter(m => m.name.includes('💎')).length;
314
+ const diamondCount = enterpriseModels.filter(m => m.name.includes('💎')).length;
315
315
  // CHECK RESTRICTIONS: Strict Check (Usage + Profile + Balance)
316
316
  let forcedModeMsg = "";
317
317
  let isLimited = false;
@@ -336,10 +336,10 @@ async function handleConnectCommand(args) {
336
336
  else {
337
337
  saveConfig({ apiKey: key, keyHasAccessToProfile: true }); // Let user keep current mode or default
338
338
  }
339
- emitStatusToast('success', `Clé Valide! (${realModels.length} modèles débloqués)`, 'Pollinations Config');
339
+ emitStatusToast('success', `Clé Valide! (${enterpriseModels.length} modèles Pro débloqués)`, 'Pollinations Config');
340
340
  return {
341
341
  handled: true,
342
- response: `✅ **Connexion Réussie!**\n- Clé: \`${masked}\`\n- Modèles Débloqués: ${realModels.length} (dont ${diamondCount} 💎 Paid)${forcedModeMsg}`
342
+ response: `✅ **Connexion Réussie!**\n- Clé: \`${masked}\`\n- Modèles Débloqués: ${enterpriseModels.length} (dont ${diamondCount} 💎 Paid)${forcedModeMsg}`
343
343
  };
344
344
  }
345
345
  else {
@@ -351,8 +351,18 @@ async function handleConnectCommand(args) {
351
351
  // Wait, generate-config falls back to providing a list containing "[Enter] GPT-4o (Fallback)" if fetch failed.
352
352
  // So we need to detect if it's a "REAL" fetch or a "FALLBACK" fetch.
353
353
  // The fallback models have `variants: {}` usually, but real ones might too.
354
- // v6.0: No fallback prefix check needed. If models is empty (only connect), key is invalid.
355
- throw new Error("Aucun modèle détecté pour cette clé. Clé invalide ou expirée.");
354
+ // A better check: The fallback list is hardcoded in generate-config.ts catch block.
355
+ // Let's modify generate-config to return EMPTY list on error?
356
+ // Or just check if the returned models work?
357
+ // Simplest: If `generatePollinationsConfig` returns any model starting with `enter/` that includes "(Fallback)" in name, we assume failure?
358
+ // "GPT-4o (Fallback)" is the name.
359
+ const isFallback = models.some(m => m.name.includes('(Fallback)') && m.id.startsWith('enter/'));
360
+ if (isFallback) {
361
+ throw new Error("Clé rejetée par l'API (Accès refusé ou invalide).");
362
+ }
363
+ // If we are here, we got no enter models, or empty list?
364
+ // If key is valid but has no access?
365
+ throw new Error("Aucun modèle Enterprise détecté pour cette clé.");
356
366
  }
357
367
  }
358
368
  catch (e) {
@@ -1,34 +1,8 @@
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
- export interface PollinationsModel {
8
- name: string;
9
- description?: string;
10
- type?: string;
11
- tools?: boolean;
12
- reasoning?: boolean;
13
- context?: number;
14
- context_window?: number;
15
- input_modalities?: string[];
16
- output_modalities?: string[];
17
- paid_only?: boolean;
18
- vision?: boolean;
19
- audio?: boolean;
20
- pricing?: {
21
- promptTextTokens?: number;
22
- completionTextTokens?: number;
23
- promptImageTokens?: number;
24
- promptAudioTokens?: number;
25
- completionAudioTokens?: number;
26
- };
27
- [key: string]: any;
28
- }
29
1
  interface OpenCodeModel {
30
2
  id: string;
31
3
  name: string;
4
+ object: string;
5
+ variants?: any;
32
6
  options?: any;
33
7
  limit?: {
34
8
  context?: number;
@@ -38,7 +12,6 @@ interface OpenCodeModel {
38
12
  input?: string[];
39
13
  output?: string[];
40
14
  };
41
- tool_call?: boolean;
42
15
  }
43
- export declare function generatePollinationsConfig(forceApiKey?: string): Promise<OpenCodeModel[]>;
16
+ export declare function generatePollinationsConfig(forceApiKey?: string, forceStrict?: boolean): Promise<OpenCodeModel[]>;
44
17
  export {};