@monotykamary/pi-opencode-provider 1.0.5 → 1.0.7

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.
@@ -103,6 +103,46 @@ function convertModel(model) {
103
103
  };
104
104
  }
105
105
 
106
+ // Load a layered JSON model file.
107
+ function loadJson(fileName) {
108
+ try {
109
+ return JSON.parse(fs.readFileSync(path.join(process.cwd(), fileName), 'utf8'));
110
+ } catch {
111
+ return fileName === 'patch.json' ? {} : [];
112
+ }
113
+ }
114
+
115
+ // Deep-merge patch overrides into a model for README documentation. The
116
+ // generated models.json remains API-derived; only the displayed model map uses patches.
117
+ function applyPatch(model, patch) {
118
+ if (!patch) return model;
119
+ const result = { ...model };
120
+ for (const [key, value] of Object.entries(patch)) {
121
+ if (key === 'cost' || key === 'compat' || key === 'thinkingLevelMap') {
122
+ result[key] = { ...(result[key] || {}), ...value };
123
+ } else {
124
+ result[key] = value;
125
+ }
126
+ }
127
+ if (!result.reasoning) {
128
+ delete result.thinkingLevelMap;
129
+ if (result.compat?.thinkingFormat) delete result.compat.thinkingFormat;
130
+ }
131
+ return result;
132
+ }
133
+
134
+ // Merge API models, patches, and custom models in the same order as index.ts.
135
+ function buildModels(baseModels, customModels, patch) {
136
+ const byId = new Map(baseModels.map(model => [model.id, model]));
137
+ for (const [id, entry] of Object.entries(patch)) {
138
+ if (byId.has(id)) byId.set(id, applyPatch(byId.get(id), entry));
139
+ }
140
+ for (const model of customModels) {
141
+ byId.set(model.id, applyPatch(model, patch[model.id]));
142
+ }
143
+ return Array.from(byId.values());
144
+ }
145
+
106
146
  // Generate README model table row
107
147
  function generateReadmeRow(model) {
108
148
  const cost = model.cost || {};
@@ -151,6 +191,67 @@ ${tableRows}`;
151
191
  console.log(` Updated README.md with ${models.length} models`);
152
192
  }
153
193
 
194
+ // Grace period for delisted models: update-models.js moves models the API no
195
+ // longer lists into deprecated-models.json (stamped with deprecatedAt) instead
196
+ // of dropping them; the runtime appends them back so sessions and saved model
197
+ // settings keep working, and after 14 days they are evicted permanently.
198
+ const DEPRECATED_MODEL_TTL_MS = 14 * 24 * 60 * 60 * 1000;
199
+
200
+ /**
201
+ * Reconcile deprecated-models.json against the freshly fetched model list.
202
+ * - in old models.json but not the API: moved into the deprecated file
203
+ * (deprecatedAt = now; preserved on repeat runs so the grace clock is not reset)
204
+ * - back in the API: resurrected (dropped from the deprecated file)
205
+ * - deprecatedAt older than 14 days: evicted permanently
206
+ * Must run BEFORE the new models.json is written; it reads the old file itself.
207
+ */
208
+ function updateDeprecatedModels(modelsJsonPath, newModels) {
209
+ const deprecatedPath = path.join(path.dirname(modelsJsonPath), 'deprecated-models.json');
210
+
211
+ let oldModels = [];
212
+ try {
213
+ const parsed = JSON.parse(fs.readFileSync(modelsJsonPath, 'utf8'));
214
+ if (Array.isArray(parsed)) oldModels = parsed;
215
+ } catch { /* first run: no previous models.json */ }
216
+
217
+ let deprecated = {};
218
+ try {
219
+ const parsed = JSON.parse(fs.readFileSync(deprecatedPath, 'utf8'));
220
+ if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) deprecated = parsed;
221
+ } catch { /* no graveyard yet */ }
222
+
223
+ const currentIds = new Set(newModels.map(m => m.id));
224
+ const now = new Date().toISOString();
225
+ const added = [];
226
+ const resurrected = [];
227
+ const evicted = [];
228
+
229
+ for (const old of oldModels) {
230
+ if (old && old.id && !currentIds.has(old.id) && !deprecated[old.id]) {
231
+ deprecated[old.id] = { ...old, deprecatedAt: now };
232
+ added.push(old.id);
233
+ }
234
+ }
235
+
236
+ for (const [id, entry] of Object.entries(deprecated)) {
237
+ if (currentIds.has(id)) {
238
+ delete deprecated[id];
239
+ resurrected.push(id);
240
+ continue;
241
+ }
242
+ const removedAt = Date.parse(entry && entry.deprecatedAt ? entry.deprecatedAt : '');
243
+ if (Number.isNaN(removedAt) || Date.now() - removedAt > DEPRECATED_MODEL_TTL_MS) {
244
+ delete deprecated[id];
245
+ evicted.push(id);
246
+ }
247
+ }
248
+
249
+ if (added.length > 0 || resurrected.length > 0 || evicted.length > 0) {
250
+ fs.writeFileSync(deprecatedPath, JSON.stringify(deprecated, null, 2) + '\n');
251
+ console.log('Updated deprecated-models.json ' + JSON.stringify({ added, resurrected, evicted }));
252
+ }
253
+ }
254
+
154
255
  async function main() {
155
256
  console.log('Fetching models from API...');
156
257
 
@@ -174,11 +275,15 @@ async function main() {
174
275
  // Convert to Pi-native format and save to models.json
175
276
  const models = apiModels.map(convertModel);
176
277
  const modelsPath = path.join(process.cwd(), 'models.json');
278
+ // Move delisted models to deprecated-models.json BEFORE models.json is overwritten
279
+ updateDeprecatedModels(modelsPath, models);
177
280
  fs.writeFileSync(modelsPath, JSON.stringify(models, null, 2) + '\n');
178
281
  console.log(` Saved ${models.length} models to models.json`);
179
282
 
180
- // Update README
181
- updateReadme(models);
283
+ // Apply layered overrides for documentation: models.json → patch.json → custom-models.json.
284
+ const patch = loadJson('patch.json');
285
+ const customModels = Array.isArray(loadJson('custom-models.json')) ? loadJson('custom-models.json') : [];
286
+ updateReadme(buildModels(models, customModels, patch));
182
287
 
183
288
  console.log('\nDone!');
184
289
  } catch (error) {