pi-lilac-provider 1.9.1 → 1.9.2

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.
@@ -0,0 +1 @@
1
+ {}
package/index.ts CHANGED
@@ -88,6 +88,7 @@ import { getAgentDir, type ExtensionAPI, type ModelRegistry } from "@earendil-wo
88
88
  import modelsData from "./models.json" with { type: "json" };
89
89
  import customModelsData from "./custom-models.json" with { type: "json" };
90
90
  import patchData from "./patch.json" with { type: "json" };
91
+ import deprecatedData from "./deprecated-models.json" with { type: "json" };
91
92
  import fs from "fs";
92
93
  import path from "path";
93
94
 
@@ -379,6 +380,35 @@ function applyPatch(model: JsonModel, patch: PatchEntry): JsonModel {
379
380
  }
380
381
 
381
382
  /** Full pipeline: base models → patch → custom → user modelOverrides → result */
383
+ // Grace period for delisted models. When the provider API stops listing a
384
+ // model, update-models.js moves its last-known definition into
385
+ // deprecated-models.json (stamped with deprecatedAt) instead of dropping it.
386
+ // For 14 days the model keeps working here so in-flight sessions and saved
387
+ // model settings do not break; afterwards it is evicted permanently.
388
+ const DEPRECATED_MODEL_TTL_MS = 14 * 24 * 60 * 60 * 1000;
389
+
390
+ // Grace-period deprecated models with deprecation metadata stripped.
391
+ function activeDeprecatedModels(): JsonModel[] {
392
+ const now = Date.now();
393
+ const result: JsonModel[] = [];
394
+ for (const entry of Object.values(deprecatedData as Record<string, JsonModel & { deprecatedAt?: string }>)) {
395
+ if (!entry?.id) continue;
396
+ const removedAt = Date.parse(entry.deprecatedAt ?? "");
397
+ if (Number.isNaN(removedAt) || now - removedAt > DEPRECATED_MODEL_TTL_MS) continue;
398
+ const model = { ...entry } as JsonModel & { deprecatedAt?: string };
399
+ delete model.deprecatedAt;
400
+ result.push(model);
401
+ }
402
+ return result;
403
+ }
404
+
405
+ // Append grace-period deprecated models the list does not already have (live data wins).
406
+ function withDeprecated(models: JsonModel[]): JsonModel[] {
407
+ const seen = new Set(models.map((m) => m.id));
408
+ const extras = activeDeprecatedModels().filter((m) => !seen.has(m.id));
409
+ return extras.length > 0 ? [...models, ...extras] : models;
410
+ }
411
+
382
412
  function buildModels(
383
413
  base: JsonModel[],
384
414
  custom: JsonModel[],
@@ -424,7 +454,7 @@ function buildModels(
424
454
  }
425
455
  }
426
456
 
427
- return Array.from(modelMap.values());
457
+ return withDeprecated(Array.from(modelMap.values()));
428
458
  }
429
459
 
430
460
  // ─── Stale-While-Revalidate Model Sync ────────────────────────────────────────
