opencode-pollinations-plugin 5.1.22 → 5.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.
package/README.md CHANGED
@@ -116,17 +116,16 @@ Just type in the chat. You are in **Manual Mode** by default.
116
116
  - Model: `openai` (GPT-4o Mini equivalent)
117
117
  - Model: `mistral` (Mistral Nemo)
118
118
 
119
- ### 2. Going Pro
120
- 1. Get your API Key from [pollinations.ai](https://pollinations.ai).
121
- 2. Run command:
122
- ```bash
123
- /pollinations config apiKey sk_YourSecretKey
124
- ```
125
- 3. Set mode to Pro:
126
- ```bash
127
- /pollinations mode pro
128
- ```
129
- 4. **Reload Window**.
119
+ ### 🔑 Configuration (API Key)
120
+
121
+ 1. Run the setup command:
122
+ ```bash
123
+ /connect
124
+ ```
125
+ 2. Choose "pollinations" and enter your key if you have one (or leave blank for free tier).
126
+ 3. **IMPORTANT**: You must **restart OpenCode** for the model list to update with your new tier (e.g. to see Paid models).
127
+
128
+ ### 🤖 Models
130
129
 
131
130
  ## 🔗 Links
132
131
 
package/dist/index.js CHANGED
@@ -2,7 +2,7 @@ import * as http from 'http';
2
2
  import * as fs from 'fs';
3
3
  import { execSync } from 'child_process';
4
4
  import { generatePollinationsConfig } from './server/generate-config.js';
5
- import { loadConfig, updateCache } from './server/config.js';
5
+ import { loadConfig } from './server/config.js';
6
6
  import { handleChatCompletion } from './server/proxy.js';
7
7
  import { createToastHooks, setGlobalClient } from './server/toast.js';
8
8
  import { createStatusHooks } from './server/status.js';
@@ -42,13 +42,11 @@ const startProxy = () => {
42
42
  res.writeHead(200, { 'Content-Type': 'application/json' });
43
43
  res.end(JSON.stringify({
44
44
  status: "ok",
45
- version: "v4.0.5",
45
+ version: "v5.2.0",
46
46
  mode: config.mode
47
47
  }));
48
48
  return;
49
49
  }
50
- // SUPPORT FLEXIBLE DES PATHS
51
- // Le SDK peut envoyer /v1/chat/completions ou juste /chat/completions
52
50
  if (req.method === 'POST' && (req.url === '/v1/chat/completions' || req.url === '/chat/completions')) {
53
51
  const chunks = [];
54
52
  req.on('data', chunk => chunks.push(chunk));
@@ -72,7 +70,7 @@ const startProxy = () => {
72
70
  res.end("Not Found");
73
71
  });
74
72
  server.listen(TRACKING_PORT, '127.0.0.1', () => {
75
- log(`[Proxy] Started V4 on port ${TRACKING_PORT}`);
73
+ log(`[Proxy] Started V5.2 on port ${TRACKING_PORT}`);
76
74
  resolve(TRACKING_PORT);
77
75
  });
78
76
  server.on('error', (e) => {
@@ -83,52 +81,35 @@ const startProxy = () => {
83
81
  };
84
82
  // === PLUGIN EXPORT ===
85
83
  export const PollinationsPlugin = async (ctx) => {
86
- log("Plugin Initializing V4.0.5 (Path Fix)...");
87
- try {
88
- log(`[DEBUG] CTX Keys: ${Object.keys(ctx).join(', ')}`);
89
- }
90
- catch (e) {
91
- log(`[DEBUG] Error inspecting ctx: ${e}`);
92
- }
84
+ log("Plugin Initializing V5.2.0 (Stable)...");
85
+ // START PROXY
93
86
  const port = await startProxy();
94
- // IMPORTANT: On ajoute /v1 à la base URL pour guider le SDK,
95
- // mais le proxy accepte aussi sans.
96
87
  const localBaseUrl = `http://127.0.0.1:${port}/v1`;
97
- setGlobalClient(ctx.client); // REGISTER CLIENT FOR IMMEDIATE TOASTS
88
+ setGlobalClient(ctx.client);
98
89
  const toastHooks = createToastHooks(ctx.client);
99
90
  const commandHooks = createCommandHooks();
100
91
  return {
101
92
  async config(config) {
102
93
  log("[Hook] config() called");
103
- // Extract API Key from incoming config to ensure Hot Reload
104
- const incomingKey = config.provider?.pollinations?.options?.apiKey ||
105
- config.provider?.pollinations_enter?.options?.apiKey;
106
- if (incomingKey) {
107
- log(`[Hook] Detected API Key update.`);
108
- updateCache({ apiKey: incomingKey, mode: 'pro' });
109
- }
110
- const modelsArray = await generatePollinationsConfig(incomingKey);
94
+ // STARTUP only - No complex hot reload logic
95
+ // The user must restart OpenCode to refresh this list if they change keys.
96
+ const modelsArray = await generatePollinationsConfig();
111
97
  const modelsObj = {};
112
98
  for (const m of modelsArray) {
113
- // Ensure ID is relative for mapping ("free/gemini")
114
- // BUT Provider needs full ID ? No, the object key is the relative ID
115
- // OpenCode: provider.models[id]
116
- // id comes from generatePollinationsConfig which returns "free/gemini"
117
- // So modelsObj["free/gemini"] = ... matches.
118
99
  modelsObj[m.id] = m;
119
100
  }
120
101
  if (!config.provider)
121
102
  config.provider = {};
122
103
  config.provider['pollinations'] = {
123
104
  id: 'pollinations',
124
- name: 'Pollinations V5.1 (Native)',
105
+ name: 'Pollinations V5.2 (Native)',
125
106
  options: { baseURL: localBaseUrl },
126
107
  models: modelsObj
127
108
  };
128
109
  log(`[Hook] Registered ${Object.keys(modelsObj).length} models.`);
129
110
  },
130
111
  ...toastHooks,
131
- ...createStatusHooks(ctx.client), // New Status Bar Logic
112
+ ...createStatusHooks(ctx.client),
132
113
  ...commandHooks
133
114
  };
134
115
  };
@@ -22,9 +22,7 @@ export interface PollinationsConfigV5 {
22
22
  enablePaidTools: boolean;
23
23
  statusBar: boolean;
24
24
  }
25
- export declare function subscribeToConfigChange(callback: () => void): void;
26
25
  export declare function loadConfig(): PollinationsConfigV5;
27
- export declare function updateCache(newConfig: Partial<PollinationsConfigV5>): void;
28
26
  export declare function saveConfig(updates: Partial<PollinationsConfigV5>): {
29
27
  version: string;
30
28
  mode: "manual" | "alwaysfree" | "pro";
@@ -9,7 +9,7 @@ const CONFIG_DIR_OPENCODE = path.join(HOMEDIR, '.config', 'opencode');
9
9
  const OPENCODE_CONFIG_FILE = path.join(CONFIG_DIR_OPENCODE, 'opencode.json');
10
10
  const AUTH_FILE = path.join(HOMEDIR, '.local', 'share', 'opencode', 'auth.json');
11
11
  // LOAD PACKAGE VERSION
12
- let PKG_VERSION = '5.0.0';
12
+ let PKG_VERSION = '5.2.0';
13
13
  try {
14
14
  const pkgPath = path.join(__dirname, '../../package.json');
15
15
  if (fs.existsSync(pkgPath)) {
@@ -21,27 +21,15 @@ catch (e) { }
21
21
  const DEFAULT_CONFIG_V5 = {
22
22
  version: PKG_VERSION,
23
23
  mode: 'manual',
24
- gui: {
25
- status: 'alert',
26
- logs: 'none'
27
- },
28
- thresholds: {
29
- tier: 10, // Alert if < 10%
30
- wallet: 5 // Switch if < 5% (Wallet Protection)
31
- },
24
+ gui: { status: 'alert', logs: 'none' },
25
+ thresholds: { tier: 10, wallet: 5 },
32
26
  fallbacks: {
33
- free: {
34
- main: 'free/mistral', // Fallback gratuit solide
35
- agent: 'free/openai-fast' // Agent gratuit rapide
36
- },
37
- enter: {
38
- agent: 'free/gemini' // Agent de secours (Free Gemini)
39
- }
27
+ free: { main: 'free/mistral', agent: 'free/openai-fast' },
28
+ enter: { agent: 'free/gemini' }
40
29
  },
41
30
  enablePaidTools: false,
42
31
  statusBar: true
43
32
  };
44
- // Debug Helper
45
33
  function logConfig(msg) {
46
34
  try {
47
35
  if (!fs.existsSync('/tmp/opencode_pollinations_config_debug.log')) {
@@ -51,81 +39,20 @@ function logConfig(msg) {
51
39
  }
52
40
  catch (e) { }
53
41
  }
54
- // CACHE & WATCHER
55
- let cachedConfig = null;
56
- const listeners = [];
57
- export function subscribeToConfigChange(callback) {
58
- listeners.push(callback);
59
- }
60
- function notifyListeners() {
61
- listeners.forEach(cb => {
62
- try {
63
- cb();
64
- }
65
- catch (e) {
66
- logConfig(`Listener Error: ${e}`);
67
- }
68
- });
69
- }
70
- // Watchers (Debounced)
71
- const watchedFiles = new Set();
72
- function watchFileSafe(filePath) {
73
- if (watchedFiles.has(filePath))
74
- return;
75
- try {
76
- if (!fs.existsSync(filePath))
77
- return;
78
- fs.watchFile(filePath, { interval: 2000 }, (curr, prev) => {
79
- if (curr.mtime !== prev.mtime) {
80
- logConfig(`File Changed: ${path.basename(filePath)}. Reloading Config...`);
81
- cachedConfig = readConfigFromDisk();
82
- notifyListeners();
83
- }
84
- });
85
- watchedFiles.add(filePath);
86
- }
87
- catch (e) {
88
- logConfig(`Watch Error for ${filePath}: ${e}`);
89
- }
90
- }
42
+ // SIMPLE LOAD (Direct Disk Read - No Caching, No Watchers)
43
+ // This ensures the Proxy ALWAYS sees the latest state from auth.json
91
44
  export function loadConfig() {
92
- // 1. Return Cache if available
93
- if (cachedConfig)
94
- return cachedConfig;
95
- // 2. Or Read Fresh
96
- cachedConfig = readConfigFromDisk();
97
- // 3. Initiate Watchers (Lazy)
98
- watchFileSafe(CONFIG_FILE);
99
- watchFileSafe(AUTH_FILE);
100
- watchFileSafe(OPENCODE_CONFIG_FILE);
101
- return cachedConfig;
102
- }
103
- export function updateCache(newConfig) {
104
- const current = loadConfig();
105
- cachedConfig = { ...current, ...newConfig };
106
- logConfig(`Cache Force-Updated via Hook. Mode: ${cachedConfig.mode}`);
45
+ return readConfigFromDisk();
107
46
  }
108
- // INTERNAL READER (The old loadConfig logic)
109
47
  function readConfigFromDisk() {
110
48
  let config = { ...DEFAULT_CONFIG_V5 };
111
49
  let keyFound = false;
112
- // 1. Try Custom Config
50
+ // 1. Custom Config
113
51
  try {
114
52
  if (fs.existsSync(CONFIG_FILE)) {
115
53
  const raw = fs.readFileSync(CONFIG_FILE, 'utf-8');
116
54
  const custom = JSON.parse(raw);
117
- // ... (Migration Logic is handled on Save, we trust Disk content here mostly)
118
- if (!custom.version || custom.version < 5) {
119
- // If old, we don't migrate inside read to avoid write-loops.
120
- // We just read what we can.
121
- if (custom.apiKey)
122
- config.apiKey = custom.apiKey;
123
- if (custom.mode)
124
- config.mode = custom.mode;
125
- }
126
- else {
127
- config = { ...config, ...custom };
128
- }
55
+ config = { ...config, ...custom };
129
56
  if (config.apiKey)
130
57
  keyFound = true;
131
58
  }
@@ -133,13 +60,12 @@ function readConfigFromDisk() {
133
60
  catch (e) {
134
61
  logConfig(`Error loading config: ${e}`);
135
62
  }
136
- // 2. Try Native Auth Storage (Recovery)
63
+ // 2. Auth Store (Priority)
137
64
  if (!keyFound) {
138
65
  try {
139
66
  if (fs.existsSync(AUTH_FILE)) {
140
67
  const raw = fs.readFileSync(AUTH_FILE, 'utf-8');
141
68
  const authData = JSON.parse(raw);
142
- // Supports flat key or nested object
143
69
  const entry = authData['pollinations'] || authData['pollinations_enter'] || authData['pollinations_api_key'];
144
70
  if (entry) {
145
71
  const key = (typeof entry === 'object' && entry.key) ? entry.key : entry;
@@ -147,7 +73,6 @@ function readConfigFromDisk() {
147
73
  config.apiKey = key;
148
74
  config.mode = 'pro';
149
75
  keyFound = true;
150
- logConfig(`Hot-Loaded API Key from Auth Store`);
151
76
  }
152
77
  }
153
78
  }
@@ -156,7 +81,7 @@ function readConfigFromDisk() {
156
81
  logConfig(`Error reading auth.json: ${e}`);
157
82
  }
158
83
  }
159
- // 3. Try OpenCode Config (Fallback)
84
+ // 3. OpenCode Config (Fallback)
160
85
  if (!keyFound) {
161
86
  try {
162
87
  if (fs.existsSync(OPENCODE_CONFIG_FILE)) {
@@ -173,7 +98,6 @@ function readConfigFromDisk() {
173
98
  }
174
99
  catch (e) { }
175
100
  }
176
- // Default mode correction
177
101
  if (!keyFound && config.mode === 'pro') {
178
102
  config.mode = 'manual';
179
103
  }
@@ -181,14 +105,12 @@ function readConfigFromDisk() {
181
105
  }
182
106
  export function saveConfig(updates) {
183
107
  try {
184
- // We must base updates on current state (even if cached is slightly old, we refresh first?)
185
- const current = readConfigFromDisk(); // Read disk for safety before write
108
+ const current = readConfigFromDisk();
186
109
  const updated = { ...current, ...updates, version: PKG_VERSION };
187
110
  if (!fs.existsSync(CONFIG_DIR_POLLI)) {
188
111
  fs.mkdirSync(CONFIG_DIR_POLLI, { recursive: true });
189
112
  }
190
113
  fs.writeFileSync(CONFIG_FILE, JSON.stringify(updated, null, 2));
191
- cachedConfig = updated; // Update Cache immediately
192
114
  return updated;
193
115
  }
194
116
  catch (e) {
@@ -2,18 +2,9 @@ import * as http from 'http';
2
2
  import * as fs from 'fs';
3
3
  import * as path from 'path';
4
4
  import { getAggregatedModels } from './pollinations-api.js';
5
- import { loadConfig, saveConfig, subscribeToConfigChange } from './config.js';
5
+ import { loadConfig, saveConfig } from './config.js';
6
6
  import { handleChatCompletion } from './proxy.js';
7
- import { emitStatusToast } from './toast.js';
8
7
  const LOG_FILE = path.join(process.env.HOME || '/tmp', '.config/opencode/plugins/pollinations-v3.log');
9
- // Hot Reload Listener
10
- subscribeToConfigChange(() => {
11
- const config = loadConfig();
12
- const mode = config.mode.toUpperCase();
13
- const hasKey = !!config.apiKey;
14
- log(`[HOT RELOAD] Config Updated. Mode: ${mode}, HasKey: ${hasKey}`);
15
- emitStatusToast('info', `Configuration Updated (Hot Reload) | Mode: ${mode}`, 'Pollinations System');
16
- });
17
8
  // Simple file logger
18
9
  function log(msg) {
19
10
  const ts = new Date().toISOString();
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.22",
4
+ "version": "5.2.0",
5
5
  "description": "Native Pollinations.ai Provider Plugin for OpenCode",
6
6
  "publisher": "pollinations",
7
7
  "repository": {