pi-hypercharm-provider 1.1.4 → 1.1.5

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-hypercharm-provider",
3
- "version": "1.1.4",
3
+ "version": "1.1.5",
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",
package/patch.json CHANGED
@@ -1,155 +1 @@
1
- {
2
- "deepseek-v4-flash": {
3
- "maxTokens": 384000,
4
- "thinkingLevelMap": {
5
- "minimal": null,
6
- "low": null,
7
- "medium": null,
8
- "high": "high",
9
- "max": "max"
10
- },
11
- "compat": {
12
- "thinkingFormat": "deepseek",
13
- "maxTokensField": "max_tokens",
14
- "supportsDeveloperRole": true,
15
- "supportsStore": false,
16
- "supportsReasoningEffort": true,
17
- "requiresReasoningContentOnAssistantMessages": true
18
- }
19
- },
20
- "deepseek-v4-pro": {
21
- "maxTokens": 384000,
22
- "thinkingLevelMap": {
23
- "minimal": null,
24
- "low": null,
25
- "medium": null,
26
- "high": "high",
27
- "max": "max"
28
- },
29
- "compat": {
30
- "thinkingFormat": "deepseek",
31
- "maxTokensField": "max_tokens",
32
- "supportsDeveloperRole": true,
33
- "supportsStore": false,
34
- "supportsReasoningEffort": true,
35
- "requiresReasoningContentOnAssistantMessages": true
36
- }
37
- },
38
- "gemma-4-26b-a4b-it": {
39
- "compat": {
40
- "thinkingFormat": "openai",
41
- "maxTokensField": "max_tokens",
42
- "supportsDeveloperRole": true,
43
- "supportsStore": false,
44
- "supportsReasoningEffort": true
45
- }
46
- },
47
- "glm-5": {
48
- "compat": {
49
- "thinkingFormat": "openai",
50
- "maxTokensField": "max_tokens",
51
- "supportsDeveloperRole": true,
52
- "supportsStore": false,
53
- "supportsReasoningEffort": true
54
- }
55
- },
56
- "glm-5.1": {
57
- "compat": {
58
- "thinkingFormat": "openai",
59
- "maxTokensField": "max_tokens",
60
- "supportsDeveloperRole": true,
61
- "supportsStore": false,
62
- "supportsReasoningEffort": true
63
- }
64
- },
65
- "gpt-oss-120b": {
66
- "compat": {
67
- "thinkingFormat": "openai",
68
- "maxTokensField": "max_tokens",
69
- "supportsDeveloperRole": true,
70
- "supportsStore": false,
71
- "supportsReasoningEffort": true
72
- }
73
- },
74
- "kimi-k2.5": {
75
- "compat": {
76
- "thinkingFormat": "openai",
77
- "maxTokensField": "max_tokens",
78
- "supportsDeveloperRole": true,
79
- "supportsStore": false,
80
- "supportsReasoningEffort": true
81
- }
82
- },
83
- "kimi-k2.6": {
84
- "compat": {
85
- "thinkingFormat": "openai",
86
- "maxTokensField": "max_tokens",
87
- "supportsDeveloperRole": true,
88
- "supportsStore": false,
89
- "supportsReasoningEffort": true
90
- }
91
- },
92
- "minimax-m2.7": {
93
- "compat": {
94
- "thinkingFormat": "openai",
95
- "maxTokensField": "max_tokens",
96
- "supportsDeveloperRole": true,
97
- "supportsStore": false,
98
- "supportsReasoningEffort": true
99
- }
100
- },
101
- "qwen3.6-flash": {
102
- "compat": {
103
- "thinkingFormat": "openai",
104
- "maxTokensField": "max_tokens",
105
- "supportsDeveloperRole": true,
106
- "supportsStore": false,
107
- "supportsReasoningEffort": true
108
- }
109
- },
110
- "qwen3.6-max": {
111
- "compat": {
112
- "thinkingFormat": "openai",
113
- "maxTokensField": "max_tokens",
114
- "supportsDeveloperRole": true,
115
- "supportsStore": false,
116
- "supportsReasoningEffort": true
117
- }
118
- },
119
- "qwen3.6-plus": {
120
- "compat": {
121
- "thinkingFormat": "openai",
122
- "maxTokensField": "max_tokens",
123
- "supportsDeveloperRole": true,
124
- "supportsStore": false,
125
- "supportsReasoningEffort": true
126
- }
127
- },
128
- "qwen3.7-max": {
129
- "compat": {
130
- "thinkingFormat": "openai",
131
- "maxTokensField": "max_tokens",
132
- "supportsDeveloperRole": true,
133
- "supportsStore": false,
134
- "supportsReasoningEffort": true
135
- }
136
- },
137
- "qwen3-coder-480b-a35b-instruct-int4-mixed-ar": {
138
- "compat": {
139
- "thinkingFormat": "openai",
140
- "maxTokensField": "max_tokens",
141
- "supportsDeveloperRole": true,
142
- "supportsStore": false,
143
- "supportsReasoningEffort": true
144
- }
145
- },
146
- "qwen3-next-80b-a3b-instruct": {
147
- "compat": {
148
- "thinkingFormat": "openai",
149
- "maxTokensField": "max_tokens",
150
- "supportsDeveloperRole": true,
151
- "supportsStore": false,
152
- "supportsReasoningEffort": true
153
- }
154
- }
155
- }
1
+ {}
@@ -1,20 +1,16 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  /**
4
- * Update HyperCharm models from API
4
+ * Update HyperCharm models from Charm's official typed provider catalog
5
5
  *
6
- * Fetches models from https://hyper.charm.land/v1/models and updates:
7
- * - models.json: Model definitions with curated reasoning/vision flags + API pricing
8
- * - README.md: Model table with patch.json overrides applied
6
+ * Fetches models from https://hyper.charm.land/v1/provider and updates:
7
+ * - models.json: canonical API-owned metadata used by @charmland/pi-hyper-provider
8
+ * - README.md: model table with patch.json overrides applied
9
9
  *
10
- * The HyperCharm API provides: id, display_name, supports_reasoning,
11
- * supports_reasoning_effort, supports_attachments, context_window,
12
- * max_output_tokens, and cost.usd pricing.
13
- *
14
- * Note: supports_reasoning is unreliable for some models (reports true for
15
- * Llama 3.3 70B which doesn't support extended thinking). models.json
16
- * curates reasoning flags based on known model capabilities; patch.json
17
- * adds compat flags and corrections.
10
+ * The endpoint provides canonical names, $/M pricing, context/output limits,
11
+ * can_reason, optional reasoning levels, and attachment support. models.json is
12
+ * pure API data. patch.json is reserved for verified endpoint regressions and
13
+ * currently contains no overrides.
18
14
  *
19
15
  * Merge order for README: models.json → apply patch.json → merge custom-models.json
20
16
  */
