opencode-pollinations-plugin 5.8.3 → 5.8.4-beta.1

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.
@@ -28,23 +28,39 @@ function log(msg) {
28
28
  catch (e) { }
29
29
  }
30
30
  // --- NETWORK HELPER ---
31
+ function fetchHead(url) {
32
+ return new Promise((resolve) => {
33
+ // Use Node.js native https check for minimal overhead
34
+ const req = https.request(url, { method: 'HEAD', timeout: 5000 }, (res) => {
35
+ resolve(res.headers['etag'] || null);
36
+ });
37
+ req.on('error', () => resolve(null));
38
+ req.on('timeout', () => { req.destroy(); resolve(null); });
39
+ req.end();
40
+ });
41
+ }
31
42
  function fetchJson(url, headers = {}) {
32
43
  return new Promise((resolve, reject) => {
33
44
  const finalHeaders = {
34
45
  ...headers,
35
- 'User-Agent': 'Mozilla/5.0 (compatible; OpenCode/5.8.2; +https://opencode.ai)'
46
+ 'User-Agent': 'Mozilla/5.0 (compatible; OpenCode/5.8.4; +https://opencode.ai)'
36
47
  };
37
48
  const req = https.get(url, { headers: finalHeaders }, (res) => {
49
+ const etag = res.headers['etag'];
38
50
  let data = '';
39
51
  res.on('data', chunk => data += chunk);
40
52
  res.on('end', () => {
41
53
  try {
42
54
  const json = JSON.parse(data);
55
+ // HACK: Attach ETag to the object to pass it up
56
+ if (etag && typeof json === 'object') {
57
+ Object.defineProperty(json, '_etag', { value: etag, enumerable: false, writable: true });
58
+ }
43
59
  resolve(json);
44
60
  }
45
61
  catch (e) {
46
62
  log(`JSON Parse Error for ${url}: ${e}`);
47
- resolve([]); // Fail safe -> empty list to trigger fallback logic
63
+ resolve([]); // Fail safe
48
64
  }
49
65
  });
50
66
  });
@@ -58,7 +74,6 @@ function fetchJson(url, headers = {}) {
58
74
  });
59
75
  });
60
76
  }
