pi-io-provider 1.1.1 → 1.1.3

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.
package/AGENTS.md CHANGED
@@ -7,6 +7,7 @@ The following files are **idempotent** and regenerated by `scripts/update-models
7
7
  | File | Why it's auto-generated |
8
8
  |------|------------------------|
9
9
  | `models.json` | Built from the provider API. `update-models.js` fetches models, preserves curated data for known IDs, and writes this file. |
10
+ | `deprecated-models.json` | Graveyard for models the API delisted. update-models.js stamps them with deprecatedAt and pi keeps serving them for a 2-week grace period, then evicts them. Never edit by hand. |
10
11
  | `README.md` (model table) | The table under `## Available Models` is replaced in-place by `update-models.js` after merging base models → patch → custom models. |
11
12
 
12
13
  ## Correct Files to Edit
package/README.md CHANGED
@@ -16,35 +16,38 @@ A [pi](https://github.com/badlogic/pi-mono) extension that adds [IO Intelligence
16
16
 
17
17
  | Model | ID | Context | Max Output | Vision | Reasoning | Cache | Input $/M | Output $/M |
18
18
  |-------|----|---------|------------|--------|-----------|-------|-----------|------------|
19
+ | Kimi K2.5 | `moonshotai/Kimi-K2.5` | 262K | 262K | ✅ | ✅ | ✅ | $0.56 | $2.82 |
20
+ | Kimi K2.6 | `moonshotai/Kimi-K2.6` | 262K | 262K | ✅ | ✅ | ✅ | $0.84 | $3.76 |
21
+ | Kimi K2.7 Code | `moonshotai/Kimi-K2.7-Code` | 262K | 262K | ✅ | ✅ | ✅ | $1.07 | $4.65 |
22
+ | Kimi K3 | `moonshotai/Kimi-K3` | 1.0M | 1.0M | ✅ | ✅ | ✅ | $3.60 | $18.00 |
23
+ | Qwen3.6 27B | `Qwen/Qwen3.6-27B` | 33K | 33K | ✅ | ✅ | ✅ | $0.37 | $3.19 |
24
+ | Qwen3.6 35B A3B | `Qwen/Qwen3.6-35B-A3B` | 262K | 262K | ✅ | ✅ | ✅ | $0.17 | $1.12 |
19
25
  | DeepSeek R1 0528 | `deepseek-ai/DeepSeek-R1-0528` | 128K | 128K | ❌ | ✅ | ✅ | $0.57 | $2.28 |
26
+ | DeepSeek V3.2 | `deepseek-ai/DeepSeek-V3.2` | 164K | 164K | ❌ | ✅ | ✅ | $0.90 | $1.76 |
27
+ | DeepSeek V4 Flash | `deepseek-ai/DeepSeek-V4-Flash` | 33K | 33K | ❌ | ✅ | ✅ | $0.20 | $0.37 |
28
+ | DeepSeek V4 Flash 0731 | `deepseek-ai/DeepSeek-V4-Flash-0731` | 262K | 66K | ❌ | ✅ | ✅ | $0.14 | $0.28 |
29
+ | DeepSeek V4 Pro | `deepseek-ai/DeepSeek-V4-Pro` | 1.0M | 600K | ❌ | ✅ | ✅ | $1.52 | $3.04 |
30
+ | Gemma 4 26B A4B | `google/gemma-4-26b-a4b-it` | 262K | 262K | ❌ | ✅ | ✅ | $0.13 | $0.43 |
31
+ | MiniMax M2.5 | `MiniMaxAI/MiniMax-M2.5` | 197K | 197K | ❌ | ✅ | ✅ | $0.29 | $1.15 |
32
+ | MiniMax M2.7 | `MiniMaxAI/MiniMax-M2.7` | 262K | 66K | ❌ | ✅ | ✅ | $0.44 | $1.72 |
20
33
  | Kimi K2 Thinking | `moonshotai/Kimi-K2-Thinking` | 262K | 262K | ❌ | ✅ | ✅ | $0.60 | $2.50 |
34
+ | gpt-oss-120b | `openai/gpt-oss-120b` | 131K | 131K | ❌ | ✅ | ✅ | $0.19 | $0.70 |
35
+ | gpt-oss-20b | `openai/gpt-oss-20b` | 64K | 64K | ❌ | ✅ | ✅ | $0.05 | $0.22 |
36
+ | MiMo-V2.5 | `XiaomiMiMo/MiMo-V2.5` | 262K | 262K | ❌ | ✅ | ✅ | $0.20 | $0.64 |
37
+ | GLM-4.5-Air | `zai-org/GLM-4.5-Air` | 131K | 131K | ❌ | ✅ | ✅ | $0.16 | $0.94 |
38
+ | GLM 4.6 | `zai-org/GLM-4.6` | 131K | 131K | ❌ | ✅ | ✅ | $0.54 | $2.07 |
39
+ | GLM 4.7 | `zai-org/GLM-4.7` | 203K | 203K | ❌ | ✅ | ✅ | $0.86 | $2.24 |
40
+ | GLM 4.7 Flash | `zai-org/GLM-4.7-Flash` | 200K | 200K | ❌ | ✅ | ✅ | $0.08 | $0.42 |
41
+ | GLM 5 | `zai-org/GLM-5` | 203K | 203K | ❌ | ✅ | ✅ | $0.85 | $2.62 |
42
+ | GLM 5.1 | `zai-org/GLM-5.1` | 203K | 33K | ❌ | ✅ | ✅ | $1.27 | $4.13 |
43
+ | GLM 5.2 | `zai-org/GLM-5.2` | 262K | 66K | ❌ | ✅ | ✅ | $1.96 | $6.16 |
21
44
  | Llama 3.2 90B Vision Instruct | `meta-llama/Llama-3.2-90B-Vision-Instruct` | 16K | 16K | ✅ | ❌ | ✅ | $0.34 | $0.34 |
22
45
  | Llama 4 Maverick 17B 128E Instruct FP8 | `meta-llama/Llama-4-Maverick-17B-128E-Instruct-FP8` | 430K | 430K | ✅ | ❌ | ✅ | $0.28 | $0.93 |
23
- | Kimi K2.5 | `moonshotai/Kimi-K2.5` | 262K | 262K | | ❌ | ✅ | $0.53 | $2.79 |
24
- | Kimi K2.6 | `moonshotai/Kimi-K2.6` | 262K | 262K | | ❌ | ✅ | $0.95 | $3.98 |
25
- | DeepSeek V3.2 | `deepseek-ai/DeepSeek-V3.2` | 164K | 164K | ❌ | ❌ | ✅ | $0.89 | $1.63 |
26
- | DeepSeek V4 Flash | `deepseek-ai/DeepSeek-V4-Flash` | 33K | 33K | ❌ | ❌ | ✅ | $0.20 | $0.37 |
27
- | DeepSeek V4 Pro | `deepseek-ai/DeepSeek-V4-Pro` | 1.0M | 600K | ❌ | ❌ | ✅ | $1.65 | $3.30 |
28
- | Gemma 4 26B A4B | `google/gemma-4-26b-a4b-it` | 262K | 262K | ❌ | ❌ | ✅ | $0.11 | $0.36 |
29
- | Qwen3 Coder 480B A35B Instruct INT4 Mixed AR | `Intel/Qwen3-Coder-480B-A35B-Instruct-int4-mixed-ar` | 106K | 106K | ❌ | ❌ | ✅ | $0.60 | $2.08 |
30
- | Llama 3.3 70B Instruct | `meta-llama/Llama-3.3-70B-Instruct` | 128K | 128K | ❌ | ❌ | ✅ | $0.61 | $1.04 |
31
- | MiniMax M2.5 | `MiniMaxAI/MiniMax-M2.5` | 197K | 197K | ❌ | ❌ | ✅ | $0.28 | $1.13 |
32
- | MiniMax M2.7 | `MiniMaxAI/MiniMax-M2.7` | 205K | 205K | ❌ | ❌ | ✅ | $0.35 | $1.33 |
33
- | Mistral Nemo Instruct 2407 | `mistralai/Mistral-Nemo-Instruct-2407` | 128K | 128K | ❌ | ❌ | ✅ | $0.06 | $0.10 |
46
+ | Qwen3 Coder 480B A35B Instruct INT4 Mixed AR | `Intel/Qwen3-Coder-480B-A35B-Instruct-int4-mixed-ar` | 106K | 106K | | ❌ | ✅ | $0.57 | $2.13 |
47
+ | Llama 3.3 70B Instruct | `meta-llama/Llama-3.3-70B-Instruct` | 128K | 128K | | ❌ | ✅ | $0.51 | $1.04 |
48
+ | Mistral Nemo Instruct 2407 | `mistralai/Mistral-Nemo-Instruct-2407` | 128K | 128K | ❌ | ❌ | ✅ | $0.07 | $0.12 |
34
49
  | Kimi K2 Instruct 0905 | `moonshotai/Kimi-K2-Instruct-0905` | 262K | 262K | ❌ | ❌ | ✅ | $0.57 | $2.30 |
35
- | Kimi K2.7 Code | `moonshotai/Kimi-K2.7-Code` | 262K | 262K | ❌ | ❌ | ✅ | $0.90 | $3.90 |
36
- | gpt-oss-120b | `openai/gpt-oss-120b` | 131K | 131K | ❌ | ❌ | ✅ | $0.16 | $0.59 |
37
- | gpt-oss-20b | `openai/gpt-oss-20b` | 64K | 64K | ❌ | ❌ | ✅ | $0.06 | $0.23 |
38
50
  | Qwen3 Next 80B A3B Instruct | `Qwen/Qwen3-Next-80B-A3B-Instruct` | 262K | 262K | ❌ | ❌ | ✅ | $0.12 | $1.14 |
39
- | Qwen3.6 27B | `Qwen/Qwen3.6-27B` | 33K | 33K | ❌ | ❌ | ✅ | $0.43 | $3.21 |
40
- | Qwen3.6 35B A3B | `Qwen/Qwen3.6-35B-A3B` | 262K | 262K | ❌ | ❌ | ✅ | $0.19 | $1.12 |
41
- | GLM-4.5-Air | `zai-org/GLM-4.5-Air` | 131K | 131K | ❌ | ❌ | ✅ | $0.16 | $0.94 |
42
- | GLM 4.6 | `zai-org/GLM-4.6` | 131K | 131K | ❌ | ❌ | ✅ | $0.54 | $2.07 |
43
- | GLM 4.7 | `zai-org/GLM-4.7` | 203K | 203K | ❌ | ❌ | ✅ | $0.86 | $2.24 |
44
- | GLM 4.7 Flash | `zai-org/GLM-4.7-Flash` | 200K | 200K | ❌ | ❌ | ✅ | $0.08 | $0.42 |
45
- | GLM 5 | `zai-org/GLM-5` | 203K | 203K | ❌ | ❌ | ✅ | $0.84 | $2.69 |
46
- | GLM 5.1 | `zai-org/GLM-5.1` | 203K | 33K | ❌ | ❌ | ✅ | $1.37 | $4.47 |
47
- | GLM 5.2 | `zai-org/GLM-5.2` | 262K | 66K | ❌ | ❌ | ✅ | $1.64 | $5.13 |
48
51
 
49
52
  *Costs are per million tokens. Cache read/write pricing available on most models.*
50
53
 
@@ -0,0 +1 @@
1
+ {}
package/index.ts CHANGED
@@ -40,6 +40,7 @@ import { getAgentDir, type ExtensionAPI, type ModelRegistry } from "@earendil-wo
40
40
  import modelsData from "./models.json" with { type: "json" };
41
41
  import customModelsData from "./custom-models.json" with { type: "json" };
42
42
  import patchData from "./patch.json" with { type: "json" };
43
+ import deprecatedData from "./deprecated-models.json" with { type: "json" };
43
44
  import fs from "fs";
44
45
  import path from "path";
45
46
 
@@ -58,12 +59,14 @@ interface JsonModel {
58
59
  };
59
60
  contextWindow: number;
60
61
  maxTokens: number;
62
+ thinkingLevelMap?: Record<string, string | null>;
61
63
  compat?: {
62
64
  supportsDeveloperRole?: boolean;
63
65
  supportsStore?: boolean;
64
66
  maxTokensField?: "max_completion_tokens" | "max_tokens";
65
67
  thinkingFormat?: "openai" | "zai" | "qwen" | "qwen-chat-template";
66
68
  supportsReasoningEffort?: boolean;
69
+ requiresReasoningContentOnAssistantMessages?: boolean;
67
70
  };
68
71
  }
