# dsh-save-money

> 按时间窗口自动暂停/恢复 DSH 模型请求，应对 DeepSeek 峰谷分时计价并可按模型档位豁免。

## Metadata

- Author: [@zhu168](https://github.com/zhu168)
- Repo: <https://github.com/zhu168/dsh-save-money.git>
- GitHub: [zhu168/dsh-save-money](https://github.com/zhu168/dsh-save-money)
- Stars: 33
- Language: JavaScript
- License: [MIT](https://spdx.org/licenses/MIT.html)
- Topics: `dsh`, `dsh-plugin`, `dsh-plugins`
- Forks: 2
- Open Issues: 1
- Last push: 2026-08-18T11:05:22.000Z
- Added: 2026-08-17T00:00:00.000Z

## Install

```bash
dsh plugin --profile web add github:zhu168/dsh-save-money
```

## Wiki

## One-Line Description
Auto-pause/resume DSH model requests based on custom time windows, addressing peak/off-peak time-of-use pricing for large model APIs like DeepSeek; no requests or costs incurred during the window, auto-resumes when window ends, configuration takes effect per model tier.

## Core Features
- **Auto-pause by time window**: When pause time arrives, active running tasks are frozen (progress preserved as-is), auto-resumes when window ends; if no active tasks, no pause record is created
- **Complete silence during pause window**: Intercepts model streams (llm/stream) during window period, all matching tier requests are suspended and waiting, released when window ends, no API costs incurred
- **Exemption by model tier**: Only applies to official DeepSeek's flash/pro tiers by default; opencode go·zen flash/pro and any other unrecognized third-party models are exempt, requests won't be blocked
- **Configuration persistence**: All settings auto-saved to workspace file `save-money.config.json`, retained after restart/browser refresh; balance sampling history persisted to `~/.dsh/dsh-save-money-balance.json` (identified by API key fingerprint, automatically invalidates old history when changing key)
- **Optional DeepSeek balance and spending chart**: After enabling "Show Balance", official account balance displayed next to header status text; backend samples every 5 minutes, hover to see recent 1 hour/10 minutes/24 hours spending amounts, click balance to pop up consumption bar chart for recent 8 hours with one bar per 10 minutes
- **One-click DeepSeek time-of-use pricing strategy**: Auto-fills Beijing time peak windows `08:58–12:02, 13:58–18:02` (pause starts 2 minutes early, resume delayed 2 minutes for buffer), not auto-enabled, you decide whether to check it

## Technical Implementation
- **Language**: TypeScript (`tsconfig.json` strict mode; 17 `.ts` + 6 `.tsx` files under `src/`)
- **Key dependencies**: No runtime third-party dependencies; build-time devDependencies only include `typescript ^5.3` (`package.json:17`), no npm libraries introduced
- **Architecture pattern**: Official dual-half plugin. Host half injects `id: save-money` line via `cordis.patch.yml`; wraps model stream at `ctx.on('llm/stream', ..., { global: true })` as request-level gate, combined with `ctx.get('goals').pause/resume` to freeze and restore active tasks during pause/resume. Client half mounted to web profile via `plugin/package.json#dsh.client`, persistent status text in session header top-right, floating banner and settings page auto-loaded. 10 language copies in `src/i18n/*`, inlined at build; React UI uses DSH theme variables `--dsw-*` with random light/dark theme
- **Entry files**: Host entry `src/host.ts` (`apply`), built to `plugin/index.js`; Client entry `src/client.ts`, built to `plugin/client.js`; mount declaration `plugin/cordis.patch.yml` (bundle install inserts 1 Loader line)

## Use Cases
When DSH users want to reduce peak billing for large model APIs like DeepSeek without missing automated long tasks: set daytime peak hours as pause window, let plugin freeze running tasks and suspend new requests at window start, seamlessly continue when window ends — especially suitable for long-duration unattended batch processing scenarios; also want to visualize consumption trends, set model tier exemptions for OpenCode subscriptions or third-party proxies, no longer worry about forgetting to re-enable the switch.

## Prerequisites & Compatibility
| Dependency | Min Version | Description |
|---|---|---|
| DeepSeek Harness (DSH) | Not declared | `package.json` doesn't declare `engines`/`peerDependencies`; installed as official `dsh plugin` form, relies on host providing `timer`/`agents`/`goals`/`fs`/`sessions`/`webServer`/`credentials`/`subprocess` etc. services |
| Node.js | Not declared | Repository doesn't declare `engines`; build scripts use built-in modules like `node:fs`/`node:path`/`node:url`/`node:test`, any Node version running DSH Web profile works |
| Platform | Cross-platform | `tsconfig.json#compilerOptions.target = ES2022`; source uses `Intl.DateTimeFormat` + IANA timezones (including DST ±12/±13 boundaries like Pacific/Auckland), no platform-specific code |
| Native modules | None | All third-party dependencies are zero; no native modules like `node-pty`/`node:sqlite` introduced |
| Timezone | Any IANA name | Settings panel provides 24 representative timezones dropdown + 24 IANA timezone candidates, `Asia/Shanghai` as default; window can independently override timezone |

## Installation
```bash
dsh plugin --profile web add github:zhu168/dsh-save-money
```

## Configuration Options
All configurations editable via Settings → Save Money Plugin (sidebar) or session header top-right "Save Money · 🠖 Working" floating layer; below is overview of adjustable fields (excerpt from `src/config.ts:26-58`, all fields auto-saved after schema validation in `applyConfig`). Language follows browser or manual selection (auto/zh/zh-TW/en/de/fr/es/it/pt/ja/ko total 10 languages).

| Config | Type | Description | Default |
|---|---|---|---|
| Enabled | boolean | Master switch: after checking, plugin pauses/requests according to window; unchecking immediately releases gate waiters | `false` (disabled) |
| Timezone | IANA name | Timezone for window and status clock; switching timezone auto-converts window times (DST-aware, e.g., Beijing 08:58 summer corresponds to London 01:58, winter 00:58) | `Asia/Shanghai` |
| Advance reminder (minutes) | number(>=0) | How many minutes before next pause to enter "About to Pause" state and show yellow reminder banner at top | `5` |
| Attempt to resume paused tasks on startup | boolean | If paused goals remnants from crash found on startup, auto resume — no task should be permanently frozen across restarts | `true` |
| Show balance | boolean | Display DeepSeek official account balance + hover card + 8-hour spending bar chart next to header status text; after closing, sampling stops, no more `/user/balance` visits | `false` (not shown) |
| Model tier: official flash | boolean | Check = pause official DeepSeek flash model requests during window; unchecked = exempt (pass through even in window) | `true` |
| Model tier: official pro | boolean | Check = pause official DeepSeek pro model requests during window; unchecked = exempt | `true` |
| Model tier: opencode flash | boolean | Check = pause opencode go/zen flash model requests during window; unchecked = exempt | `false` |
| Model tier: opencode pro | boolean | Check = pause opencode go/zen pro model requests during window; unchecked = exempt | `false` |
| Pause window | array<{time, weekday, timezone}> | Customize a set of pause/resume times (HH:mm, supports cross-midnight like 23:00–08:00); same-group weekday filtering (1=Monday…7=Sunday); single window can have independent timezone. When default empty, settings panel pre-fills DeepSeek peak `08:58–12:02, 13:58–18:02` Beijing time | `[]` |
| One-click DeepSeek time-of-use pricing strategy | button | Does not modify enabled switch; dedup-append `08:58–12:02, 13:58–18:02` Beijing time peak windows based on currently selected window view | — |
| End this save money session | button | One-time skip current pause window: if already paused, immediately resume; if about to pause, cancel this occurrence; persisted "Enabled" switch **will NOT be turned off** | — |

## FAQ

**Q: After installation, status text/floating banner/settings page don't appear, what to do?**

A: Must fully restart DSH (Ctrl+C to stop then start) and force refresh browser (Ctrl+Shift+R), plugin UI mounts at startup. If still not appearing, using old build before v1.2.4 — just upgrade (`README.zh.md:304`).

**Q: "Enabled" checkbox won't check, settings items can't be clicked, is this a bug?**

A: No. Early versions (before v1.2.5) on some machines plugin ready before Web service, interface requests can't reach plugin. Current 1.4.3 fixed — waits for `webServer` ready before registering HTTP endpoints (`README.zh.md:305` / `src/host-http.ts:23-63`).

**Q: Will this plugin pause all model calls? Will third-party models be paused?**

A: Only pauses official DeepSeek's flash/pro tiers by default; opencode go·zen flash/pro and any other models not recognized as official flash/pro or opencode flash/pro are always exempt, requests never blocked. Unrecognized providers (like other third-party proxies, old names `deepseek-chat`/`deepseek-reasoner`) will never hang your requests (`src/balance-bars.ts:53-63`).

**Q: Where is data stored? What happens after deletion?**

A: Settings file goes to workspace `save-money.config.json` (gitignored); balance sampling history stored at `~/.dsh/dsh-save-money-balance.json` (identified by API key fingerprint, changing key automatically invalidates old history). Deleting former restores defaults; deleting latter clears spending statistics (`src/config.ts:57-58` / `src/balance-tracker.ts:81-100`).

**Q: After pause window starts, will running tasks be lost?**

A: No. Active running tasks are safely "frozen" (progress preserved as-is), auto-resume and continue when window ends; if no active tasks at that moment, plugin won't create pause record (`src/host-goals.ts:63-90` / `src/host.ts:151-155`).

**Q: Will "End this save money session" button turn off the entire plugin?**

A: No. That button only skips current pause window (if already paused, immediately resume; if about to pause, cancel this occurrence), next window (today or future) works as normal, and won't change persisted "Enabled" switch (`src/state.ts:50-79` / `src/host.ts:348-366`).

**Q: Balance numbers display strangely — disappear after switching models?**

A: Balance only works for official DeepSeek (provider is `deepseek-official`); when switching to OpenCode/third-party models, balance auto-hides, but sampled consumption statistics won't be cleared, balance immediately restores after switching back to DeepSeek (`src/balance-tracker.ts:178`).

**Q: How to uninstall? Will settings remain after uninstall?**

A: `dsh plugin --profile web remove dsh-save-money`, restart DSH. Config file `save-money.config.json` stays in original directory — uninstall/reinstall won't touch it, manually delete that file to restore defaults (`README.zh.md:238` / `README.zh.md:306`).

## Difficulty Level
Beginner — After installation, just restart DSH and force refresh browser, "Save Money" status text appears in session header top-right; after entering settings, click one-click DeepSeek time-of-use pricing strategy + check "Enabled" to take effect, no need to care about internal gates/goal freezing/timezone conversion mechanisms.

## Known Issues & Limitations
- Pause effect only applies to models recognizable as official flash/pro or opencode flash/pro: other third-party proxies, old names `deepseek-chat`/`deepseek-reasoner`, undeclared model names all exempt, won't be paused (`src/balance-bars.ts:53-63`)
- Balance display depends on recent real requests running on official DeepSeek: when switching to OpenCode or third-party proxies, balance auto-hides (sampled consumption statistics won't be cleared), only when baseURL changed to non-official endpoint (proxy has no `/user/balance`) will it be disabled (`src/balance-tracker.ts:178` / `CHANGELOG.zh.md:67-68`)
- "Advance reminder" calculates minute difference by configured global timezone: reminder time for cross-midnight or cross-timezone windows may slightly deviate from intuition (`src/state.ts:62-78`)
- Config file lookup follows 6-level candidates "pointer → workspace → repo directory → DSH startup directory → sibling `dsh-save-money` directory → sandboxPolicy.workspaceRoot": if multiple configs scattered in different directories, the **first directory with existing config file** wins, strongly related to startup location (`src/config.ts:210-240`)
- Balance history bound to API key fingerprint: after更换 DeepSeek API key, old history automatically invalidates, won't bring old account's balance trajectory to new account (`src/balance-tracker.ts:81-100`)
- Requests inside pause window will hang indefinitely waiting for gate to open: client cancelling request (abort signal) or DSH shutdown lets gate release immediately to avoid hanging; but if long-hanging with no wake event triggered, request wakes up at next 60-second timer tick at latest (`src/host.ts:218-238` / `src/gate.ts:55-72`)
- Must restart DSH after installing/updating plugin for changes to take effect: bundle line parsing and client bundle both scanned at startup, only refreshing browser won't reload server-side plugin (`README.zh.md:93`)

---

This document is auto-generated by [deepseek-plugin.org](https://deepseek-plugin.org). HTML page: [dsh-save-money](https://deepseek-plugin.org/plugins/zhu168/dsh-save-money)
Wiki generated by AI (model: `MiniMax-M2.5`)
