pi-hypercharm-provider 1.3.0 → 1.3.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.
@@ -1,95 +1,6 @@
1
1
  {
2
2
  "format": 1,
3
- "entries": {
4
- "topology/hosts/b98efd3cf26df4c69c6deae014475c4c0c080b1d0e5ef1cb0ebcc8dca7501c85": {
5
- "key": "topology/hosts/b98efd3cf26df4c69c6deae014475c4c0c080b1d0e5ef1cb0ebcc8dca7501c85",
6
- "value": {
7
- "format": 1,
8
- "id": "session:01a00efe-4e23-7b2b-8583-2e8493780899",
9
- "rootId": "session:01a00efe-4e23-7b2b-8583-2e8493780899",
10
- "identity": {
11
- "id": "session:01a00efe-4e23-7b2b-8583-2e8493780899",
12
- "name": "main",
13
- "kind": "main",
14
- "sessionId": "01a00efe-4e23-7b2b-8583-2e8493780899"
15
- },
16
- "startedAt": 1786958035702,
17
- "updatedAt": 1786958850932,
18
- "expiresAt": 1786958865932
19
- },
20
- "version": 196,
21
- "updatedAt": 1786958850934,
22
- "updatedBy": {
23
- "id": "session:01a00efe-4e23-7b2b-8583-2e8493780899",
24
- "name": "main",
25
- "kind": "main",
26
- "sessionId": "01a00efe-4e23-7b2b-8583-2e8493780899"
27
- }
28
- },
29
- "sessions/01a00efe-4e23-7b2b-8583-2e8493780899": {
30
- "key": "sessions/01a00efe-4e23-7b2b-8583-2e8493780899",
31
- "value": {
32
- "id": "session:01a00efe-4e23-7b2b-8583-2e8493780899",
33
- "name": "Peer 01a00efe",
34
- "kind": "peer",
35
- "status": "running",
36
- "runner": "pi",
37
- "transport": "host",
38
- "cwd": "/Users/monotykamary/VCS/working-remote/open-source/pi-hypercharm-provider",
39
- "sessionId": "01a00efe-4e23-7b2b-8583-2e8493780899",
40
- "model": "neuralwatt/kimi-k3",
41
- "thinking": "max",
42
- "startedAt": 1786958035702,
43
- "updatedAt": 1786958850932,
44
- "pendingMessages": false,
45
- "local": false
46
- },
47
- "version": 196,
48
- "updatedAt": 1786958850935,
49
- "updatedBy": {
50
- "id": "session:01a00efe-4e23-7b2b-8583-2e8493780899",
51
- "name": "main",
52
- "kind": "main",
53
- "sessionId": "01a00efe-4e23-7b2b-8583-2e8493780899"
54
- }
55
- },
56
- "topology/participants/b98efd3cf26df4c69c6deae014475c4c0c080b1d0e5ef1cb0ebcc8dca7501c85": {
57
- "key": "topology/participants/b98efd3cf26df4c69c6deae014475c4c0c080b1d0e5ef1cb0ebcc8dca7501c85",
58
- "value": {
59
- "format": 1,
60
- "id": "session:01a00efe-4e23-7b2b-8583-2e8493780899",
61
- "kind": "root",
62
- "rootId": "session:01a00efe-4e23-7b2b-8583-2e8493780899",
63
- "ownerHostId": "session:01a00efe-4e23-7b2b-8583-2e8493780899",
64
- "ownerIdentityId": "session:01a00efe-4e23-7b2b-8583-2e8493780899",
65
- "name": "main",
66
- "status": "running",
67
- "runner": "pi",
68
- "transport": "host",
69
- "capabilities": [
70
- "steer",
71
- "followUp",
72
- "fabric"
73
- ],
74
- "cwd": "/Users/monotykamary/VCS/working-remote/open-source/pi-hypercharm-provider",
75
- "sessionId": "01a00efe-4e23-7b2b-8583-2e8493780899",
76
- "model": "neuralwatt/kimi-k3",
77
- "thinking": "max",
78
- "startedAt": 1786958035702,
79
- "updatedAt": 1786958850932,
80
- "pendingMessages": false,
81
- "controlProtocol": "v1"
82
- },
83
- "version": 196,
84
- "updatedAt": 1786958850936,
85
- "updatedBy": {
86
- "id": "session:01a00efe-4e23-7b2b-8583-2e8493780899",
87
- "name": "main",
88
- "kind": "main",
89
- "sessionId": "01a00efe-4e23-7b2b-8583-2e8493780899"
90
- }
91
- }
92
- },
3
+ "entries": {},
93
4
  "versions": {
94
5
  "topology/hosts/e9758aa8c95305a8d60a8aa2c20fd53195591d479b2460e1273457793d3f1fec": 18,
95
6
  "sessions/019fe2c8-1dd2-7435-9fa1-fa664831df18": 17,
@@ -100,9 +11,9 @@
100
11
  "topology/hosts/3195ffccdcff6cb767344b2d1af6c5f3e1e3e4a9906a81129ffaf24352769d2a": 217,
101
12
  "sessions/01a00c57-b7aa-7ea3-b78a-11e22d3cdd86": 216,
102
13
  "topology/participants/3195ffccdcff6cb767344b2d1af6c5f3e1e3e4a9906a81129ffaf24352769d2a": 217,
103
- "topology/hosts/b98efd3cf26df4c69c6deae014475c4c0c080b1d0e5ef1cb0ebcc8dca7501c85": 196,
104
- "sessions/01a00efe-4e23-7b2b-8583-2e8493780899": 196,
105
- "topology/participants/b98efd3cf26df4c69c6deae014475c4c0c080b1d0e5ef1cb0ebcc8dca7501c85": 196
14
+ "topology/hosts/b98efd3cf26df4c69c6deae014475c4c0c080b1d0e5ef1cb0ebcc8dca7501c85": 388,
15
+ "sessions/01a00efe-4e23-7b2b-8583-2e8493780899": 387,
16
+ "topology/participants/b98efd3cf26df4c69c6deae014475c4c0c080b1d0e5ef1cb0ebcc8dca7501c85": 388
106
17
  },
107
18
  "tombstoneOrder": [
108
19
  "sessions/019fe2c8-1dd2-7435-9fa1-fa664831df18",
@@ -113,6 +24,9 @@
113
24
  "topology/hosts/e0efb9d647e046c355b8bff12785e7805d774e7b89892edb38b4b29667ddc15f",
114
25
  "sessions/01a00c57-b7aa-7ea3-b78a-11e22d3cdd86",
115
26
  "topology/participants/3195ffccdcff6cb767344b2d1af6c5f3e1e3e4a9906a81129ffaf24352769d2a",
116
- "topology/hosts/3195ffccdcff6cb767344b2d1af6c5f3e1e3e4a9906a81129ffaf24352769d2a"
27
+ "topology/hosts/3195ffccdcff6cb767344b2d1af6c5f3e1e3e4a9906a81129ffaf24352769d2a",
28
+ "sessions/01a00efe-4e23-7b2b-8583-2e8493780899",
29
+ "topology/participants/b98efd3cf26df4c69c6deae014475c4c0c080b1d0e5ef1cb0ebcc8dca7501c85",
30
+ "topology/hosts/b98efd3cf26df4c69c6deae014475c4c0c080b1d0e5ef1cb0ebcc8dca7501c85"
117
31
  ]
118
32
  }