69
72
 
@@ -79,6 +82,7 @@ interface PatchEntry {
79
82
  };
80
83
  contextWindow?: number;
81
84
  maxTokens?: number;
85
+ thinkingLevelMap?: Record<string, string | null>;
82
86
  compat?: Record<string, unknown>;
83
87
  }
84
88
 
@@ -94,6 +98,7 @@ function applyPatch(model: JsonModel, patch: PatchEntry): JsonModel {
94
98
  if (patch.input !== undefined) result.input = patch.input;
95
99
  if (patch.contextWindow !== undefined) result.contextWindow = patch.contextWindow;
96
100
  if (patch.maxTokens !== undefined) result.maxTokens = patch.maxTokens;
101
+ if (patch.thinkingLevelMap !== undefined) result.thinkingLevelMap = { ...patch.thinkingLevelMap };
97
102
 
98
103
  if (patch.cost) {
99
104
  result.cost = {
@@ -110,6 +115,9 @@ function applyPatch(model: JsonModel, patch: PatchEntry): JsonModel {
110
115
  if (!result.reasoning && result.compat?.thinkingFormat) {
111
116
  delete result.compat.thinkingFormat;
112
117
  }
118
+ if (!result.reasoning && result.thinkingLevelMap) {
119
+ delete result.thinkingLevelMap;
120
+ }
113
121
  if (result.compat && Object.keys(result.compat).length === 0) {
114
122
  delete result.compat;
115
123
  }
@@ -121,7 +129,10 @@ function applyPatch(model: JsonModel, patch: PatchEntry): JsonModel {
121
129
  function buildModels(base: JsonModel[], custom: JsonModel[], patch: PatchData): JsonModel[] {
122
130
  const modelMap = new Map<string, JsonModel>();
123
131
 
124
- for (const model of base) {
132
+ // Seed with the base list plus grace-period deprecated models so patch.json
133
+ // entries apply to deprecated models exactly as while the model was live
134
+ // (withDeprecated keeps live data on id conflicts).
135
+ for (const model of withDeprecated(base)) {
125
136
  modelMap.set(model.id, model);
126
137
  }
127
138
 
@@ -160,7 +171,7 @@ const LIVE_FETCH_TIMEOUT_MS = 8000;
160
171
 
161
172
  /** Transform a model from the IO Intelligence /v1/models API to JsonModel format. */
162
173
  function transformApiModel(apiModel: any): JsonModel | null {
163
- const hasVision = apiModel.supports_images_input === true;
174
+ const hasVision = apiModel.supports_images_input === true || (apiModel.input_modalities || []).includes("image");
164
175
  // IO returns per-token pricing, convert to per-million. Round to 6 decimals to
165
176
  // normalize float noise from the ×1e6 multiply and preserve sub-cent cache prices.
166
177
  const toPerM = (v: any) => Math.round((typeof v === "number" ? v * 1_000_000 : 0) * 1e6) / 1e6;
@@ -177,13 +188,14 @@ function transformApiModel(apiModel: any): JsonModel | null {
177
188
  cacheWrite: toPerM(apiModel.cache_write_token_price),
178
189
  },
179
190
  contextWindow: apiModel.context_window || 131072,
180
- maxTokens: apiModel.max_tokens || 0,
191
+ maxTokens: apiModel.max_tokens || apiModel.context_window || 131072,
192
+ compat: {
193
+ supportsStore: false,
194
+ supportsDeveloperRole: false,
195
+ maxTokensField: "max_tokens",
196
+ ...(hasReasoning ? { supportsReasoningEffort: true } : {}),
197
+ },
181
198
  };
182
- if (hasReasoning) {
183
- model.compat = {
184
- supportsReasoningEffort: true,
185
- };
186
- }
187
199
  return model;
188
200
  }
189
201
 
@@ -258,6 +270,35 @@ function mergeWithEmbedded(liveModels: JsonModel[], embeddedModels: JsonModel[])
258
270
  return result;
259
271
  }
260
272
 
273
+ // Grace period for delisted models. When the provider API stops listing a
274
+ // model, update-models.js moves its last-known definition into
275
+ // deprecated-models.json (stamped with deprecatedAt) instead of dropping it.
276
+ // For 14 days the model keeps working here so in-flight sessions and saved
277
+ // model settings do not break; afterwards it is evicted permanently.
278
+ const DEPRECATED_MODEL_TTL_MS = 14 * 24 * 60 * 60 * 1000;
279
+
280
+ // Grace-period deprecated models with deprecation metadata stripped.
281
+ function activeDeprecatedModels(): JsonModel[] {
282
+ const now = Date.now();
283
+ const result: JsonModel[] = [];
284
+ for (const entry of Object.values(deprecatedData as Record<string, JsonModel & { deprecatedAt?: string }>)) {
285
+ if (!entry?.id) continue;
286
+ const removedAt = Date.parse(entry.deprecatedAt ?? "");
287
+ if (Number.isNaN(removedAt) || now - removedAt > DEPRECATED_MODEL_TTL_MS) continue;
288
+ const model = { ...entry } as JsonModel & { deprecatedAt?: string };
289
+ delete model.deprecatedAt;
290
+ result.push(model);
291
+ }
292
+ return result;
293
+ }
294
+
295
+ // Append grace-period deprecated models the list does not already have (live data wins).
296
+ function withDeprecated(models: JsonModel[]): JsonModel[] {
297
+ const seen = new Set(models.map((m) => m.id));
298
+ const extras = activeDeprecatedModels().filter((m) => !seen.has(m.id));
299
+ return extras.length > 0 ? [...models, ...extras] : models;
300
+ }
301
+
261
302
  function loadStaleModels(embeddedModels: JsonModel[]): JsonModel[] {
262
303
  const cached = loadCachedModels();
263
304
  if (!cached || cached.length === 0) return embeddedModels;
package/models.json CHANGED
@@ -1,88 +1,188 @@
1
1
  [
2
+ {
3
+ "id": "deepseek-ai/DeepSeek-V4-Flash-0731",
4
+ "name": "DeepSeek V4 Flash 0731",
5
+ "reasoning": true,
6
+ "input": [
7
+ "text"
8
+ ],
9
+ "cost": {
10
+ "input": 0.14,
11
+ "output": 0.28,
12
+ "cacheRead": 0.07,
13
+ "cacheWrite": 0
14
+ },
15
+ "contextWindow": 262100,
16
+ "maxTokens": 65536,
17
+ "compat": {
18
+ "supportsStore": false,
19
+ "supportsDeveloperRole": false,
20
+ "maxTokensField": "max_tokens",
21
+ "supportsReasoningEffort": true
22
+ }
23
+ },
24
+ {
25
+ "id": "moonshotai/Kimi-K3",
26
+ "name": "Kimi K3",
27
+ "reasoning": true,
28
+ "input": [
29
+ "text",
30
+ "image"
31
+ ],
32
+ "cost": {
33
+ "input": 3.6,
34
+ "output": 18,
35
+ "cacheRead": 1.8,
36
+ "cacheWrite": 0
37
+ },
38
+ "contextWindow": 1048576,
39
+ "maxTokens": 1048576,
40
+ "compat": {
41
+ "supportsStore": false,
42
+ "supportsDeveloperRole": false,
43
+ "maxTokensField": "max_tokens",
44
+ "supportsReasoningEffort": true
45
+ }
46
+ },
47
+ {
48
+ "id": "XiaomiMiMo/MiMo-V2.5",
49
+ "name": "MiMo-V2.5",
50
+ "reasoning": true,
51
+ "input": [
52
+ "text"
53
+ ],
54
+ "cost": {
55
+ "input": 0.1976,
56
+ "output": 0.6352,
57
+ "cacheRead": 0.0988,
58
+ "cacheWrite": 0
59
+ },
60
+ "contextWindow": 262144,
61
+ "maxTokens": 262144,
62
+ "compat": {
63
+ "supportsStore": false,
64
+ "supportsDeveloperRole": false,
65
+ "maxTokensField": "max_tokens",
66
+ "supportsReasoningEffort": true
67
+ }
68
+ },
2
69
  {
3
70
  "id": "zai-org/GLM-5.2",
4
71
  "name": "GLM 5.2",
5
- "reasoning": false,
72
+ "reasoning": true,
6
73
  "input": [
7
74
  "text"
8
75
  ],
9
76
  "cost": {
10
- "input": 1.638,
11
- "output": 5.13,
12
- "cacheRead": 0.819,
77
+ "input": 1.96,
78
+ "output": 6.16,
79
+ "cacheRead": 0.98,
13
80
  "cacheWrite": 0
14
81
  },
15
82
  "contextWindow": 262144,
16
- "maxTokens": 65536
83
+ "maxTokens": 65536,
84
+ "compat": {
85
+ "supportsStore": false,
86
+ "supportsDeveloperRole": false,
87
+ "maxTokensField": "max_tokens",
88
+ "supportsReasoningEffort": true
89
+ }
17
90
  },
18
91
  {
19
92
  "id": "moonshotai/Kimi-K2.7-Code",
20
93
  "name": "Kimi K2.7 Code",
21
- "reasoning": false,
94
+ "reasoning": true,
22
95
  "input": [
23
- "text"
96
+ "text",
97
+ "image"
24
98
  ],
25
99
  "cost": {
26
- "input": 0.9,
27
- "output": 3.9,
28
- "cacheRead": 0.45,
100
+ "input": 1.072,
101
+ "output": 4.65,
102
+ "cacheRead": 0.536,
29
103
  "cacheWrite": 0
30
104
  },
31
105
  "contextWindow": 262144,
32
- "maxTokens": 262144
106
+ "maxTokens": 262144,
107
+ "compat": {
108
+ "supportsStore": false,
109
+ "supportsDeveloperRole": false,
110
+ "maxTokensField": "max_tokens",
111
+ "supportsReasoningEffort": true
112
+ }
33
113
  },
34
114
  {
35
115
  "id": "Qwen/Qwen3.6-35B-A3B",
36
116
  "name": "Qwen3.6 35B A3B",
37
- "reasoning": false,
117
+ "reasoning": true,
38
118
  "input": [
39
- "text"
119
+ "text",
120
+ "image"
40
121
  ],
41
122
  "cost": {
42
- "input": 0.1932,
43
- "output": 1.12275,
44
- "cacheRead": 0.0966,
123
+ "input": 0.1672,
124
+ "output": 1.11675,
125
+ "cacheRead": 0.0836,
45
126
  "cacheWrite": 0
46
127
  },
47
128
  "contextWindow": 262140,
48
- "maxTokens": 262140
129
+ "maxTokens": 262140,
130
+ "compat": {
131
+ "supportsStore": false,
132
+ "supportsDeveloperRole": false,
133
+ "maxTokensField": "max_tokens",
134
+ "supportsReasoningEffort": true
135
+ }
49
136
  },
50
137
  {
51
138
  "id": "Qwen/Qwen3.6-27B",
52
139
  "name": "Qwen3.6 27B",
53
- "reasoning": false,
140
+ "reasoning": true,
54
141
  "input": [
55
- "text"
142
+ "text",
143
+ "image"
56
144
  ],
57
145
  "cost": {
58
- "input": 0.4266,
59
- "output": 3.21,
60
- "cacheRead": 0.2133,
146
+ "input": 0.373,
147
+ "output": 3.19,
148
+ "cacheRead": 0.1865,
61
149
  "cacheWrite": 0
62
150
  },
63
151
  "contextWindow": 32768,
64
- "maxTokens": 32768
152
+ "maxTokens": 32768,
153
+ "compat": {
154
+ "supportsStore": false,
155
+ "supportsDeveloperRole": false,
156
+ "maxTokensField": "max_tokens",
157
+ "supportsReasoningEffort": true
158
+ }
65
159
  },
66
160
  {
67
161
  "id": "MiniMaxAI/MiniMax-M2.7",
68
162
  "name": "MiniMax M2.7",
69
- "reasoning": false,
163
+ "reasoning": true,
70
164
  "input": [
71
165
  "text"
72
166
  ],
73
167
  "cost": {
74
- "input": 0.348,
75
- "output": 1.332,
76
- "cacheRead": 0.174,
168
+ "input": 0.436,
169
+ "output": 1.72,
170
+ "cacheRead": 0.218,
77
171
  "cacheWrite": 0
78
172
  },
79
- "contextWindow": 204800,
80
- "maxTokens": 204800
173
+ "contextWindow": 262100,
174
+ "maxTokens": 65536,
175
+ "compat": {
176
+ "supportsStore": false,
177
+ "supportsDeveloperRole": false,
178
+ "maxTokensField": "max_tokens",
179
+ "supportsReasoningEffort": true
180
+ }
81
181
  },
82
182
  {
83
183
  "id": "deepseek-ai/DeepSeek-V4-Flash",
84
184
  "name": "DeepSeek V4 Flash",
85
- "reasoning": false,
185
+ "reasoning": true,
86
186
  "input": [
87
187
  "text"
88
188
  ],
@@ -93,121 +193,169 @@
93
193
  "cacheWrite": 0
94
194
  },
95
195
  "contextWindow": 32768,
96
- "maxTokens": 32768
196
+ "maxTokens": 32768,
197
+ "compat": {
198
+ "supportsStore": false,
199
+ "supportsDeveloperRole": false,
200
+ "maxTokensField": "max_tokens",
201
+ "supportsReasoningEffort": true
202
+ }
97
203
  },
98
204
  {
99
205
  "id": "deepseek-ai/DeepSeek-V4-Pro",
100
206
  "name": "DeepSeek V4 Pro",
101
- "reasoning": false,
207
+ "reasoning": true,
102
208
  "input": [
103
209
  "text"
104
210
  ],
105
211
  "cost": {
106
- "input": 1.652,
107
- "output": 3.304,
108
- "cacheRead": 0.826,
212
+ "input": 1.52044,
213
+ "output": 3.04088,
214
+ "cacheRead": 0.76022,
109
215
  "cacheWrite": 0
110
216
  },
111
217
  "contextWindow": 1048576,
112
- "maxTokens": 600000
218
+ "maxTokens": 600000,
219
+ "compat": {
220
+ "supportsStore": false,
221
+ "supportsDeveloperRole": false,
222
+ "maxTokensField": "max_tokens",
223
+ "supportsReasoningEffort": true
224
+ }
113
225
  },
114
226
  {
115
227
  "id": "moonshotai/Kimi-K2.6",
116
228
  "name": "Kimi K2.6",
117
- "reasoning": false,
229
+ "reasoning": true,
118
230
  "input": [
119
231
  "text",
120
232
  "image"
121
233
  ],
122
234
  "cost": {
123
- "input": 0.95,
124
- "output": 3.98,
125
- "cacheRead": 0.475,
235
+ "input": 0.842,
236
+ "output": 3.762,
237
+ "cacheRead": 0.421,
126
238
  "cacheWrite": 0
127
239
  },
128
240
  "contextWindow": 262142,
129
- "maxTokens": 262142
241
+ "maxTokens": 262142,
242
+ "compat": {
243
+ "supportsStore": false,
244
+ "supportsDeveloperRole": false,
245
+ "maxTokensField": "max_tokens",
246
+ "supportsReasoningEffort": true
247
+ }
130
248
  },
131
249
  {
132
250
  "id": "zai-org/GLM-5.1",
133
251
  "name": "GLM 5.1",
134
- "reasoning": false,
252
+ "reasoning": true,
135
253
  "input": [
136
254
  "text"
137
255
  ],
138
256
  "cost": {
139
- "input": 1.368,
140
- "output": 4.468,
141
- "cacheRead": 0.684,
257
+ "input": 1.2732,
258
+ "output": 4.1272,
259
+ "cacheRead": 0.6366,
142
260
  "cacheWrite": 0
143
261
  },
144
262
  "contextWindow": 202750,
145
- "maxTokens": 32768
263
+ "maxTokens": 32768,
264
+ "compat": {
265
+ "supportsStore": false,
266
+ "supportsDeveloperRole": false,
267
+ "maxTokensField": "max_tokens",
268
+ "supportsReasoningEffort": true
269
+ }
146
270
  },
147
271
  {
148
272
  "id": "MiniMaxAI/MiniMax-M2.5",
149
273
  "name": "MiniMax M2.5",
150
- "reasoning": false,
274
+ "reasoning": true,
151
275
  "input": [
152
276
  "text"
153
277
  ],
154
278
  "cost": {
155
- "input": 0.281,
156
- "output": 1.128,
157
- "cacheRead": 0.1405,
279
+ "input": 0.287,
280
+ "output": 1.152,
281
+ "cacheRead": 0.1435,
158
282
  "cacheWrite": 0
159
283
  },
160
284
  "contextWindow": 196600,
161
- "maxTokens": 196600
285
+ "maxTokens": 196600,
286
+ "compat": {
287
+ "supportsStore": false,
288
+ "supportsDeveloperRole": false,
289
+ "maxTokensField": "max_tokens",
290
+ "supportsReasoningEffort": true
291
+ }
162
292
  },
163
293
  {
164
294
  "id": "moonshotai/Kimi-K2.5",
165
295
  "name": "Kimi K2.5",
166
- "reasoning": false,
296
+ "reasoning": true,
167
297
  "input": [
168
298
  "text",
169
299
  "image"
170
300
  ],
171
301
  "cost": {
172
- "input": 0.528,
173
- "output": 2.79,
174
- "cacheRead": 0.264,
302
+ "input": 0.564,
303
+ "output": 2.82,
304
+ "cacheRead": 0.282,
175
305
  "cacheWrite": 1.1
176
306
  },
177
307
  "contextWindow": 262144,
178
- "maxTokens": 262144
308
+ "maxTokens": 262144,
309
+ "compat": {
310
+ "supportsStore": false,
311
+ "supportsDeveloperRole": false,
312
+ "maxTokensField": "max_tokens",
313
+ "supportsReasoningEffort": true
314
+ }
179
315
  },
180
316
  {
181
317
  "id": "zai-org/GLM-5",
182
318
  "name": "GLM 5",
183
- "reasoning": false,
319
+ "reasoning": true,
184
320
  "input": [
185
321
  "text"
186
322
  ],
187
323
  "cost": {
188
- "input": 0.84,
189
- "output": 2.688,
190
- "cacheRead": 0.42,
324
+ "input": 0.85,
325
+ "output": 2.622,
326
+ "cacheRead": 0.425,
191
327
  "cacheWrite": 0
192
328
  },
193
329
  "contextWindow": 202752,
194
- "maxTokens": 202752
330
+ "maxTokens": 202752,
331
+ "compat": {
332
+ "supportsStore": false,
333
+ "supportsDeveloperRole": false,
334
+ "maxTokensField": "max_tokens",
335
+ "supportsReasoningEffort": true
336
+ }
195
337
  },
196
338
  {
197
339
  "id": "deepseek-ai/DeepSeek-V3.2",
198
340
  "name": "DeepSeek V3.2",
199
- "reasoning": false,
341
+ "reasoning": true,
200
342
  "input": [
201
343
  "text"
202
344
  ],
203
345
  "cost": {
204
- "input": 0.8934,
205
- "output": 1.6336,
206
- "cacheRead": 0.4467,
346
+ "input": 0.90054,
347
+ "output": 1.75646,
348
+ "cacheRead": 0.45027,
207
349
  "cacheWrite": 0.5
208
350
  },
209
351
  "contextWindow": 163840,
210
- "maxTokens": 163840
352
+ "maxTokens": 163840,
353
+ "compat": {
354
+ "supportsStore": false,
355
+ "supportsDeveloperRole": false,
356
+ "maxTokensField": "max_tokens",
357
+ "supportsReasoningEffort": true
358
+ }
211
359
  },
212
360
  {
213
361
  "id": "moonshotai/Kimi-K2-Thinking",
@@ -223,12 +371,18 @@
223
371
  "cacheWrite": 0.64
224
372
  },
225
373
  "contextWindow": 262144,
226
- "maxTokens": 262144
374
+ "maxTokens": 262144,
375
+ "compat": {
376
+ "supportsStore": false,
377
+ "supportsDeveloperRole": false,
378
+ "maxTokensField": "max_tokens",
379
+ "supportsReasoningEffort": true
380
+ }
227
381
  },
228
382
  {
229
383
  "id": "zai-org/GLM-4.5-Air",
230
384
  "name": "GLM-4.5-Air",
231
- "reasoning": false,
385
+ "reasoning": true,
232
386
  "input": [
233
387
  "text"
234
388
  ],
@@ -239,28 +393,40 @@
239
393
  "cacheWrite": 0
240
394
  },
241
395
  "contextWindow": 131070,
242
- "maxTokens": 131070
396
+ "maxTokens": 131070,
397
+ "compat": {
398
+ "supportsStore": false,
399
+ "supportsDeveloperRole": false,
400
+ "maxTokensField": "max_tokens",
401
+ "supportsReasoningEffort": true
402
+ }
243
403
  },
244
404
  {
245
405
  "id": "google/gemma-4-26b-a4b-it",
246
406
  "name": "Gemma 4 26B A4B",
247
- "reasoning": false,
407
+ "reasoning": true,
248
408
  "input": [
249
409
  "text"
250
410
  ],
251
411
  "cost": {
252
- "input": 0.11,
253
- "output": 0.358,
254
- "cacheRead": 0.055,
412
+ "input": 0.13,
413
+ "output": 0.43,
414
+ "cacheRead": 0.065,
255
415
  "cacheWrite": 0
256
416
  },
257
417
  "contextWindow": 262142,
258
- "maxTokens": 262142
418
+ "maxTokens": 262142,
419
+ "compat": {
420
+ "supportsStore": false,
421
+ "supportsDeveloperRole": false,
422
+ "maxTokensField": "max_tokens",
423
+ "supportsReasoningEffort": true
424
+ }
259
425
  },
260
426
  {
261
427
  "id": "zai-org/GLM-4.7-Flash",
262
428
  "name": "GLM 4.7 Flash",
263
- "reasoning": false,
429
+ "reasoning": true,
264
430
  "input": [
265
431
  "text"
266
432
  ],
@@ -271,12 +437,18 @@
271
437
  "cacheWrite": 0.14
272
438
  },
273
439
  "contextWindow": 200000,
274
- "maxTokens": 200000
440
+ "maxTokens": 200000,
441
+ "compat": {
442
+ "supportsStore": false,
443
+ "supportsDeveloperRole": false,
444
+ "maxTokensField": "max_tokens",
445
+ "supportsReasoningEffort": true
446
+ }
275
447
  },
276
448
  {
277
449
  "id": "zai-org/GLM-4.7",
278
450
  "name": "GLM 4.7",
279
- "reasoning": false,
451
+ "reasoning": true,
280
452
  "input": [
281
453
  "text"
282
454
  ],
@@ -287,7 +459,13 @@
287
459
  "cacheWrite": 0.6
288
460
  },
289
461
  "contextWindow": 202752,
290
- "maxTokens": 202752
462
+ "maxTokens": 202752,
463
+ "compat": {
464
+ "supportsStore": false,
465
+ "supportsDeveloperRole": false,
466
+ "maxTokensField": "max_tokens",
467
+ "supportsReasoningEffort": true
468
+ }
291
469
  },
292
470
  {
293
471
  "id": "moonshotai/Kimi-K2-Instruct-0905",
@@ -303,7 +481,12 @@
303
481
  "cacheWrite": 0.78
304
482
  },
305
483
  "contextWindow": 262144,
306
- "maxTokens": 262144
484
+ "maxTokens": 262144,
485
+ "compat": {
486
+ "supportsStore": false,
487
+ "supportsDeveloperRole": false,
488
+ "maxTokensField": "max_tokens"
489
+ }
307
490
  },
308
491
  {
309
492
  "id": "meta-llama/Llama-3.2-90B-Vision-Instruct",
@@ -320,23 +503,34 @@
320
503
  "cacheWrite": 0.7
321
504
  },
322
505
  "contextWindow": 16000,
323
- "maxTokens": 16000
506
+ "maxTokens": 16000,
507
+ "compat": {
508
+ "supportsStore": false,
509
+ "supportsDeveloperRole": false,
510
+ "maxTokensField": "max_tokens"
511
+ }
324
512
  },