package/package.json CHANGED
@@ -1,12 +1,13 @@
1
1
  {
2
2
  "name": "pi-lilac-provider",
3
- "version": "1.9.1",
3
+ "version": "1.9.2",
4
4
  "description": "Lilac provider extension for pi - Access Kimi K2.6, GLM 5.1, and Gemma 4 models through Lilac's OpenAI-compatible API on idle GPUs",
5
5
  "type": "module",
6
6
  "main": "index.ts",
7
7
  "files": [
8
8
  "index.ts",
9
9
  "models.json",
10
+ "deprecated-models.json",
10
11
  "patch.json",
11
12
  "custom-models.json",
12
13
  "scripts"
@@ -29,7 +30,7 @@
29
30
  "@earendil-works/pi-coding-agent": ">=0.74.0"
30
31
  },
31
32
  "devDependencies": {
32
- "@earendil-works/pi-coding-agent": "0.80.3",
33
+ "@earendil-works/pi-coding-agent": "0.83.0",
33
34
  "typescript": "6.0.3",
34
35
  "@types/node": "25.9.1",
35
36
  "knip": "6.14.1"
@@ -295,6 +295,67 @@ function updateReadme(models) {
295
295
 
296
296
  // ─── Main ────────────────────────────────────────────────────────────────────
297
297
 
298
+ // Grace period for delisted models: update-models.js moves models the API no
299
+ // longer lists into deprecated-models.json (stamped with deprecatedAt) instead
300
+ // of dropping them; the runtime appends them back so sessions and saved model
301
+ // settings keep working, and after 14 days they are evicted permanently.
302
+ const DEPRECATED_MODEL_TTL_MS = 14 * 24 * 60 * 60 * 1000;
303
+
304
+ /**
305
+ * Reconcile deprecated-models.json against the freshly fetched model list.
306
+ * - in old models.json but not the API: moved into the deprecated file
307
+ * (deprecatedAt = now; preserved on repeat runs so the grace clock is not reset)
308
+ * - back in the API: resurrected (dropped from the deprecated file)
309
+ * - deprecatedAt older than 14 days: evicted permanently
310
+ * Must run BEFORE the new models.json is written; it reads the old file itself.
311
+ */
312
+ function updateDeprecatedModels(modelsJsonPath, newModels) {
313
+ const deprecatedPath = path.join(path.dirname(modelsJsonPath), 'deprecated-models.json');
314
+
315
+ let oldModels = [];
316
+ try {
317
+ const parsed = JSON.parse(fs.readFileSync(modelsJsonPath, 'utf8'));
318
+ if (Array.isArray(parsed)) oldModels = parsed;
319
+ } catch { /* first run: no previous models.json */ }
320
+
321
+ let deprecated = {};
322
+ try {
323
+ const parsed = JSON.parse(fs.readFileSync(deprecatedPath, 'utf8'));
324
+ if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) deprecated = parsed;
325
+ } catch { /* no graveyard yet */ }
326
+
327
+ const currentIds = new Set(newModels.map(m => m.id));
328
+ const now = new Date().toISOString();
329
+ const added = [];
330
+ const resurrected = [];
331
+ const evicted = [];
332
+
333
+ for (const old of oldModels) {
334
+ if (old && old.id && !currentIds.has(old.id) && !deprecated[old.id]) {
335
+ deprecated[old.id] = { ...old, deprecatedAt: now };
336
+ added.push(old.id);
337
+ }
338
+ }
339
+
340
+ for (const [id, entry] of Object.entries(deprecated)) {
341
+ if (currentIds.has(id)) {
342
+ delete deprecated[id];
343
+ resurrected.push(id);
344
+ continue;
345
+ }
346
+ const removedAt = Date.parse(entry && entry.deprecatedAt ? entry.deprecatedAt : '');
347
+ if (Number.isNaN(removedAt) || Date.now() - removedAt > DEPRECATED_MODEL_TTL_MS) {
348
+ delete deprecated[id];
349
+ evicted.push(id);
350
+ }
351
+ }
352
+
353
+ if (added.length > 0 || resurrected.length > 0 || evicted.length > 0) {
354
+ fs.writeFileSync(deprecatedPath, JSON.stringify(deprecated, null, 2) + '\n');
355
+ console.log('Updated deprecated-models.json ' + JSON.stringify({ added, resurrected, evicted }));
356
+ }
357
+ }
358
+
298
359
  async function main() {
299
360
  try {
300
361
  const apiModels = await fetchModels();
@@ -318,6 +379,8 @@ async function main() {
318
379
  models.sort((a, b) => a.name.localeCompare(b.name));
319
380
 
320
381
  // Save models.json (pure API output, no patch/custom baked in)
382
+ // Move delisted models to deprecated-models.json BEFORE models.json is overwritten
383
+ updateDeprecatedModels(MODELS_JSON_PATH, models);
321
384
  saveJson(MODELS_JSON_PATH, models);
322
385
 
323
386
  // Build full model list for README: base → patch → custom