package/README.md CHANGED
@@ -27,21 +27,21 @@ _Hyperoptimized coding models — DeepSeek, GLM, Kimi, Qwen, MiniMax, Gemma, GPT
27
27
  | Model | Type | Context | Max Tokens | Input Cost | Output Cost |
28
28
  |-------|------|---------|------------|------------|-------------|
29
29
  | DeepSeek V4 Flash | Text | 1.0M | 384K | $0.20 | $0.40 |
30
- | DeepSeek V4 Flash 0731 | Text | 1.0M | 384K | $0.20 | $0.40 |
30
+ | DeepSeek V4 Flash 0731 | Text | 1.0M | 384K | $0.13 | $0.26 |
31
31
  | DeepSeek V4 Pro | Text | 1.0M | 384K | $2.40 | $4.80 |
32
32
  | DeepSeek V4 Pro 0813 | Text | 1.0M | 262K | $1.44 | $4.31 |
33
33
  | Gemma 4 26B A4B | Text | 256K | 26K | $0.12 | $0.42 |
34
- | GLM-5 | Text | 203K | 20K | $0.76 | $2.36 |
35
- | GLM-5.1 | Text | 203K | 3K | $1.52 | $4.79 |
34
+ | GLM-5 | Text | 203K | 20K | $0.84 | $2.61 |
35
+ | GLM-5.1 | Text | 203K | 3K | $1.36 | $4.40 |
36
36
  | GLM-5.2 | Text | 1.0M | 33K | $1.40 | $4.40 |