325
513
  {
326
514
  "id": "openai/gpt-oss-120b",
327
515
  "name": "gpt-oss-120b",
328
- "reasoning": false,
516
+ "reasoning": true,
329
517
  "input": [
330
518
  "text"
331
519
  ],
332
520
  "cost": {
333
- "input": 0.158,
334
- "output": 0.592,
335
- "cacheRead": 0.079,
521
+ "input": 0.188,
522
+ "output": 0.7,
523
+ "cacheRead": 0.094,
336
524
  "cacheWrite": 0.04
337
525
  },
338
526
  "contextWindow": 131072,
339
- "maxTokens": 131072
527
+ "maxTokens": 131072,
528
+ "compat": {
529
+ "supportsStore": false,
530
+ "supportsDeveloperRole": false,
531
+ "maxTokensField": "max_tokens",
532
+ "supportsReasoningEffort": true
533
+ }
340
534
  },
341
535
  {
342
536
  "id": "deepseek-ai/DeepSeek-R1-0528",
@@ -352,12 +546,18 @@
352
546
  "cacheWrite": 0.8
353
547
  },
354
548
  "contextWindow": 128000,
355
- "maxTokens": 128000
549
+ "maxTokens": 128000,
550
+ "compat": {
551
+ "supportsStore": false,
552
+ "supportsDeveloperRole": false,
553
+ "maxTokensField": "max_tokens",
554
+ "supportsReasoningEffort": true
555
+ }
356
556
  },
