@javierfrancoleal/deepseek-prices 0.0.0-stage → 1.0.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.
- package/README.md +117 -2
- package/cordis.patch.yml +5 -0
- package/icon.svg +6 -0
- package/lib/client.js +217 -0
- package/lib/index.js +181 -0
- package/locale/en.json +6 -0
- package/locale/es.json +6 -0
- package/locale/fr.json +6 -0
- package/locale/zh.json +6 -0
- package/package.json +53 -4
package/README.md
CHANGED
|
@@ -1,3 +1,118 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @javierfrancoleal/deepseek-prices
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Barre de prix **en direct** des jetons DeepSeek pour la GUI Web de DeepSeek Harness.
|
|
4
|
+
Live DeepSeek token **price bar** for the DeepSeek Harness Web GUI.
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Ce que fait le plugin / What it does
|
|
9
|
+
|
|
10
|
+
Affiche une barre pleine largeur en bas de la fenêtre (`shell.bottom`) qui montre, en
|
|
11
|
+
temps réel :
|
|
12
|
+
|
|
13
|
+
- **La tarifa actual** — 🟢 VALLE (hors pointe) ou 🔴 PICO (pointe).
|
|
14
|
+
- **Le prix des jetons** (USD / 1M tokens) pour `deepseek-flash` et `deepseek-v4-pro`
|
|
15
|
+
— entrée (cache miss) → sortie. Le détail complet (cache hit + cache miss + sortie,
|
|
16
|
+
pointe et hors pointe) apparaît au survol.
|
|
17
|
+
- **L'heure d'origine (UTC)** et **l'heure locale (Québec, `America/Toronto`)**.
|
|
18
|
+
- **Le compte à rebours** jusqu'au prochain changement de tarif, avec l'heure locale
|
|
19
|
+
du basculement.
|
|
20
|
+
- **La date de vérification** des prix (survol de la barre).
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## La grille tarifaire réelle de DeepSeek (vérifiée le 2026-11-03)
|
|
25
|
+
|
|
26
|
+
Source officielle : <https://api-docs.deepseek.com/quick_start/pricing>
|
|
27
|
+
|
|
28
|
+
Il y a **deux tarifs** : **pointe (peak)** et **hors pointe (off-peak)**. Hors pointe
|
|
29
|
+
= **50 %** du tarif pointe.
|
|
30
|
+
|
|
31
|
+
| Modèle | Entrée (cache hit) | Entrée (cache miss) | Sortie |
|
|
32
|
+
|---|---|---|---|
|
|
33
|
+
| `deepseek-flash` (V4.1) | pointe $0.006 · HP $0.003 | pointe $0.30 · HP $0.15 | pointe $1.20 · HP $0.60 |
|
|
34
|
+
| `deepseek-v4-pro` | pointe $0.044 · HP $0.022 | pointe $1.32 · HP $0.66 | pointe $3.96 · HP $1.98 |
|
|
35
|
+
|
|
36
|
+
**Heures de pointe** : `01:00–04:00` et `06:00–10:00` **UTC**, du lundi au vendredi,
|
|
37
|
+
**hors jours fériés chinois**. Tout le reste (nuits, weekends, jours fériés chinois)
|
|
38
|
+
est **hors pointe**.
|
|
39
|
+
|
|
40
|
+
> ⚠️ Deux fenêtres de pointe par jour (avec un creux hors pointe de 04:00 à 06:00 UTC).
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
## Installation
|
|
45
|
+
|
|
46
|
+
Le plugin est un paquet « double-face » : un half **host** (vérification
|
|
47
|
+
quotidienne) et un half **browser** (la barre).
|
|
48
|
+
|
|
49
|
+
Dans le profil (`~/.dsh/profiles/desktop/`), ajoutez la dépendance et le bundle :
|
|
50
|
+
|
|
51
|
+
```jsonc
|
|
52
|
+
// package.json
|
|
53
|
+
{
|
|
54
|
+
"dependencies": {
|
|
55
|
+
"@javierfrancoleal/deepseek-prices": "link:/…/plugins/jfranco-deepseek-prices"
|
|
56
|
+
},
|
|
57
|
+
"dsh": {
|
|
58
|
+
"profile": {
|
|
59
|
+
"bundles": [
|
|
60
|
+
"@deepseek-ai/dsh-base",
|
|
61
|
+
"@deepseek-ai/dsh-web-app",
|
|
62
|
+
"@javierfrancoleal/deepseek-prices"
|
|
63
|
+
]
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Puis :
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
cd ~/.dsh/profiles/desktop
|
|
73
|
+
pnpm install
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Rechargez la page Web. La barre apparaît en bas de la fenêtre. (Le `cordis.patch.yml`
|
|
77
|
+
du plugin insère déjà sa ligne `jfranco-deepseek-prices`.)
|
|
78
|
+
|
|
79
|
+
---
|
|
80
|
+
|
|
81
|
+
## Vérification quotidienne
|
|
82
|
+
|
|
83
|
+
Le half host (`lib/index.js`) télécharge **une fois par jour** la page officielle
|
|
84
|
+
des prix, la compare à la grille embarquée, et ajoute un enregistrement JSONL à
|
|
85
|
+
`~/.dsh/deepseek-prices/verify.jsonl`.
|
|
86
|
+
|
|
87
|
+
- `status: "ok"` → aucune dérive.
|
|
88
|
+
- `status: "drift"` → les heures de pointe ou des prix clés ont changé (warning dans
|
|
89
|
+
le journal du host).
|
|
90
|
+
- `status: "error"` → la page n'a pas pu être lue (warning dans le journal).
|
|
91
|
+
|
|
92
|
+
La barre affiche la date de vérification embarquée au survol.
|
|
93
|
+
|
|
94
|
+
---
|
|
95
|
+
|
|
96
|
+
## Configuration / limites
|
|
97
|
+
|
|
98
|
+
- **Fuseau local** : `America/Toronto` (Québec). Modifiable dans `lib/client.js`
|
|
99
|
+
(`fmtClock(..., "America/Toronto")`).
|
|
100
|
+
- **Jours fériés chinois** : liste 2026 approximative dans `PRICING.chineseHolidays2026`
|
|
101
|
+
(host et client). À réviser chaque année — le vérificateur quotidien contrôle les
|
|
102
|
+
prix et les heures de pointe, **pas** cette liste.
|
|
103
|
+
- **Prix embarqués** : si DeepSeek change ses prix, le vérificateur quotidien le
|
|
104
|
+
signale ; mettez à jour `PRICING` dans `lib/index.js` **et** `lib/client.js`.
|
|
105
|
+
|
|
106
|
+
---
|
|
107
|
+
|
|
108
|
+
## Structure
|
|
109
|
+
|
|
110
|
+
| Fichier | Rôle |
|
|
111
|
+
|---|---|
|
|
112
|
+
| `lib/index.js` | Half host : vérification quotidienne contre la page officielle |
|
|
113
|
+
| `lib/client.js` | Half browser : la barre de prix dans le slot global `shell.bottom` |
|
|
114
|
+
| `cordis.patch.yml` | Insère la ligne `jfranco-deepseek-prices` |
|
|
115
|
+
| `package.json` | Déclare `dsh.bundle.patch` (host) + `dsh.client` (browser) |
|
|
116
|
+
| `locale/*.json` | Métadonnées d'affichage du gestionnaire de plugins |
|
|
117
|
+
|
|
118
|
+
Créé par **J.Franco**.
|
package/cordis.patch.yml
ADDED
package/icon.svg
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="128" height="128" viewBox="0 0 128 128" fill="none">
|
|
2
|
+
<rect width="128" height="128" rx="24" fill="#4D6BFE"/>
|
|
3
|
+
<path d="M30 84 L48 58 L62 70 L82 40 L98 52" stroke="#ffffff" stroke-width="8" stroke-linecap="round" stroke-linejoin="round" fill="none"/>
|
|
4
|
+
<circle cx="98" cy="52" r="6" fill="#ffffff"/>
|
|
5
|
+
<path d="M30 96 H98" stroke="#ffffff" stroke-width="6" stroke-linecap="round" opacity="0.6"/>
|
|
6
|
+
</svg>
|
package/lib/client.js
ADDED
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
window.__ModuleLoader__.load({
|
|
2
|
+
id: "@javierfrancoleal/deepseek-prices",
|
|
3
|
+
factory: (require) => {
|
|
4
|
+
var module = { exports: {} };
|
|
5
|
+
var exports = module.exports;
|
|
6
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
7
|
+
const React = require("react");
|
|
8
|
+
|
|
9
|
+
// ── styles ────────────────────────────────────────────────────────────────
|
|
10
|
+
const CSS = [
|
|
11
|
+
".Jfdp_bar{box-sizing:border-box;width:100%;height:34px;display:flex;align-items:center;gap:14px;padding:0 16px;border-top:1px solid var(--dsw-alias-border-l1, rgba(127,127,127,.2));background:var(--dsw-specific-menu, rgba(0,0,0,.04));color:var(--dsw-alias-label-secondary, #666);font-size:12px;line-height:1;font-family:var(--dsh-font-family-body, inherit);overflow:hidden;white-space:nowrap}",
|
|
12
|
+
".Jfdp_bar[data-tariff='peak']{background:linear-gradient(90deg, rgba(229,72,77,.10), var(--dsw-specific-menu, rgba(0,0,0,.04))) }",
|
|
13
|
+
".Jfdp_bar[data-tariff='offpeak']{background:linear-gradient(90deg, rgba(48,164,108,.10), var(--dsw-specific-menu, rgba(0,0,0,.04))) }",
|
|
14
|
+
".Jfdp_dot{width:9px;height:9px;border-radius:50%;flex:none;background:#e5484d;box-shadow:0 0 0 3px rgba(229,72,77,.15)}",
|
|
15
|
+
".Jfdp_bar[data-tariff='offpeak'] .Jfdp_dot{background:#30a46c;box-shadow:0 0 0 3px rgba(48,164,108,.15)}",
|
|
16
|
+
".Jfdp_status{flex:none;font-weight:700;font-size:12px;letter-spacing:.06em;color:var(--dsw-alias-label-primary, #111)}",
|
|
17
|
+
".Jfdp_bar[data-tariff='peak'] .Jfdp_status{color:#e5484d}",
|
|
18
|
+
".Jfdp_bar[data-tariff='offpeak'] .Jfdp_status{color:#30a46c}",
|
|
19
|
+
".Jfdp_model{display:inline-flex;align-items:baseline;gap:6px;flex:none}",
|
|
20
|
+
".Jfdp_modelName{font-weight:600;color:var(--dsw-alias-label-primary, #111)}",
|
|
21
|
+
".Jfdp_price{font-variant-numeric:tabular-nums;color:var(--dsw-alias-label-secondary, #555)}",
|
|
22
|
+
".Jfdp_arrow{opacity:.55}",
|
|
23
|
+
".Jfdp_spacer{flex:1}",
|
|
24
|
+
".Jfdp_clock{flex:none;font-variant-numeric:tabular-nums;color:var(--dsw-alias-label-tertiary, #888)}",
|
|
25
|
+
".Jfdp_countdown{flex:none;font-weight:600;font-variant-numeric:tabular-nums;color:var(--dsw-alias-label-primary, #111)}",
|
|
26
|
+
".Jfdp_countdown b{color:var(--dsw-alias-label-secondary, #555);font-weight:600}",
|
|
27
|
+
".Jfdp_hideSmall{flex:none}",
|
|
28
|
+
"@media (max-width:900px){.Jfdp_bar{gap:8px;padding:0 10px}.Jfdp_hideSmall{display:none}}"
|
|
29
|
+
].join("\n");
|
|
30
|
+
const cssTagId = "@javierfrancoleal/deepseek-prices/PriceBar.css";
|
|
31
|
+
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(cssTagId) + "]") === null) {
|
|
32
|
+
const tag = document.createElement("style");
|
|
33
|
+
tag.dataset.plugin = "@javierfrancoleal/deepseek-prices";
|
|
34
|
+
tag.dataset.pluginCss = cssTagId;
|
|
35
|
+
tag.textContent = CSS;
|
|
36
|
+
document.head.appendChild(tag);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// ── canonical pricing snapshot (verified 2026-11-03) ─────────────────────
|
|
40
|
+
const PRICING = {
|
|
41
|
+
verifiedAt: "2026-11-03",
|
|
42
|
+
source: "https://api-docs.deepseek.com/quick_start/pricing",
|
|
43
|
+
peakWindowsUtc: [
|
|
44
|
+
{ start: "01:00", end: "04:00" },
|
|
45
|
+
{ start: "06:00", end: "10:00" }
|
|
46
|
+
],
|
|
47
|
+
chineseHolidays2026: [
|
|
48
|
+
"2026-01-01",
|
|
49
|
+
"2026-02-16", "2026-02-17", "2026-02-18", "2026-02-19", "2026-02-20", "2026-02-21", "2026-02-22",
|
|
50
|
+
"2026-04-04", "2026-04-05", "2026-04-06",
|
|
51
|
+
"2026-05-01", "2026-05-02", "2026-05-03", "2026-05-04", "2026-05-05",
|
|
52
|
+
"2026-06-19", "2026-06-20", "2026-06-21",
|
|
53
|
+
"2026-09-25", "2026-09-26", "2026-09-27",
|
|
54
|
+
"2026-10-01", "2026-10-02", "2026-10-03", "2026-10-04", "2026-10-05", "2026-10-06", "2026-10-07"
|
|
55
|
+
],
|
|
56
|
+
models: {
|
|
57
|
+
"deepseek-flash": {
|
|
58
|
+
label: "Flash",
|
|
59
|
+
inputCacheHit: { peak: 0.006, offPeak: 0.003 },
|
|
60
|
+
inputCacheMiss: { peak: 0.3, offPeak: 0.15 },
|
|
61
|
+
output: { peak: 1.2, offPeak: 0.6 }
|
|
62
|
+
},
|
|
63
|
+
"deepseek-v4-pro": {
|
|
64
|
+
label: "Pro",
|
|
65
|
+
inputCacheHit: { peak: 0.044, offPeak: 0.022 },
|
|
66
|
+
inputCacheMiss: { peak: 1.32, offPeak: 0.66 },
|
|
67
|
+
output: { peak: 3.96, offPeak: 1.98 }
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
const WINDOWS = PRICING.peakWindowsUtc.map(function (w) {
|
|
73
|
+
const a = w.start.split(":").map(Number);
|
|
74
|
+
const b = w.end.split(":").map(Number);
|
|
75
|
+
return [a[0] * 60 + a[1], b[0] * 60 + b[1]];
|
|
76
|
+
});
|
|
77
|
+
const HOLIDAYS = new Set(PRICING.chineseHolidays2026 || []);
|
|
78
|
+
|
|
79
|
+
function isoDate(date) {
|
|
80
|
+
const y = date.getUTCFullYear();
|
|
81
|
+
const m = String(date.getUTCMonth() + 1).padStart(2, "0");
|
|
82
|
+
const d = String(date.getUTCDate()).padStart(2, "0");
|
|
83
|
+
return y + "-" + m + "-" + d;
|
|
84
|
+
}
|
|
85
|
+
function isPeak(date) {
|
|
86
|
+
const day = date.getUTCDay();
|
|
87
|
+
if (day === 0 || day === 6) return false;
|
|
88
|
+
if (HOLIDAYS.has(isoDate(date))) return false;
|
|
89
|
+
const mins = date.getUTCHours() * 60 + date.getUTCMinutes();
|
|
90
|
+
for (let i = 0; i < WINDOWS.length; i++) {
|
|
91
|
+
if (mins >= WINDOWS[i][0] && mins < WINDOWS[i][1]) return true;
|
|
92
|
+
}
|
|
93
|
+
return false;
|
|
94
|
+
}
|
|
95
|
+
function nextTransition(date) {
|
|
96
|
+
const cur = isPeak(date);
|
|
97
|
+
let t = new Date(date.getTime());
|
|
98
|
+
for (let i = 0; i < 60 * 24 * 10; i++) {
|
|
99
|
+
t = new Date(t.getTime() + 60000);
|
|
100
|
+
if (isPeak(t) !== cur) return t;
|
|
101
|
+
}
|
|
102
|
+
return null;
|
|
103
|
+
}
|
|
104
|
+
function fmtClock(date, tz) {
|
|
105
|
+
try {
|
|
106
|
+
return new Intl.DateTimeFormat("en-CA", { timeZone: tz, hour: "2-digit", minute: "2-digit", hour12: false }).format(date);
|
|
107
|
+
} catch (e) {
|
|
108
|
+
return "--:--";
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
function fmtDuration(ms) {
|
|
112
|
+
let s = Math.max(0, Math.floor(ms / 1000));
|
|
113
|
+
const h = Math.floor(s / 3600);
|
|
114
|
+
const m = Math.floor((s % 3600) / 60);
|
|
115
|
+
const sec = s % 60;
|
|
116
|
+
if (h > 0) return h + "h " + m + "m " + sec + "s";
|
|
117
|
+
if (m > 0) return m + "m " + sec + "s";
|
|
118
|
+
return sec + "s";
|
|
119
|
+
}
|
|
120
|
+
function fmtPrice(n) {
|
|
121
|
+
if (n < 0.01) return "$" + n.toFixed(3);
|
|
122
|
+
return "$" + n.toFixed(2);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const LANGS = {
|
|
126
|
+
en: { peak: "PEAK", offPeak: "OFF-PEAK", to: "to", utc: "UTC", quebec: "Quebec", changes: "changes to", in: "in", next: "next" },
|
|
127
|
+
fr: { peak: "POINTE", offPeak: "HORS POINTE", to: "à", utc: "UTC", quebec: "Québec", changes: "passe à", in: "dans", next: "prochaine" },
|
|
128
|
+
es: { peak: "PICO", offPeak: "VALLE", to: "a", utc: "UTC", quebec: "Québec", changes: "cambia a", in: "en", next: "próximo" }
|
|
129
|
+
};
|
|
130
|
+
function pickLang() {
|
|
131
|
+
try {
|
|
132
|
+
const l = (navigator.language || "").toLowerCase();
|
|
133
|
+
if (l.indexOf("fr") === 0) return "fr";
|
|
134
|
+
if (l.indexOf("en") === 0) return "en";
|
|
135
|
+
} catch (e) { /* ignore */ }
|
|
136
|
+
return "es";
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function PriceBar() {
|
|
140
|
+
const [now, setNow] = React.useState(function () { return new Date(); });
|
|
141
|
+
React.useEffect(function () {
|
|
142
|
+
const id = setInterval(function () { setNow(new Date()); }, 1000);
|
|
143
|
+
return function () { clearInterval(id); };
|
|
144
|
+
}, []);
|
|
145
|
+
|
|
146
|
+
const peak = isPeak(now);
|
|
147
|
+
const next = nextTransition(now);
|
|
148
|
+
const lang = pickLang();
|
|
149
|
+
const t = LANGS[lang] || LANGS.es;
|
|
150
|
+
const tariff = peak ? "peak" : "offpeak";
|
|
151
|
+
const statusLabel = peak ? t.peak : t.offPeak;
|
|
152
|
+
const nextLabel = next ? (isPeak(next) ? t.peak : t.offPeak) : "";
|
|
153
|
+
const remaining = next ? fmtDuration(next.getTime() - now.getTime()) : "—";
|
|
154
|
+
|
|
155
|
+
const modelRows = Object.keys(PRICING.models).map(function (key) {
|
|
156
|
+
const m = PRICING.models[key];
|
|
157
|
+
const inp = m.inputCacheMiss[peak ? "peak" : "offPeak"];
|
|
158
|
+
const out = m.output[peak ? "peak" : "offPeak"];
|
|
159
|
+
return React.createElement("span", { className: "Jfdp_model", key: key },
|
|
160
|
+
React.createElement("span", { className: "Jfdp_modelName" }, m.label),
|
|
161
|
+
React.createElement("span", { className: "Jfdp_price" },
|
|
162
|
+
fmtPrice(inp),
|
|
163
|
+
React.createElement("span", { className: "Jfdp_arrow" }, " → "),
|
|
164
|
+
fmtPrice(out)
|
|
165
|
+
)
|
|
166
|
+
);
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
// Full-detail tooltip (cache hit + cache miss + output, peak & off-peak).
|
|
170
|
+
const detail = Object.keys(PRICING.models).map(function (key) {
|
|
171
|
+
const m = PRICING.models[key];
|
|
172
|
+
return m.label + " — " + t.peak + ": in " + fmtPrice(m.inputCacheMiss.peak)
|
|
173
|
+
+ " (hit " + fmtPrice(m.inputCacheHit.peak) + ") / out " + fmtPrice(m.output.peak)
|
|
174
|
+
+ " · " + t.offPeak + ": in " + fmtPrice(m.inputCacheMiss.offPeak)
|
|
175
|
+
+ " (hit " + fmtPrice(m.inputCacheHit.offPeak) + ") / out " + fmtPrice(m.output.offPeak);
|
|
176
|
+
}).join("\n");
|
|
177
|
+
const title = "DeepSeek token prices (USD / 1M)\n" + detail
|
|
178
|
+
+ "\n\n" + t.peak + ": 01:00–04:00 & 06:00–10:00 UTC (Mon–Fri, excl. CN holidays)"
|
|
179
|
+
+ "\n" + t.offPeak + ": all other hours (50% off)"
|
|
180
|
+
+ "\nSource: " + PRICING.source + "\nVerified: " + PRICING.verifiedAt;
|
|
181
|
+
|
|
182
|
+
return React.createElement("div", {
|
|
183
|
+
className: "Jfdp_bar",
|
|
184
|
+
"data-tariff": tariff,
|
|
185
|
+
"data-deepseek-prices": "true",
|
|
186
|
+
title: title
|
|
187
|
+
},
|
|
188
|
+
React.createElement("span", { className: "Jfdp_dot" }),
|
|
189
|
+
React.createElement("span", { className: "Jfdp_status" }, statusLabel),
|
|
190
|
+
React.createElement("span", { className: "Jfdp_hideSmall" }, "·"),
|
|
191
|
+
modelRows,
|
|
192
|
+
React.createElement("span", { className: "Jfdp_spacer" }),
|
|
193
|
+
React.createElement("span", { className: "Jfdp_clock" }, t.utc + " " + fmtClock(now, "UTC")),
|
|
194
|
+
React.createElement("span", { className: "Jfdp_clock Jfdp_hideSmall" }, t.quebec + " " + fmtClock(now, "America/Toronto")),
|
|
195
|
+
React.createElement("span", { className: "Jfdp_countdown" },
|
|
196
|
+
next ? (t.changes + " " + nextLabel + " " + t.in + " " + remaining + " · " + fmtClock(next, "America/Toronto") + " " + t.quebec) : ""
|
|
197
|
+
)
|
|
198
|
+
);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
const inject = ["slots"];
|
|
202
|
+
function apply(ctx) {
|
|
203
|
+
ctx.slots.inject("shell.bottom", function () {
|
|
204
|
+
return ctx.slots.register({
|
|
205
|
+
name: "shell.bottom",
|
|
206
|
+
id: "jfranco-deepseek-prices",
|
|
207
|
+
order: 0
|
|
208
|
+
}, PriceBar);
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
exports.PriceBar = PriceBar;
|
|
213
|
+
exports.apply = apply;
|
|
214
|
+
exports.inject = inject;
|
|
215
|
+
return module.exports;
|
|
216
|
+
}
|
|
217
|
+
});
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
// @javierfrancoleal/deepseek-prices — host half.
|
|
2
|
+
//
|
|
3
|
+
// Schedules a daily self-verification against the official DeepSeek pricing
|
|
4
|
+
// page and appends one JSONL record per run to
|
|
5
|
+
// `~/.dsh/deepseek-prices/verify.jsonl`. A drift (peak-hour windows or key
|
|
6
|
+
// token prices differing from the canonical snapshot below) is logged as a
|
|
7
|
+
// warning through the host logger.
|
|
8
|
+
//
|
|
9
|
+
// The browser half (lib/client.js) renders the live price bar; it ships its
|
|
10
|
+
// own copy of PRICING so the two halves stay independently loadable.
|
|
11
|
+
import { appendFile, mkdir } from "node:fs/promises";
|
|
12
|
+
import { homedir } from "node:os";
|
|
13
|
+
import { join } from "node:path";
|
|
14
|
+
|
|
15
|
+
const name = "jfranco-deepseek-prices";
|
|
16
|
+
|
|
17
|
+
// Canonical pricing snapshot.
|
|
18
|
+
// Verified 2026-11-03 against https://api-docs.deepseek.com/quick_start/pricing
|
|
19
|
+
// (prices are USD per 1M tokens; off-peak is half of peak).
|
|
20
|
+
export const PRICING = {
|
|
21
|
+
verifiedAt: "2026-11-03",
|
|
22
|
+
source: "https://api-docs.deepseek.com/quick_start/pricing",
|
|
23
|
+
// Peak hours are 01:00-04:00 and 06:00-10:00 UTC, Monday-Friday, excluding
|
|
24
|
+
// Chinese public holidays. Everything else is off-peak (50% of peak).
|
|
25
|
+
peakWindowsUtc: [
|
|
26
|
+
{ start: "01:00", end: "04:00" },
|
|
27
|
+
{ start: "06:00", end: "10:00" },
|
|
28
|
+
],
|
|
29
|
+
peakDays: "Mon-Fri",
|
|
30
|
+
peakNote: "excluding Chinese public holidays",
|
|
31
|
+
// 2026 Chinese statutory holidays (approximate — review annually; the daily
|
|
32
|
+
// verifier checks prices and peak windows, not this list).
|
|
33
|
+
chineseHolidays2026: [
|
|
34
|
+
"2026-01-01",
|
|
35
|
+
"2026-02-16", "2026-02-17", "2026-02-18", "2026-02-19", "2026-02-20", "2026-02-21", "2026-02-22",
|
|
36
|
+
"2026-04-04", "2026-04-05", "2026-04-06",
|
|
37
|
+
"2026-05-01", "2026-05-02", "2026-05-03", "2026-05-04", "2026-05-05",
|
|
38
|
+
"2026-06-19", "2026-06-20", "2026-06-21",
|
|
39
|
+
"2026-09-25", "2026-09-26", "2026-09-27",
|
|
40
|
+
"2026-10-01", "2026-10-02", "2026-10-03", "2026-10-04", "2026-10-05", "2026-10-06", "2026-10-07",
|
|
41
|
+
],
|
|
42
|
+
models: {
|
|
43
|
+
"deepseek-flash": {
|
|
44
|
+
inputCacheHit: { peak: 0.006, offPeak: 0.003 },
|
|
45
|
+
inputCacheMiss: { peak: 0.3, offPeak: 0.15 },
|
|
46
|
+
output: { peak: 1.2, offPeak: 0.6 },
|
|
47
|
+
},
|
|
48
|
+
"deepseek-v4-pro": {
|
|
49
|
+
inputCacheHit: { peak: 0.044, offPeak: 0.022 },
|
|
50
|
+
inputCacheMiss: { peak: 1.32, offPeak: 0.66 },
|
|
51
|
+
output: { peak: 3.96, offPeak: 1.98 },
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
const VERIFY_LOG_DIR = join(homedir(), ".dsh", "deepseek-prices");
|
|
57
|
+
|
|
58
|
+
/** Strip HTML tags so a Docusaurus page can be scanned as plain text. */
|
|
59
|
+
function stripHtml(html) {
|
|
60
|
+
return html
|
|
61
|
+
.replace(/<script[\s\S]*?<\/script>/gi, " ")
|
|
62
|
+
.replace(/<style[\s\S]*?<\/style>/gi, " ")
|
|
63
|
+
.replace(/<[^>]+>/g, " ")
|
|
64
|
+
.replace(/ /gi, " ")
|
|
65
|
+
.replace(/&/gi, "&")
|
|
66
|
+
.replace(/\s+/g, " ")
|
|
67
|
+
.trim();
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** Extract the expected peak/off-peak signals from the fetched pricing text. */
|
|
71
|
+
function scanPage(text) {
|
|
72
|
+
const findings = {};
|
|
73
|
+
// Peak-hours sentence, e.g. "Peak hours are 01:00 - 04:00 and 06:00 - 10:00 UTC, ..."
|
|
74
|
+
const peakMatch = /Peak hours are\s+([^.]+)/i.exec(text);
|
|
75
|
+
findings.peakHours = peakMatch ? peakMatch[1].trim() : null;
|
|
76
|
+
findings.halfPrice = /Off-peak rates are half of the peak rates/i.test(text);
|
|
77
|
+
|
|
78
|
+
// Key prices we expect to see anywhere on the page (USD per 1M tokens).
|
|
79
|
+
const expectedPrices = [
|
|
80
|
+
"0.003", "0.006", "0.15", "0.3", "0.6", "1.2",
|
|
81
|
+
"0.022", "0.044", "0.66", "1.32", "1.98", "3.96",
|
|
82
|
+
];
|
|
83
|
+
findings.missingPrices = expectedPrices.filter((p) => !text.includes(p));
|
|
84
|
+
|
|
85
|
+
// Model names present.
|
|
86
|
+
findings.models = {
|
|
87
|
+
flash: text.includes("deepseek-flash"),
|
|
88
|
+
pro: text.includes("deepseek-v4-pro"),
|
|
89
|
+
};
|
|
90
|
+
return findings;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Fetch the official pricing page and compare it against PRICING.
|
|
95
|
+
* Returns a plain record; never throws.
|
|
96
|
+
*/
|
|
97
|
+
export async function verify(_ctx) {
|
|
98
|
+
const result = {
|
|
99
|
+
checkedAt: new Date().toISOString(),
|
|
100
|
+
source: PRICING.source,
|
|
101
|
+
status: "ok",
|
|
102
|
+
drift: null,
|
|
103
|
+
findings: null,
|
|
104
|
+
error: null,
|
|
105
|
+
};
|
|
106
|
+
try {
|
|
107
|
+
const controller = new AbortController();
|
|
108
|
+
const timeout = setTimeout(() => controller.abort(), 20000);
|
|
109
|
+
let res;
|
|
110
|
+
try {
|
|
111
|
+
res = await fetch(PRICING.source, {
|
|
112
|
+
signal: controller.signal,
|
|
113
|
+
headers: { "user-agent": "deepseek-prices-verifier/1.0" },
|
|
114
|
+
});
|
|
115
|
+
} finally {
|
|
116
|
+
clearTimeout(timeout);
|
|
117
|
+
}
|
|
118
|
+
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
119
|
+
const text = await res.text();
|
|
120
|
+
const findings = scanPage(stripHtml(text));
|
|
121
|
+
result.findings = findings;
|
|
122
|
+
|
|
123
|
+
const drift = [];
|
|
124
|
+
const expectedHours = "01:00 - 04:00 and 06:00 - 10:00 UTC";
|
|
125
|
+
if (findings.peakHours && !findings.peakHours.toLowerCase().includes("01:00")
|
|
126
|
+
&& !findings.peakHours.includes("06:00")) {
|
|
127
|
+
drift.push(`peak-hours changed: ${findings.peakHours} (expected ${expectedHours})`);
|
|
128
|
+
}
|
|
129
|
+
if (findings.missingPrices.length > 0) {
|
|
130
|
+
drift.push(`prices not found on page: ${findings.missingPrices.join(", ")}`);
|
|
131
|
+
}
|
|
132
|
+
if (!findings.halfPrice) {
|
|
133
|
+
drift.push("off-peak-is-half rule not found on page");
|
|
134
|
+
}
|
|
135
|
+
if (drift.length > 0) {
|
|
136
|
+
result.status = "drift";
|
|
137
|
+
result.drift = drift;
|
|
138
|
+
}
|
|
139
|
+
} catch (error) {
|
|
140
|
+
result.status = "error";
|
|
141
|
+
result.error = error instanceof Error ? error.message : String(error);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Append one JSONL record (best effort; a full disk must not crash the host).
|
|
145
|
+
try {
|
|
146
|
+
await mkdir(VERIFY_LOG_DIR, { recursive: true });
|
|
147
|
+
await appendFile(join(VERIFY_LOG_DIR, "verify.jsonl"), JSON.stringify(result) + "\n", "utf8");
|
|
148
|
+
} catch (_error) {
|
|
149
|
+
/* ignore */
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if (_ctx?.logger) {
|
|
153
|
+
if (result.status === "error") {
|
|
154
|
+
_ctx.logger.warn(`[deepseek-prices] verification failed: ${result.error}`);
|
|
155
|
+
} else if (result.status === "drift") {
|
|
156
|
+
_ctx.logger.warn(`[deepseek-prices] PRICE DRIFT: ${result.drift.join(" | ")}`);
|
|
157
|
+
} else {
|
|
158
|
+
_ctx.logger.info(`[deepseek-prices] verified against official page, no drift (${result.checkedAt})`);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
return result;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function apply(ctx) {
|
|
165
|
+
ctx.effect(() => {
|
|
166
|
+
let cancelled = false;
|
|
167
|
+
let timer = null;
|
|
168
|
+
const run = async () => {
|
|
169
|
+
await verify(ctx);
|
|
170
|
+
if (!cancelled) timer = setTimeout(run, 24 * 60 * 60 * 1000);
|
|
171
|
+
};
|
|
172
|
+
// First check shortly after the host settles, then once a day.
|
|
173
|
+
timer = setTimeout(run, 30 * 1000);
|
|
174
|
+
return () => {
|
|
175
|
+
cancelled = true;
|
|
176
|
+
if (timer) clearTimeout(timer);
|
|
177
|
+
};
|
|
178
|
+
}, "deepseek-prices: daily verification");
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
export { apply, name };
|
package/locale/en.json
ADDED
package/locale/es.json
ADDED
package/locale/fr.json
ADDED
package/locale/zh.json
ADDED
package/package.json
CHANGED
|
@@ -1,6 +1,55 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@javierfrancoleal/deepseek-prices",
|
|
3
|
-
"version": "
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "1.0.2",
|
|
4
|
+
"description": "Live DeepSeek API token price bar for the web GUI: peak/off-peak tariff, UTC + local (Quebec) clocks, countdown to the next tariff change, and a daily self-verification against the official DeepSeek pricing page.",
|
|
5
|
+
"author": "J.Franco",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"main": "lib/index.js",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": "./lib/index.js",
|
|
11
|
+
"./client": "./lib/client.js",
|
|
12
|
+
"./cordis.patch.yml": "./cordis.patch.yml",
|
|
13
|
+
"./package.json": "./package.json",
|
|
14
|
+
"./locale/*.json": "./locale/*.json"
|
|
15
|
+
},
|
|
16
|
+
"icon": "./icon.svg",
|
|
17
|
+
"files": [
|
|
18
|
+
"icon.svg",
|
|
19
|
+
"locale/*.json",
|
|
20
|
+
"lib/index.js",
|
|
21
|
+
"lib/client.js",
|
|
22
|
+
"cordis.patch.yml",
|
|
23
|
+
"README.md"
|
|
24
|
+
],
|
|
25
|
+
"engines": {
|
|
26
|
+
"node": ">=24",
|
|
27
|
+
"dsh": ">=0.2.1-rc.0"
|
|
28
|
+
},
|
|
29
|
+
"keywords": [
|
|
30
|
+
"deepseek-harness",
|
|
31
|
+
"pricing",
|
|
32
|
+
"tokens",
|
|
33
|
+
"peak",
|
|
34
|
+
"off-peak",
|
|
35
|
+
"tariff"
|
|
36
|
+
],
|
|
37
|
+
"dsh": {
|
|
38
|
+
"manifestVersion": 1,
|
|
39
|
+
"bundle": {
|
|
40
|
+
"patch": "./cordis.patch.yml"
|
|
41
|
+
},
|
|
42
|
+
"client": {
|
|
43
|
+
"platform": "web",
|
|
44
|
+
"inject": [
|
|
45
|
+
"@deepseek-ai/dsh-client-ui-layout"
|
|
46
|
+
]
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
"publishConfig": {
|
|
50
|
+
"access": "public"
|
|
51
|
+
},
|
|
52
|
+
"peerDependencies": {
|
|
53
|
+
"@deepseek-ai/cordis": "~4.0.5"
|
|
54
|
+
}
|
|
55
|
+
}
|