dsh-peak-block 0.1.0

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 betterer
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,71 @@
1
+ # dsh·梁文峰时间拦截插件
2
+
3
+ 在 DeepSeek 官方高峰时段拦截官方 provider 请求,保障安心使用梁文谷。
4
+
5
+ 高峰时段默认北京时间工作日 09:00–12:00、14:00–18:00,周末全天谷价。拦截到官方请求时:已配置 `targetProvider` 则切到该目标,未配置则阻止并提示;非高峰不拦截,正常走官方。官方判定默认只精确匹配 `deepseek-official`,不做名称前缀规则,因此 pi-ai 自带的三方 `deepseek` 中转不会误拦。
6
+
7
+ ## 效果
8
+
9
+ 未配置 `targetProvider` 时于高峰时段发起官方请求,请求被阻止,界面呈现「未配置拦截目标」的报错:
10
+
11
+ ![拦截效果](docs/拦截效果.webp)
12
+
13
+ ## 安装
14
+
15
+ ```powershell
16
+ dsh plugin --profile web add github:better-er/dsh-peak-block
17
+ ```
18
+
19
+ 一条命令装完即生效,自动挂载,重启 DSH web 后启用,无需手工编辑任何文件。
20
+
21
+ ## 卸载
22
+
23
+ ```powershell
24
+ dsh plugin --profile web remove dsh-peak-block
25
+ ```
26
+
27
+ 彻底移除,重启 DSH web 后不再加载。
28
+
29
+ ## 配置
30
+
31
+ 插件通过 cordis 配置注入,默认 `enabled: true`,其余键可覆盖:
32
+
33
+ | 键 | 默认 | 说明 |
34
+ |---|---|---|
35
+ | `enabled` | `true` | 总开关 |
36
+ | `officialProviders` | 未设(只认 `deepseek-official`) | 视为「官方」的 provider 精确名单,不设用默认判定 |
37
+ | `targetProvider` | 空 | 拦截后切到的目标 provider;留空 = 阻止并提示 |
38
+ | `peakWindow` | 工作日 9–12、14–18,周末谷 | 峰谷时段窗口,可整体或局部覆盖 |
39
+
40
+ 配置示例:
41
+
42
+ ```yaml
43
+ plugins:
44
+ dsh-peak-block:
45
+ enabled: true
46
+ targetProvider: opencode-go
47
+ peakWindow:
48
+ days: [1, 2, 3, 4, 5]
49
+ hourRanges: [[9, 12], [14, 18]]
50
+ weekendOffPeak: true
51
+ ```
52
+
53
+ ## 边界
54
+
55
+ - 只拦对话模型请求:`agent/request` 覆盖 agent loop 的正常对话;compaction 等走 `ctx.llm.stream()` 的路径不拦。
56
+ - `targetProvider` 必须是 DSH 已注册适配器路由,否则切换后以 `NO_ADAPTER` 失败。
57
+ - `days` 用 JS `getUTCDay` 序号,1..5 为周一..周五;时区判定纯 UTC+8 数学换算,与系统时区无关。
58
+
59
+ ## 要求与开发
60
+
61
+ - 是**标准形态的 dsh 主机加客户端双半身插件**:host 在 `agent/request` waterfall 里拦截并切换/阻止,`./client` 在设置面板提供一个独立的「梁文峰拦截」标签页,内含 enabled、targetProvider、officialProviders、peakWindow 与预留转 API 端口的配置骨架。当前配置骨架不写回 host,正式生效待接线。
62
+ - 无构建:`lib/index.js` 与 `lib/client.js` 均为源码即产物,改完即用。
63
+ - 纯函数 `isPeakBeijing` / `isOfficial` / `decide` 在 `lib/index.js` 导出,冒烟测试:
64
+
65
+ ```powershell
66
+ node scripts/smoke.mjs
67
+ ```
68
+
69
+ ## License
70
+
71
+ [MIT](./LICENSE)
@@ -0,0 +1,8 @@
1
+ # dsh-peak-block bundle patch: inserts this plugin into a profile's layer stack.
2
+ # Applied when a profile lists this bundle (or wired by dev_install_package).
3
+ # targetProvider 留空即「未配置拦截目标」,梁文峰时间拦截到时走「阻止并提示」。
4
+ - insert:
5
+ - id: dsh-peak-block
6
+ name: 'dsh-peak-block'
7
+ config:
8
+ enabled: true
package/lib/client.js ADDED
@@ -0,0 +1,191 @@
1
+ // dsh-peak-block 浏览器半身:设置面板里的一个独立标签页。
2
+ // host 侧(lib/index.js)完成真正的拦截与路由切换。本半身提供配置入口的 UI 骨架:
3
+ // enabled、targetProvider、officialProviders、peakWindow 与预留的「转 API 端口」。
4
+ // 当前仅为界面演示,字段值不写回 host;正式生效需后续接线 host 的配置读写通道。
5
+ // 真正的「被拦截且未配置目标」提示,由 host 侧抛出的失败文案随界面失败信息呈现,本半身不重复注入,避免易碎的 UI 依赖。
6
+
7
+ window.__ModuleLoader__.load({
8
+ id: 'dsh-peak-block',
9
+ factory: (require) => {
10
+ const module = { exports: {} }
11
+ const exports = module.exports
12
+
13
+ const React = require('react')
14
+
15
+ const plugin = {
16
+ name: 'dsh-peak-block',
17
+ inject: ['slots'],
18
+ apply(ctx) {
19
+ const slots = ctx.get && ctx.get('slots')
20
+ if (!slots) return
21
+
22
+ const DOC = typeof document !== 'undefined' ? document : null
23
+ let styleTag = null
24
+
25
+ function ensureStyle() {
26
+ if (!DOC || styleTag) return
27
+ const css = [
28
+ '.dshpb-section{display:flex;flex-direction:column;gap:12px;max-width:720px;color:var(--dsw-alias-label-primary)}',
29
+ '.dshpb-title{margin:0;font-size:16px;line-height:24px;font-weight:500;color:var(--dsw-alias-label-primary)}',
30
+ '.dshpb-intro{margin:0;font-size:14px;line-height:22px;color:var(--dsw-alias-label-tertiary)}',
31
+ '.dshpb-notice{margin:0;font-size:12px;line-height:18px;color:var(--dsw-alias-state-warn-label)}',
32
+ '.dshpb-card{border:1px solid var(--dsw-alias-border-l2);border-radius:12px;padding:14px 16px;display:flex;flex-direction:column;gap:14px}',
33
+ '.dshpb-field{display:flex;flex-direction:column;gap:6px}',
34
+ '.dshpb-fieldLabel{font-size:12px;line-height:18px;font-weight:500;color:var(--dsw-alias-label-secondary)}',
35
+ '.dshpb-hint{margin:0;font-size:12px;line-height:18px;color:var(--dsw-alias-label-tertiary)}',
36
+ '.dshpb-input{box-sizing:border-box;width:100%;height:32px;padding:0 10px;border:1px solid var(--dsw-alias-border-l2);border-radius:8px;font:inherit;font-size:14px;line-height:22px;background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-primary)}',
37
+ '.dshpb-input:focus{outline:none;border-color:var(--dsw-alias-brand-primary)}',
38
+ '.dshpb-input::placeholder{color:var(--dsw-alias-label-dimmed)}',
39
+ 'select.dshpb-input{max-width:320px;cursor:pointer}',
40
+ '.dshpb-rowInput{max-width:320px}',
41
+ '.dshpb-actions{display:flex;justify-content:flex-end;gap:8px}',
42
+ '.dshpb-button{box-sizing:border-box;display:inline-flex;align-items:center;justify-content:center;gap:4px;height:36px;padding:0 14px;border:none;border-radius:18px;font:inherit;font-size:14px;line-height:22px;cursor:pointer}',
43
+ '.dshpb-buttonPrimary{background:var(--dsw-alias-button-primary-fill);color:var(--dsw-alias-label-primary-foreground)}',
44
+ '.dshpb-buttonPrimary:hover{background:var(--dsw-alias-button-primary-hover)}',
45
+ '.dshpb-switch{display:inline-flex;align-items:center;gap:8px;cursor:pointer;font-size:14px;line-height:22px}',
46
+ ].join('')
47
+ const tag = DOC.createElement('style')
48
+ tag.textContent = css
49
+ DOC.head.appendChild(tag)
50
+ styleTag = tag
51
+ }
52
+
53
+ const TARGET_TYPES = {
54
+ none: '未配置(高峰阻止并提示)',
55
+ proxy: '第三方中转路由',
56
+ port: '转 API 端口',
57
+ }
58
+
59
+ function Section() {
60
+ const [enabled, setEnabled] = React.useState(true)
61
+ const [targetType, setTargetType] = React.useState('none')
62
+ const [proxyRoute, setProxyRoute] = React.useState('opencode-go')
63
+ const [apiPort, setApiPort] = React.useState('127.0.0.1:8080')
64
+ const [official, setOfficial] = React.useState('deepseek-official')
65
+ const [savedHint, setSavedHint] = React.useState('')
66
+
67
+ function onSave() {
68
+ setSavedHint('已保存到界面状态,尚未写回 host,正式生效需接线后端。')
69
+ }
70
+
71
+ return React.createElement(
72
+ 'div',
73
+ { className: 'dshpb-section', role: 'region', 'aria-label': '梁文峰拦截设置' },
74
+ React.createElement('h2', { className: 'dshpb-title' }, '梁文峰时间拦截'),
75
+ React.createElement('p', { className: 'dshpb-intro' }, '在 DeepSeek 官方高峰时段拦截官方 provider 请求,可切换到第三方中转或本机转 API 端口。'),
76
+ React.createElement('p', { className: 'dshpb-notice' }, '当前为配置界面骨架,改动不写回 host,正式生效需后续接线。'),
77
+ React.createElement(
78
+ 'div',
79
+ { className: 'dshpb-card' },
80
+ React.createElement(
81
+ 'label',
82
+ { className: 'dshpb-switch' },
83
+ React.createElement('input', {
84
+ type: 'checkbox',
85
+ checked: enabled,
86
+ onChange: function (e) { setEnabled(e.target.checked) },
87
+ }),
88
+ '启用拦截'
89
+ ),
90
+ React.createElement(
91
+ 'div',
92
+ { className: 'dshpb-field' },
93
+ React.createElement('label', { className: 'dshpb-fieldLabel', htmlFor: 'dshpb-target' }, '拦截目标 targetProvider'),
94
+ React.createElement(
95
+ 'select',
96
+ {
97
+ id: 'dshpb-target',
98
+ className: 'dshpb-input',
99
+ value: targetType,
100
+ onChange: function (e) { setTargetType(e.target.value) },
101
+ },
102
+ React.createElement('option', { value: 'none' }, TARGET_TYPES.none),
103
+ React.createElement('option', { value: 'proxy' }, TARGET_TYPES.proxy),
104
+ React.createElement('option', { value: 'port' }, TARGET_TYPES.port)
105
+ ),
106
+ targetType === 'proxy' &&
107
+ React.createElement('input', {
108
+ className: 'dshpb-input dshpb-rowInput',
109
+ value: proxyRoute,
110
+ placeholder: '如 opencode-go',
111
+ 'aria-label': '第三方中转路由',
112
+ onChange: function (e) { setProxyRoute(e.target.value) },
113
+ }),
114
+ targetType === 'port' &&
115
+ React.createElement(
116
+ 'div',
117
+ { className: 'dshpb-field' },
118
+ React.createElement('label', { className: 'dshpb-fieldLabel', htmlFor: 'dshpb-port' }, '转 API 端口'),
119
+ React.createElement('input', {
120
+ id: 'dshpb-port',
121
+ className: 'dshpb-input dshpb-rowInput',
122
+ value: apiPort,
123
+ placeholder: '127.0.0.1:8080',
124
+ onChange: function (e) { setApiPort(e.target.value) },
125
+ }),
126
+ React.createElement('p', { className: 'dshpb-hint' }, '预留:把被拦请求转发到本机 OpenAI 兼容端口。')
127
+ )
128
+ ),
129
+ React.createElement(
130
+ 'div',
131
+ { className: 'dshpb-field' },
132
+ React.createElement('label', { className: 'dshpb-fieldLabel', htmlFor: 'dshpb-official' }, '官方判定 provider 名单(逗号分隔)'),
133
+ React.createElement('input', {
134
+ id: 'dshpb-official',
135
+ className: 'dshpb-input dshpb-rowInput',
136
+ value: official,
137
+ onChange: function (e) { setOfficial(e.target.value) },
138
+ })
139
+ ),
140
+ React.createElement(
141
+ 'div',
142
+ { className: 'dshpb-field' },
143
+ React.createElement('span', { className: 'dshpb-fieldLabel' }, '高峰时段'),
144
+ React.createElement('p', { className: 'dshpb-hint' }, '北京时间工作日 09:00–12:00、14:00–18:00,周末全天谷价。')
145
+ ),
146
+ React.createElement(
147
+ 'div',
148
+ { className: 'dshpb-actions' },
149
+ React.createElement(
150
+ 'button',
151
+ { type: 'button', className: 'dshpb-button dshpb-buttonPrimary', onClick: onSave },
152
+ '保存(演示)'
153
+ )
154
+ ),
155
+ savedHint !== '' &&
156
+ React.createElement('p', { className: 'dshpb-hint' }, savedHint)
157
+ )
158
+ )
159
+ }
160
+
161
+ slots.inject('settings.section', function () {
162
+ return slots.register(
163
+ {
164
+ name: 'settings.section',
165
+ id: 'peak-block',
166
+ order: 90,
167
+ label: '梁文峰拦截',
168
+ },
169
+ () => React.createElement(Section)
170
+ )
171
+ })
172
+
173
+ ctx.effect(function () {
174
+ ensureStyle()
175
+ return function () {
176
+ if (styleTag && styleTag.parentNode) styleTag.parentNode.removeChild(styleTag)
177
+ styleTag = null
178
+ }
179
+ })
180
+ },
181
+ }
182
+
183
+ exports.default = plugin
184
+ exports.name = plugin.name
185
+ exports.inject = plugin.inject
186
+ exports.apply = plugin.apply
187
+
188
+ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' })
189
+ return module.exports
190
+ },
191
+ })
package/lib/index.js ADDED
@@ -0,0 +1,88 @@
1
+ // dsh-peak-block host 端:梁文峰时间拦截向 DeepSeek 官方 API 的模型请求。
2
+ //
3
+ // 在「梁文峰时间」,即 DeepSeek 官方高峰时段——北京时间工作日 09:00–12:00、14:00–18:00、周末全天谷价——拦截原本发往官方 provider 的对话请求。
4
+ // 已配置 targetProvider 则切换路由,未配置则阻止并抛出带文案的错误提示。非高峰时段不拦截,正常走官方。
5
+ //
6
+ // 拦截 seam:agent/request waterfall。它对每次对话模型请求携带冻结的调用配置种子 LlmCallConfig(provider/model/reasoningEffort/temperature/maxTokens/stop)。
7
+ // listener 可返回替代 config 切换 provider。要判断请求是否官方必须先调用 next() 拿到 config,故实现为「先取 config 再决策」:切换时返回替代 config,阻止时抛带提示文案的错误。
8
+ // compaction 等走 ctx.llm.stream() 直接调用的路径不经此 waterfall,本插件不拦。
9
+
10
+ export const name = 'dsh-peak-block'
11
+ export const inject = []
12
+
13
+ /** 梁文峰时间默认窗口:工作日 09:00–12:00、14:00–18:00(UTC+8),周末全天谷。 */
14
+ export const DEFAULT_PEAK = {
15
+ days: [1, 2, 3, 4, 5], // JS getUTCDay:0=周日 … 6=周六,1..5=周一..周五
16
+ hourRanges: [[9, 12], [14, 18]],
17
+ weekendOffPeak: true,
18
+ }
19
+
20
+ /** 默认官方 provider 判定:只精确匹配 DSH 官方注册路由 `deepseek-official`。
21
+ * 不用名称前缀规则——pi-ai 自带的第三方 `deepseek` 中转不应算官方,避免误拦。 */
22
+ export function defaultIsOfficial(provider) {
23
+ return provider === 'deepseek-official'
24
+ }
25
+
26
+ /**
27
+ * 时刻是否处于梁文峰时间。纯 UTC+8 数学换算,与系统时区无关,本机时钟/时区不可信。
28
+ * 红线:周末全天谷价,仅工作日有峰。
29
+ */
30
+ export function isPeakBeijing(timeMs, peak = DEFAULT_PEAK) {
31
+ const shifted = timeMs + 8 * 3600 * 1000
32
+ const d = new Date(shifted)
33
+ const day = d.getUTCDay()
34
+ if (peak.weekendOffPeak !== false && (day === 0 || day === 6)) return false
35
+ if (Array.isArray(peak.days) && peak.days.length > 0 && !peak.days.includes(day)) return false
36
+ const hour = d.getUTCHours()
37
+ for (const [a, b] of peak.hourRanges) {
38
+ if (hour >= a && hour < b) return true
39
+ }
40
+ return false
41
+ }
42
+
43
+ /** provider 是否视为官方。显式名单用精确匹配;未提供名单用默认判定(仅 deepseek-official)。 */
44
+ export function isOfficial(provider, officialProviders) {
45
+ if (!provider) return false
46
+ if (Array.isArray(officialProviders)) {
47
+ return officialProviders.some((p) => provider === p)
48
+ }
49
+ return defaultIsOfficial(provider)
50
+ }
51
+
52
+ /** 阻止时抛出的提示文案(用户可见,随界面失败信息呈现)。 */
53
+ export const BLOCK_MESSAGE =
54
+ '【梁文峰时间拦截 · dsh-peak-block】当前为 DeepSeek 官方高峰时段,且未配置拦截目标(targetProvider),请求已阻止。请为 dsh-peak-block 配置 targetProvider,或将请求留到梁文谷时段再发。'
55
+
56
+ /**
57
+ * 纯拦截决策(可单测):返回 { action: 'pass' | 'switch' | 'block', config? }。
58
+ * - 非梁文峰时间或非官方 → pass(放行)
59
+ * - 梁文峰时间 + 官方 + 有目标 → switch(切 provider 到目标)
60
+ * - 梁文峰时间 + 官方 + 无目标 → block(阻止并提示)
61
+ */
62
+ export function decide(seed, opts) {
63
+ if (!isPeakBeijing(opts.timeMs, opts.peakWindow)) return { action: 'pass' }
64
+ if (!isOfficial(seed && seed.provider, opts.officialProviders)) return { action: 'pass' }
65
+ if (opts.targetProvider) {
66
+ return { action: 'switch', config: { ...(seed || {}), provider: opts.targetProvider } }
67
+ }
68
+ return { action: 'block' }
69
+ }
70
+
71
+ export function apply(ctx, config = {}) {
72
+ if (config.enabled === false) return
73
+ const peakWindow = config.peakWindow || DEFAULT_PEAK
74
+ const officialProviders = config.officialProviders
75
+ const targetProvider = config.targetProvider
76
+ ctx.on('agent/request', async (_payload, next) => {
77
+ const seed = await next()
78
+ const decision = decide(seed, {
79
+ timeMs: Date.now(),
80
+ peakWindow,
81
+ officialProviders,
82
+ targetProvider,
83
+ })
84
+ if (decision.action === 'pass') return seed
85
+ if (decision.action === 'switch') return decision.config
86
+ throw new Error(BLOCK_MESSAGE)
87
+ })
88
+ }
package/package.json ADDED
@@ -0,0 +1,46 @@
1
+ {
2
+ "name": "dsh-peak-block",
3
+ "version": "0.1.0",
4
+ "description": "梁文峰时间拦截官方 DeepSeek API 请求:北京时间工作日高峰时段 09:00-12:00、14:00-18:00 自动拦截发往官方 provider `deepseek-official` 的模型请求,可切换到第三方中转或预留的本机转 API 端口,未配置则阻止并提示;非高峰不拦、正常走官方,专治高峰掉速与刷屏。标准可安装 dsh 插件,host+client 双半身。",
5
+ "keywords": [
6
+ "deepseek-harness",
7
+ "dsh",
8
+ "dsh-plugin",
9
+ "deepseek",
10
+ "peak-hours",
11
+ "rate-limit",
12
+ "official",
13
+ "provider",
14
+ "routing"
15
+ ],
16
+ "repository": {
17
+ "type": "git",
18
+ "url": "git+https://github.com/better-er/dsh-peak-block.git"
19
+ },
20
+ "type": "module",
21
+ "main": "lib/index.js",
22
+ "exports": {
23
+ ".": "./lib/index.js",
24
+ "./client": "./lib/client.js",
25
+ "./cordis.patch.yml": "./cordis.patch.yml",
26
+ "./package.json": "./package.json"
27
+ },
28
+ "dsh": {
29
+ "bundle": {
30
+ "patch": "./cordis.patch.yml"
31
+ },
32
+ "client": {
33
+ "platform": "web",
34
+ "inject": [
35
+ "@deepseek-ai/dsh-client-ui-slots"
36
+ ]
37
+ }
38
+ },
39
+ "files": [
40
+ "lib/index.js",
41
+ "lib/client.js",
42
+ "cordis.patch.yml",
43
+ "docs/"
44
+ ],
45
+ "license": "MIT"
46
+ }