357
557
  {
358
558
  "id": "zai-org/GLM-4.6",
359
559
  "name": "GLM 4.6",
360
- "reasoning": false,
560
+ "reasoning": true,
361
561
  "input": [
362
562
  "text"
363
563
  ],
@@ -368,7 +568,13 @@
368
568
  "cacheWrite": 0.7
369
569
  },
370
570
  "contextWindow": 131072,
371
- "maxTokens": 131072
571
+ "maxTokens": 131072,
572
+ "compat": {
573
+ "supportsStore": false,
574
+ "supportsDeveloperRole": false,
575
+ "maxTokensField": "max_tokens",
576
+ "supportsReasoningEffort": true
577
+ }
372
578
  },
373
579
  {
374
580
  "id": "Qwen/Qwen3-Next-80B-A3B-Instruct",
@@ -384,7 +590,12 @@
384
590
  "cacheWrite": 0.12
385
591
  },
386
592
  "contextWindow": 262144,
387
- "maxTokens": 262144
593
+ "maxTokens": 262144,
594
+ "compat": {
595
+ "supportsStore": false,
596
+ "supportsDeveloperRole": false,
597
+ "maxTokensField": "max_tokens"
598
+ }
388
599
  },
389
600
  {
390
601
  "id": "Intel/Qwen3-Coder-480B-A35B-Instruct-int4-mixed-ar",
@@ -394,13 +605,18 @@
394
605
  "text"
395
606
  ],
