opencode-pollinations-plugin 6.4.10 → 6.5.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.
Files changed (50) hide show
  1. package/README.de.md +67 -55
  2. package/README.es.md +79 -67
  3. package/README.fr.md +70 -58
  4. package/README.it.md +78 -66
  5. package/README.md +33 -29
  6. package/README.zh.md +77 -65
  7. package/dist/locales/de.json +82 -50
  8. package/dist/locales/en.json +84 -52
  9. package/dist/locales/es.json +82 -50
  10. package/dist/locales/fr.json +81 -49
  11. package/dist/locales/it.json +82 -50
  12. package/dist/locales/zh.json +82 -50
  13. package/dist/server/commands.js +112 -110
  14. package/dist/server/config.d.ts +22 -6
  15. package/dist/server/config.js +44 -4
  16. package/dist/server/connect-response.js +7 -7
  17. package/dist/server/models/cache.d.ts +23 -10
  18. package/dist/server/models/cache.js +46 -24
  19. package/dist/server/models/worker.js +4 -4
  20. package/dist/server/proxy.d.ts +6 -0
  21. package/dist/server/proxy.js +313 -210
  22. package/dist/server/quota.d.ts +23 -32
  23. package/dist/server/quota.js +44 -184
  24. package/dist/server/status.js +1 -2
  25. package/dist/server/toast.js +1 -1
  26. package/dist/tools/index.d.ts +2 -1
  27. package/dist/tools/index.js +3 -1
  28. package/dist/tools/pollinations/artifact-core.d.ts +53 -0
  29. package/dist/tools/pollinations/artifact-core.js +159 -0
  30. package/dist/tools/pollinations/beta_discovery.js +2 -1
  31. package/dist/tools/pollinations/cost-guard.d.ts +2 -2
  32. package/dist/tools/pollinations/error-parser.d.ts +38 -0
  33. package/dist/tools/pollinations/error-parser.js +112 -0
  34. package/dist/tools/pollinations/gen_3d.d.ts +17 -0
  35. package/dist/tools/pollinations/gen_3d.js +207 -0
  36. package/dist/tools/pollinations/gen_image.js +29 -10
  37. package/dist/tools/pollinations/gen_music.js +3 -2
  38. package/dist/tools/pollinations/gen_video.js +13 -2
  39. package/dist/tools/pollinations/polli_config.js +15 -21
  40. package/dist/tools/pollinations/polli_gen_confirm.js +2 -0
  41. package/dist/tools/pollinations/shared.d.ts +1 -1
  42. package/dist/tools/pollinations/shared.js +53 -142
  43. package/dist/tools/pollinations/timeout-policy.d.ts +80 -0
  44. package/dist/tools/pollinations/timeout-policy.js +124 -0
  45. package/dist/tools/pollinations/tool-capability-registry.d.ts +51 -0
  46. package/dist/tools/pollinations/tool-capability-registry.js +215 -0
  47. package/dist/tools/pollinations/transcribe_audio.js +5 -24
  48. package/package.json +6 -4
  49. package/dist/server/tier-info.d.ts +0 -36
  50. package/dist/server/tier-info.js +0 -107
@@ -3,6 +3,9 @@
3
3
  *
4
4
  * Central access point for all model metadata. Backed by the fetcher
5
5
  * with a configurable TTL. Falls back to static data if fetch fails.
6
+ *
7
+ * v6.5: every read path (get/list/all) triggers a coalesced, non-blocking
8
+ * freshness check, so long sessions always converge to the live catalog.
6
9
  */
7
10
  import * as fs from 'fs';
8
11
  import * as path from 'path';
@@ -60,31 +63,41 @@ function saveCacheToDisk(models, timestamp) {
60
63
  }
61
64
  }
62
65
  // ─── Registry Implementation ─────────────────────────────────────────────