@@ -25,7 +21,7 @@ import { fileURLToPath } from 'url';
25
21
 
26
22
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
27
23
 
28
- const MODELS_API_URL = 'https://hyper.charm.land/v1/models';
24
+ const MODELS_API_URL = 'https://hyper.charm.land/v1/provider';
29
25
  const MODELS_JSON_PATH = path.join(__dirname, '..', 'models.json');
30
26
  const PATCH_JSON_PATH = path.join(__dirname, '..', 'patch.json');
31
27
  const CUSTOM_MODELS_JSON_PATH = path.join(__dirname, '..', 'custom-models.json');
@@ -76,6 +72,9 @@ function applyPatch(model, patch) {
76
72
  if (!result.reasoning && result.compat?.thinkingFormat) {
77
73
  delete result.compat.thinkingFormat;
78
74
  }
75
+ if (!result.reasoning && result.thinkingLevelMap) {
76
+ delete result.thinkingLevelMap;
77
+ }
79
78
  if (result.compat && Object.keys(result.compat).length === 0) {
80
79
  delete result.compat;
81
80
  }
@@ -102,91 +101,68 @@ function buildModels(baseModels, customModels, patchData) {
102
101
 
103
102
  // ─── Model transformation ─────────────────────────────────────────────────────
104
103
 
105
- // Known non-reasoning models (API incorrectly reports supports_reasoning: true)
106
- const NON_REASONING_IDS = new Set([
107
- 'llama-3.3-70b-instruct',
108
- 'llama-4-maverick-17b-128e-instruct-fp8',
109
- ]);
110
-
111
- // Known vision models
112
- const VISION_IDS = new Set([
113
- 'kimi-k2.5',
114
- 'kimi-k2.6',
115
- 'glm-5.1',
116
- 'gemma-4-26b-a4b-it',
117
- 'qwen3.6-flash',
118
- 'qwen3.6-max',
119
- 'qwen3.6-plus',
120
- 'qwen3.7-max',
121
- ]);
122
-
123
- function transformModel(apiModel, existingModelsMap) {
124
- const modelId = apiModel.id;
125
-
126
- // Preserve existing curated data
127
- if (existingModelsMap[modelId]) {
128
- const existing = { ...existingModelsMap[modelId] };
129
-
130
- // Update fields from API that may change
131
- const cost = apiModel.cost?.usd || {};
132
- const inputCost = convertPricing(cost['1m_in']);
133
- const outputCost = convertPricing(cost['1m_out']);
134
- const cacheReadCost = convertPricing(cost['1m_in_cache']);
135
- const cacheWriteCost = convertPricing(cost['1m_out_cache']);
136
-
137
- if (inputCost > 0) existing.cost.input = inputCost;
138
- if (outputCost > 0) existing.cost.output = outputCost;
139
- if (cacheReadCost > 0) existing.cost.cacheRead = cacheReadCost;
140
- if (cacheWriteCost > 0) existing.cost.cacheWrite = cacheWriteCost;
141
- if (apiModel.context_window) existing.contextWindow = apiModel.context_window;
142
- // Don't override maxTokens from API for DeepSeek — it reports 8000 but the
143
- // actual max is 384K (set in models.json / patch.json)
144
- if (apiModel.max_output_tokens && !/^deepseek-v/.test(modelId)) {
145
- existing.maxTokens = apiModel.max_output_tokens;
146
- }
147
-
148
- return existing;
104
+ const PI_THINKING_LEVELS = ['minimal', 'low', 'medium', 'high', 'xhigh', 'max'];
105
+
106
+ // Charm's official extension treats a reasoning-capable model with no levels as
107
+ // a boolean on/off model: Pi's max level selects the single on state.
108
+ const ON_OFF_THINKING_LEVEL_MAP = {
109
+ off: 'off',
110
+ minimal: null,
111
+ low: null,
112
+ medium: null,
113
+ high: null,
114
+ xhigh: null,
115
+ max: 'max',
116
+ };
117
+
118
+ function buildThinkingLevelMap(levels) {
119
+ if (levels.length === 0) return undefined;
120
+ const available = new Set(levels);
121
+ const result = {
122
+ // The provider enum uses "none" for the off state on newer deployments;
123
+ // the official extension looked only for the older "off" spelling.
124
+ off: available.has('off') ? 'off' : available.has('none') ? 'none' : null,
125
+ };
126
+ for (const level of PI_THINKING_LEVELS) {
127
+ result[level] = available.has(level) ? level : null;
149
128
  }
129
+ return result;
130
+ }
150
131
 
151
- // New model — build from API data + curated defaults
152
- const cost = apiModel.cost?.usd || {};
153
- const isReasoning = apiModel.supports_reasoning === true && !NON_REASONING_IDS.has(modelId);
154
- const isVision = VISION_IDS.has(modelId);
155
- const isDeepSeek = /^deepseek-v/.test(modelId);
156
-
157
- const model = {
158
- id: modelId,
159
- name: apiModel.display_name || modelId,
160
- reasoning: isReasoning,
161
- input: isVision ? ['text', 'image'] : ['text'],
132
+ function transformModel(apiModel) {
133
+ const reasoningLevels = Array.isArray(apiModel.reasoning_levels)
134
+ ? apiModel.reasoning_levels.filter(level => typeof level === 'string')
135
+ : [];
136
+ const supportsReasoningEffort = reasoningLevels.length > 0;
137
+ const thinkingLevelMap = supportsReasoningEffort
138
+ ? buildThinkingLevelMap(reasoningLevels)
139
+ : apiModel.can_reason === true
140
+ ? ON_OFF_THINKING_LEVEL_MAP
141
+ : undefined;
142
+
143
+ return {
144
+ id: apiModel.id,
145
+ name: apiModel.name,
146
+ reasoning: apiModel.can_reason === true,
147
+ ...(thinkingLevelMap ? { thinkingLevelMap } : {}),
148
+ input: apiModel.supports_attachments === true ? ['text', 'image'] : ['text'],
162
149
  cost: {
163
- input: convertPricing(cost['1m_in']),
164
- output: convertPricing(cost['1m_out']),
165
- cacheRead: convertPricing(cost['1m_in_cache']),
166
- cacheWrite: convertPricing(cost['1m_out_cache']),
150
+ input: typeof apiModel.cost_per_1m_in === 'number' ? apiModel.cost_per_1m_in : 0,
151
+ output: typeof apiModel.cost_per_1m_out === 'number' ? apiModel.cost_per_1m_out : 0,
152
+ cacheRead: typeof apiModel.cost_per_1m_in_cached === 'number' ? apiModel.cost_per_1m_in_cached : 0,
153
+ // Hyper exposes discounted cached-output pricing, not cache-write pricing;
154
+ // pi's cost.cacheWrite field remains unused. This matches Charm's official extension.
155
+ cacheWrite: 0,
167
156
  },
168
157
  contextWindow: apiModel.context_window || 0,
169
- maxTokens: apiModel.max_output_tokens || 0,
170
- };
171
-
172
- // DeepSeek models: override maxTokens (API reports 8000, actual is 384K)
173
- // and add thinkingLevelMap + deepseek compat
174
- if (isDeepSeek && isReasoning) {
175
- model.maxTokens = 384000;
176
- model.thinkingLevelMap = {
177
- minimal: null, low: null, medium: null, high: 'high', max: 'max',
178
- };
179
- model.compat = {
158
+ maxTokens: apiModel.default_max_tokens || apiModel.context_window || 0,
159
+ compat: {
160
+ supportsStore: false,
161
+ supportsReasoningEffort,
180
162
  thinkingFormat: 'deepseek',
181
163
  maxTokensField: 'max_tokens',
182
- supportsDeveloperRole: true,
183
- supportsStore: false,
184
- supportsReasoningEffort: true,
185
- requiresReasoningContentOnAssistantMessages: true,
186
- };
187
- }
188
-
189
- return model;
164
+ },
165
+ };
190
166
  }
191
167
 
192
168
  // ─── README generation ────────────────────────────────────────────────────────
@@ -317,7 +293,9 @@ async function main() {
317
293
  }
318
294
 
319
295
  const apiResponse = await response.json();
320
- const apiModels = apiResponse.data || apiResponse;
296
+ const apiModels = Array.isArray(apiResponse)
297
+ ? apiResponse
298
+ : (apiResponse.models || apiResponse.data || []);
321
299
 
322
300
  if (!Array.isArray(apiModels)) {
323
301
  throw new Error('API response does not contain an array of models');
@@ -338,16 +316,12 @@ async function main() {
338
316
  }
339
317
 
340
318
  // Transform models from API, preserving existing curated data
341
- let apiTransformed = apiModels.map(m => transformModel(m, existingModelsMap));
319
+ let apiTransformed = apiModels.map(m => transformModel(m));
342
320
  apiTransformed.sort((a, b) => a.name.localeCompare(b.name));
343
321
 
344
- // Log new models (not in patch.json)
322
+ // Load patch overrides for README rendering. Canonical metadata already
323
+ // comes from /v1/provider, so new models do not require a patch entry.
345
324
  const patch = loadJson(PATCH_JSON_PATH);
346
- for (const m of apiTransformed) {
347
- if (!patch[m.id]) {
348
- console.log(` 🆕 New model: ${m.id} (${m.name}) — add to patch.json for compat overrides`);
349
- }
350
- }
351
325
 
352
326
  // Update models.json — curated API data
353
327
  // Move delisted models to deprecated-models.json BEFORE models.json is overwritten