oc-deepseek-pricing 0.1.0 → 0.1.1
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 +69 -67
- package/package.json +4 -8
- package/src/pricing.test.ts +0 -121
package/README.md
CHANGED
|
@@ -2,13 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
An OpenCode v2 TUI plugin that displays the current DeepSeek API pricing window in the prompt footer and session sidebar.
|
|
4
4
|
|
|
5
|
-
The indicator is
|
|
5
|
+
The indicator is shown when the selected model ID contains `deepseek`. DeepSeek off-peak rates are half of peak rates.
|
|
6
6
|
|
|
7
7
|
## Features
|
|
8
8
|
|
|
9
9
|
- Displays the current pricing window in the prompt footer.
|
|
10
|
-
- Displays pricing
|
|
11
|
-
-
|
|
10
|
+
- Displays a compact pricing status in the session sidebar.
|
|
11
|
+
- Exposes complete pricing details through `/deepseek-pricing`.
|
|
12
|
+
- Distinguishes peak and off-peak periods with whale emoji indicators.
|
|
12
13
|
- Shows the next pricing transition and a countdown.
|
|
13
14
|
- Treats weekends as fully off-peak.
|
|
14
15
|
- Supports Chinese public-holiday overrides.
|
|
@@ -19,23 +20,44 @@ The indicator is intended for the direct DeepSeek provider. DeepSeek off-peak ra
|
|
|
19
20
|
|
|
20
21
|
Peak windows are evaluated in UTC:
|
|
21
22
|
|
|
22
|
-
-
|
|
23
|
-
-
|
|
23
|
+
- 01:00-04:00 UTC, Monday through Friday;
|
|
24
|
+
- 06:00-10:00 UTC, Monday through Friday.
|
|
24
25
|
|
|
25
26
|
All other times are off-peak, including:
|
|
26
27
|
|
|
27
|
-
-
|
|
28
|
-
- all hours after
|
|
28
|
+
- 04:00-06:00 UTC on weekdays;
|
|
29
|
+
- all hours after 10:00 UTC and before 01:00 UTC;
|
|
29
30
|
- Saturdays and Sundays in full;
|
|
30
31
|
- Chinese public holidays in full.
|
|
31
32
|
|
|
32
|
-
The off-peak price multiplier is
|
|
33
|
+
The off-peak price multiplier is 0.5.
|
|
34
|
+
|
|
35
|
+
## Installation
|
|
36
|
+
|
|
37
|
+
Install the published plugin directly with the OpenCode CLI:
|
|
38
|
+
|
|
39
|
+
```sh
|
|
40
|
+
opencode plugin add oc-deepseek-pricing
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
To install this exact release:
|
|
44
|
+
|
|
45
|
+
```sh
|
|
46
|
+
opencode plugin add oc-deepseek-pricing@0.1.0
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
OpenCode registers the plugin automatically. No manual change to `cli.json`, `opencode.json`, or `tui.jsonc` is required.
|
|
50
|
+
|
|
51
|
+
Verify the installation with:
|
|
52
|
+
|
|
53
|
+
```sh
|
|
54
|
+
opencode plugin list
|
|
55
|
+
```
|
|
33
56
|
|
|
34
57
|
## Requirements
|
|
35
58
|
|
|
36
|
-
- OpenCode v2 with TUI plugin support
|
|
37
|
-
-
|
|
38
|
-
- A terminal supported by OpenCode.
|
|
59
|
+
- OpenCode v2 with TUI plugin support;
|
|
60
|
+
- a terminal supported by OpenCode.
|
|
39
61
|
|
|
40
62
|
Development dependencies include:
|
|
41
63
|
|
|
@@ -50,6 +72,7 @@ Development dependencies include:
|
|
|
50
72
|
```text
|
|
51
73
|
oc-deepseek-pricing/
|
|
52
74
|
├── CALENDAR_UPDATE.md
|
|
75
|
+
├── LICENSE
|
|
53
76
|
├── README.md
|
|
54
77
|
├── bun.lock
|
|
55
78
|
├── package.json
|
|
@@ -58,7 +81,8 @@ oc-deepseek-pricing/
|
|
|
58
81
|
│ ├── pricing.test.ts
|
|
59
82
|
│ ├── pricing.ts
|
|
60
83
|
│ └── tui.tsx
|
|
61
|
-
|
|
84
|
+
├── tsconfig.json
|
|
85
|
+
└── tui.ts
|
|
62
86
|
```
|
|
63
87
|
|
|
64
88
|
## Development setup
|
|
@@ -69,10 +93,10 @@ Install dependencies:
|
|
|
69
93
|
bun install
|
|
70
94
|
```
|
|
71
95
|
|
|
72
|
-
Run
|
|
96
|
+
Run all validation checks:
|
|
73
97
|
|
|
74
98
|
```sh
|
|
75
|
-
bun check
|
|
99
|
+
bun run check
|
|
76
100
|
```
|
|
77
101
|
|
|
78
102
|
Run the test suite:
|
|
@@ -81,86 +105,60 @@ Run the test suite:
|
|
|
81
105
|
bun test
|
|
82
106
|
```
|
|
83
107
|
|
|
84
|
-
A successful validation
|
|
108
|
+
A successful validation completes without formatting, lint, TypeScript, or test errors.
|
|
85
109
|
|
|
86
|
-
## OpenCode
|
|
110
|
+
## Restarting OpenCode
|
|
87
111
|
|
|
88
|
-
|
|
112
|
+
After changing the plugin source or configuration, close the OpenCode TUI completely and start it again.
|
|
89
113
|
|
|
90
|
-
|
|
91
|
-
~/.config/opencode/cli.json
|
|
92
|
-
```
|
|
114
|
+
Opening a new conversation is not necessarily sufficient because plugin entrypoints are loaded during TUI startup.
|
|
93
115
|
|
|
94
|
-
|
|
116
|
+
## Expected interface
|
|
95
117
|
|
|
96
|
-
|
|
118
|
+
When a DeepSeek model is selected during an off-peak window, the prompt and home footers display:
|
|
97
119
|
|
|
98
|
-
```
|
|
99
|
-
|
|
100
|
-
"diffs": {
|
|
101
|
-
"wrap": "word",
|
|
102
|
-
"fullscreen": true
|
|
103
|
-
},
|
|
104
|
-
"session": {
|
|
105
|
-
"sidebar": "auto",
|
|
106
|
-
"scrollbar": false,
|
|
107
|
-
"thinking": "hide"
|
|
108
|
-
},
|
|
109
|
-
"animations": true,
|
|
110
|
-
"plugins": ["./herdr-opencode", "./plugins/oc-deepseek-pricing"]
|
|
111
|
-
}
|
|
120
|
+
```text
|
|
121
|
+
🐋💤 off-peak · 9h 13m left
|
|
112
122
|
```
|
|
113
123
|
|
|
114
|
-
|
|
124
|
+
During a peak window, they display:
|
|
115
125
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
```json
|
|
119
|
-
{
|
|
120
|
-
"exports": {
|
|
121
|
-
"./tui": "./src/tui.tsx"
|
|
122
|
-
}
|
|
123
|
-
}
|
|
126
|
+
```text
|
|
127
|
+
🐳 peak · 1h 15m left
|
|
124
128
|
```
|
|
125
129
|
|
|
126
|
-
The
|
|
127
|
-
|
|
128
|
-
## Restarting OpenCode
|
|
129
|
-
|
|
130
|
-
After changing the plugin source or configuration, close the OpenCode TUI completely and start it again.
|
|
131
|
-
|
|
132
|
-
Opening a new conversation is not necessarily sufficient because plugin entrypoints are loaded during TUI startup.
|
|
133
|
-
|
|
134
|
-
## Expected interface
|
|
135
|
-
|
|
136
|
-
When loaded, the prompt footer displays a compact indicator similar to:
|
|
130
|
+
The session sidebar displays only the current status and remaining time:
|
|
137
131
|
|
|
138
132
|
```text
|
|
139
|
-
|
|
133
|
+
Status 🐋💤 off-peak
|
|
134
|
+
9h 13m left
|
|
140
135
|
```
|
|
141
136
|
|
|
142
|
-
During a peak window
|
|
137
|
+
During a peak window, the sidebar displays:
|
|
143
138
|
|
|
144
139
|
```text
|
|
145
|
-
|
|
140
|
+
Status 🐳 peak
|
|
141
|
+
1h 15m left
|
|
146
142
|
```
|
|
147
143
|
|
|
148
|
-
|
|
144
|
+
Run `/deepseek-pricing` to display the complete pricing details:
|
|
149
145
|
|
|
150
146
|
```text
|
|
151
147
|
DeepSeek pricing
|
|
152
|
-
Status
|
|
148
|
+
Status 🐋💤 off-peak
|
|
153
149
|
Reason outside peak windows
|
|
154
|
-
Next
|
|
150
|
+
Next peak in 9h 13m
|
|
155
151
|
At 10/28/2026, 2:00:00 AM
|
|
156
152
|
China 2026-10-27
|
|
157
153
|
```
|
|
158
154
|
|
|
159
|
-
The
|
|
155
|
+
The `Next` line intentionally uses the state name without an emoji. If the Chinese public-holiday calendar does not cover the current year, the command also displays a warning.
|
|
156
|
+
|
|
157
|
+
The exact local transition time depends on the operating-system timezone and daylight-saving rules. Pricing classification is based on UTC, while holiday lookup uses the Asia/Shanghai calendar date.
|
|
160
158
|
|
|
161
159
|
## Chinese public holidays
|
|
162
160
|
|
|
163
|
-
|
|
161
|
+
Holiday data is stored in:
|
|
164
162
|
|
|
165
163
|
```text
|
|
166
164
|
src/holidays.ts
|
|
@@ -168,7 +166,7 @@ src/holidays.ts
|
|
|
168
166
|
|
|
169
167
|
Only officially verified calendar years should be added to `COVERED_HOLIDAY_YEARS`.
|
|
170
168
|
|
|
171
|
-
For the
|
|
169
|
+
For the complete annual update procedure, source requirements, timezone rules, validation checklist, and required tests, see [CALENDAR_UPDATE.md](./CALENDAR_UPDATE.md).
|
|
172
170
|
|
|
173
171
|
If the current Chinese calendar year is not covered, the plugin continues to apply the normal UTC weekday and weekend schedule but displays a coverage warning. Do not suppress the warning by adding a year without adding and reviewing the complete official calendar.
|
|
174
172
|
|
|
@@ -200,11 +198,15 @@ bun test
|
|
|
200
198
|
Run:
|
|
201
199
|
|
|
202
200
|
```sh
|
|
203
|
-
bun
|
|
201
|
+
bun run typecheck
|
|
204
202
|
```
|
|
205
203
|
|
|
206
|
-
The repository uses
|
|
204
|
+
The repository uses NodeNext module resolution. Relative TypeScript imports must therefore use the emitted JavaScript extension, for example:
|
|
207
205
|
|
|
208
206
|
```ts
|
|
209
207
|
import { getPricingSnapshot } from "./pricing.js"
|
|
210
208
|
```
|
|
209
|
+
|
|
210
|
+
## License
|
|
211
|
+
|
|
212
|
+
This project is licensed under the MIT License. See [LICENSE](./LICENSE).
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "oc-deepseek-pricing",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "OpenCode TUI plugin showing DeepSeek peak and off-peak pricing windows",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -12,7 +12,9 @@
|
|
|
12
12
|
"tui"
|
|
13
13
|
],
|
|
14
14
|
"files": [
|
|
15
|
-
"src",
|
|
15
|
+
"src/holidays.ts",
|
|
16
|
+
"src/pricing.ts",
|
|
17
|
+
"src/tui.tsx",
|
|
16
18
|
"README.md",
|
|
17
19
|
"LICENSE"
|
|
18
20
|
],
|
|
@@ -35,12 +37,6 @@
|
|
|
35
37
|
"pack:check": "npm pack --dry-run",
|
|
36
38
|
"prepublishOnly": "bun run check"
|
|
37
39
|
},
|
|
38
|
-
"peerDependencies": {
|
|
39
|
-
"@opencode/plugin": ">=2.2.0 <2.3.0",
|
|
40
|
-
"@opentui/core": "0.7.x",
|
|
41
|
-
"@opentui/solid": "0.7.x",
|
|
42
|
-
"solid-js": "1.9.17"
|
|
43
|
-
},
|
|
44
40
|
"devDependencies": {
|
|
45
41
|
"@biomejs/biome": "2.6.3",
|
|
46
42
|
"@opencode/plugin": "2.2.0",
|
package/src/pricing.test.ts
DELETED
|
@@ -1,121 +0,0 @@
|
|
|
1
|
-
import { describe, expect, test } from "bun:test"
|
|
2
|
-
|
|
3
|
-
import { formatDuration, getPricingSnapshot } from "./pricing.js"
|
|
4
|
-
|
|
5
|
-
const COVERED_YEARS = new Set([2026])
|
|
6
|
-
|
|
7
|
-
function snapshotAt(iso: string, holidays: ReadonlySet<string> = new Set()) {
|
|
8
|
-
return getPricingSnapshot(new Date(iso), {
|
|
9
|
-
chinesePublicHolidays: holidays,
|
|
10
|
-
coveredHolidayYears: COVERED_YEARS,
|
|
11
|
-
})
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
function stateAt(iso: string) {
|
|
15
|
-
return snapshotAt(iso).state
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
describe("pricing windows", () => {
|
|
19
|
-
test("handles both weekday peak windows", () => {
|
|
20
|
-
expect(stateAt("2026-10-26T00:59:59Z")).toBe("off-peak")
|
|
21
|
-
expect(stateAt("2026-10-26T01:00:00Z")).toBe("peak")
|
|
22
|
-
expect(stateAt("2026-10-26T04:00:00Z")).toBe("off-peak")
|
|
23
|
-
expect(stateAt("2026-10-26T06:00:00Z")).toBe("peak")
|
|
24
|
-
expect(stateAt("2026-10-26T10:00:00Z")).toBe("off-peak")
|
|
25
|
-
})
|
|
26
|
-
|
|
27
|
-
test("keeps weekends off-peak", () => {
|
|
28
|
-
const saturday = snapshotAt("2026-10-24T02:00:00Z")
|
|
29
|
-
const sunday = snapshotAt("2026-10-25T07:00:00Z")
|
|
30
|
-
|
|
31
|
-
expect(saturday.state).toBe("off-peak")
|
|
32
|
-
expect(saturday.reason).toBe("weekend")
|
|
33
|
-
|
|
34
|
-
expect(sunday.state).toBe("off-peak")
|
|
35
|
-
expect(sunday.reason).toBe("weekend")
|
|
36
|
-
})
|
|
37
|
-
|
|
38
|
-
test("marks ordinary weekday hours outside the windows as off-peak", () => {
|
|
39
|
-
const snapshot = snapshotAt("2026-10-26T05:00:00Z")
|
|
40
|
-
|
|
41
|
-
expect(snapshot.state).toBe("off-peak")
|
|
42
|
-
expect(snapshot.reason).toBe("outside-peak-window")
|
|
43
|
-
expect(snapshot.rateMultiplier).toBe(0.5)
|
|
44
|
-
})
|
|
45
|
-
|
|
46
|
-
test("uses the full multiplier during peak hours", () => {
|
|
47
|
-
const snapshot = snapshotAt("2026-10-26T08:00:00Z")
|
|
48
|
-
|
|
49
|
-
expect(snapshot.state).toBe("peak")
|
|
50
|
-
expect(snapshot.reason).toBe("peak-window")
|
|
51
|
-
expect(snapshot.rateMultiplier).toBe(1)
|
|
52
|
-
})
|
|
53
|
-
})
|
|
54
|
-
|
|
55
|
-
describe("Chinese public holidays", () => {
|
|
56
|
-
test("overrides a weekday peak window", () => {
|
|
57
|
-
const snapshot = snapshotAt("2026-10-01T02:00:00Z", new Set(["2026-10-01"]))
|
|
58
|
-
|
|
59
|
-
expect(snapshot.state).toBe("off-peak")
|
|
60
|
-
expect(snapshot.reason).toBe("chinese-public-holiday")
|
|
61
|
-
expect(snapshot.rateMultiplier).toBe(0.5)
|
|
62
|
-
})
|
|
63
|
-
|
|
64
|
-
test("reports whether the Chinese calendar year is covered", () => {
|
|
65
|
-
expect(snapshotAt("2026-10-27T12:00:00Z").holidayCalendarCovered).toBe(true)
|
|
66
|
-
|
|
67
|
-
const uncovered = getPricingSnapshot(new Date("2027-01-05T12:00:00Z"), {
|
|
68
|
-
chinesePublicHolidays: new Set(),
|
|
69
|
-
coveredHolidayYears: COVERED_YEARS,
|
|
70
|
-
})
|
|
71
|
-
|
|
72
|
-
expect(uncovered.holidayCalendarCovered).toBe(false)
|
|
73
|
-
})
|
|
74
|
-
})
|
|
75
|
-
|
|
76
|
-
describe("next transition", () => {
|
|
77
|
-
test("finds the end of the first peak window", () => {
|
|
78
|
-
const snapshot = snapshotAt("2026-10-27T02:15:00Z")
|
|
79
|
-
|
|
80
|
-
expect(snapshot.nextState).toBe("off-peak")
|
|
81
|
-
expect(snapshot.nextTransition).toBe("2026-10-27T04:00:00.000Z")
|
|
82
|
-
expect(snapshot.secondsUntilTransition).toBe(6_300)
|
|
83
|
-
})
|
|
84
|
-
|
|
85
|
-
test("finds the beginning of the second peak window", () => {
|
|
86
|
-
const snapshot = snapshotAt("2026-10-27T05:30:00Z")
|
|
87
|
-
|
|
88
|
-
expect(snapshot.nextState).toBe("peak")
|
|
89
|
-
expect(snapshot.nextTransition).toBe("2026-10-27T06:00:00.000Z")
|
|
90
|
-
expect(snapshot.secondsUntilTransition).toBe(1_800)
|
|
91
|
-
})
|
|
92
|
-
|
|
93
|
-
test("skips the weekend when finding the next peak window", () => {
|
|
94
|
-
const snapshot = snapshotAt("2026-10-30T10:00:00Z")
|
|
95
|
-
|
|
96
|
-
expect(snapshot.state).toBe("off-peak")
|
|
97
|
-
expect(snapshot.nextState).toBe("peak")
|
|
98
|
-
expect(snapshot.nextTransition).toBe("2026-11-02T01:00:00.000Z")
|
|
99
|
-
})
|
|
100
|
-
})
|
|
101
|
-
|
|
102
|
-
describe("duration formatting", () => {
|
|
103
|
-
test("formats minutes and hours", () => {
|
|
104
|
-
expect(formatDuration(60)).toBe("1m")
|
|
105
|
-
expect(formatDuration(3_600)).toBe("1h")
|
|
106
|
-
expect(formatDuration(5_400)).toBe("1h 30m")
|
|
107
|
-
})
|
|
108
|
-
|
|
109
|
-
test("rounds partial minutes up", () => {
|
|
110
|
-
expect(formatDuration(1)).toBe("1m")
|
|
111
|
-
expect(formatDuration(61)).toBe("2m")
|
|
112
|
-
})
|
|
113
|
-
})
|
|
114
|
-
|
|
115
|
-
describe("input validation", () => {
|
|
116
|
-
test("rejects invalid dates", () => {
|
|
117
|
-
expect(() => {
|
|
118
|
-
getPricingSnapshot(new Date("invalid"))
|
|
119
|
-
}).toThrow("Invalid date")
|
|
120
|
-
})
|
|
121
|
-
})
|