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 +10 -11
- package/dist/index.js +11 -30
- package/dist/server/config.d.ts +0 -2
- package/dist/server/config.js +13 -91
- package/dist/server/index.js +1 -10
- package/package.json +1 -1
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
|
-
###
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
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
|
|
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: "
|
|
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
|
|
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
|
|
87
|
-
|
|
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);
|
|
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
|
-
//
|
|
104
|
-
|
|
105
|
-
|
|
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.
|
|
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),
|
|
112
|
+
...createStatusHooks(ctx.client),
|
|
132
113
|
...commandHooks
|
|
133
114
|
};
|
|
134
115
|
};
|
package/dist/server/config.d.ts
CHANGED
|
@@ -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";
|
package/dist/server/config.js
CHANGED
|
@@ -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.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
55
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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) {
|
package/dist/server/index.js
CHANGED
|
@@ -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
|
|
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