396
607
  "cost": {
397
- "input": 0.601,
398
- "output": 2.085,
399
- "cacheRead": 0.3005,
608
+ "input": 0.569,
609
+ "output": 2.135,
610
+ "cacheRead": 0.2845,
400
611
  "cacheWrite": 0.44
401
612
  },
402
613
  "contextWindow": 106000,
403
- "maxTokens": 106000
614
+ "maxTokens": 106000,
615
+ "compat": {
616
+ "supportsStore": false,
617
+ "supportsDeveloperRole": false,
618
+ "maxTokensField": "max_tokens"
619
+ }
404
620
  },
405
621
  {
406
622
  "id": "meta-llama/Llama-4-Maverick-17B-128E-Instruct-FP8",
@@ -417,7 +633,12 @@
417
633
  "cacheWrite": 0.3
418
634
  },
419
635
  "contextWindow": 430000,
420
- "maxTokens": 430000
636
+ "maxTokens": 430000,
637
+ "compat": {
638
+ "supportsStore": false,
639
+ "supportsDeveloperRole": false,
640
+ "maxTokensField": "max_tokens"
641
+ }
421
642
  },
422
643
  {
423
644
  "id": "mistralai/Mistral-Nemo-Instruct-2407",
@@ -427,29 +648,40 @@
427
648
  "text"
428
649
  ],
