Skip to content
Merged
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
5 changes: 5 additions & 0 deletions .changeset/646af05b.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@mynameistito/opencode-usage-limits": minor
---

Add Novita AI available-balance provider
5 changes: 5 additions & 0 deletions .changeset/b908f8ed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@mynameistito/opencode-plugins-docs": patch
---

Document Novita AI balance support
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ Supported providers:
- [ChatGPT](https://chatgpt.com/)
- [Command Code](https://commandcode.ai/)
- [DeepSeek](https://www.deepseek.com/)
- [Novita AI](https://novita.ai/)
- [OpenCode GO](https://opencode.ai/go)
- [MiniMax Token Plan](https://platform.minimax.io/subscribe/token-plan)
- [Synthetic](https://synthetic.ai/)
Expand Down
4 changes: 2 additions & 2 deletions apps/web/docs/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ Both packages are published to npm and installed through the OpenCode CLI. They
composer hint.
</Card>
<Card title="Usage Limits" href="/usage-limits" icon="gauge">
Track Codex, DeepSeek, Command Code, Z.AI, Synthetic, MiniMax, Qwen, Alibaba
Token Plan, and OpenCode GO windows and balances.
Track Codex, DeepSeek, Novita AI, Command Code, Z.AI, Synthetic, MiniMax,
Qwen, Alibaba Token Plan, and OpenCode GO windows and balances.
</Card>
</CardGroup>

Expand Down
22 changes: 22 additions & 0 deletions apps/web/docs/usage-limits.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,9 @@ The top-level `enabled` flag disables the plugin. Each provider's `enabled` flag
<img src="https://cdn.simpleicons.org/minimax" alt="MiniMax" />
<span class="provider-logo-name">MiniMax</span>
</a>
<a href="#novita-ai" aria-label="Novita AI">
<span class="provider-logo-name">Novita AI</span>
</a>
<a href="#openai" aria-label="OpenAI">
<svg
xmlns="http://www.w3.org/2000/svg"
Expand Down Expand Up @@ -247,6 +250,25 @@ The adapter uses `total_balance` directly. It does not add `granted_balance` and

Token Plan rolling five-hour and weekly windows. Uses MiniMax auth or `OC_MINIMAX_TOKEN_PLAN_KEY`. See the [MiniMax documentation](https://platform.minimax.io/docs/token-plan/intro).

### Novita AI

Displays the current available API balance as a remaining USD amount, not a monthly usage window. Novita's official [`GET /openapi/v1/billing/balance/detail`](https://novita.ai/docs/api-reference/basic-get-user-balance) endpoint returns monetary fields in 1/10,000 USD units; the plugin converts `availableBalance` to USD and does not infer a percentage, balance total, or reset time.

Enable the `novita-ai` provider using OpenCode's saved credential, or configure a key explicitly:

```jsonc
{
"providers": {
"novita-ai": {
"enabled": true,
"apiKey": "{env:NOVITA_API_KEY}",
},
},
}
```

OpenCode credentials are only sent to `https://api.novita.ai`; a custom `baseUrl` requires an explicit `authPath` or `apiKey`.

### OpenAI

ChatGPT/OpenAI rolling windows, daily, weekly, or monthly. Credentials come from OpenCode `openai` auth, then the Codex auth file. See the [OpenAI documentation](https://developers.openai.com/codex).
Expand Down
17 changes: 15 additions & 2 deletions packages/opencode-usage-limits/README.md
Original file line number Diff line number Diff line change
@@ -1,20 +1,21 @@
# @mynameistito/opencode-usage-limits

OpenCode TUI plugin that shows Codex, DeepSeek, Command Code, OpenCode GO, ZAI, Synthetic, MiniMax Token Plan, Qwen, and Alibaba Token Plan usage limits in the sidebar and prompt footer.
OpenCode TUI plugin that shows Codex, DeepSeek, Novita AI, Command Code, OpenCode GO, ZAI, Synthetic, MiniMax Token Plan, Qwen, and Alibaba Token Plan usage limits in the sidebar and prompt footer.

## Features

- Adds a `Usage Limits` block under the sidebar `Context` section.
- Shows current Codex usage windows from OpenAI/Codex auth.
- Shows current DeepSeek currency balances from the official balance API.
- Shows the current available Novita AI API balance from its official billing API.
- Shows current ZAI quota windows from ZAI Coding Plan auth.
- Shows current Synthetic rolling 5-hour and weekly windows.
- Shows current MiniMax Token Plan rolling 5-hour and weekly windows.
- Shows current Qwen Token Plan windows from the local `qwencloud` CLI.
- Shows current Alibaba Token Plan 5-hour and weekly windows from the local `bl` CLI.
- Shows current OpenCode GO rolling, weekly, and monthly windows.
- Displays current Command Code 5-hour, weekly, and derived monthly credit usage.
- Adds compact prompt-footer usage when the current session uses an OpenAI, DeepSeek, Command Code, OpenCode GO, ZAI Coding Plan, Synthetic, MiniMax Token Plan, or Qwen Token Plan model.
- Adds compact prompt-footer usage when the current session uses an OpenAI, DeepSeek, Novita AI, Command Code, OpenCode GO, ZAI Coding Plan, Synthetic, MiniMax Token Plan, or Qwen Token Plan model.
- Providers are toggled from `~/.config/opencode/usage-limits.jsonc`.
- Reads OpenCode-connected credentials first, then falls back to explicit config/env credentials.

Expand Down Expand Up @@ -154,6 +155,7 @@ The response contract follows the official CLI's [`usage/token-plan.ts`](https:/
| --- | --- | --- | --- | --- |
| `codex` | ChatGPT Codex usage | — | Bearer | `https://chatgpt.com/backend-api` |
| `deepseek` | DeepSeek balances | `DEEPSEEK_API_KEY` | Bearer | `https://api.deepseek.com` |
| `novita-ai` | Novita AI available API balance (USD) | `NOVITA_API_KEY` | Bearer | `https://api.novita.ai` |
| `zai` | Z.AI Coding Plan quota | `OC_ZAI_API_KEY` | raw / Bearer | `https://api.z.ai` |
| `synthetic` | Synthetic quotas | `OC_SYNTHETIC_API_KEY` | Bearer | `https://api.synthetic.new` |
| `minimax` | MiniMax Token Plan | `OC_MINIMAX_TOKEN_PLAN_KEY` | Bearer | `https://www.minimax.io` |
Expand All @@ -168,6 +170,8 @@ Synthetic always uses `Bearer` auth and ignores `authorizationScheme`.

DeepSeek reads `GET https://api.deepseek.com/user/balance`. Each reported currency is displayed as an independent `credits` balance using `total_balance`; the component balances are not summed. OpenCode auth is used only for the exact official DeepSeek origin. A custom `baseUrl` requires an explicit `authPath` or `apiKey`, including `{env:DEEPSEEK_API_KEY}`.

Novita AI reads `GET https://api.novita.ai/openapi/v1/billing/balance/detail` and displays `availableBalance` as the remaining USD balance. API monetary values are strings in units of 1/10,000 USD (for example, `10000` is `$1.00`). The adapter does not derive the balance from cash, credit, debt, or invoice fields, and does not invent a percentage or reset time. OpenCode-discovered credentials are used only for the exact official Novita origin; a custom `baseUrl` requires an explicit `authPath` or `apiKey`, including `{env:NOVITA_API_KEY}`.

Set `baseUrl` on `minimax` to `https://api.minimaxi.com` when using the mainland-China region. MiniMax always uses `Bearer` auth and ignores `authorizationScheme`.

## Credential Lookup
Expand Down Expand Up @@ -206,6 +210,12 @@ DeepSeek lookup order:
2. OpenCode auth at `~/.local/share/opencode/auth.json`, provider `deepseek`.
3. Config `apiKey`, including `{env:DEEPSEEK_API_KEY}` references.

Novita AI lookup order:

1. Config `authPath` JSON file (`{ "key": "..." }` / `{ "apiKey": "..." }` / `{ "novita-ai": { "key": "..." } }`).
2. OpenCode auth at `~/.local/share/opencode/auth.json`, provider `novita-ai`.
3. Config `apiKey`, including `{env:NOVITA_API_KEY}` references.

Command Code lookup order:

1. Config `authPath` JSON file (`{ "key": "..." }` / `{ "apiKey": "..." }` / `{ "commandcode": { "key": "..." } }`).
Expand Down Expand Up @@ -234,6 +244,8 @@ MiniMax
DeepSeek
USD: $12.50 remaining
CNY: ¥0.00 remaining
Novita AI
balance: $100.00 remaining
```

Prompt footer shows compact usage when the current session model belongs to a supported provider:
Expand All @@ -248,6 +260,7 @@ Provider mapping:

- OpenCode provider `openai` -> Codex usage.
- OpenCode provider `deepseek` -> DeepSeek balance usage.
- OpenCode provider `novita-ai` -> Novita AI available-balance usage.
- OpenCode provider `zai-coding-plan` -> ZAI token usage.
- OpenCode provider `synthetic` -> Synthetic usage.
- OpenCode provider `minimax-coding-plan` -> MiniMax Token Plan usage (prompt footer); `minimax` is also accepted as an alias.
Expand Down
27 changes: 26 additions & 1 deletion packages/opencode-usage-limits/__tests__/config.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ interface PublishedSchema {
commonDisplayFields: PublishedProviderDefinition;
deepSeekProvider: PublishedProviderDefinition;
minimaxProvider: PublishedProviderDefinition;
novitaAiProvider: PublishedProviderDefinition;
openCodeGoProvider: PublishedProviderDefinition;
qwenProvider: PublishedProviderDefinition;
syntheticProvider: PublishedProviderDefinition;
Expand All @@ -65,6 +66,7 @@ interface PublishedSchema {
commandcode: { $ref: "#/$defs/commandCodeProvider" };
deepseek: { $ref: "#/$defs/deepSeekProvider" };
minimax: { $ref: "#/$defs/minimaxProvider" };
"novita-ai": { $ref: "#/$defs/novitaAiProvider" };
"opencode-go": { $ref: "#/$defs/openCodeGoProvider" };
qwen: { $ref: "#/$defs/qwenProvider" };
synthetic: { $ref: "#/$defs/syntheticProvider" };
Expand Down Expand Up @@ -99,6 +101,9 @@ describe("configuration parsing", () => {
minimax: Object.keys(
publishedSchema.$defs.minimaxProvider.properties
).toSorted(),
"novita-ai": Object.keys(
publishedSchema.$defs.novitaAiProvider.properties
).toSorted(),
"opencode-go": Object.keys(
publishedSchema.$defs.openCodeGoProvider.properties
).toSorted(),
Expand All @@ -117,6 +122,7 @@ describe("configuration parsing", () => {
commandcode: { $ref: "#/$defs/commandCodeProvider" },
deepseek: { $ref: "#/$defs/deepSeekProvider" },
minimax: { $ref: "#/$defs/minimaxProvider" },
"novita-ai": { $ref: "#/$defs/novitaAiProvider" },
"opencode-go": { $ref: "#/$defs/openCodeGoProvider" },
qwen: { $ref: "#/$defs/qwenProvider" },
synthetic: { $ref: "#/$defs/syntheticProvider" },
Expand All @@ -143,6 +149,7 @@ describe("configuration parsing", () => {
const apiKeyProviders = [
providerFields.commandcode,
providerFields.deepseek,
providerFields["novita-ai"],
providerFields.minimax,
providerFields["opencode-go"],
providerFields.synthetic,
Expand Down Expand Up @@ -203,19 +210,28 @@ describe("configuration parsing", () => {
enabled: true,
label: "DS",
},
"novita-ai": {
apiKey: "novita-secret",
authPath: "~/.config/opencode/auth.json",
baseUrl: "https://api.novita.ai",
enabled: true,
label: "Novita",
},
},
});

const success = Result.isSuccess(result) ? result.success : undefined;
const apiKey = success?.providers.codex?.apiKey;
const commandCodeApiKey = success?.providers.commandcode?.apiKey;
const deepSeekApiKey = success?.providers.deepseek?.apiKey;
const novitaAiApiKey = success?.providers["novita-ai"]?.apiKey;
expect(Result.isSuccess(result)).toBeTruthy();
expect([
Redacted.isRedacted(apiKey),
Redacted.isRedacted(commandCodeApiKey),
Redacted.isRedacted(deepSeekApiKey),
]).toStrictEqual([true, true, true]);
Redacted.isRedacted(novitaAiApiKey),
]).toStrictEqual([true, true, true, true]);
expect(String(apiKey)).not.toContain("do-not-log");
expect(success?.providers).toMatchObject({
codex: {
Expand All @@ -241,6 +257,12 @@ describe("configuration parsing", () => {
enabled: true,
label: "DS",
},
"novita-ai": {
authPath: "~/.config/opencode/auth.json",
baseUrl: "https://api.novita.ai",
enabled: true,
label: "Novita",
},
});
});

Expand Down Expand Up @@ -513,6 +535,7 @@ describe("configuration loading", () => {
minimax: { key: "minimax" },
"minimax-coding-plan": { apiKey: "coding" },
"minimax-token-plan": { key: "token-plan" },
"novita-ai": { apiKey: "novita-ai" },
openai: { accountId: "account" },
opencode: { key: "opencode" },
"opencode-go": { key: "go" },
Expand All @@ -530,6 +553,7 @@ describe("configuration loading", () => {
credentialValue(auth["opencode-go"]?.key),
credentialValue(auth.commandcode?.key),
credentialValue(auth.deepseek?.key),
credentialValue(auth["novita-ai"]?.apiKey),
credentialValue(auth.synthetic?.apiKey),
credentialValue(auth.zai?.key),
credentialValue(auth["zai-coding-plan"]?.key),
Expand All @@ -542,6 +566,7 @@ describe("configuration loading", () => {
"go",
"commandcode",
"deepseek",
"novita-ai",
"synthetic",
"zai",
"zai-plan",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ describe("provider manifest", () => {
expect([
["openai", pluginProviderForOpenCode("openai")],
["deepseek", pluginProviderForOpenCode("deepseek")],
["novita-ai", pluginProviderForOpenCode("novita-ai")],
["zai-coding-plan", pluginProviderForOpenCode("zai-coding-plan")],
["minimax-coding-plan", pluginProviderForOpenCode("minimax-coding-plan")],
["minimax", pluginProviderForOpenCode("minimax")],
Expand All @@ -61,6 +62,7 @@ describe("provider manifest", () => {
]).toStrictEqual([
["openai", "codex"],
["deepseek", "deepseek"],
["novita-ai", "novita-ai"],
["zai-coding-plan", "zai"],
["minimax-coding-plan", "minimax"],
["minimax", "minimax"],
Expand Down
Loading
Loading