Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ OpenUsage lives in your menu bar and shows you how much of your AI coding subscr
- [**Codex**](docs/providers/codex.md) / session, weekly, reviews, credits
- [**Copilot**](docs/providers/copilot.md) / premium, chat, completions
- [**Cursor**](docs/providers/cursor.md) / credits, total usage, auto usage, API usage, on-demand, CLI auth
- [**DeepSeek**](docs/providers/deepseek.md) / balance (USD)
- [**Factory / Droid**](docs/providers/factory.md) / standard, premium tokens
- [**Gemini**](docs/providers/gemini.md) / pro, flash, workspace/free/paid tier
- [**JetBrains AI Assistant**](docs/providers/jetbrains-ai-assistant.md) / quota, remaining
Expand Down
79 changes: 79 additions & 0 deletions docs/providers/deepseek.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
# DeepSeek

> Uses the DeepSeek user balance API with a user-provided API key and starting balance.

## Overview

- **Protocol:** HTTPS (JSON)
- **Endpoint:** `GET https://api.deepseek.com/user/balance`
- **Auth:** `Authorization: Bearer <api_key>`
- **Balance model:** remaining USD (or CNY) balance — no time-based limit

## Authentication

Set the following environment variables:

| Variable | Required | Description |
|---|---|---|
| `DEEPSEEK_API_KEY` | yes | DeepSeek API key from [platform.deepseek.com](https://platform.deepseek.com/) |
| `DEEPSEEK_INITIAL_BALANCE` | yes | Your starting balance (e.g. `10.00`). Must be > 0. |

Comment on lines +16 to +20
If any variable is missing or invalid, the plugin throws:

- `DeepSeek API key missing. Set DEEPSEEK_API_KEY.`
- `DeepSeek initial balance missing or invalid. Set DEEPSEEK_INITIAL_BALANCE to your starting balance (e.g. 10.00).`

## Data Source

Request:

```http
GET /user/balance HTTP/1.1
Host: api.deepseek.com
Authorization: Bearer <api_key>
Accept: application/json
```

Response:

```jsonc
{
"is_available": true,
"balance_infos": [
{
"currency": "USD", // "USD" or "CNY"
"total_balance": "3.55", // string, parse as float
"granted_balance": "0.00", // non-expired granted balance
"topped_up_balance": "3.55" // topped-up balance
}
]
}
```

## Usage Mapping

- Prefer the `USD` entry in `balance_infos`. Fall back to `CNY` if no USD entry is present.
- `used = DEEPSEEK_INITIAL_BALANCE − total_balance` (clamped to ≥ 0).
- `limit = DEEPSEEK_INITIAL_BALANCE`.
- No reset timestamp — balance is a lifetime metric, not a periodic window.
- Plan name is not reported by this API.

## Output

- **Balance** (overview progress line):
- `label`: `Balance`
- `format`: dollars (shown as `$X.XX / $Y.YY`)
- `used`: dollars spent (initial − remaining)
- `limit`: initial balance set by user

## Errors

| Condition | Message |
|---|---|
| Missing API key | `DeepSeek API key missing. Set DEEPSEEK_API_KEY.` |
| Missing/invalid initial balance | `DeepSeek initial balance missing or invalid. Set DEEPSEEK_INITIAL_BALANCE to your starting balance (e.g. 10.00).` |
| HTTP 401/403 | `Session expired. Check your DeepSeek API key.` |
| Non-2xx | `Request failed (HTTP {status}). Try again later.` |
| Network failure | `Request failed. Check your connection.` |
| Unparseable response | `Could not parse usage data.` |
| No USD/CNY balance in response | `Could not find balance in response.` |
4 changes: 4 additions & 0 deletions plugins/deepseek/icon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
118 changes: 118 additions & 0 deletions plugins/deepseek/plugin.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
(function () {
const USAGE_URL = "https://api.deepseek.com/user/balance"
const API_KEY_ENV_VARS = ["DEEPSEEK_API_KEY"]

function readString(value) {
if (typeof value !== "string") return null
const trimmed = value.trim()
return trimmed ? trimmed : null
}

function readNumber(value) {
if (typeof value === "number") return Number.isFinite(value) ? value : null
if (typeof value !== "string") return null
const trimmed = value.trim()
if (!trimmed) return null
const n = Number(trimmed)
return Number.isFinite(n) ? n : null
}

function loadApiKey(ctx) {
for (let i = 0; i < API_KEY_ENV_VARS.length; i += 1) {
const name = API_KEY_ENV_VARS[i]
let value = null
try {
value = ctx.host.env.get(name)
} catch (e) {
ctx.host.log.warn("env read failed for " + name + ": " + String(e))
}
const key = readString(value)
if (key) {
ctx.host.log.info("api key loaded from " + name)
return key
}
}
return null
}

function loadInitialBalance(ctx) {
let value = null
try {
value = ctx.host.env.get("DEEPSEEK_INITIAL_BALANCE")
} catch (e) {
ctx.host.log.warn("env read failed for DEEPSEEK_INITIAL_BALANCE: " + String(e))
}
return readNumber(value)
}

function findBalance(balanceInfos) {
if (!Array.isArray(balanceInfos) || balanceInfos.length === 0) return null
let cnyBalance = null
for (let i = 0; i < balanceInfos.length; i += 1) {
const info = balanceInfos[i]
if (!info || typeof info !== "object") continue
const balance = readNumber(info.total_balance)
if (balance === null) continue
if (info.currency === "USD") return balance
if (info.currency === "CNY") cnyBalance = balance
}
return cnyBalance
Comment on lines +56 to +59
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Reject or convert CNY before reporting dollar usage

DeepSeek’s /user/balance can return CNY balances, but findBalance falls back to CNY and the plugin still reports the metric with format: { kind: "dollars" }. In that case, the app subtracts a CNY amount from DEEPSEEK_INITIAL_BALANCE and presents the result as USD, which produces incorrect spend values for CNY-only accounts. Handle non-USD explicitly (convert with a reliable rate or fail with a clear error) before emitting a dollars-formatted progress line.

Useful? React with 👍 / 👎.

}

function probe(ctx) {
const apiKey = loadApiKey(ctx)
if (!apiKey) {
throw "DeepSeek API key missing. Set DEEPSEEK_API_KEY."
}

const initialBalance = loadInitialBalance(ctx)
if (initialBalance === null || initialBalance <= 0) {
throw "DeepSeek initial balance missing or invalid. Set DEEPSEEK_INITIAL_BALANCE to your starting balance (e.g. 10.00)."
}

let resp
try {
resp = ctx.util.request({
method: "GET",
url: USAGE_URL,
headers: {
Authorization: "Bearer " + apiKey,
Accept: "application/json",
},
timeoutMs: 15000,
})
} catch (e) {
throw "Request failed. Check your connection."
}

if (ctx.util.isAuthStatus(resp.status)) {
throw "Session expired. Check your DeepSeek API key."
}
if (resp.status < 200 || resp.status >= 300) {
throw "Request failed (HTTP " + resp.status + "). Try again later."
}

const json = ctx.util.tryParseJson(resp.bodyText)
if (!json || typeof json !== "object") {
throw "Could not parse usage data."
}

const remainingBalance = findBalance(json.balance_infos)
if (remainingBalance === null) {
throw "Could not find balance in response."
}

const used = Math.max(0, initialBalance - remainingBalance)

const line = {
label: "Balance",
used: used,
limit: initialBalance,
format: { kind: "dollars" },
}
Comment on lines +100 to +112

return { lines: [ctx.line.progress(line)] }
}

globalThis.__openusage_plugin = { id: "deepseek", probe }
})()
12 changes: 12 additions & 0 deletions plugins/deepseek/plugin.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"schemaVersion": 1,
"id": "deepseek",
"name": "DeepSeek",
"version": "0.0.1",
"entry": "plugin.js",
"icon": "icon.svg",
"brandColor": "#4D6BFE",
"lines": [
{ "type": "progress", "label": "Balance", "scope": "overview", "primaryOrder": 1 }
]
}
Loading
Loading