429
650
  "cost": {
430
- "input": 0.05675,
431
- "output": 0.095,
432
- "cacheRead": 0.028375,
651
+ "input": 0.069667,
652
+ "output": 0.116667,
653
+ "cacheRead": 0.034834,
433
654
  "cacheWrite": 0.04
434
655
  },
435
656
  "contextWindow": 128000,
436
- "maxTokens": 128000
657
+ "maxTokens": 128000,
658
+ "compat": {
659
+ "supportsStore": false,
660
+ "supportsDeveloperRole": false,
661
+ "maxTokensField": "max_tokens"
662
+ }
437
663
  },
438
664
  {
439
665
  "id": "openai/gpt-oss-20b",
440
666
  "name": "gpt-oss-20b",
441
- "reasoning": false,
667
+ "reasoning": true,
442
668
  "input": [
443
669
  "text"
444
670
  ],
445
671
  "cost": {
446
- "input": 0.063,
447
- "output": 0.226,
448
- "cacheRead": 0.0315,
672
+ "input": 0.053,
673
+ "output": 0.216,
674
+ "cacheRead": 0.0265,
449
675
  "cacheWrite": 0.03
450
676
  },
451
677
  "contextWindow": 64000,
452
- "maxTokens": 64000
678
+ "maxTokens": 64000,
679
+ "compat": {
680
+ "supportsStore": false,
681
+ "supportsDeveloperRole": false,
682
+ "maxTokensField": "max_tokens",
683
+ "supportsReasoningEffort": true
684
+ }
453
685
  },