61
- // --- CACHE MANAGER ---
62
77
  function loadCache() {
63
78
  try {
64
79
  if (fs.existsSync(CACHE_FILE)) {
@@ -71,10 +86,11 @@ function loadCache() {
71
86
  }
72
87
  return null;
73
88
  }
74
- function saveCache(models) {
89
+ function saveCache(models, etag) {
75
90
  try {
76
91
  const data = {
77
92
  timestamp: Date.now(),
93
+ etag: etag,
78
94
  models: models
79
95
  };
80
96
  if (!fs.existsSync(CONFIG_DIR_POLLI))
@@ -89,25 +105,43 @@ function saveCache(models) {
89
105
  export async function generatePollinationsConfig(forceApiKey, forceStrict = false) {
90
106
  const config = loadConfig();
91
107
  const modelsOutput = [];
92
- log(`Starting Configuration (v5.8.2-Robust)...`);
108
+ log(`Starting Configuration (v5.8.4-Beta1)...`);
93
109
  const effectiveKey = forceApiKey || config.apiKey;
94
- // 1. FREE UNIVERSE (Cache System)
110
+ // 1. FREE UNIVERSE (Smart Cache System)
95
111
  let freeModelsList = [];
96
112
  let isOffline = false;
97
113
  let cache = loadCache();
98
114
  const CACHE_TTL = 7 * 24 * 3600 * 1000; // 7 days
99
- // Decision: Fetch or Cache?
115
+ // Decision Logic
100
116
  const now = Date.now();
101
117
  let shouldFetch = !cache || (now - cache.timestamp > CACHE_TTL);
118
+ // ETag Check: If cache is valid but we want to be proactive
119
+ if (!shouldFetch && cache && cache.etag) {
120
+ try {
121
+ log('Smart Refresh: Checking for updates (HEAD)...');
122
+ const remoteEtag = await fetchHead('https://text.pollinations.ai/models');
123
+ if (remoteEtag && remoteEtag !== cache.etag) {
124
+ log(`Update Detected! (Remote: ${remoteEtag} != Local: ${cache.etag}). Forcing refresh.`);
125
+ shouldFetch = true;
126
+ }
127
+ else {
128
+ log('Cache is clean (ETag match). No refresh needed.');
129
+ }
130
+ }
131
+ catch (e) {
132
+ log(`Smart Refresh check failed: ${e}. Ignoring.`);
133
+ }
134
+ }
102
135
  if (shouldFetch) {
103
- log('Attempting to fetch fresh Free models...');
136
+ log('Fetching fresh Free models...');
104
137
  try {
105
138
  const raw = await fetchJson('https://text.pollinations.ai/models');
106
139
  const list = Array.isArray(raw) ? raw : (raw.data || []);
140
+ const newEtag = raw._etag; // Get hidden ETag
107
141
  if (list.length > 0) {
108
142
  freeModelsList = list;
109
- saveCache(list);
110
- log(`Fetched and cached ${list.length} models.`);
143
+ saveCache(list, newEtag);
144
+ log(`Fetched and cached ${list.length} models (ETag: ${newEtag || 'N/A'}).`);
111
145
  }
112
146
  else {
113
147
  throw new Error('API returned empty list');
@@ -116,7 +150,7 @@ export async function generatePollinationsConfig(forceApiKey, forceStrict = fals
116
150
  catch (e) {
117
151
  log(`Fetch failed: ${e}.`);
118
152
  isOffline = true;
119
- // Fallback to Cache or Default
153
+ // Fallback Logic
120
154
  if (cache && cache.models.length > 0) {
121
155
  log('Using cached models (Offline).');
122
156
  freeModelsList = cache.models;
@@ -128,15 +162,13 @@ export async function generatePollinationsConfig(forceApiKey, forceStrict = fals
128
162
  }
129
163
  }
130
164
  else {
131
- log('Cache is recent. Using cached models.');
165
+ log('Using cached models (Skipped fetch).');
132
166
  freeModelsList = cache.models;
133
167
  }
134
168
  // Map Free Models
135
169
  freeModelsList.forEach((m) => {
136
- // Appending (Offline) if we are in offline mode due to error,
137
- // OR (Cache) if we just used cache? User said: "rajoutant dans les noms de modeles à la fin (down)"
138
- // when valid list is reached but date > 8 days (deprecated) or fallback used?
139
- // Let's mark it only if we tried to fetch and failed.
170
+ // Tag (Offline) only if we explicitly failed a fetch attempt or are using Fallback SEED when fetch failed.
171
+ // If we use cache because it's valid (Skipped fetch), we don't tag (Offline).
140
172
  const suffix = isOffline ? ' (Offline)' : '';
141
173
  const mapped = mapModel(m, 'free/', `[Free] `, suffix);
142
174
  modelsOutput.push(mapped);
@@ -160,7 +192,6 @@ export async function generatePollinationsConfig(forceApiKey, forceStrict = fals
160
192
  log(`Error fetching Enterprise models: ${e}`);
161
193
  if (forceStrict)
162
194
  throw e;
163
- // Fallback Enter (could be cached too in future)
164
195
  modelsOutput.push({ id: "enter/gpt-4o", name: "[Enter] GPT-4o (Fallback)", object: "model", variants: {} });
165
196
  }
166
197
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "opencode-pollinations-plugin",
3
3
  "displayName": "Pollinations AI (V5.6)",
4
- "version": "5.8.3",
4
+ "version": "5.8.4-beta.1",
5
5
  "description": "Native Pollinations.ai Provider Plugin for OpenCode",
6
6
  "publisher": "pollinations",
7
7
  "repository": {
@@ -55,4 +55,4 @@
55
55
  "@types/node": "^20.0.0",
56
56
  "typescript": "^5.0.0"
57
57
  }
58
- }
58
+ }