pi-umans-provider 1.0.9 → 1.0.10
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/README.md +7 -2
- package/package.json +5 -1
- package/.github/FUNDING.yml +0 -4
- package/AGENTS.md +0 -57
- package/custom-models.json +0 -1
- package/deprecated-models.json +0 -55
- package/models.json +0 -118
- package/patch.json +0 -90
- package/pnpm-workspace.yaml +0 -11
- package/scripts/update-models.js +0 -326
- package/tsconfig.json +0 -12
package/README.md
CHANGED
|
@@ -29,11 +29,16 @@ _A [pi](https://github.com/earendil-works/pi-coding-agent) provider extension wi
|
|
|
29
29
|
|-------|------|---------|--------|-----------|------------|
|
|
30
30
|
| Coder | Kimi K2.7-Code | 262K | ✅ | ✅ | 33K |
|
|
31
31
|
| DeepSeek V4 Flash | DeepSeek-V4-Flash | 1.0M | ❌ | ✅ | 393K |
|
|
32
|
-
| DeepSeek V4
|
|
32
|
+
| DeepSeek V4 Flash (lab) | — | 1.0M | ❌ | ✅ | 393K |
|
|
33
|
+
| DeepSeek V4 Flash Vision (lab) | — | 1.0M | ✅ | ✅ | 393K |
|
|
34
|
+
| DeepSeek V4 Pro | DeepSeek-V4-Pro-0813 | 1.0M | ❌ | ✅ | 393K |
|
|
35
|
+
| DeepSeek V4 Pro 0813 (lab) | — | 1.0M | ❌ | ✅ | 393K |
|
|
33
36
|
| Flash | Qwen3.6-35B-A3B | 262K | ✅ | ✅ | 33K |
|
|
34
|
-
| GLM 5.2 |
|
|
37
|
+
| GLM 5.2 | — | 406K | ❌ | ✅ | 131K |
|
|
38
|
+
| Kimi K2.7 Code | — | 262K | ✅ | ✅ | 33K |
|
|
35
39
|
| Kimi K3 | Kimi K3 | 1.0M | ✅ | ✅ | 131K |
|
|
36
40
|
| Qwen3.6 35B A3B | Qwen3.6-35B-A3B | 262K | ✅ | ✅ | 33K |
|
|
41
|
+
| Qwen3.8 27B (lab) | Qwen3.8-27B | 262K | ✅ | ✅ | 131K |
|
|
37
42
|
|
|
38
43
|
> **Note:** `umans-flash-beta` is deprecated (sunset 2026-06-07). Use `umans-flash` instead.
|
|
39
44
|
> `umans-qwen3.6-35b-a3b` is a technical alias for `umans-flash`.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-umans-provider",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.10",
|
|
4
4
|
"description": "UMANS provider extension for pi - Access Kimi K2.6, GLM 5.1, Qwen 3.6, and coding-optimized models through the UMANS API",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.ts",
|
|
@@ -34,6 +34,10 @@
|
|
|
34
34
|
"@earendil-works/pi-ai": "*",
|
|
35
35
|
"@earendil-works/pi-coding-agent": "*"
|
|
36
36
|
},
|
|
37
|
+
"files": [
|
|
38
|
+
"README.md",
|
|
39
|
+
"LICENSE"
|
|
40
|
+
],
|
|
37
41
|
"scripts": {
|
|
38
42
|
"clean": "echo 'nothing to clean'",
|
|
39
43
|
"build": "echo 'nothing to build'",
|
package/.github/FUNDING.yml
DELETED
package/AGENTS.md
DELETED
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
# AGENTS.md
|
|
2
|
-
|
|
3
|
-
## DO NOT EDIT — Auto-generated Files
|
|
4
|
-
|
|
5
|
-
The following files are **idempotent** and regenerated by `scripts/update-models.js`. Never edit them directly — your changes will be overwritten on the next model sync.
|
|
6
|
-
|
|
7
|
-
| File | Why it's auto-generated |
|
|
8
|
-
|------|------------------------|
|
|
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. |
|
|
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. |
|
|
12
|
-
|
|
13
|
-
## Correct Files to Edit
|
|
14
|
-
|
|
15
|
-
When a model needs overrides, new properties, or corrections, edit the appropriate source file below. These are the **source of truth** that the update script reads but never writes.
|
|
16
|
-
|
|
17
|
-
| File | Purpose |
|
|
18
|
-
|------|---------|
|
|
19
|
-
| `patch.json` | Per-model overrides keyed by model ID. Add reasoning flags, compat settings, pricing corrections, thinking level maps, etc. Applied on top of `models.json` at runtime and for README generation. |
|
|
20
|
-
| `custom-models.json` | Models that don't exist in the provider API (hidden models, router endpoints, cross-provider aliases). Merged after patch. Format: array of full model objects (same schema as `models.json` entries). |
|
|
21
|
-
| `index.ts` | Provider extension code. |
|
|
22
|
-
| `scripts/update-models.js` | The sync script itself (edit only if changing how models are fetched/transformed). |
|
|
23
|
-
|
|
24
|
-
## Data Flow
|
|
25
|
-
|
|
26
|
-
```
|
|
27
|
-
Provider API ──fetch──► models.json ──apply──► patch.json ──merge──► custom-models.json
|
|
28
|
-
│ │ │
|
|
29
|
-
└────────────────────────────┴──────────────────────┘
|
|
30
|
-
│
|
|
31
|
-
README model table
|
|
32
|
-
```
|
|
33
|
-
|
|
34
|
-
1. `models.json` — base data from the provider API (auto-generated, DO NOT EDIT)
|
|
35
|
-
2. `patch.json` — overrides applied on top (EDIT THIS for corrections/enrichments)
|
|
36
|
-
3. `custom-models.json` — additional models not in the API (EDIT THIS for new models)
|
|
37
|
-
4. README table — rendered from the merged result of all three (auto-generated, DO NOT EDIT)
|
|
38
|
-
|
|
39
|
-
## Common Tasks
|
|
40
|
-
|
|
41
|
-
### Add a compat setting or override pricing for an existing model
|
|
42
|
-
→ Edit `patch.json`. Add an entry keyed by the model's `id`.
|
|
43
|
-
|
|
44
|
-
### Add a model not available in the provider API
|
|
45
|
-
→ Edit `custom-models.json`. Add a full model object to the array.
|
|
46
|
-
|
|
47
|
-
### Update models from the provider API
|
|
48
|
-
→ Run `node scripts/update-models.js` (may require an API key env var).
|
|
49
|
-
|
|
50
|
-
### Regenerate the README model table
|
|
51
|
-
→ Run `node scripts/update-models.js` — it updates both `models.json` and the README table.
|
|
52
|
-
|
|
53
|
-
## TL;DR
|
|
54
|
-
|
|
55
|
-
- **Never edit `models.json`** — edit `patch.json` instead.
|
|
56
|
-
- **Never edit the README model table** — run the update script instead.
|
|
57
|
-
- `patch.json` and `custom-models.json` are the source files you should modify.
|
package/custom-models.json
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
[]
|
package/deprecated-models.json
DELETED
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"umans-kimi-k2.7": {
|
|
3
|
-
"id": "umans-kimi-k2.7",
|
|
4
|
-
"name": "Umans Kimi K2.7 Code",
|
|
5
|
-
"reasoning": true,
|
|
6
|
-
"input": [
|
|
7
|
-
"text",
|
|
8
|
-
"image"
|
|
9
|
-
],
|
|
10
|
-
"cost": {
|
|
11
|
-
"input": 0,
|
|
12
|
-
"output": 0,
|
|
13
|
-
"cacheRead": 0,
|
|
14
|
-
"cacheWrite": 0
|
|
15
|
-
},
|
|
16
|
-
"contextWindow": 262144,
|
|
17
|
-
"maxTokens": 32768,
|
|
18
|
-
"deprecatedAt": "2026-08-05T02:00:18.239Z"
|
|
19
|
-
},
|
|
20
|
-
"umans-deepseek-v4-flash-0731-lab": {
|
|
21
|
-
"id": "umans-deepseek-v4-flash-0731-lab",
|
|
22
|
-
"name": "Umans DeepSeek V4 Flash (lab)",
|
|
23
|
-
"reasoning": true,
|
|
24
|
-
"input": [
|
|
25
|
-
"text"
|
|
26
|
-
],
|
|
27
|
-
"cost": {
|
|
28
|
-
"input": 0,
|
|
29
|
-
"output": 0,
|
|
30
|
-
"cacheRead": 0,
|
|
31
|
-
"cacheWrite": 0
|
|
32
|
-
},
|
|
33
|
-
"contextWindow": 1048576,
|
|
34
|
-
"maxTokens": 393215,
|
|
35
|
-
"deprecatedAt": "2026-08-12T06:38:20.905Z"
|
|
36
|
-
},
|
|
37
|
-
"umans-deepseek-v4-flash-0731-vision-lab": {
|
|
38
|
-
"id": "umans-deepseek-v4-flash-0731-vision-lab",
|
|
39
|
-
"name": "Umans DeepSeek V4 Flash Vision (lab)",
|
|
40
|
-
"reasoning": true,
|
|
41
|
-
"input": [
|
|
42
|
-
"text",
|
|
43
|
-
"image"
|
|
44
|
-
],
|
|
45
|
-
"cost": {
|
|
46
|
-
"input": 0,
|
|
47
|
-
"output": 0,
|
|
48
|
-
"cacheRead": 0,
|
|
49
|
-
"cacheWrite": 0
|
|
50
|
-
},
|
|
51
|
-
"contextWindow": 1048576,
|
|
52
|
-
"maxTokens": 393215,
|
|
53
|
-
"deprecatedAt": "2026-08-14T02:00:18.070Z"
|
|
54
|
-
}
|
|
55
|
-
}
|
package/models.json
DELETED
|
@@ -1,118 +0,0 @@
|
|
|
1
|
-
[
|
|
2
|
-
{
|
|
3
|
-
"id": "umans-coder",
|
|
4
|
-
"name": "Umans Coder",
|
|
5
|
-
"reasoning": true,
|
|
6
|
-
"input": [
|
|
7
|
-
"text",
|
|
8
|
-
"image"
|
|
9
|
-
],
|
|
10
|
-
"cost": {
|
|
11
|
-
"input": 0,
|
|
12
|
-
"output": 0,
|
|
13
|
-
"cacheRead": 0,
|
|
14
|
-
"cacheWrite": 0
|
|
15
|
-
},
|
|
16
|
-
"contextWindow": 262144,
|
|
17
|
-
"maxTokens": 32768
|
|
18
|
-
},
|
|
19
|
-
{
|
|
20
|
-
"id": "umans-deepseek-v4-flash-0731",
|
|
21
|
-
"name": "Umans DeepSeek V4 Flash",
|
|
22
|
-
"reasoning": true,
|
|
23
|
-
"input": [
|
|
24
|
-
"text"
|
|
25
|
-
],
|
|
26
|
-
"cost": {
|
|
27
|
-
"input": 0,
|
|
28
|
-
"output": 0,
|
|
29
|
-
"cacheRead": 0,
|
|
30
|
-
"cacheWrite": 0
|
|
31
|
-
},
|
|
32
|
-
"contextWindow": 1048576,
|
|
33
|
-
"maxTokens": 393215
|
|
34
|
-
},
|
|
35
|
-
{
|
|
36
|
-
"id": "umans-deepseek-v4-pro-0813-lab",
|
|
37
|
-
"name": "Umans DeepSeek V4 Pro 0813 (lab)",
|
|
38
|
-
"reasoning": true,
|
|
39
|
-
"input": [
|
|
40
|
-
"text"
|
|
41
|
-
],
|
|
42
|
-
"cost": {
|
|
43
|
-
"input": 0,
|
|
44
|
-
"output": 0,
|
|
45
|
-
"cacheRead": 0,
|
|
46
|
-
"cacheWrite": 0
|
|
47
|
-
},
|
|
48
|
-
"contextWindow": 524288,
|
|
49
|
-
"maxTokens": 393215
|
|
50
|
-
},
|
|
51
|
-
{
|
|
52
|
-
"id": "umans-flash",
|
|
53
|
-
"name": "Umans Flash",
|
|
54
|
-
"reasoning": true,
|
|
55
|
-
"input": [
|
|
56
|
-
"text",
|
|
57
|
-
"image"
|
|
58
|
-
],
|
|
59
|
-
"cost": {
|
|
60
|
-
"input": 0,
|
|
61
|
-
"output": 0,
|
|
62
|
-
"cacheRead": 0,
|
|
63
|
-
"cacheWrite": 0
|
|
64
|
-
},
|
|
65
|
-
"contextWindow": 262144,
|
|
66
|
-
"maxTokens": 32768
|
|
67
|
-
},
|
|
68
|
-
{
|
|
69
|
-
"id": "umans-glm-5.2",
|
|
70
|
-
"name": "Umans GLM 5.2",
|
|
71
|
-
"reasoning": true,
|
|
72
|
-
"input": [
|
|
73
|
-
"text"
|
|
74
|
-
],
|
|
75
|
-
"cost": {
|
|
76
|
-
"input": 0,
|
|
77
|
-
"output": 0,
|
|
78
|
-
"cacheRead": 0,
|
|
79
|
-
"cacheWrite": 0
|
|
80
|
-
},
|
|
81
|
-
"contextWindow": 405504,
|
|
82
|
-
"maxTokens": 131071
|
|
83
|
-
},
|
|
84
|
-
{
|
|
85
|
-
"id": "umans-kimi-k3",
|
|
86
|
-
"name": "Umans Kimi K3",
|
|
87
|
-
"reasoning": true,
|
|
88
|
-
"input": [
|
|
89
|
-
"text",
|
|
90
|
-
"image"
|
|
91
|
-
],
|
|
92
|
-
"cost": {
|
|
93
|
-
"input": 0,
|
|
94
|
-
"output": 0,
|
|
95
|
-
"cacheRead": 0,
|
|
96
|
-
"cacheWrite": 0
|
|
97
|
-
},
|
|
98
|
-
"contextWindow": 1048576,
|
|
99
|
-
"maxTokens": 131071
|
|
100
|
-
},
|
|
101
|
-
{
|
|
102
|
-
"id": "umans-qwen3.6-35b-a3b",
|
|
103
|
-
"name": "Umans Qwen3.6 35B A3B",
|
|
104
|
-
"reasoning": true,
|
|
105
|
-
"input": [
|
|
106
|
-
"text",
|
|
107
|
-
"image"
|
|
108
|
-
],
|
|
109
|
-
"cost": {
|
|
110
|
-
"input": 0,
|
|
111
|
-
"output": 0,
|
|
112
|
-
"cacheRead": 0,
|
|
113
|
-
"cacheWrite": 0
|
|
114
|
-
},
|
|
115
|
-
"contextWindow": 262144,
|
|
116
|
-
"maxTokens": 32768
|
|
117
|
-
}
|
|
118
|
-
]
|
package/patch.json
DELETED
|
@@ -1,90 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"umans-coder": {
|
|
3
|
-
"thinkingLevelMap": {
|
|
4
|
-
"off": "none"
|
|
5
|
-
},
|
|
6
|
-
"compat": {
|
|
7
|
-
"supportsDeveloperRole": false,
|
|
8
|
-
"supportsReasoningEffort": true,
|
|
9
|
-
"requiresReasoningContentOnAssistantMessages": true
|
|
10
|
-
}
|
|
11
|
-
},
|
|
12
|
-
"umans-flash": {
|
|
13
|
-
"thinkingLevelMap": {
|
|
14
|
-
"off": "none"
|
|
15
|
-
},
|
|
16
|
-
"compat": {
|
|
17
|
-
"supportsDeveloperRole": false,
|
|
18
|
-
"supportsReasoningEffort": true,
|
|
19
|
-
"requiresReasoningContentOnAssistantMessages": true
|
|
20
|
-
}
|
|
21
|
-
},
|
|
22
|
-
"umans-kimi-k2.7": {
|
|
23
|
-
"thinkingLevelMap": {
|
|
24
|
-
"off": "none"
|
|
25
|
-
},
|
|
26
|
-
"compat": {
|
|
27
|
-
"supportsDeveloperRole": false,
|
|
28
|
-
"supportsReasoningEffort": true,
|
|
29
|
-
"requiresReasoningContentOnAssistantMessages": true
|
|
30
|
-
}
|
|
31
|
-
},
|
|
32
|
-
"umans-glm-5.2": {
|
|
33
|
-
"thinkingLevelMap": {
|
|
34
|
-
"off": "none"
|
|
35
|
-
},
|
|
36
|
-
"compat": {
|
|
37
|
-
"supportsDeveloperRole": false,
|
|
38
|
-
"supportsReasoningEffort": true,
|
|
39
|
-
"requiresReasoningContentOnAssistantMessages": true
|
|
40
|
-
}
|
|
41
|
-
},
|
|
42
|
-
"umans-qwen3.6-35b-a3b": {
|
|
43
|
-
"thinkingLevelMap": {
|
|
44
|
-
"off": "none"
|
|
45
|
-
},
|
|
46
|
-
"compat": {
|
|
47
|
-
"supportsDeveloperRole": false,
|
|
48
|
-
"supportsReasoningEffort": true,
|
|
49
|
-
"requiresReasoningContentOnAssistantMessages": true
|
|
50
|
-
}
|
|
51
|
-
},
|
|
52
|
-
"umans-deepseek-v4-flash-0731": {
|
|
53
|
-
"reasoning": true,
|
|
54
|
-
"thinkingLevelMap": {
|
|
55
|
-
"off": "none",
|
|
56
|
-
"minimal": null,
|
|
57
|
-
"low": "high",
|
|
58
|
-
"medium": "high",
|
|
59
|
-
"high": "high",
|
|
60
|
-
"xhigh": null,
|
|
61
|
-
"max": "max"
|
|
62
|
-
},
|
|
63
|
-
"compat": {
|
|
64
|
-
"thinkingFormat": "openai",
|
|
65
|
-
"maxTokensField": "max_tokens",
|
|
66
|
-
"supportsReasoningEffort": true
|
|
67
|
-
}
|
|
68
|
-
},
|
|
69
|
-
"umans-kimi-k3": {
|
|
70
|
-
"reasoning": true,
|
|
71
|
-
"input": [
|
|
72
|
-
"text",
|
|
73
|
-
"image"
|
|
74
|
-
],
|
|
75
|
-
"thinkingLevelMap": {
|
|
76
|
-
"off": null,
|
|
77
|
-
"minimal": null,
|
|
78
|
-
"low": "low",
|
|
79
|
-
"medium": null,
|
|
80
|
-
"high": "high",
|
|
81
|
-
"xhigh": null,
|
|
82
|
-
"max": "max"
|
|
83
|
-
},
|
|
84
|
-
"compat": {
|
|
85
|
-
"thinkingFormat": "openai",
|
|
86
|
-
"maxTokensField": "max_tokens",
|
|
87
|
-
"supportsReasoningEffort": true
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
}
|
package/pnpm-workspace.yaml
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
allowBuilds:
|
|
2
|
-
'@google/genai': false
|
|
3
|
-
protobufjs: false
|
|
4
|
-
minimumReleaseAgeExclude:
|
|
5
|
-
- '@earendil-works/pi-agent-core@0.84.0 || 0.84.1 || 0.84.2'
|
|
6
|
-
- '@earendil-works/pi-ai@0.84.0 || 0.84.1 || 0.84.2'
|
|
7
|
-
- '@earendil-works/pi-client@0.84.0 || 0.84.1 || 0.84.2'
|
|
8
|
-
- '@earendil-works/pi-coding-agent@0.84.0 || 0.84.1 || 0.84.2'
|
|
9
|
-
- '@earendil-works/pi-protocol@0.84.0 || 0.84.1 || 0.84.2'
|
|
10
|
-
- '@earendil-works/pi-telemetry@0.84.0 || 0.84.1 || 0.84.2'
|
|
11
|
-
- '@earendil-works/pi-tui@0.84.0 || 0.84.1 || 0.84.2'
|
package/scripts/update-models.js
DELETED
|
@@ -1,326 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* Update UMANS models from API
|
|
4
|
-
*
|
|
5
|
-
* Fetches models from https://api.code.umans.ai/v1/models/info and updates:
|
|
6
|
-
* - models.json: Pure API model definitions (no patches baked in)
|
|
7
|
-
* - README.md: Model table with patch.json overrides applied
|
|
8
|
-
*
|
|
9
|
-
* models.json reflects the raw API data as-is. patch.json corrects
|
|
10
|
-
* compatibility details at runtime (index.ts) and is also applied when
|
|
11
|
-
* generating the README table so the docs reflect reality.
|
|
12
|
-
*
|
|
13
|
-
* Idempotent: re-running produces the same result if the API hasn't changed.
|
|
14
|
-
*/
|
|
15
|
-
|
|
16
|
-
import fs from "fs";
|
|
17
|
-
import path from "path";
|
|
18
|
-
import { fileURLToPath } from "url";
|
|
19
|
-
|
|
20
|
-
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
21
|
-
|
|
22
|
-
const MODELS_INFO_API_URL = "https://api.code.umans.ai/v1/models/info";
|
|
23
|
-
const MODELS_JSON_PATH = path.join(__dirname, "..", "models.json");
|
|
24
|
-
const PATCH_JSON_PATH = path.join(__dirname, "..", "patch.json");
|
|
25
|
-
const CUSTOM_MODELS_JSON_PATH = path.join(__dirname, "..", "custom-models.json");
|
|
26
|
-
const README_PATH = path.join(__dirname, "..", "README.md");
|
|
27
|
-
|
|
28
|
-
// Skip deprecated aliases
|
|
29
|
-
const DEPRECATED_MODELS = new Set(["umans-flash-beta"]);
|
|
30
|
-
|
|
31
|
-
function transformApiModel(id, info) {
|
|
32
|
-
if (info.deprecation || DEPRECATED_MODELS.has(id)) return null;
|
|
33
|
-
|
|
34
|
-
const caps = info.capabilities || {};
|
|
35
|
-
const hasVision = caps.supports_vision === true;
|
|
36
|
-
|
|
37
|
-
return {
|
|
38
|
-
id,
|
|
39
|
-
name: info.display_name || info.name || id,
|
|
40
|
-
reasoning: true, // All UMANS models are reasoning-capable
|
|
41
|
-
input: hasVision ? ["text", "image"] : ["text"],
|
|
42
|
-
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, // Subscription-based
|
|
43
|
-
contextWindow: caps.context_window || 131072,
|
|
44
|
-
maxTokens: caps.recommended_max_tokens || caps.max_completion_tokens || 131072,
|
|
45
|
-
};
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
function applyPatch(model, patch) {
|
|
49
|
-
const result = { ...model };
|
|
50
|
-
if (patch.name !== undefined) result.name = patch.name;
|
|
51
|
-
if (patch.reasoning !== undefined) result.reasoning = patch.reasoning;
|
|
52
|
-
if (patch.input !== undefined) result.input = patch.input;
|
|
53
|
-
if (patch.contextWindow !== undefined) result.contextWindow = patch.contextWindow;
|
|
54
|
-
if (patch.maxTokens !== undefined) result.maxTokens = patch.maxTokens;
|
|
55
|
-
if (patch.cost) {
|
|
56
|
-
result.cost = {
|
|
57
|
-
input: patch.cost.input ?? result.cost.input,
|
|
58
|
-
output: patch.cost.output ?? result.cost.output,
|
|
59
|
-
cacheRead: patch.cost.cacheRead ?? result.cost.cacheRead,
|
|
60
|
-
cacheWrite: patch.cost.cacheWrite ?? result.cost.cacheWrite,
|
|
61
|
-
};
|
|
62
|
-
}
|
|
63
|
-
if (patch.compat) {
|
|
64
|
-
result.compat = { ...(result.compat || {}), ...patch.compat };
|
|
65
|
-
}
|
|
66
|
-
if (!result.reasoning && result.compat?.thinkingFormat) {
|
|
67
|
-
delete result.compat.thinkingFormat;
|
|
68
|
-
}
|
|
69
|
-
if (result.compat && Object.keys(result.compat).length === 0) {
|
|
70
|
-
delete result.compat;
|
|
71
|
-
}
|
|
72
|
-
return result;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
function buildModels(baseModels, customModels, patchData) {
|
|
76
|
-
const modelMap = new Map();
|
|
77
|
-
for (const model of baseModels) {
|
|
78
|
-
modelMap.set(model.id, model);
|
|
79
|
-
}
|
|
80
|
-
for (const [id, patchEntry] of Object.entries(patchData)) {
|
|
81
|
-
const existing = modelMap.get(id);
|
|
82
|
-
if (existing) {
|
|
83
|
-
modelMap.set(id, applyPatch(existing, patchEntry));
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
for (const model of customModels) {
|
|
87
|
-
const existing = modelMap.get(model.id);
|
|
88
|
-
const patchEntry = patchData[model.id];
|
|
89
|
-
if (existing && patchEntry) {
|
|
90
|
-
modelMap.set(model.id, applyPatch(model, patchEntry));
|
|
91
|
-
} else if (existing) {
|
|
92
|
-
modelMap.set(model.id, model);
|
|
93
|
-
} else if (patchEntry) {
|
|
94
|
-
modelMap.set(model.id, applyPatch(model, patchEntry));
|
|
95
|
-
} else {
|
|
96
|
-
modelMap.set(model.id, model);
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
return Array.from(modelMap.values());
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
function loadJson(filePath) {
|
|
103
|
-
try {
|
|
104
|
-
return JSON.parse(fs.readFileSync(filePath, "utf8"));
|
|
105
|
-
} catch {
|
|
106
|
-
return Array.isArray(filePath) ? [] : {};
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
function formatContextWindow(n) {
|
|
111
|
-
if (n >= 1000000) return `${(n / 1000000).toFixed(1)}M`;
|
|
112
|
-
if (n >= 1000) return `${Math.round(n / 1000)}K`;
|
|
113
|
-
return n.toString();
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
function generateReadmeTable(models) {
|
|
117
|
-
const lines = [
|
|
118
|
-
"| Model | Base | Context | Vision | Reasoning | Max Output |",
|
|
119
|
-
"|-------|------|---------|--------|-----------|------------|",
|
|
120
|
-
];
|
|
121
|
-
|
|
122
|
-
for (const model of models) {
|
|
123
|
-
const name = model.name.replace(/^Umans\s+/, "");
|
|
124
|
-
const base = model._meta?.baseModel || "—";
|
|
125
|
-
const context = formatContextWindow(model.contextWindow);
|
|
126
|
-
const vision = model.input.includes("image") ? "✅" : "❌";
|
|
127
|
-
const reasoning = model.reasoning ? "✅" : "❌";
|
|
128
|
-
const maxTokens = formatContextWindow(model.maxTokens);
|
|
129
|
-
|
|
130
|
-
lines.push(`| ${name} | ${base} | ${context} | ${vision} | ${reasoning} | ${maxTokens} |`);
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
return lines.join("\n");
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
function updateReadme(models) {
|
|
137
|
-
let readme = fs.readFileSync(README_PATH, "utf8");
|
|
138
|
-
const newTable = generateReadmeTable(models);
|
|
139
|
-
|
|
140
|
-
const tableRegex =
|
|
141
|
-
/(## Available Models\n\n)\| Model \| Base \| Context \| Vision \| Reasoning \| Max Output \|\n\|[-| ]+\|(\n\|[^\n]+\|)*\n*/;
|
|
142
|
-
|
|
143
|
-
if (tableRegex.test(readme)) {
|
|
144
|
-
readme = readme.replace(tableRegex, (match, header) => `${header}${newTable}\n\n`);
|
|
145
|
-
fs.writeFileSync(README_PATH, readme);
|
|
146
|
-
console.log("✓ Updated README.md");
|
|
147
|
-
} else {
|
|
148
|
-
console.warn('⚠ Could not find model table in "## Available Models" section');
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
function cleanModelForJson(model) {
|
|
153
|
-
const { _meta, ...cleanModel } = model;
|
|
154
|
-
return cleanModel;
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
// Grace period for delisted models: update-models.js moves models the API no
|
|
158
|
-
// longer lists into deprecated-models.json (stamped with deprecatedAt) instead
|
|
159
|
-
// of dropping them; the runtime appends them back so sessions and saved model
|
|
160
|
-
// settings keep working, and after 14 days they are evicted permanently.
|
|
161
|
-
const DEPRECATED_MODEL_TTL_MS = 14 * 24 * 60 * 60 * 1000;
|
|
162
|
-
|
|
163
|
-
/**
|
|
164
|
-
* Reconcile deprecated-models.json against the freshly fetched model list.
|
|
165
|
-
* - in old models.json but not the API: moved into the deprecated file
|
|
166
|
-
* (deprecatedAt = now; preserved on repeat runs so the grace clock is not reset)
|
|
167
|
-
* - back in the API: resurrected (dropped from the deprecated file)
|
|
168
|
-
* - deprecatedAt older than 14 days: evicted permanently
|
|
169
|
-
* Must run BEFORE the new models.json is written; it reads the old file itself.
|
|
170
|
-
*/
|
|
171
|
-
function updateDeprecatedModels(modelsJsonPath, newModels) {
|
|
172
|
-
const deprecatedPath = path.join(path.dirname(modelsJsonPath), 'deprecated-models.json');
|
|
173
|
-
|
|
174
|
-
let oldModels = [];
|
|
175
|
-
try {
|
|
176
|
-
const parsed = JSON.parse(fs.readFileSync(modelsJsonPath, 'utf8'));
|
|
177
|
-
if (Array.isArray(parsed)) oldModels = parsed;
|
|
178
|
-
} catch { /* first run: no previous models.json */ }
|
|
179
|
-
|
|
180
|
-
let deprecated = {};
|
|
181
|
-
try {
|
|
182
|
-
const parsed = JSON.parse(fs.readFileSync(deprecatedPath, 'utf8'));
|
|
183
|
-
if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) deprecated = parsed;
|
|
184
|
-
} catch { /* no graveyard yet */ }
|
|
185
|
-
|
|
186
|
-
const currentIds = new Set(newModels.map(m => m.id));
|
|
187
|
-
const now = new Date().toISOString();
|
|
188
|
-
const added = [];
|
|
189
|
-
const resurrected = [];
|
|
190
|
-
const evicted = [];
|
|
191
|
-
|
|
192
|
-
for (const old of oldModels) {
|
|
193
|
-
if (old && old.id && !currentIds.has(old.id) && !deprecated[old.id]) {
|
|
194
|
-
deprecated[old.id] = { ...old, deprecatedAt: now };
|
|
195
|
-
added.push(old.id);
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
for (const [id, entry] of Object.entries(deprecated)) {
|
|
200
|
-
if (currentIds.has(id)) {
|
|
201
|
-
delete deprecated[id];
|
|
202
|
-
resurrected.push(id);
|
|
203
|
-
continue;
|
|
204
|
-
}
|
|
205
|
-
const removedAt = Date.parse(entry && entry.deprecatedAt ? entry.deprecatedAt : '');
|
|
206
|
-
if (Number.isNaN(removedAt) || Date.now() - removedAt > DEPRECATED_MODEL_TTL_MS) {
|
|
207
|
-
delete deprecated[id];
|
|
208
|
-
evicted.push(id);
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
if (added.length > 0 || resurrected.length > 0 || evicted.length > 0) {
|
|
213
|
-
fs.writeFileSync(deprecatedPath, JSON.stringify(deprecated, null, 2) + '\n');
|
|
214
|
-
console.log('Updated deprecated-models.json ' + JSON.stringify({ added, resurrected, evicted }));
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
async function main() {
|
|
219
|
-
console.log(`Fetching models from ${MODELS_INFO_API_URL}...`);
|
|
220
|
-
|
|
221
|
-
try {
|
|
222
|
-
const response = await fetch(MODELS_INFO_API_URL);
|
|
223
|
-
if (!response.ok) {
|
|
224
|
-
throw new Error(`HTTP error! status: ${response.status}`);
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
const apiData = await response.json();
|
|
228
|
-
|
|
229
|
-
if (!apiData || typeof apiData !== "object") {
|
|
230
|
-
throw new Error("API response is not a valid object");
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
// Load existing models.json — source of truth for curated specs
|
|
234
|
-
let existingModels = [];
|
|
235
|
-
try {
|
|
236
|
-
existingModels = JSON.parse(fs.readFileSync(MODELS_JSON_PATH, "utf8"));
|
|
237
|
-
} catch {}
|
|
238
|
-
const existingModelsMap = {};
|
|
239
|
-
for (const m of existingModels) {
|
|
240
|
-
existingModelsMap[m.id] = m;
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
// Transform models from API
|
|
244
|
-
let apiTransformed = Object.entries(apiData)
|
|
245
|
-
.map(([id, info]) => transformApiModel(id, info))
|
|
246
|
-
.filter((m) => m !== null);
|
|
247
|
-
|
|
248
|
-
// Enrich with base model info for README
|
|
249
|
-
apiTransformed = apiTransformed.map((model) => {
|
|
250
|
-
const info = apiData[model.id];
|
|
251
|
-
const baseModel = info?.base_model?.oss_base || info?.base_model?.name || "—";
|
|
252
|
-
return { ...model, _meta: { baseModel } };
|
|
253
|
-
});
|
|
254
|
-
|
|
255
|
-
// Sort models alphabetically by name
|
|
256
|
-
apiTransformed.sort((a, b) => a.name.localeCompare(b.name));
|
|
257
|
-
|
|
258
|
-
console.log(`✓ Fetched and transformed ${apiTransformed.length} models from API`);
|
|
259
|
-
|
|
260
|
-
// Preserve existing curated data (compat, reasoning flags, etc.)
|
|
261
|
-
apiTransformed = apiTransformed.map((model) => {
|
|
262
|
-
const existing = existingModelsMap[model.id];
|
|
263
|
-
if (existing) {
|
|
264
|
-
return {
|
|
265
|
-
...model,
|
|
266
|
-
// Preserve curated fields that the API doesn't provide
|
|
267
|
-
reasoning: typeof existing.reasoning === "boolean" ? existing.reasoning : model.reasoning,
|
|
268
|
-
input: model.input,
|
|
269
|
-
maxTokens: existing.maxTokens || model.maxTokens,
|
|
270
|
-
_meta: model._meta, // Keep API-derived _meta for README
|
|
271
|
-
};
|
|
272
|
-
}
|
|
273
|
-
return model;
|
|
274
|
-
});
|
|
275
|
-
|
|
276
|
-
// Update models.json — pure API data, no patches baked in
|
|
277
|
-
const cleanModels = apiTransformed.map(cleanModelForJson);
|
|
278
|
-
// Move delisted models to deprecated-models.json BEFORE models.json is overwritten
|
|
279
|
-
updateDeprecatedModels(MODELS_JSON_PATH, cleanModels);
|
|
280
|
-
fs.writeFileSync(MODELS_JSON_PATH, JSON.stringify(cleanModels, null, 2) + "\n");
|
|
281
|
-
console.log("✓ Updated models.json (pure API data)");
|
|
282
|
-
|
|
283
|
-
// Load patch.json and apply for README generation
|
|
284
|
-
let patch = {};
|
|
285
|
-
try {
|
|
286
|
-
patch = JSON.parse(fs.readFileSync(PATCH_JSON_PATH, "utf8"));
|
|
287
|
-
console.log(`✓ Loaded ${Object.keys(patch).length} patch overrides from patch.json`);
|
|
288
|
-
} catch {
|
|
289
|
-
console.warn("⚠ Could not load patch.json, README may show incorrect compat flags");
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
// Build full model list for README: base → patch → custom
|
|
293
|
-
const customModels = loadJson(CUSTOM_MODELS_JSON_PATH);
|
|
294
|
-
const readmeModels = buildModels(apiTransformed, Array.isArray(customModels) ? customModels : [], patch);
|
|
295
|
-
readmeModels.sort((a, b) => a.name.localeCompare(b.name));
|
|
296
|
-
console.log("✓ Built model list (base → patch → custom) for README");
|
|
297
|
-
|
|
298
|
-
// Update README.md with patched data
|
|
299
|
-
updateReadme(readmeModels);
|
|
300
|
-
|
|
301
|
-
// Summary
|
|
302
|
-
console.log("\n--- Summary ---");
|
|
303
|
-
console.log(`Total models: ${readmeModels.length}`);
|
|
304
|
-
console.log(`Reasoning models: ${readmeModels.filter((m) => m.reasoning).length}`);
|
|
305
|
-
console.log(`Vision models: ${readmeModels.filter((m) => m.input.includes("image")).length}`);
|
|
306
|
-
console.log(`Subscription-based: All models $0/M (included in plan)`);
|
|
307
|
-
|
|
308
|
-
const oldIds = new Set(existingModels.map((m) => m.id));
|
|
309
|
-
const newIds = new Set(apiTransformed.map((m) => m.id));
|
|
310
|
-
|
|
311
|
-
const added = [...newIds].filter((id) => !oldIds.has(id));
|
|
312
|
-
const removed = [...oldIds].filter((id) => !newIds.has(id));
|
|
313
|
-
|
|
314
|
-
if (added.length > 0) {
|
|
315
|
-
console.log(`\nNew models: ${added.join(", ")}`);
|
|
316
|
-
}
|
|
317
|
-
if (removed.length > 0) {
|
|
318
|
-
console.log(`\nRemoved models: ${removed.join(", ")}`);
|
|
319
|
-
}
|
|
320
|
-
} catch (error) {
|
|
321
|
-
console.error("Error:", error.message);
|
|
322
|
-
process.exit(1);
|
|
323
|
-
}
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
main();
|