454
686
  {
455
687
  "id": "meta-llama/Llama-3.3-70B-Instruct",
@@ -459,12 +691,17 @@
459
691
  "text"
460
692
  ],
461
693
  "cost": {
462
- "input": 0.6066,
463
- "output": 1.0386,
464
- "cacheRead": 0.3033,
694
+ "input": 0.5126,
695
+ "output": 1.0446,
696
+ "cacheRead": 0.2563,
465
697
  "cacheWrite": 0.2
466
698
  },
467
699
  "contextWindow": 128000,
468
- "maxTokens": 128000
700
+ "maxTokens": 128000,
701
+ "compat": {
702
+ "supportsStore": false,
703
+ "supportsDeveloperRole": false,
704
+ "maxTokensField": "max_tokens"
705
+ }
469
706
  }
470
707
  ]
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-io-provider",
3
- "version": "1.1.1",
3
+ "version": "1.1.3",
4
4
  "description": "IO Intelligence provider extension for pi - Access DeepSeek, Kimi, GLM, Llama, Qwen, Mistral, and more through the IO Intelligence API",
5
5
  "type": "module",
6
6
  "main": "index.ts",
@@ -81,9 +81,17 @@ function convertModel(apiModel, existingModelsMap) {
81
81
  if (priceOut > 0) existing.cost.output = Math.round(priceOut * 1e6) / 1e6;
82
82
  if (cacheRead > 0) existing.cost.cacheRead = Math.round(cacheRead * 1e6) / 1e6;
83
83
  if (cacheWrite > 0) existing.cost.cacheWrite = Math.round(cacheWrite * 1e6) / 1e6;
84
- if (apiModel.supports_images_input && !existing.input.includes('image')) {
85
- existing.input = ['text', 'image'];
86
- }
84
+ const hasVision = apiModel.supports_images_input === true || (apiModel.input_modalities || []).includes('image');
85
+ const hasReasoning = apiModel.capabilities?.reasoning === true || apiModel.supports_reasoning === true;
86
+ existing.input = hasVision ? ['text', 'image'] : ['text'];
87
+ existing.reasoning = hasReasoning;
88
+ existing.compat = {
89
+ ...(existing.compat || {}),
90
+ supportsStore: false,
91
+ supportsDeveloperRole: false,
92
+ maxTokensField: 'max_tokens',
93
+ ...(hasReasoning ? { supportsReasoningEffort: true } : {}),
94
+ };
87
95
  return existing;
88
96
  }
89
97
 