37
37
  | gpt-oss-120b | Text | 131K | 13K | $0.16 | $0.65 |
38
- | Kimi K2.5 | Text | 262K | 26K | $0.56 | $2.94 |
38
+ | Kimi K2.5 | Text | 262K | 26K | $0.57 | $3.00 |
39
39
  | Kimi K2.6 | Text + Image | 262K | 26K | $0.95 | $4.00 |
40
40
  | Kimi K2.7 Code | Text + Image | 256K | 16K | $0.95 | $4.00 |
41
41
  | Kimi K3 | Text + Image | 1.0M | 131K | $3.00 | $15.00 |
42
- | Llama 3.3 70B Instruct | Text | 128K | 13K | $0.61 | $0.84 |
42
+ | Llama 3.3 70B Instruct | Text | 128K | 13K | $0.64 | $0.77 |
43
43
  | Llama 4 Maverick 17B 128E Instruct FP8 | Text | 430K | 43K | $0.27 | $0.90 |
44
- | MiniMax M2.7 | Text | 262K | 7K | $0.43 | $1.62 |
44
+ | MiniMax M2.7 | Text | 262K | 7K | $0.47 | $1.76 |
45
45
  | MiniMax M3 | Text + Image | 512K | 512K | $0.33 | $1.31 |
46
46
  | Qwen3 Coder 480B A35B Instruct INT4 Mixed AR | Text | 106K | 11K | $0.45 | $2.15 |
47
47
  | Qwen3 Next 80B A3B Instruct | Text | 262K | 26K | $0.12 | $1.14 |
package/index.ts CHANGED
@@ -624,6 +624,9 @@ const CREDITS_MIN_INTERVAL_MS = 15_000;
624
624
  const ACCOUNT_FETCH_TIMEOUT_MS = 8_000;
625
625
 
626
626
  let statusAbort: AbortController | null = null;
627
+ // Bumped on every session_start; async continuations compare against this to
628
+ // drop work belonging to a replaced session (its ctx is stale and throws).
629
+ let statusEpoch = 0;
627
630
  let lastCreditsFetchAt = 0;
628
631
  let creditsInFlight: Promise<void> | null = null;
629
632
  let metaFetched = false;
@@ -707,7 +710,30 @@ function currentProviderId(ctx: ExtensionContext): string | undefined {
707
710
  }
708
711
  }
709
712
 