63
- class ModelRegistryImpl {
66
+ export class ModelRegistryImpl {
64
67
  models = [];
65
68
  lastRefresh = 0;
66
69
  ttl = DEFAULT_TTL;
67
70
  ready = false;
68
- refreshing = false;
69
- constructor() {
70
- const diskCache = loadCacheFromDisk();
71
- if (diskCache && (Date.now() - diskCache.timestamp) < this.ttl) {
72
- this.models = diskCache.models;
73
- this.lastRefresh = diskCache.timestamp;
74
- this.ready = true;
75
- log(`[ModelRegistry] Loaded ${this.models.length} models from disk cache.`);
71
+ refreshing = null;
72
+ fetcher;
73
+ useDiskCache;
74
+ constructor(options = {}) {
75
+ this.ttl = options.ttlMs ?? DEFAULT_TTL;
76
+ this.fetcher = options.fetcher ?? ((apiKey) => fetchAllModels(apiKey));
77
+ this.useDiskCache = options.diskCache ?? true;
78
+ if (this.useDiskCache) {
79
+ const diskCache = loadCacheFromDisk();
80
+ if (diskCache && (Date.now() - diskCache.timestamp) < this.ttl) {
81
+ this.models = diskCache.models;
82
+ this.lastRefresh = diskCache.timestamp;
83
+ this.ready = true;
84
+ log(`[ModelRegistry] Loaded ${this.models.length} models from disk cache.`);
85
+ }
76
86
  }
77
87
  }
78
- /** Get a single model by category and name */
88
+ /** Get a single model by category and name. Triggers lazy freshness check. */
79
89
  get(category, name) {
90
+ this.ensureFresh().catch(() => { });
80
91
  return this.models.find(m => m.category === category && m.name === name);
81
92
  }
82
- /** Also search by alias */
93
+ /** Also search by alias. Triggers lazy freshness check. */
83
94
  getByNameOrAlias(category, name) {
95
+ this.ensureFresh().catch(() => { });
84
96
  return this.models.find(m => m.category === category && (m.name === name || m.aliases.includes(name)));
85
97
  }
86
- /** List all models in a category */
98
+ /** List all models in a category. Triggers lazy freshness check. */
87
99
  list(category) {
100
+ this.ensureFresh().catch(() => { });
88
101
  return this.models.filter(m => m.category === category);
89
102
  }
90
103
  /** Check if registry has been populated */
@@ -95,19 +108,29 @@ class ModelRegistryImpl {
95
108
  isStale() {
96
109
  return Date.now() - this.lastRefresh > this.ttl;
97
110
  }
98
- /** Force refresh from API */
99
- async refresh(apiKey) {
111
+ /** Timestamp of last successful refresh (for tests/diagnostics). */
112
+ lastRefreshAt() {
113
+ return this.lastRefresh;
114
+ }
115
+ /** Force refresh from API. Concurrent calls are coalesced on one fetch. */
116
+ refresh(apiKey) {
100
117
  if (this.refreshing)
101
- return; // Prevent concurrent refreshes
102
- this.refreshing = true;
118
+ return this.refreshing; // coalesce concurrent refreshes
119
+ this.refreshing = this.performRefresh(apiKey).finally(() => {
120
+ this.refreshing = null;
121
+ });
122
+ return this.refreshing;
123
+ }
124
+ async performRefresh(apiKey) {
103
125
  try {
104
126
  const key = apiKey || loadConfig().apiKey;
105
- const fetched = await fetchAllModels(key);
127
+ const fetched = await this.fetcher(key);
106
128
  if (fetched.length > 0) {
107
129
  this.models = fetched;
108
130
  this.lastRefresh = Date.now();
109
131
  this.ready = true;
110
- saveCacheToDisk(this.models, this.lastRefresh);
132
+ if (this.useDiskCache)
133
+ saveCacheToDisk(this.models, this.lastRefresh);
111
134
  log(`[ModelRegistry] Refreshed: ${this.models.length} models cached to disk.`);
112
135
  }
113
136
  else {
@@ -132,19 +155,18 @@ class ModelRegistryImpl {
132
155
  log(`[ModelRegistry] Refresh failed, keeping cache: ${e}`);
133
156
  }
134
157
  }
135
- finally {
136
- this.refreshing = false;
137
- }
138
158
  }
139
- /** Get all models across all categories */
159
+ /** Get all models across all categories. Triggers lazy freshness check. */
140
160
  all() {
161
+ this.ensureFresh().catch(() => { });
141
162
  return [...this.models];
142
163
  }
143
- /** Auto-refresh if stale (non-blocking) */
164
+ /** Auto-refresh if stale (non-blocking). Returns the refresh promise for tests. */
144
165
  ensureFresh() {
145
166
  if (this.isStale()) {
146
- this.refresh().catch(() => { }); // Fire-and-forget
167
+ return this.refresh(); // coalesced; refresh() never rejects (handles offline)
147
168
  }
169
+ return Promise.resolve();
148
170
  }
149
171
  /** Get count per category (for logging) */
150
172
  stats() {
@@ -54,7 +54,7 @@ export class ToolRegistryWorker {
54
54
  const videoModels = ModelRegistry.list('video');
55
55
  if (videoModels.length > 0 && polliGenVideoTool.description) {
56
56
  let videoTable = `\n\n**🎬 Modèles Vidéo Détectés (${videoModels.length}) :**\n`;
57
- videoTable += `*(Légende: [💎 Paid] = Enter Tier (Consomme le Wallet USD), [🌿 Free] = Free Tier (Consomme d'abord le Quota Pollen Gratuit Journalier))*\n`;
57
+ videoTable += `*(Légende: [💎 Paid] = Pollen acheté (consomme le Wallet USD), [🌿 Free] = Quest Pollen (consomme d'abord le Quota Gratuit Journalier))*\n`;
58
58
  videoTable += `| Modèle | Source I/O | Audio | 1 pollen ≈ | Specs |\n`;
59
59
  videoTable += `|--------|------------|-------|------------|-------|\n`;
60
60
  for (const m of videoModels) {
@@ -76,7 +76,7 @@ export class ToolRegistryWorker {
76
76
  const imageModels = ModelRegistry.list('image');
77
77
  if (imageModels.length > 0 && polliGenImageTool.description) {
78
78
  let imageTable = `\n\n**🎨 Modèles Image Détectés (${imageModels.length}) :**\n`;
79
- imageTable += `*(Légende: [💎 Paid] = Enter Tier (Consomme le Wallet USD), [🌿 Free] = Free Tier (Consomme d'abord le Quota Pollen Gratuit Journalier))*\n`;
79
+ imageTable += `*(Légende: [💎 Paid] = Pollen acheté (consomme le Wallet USD), [🌿 Free] = Quest Pollen (consomme d'abord le Quota Gratuit Journalier))*\n`;
80
80
  imageTable += `| Modèle | I2I | Qualité | 1 pollen ≈ |\n`;
81
81
  imageTable += `|--------|-----|---------|------------|\n`;
82
82
  // Afficher max 20 modèles pour éviter de saturer le prompt LLM
@@ -101,7 +101,7 @@ export class ToolRegistryWorker {
101
101
  const audioModels = ModelRegistry.list('audio');
102
102
  if (audioModels.length > 0 && polliGenAudioTool.description && polliGenMusicTool.description) {
103
103
  let audioTable = `\n\n**🎵 Modèles Audio/Music Détectés (${audioModels.length}) :**\n`;
104
- audioTable += `*(Légende: [💎 Paid] = Enter Tier (Consomme le Wallet USD), [🌿 Free] = Free Tier (Consomme d'abord le Quota Pollen Gratuit Journalier))*\n`;
104
+ audioTable += `*(Légende: [💎 Paid] = Pollen acheté (consomme le Wallet USD), [🌿 Free] = Quest Pollen (consomme d'abord le Quota Gratuit Journalier))*\n`;
105
105
  audioTable += `| Modèle | Durée max | Qualité |\n`;
106
106
  audioTable += `|--------|-----------|---------|\n`;
107
107
  for (const m of audioModels) {
@@ -134,7 +134,7 @@ export class ToolRegistryWorker {
134
134
  });
135
135
  if (searchModels.length > 0) {
136
136
  let searchTable = `\n\n**🌍 Modèles de Recherche & Grounding Détectés (${searchModels.length}) :**\n`;
137
- searchTable += `*(Légende: [💎 Paid] = Enter Tier, [🌿 Free] = Quota Gratuit. Obligatoire de choisir un modèle exact au lieu de 'deep' ou 'rapid' !)*\n`;
137
+ searchTable += `*(Légende: [💎 Paid] = Pollen acheté, [🌿 Free] = Quest (Quota Gratuit). Obligatoire de choisir un modèle exact au lieu de 'deep' ou 'rapid' !)*\n`;
138
138
  searchTable += `| Modèle | Description / Specs |\n`;
139
139
  searchTable += `|--------|---------------------|\n`;
140
140
  for (const m of searchModels) {
@@ -1,2 +1,8 @@
1
1
  import * as http from 'http';
2
+ type RetrySignal = 'abort' | 'network' | number;
3
+ export declare function classifyRetry(signal: RetrySignal): 'RETRY' | 'NO_RETRY';
4
+ export declare function fetchWithRetry(url: string, options: any, retries?: number): Promise<Response>;
5
+ /** Normalize a single raw SSE `data:` payload line (JSON). Non-JSON passes through. */
6
+ export declare function normalizeChunkLine(payload: string): string;
2
7
  export declare function handleChatCompletion(req: http.IncomingMessage, res: http.ServerResponse, bodyRaw: string): Promise<void>;
8
+ export {};