@@ -91,7 +99,7 @@ function convertModel(apiModel, existingModelsMap) {
91
99
  const ctx = apiModel.context_window || 0;
92
100
  const maxTok = apiModel.max_tokens || ctx;
93
101
  const input = ['text'];
94
- if (apiModel.supports_images_input) input.push('image');
102
+ if (apiModel.supports_images_input === true || (apiModel.input_modalities || []).includes('image')) input.push('image');
95
103
 
96
104
  const priceIn = (apiModel.input_token_price || 0) * 1_000_000;
97
105
  const priceOut = (apiModel.output_token_price || 0) * 1_000_000;
@@ -101,7 +109,7 @@ function convertModel(apiModel, existingModelsMap) {
101
109
  return {
102
110
  id,
103
111
  name: cleanName(apiModel.name, id),
104
- reasoning: false,
112
+ reasoning: apiModel.capabilities?.reasoning === true || apiModel.supports_reasoning === true,
105
113
  input,
106
114
  cost: {
107
115
  input: Math.round(priceIn * 1e6) / 1e6,
@@ -111,6 +119,14 @@ function convertModel(apiModel, existingModelsMap) {
111
119
  },
112
120
  contextWindow: ctx,
113
121
  maxTokens: maxTok,
122
+ compat: {
123
+ supportsStore: false,
124
+ supportsDeveloperRole: false,
125
+ maxTokensField: 'max_tokens',
126
+ ...((apiModel.capabilities?.reasoning === true || apiModel.supports_reasoning === true)
127
+ ? { supportsReasoningEffort: true }
128
+ : {}),
129
+ },
114
130
  };
115
131
  }
116
132
 
@@ -123,6 +139,7 @@ function applyPatch(model, patch) {
123
139
  if (patch.input !== undefined) result.input = patch.input;
124
140
  if (patch.contextWindow !== undefined) result.contextWindow = patch.contextWindow;
125
141
  if (patch.maxTokens !== undefined) result.maxTokens = patch.maxTokens;
142
+ if (patch.thinkingLevelMap !== undefined) result.thinkingLevelMap = { ...patch.thinkingLevelMap };
126
143
  if (patch.cost) {
127
144
  result.cost = {
128
145
  input: patch.cost.input ?? result.cost.input,
@@ -137,6 +154,9 @@ function applyPatch(model, patch) {
137
154
  if (!result.reasoning && result.compat?.thinkingFormat) {
138
155
  delete result.compat.thinkingFormat;
139
156
  }
157
+ if (!result.reasoning && result.thinkingLevelMap) {
158
+ delete result.thinkingLevelMap;
159
+ }
140
160
  if (result.compat && Object.keys(result.compat).length === 0) {
141
161
  delete result.compat;
142
162
  }
@@ -328,6 +348,67 @@ MIT
328
348
 
329
349
  // ─── Main ────────────────────────────────────────────────────────────────────
330
350
 
351
+ // Grace period for delisted models: update-models.js moves models the API no
352
+ // longer lists into deprecated-models.json (stamped with deprecatedAt) instead
353
+ // of dropping them; the runtime appends them back so sessions and saved model
354
+ // settings keep working, and after 14 days they are evicted permanently.
355
+ const DEPRECATED_MODEL_TTL_MS = 14 * 24 * 60 * 60 * 1000;
356
+
357
+ /**
358
+ * Reconcile deprecated-models.json against the freshly fetched model list.
359
+ * - in old models.json but not the API: moved into the deprecated file
360
+ * (deprecatedAt = now; preserved on repeat runs so the grace clock is not reset)
361
+ * - back in the API: resurrected (dropped from the deprecated file)
362
+ * - deprecatedAt older than 14 days: evicted permanently
363
+ * Must run BEFORE the new models.json is written; it reads the old file itself.
364
+ */
365
+ function updateDeprecatedModels(modelsJsonPath, newModels) {
366
+ const deprecatedPath = path.join(path.dirname(modelsJsonPath), 'deprecated-models.json');
367
+
368
+ let oldModels = [];
369
+ try {
370
+ const parsed = JSON.parse(fs.readFileSync(modelsJsonPath, 'utf8'));
371
+ if (Array.isArray(parsed)) oldModels = parsed;
372
+ } catch { /* first run: no previous models.json */ }
373
+
374
+ let deprecated = {};
375
+ try {
376
+ const parsed = JSON.parse(fs.readFileSync(deprecatedPath, 'utf8'));
377
+ if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) deprecated = parsed;
378
+ } catch { /* no graveyard yet */ }
379
+
380
+ const currentIds = new Set(newModels.map(m => m.id));
381
+ const now = new Date().toISOString();
382
+ const added = [];
383
+ const resurrected = [];
384
+ const evicted = [];
385
+
386
+ for (const old of oldModels) {
387
+ if (old && old.id && !currentIds.has(old.id) && !deprecated[old.id]) {
388
+ deprecated[old.id] = { ...old, deprecatedAt: now };
389
+ added.push(old.id);
390
+ }
391
+ }
392
+
393
+ for (const [id, entry] of Object.entries(deprecated)) {
394
+ if (currentIds.has(id)) {
395
+ delete deprecated[id];
396
+ resurrected.push(id);
397
+ continue;
398
+ }
399
+ const removedAt = Date.parse(entry && entry.deprecatedAt ? entry.deprecatedAt : '');
400
+ if (Number.isNaN(removedAt) || Date.now() - removedAt > DEPRECATED_MODEL_TTL_MS) {
401
+ delete deprecated[id];
402
+ evicted.push(id);
403
+ }
404
+ }
405
+
406
+ if (added.length > 0 || resurrected.length > 0 || evicted.length > 0) {
407
+ fs.writeFileSync(deprecatedPath, JSON.stringify(deprecated, null, 2) + '\n');
408
+ console.log('Updated deprecated-models.json ' + JSON.stringify({ added, resurrected, evicted }));
409
+ }
410
+ }
411
+
331
412
  async function main() {
332
413
  const apiKey = process.env.IOINTELLIGENCE_API_KEY;
333
414
  if (!apiKey) {
@@ -362,6 +443,8 @@ async function main() {
362
443
  console.log(`Converted ${models.length} models`);
363
444
 
364
445
  // Save models.json (pure API output, no patch/custom baked in)
446
+ // Move delisted models to deprecated-models.json BEFORE models.json is overwritten
447
+ updateDeprecatedModels(MODELS_PATH, models);
365
448
  fs.writeFileSync(MODELS_PATH, JSON.stringify(models, null, 2) + '\n');
366
449
  console.log(`✓ Saved ${models.length} models to models.json`);
367
450