713
+ function isStaleCtxError(err: unknown): boolean {
714
+ return err instanceof Error && err.message.includes("This extension ctx is stale");
715
+ }
716
+
717
+ // Render entry point: swallows the stale-ctx throw so a refresh racing a
718
+ // session replacement (newSession/fork/switchSession/reload) can't crash pi.
710
719
  function updateStatus(ctx: ExtensionContext): void {
720
+ try {
721
+ renderStatus(ctx);
722
+ } catch (err) {
723
+ if (!isStaleCtxError(err)) throw err;
724
+ }
725
+ }
726
+
727
+ // Re-render once an async refresh lands, unless the session was replaced
728
+ // meanwhile (epoch bump) — its ctx is stale and the render is obsolete anyway.
729
+ function updateStatusAfter(promise: Promise<void>, ctx: ExtensionContext): void {
730
+ const epoch = statusEpoch;
731
+ void promise.then(() => {
732
+ if (epoch === statusEpoch) updateStatus(ctx);
733
+ });
734
+ }
735
+
736
+ function renderStatus(ctx: ExtensionContext): void {
711
737
  const provider = currentProviderId(ctx);
712
738
  const hiddenByOtherProvider =
713
739
  statusConfig.hideOnOtherProvider && provider !== undefined && provider !== PROVIDER_ID;
@@ -791,7 +817,7 @@ function commitPending(ctx: ExtensionContext): void {
791
817
  if (pendingSawOutOfCredits) {
792
818
  pendingSawOutOfCredits = false;
793
819
  // Re-fetch now so the balance reflects exhaustion immediately
794
- void refreshCredits(cachedApiKey, statusAbort?.signal ?? undefined, true).then(() => updateStatus(ctx));
820
+ updateStatusAfter(refreshCredits(cachedApiKey, statusAbort?.signal ?? undefined, true), ctx);
795
821
  if (!outOfCreditsNotified && ctx.hasUI) {
796
822
  outOfCreditsNotified = true;
797
823
  ctx.ui.notify("HyperCharm is out of Hypercredits — recharge at hyper.charm.land", "error");
@@ -854,7 +880,7 @@ async function handleStatusCommand(args: string, ctx: ExtensionContext): Promise
854
880
  writeStatusConfig();
855
881
  if (value !== "off" && key === "account") {
856
882
  // Turning account on: make sure we have data to show
857
- void refreshCredits(cachedApiKey, statusAbort?.signal ?? undefined, true).then(() => updateStatus(ctx));
883
+ updateStatusAfter(refreshCredits(cachedApiKey, statusAbort?.signal ?? undefined, true), ctx);
858
884
  void refreshAccountMeta(cachedApiKey, statusAbort?.signal ?? undefined);
859
885
  }
860
886
  updateStatus(ctx);
@@ -929,7 +955,7 @@ async function configureStatusInteractive(ctx: ExtensionContext): Promise<void>
929
955
  statusConfig.account = nextMode(statusConfig.account);
930
956
  writeStatusConfig();
931
957
  if (statusConfig.account !== "off") {
932
- void refreshCredits(cachedApiKey, statusAbort?.signal ?? undefined, true).then(() => updateStatus(ctx));
958
+ updateStatusAfter(refreshCredits(cachedApiKey, statusAbort?.signal ?? undefined, true), ctx);
933
959
  void refreshAccountMeta(cachedApiKey, statusAbort?.signal ?? undefined);
934
960
  }
935
961
  continue;
@@ -1003,11 +1029,13 @@ export default function (pi: ExtensionAPI) {
1003
1029
  });
1004
1030
 
1005
1031
  pi.on("session_start", async (_event, ctx) => {
1032
+ const epoch = ++statusEpoch;
1006
1033
  revalidateAbort?.abort();
1007
1034
  revalidateAbort = new AbortController();
1008
1035
  const signal = revalidateAbort.signal;
1009
1036
  statusAbort?.abort();
1010
1037
  statusAbort = new AbortController();
1038
+ const statusSignal = statusAbort.signal;
1011
1039
 
1012
1040
  loadStatusConfig();
1013
1041
  resetStatusState();
@@ -1017,15 +1045,18 @@ export default function (pi: ExtensionAPI) {
1017
1045
  pi.registerProvider(PROVIDER_ID, makeProviderConfig());
1018
1046
 
1019
1047
  resolveApiKey(ctx.modelRegistry).then(() => {
1048
+ // A session replacement while the key resolved invalidated the
1049
+ // captured ctx (fast-resume, /new, /fork); nothing below may touch it.
1050
+ if (epoch !== statusEpoch) return;
1020
1051
  // Prefetch credits/team metadata only when a HyperCharm model is active
1021
1052
  // (pi-neuralwatt also prefetches so the first turn ends with data, but
1022
1053
  // gating here avoids API calls in sessions that never use the provider).
1023
1054
  if (currentProviderId(ctx) === PROVIDER_ID) {
1024
- void refreshCredits(cachedApiKey, statusAbort!.signal, true).then(() => updateStatus(ctx));
1025
- void refreshAccountMeta(cachedApiKey, statusAbort!.signal).then(() => updateStatus(ctx));
1055
+ updateStatusAfter(refreshCredits(cachedApiKey, statusSignal, true), ctx);
1056
+ updateStatusAfter(refreshAccountMeta(cachedApiKey, statusSignal), ctx);
1026
1057
  }
1027
1058
  revalidateModels(cachedApiKey, embeddedModels, signal).then((freshBase) => {
1028
- if (freshBase && !signal.aborted) {
1059
+ if (freshBase && epoch === statusEpoch && !signal.aborted) {
1029
1060
  currentModels = buildModels(freshBase, customModels, patches);
1030
1061
  pi.registerProvider(PROVIDER_ID, makeProviderConfig());
1031
1062
  }
@@ -1037,7 +1068,7 @@ export default function (pi: ExtensionAPI) {
1037
1068
  updateStatus(ctx);
1038
1069
  const model: any = (event as any).model;
1039
1070
  if (model?.provider === PROVIDER_ID && cachedApiKey) {
1040
- void refreshCredits(cachedApiKey, statusAbort?.signal ?? undefined, false).then(() => updateStatus(ctx));
1071
+ updateStatusAfter(refreshCredits(cachedApiKey, statusAbort?.signal ?? undefined, false), ctx);
1041
1072
  void refreshAccountMeta(cachedApiKey, statusAbort?.signal ?? undefined);
1042
1073
  }
1043
1074
  });
package/models.json CHANGED
@@ -35,24 +35,24 @@
35
35
  "name": "DeepSeek V4 Flash 0731",
36
36
  "reasoning": true,
37
37
  "thinkingLevelMap": {
38
- "off": null,
39
- "minimal": null,
38
+ "off": "none",
39
+ "minimal": "minimal",
40
40
  "low": "low",
41
- "medium": null,
41
+ "medium": "medium",
42
42
  "high": "high",
43
43
  "xhigh": "xhigh",
44
- "max": null
44
+ "max": "max"
45
45
  },
46
46
  "input": [
47
47
  "text"
48
48
  ],
49
49
  "cost": {
50
- "input": 0.2,
51
- "output": 0.4,
52
- "cacheRead": 0.04,
50
+ "input": 0.13,
51
+ "output": 0.26,
52
+ "cacheRead": 0.028,
53
53
  "cacheWrite": 0
54
54
  },
55
- "contextWindow": 1000000,
55
+ "contextWindow": 1048576,
56
56
  "maxTokens": 384000,
57
57
  "compat": {
58
58
  "supportsStore": false,
@@ -140,10 +140,10 @@
140
140
  "text"
141
141
  ],
142
142
  "cost": {
143
- "input": 0.124,
143
+ "input": 0.122,
144
144
  "output": 0.42,
145
145
  "cacheRead": 0,
146
- "cacheWrite": 0.062
146
+ "cacheWrite": 0.061
147
147
  },
148
148
  "contextWindow": 256000,
149
149
  "maxTokens": 25600,
@@ -171,10 +171,10 @@
171
171
  "text"
172
172
  ],
173
173
  "cost": {
174
- "input": 0.76,
175
- "output": 2.356,
174
+ "input": 0.84,
175
+ "output": 2.612,
176
176
  "cacheRead": 0,
177
- "cacheWrite": 0.38
177
+ "cacheWrite": 0.42
178
178
  },
179
179
  "contextWindow": 202752,
180
180
  "maxTokens": 20275,
@@ -190,11 +190,11 @@
190
190
  "name": "GLM-5.1",
191
191
  "reasoning": true,
192
192
  "thinkingLevelMap": {
193
- "off": null,
193
+ "off": "off",
194
194
  "minimal": null,
195
195
  "low": null,
196
196
  "medium": null,
197
- "high": "high",
197
+ "high": null,
198
198
  "xhigh": null,
199
199
  "max": "max"
200
200
  },
@@ -202,16 +202,16 @@
202
202
  "text"
203
203
  ],
204
204
  "cost": {
205
- "input": 1.52432,
206
- "output": 4.79072,
207
- "cacheRead": 0.283088,
208
- "cacheWrite": 0
205
+ "input": 1.36,
206
+ "output": 4.4,
207
+ "cacheRead": 0,
208
+ "cacheWrite": 0.68
209
209
  },
210
- "contextWindow": 202800,
210
+ "contextWindow": 202750,
211
211
  "maxTokens": 3276,
212
212
  "compat": {
213
213
  "supportsStore": false,
214
- "supportsReasoningEffort": true,
214
+ "supportsReasoningEffort": false,
215
215
  "thinkingFormat": "deepseek",
216
216
  "maxTokensField": "max_tokens"
217
217
  }
@@ -295,10 +295,10 @@
295
295
  "text"
296
296
  ],
297
297
  "cost": {
298
- "input": 0.558,
299
- "output": 2.94,
298
+ "input": 0.5744,
299
+ "output": 3.005,
300
300
  "cacheRead": 0,
301
- "cacheWrite": 0.279
301
+ "cacheWrite": 0.2872
302
302
  },
303
303
  "contextWindow": 262144,
304
304
  "maxTokens": 26214,
@@ -329,8 +329,8 @@
329
329
  "cost": {
330
330
  "input": 0.95,
331
331
  "output": 4,
332
- "cacheRead": 0,
333
- "cacheWrite": 0.16
332
+ "cacheRead": 0.16,
333
+ "cacheWrite": 0
334
334
  },
335
335
  "contextWindow": 262000,
336
336
  "maxTokens": 26214,
@@ -384,8 +384,8 @@
384
384
  "cost": {
385
385
  "input": 3,
386
386
  "output": 15,
387
- "cacheRead": 0,
388
- "cacheWrite": 0.3
387
+ "cacheRead": 0.3,
388
+ "cacheWrite": 0
389
389
  },
390
390
  "contextWindow": 1048576,
391
391
  "maxTokens": 131072,
@@ -413,10 +413,10 @@
413
413
  "text"
414
414
  ],
415
415
  "cost": {
416
- "input": 0.608,
417
- "output": 0.838,
416
+ "input": 0.638,
417
+ "output": 0.768,
418
418
  "cacheRead": 0,
419
- "cacheWrite": 0.304
419
+ "cacheWrite": 0.319
420
420
  },
421
421
  "contextWindow": 128000,
422
422
  "maxTokens": 12800,
@@ -466,10 +466,10 @@
466
466
  "text"
467
467
  ],
468
468
  "cost": {
469
- "input": 0.426,
470
- "output": 1.62,
469
+ "input": 0.47,
470
+ "output": 1.76,
471
471
  "cacheRead": 0,
472
- "cacheWrite": 0.213
472
+ "cacheWrite": 0.235
473
473
  },
474
474
  "contextWindow": 262100,
475
475
  "maxTokens": 6553,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-hypercharm-provider",
3
- "version": "1.3.0",
3
+ "version": "1.3.2",
4
4
  "description": "HyperCharm provider extension for pi - Access DeepSeek, GLM, Kimi, Qwen, MiniMax, Gemma, and GPT-OSS models through the Charm Hyper API",
5
5
  "type": "module",
6
6
  "main": "index.ts",
@@ -274,10 +274,9 @@ function transformModel(apiModel) {
274
274
  // ─── README generation ────────────────────────────────────────────────────────
275
275
 
276
276
  function formatCost(cost) {
277
- if (cost === 0) return 'Free';
278
- if (cost === null || cost === undefined) return '-';
279
- if (cost < 0.01) return `$${cost.toFixed(4)}`;
280
- return `$${cost.toFixed(2)}`;
277
+ if (cost === 0) return '';
278
+ if (cost === null || cost === undefined) return '';
279
+ return '$' + cost.toFixed(2);
281
280
  }
282
281
 
283
282
  function formatNumber(num) {
@@ -380,6 +379,31 @@ function updateDeprecatedModels(modelsJsonPath, newModels) {
380
379
  }
381
380
  }
382
381
 
382
+ /**
383
+ * Grace-period deprecated models (deprecatedAt within TTL) with metadata stripped.
384
+ * Keeps the README table serving models that are delisted but still within their
385
+ * 14-day grace window.
386
+ */
387
+ function withDeprecatedForReadme(models) {
388
+ const deprecatedPath = path.join(process.cwd(), 'deprecated-models.json');
389
+ let deprecated = {};
390
+ try {
391
+ const parsed = JSON.parse(fs.readFileSync(deprecatedPath, 'utf8'));
392
+ if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) deprecated = parsed;
393
+ } catch { /* no graveyard yet */ }
394
+ const now = Date.now();
395
+ const seen = new Set(models.map(m => m.id));
396
+ const extras = [];
397
+ for (const entry of Object.values(deprecated)) {
398
+ if (!entry || !entry.id || seen.has(entry.id)) continue;
399
+ const removedAt = Date.parse(entry.deprecatedAt || '');
400
+ if (Number.isNaN(removedAt) || now - removedAt > DEPRECATED_MODEL_TTL_MS) continue;
401
+ const m = { ...entry };
402
+ delete m.deprecatedAt;
403
+ extras.push(m);
404
+ }
405
+ return extras.length > 0 ? [...models, ...extras] : models;
406
+ }
383
407
  async function main() {
384
408
  const apiKey = resolveApiKey();
385
409
  if (!apiKey) {
@@ -456,7 +480,7 @@ async function main() {
456
480
  }
457
481
 
458
482
  // Build merged models with patches for README
459
- const readmeModels = buildModels(apiTransformed, customModels, patch);
483
+ const readmeModels = buildModels(withDeprecatedForReadme(apiTransformed), customModels, patch);
460
484
  readmeModels.sort((a, b) => a.name.localeCompare(b.name));
461
485
 
462
486
  // Update README