opencode-cmd-provider 1.1.1 → 1.2.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/CHANGELOG.md +37 -0
- package/README.md +72 -32
- package/dist/src/catalog/facts.d.ts +4 -4
- package/dist/src/catalog/facts.js +6 -4
- package/dist/src/catalog/snapshot.js +2 -1
- package/dist/src/deals/catalog.d.ts +55 -0
- package/dist/src/deals/catalog.js +76 -0
- package/dist/src/deals/enrichment.d.ts +4 -0
- package/dist/src/deals/enrichment.js +63 -0
- package/dist/src/deals/index.d.ts +5 -0
- package/dist/src/deals/index.js +16 -0
- package/dist/src/deals/plan-summary.d.ts +14 -0
- package/dist/src/deals/plan-summary.js +160 -0
- package/dist/src/deals/tui.d.ts +10 -0
- package/dist/src/deals/tui.js +104 -0
- package/dist/src/deals/vendor.d.ts +1 -0
- package/dist/src/deals/vendor.js +29 -0
- package/dist/src/plugin/index.js +5 -0
- package/dist/src/provider/command-code-model.js +2 -9
- package/dist/src/provider/cost.d.ts +7 -0
- package/dist/src/provider/cost.js +14 -0
- package/dist/src/provider/reasoning.js +1 -0
- package/dist/src/provider/stream.js +3 -3
- package/dist/tui.js +180 -0
- package/package.json +13 -5
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,42 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.2.1 - 2026-08-21
|
|
4
|
+
|
|
5
|
+
Fix: `/connect` no longer lists Command Code — the plugin failed to load.
|
|
6
|
+
|
|
7
|
+
- The `cmd_plan_summary` tool runtime-imported `@opencode-ai/plugin`, an
|
|
8
|
+
optional peer dependency that `opencode plugin <pkg>` installs in
|
|
9
|
+
`.opencode/`, not next to the plugin. That import threw `ERR_MODULE_NOT_FOUND`
|
|
10
|
+
at load, silently killing the whole server plugin — no auto-registration and
|
|
11
|
+
no `/connect` entry (Command Code vanished from the provider list).
|
|
12
|
+
- The tool now builds its Zod args from a direct `zod` dependency instead of
|
|
13
|
+
`@opencode-ai/plugin`'s `tool()` helper, so the server bundle has zero
|
|
14
|
+
runtime imports of optional peers.
|
|
15
|
+
- Added a contract test that scans the built `dist/` and fails on any runtime
|
|
16
|
+
`@opencode-ai/*` import, so this class of regression can't ship again.
|
|
17
|
+
|
|
18
|
+
## 1.2.0 - 2026-08-21
|
|
19
|
+
|
|
20
|
+
Feature: Deals intelligence — per-model pricing/allowance data in a sidebar panel and a plan-summary tool, delivered zero-step.
|
|
21
|
+
|
|
22
|
+
### Features
|
|
23
|
+
|
|
24
|
+
- New Deals catalog (`src/deals/catalog.ts`): tier, benchmarks, deal discounts (`was`/`now` rates), peak/off-peak windows, and GOAT/Pro monthly allowances for every model, scraped from the Command Code docs.
|
|
25
|
+
- New `cmd_plan_summary` tool (plan-aware allowances and deal rates) and a TUI sidebar "Command Code" section for the selected model.
|
|
26
|
+
- Zero-step delivery: the package exports a `./tui` target (`dist/tui.js`), and `opencode plugin opencode-cmd-provider` writes both `opencode.json(c)` and `tui.json` from one command — no hand-written `tui.json` (fixes #39).
|
|
27
|
+
- Deals intelligence is an excisable slice (`src/deals/`); deleting it plus two lines in the plugin entry leaves Core (models, auth, streaming) unchanged.
|
|
28
|
+
- Visible degradation: when the Deals catalog is empty (scraping mitigated), the sidebar shows a "Deals unavailable" banner with placeholder rows and the tool reports no bundled data — Core is unaffected.
|
|
29
|
+
- `refresh:deals` no longer blocks a release: it falls back to fixtures or an empty catalog with a warning, and the Deals gate in the release workflow is non-blocking.
|
|
30
|
+
|
|
31
|
+
### Fixes
|
|
32
|
+
|
|
33
|
+
- Streaming usage now reports cache-inclusive input token totals (the AI SDK convention). OpenChamber showed context usage as low as ~0.1% instead of ~5% because the converter reported only non-cached input; `inputTokens.total` now includes cache read/write tokens (issue #36).
|
|
34
|
+
|
|
35
|
+
### Chores
|
|
36
|
+
|
|
37
|
+
- Catalog refresh to `command-code@1.31.0`, adding the free reasoning model `stealth/ox-alpha` (Ox Alpha).
|
|
38
|
+
- Docs: corrected the install command (`opencode plugin`, no `add`), simplified the README Install section, and added build/test/architecture guidance to `AGENTS.md`.
|
|
39
|
+
|
|
3
40
|
## 1.1.1 - 2026-08-20
|
|
4
41
|
|
|
5
42
|
Fix: generate image-input modalities from the Command Code CLI catalog.
|
package/README.md
CHANGED
|
@@ -9,7 +9,23 @@ A provider and plugin for [OpenCode](https://opencode.ai) that connects to the [
|
|
|
9
9
|
|
|
10
10
|
## Install
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
```sh
|
|
13
|
+
opencode plugin opencode-cmd-provider
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
Update an existing install:
|
|
17
|
+
|
|
18
|
+
```sh
|
|
19
|
+
opencode plugin opencode-cmd-provider --force
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Install globally (available in every project):
|
|
23
|
+
|
|
24
|
+
```sh
|
|
25
|
+
opencode plugin opencode-cmd-provider --global
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Manual config also works:
|
|
13
29
|
|
|
14
30
|
```jsonc
|
|
15
31
|
// opencode.json
|
|
@@ -19,28 +35,7 @@ Add the package to your OpenCode configuration:
|
|
|
19
35
|
}
|
|
20
36
|
```
|
|
21
37
|
|
|
22
|
-
|
|
23
|
-
auto-registers the `commandcode` provider — npm package, name, API key env var,
|
|
24
|
-
and every model — when OpenCode loads. No `provider.commandcode` block, no
|
|
25
|
-
`models` map, no network access needed. All Command Code models appear in
|
|
26
|
-
`/models` with the `[CMD]` display-name prefix (e.g. `[CMD] Claude Sonnet 5`) so
|
|
27
|
-
they aren't confused with same-named models from other providers.
|
|
28
|
-
|
|
29
|
-
You can still declare your own `provider.commandcode` entry to customize
|
|
30
|
-
behavior; your declarations always win and the snapshot fills in only what's
|
|
31
|
-
missing:
|
|
32
|
-
|
|
33
|
-
- Declared provider-level settings (`name`, `options`, `baseURL`, `env`) are
|
|
34
|
-
kept as you wrote them.
|
|
35
|
-
- Declared models stay exactly as you wrote them — the snapshot never modifies
|
|
36
|
-
or removes them, and models that left the catalog remain usable.
|
|
37
|
-
- `whitelist`/`blacklist` on a declared entry filter the auto-registered models
|
|
38
|
-
too, keeping the picker uncluttered.
|
|
39
|
-
|
|
40
|
-
The catalog snapshot is refreshed at every release; newly published Command
|
|
41
|
-
Code models appear after a plugin update.
|
|
42
|
-
|
|
43
|
-
Start or reload OpenCode, then authenticate:
|
|
38
|
+
Then authenticate:
|
|
44
39
|
|
|
45
40
|
```txt
|
|
46
41
|
/connect
|
|
@@ -101,12 +96,52 @@ Pick a model with `/models`, or run non-interactively:
|
|
|
101
96
|
opencode run --model commandcode/claude-sonnet-5 "hello"
|
|
102
97
|
```
|
|
103
98
|
|
|
99
|
+
## Deals intelligence
|
|
100
|
+
|
|
101
|
+
Every Command Code model ships with deal/allowance/benchmark intelligence
|
|
102
|
+
(bundled in `src/deals/catalog.ts`), scraped from the Command Code docs. It
|
|
103
|
+
surfaces in two places:
|
|
104
|
+
|
|
105
|
+
- **Sidebar** — in a session, the OpenCode sidebar (`ctrl+x b`) shows a
|
|
106
|
+
`Command Code` section for the selected model: tier, GOAT/Pro allowance,
|
|
107
|
+
benchmark (intelligence, tok/s), deal discounts (`was`/`now` rates), and
|
|
108
|
+
peak/off-peak windows.
|
|
109
|
+
- **`cmd_plan_summary` tool** — plan-aware allowances and deal rates, to
|
|
110
|
+
estimate monthly requests.
|
|
111
|
+
|
|
112
|
+
Delivery is zero-step: the package exports both a `server` and a `tui` target
|
|
113
|
+
(`exports["./tui"]` → `dist/tui.js`), and `opencode plugin
|
|
114
|
+
opencode-cmd-provider` writes both `opencode.json(c)` and `tui.json` from one
|
|
115
|
+
spec. The sidebar is a TUI plugin loaded from `tui.json` — which is why
|
|
116
|
+
installs made before the `./tui` export only wrote the server target and never
|
|
117
|
+
showed a sidebar. Re-run with `--force` to add `tui.json`.
|
|
118
|
+
|
|
119
|
+
When the Deals catalog is empty (scraping mitigated), the feature degrades
|
|
120
|
+
visibly rather than silently: the sidebar shows a `Deals unavailable` banner
|
|
121
|
+
with placeholder rows, and `cmd_plan_summary` reports that no deal data is
|
|
122
|
+
bundled. Core (models, auth, streaming) is unaffected.
|
|
123
|
+
|
|
104
124
|
## Model discovery and offline behavior
|
|
105
125
|
|
|
106
|
-
The plugin ships
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
126
|
+
The plugin ships two bundled catalogs and auto-registers every model into
|
|
127
|
+
OpenCode's config at startup, with the `[CMD]` display-name prefix (e.g.
|
|
128
|
+
`[CMD] Claude Sonnet 5`) so they aren't confused with same-named models from
|
|
129
|
+
other providers. Model availability changes when the package is updated. You
|
|
130
|
+
can still declare your own `provider.commandcode` entry; your declarations
|
|
131
|
+
always win and the snapshot fills in only what's missing (`whitelist`/
|
|
132
|
+
`blacklist` on a declared entry filter the auto-registered models too).
|
|
133
|
+
|
|
134
|
+
- Snapshot — `src/catalog/snapshot.ts` (model ids, names, context lengths) plus
|
|
135
|
+
`src/catalog/facts.ts` (reasoning efforts, per-1M-token rates, input
|
|
136
|
+
modalities). Regenerated from the live catalog via `npm run refresh:snapshot`.
|
|
137
|
+
- Deals — `src/deals/catalog.ts` (per-model tier, benchmarks, deal
|
|
138
|
+
discounts, `was`/`now` rates, peak/off-peak windows, and GOAT/Pro monthly
|
|
139
|
+
allowances for every model). Scraped from the Command Code docs
|
|
140
|
+
(`pricing-limits`, `plans/goat`, `plans/pro`; fixtures in
|
|
141
|
+
`tests/fixtures/*.html`) via `npm run refresh:deals` (add `-- --fixtures` to
|
|
142
|
+
regenerate offline from fixtures; without it, fetches live pages).
|
|
143
|
+
|
|
144
|
+
Run `npm run refresh` to regenerate both catalogs at once (offline from fixtures).
|
|
110
145
|
|
|
111
146
|
Auto-registration adds no network latency to OpenCode startup — the snapshot is
|
|
112
147
|
bundled, and the plugin never contacts the Command Code API to list models. The
|
|
@@ -116,11 +151,14 @@ endpoint being reachable.
|
|
|
116
151
|
The following environment variables are intended for tests, local mocks, and
|
|
117
152
|
compatible API endpoints:
|
|
118
153
|
|
|
119
|
-
| Variable
|
|
120
|
-
|
|
|
121
|
-
| `COMMANDCODE_API_BASE`
|
|
122
|
-
| `COMMANDCODE_FACTS_URL`
|
|
123
|
-
| `COMMANDCODE_MODALITIES_URL`
|
|
154
|
+
| Variable | Purpose |
|
|
155
|
+
| ------------------------------- | -------------------------------------- |
|
|
156
|
+
| `COMMANDCODE_API_BASE` | Override the Command Code API base URL |
|
|
157
|
+
| `COMMANDCODE_FACTS_URL` | Override the bundled `models.md` URL |
|
|
158
|
+
| `COMMANDCODE_MODALITIES_URL` | Override the CLI bundle URL |
|
|
159
|
+
| `COMMANDCODE_DEALS_PRICING_URL` | Override the `pricing-limits` docs URL |
|
|
160
|
+
| `COMMANDCODE_DEALS_GOAT_URL` | Override the `plans/goat` docs URL |
|
|
161
|
+
| `COMMANDCODE_DEALS_PRO_URL` | Override the `plans/pro` docs URL |
|
|
124
162
|
|
|
125
163
|
### Reasoning support
|
|
126
164
|
|
|
@@ -155,6 +193,8 @@ npm test
|
|
|
155
193
|
npm run format:check
|
|
156
194
|
```
|
|
157
195
|
|
|
196
|
+
`npm run build` needs [bun](https://bun.sh) on PATH — it compiles `src/deals/tui.tsx` with the solid transform (`scripts/build-tui.ts`), so the TUI panel's JSX props are reactive and the sidebar repaints on mid-session model switches.
|
|
197
|
+
|
|
158
198
|
The headless end-to-end test runs the real OpenCode CLI against a mock Command Code server through the built package:
|
|
159
199
|
|
|
160
200
|
```sh
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
export declare const FACTS_SOURCE_URL = "https://unpkg.com/command-code@1.
|
|
2
|
-
export declare const MODALITIES_SOURCE_URL = "https://unpkg.com/command-code@1.
|
|
3
|
-
export declare const FACTS_PACKAGE_VERSION = "1.
|
|
4
|
-
export declare const FACTS_LAST_REFRESHED = "2026-08-
|
|
1
|
+
export declare const FACTS_SOURCE_URL = "https://unpkg.com/command-code@1.31.0/dist/bundled/command-code-knowledge/reference/models.md";
|
|
2
|
+
export declare const MODALITIES_SOURCE_URL = "https://unpkg.com/command-code@1.31.0/dist/cli.mjs";
|
|
3
|
+
export declare const FACTS_PACKAGE_VERSION = "1.31.0";
|
|
4
|
+
export declare const FACTS_LAST_REFRESHED = "2026-08-21";
|
|
5
5
|
export declare const MODEL_EFFORTS: Readonly<Record<string, readonly string[]>>;
|
|
6
6
|
export declare const MODEL_COSTS: Readonly<Record<string, {
|
|
7
7
|
input: number;
|
|
@@ -4,10 +4,10 @@
|
|
|
4
4
|
// command-code npm package's bundled model catalog (models.md), plus input
|
|
5
5
|
// modalities parsed from its CLI bundle (dist/cli.mjs). Regenerate
|
|
6
6
|
// with `npm run refresh:snapshot`.
|
|
7
|
-
export const FACTS_SOURCE_URL = "https://unpkg.com/command-code@1.
|
|
8
|
-
export const MODALITIES_SOURCE_URL = "https://unpkg.com/command-code@1.
|
|
9
|
-
export const FACTS_PACKAGE_VERSION = "1.
|
|
10
|
-
export const FACTS_LAST_REFRESHED = "2026-08-
|
|
7
|
+
export const FACTS_SOURCE_URL = "https://unpkg.com/command-code@1.31.0/dist/bundled/command-code-knowledge/reference/models.md";
|
|
8
|
+
export const MODALITIES_SOURCE_URL = "https://unpkg.com/command-code@1.31.0/dist/cli.mjs";
|
|
9
|
+
export const FACTS_PACKAGE_VERSION = "1.31.0";
|
|
10
|
+
export const FACTS_LAST_REFRESHED = "2026-08-21";
|
|
11
11
|
export const MODEL_EFFORTS = {
|
|
12
12
|
"deepseek/deepseek-v4-pro": ["high", "max"],
|
|
13
13
|
"deepseek/deepseek-v4-flash": ["high", "max"],
|
|
@@ -69,6 +69,7 @@ export const MODEL_COSTS = {
|
|
|
69
69
|
"thinkingmachines/inkling": { input: 1, output: 4.05, cacheRead: 0.17, cacheWrite: 0 },
|
|
70
70
|
"thinkingmachines/inkling-small": { input: 0.5, output: 1.2, cacheRead: 0.1, cacheWrite: 0 },
|
|
71
71
|
"poolside/laguna-s-2.1-free": { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
72
|
+
"stealth/ox-alpha": { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
72
73
|
"claude-sonnet-5": { input: 2, output: 10, cacheRead: 0.2, cacheWrite: 2.5 },
|
|
73
74
|
"claude-sonnet-4-6": { input: 3, output: 15, cacheRead: 0.3, cacheWrite: 3.75 },
|
|
74
75
|
"claude-fable-5": { input: 10, output: 50, cacheRead: 1, cacheWrite: 12.5 },
|
|
@@ -130,6 +131,7 @@ export const MODEL_INPUT_MODALITIES = {
|
|
|
130
131
|
"moonshotai/Kimi-K2.7-Code-Highspeed": ["text", "image"],
|
|
131
132
|
"moonshotai/Kimi-K3": ["text", "image"],
|
|
132
133
|
"sakana/fugu-ultra": ["text", "image"],
|
|
134
|
+
"stealth/ox-alpha": ["text", "image"],
|
|
133
135
|
"stepfun/Step-3.7-Flash": ["text", "image"],
|
|
134
136
|
"thinkingmachines/inkling": ["text", "image"],
|
|
135
137
|
"thinkingmachines/inkling-small": ["text", "image"],
|
|
@@ -14,7 +14,7 @@ export const MODEL_SNAPSHOT = [
|
|
|
14
14
|
{ id: "gpt-5.6-sol", name: "GPT-5.6 Sol", contextLength: 1050000 },
|
|
15
15
|
{ id: "gpt-5.6-terra", name: "GPT-5.6 Terra", contextLength: 1050000 },
|
|
16
16
|
{ id: "gpt-5.6-luna", name: "GPT-5.6 Luna", contextLength: 1050000 },
|
|
17
|
-
{ id: "gpt-5.5", name: "GPT-5.5", contextLength:
|
|
17
|
+
{ id: "gpt-5.5", name: "GPT-5.5", contextLength: 400000 },
|
|
18
18
|
{ id: "gpt-5.4", name: "GPT-5.4", contextLength: 400000 },
|
|
19
19
|
{ id: "gpt-5.3-codex", name: "GPT-5.3 Codex", contextLength: 400000 },
|
|
20
20
|
{ id: "gpt-5.4-mini", name: "GPT-5.4 Mini", contextLength: 400000 },
|
|
@@ -54,6 +54,7 @@ export const MODEL_SNAPSHOT = [
|
|
|
54
54
|
{ id: "nvidia/nemotron-3-ultra-550b-a55b", name: "Nemotron 3 Ultra", contextLength: 1000000 },
|
|
55
55
|
{ id: "thinkingmachines/inkling", name: "Inkling", contextLength: 256000 },
|
|
56
56
|
{ id: "thinkingmachines/inkling-small", name: "Inkling Small", contextLength: 1000000 },
|
|
57
|
+
{ id: "stealth/ox-alpha", name: "Ox Alpha", contextLength: 1048576 },
|
|
57
58
|
{ id: "poolside/laguna-s-2.1-free", name: "Laguna S 2.1", contextLength: 256000 },
|
|
58
59
|
{ id: "meta/muse-spark-1.1", name: "Muse Spark 1.1", contextLength: 1048576 },
|
|
59
60
|
{ id: "meta/muse-spark-1.2", name: "Muse Spark 1.2", contextLength: 1048576 },
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
export type PlanId = "go" | "goat" | "pro" | "max" | "max20" | "teampro" | "provider";
|
|
2
|
+
export interface DealRates {
|
|
3
|
+
input: number;
|
|
4
|
+
output: number;
|
|
5
|
+
cacheRead: number;
|
|
6
|
+
cacheWrite: number;
|
|
7
|
+
}
|
|
8
|
+
export interface PlanInfo {
|
|
9
|
+
price: number;
|
|
10
|
+
credits: number;
|
|
11
|
+
window5h: number;
|
|
12
|
+
windowWeek: number;
|
|
13
|
+
/** Human-facing plan name as shown on the Command Code pricing page. */
|
|
14
|
+
display: string;
|
|
15
|
+
}
|
|
16
|
+
export interface ModelDeals {
|
|
17
|
+
/** $/month credit allowance per plan. Absent plan = no data. */
|
|
18
|
+
allowance?: Partial<Record<PlanId, number>>;
|
|
19
|
+
/** Deal metadata. endsAt: ISO date or "while capacity lasts". */
|
|
20
|
+
discount?: {
|
|
21
|
+
pct: number;
|
|
22
|
+
endsAt?: string;
|
|
23
|
+
};
|
|
24
|
+
/** Pre-deal list prices (for reference). */
|
|
25
|
+
was?: {
|
|
26
|
+
input: number;
|
|
27
|
+
output: number;
|
|
28
|
+
cacheRead: number;
|
|
29
|
+
};
|
|
30
|
+
/** Discounted deal prices (what you pay now). */
|
|
31
|
+
now?: {
|
|
32
|
+
input: number;
|
|
33
|
+
output: number;
|
|
34
|
+
cacheRead: number;
|
|
35
|
+
};
|
|
36
|
+
/** Time-varying rates (DeepSeek V4 peak/off-peak). */
|
|
37
|
+
peakOffPeak?: {
|
|
38
|
+
peak: DealRates;
|
|
39
|
+
offPeak: DealRates;
|
|
40
|
+
windows: string;
|
|
41
|
+
};
|
|
42
|
+
/** Higher-context tier rates (docs: MiniMax M3 >512K). */
|
|
43
|
+
overContext?: DealRates;
|
|
44
|
+
benchmark?: {
|
|
45
|
+
intelligence?: number;
|
|
46
|
+
tokPerSec?: number;
|
|
47
|
+
};
|
|
48
|
+
tier?: "opensource" | "premium";
|
|
49
|
+
free: boolean;
|
|
50
|
+
}
|
|
51
|
+
export declare const MODEL_DEALS: Readonly<Record<string, ModelDeals>>;
|
|
52
|
+
export declare const PLAN_CATALOG: Readonly<Record<PlanId, PlanInfo>>;
|
|
53
|
+
export declare const DEAL_SOURCE_URL = "https://commandcode.ai/docs/resources/pricing-limits";
|
|
54
|
+
export declare const DEAL_LAST_REFRESHED = "2026-08-21";
|
|
55
|
+
export declare const DEAL_PACKAGE_VERSION = "docs";
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
// src/deals/catalog.ts — GENERATED by scripts/refresh-deals.mjs. Do not edit.
|
|
2
|
+
//
|
|
3
|
+
// Deal/allowance/benchmark intelligence parsed from the Command Code docs
|
|
4
|
+
// site (pricing-limits, plans/goat, plans/pro). Bundled so the plugin can
|
|
5
|
+
// enrich the model picker, the sidebar panel, and the plan summary tool
|
|
6
|
+
// without network access at runtime. Regenerate with `npm run refresh:deals`.
|
|
7
|
+
export const MODEL_DEALS = {
|
|
8
|
+
"claude-fable-5": { tier: "premium", benchmark: { "intelligence": 62.1, "tokPerSec": 67.5 }, free: false },
|
|
9
|
+
"claude-haiku-4-5-20251001": { tier: "premium", benchmark: { "intelligence": 24.1, "tokPerSec": 101.1 }, free: false, allowance: { "pro": 20 } },
|
|
10
|
+
"claude-opus-4-7": { tier: "premium", benchmark: { "intelligence": 55 }, free: false },
|
|
11
|
+
"claude-opus-4-8": { tier: "premium", benchmark: { "intelligence": 57.3 }, free: false },
|
|
12
|
+
"claude-opus-5": { tier: "premium", benchmark: { "intelligence": 63.1, "tokPerSec": 54.4 }, free: false },
|
|
13
|
+
"claude-sonnet-4-6": { tier: "premium", benchmark: { "intelligence": 48.4 }, free: false, allowance: { "pro": 20 } },
|
|
14
|
+
"claude-sonnet-5": { tier: "premium", benchmark: { "intelligence": 55.3, "tokPerSec": 81.9 }, free: false, allowance: { "pro": 20 } },
|
|
15
|
+
"deepseek/deepseek-v4-flash": { tier: "opensource", benchmark: { "intelligence": 51.8, "tokPerSec": 114.6 }, peakOffPeak: { "peak": { "input": 0.44, "output": 1.32, "cacheRead": 0.014, "cacheWrite": 0 }, "offPeak": { "input": 0.22, "output": 0.66, "cacheRead": 0.007, "cacheWrite": 0 }, "windows": "01–04 & 06–10 UTC" }, free: false, allowance: { "goat": 60, "pro": 70 } },
|
|
16
|
+
"deepseek/deepseek-v4-pro": { tier: "opensource", benchmark: { "intelligence": 53.2, "tokPerSec": 75.3 }, peakOffPeak: { "peak": { "input": 1.32, "output": 3.96, "cacheRead": 0.044, "cacheWrite": 0 }, "offPeak": { "input": 0.66, "output": 1.98, "cacheRead": 0.022, "cacheWrite": 0 }, "windows": "01–04 & 06–10 UTC" }, free: false, allowance: { "goat": 20, "pro": 30 } },
|
|
17
|
+
"google/gemini-3.1-flash-lite": { tier: "premium", benchmark: { "intelligence": 25.6 }, free: false, allowance: { "pro": 20 } },
|
|
18
|
+
"google/gemini-3.5-flash": { tier: "premium", benchmark: { "intelligence": 52 }, free: false, allowance: { "pro": 20 } },
|
|
19
|
+
"google/gemini-3.5-flash-lite": { tier: "premium", benchmark: { "intelligence": 37.4, "tokPerSec": 361.2 }, free: false, allowance: { "pro": 20 } },
|
|
20
|
+
"google/gemini-3.6-flash": { tier: "premium", benchmark: { "intelligence": 51.6, "tokPerSec": 187.4 }, free: false, allowance: { "pro": 20 } },
|
|
21
|
+
"google/gemini-3.7-flash": { tier: "opensource", discount: { "pct": 50, "endsAt": "2026-12-31" }, was: { "input": 1.5, "output": 7.5, "cacheRead": 0.15 }, now: { "input": 0.75, "output": 3.75, "cacheRead": 0.075 }, benchmark: { "intelligence": 56, "tokPerSec": 339.4 }, free: false, allowance: { "goat": 40, "pro": 60 } },
|
|
22
|
+
"gpt-5.3-codex": { tier: "premium", benchmark: { "intelligence": 45.5, "tokPerSec": 132.3 }, free: false, allowance: { "pro": 20 } },
|
|
23
|
+
"gpt-5.4": { tier: "premium", benchmark: { "intelligence": 53.1 }, free: false, allowance: { "pro": 20 } },
|
|
24
|
+
"gpt-5.4-mini": { tier: "premium", benchmark: { "intelligence": 40.9 }, free: false, allowance: { "pro": 20 } },
|
|
25
|
+
"gpt-5.5": { tier: "premium", benchmark: { "intelligence": 56.3 }, free: false, allowance: { "pro": 20 } },
|
|
26
|
+
"gpt-5.6-luna": { tier: "opensource", benchmark: { "intelligence": 52.3, "tokPerSec": 165 }, overContext: { "input": 0.4, "output": 1.8, "cacheRead": 0.04, "cacheWrite": 0.5 }, free: false, allowance: { "goat": 20, "pro": 30 } },
|
|
27
|
+
"gpt-5.6-sol": { tier: "opensource", benchmark: { "intelligence": 60.9, "tokPerSec": 68.7 }, overContext: { "input": 10, "output": 45, "cacheRead": 1, "cacheWrite": 12.5 }, free: false, allowance: { "goat": 70, "pro": 80 } },
|
|
28
|
+
"gpt-5.6-terra": { tier: "premium", benchmark: { "intelligence": 56.6, "tokPerSec": 117.1 }, overContext: { "input": 4, "output": 18, "cacheRead": 0.4, "cacheWrite": 5 }, free: false, allowance: { "pro": 20 } },
|
|
29
|
+
"meta/muse-spark-1.1": { tier: "premium", benchmark: { "intelligence": 53.2 }, free: false, allowance: { "pro": 20 } },
|
|
30
|
+
"meta/muse-spark-1.2": { tier: "opensource", benchmark: { "intelligence": 56.8 }, free: false, allowance: { "goat": 20, "pro": 30 } },
|
|
31
|
+
"meta/muse-spark-1.2-contributor": { tier: "opensource", benchmark: { "intelligence": 56.8 }, free: false, allowance: { "goat": 20, "pro": 30 } },
|
|
32
|
+
"MiniMaxAI/MiniMax-M2.5": { tier: "opensource", benchmark: { "intelligence": 34.5 }, free: false },
|
|
33
|
+
"MiniMaxAI/MiniMax-M2.7": { tier: "opensource", benchmark: { "intelligence": 38.9 }, free: false },
|
|
34
|
+
"MiniMaxAI/MiniMax-M3": { tier: "opensource", discount: { "pct": 50 }, was: { "input": 0.6, "output": 2.4, "cacheRead": 0.12 }, now: { "input": 0.3, "output": 1.2, "cacheRead": 0.06 }, benchmark: { "intelligence": 45.4, "tokPerSec": 104.4 }, free: false, allowance: { "goat": 47, "pro": 57 } },
|
|
35
|
+
"moonshotai/Kimi-K2.5": { tier: "opensource", benchmark: { "intelligence": 36 }, free: false },
|
|
36
|
+
"moonshotai/Kimi-K2.6": { tier: "opensource", benchmark: { "intelligence": 45.1 }, free: false },
|
|
37
|
+
"moonshotai/Kimi-K2.7-Code": { tier: "opensource", benchmark: { "intelligence": 43, "tokPerSec": 38.2 }, free: false, allowance: { "goat": 60, "pro": 70 } },
|
|
38
|
+
"moonshotai/Kimi-K2.7-Code-Highspeed": { tier: "opensource", free: false, allowance: { "goat": 20, "pro": 30 } },
|
|
39
|
+
"moonshotai/Kimi-K3": { tier: "opensource", benchmark: { "intelligence": 59.7, "tokPerSec": 38.6 }, free: false, allowance: { "goat": 20, "pro": 30 } },
|
|
40
|
+
"nvidia/nemotron-3-ultra-550b-a55b": { tier: "opensource", benchmark: { "intelligence": 38.3, "tokPerSec": 135.8 }, free: false, allowance: { "goat": 20, "pro": 30 } },
|
|
41
|
+
"poolside/laguna-s-2.1-free": { tier: "opensource", free: true },
|
|
42
|
+
"Qwen/Qwen3.6-Max-Preview": { tier: "opensource", benchmark: { "intelligence": 41.1 }, free: false },
|
|
43
|
+
"Qwen/Qwen3.6-Plus": { tier: "opensource", benchmark: { "intelligence": 40.5 }, overContext: { "input": 2, "output": 6, "cacheRead": 0.2, "cacheWrite": 0 }, free: false, allowance: { "goat": 33, "pro": 43 } },
|
|
44
|
+
"Qwen/Qwen3.7-Flash": { tier: "opensource", overContext: { "input": 0.2, "output": 0.8, "cacheRead": 0.04, "cacheWrite": 0.25 }, free: false },
|
|
45
|
+
"Qwen/Qwen3.7-Max": { tier: "opensource", benchmark: { "intelligence": 46.7 }, free: false, allowance: { "goat": 33, "pro": 43 } },
|
|
46
|
+
"Qwen/Qwen3.7-Plus": { tier: "opensource", benchmark: { "intelligence": 39.4, "tokPerSec": 55.6 }, overContext: { "input": 1.2, "output": 4.8, "cacheRead": 0.24, "cacheWrite": 1.5 }, free: false, allowance: { "goat": 33, "pro": 43 } },
|
|
47
|
+
"Qwen/Qwen3.8-27B": { tier: "opensource", benchmark: { "intelligence": 52 }, free: false, allowance: { "goat": 70, "pro": 80 } },
|
|
48
|
+
"Qwen/Qwen3.8-Max": { tier: "opensource", benchmark: { "intelligence": 58.1, "tokPerSec": 46.7 }, free: false, allowance: { "goat": 20, "pro": 30 } },
|
|
49
|
+
"sakana/fugu-ultra": { tier: "premium", free: false },
|
|
50
|
+
"stepfun/Step-3.5-Flash": { tier: "opensource", benchmark: { "intelligence": 26.5 }, free: false, allowance: { "goat": 20, "pro": 30 } },
|
|
51
|
+
"stepfun/Step-3.7-Flash": { tier: "opensource", benchmark: { "intelligence": 30.9, "tokPerSec": 120.1 }, free: false, allowance: { "goat": 20, "pro": 30 } },
|
|
52
|
+
"tencent/hy3-paid": { tier: "opensource", benchmark: { "intelligence": 42.2, "tokPerSec": 74.8 }, free: false, allowance: { "goat": 70, "pro": 80 } },
|
|
53
|
+
"thinkingmachines/inkling": { tier: "opensource", benchmark: { "intelligence": 42.3, "tokPerSec": 69.8 }, free: false, allowance: { "goat": 20, "pro": 30 } },
|
|
54
|
+
"thinkingmachines/inkling-small": { tier: "opensource", benchmark: { "intelligence": 41.2, "tokPerSec": 61.3 }, free: false, allowance: { "goat": 20, "pro": 30 } },
|
|
55
|
+
"xai/grok-4.5": { tier: "opensource", benchmark: { "intelligence": 55.8, "tokPerSec": 51 }, free: false, allowance: { "goat": 20, "pro": 30 } },
|
|
56
|
+
"xai/grok-4.6": { tier: "opensource", benchmark: { "intelligence": 60.9, "tokPerSec": 66 }, overContext: { "input": 4, "output": 12, "cacheRead": 1, "cacheWrite": 0 }, free: false, allowance: { "goat": 20, "pro": 30 } },
|
|
57
|
+
"xiaomi/mimo-v2.5": { tier: "opensource", discount: { "pct": 98 }, was: { "input": 0.8, "output": 4, "cacheRead": 0.16 }, now: { "input": 0.14, "output": 0.28, "cacheRead": 0.0028 }, benchmark: { "intelligence": 38, "tokPerSec": 61.8 }, free: false, allowance: { "goat": 30, "pro": 40 } },
|
|
58
|
+
"xiaomi/mimo-v2.5-pro": { tier: "opensource", discount: { "pct": 99 }, was: { "input": 2, "output": 6, "cacheRead": 0.4 }, now: { "input": 0.435, "output": 0.87, "cacheRead": 0.0036 }, benchmark: { "intelligence": 42.9, "tokPerSec": 58.1 }, free: false, allowance: { "goat": 20, "pro": 30 } },
|
|
59
|
+
"zai-org/GLM-5": { tier: "opensource", benchmark: { "intelligence": 40.6 }, free: false },
|
|
60
|
+
"zai-org/GLM-5.1": { tier: "opensource", benchmark: { "intelligence": 41, "tokPerSec": 28.2 }, free: false },
|
|
61
|
+
"zai-org/GLM-5.2": { tier: "opensource", benchmark: { "intelligence": 52.6, "tokPerSec": 69 }, free: false, allowance: { "goat": 70, "pro": 80 } },
|
|
62
|
+
"zai-org/GLM-5.2-Fast": { tier: "opensource", free: false, allowance: { "goat": 20, "pro": 30 } },
|
|
63
|
+
"zai-org/GLM-5.3": { tier: "opensource", benchmark: { "intelligence": 59.5, "tokPerSec": 79.6 }, free: false, allowance: { "goat": 20, "pro": 30 } },
|
|
64
|
+
};
|
|
65
|
+
export const PLAN_CATALOG = {
|
|
66
|
+
go: { price: 1, credits: 10, window5h: 3, windowWeek: 6, display: "Go" },
|
|
67
|
+
goat: { price: 10, credits: 70, window5h: 14, windowWeek: 35, display: "GOAT" },
|
|
68
|
+
pro: { price: 20, credits: 80, window5h: 16, windowWeek: 40, display: "Pro" },
|
|
69
|
+
max: { price: 100, credits: 150, window5h: 45, windowWeek: 90, display: "Max 10×" },
|
|
70
|
+
max20: { price: 200, credits: 300, window5h: 90, windowWeek: 180, display: "Max 20×" },
|
|
71
|
+
teampro: { price: 40, credits: 40, window5h: 12, windowWeek: 24, display: "Team Pro" },
|
|
72
|
+
provider: { price: 15, credits: 0, window5h: 0, windowWeek: 0, display: "Provider" },
|
|
73
|
+
};
|
|
74
|
+
export const DEAL_SOURCE_URL = "https://commandcode.ai/docs/resources/pricing-limits";
|
|
75
|
+
export const DEAL_LAST_REFRESHED = "2026-08-21";
|
|
76
|
+
export const DEAL_PACKAGE_VERSION = "docs";
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { Config } from "@opencode-ai/sdk/v2";
|
|
2
|
+
import { type ModelDeals } from "./catalog.js";
|
|
3
|
+
export declare function enrichCommandCodeModels(config: Config, deals?: Readonly<Record<string, ModelDeals>>): void;
|
|
4
|
+
export declare function buildCmdOptions(deals: ModelDeals): Record<string, unknown>;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { MODEL_DEALS } from "./catalog.js";
|
|
2
|
+
import { vendorFamilyForModel } from "./vendor.js";
|
|
3
|
+
export function enrichCommandCodeModels(config, deals = MODEL_DEALS) {
|
|
4
|
+
const provider = config.provider?.["commandcode"];
|
|
5
|
+
if (!provider?.models)
|
|
6
|
+
return;
|
|
7
|
+
const isEmpty = Object.keys(deals).length === 0;
|
|
8
|
+
for (const [modelId, model] of Object.entries(provider.models)) {
|
|
9
|
+
if (!model)
|
|
10
|
+
continue;
|
|
11
|
+
if (model.family === undefined) {
|
|
12
|
+
const family = vendorFamilyForModel(modelId);
|
|
13
|
+
if (family !== undefined)
|
|
14
|
+
model.family = family;
|
|
15
|
+
}
|
|
16
|
+
const entry = deals[modelId];
|
|
17
|
+
if (!entry) {
|
|
18
|
+
if (isEmpty && model.options?.["cmd"] === undefined) {
|
|
19
|
+
model.options ??= {};
|
|
20
|
+
model.options["cmd"] = { unavailable: true };
|
|
21
|
+
}
|
|
22
|
+
continue;
|
|
23
|
+
}
|
|
24
|
+
if (model.options?.["cmd"] === undefined) {
|
|
25
|
+
model.options ??= {};
|
|
26
|
+
model.options["cmd"] = buildCmdOptions(entry);
|
|
27
|
+
}
|
|
28
|
+
if (entry.overContext && model.cost?.["context_over_200k"] === undefined) {
|
|
29
|
+
const c = entry.overContext;
|
|
30
|
+
// Only the higher-context tier is carried here; the SDK `cost` type
|
|
31
|
+
// requires base input/output, so the unset branch is cast.
|
|
32
|
+
model.cost ??= {};
|
|
33
|
+
model.cost["context_over_200k"] = {
|
|
34
|
+
input: c.input,
|
|
35
|
+
output: c.output,
|
|
36
|
+
cache_read: c.cacheRead,
|
|
37
|
+
cache_write: c.cacheWrite,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
export function buildCmdOptions(deals) {
|
|
43
|
+
const out = {};
|
|
44
|
+
if (deals.tier !== undefined)
|
|
45
|
+
out.tier = deals.tier;
|
|
46
|
+
if (deals.allowance !== undefined && Object.keys(deals.allowance).length > 0) {
|
|
47
|
+
out.allowance = deals.allowance;
|
|
48
|
+
}
|
|
49
|
+
if (deals.discount !== undefined)
|
|
50
|
+
out.discount = deals.discount;
|
|
51
|
+
if (deals.was !== undefined)
|
|
52
|
+
out.was = deals.was;
|
|
53
|
+
if (deals.now !== undefined)
|
|
54
|
+
out.now = deals.now;
|
|
55
|
+
if (deals.benchmark !== undefined)
|
|
56
|
+
out.benchmark = deals.benchmark;
|
|
57
|
+
if (deals.peakOffPeak !== undefined)
|
|
58
|
+
out.peakOffPeak = deals.peakOffPeak;
|
|
59
|
+
if (deals.overContext !== undefined)
|
|
60
|
+
out.overContext = deals.overContext;
|
|
61
|
+
out.free = deals.free;
|
|
62
|
+
return out;
|
|
63
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { MODEL_DEALS, PLAN_CATALOG, DEAL_SOURCE_URL, DEAL_LAST_REFRESHED, DEAL_PACKAGE_VERSION, } from "./catalog.js";
|
|
2
|
+
export type { ModelDeals, PlanId, PlanInfo, DealRates } from "./catalog.js";
|
|
3
|
+
export { vendorFamilyForModel } from "./vendor.js";
|
|
4
|
+
export { enrichCommandCodeModels, buildCmdOptions } from "./enrichment.js";
|
|
5
|
+
export { planSummaryTool, renderPlanSummary, resolvePlan, normalizePlan } from "./plan-summary.js";
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// src/deals/index.ts — server-side public entry for Deals intelligence
|
|
2
|
+
// (catalog + enrichment + cmd_plan_summary tool). The TUI panel is a separate
|
|
3
|
+
// host and is reached via tui.ts → src/deals/tui.tsx, never from the server
|
|
4
|
+
// barrel, so the provider process does not load solid-js/@opentui. Deleting
|
|
5
|
+
// this folder plus the two registration lines in src/plugin/index.ts leaves
|
|
6
|
+
// Core (Snapshot, Auto-registration, provider/* streaming) byte-identical.
|
|
7
|
+
// Catalog
|
|
8
|
+
export { MODEL_DEALS, PLAN_CATALOG, DEAL_SOURCE_URL, DEAL_LAST_REFRESHED, DEAL_PACKAGE_VERSION, } from "./catalog.js";
|
|
9
|
+
// Vendor (used by enrichment, but exported for tests that import vendor directly
|
|
10
|
+
// from the deals deep module — keep as part of the slice so tests don't reach
|
|
11
|
+
// into src/plugin)
|
|
12
|
+
export { vendorFamilyForModel } from "./vendor.js";
|
|
13
|
+
// Enrichment (family + options.cmd + context_over_200k cost)
|
|
14
|
+
export { enrichCommandCodeModels, buildCmdOptions } from "./enrichment.js";
|
|
15
|
+
// Tool
|
|
16
|
+
export { planSummaryTool, renderPlanSummary, resolvePlan, normalizePlan } from "./plan-summary.js";
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { type ModelDeals, type PlanId, type PlanInfo } from "./catalog.js";
|
|
3
|
+
export declare function normalizePlan(value: unknown): PlanId | undefined;
|
|
4
|
+
export declare function resolvePlan(planArg: string | undefined, env?: NodeJS.ProcessEnv): Promise<PlanId>;
|
|
5
|
+
export declare function renderPlanSummary(plan: PlanId, deals?: Readonly<Record<string, ModelDeals>>, catalog?: Readonly<Record<PlanId, PlanInfo>>): string;
|
|
6
|
+
export declare function planSummaryTool(): {
|
|
7
|
+
description: string;
|
|
8
|
+
args: {
|
|
9
|
+
plan: z.ZodOptional<z.ZodString>;
|
|
10
|
+
};
|
|
11
|
+
execute: (args: {
|
|
12
|
+
plan?: string;
|
|
13
|
+
}) => Promise<string>;
|
|
14
|
+
};
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
// src/deals/plan-summary.ts — cmd_plan_summary tool: plan-aware allowance
|
|
2
|
+
// breakdown. Plan resolution: tool arg → COMMANDCODE_PLAN → live /alpha/whoami
|
|
3
|
+
// (when a key is present and the network works) → default "go". Rendering is a
|
|
4
|
+
// pure function so tests never touch the network.
|
|
5
|
+
import { z } from "zod";
|
|
6
|
+
import { MODEL_COSTS } from "../catalog/facts.js";
|
|
7
|
+
import { MODEL_DEALS, PLAN_CATALOG, } from "./catalog.js";
|
|
8
|
+
import { getApiBase } from "../env.js";
|
|
9
|
+
const PLAN_DISPLAY = {
|
|
10
|
+
go: "Go",
|
|
11
|
+
goat: "GOAT",
|
|
12
|
+
pro: "Pro",
|
|
13
|
+
max: "Max 10×",
|
|
14
|
+
max20: "Max 20×",
|
|
15
|
+
teampro: "Team Pro",
|
|
16
|
+
provider: "Provider",
|
|
17
|
+
};
|
|
18
|
+
const PLAN_ALIASES = {
|
|
19
|
+
go: "go",
|
|
20
|
+
"individual-go": "go",
|
|
21
|
+
goat: "goat",
|
|
22
|
+
"individual-goat": "goat",
|
|
23
|
+
pro: "pro",
|
|
24
|
+
"individual-pro": "pro",
|
|
25
|
+
"individual-pro-v1": "pro",
|
|
26
|
+
max: "max",
|
|
27
|
+
max10: "max",
|
|
28
|
+
"max-10x": "max",
|
|
29
|
+
"max 10x": "max",
|
|
30
|
+
"individual-max": "max",
|
|
31
|
+
max20: "max20",
|
|
32
|
+
"max-20x": "max20",
|
|
33
|
+
"max 20x": "max20",
|
|
34
|
+
"individual-ultra": "max20",
|
|
35
|
+
ultra: "max20",
|
|
36
|
+
teampro: "teampro",
|
|
37
|
+
"team-pro": "teampro",
|
|
38
|
+
"team pro": "teampro",
|
|
39
|
+
provider: "provider",
|
|
40
|
+
"individual-provider": "provider",
|
|
41
|
+
};
|
|
42
|
+
export function normalizePlan(value) {
|
|
43
|
+
if (typeof value !== "string")
|
|
44
|
+
return undefined;
|
|
45
|
+
return PLAN_ALIASES[value.toLowerCase()];
|
|
46
|
+
}
|
|
47
|
+
export async function resolvePlan(planArg, env = process.env) {
|
|
48
|
+
const fromArg = normalizePlan(planArg);
|
|
49
|
+
if (fromArg)
|
|
50
|
+
return fromArg;
|
|
51
|
+
const fromEnv = normalizePlan(env.COMMANDCODE_PLAN);
|
|
52
|
+
if (fromEnv)
|
|
53
|
+
return fromEnv;
|
|
54
|
+
if (env.COMMANDCODE_API_KEY) {
|
|
55
|
+
try {
|
|
56
|
+
const response = await fetch(`${getApiBase(env)}/alpha/whoami`, {
|
|
57
|
+
headers: { authorization: `Bearer ${env.COMMANDCODE_API_KEY}` },
|
|
58
|
+
signal: AbortSignal.timeout(5000),
|
|
59
|
+
});
|
|
60
|
+
if (response.ok) {
|
|
61
|
+
const body = (await response.json());
|
|
62
|
+
const plan = normalizePlan(body.planId ?? body.plan?.id);
|
|
63
|
+
if (plan)
|
|
64
|
+
return plan;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
// offline or unreachable — fall through to the default
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return "go";
|
|
72
|
+
}
|
|
73
|
+
const REQUEST_PROFILE = { input: 800, output: 200, cacheRead: 50_000 };
|
|
74
|
+
export function renderPlanSummary(plan, deals = MODEL_DEALS, catalog = PLAN_CATALOG) {
|
|
75
|
+
const info = catalog[plan];
|
|
76
|
+
const lines = [];
|
|
77
|
+
lines.push(`# Command Code plan: ${PLAN_DISPLAY[plan]}`);
|
|
78
|
+
if (plan === "provider") {
|
|
79
|
+
lines.push("pay-as-you-go at model API rates — no monthly allowances or window caps.");
|
|
80
|
+
}
|
|
81
|
+
else if (info) {
|
|
82
|
+
lines.push(`$${info.price}/mo buys $${info.credits} of credits; 5-hour window $${info.window5h}, weekly window $${info.windowWeek}.`);
|
|
83
|
+
if (plan !== "goat" && plan !== "pro") {
|
|
84
|
+
lines.push("This plan has no per-model allowances — active deals apply to your full credit balance at the discounted rates below.");
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
const rows = Object.entries(deals).filter(([, d]) => d.allowance?.[plan] !== undefined);
|
|
88
|
+
const freeRows = Object.entries(deals).filter(([id, d]) => d.free && !d.allowance?.[plan]);
|
|
89
|
+
const hasAllowances = plan === "goat" || plan === "pro";
|
|
90
|
+
const dealRows = hasAllowances
|
|
91
|
+
? []
|
|
92
|
+
: Object.entries(deals).filter(([, d]) => d.discount || d.peakOffPeak);
|
|
93
|
+
if (rows.length === 0 && freeRows.length === 0 && dealRows.length === 0) {
|
|
94
|
+
lines.push("No deal data is bundled for this plan.");
|
|
95
|
+
lines.push("See https://commandcode.ai/docs/resources/pricing-limits for the live table.");
|
|
96
|
+
return lines.join("\n");
|
|
97
|
+
}
|
|
98
|
+
lines.push("");
|
|
99
|
+
if (hasAllowances) {
|
|
100
|
+
lines.push("| Model | $/mo allowance | ~requests/mo | Deal |");
|
|
101
|
+
lines.push("| --- | --- | --- | --- |");
|
|
102
|
+
}
|
|
103
|
+
else {
|
|
104
|
+
lines.push("| Model | Deal | Rates |");
|
|
105
|
+
lines.push("| --- | --- | --- |");
|
|
106
|
+
}
|
|
107
|
+
for (const [id, d] of [...rows, ...freeRows, ...dealRows]) {
|
|
108
|
+
const safeId = id.replace(/[|`]/g, " ");
|
|
109
|
+
const dealBits = [];
|
|
110
|
+
if (d.free)
|
|
111
|
+
dealBits.push("FREE");
|
|
112
|
+
if (d.discount)
|
|
113
|
+
dealBits.push(`${d.discount.pct}% off${d.discount.endsAt ? ` until ${d.discount.endsAt}` : ""}`);
|
|
114
|
+
if (d.peakOffPeak)
|
|
115
|
+
dealBits.push(`peak/off-peak (${d.peakOffPeak.windows})`);
|
|
116
|
+
const dealText = dealBits.join("; ") || "—";
|
|
117
|
+
if (hasAllowances) {
|
|
118
|
+
const allowance = d.allowance?.[plan];
|
|
119
|
+
const estimate = allowance && d.free === false
|
|
120
|
+
? estimateMonthlyRequests(id, allowance).toLocaleString("en-US")
|
|
121
|
+
: "—";
|
|
122
|
+
lines.push(`| \`${safeId}\` | ${allowance !== undefined ? `$${allowance}` : "free"} | ${estimate} | ${dealText} |`);
|
|
123
|
+
}
|
|
124
|
+
else {
|
|
125
|
+
const rates = d.discount && d.was
|
|
126
|
+
? `was $${d.was.input}/$${d.was.output} in/out`
|
|
127
|
+
: d.peakOffPeak
|
|
128
|
+
? `$${d.peakOffPeak.peak.input}/$${d.peakOffPeak.peak.output} peak`
|
|
129
|
+
: "—";
|
|
130
|
+
lines.push(`| \`${safeId}\` | ${dealText} | ${rates} |`);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
lines.push("");
|
|
134
|
+
lines.push("Estimates assume ~800 fresh input + 50K cache-read + 200 output tokens per request.");
|
|
135
|
+
lines.push("Source: https://commandcode.ai/docs/resources/pricing-limits");
|
|
136
|
+
return lines.join("\n");
|
|
137
|
+
}
|
|
138
|
+
function estimateMonthlyRequests(modelId, allowance) {
|
|
139
|
+
const cost = MODEL_COSTS[modelId];
|
|
140
|
+
// No cost row bundled (e.g. not yet in the snapshot): the allowance is the
|
|
141
|
+
// best estimate we have, so report it directly as a placeholder.
|
|
142
|
+
if (!cost)
|
|
143
|
+
return Math.round(allowance);
|
|
144
|
+
const perRequest = (REQUEST_PROFILE.input * cost.input +
|
|
145
|
+
REQUEST_PROFILE.output * cost.output +
|
|
146
|
+
REQUEST_PROFILE.cacheRead * cost.cacheRead) /
|
|
147
|
+
1_000_000;
|
|
148
|
+
if (perRequest <= 0)
|
|
149
|
+
return Math.round(allowance);
|
|
150
|
+
return Math.floor(allowance / perRequest + 1e-9);
|
|
151
|
+
}
|
|
152
|
+
export function planSummaryTool() {
|
|
153
|
+
return {
|
|
154
|
+
description: "Show the Command Code plan's credits, usage windows, per-model monthly allowances (GOAT/Pro) or active deals (other plans), with estimated monthly request counts. Set COMMANDCODE_PLAN (go|goat|pro|max|max20|teampro|provider) to pin the plan without network access.",
|
|
155
|
+
args: {
|
|
156
|
+
plan: z.string().optional().describe("go|goat|pro|max|max20|teampro|provider"),
|
|
157
|
+
},
|
|
158
|
+
execute: async (args) => renderPlanSummary(await resolvePlan(args.plan)),
|
|
159
|
+
};
|
|
160
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { TuiPluginModule } from "@opencode-ai/plugin/tui";
|
|
2
|
+
export declare function dealsRows(model: {
|
|
3
|
+
options?: {
|
|
4
|
+
cmd?: Record<string, unknown>;
|
|
5
|
+
};
|
|
6
|
+
} | undefined): Array<[string, string]>;
|
|
7
|
+
declare const plugin: TuiPluginModule & {
|
|
8
|
+
id: string;
|
|
9
|
+
};
|
|
10
|
+
export default plugin;
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "@opentui/solid/jsx-runtime";
|
|
2
|
+
/** @jsxImportSource @opentui/solid */
|
|
3
|
+
// src/deals/tui.tsx — TUI plugin: "Command Code" deals section in the session
|
|
4
|
+
// sidebar (sidebar_content slot). Renders deal details from the picked model's
|
|
5
|
+
// enriched options.cmd (produced by the server plugin's config hook). Renders
|
|
6
|
+
// nothing when the model has no deals data — zero sidebar noise.
|
|
7
|
+
import { For, Show, createMemo } from "solid-js";
|
|
8
|
+
import { DEAL_SOURCE_URL, PLAN_CATALOG } from "./catalog.js";
|
|
9
|
+
function planDisplay(plan) {
|
|
10
|
+
return PLAN_CATALOG[plan]?.display ?? plan;
|
|
11
|
+
}
|
|
12
|
+
const TIER_DISPLAY = {
|
|
13
|
+
opensource: "Open Source",
|
|
14
|
+
premium: "Premium",
|
|
15
|
+
};
|
|
16
|
+
function tierDisplay(tier) {
|
|
17
|
+
return TIER_DISPLAY[tier] ?? tier;
|
|
18
|
+
}
|
|
19
|
+
function rateString(rates) {
|
|
20
|
+
if (typeof rates.input !== "number" || typeof rates.output !== "number")
|
|
21
|
+
return undefined;
|
|
22
|
+
return `$${rates.input}/$${rates.output} in/out`;
|
|
23
|
+
}
|
|
24
|
+
export function dealsRows(model) {
|
|
25
|
+
const cmd = model?.options?.cmd;
|
|
26
|
+
if (!cmd)
|
|
27
|
+
return [];
|
|
28
|
+
if (cmd.unavailable === true) {
|
|
29
|
+
return [
|
|
30
|
+
[`Deals unavailable — ${DEAL_SOURCE_URL}`, ""],
|
|
31
|
+
["Tier", "—"],
|
|
32
|
+
["Intelligence", "—"],
|
|
33
|
+
["Tok/s", "—"],
|
|
34
|
+
];
|
|
35
|
+
}
|
|
36
|
+
const rows = [];
|
|
37
|
+
if (typeof cmd.tier === "string")
|
|
38
|
+
rows.push(["Tier", tierDisplay(cmd.tier)]);
|
|
39
|
+
if (cmd.free === true)
|
|
40
|
+
rows.push(["Status", "FREE"]);
|
|
41
|
+
if (cmd.allowance) {
|
|
42
|
+
for (const [plan, value] of Object.entries(cmd.allowance)) {
|
|
43
|
+
if (typeof value === "number")
|
|
44
|
+
rows.push([`${planDisplay(plan)} allowance`, `$${value}/mo`]);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
if (cmd.discount && typeof cmd.discount.pct === "number") {
|
|
48
|
+
rows.push([
|
|
49
|
+
"Deal",
|
|
50
|
+
`${cmd.discount.pct}% off${typeof cmd.discount.endsAt === "string" ? ` until ${cmd.discount.endsAt}` : ""}`,
|
|
51
|
+
]);
|
|
52
|
+
}
|
|
53
|
+
const was = cmd.was ? rateString(cmd.was) : undefined;
|
|
54
|
+
const now = cmd.now ? rateString(cmd.now) : undefined;
|
|
55
|
+
if (was)
|
|
56
|
+
rows.push(["Was", was]);
|
|
57
|
+
if (now)
|
|
58
|
+
rows.push(["Now", now]);
|
|
59
|
+
if (cmd.benchmark) {
|
|
60
|
+
rows.push([
|
|
61
|
+
"Intelligence",
|
|
62
|
+
typeof cmd.benchmark.intelligence === "number" ? String(cmd.benchmark.intelligence) : "—",
|
|
63
|
+
]);
|
|
64
|
+
rows.push([
|
|
65
|
+
"Tok/s",
|
|
66
|
+
typeof cmd.benchmark.tokPerSec === "number" ? String(cmd.benchmark.tokPerSec) : "—",
|
|
67
|
+
]);
|
|
68
|
+
}
|
|
69
|
+
if (cmd.peakOffPeak) {
|
|
70
|
+
rows.push([
|
|
71
|
+
"Rates",
|
|
72
|
+
`peak/off-peak${typeof cmd.peakOffPeak.windows === "string" ? ` (${cmd.peakOffPeak.windows})` : ""}`,
|
|
73
|
+
]);
|
|
74
|
+
}
|
|
75
|
+
return rows;
|
|
76
|
+
}
|
|
77
|
+
const id = "commandcode.deals";
|
|
78
|
+
const tui = async (api) => {
|
|
79
|
+
api.slots.register({
|
|
80
|
+
order: 200,
|
|
81
|
+
slots: {
|
|
82
|
+
sidebar_content(_ctx, props) {
|
|
83
|
+
return _jsx(DealsPanel, { api: api, session_id: props.session_id });
|
|
84
|
+
},
|
|
85
|
+
},
|
|
86
|
+
});
|
|
87
|
+
};
|
|
88
|
+
function DealsPanel(props) {
|
|
89
|
+
const theme = () => props.api.theme.current;
|
|
90
|
+
// Mid-session model switches update the session record (`session.updated`
|
|
91
|
+
// reconciles it into the sync store), so reading `session.model` reactively
|
|
92
|
+
// is enough — no event subscription needed.
|
|
93
|
+
const model = createMemo(() => {
|
|
94
|
+
const current = props.api.state.session.get(props.session_id)?.model;
|
|
95
|
+
if (!current)
|
|
96
|
+
return undefined;
|
|
97
|
+
return props.api.state.provider.find((provider) => provider.id === current.providerID)
|
|
98
|
+
?.models[current.id];
|
|
99
|
+
});
|
|
100
|
+
const rows = createMemo(() => dealsRows(model()));
|
|
101
|
+
return (_jsx(Show, { when: rows().length > 0, children: _jsxs("box", { children: [_jsx("text", { fg: theme().text, children: _jsx("b", { children: "Command Code" }) }), _jsx(For, { each: rows(), children: (row) => (_jsx("text", { fg: theme().textMuted, children: row[1] ? `${row[0]}: ${row[1]}` : row[0] })) })] }) }));
|
|
102
|
+
}
|
|
103
|
+
const plugin = { id, tui };
|
|
104
|
+
export default plugin;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function vendorFamilyForModel(modelId: string): string | undefined;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// src/deals/vendor.ts — model id → vendor family mapping for the config hook.
|
|
2
|
+
// Derived from the model id namespace, never from scraped data, so it cannot
|
|
3
|
+
// go stale. Unknown ids map to undefined and the family field is left unset.
|
|
4
|
+
const VENDOR_FAMILIES = {
|
|
5
|
+
"claude-": "claude",
|
|
6
|
+
"gpt-": "gpt",
|
|
7
|
+
"google/": "gemini",
|
|
8
|
+
"deepseek/": "deepseek",
|
|
9
|
+
"Qwen/": "qwen",
|
|
10
|
+
"moonshotai/": "kimi",
|
|
11
|
+
"zai-org/": "glm",
|
|
12
|
+
"MiniMaxAI/": "minimax",
|
|
13
|
+
"xiaomi/": "mimo",
|
|
14
|
+
"stepfun/": "step",
|
|
15
|
+
"tencent/": "tencent",
|
|
16
|
+
"nvidia/": "nemotron",
|
|
17
|
+
"thinkingmachines/": "inkling",
|
|
18
|
+
"poolside/": "laguna",
|
|
19
|
+
"meta/": "muse",
|
|
20
|
+
"xai/": "grok",
|
|
21
|
+
"sakana/": "sakana",
|
|
22
|
+
};
|
|
23
|
+
export function vendorFamilyForModel(modelId) {
|
|
24
|
+
for (const [prefix, family] of Object.entries(VENDOR_FAMILIES)) {
|
|
25
|
+
if (modelId.startsWith(prefix))
|
|
26
|
+
return family;
|
|
27
|
+
}
|
|
28
|
+
return undefined;
|
|
29
|
+
}
|
package/dist/src/plugin/index.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { MODEL_SNAPSHOT } from "../catalog/snapshot.js";
|
|
3
3
|
import { getApiBase } from "../env.js";
|
|
4
4
|
import { augmentConfigCommandCodeModels, autoRegister } from "./models.js";
|
|
5
|
+
import { enrichCommandCodeModels, planSummaryTool } from "../deals/index.js";
|
|
5
6
|
import { runAuthFlow } from "./auth.js";
|
|
6
7
|
const server = async () => {
|
|
7
8
|
return {
|
|
@@ -12,6 +13,7 @@ const server = async () => {
|
|
|
12
13
|
baseURL: getApiBase(),
|
|
13
14
|
});
|
|
14
15
|
augmentConfigCommandCodeModels(config);
|
|
16
|
+
enrichCommandCodeModels(config);
|
|
15
17
|
},
|
|
16
18
|
auth: {
|
|
17
19
|
provider: "commandcode",
|
|
@@ -23,6 +25,9 @@ const server = async () => {
|
|
|
23
25
|
},
|
|
24
26
|
],
|
|
25
27
|
},
|
|
28
|
+
tool: {
|
|
29
|
+
cmd_plan_summary: planSummaryTool(),
|
|
30
|
+
},
|
|
26
31
|
};
|
|
27
32
|
};
|
|
28
33
|
export default { id: "commandcode", server };
|
|
@@ -9,7 +9,7 @@ import { resolveApiKey } from "./auth-key.js";
|
|
|
9
9
|
import { messagesToCC, toolsToJson, systemPromptToText, getEnvironmentInfo, isRecord, } from "./converters.js";
|
|
10
10
|
import { parseStreamEventLine, ccEventToStreamPart } from "./stream.js";
|
|
11
11
|
import { redactCommandCodeErrorText, commandCodeErrorMessage } from "./redact.js";
|
|
12
|
-
import { calculateCommandCodeCost } from "./cost.js";
|
|
12
|
+
import { calculateCommandCodeCost, costUsageFromAiSdkUsage } from "./cost.js";
|
|
13
13
|
import { ZERO_MODEL_COST, MODEL_COSTS } from "./pricing.js";
|
|
14
14
|
import { mappedReasoningEffort, resolveProviderReasoning, thinkingMetadataForModel, isReasoningModel, } from "./reasoning.js";
|
|
15
15
|
import { modelSupportsImageInput } from "./modalities.js";
|
|
@@ -233,14 +233,7 @@ export class CommandCodeLanguageModel {
|
|
|
233
233
|
for (const part of parts) {
|
|
234
234
|
if (part.type === "finish") {
|
|
235
235
|
finished = true;
|
|
236
|
-
|
|
237
|
-
calculateCommandCodeCost(this.costForModel(), {
|
|
238
|
-
input: usage.inputTokens.total ?? 0,
|
|
239
|
-
output: usage.outputTokens.total ?? 0,
|
|
240
|
-
cacheRead: 0,
|
|
241
|
-
cacheWrite: 0,
|
|
242
|
-
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
|
|
243
|
-
});
|
|
236
|
+
calculateCommandCodeCost(this.costForModel(), costUsageFromAiSdkUsage(part.usage));
|
|
244
237
|
}
|
|
245
238
|
emit(part);
|
|
246
239
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { LanguageModelV3Usage } from "@ai-sdk/provider";
|
|
1
2
|
import type { CommandCodeModelCost } from "./pricing.js";
|
|
2
3
|
export interface CostUsage {
|
|
3
4
|
input: number;
|
|
@@ -16,4 +17,10 @@ export interface CostUsage {
|
|
|
16
17
|
export interface CostModel {
|
|
17
18
|
cost: CommandCodeModelCost;
|
|
18
19
|
}
|
|
20
|
+
/**
|
|
21
|
+
* Maps an emitted AI SDK v3 usage onto billable token counts: fresh input
|
|
22
|
+
* (`noCache`), cache read, and cache write each bill at their own rate, so
|
|
23
|
+
* the cache-inclusive `total` must not be billed as fresh input (issue #36).
|
|
24
|
+
*/
|
|
25
|
+
export declare function costUsageFromAiSdkUsage(usage: LanguageModelV3Usage): CostUsage;
|
|
19
26
|
export declare function calculateCommandCodeCost(model: CostModel, usage: CostUsage): void;
|
|
@@ -1,3 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Maps an emitted AI SDK v3 usage onto billable token counts: fresh input
|
|
3
|
+
* (`noCache`), cache read, and cache write each bill at their own rate, so
|
|
4
|
+
* the cache-inclusive `total` must not be billed as fresh input (issue #36).
|
|
5
|
+
*/
|
|
6
|
+
export function costUsageFromAiSdkUsage(usage) {
|
|
7
|
+
return {
|
|
8
|
+
input: usage.inputTokens.noCache ?? 0,
|
|
9
|
+
output: usage.outputTokens.total ?? 0,
|
|
10
|
+
cacheRead: usage.inputTokens.cacheRead ?? 0,
|
|
11
|
+
cacheWrite: usage.inputTokens.cacheWrite ?? 0,
|
|
12
|
+
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
|
|
13
|
+
};
|
|
14
|
+
}
|
|
1
15
|
export function calculateCommandCodeCost(model, usage) {
|
|
2
16
|
const rates = model.cost;
|
|
3
17
|
const longWrite = usage.cacheWrite1h ?? 0;
|
|
@@ -39,13 +39,13 @@ export function ccUsageToAiSdkUsage(event) {
|
|
|
39
39
|
return undefined;
|
|
40
40
|
const details = isRecord(usage.inputTokenDetails) ? usage.inputTokenDetails : undefined;
|
|
41
41
|
const totalInput = numberValue(usage.inputTokens) ?? 0;
|
|
42
|
-
const noCache = numberValue(details?.noCacheTokens);
|
|
43
42
|
const cacheRead = numberValue(details?.cacheReadTokens) ?? 0;
|
|
44
43
|
const cacheWrite = numberValue(details?.cacheWriteTokens) ?? 0;
|
|
45
|
-
const
|
|
44
|
+
const explicitNoCache = numberValue(details?.noCacheTokens);
|
|
45
|
+
const noCache = explicitNoCache ?? Math.max(0, totalInput - cacheRead - cacheWrite);
|
|
46
46
|
const outputTokens = numberValue(usage.outputTokens) ?? 0;
|
|
47
47
|
return {
|
|
48
|
-
inputTokens: { total:
|
|
48
|
+
inputTokens: { total: totalInput, noCache, cacheRead, cacheWrite },
|
|
49
49
|
outputTokens: { total: outputTokens, text: outputTokens, reasoning: 0 },
|
|
50
50
|
};
|
|
51
51
|
}
|
package/dist/tui.js
ADDED
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
// src/deals/tui.tsx
|
|
3
|
+
import { memo as _$memo } from "@opentui/solid";
|
|
4
|
+
import { setProp as _$setProp } from "@opentui/solid";
|
|
5
|
+
import { effect as _$effect } from "@opentui/solid";
|
|
6
|
+
import { insert as _$insert } from "@opentui/solid";
|
|
7
|
+
import { createTextNode as _$createTextNode } from "@opentui/solid";
|
|
8
|
+
import { insertNode as _$insertNode } from "@opentui/solid";
|
|
9
|
+
import { createElement as _$createElement } from "@opentui/solid";
|
|
10
|
+
import { createComponent as _$createComponent } from "@opentui/solid";
|
|
11
|
+
import { For, Show, createMemo } from "solid-js";
|
|
12
|
+
|
|
13
|
+
// src/deals/catalog.ts
|
|
14
|
+
var PLAN_CATALOG = {
|
|
15
|
+
go: {
|
|
16
|
+
price: 1,
|
|
17
|
+
credits: 10,
|
|
18
|
+
window5h: 3,
|
|
19
|
+
windowWeek: 6,
|
|
20
|
+
display: "Go"
|
|
21
|
+
},
|
|
22
|
+
goat: {
|
|
23
|
+
price: 10,
|
|
24
|
+
credits: 70,
|
|
25
|
+
window5h: 14,
|
|
26
|
+
windowWeek: 35,
|
|
27
|
+
display: "GOAT"
|
|
28
|
+
},
|
|
29
|
+
pro: {
|
|
30
|
+
price: 20,
|
|
31
|
+
credits: 80,
|
|
32
|
+
window5h: 16,
|
|
33
|
+
windowWeek: 40,
|
|
34
|
+
display: "Pro"
|
|
35
|
+
},
|
|
36
|
+
max: {
|
|
37
|
+
price: 100,
|
|
38
|
+
credits: 150,
|
|
39
|
+
window5h: 45,
|
|
40
|
+
windowWeek: 90,
|
|
41
|
+
display: "Max 10\xD7"
|
|
42
|
+
},
|
|
43
|
+
max20: {
|
|
44
|
+
price: 200,
|
|
45
|
+
credits: 300,
|
|
46
|
+
window5h: 90,
|
|
47
|
+
windowWeek: 180,
|
|
48
|
+
display: "Max 20\xD7"
|
|
49
|
+
},
|
|
50
|
+
teampro: {
|
|
51
|
+
price: 40,
|
|
52
|
+
credits: 40,
|
|
53
|
+
window5h: 12,
|
|
54
|
+
windowWeek: 24,
|
|
55
|
+
display: "Team Pro"
|
|
56
|
+
},
|
|
57
|
+
provider: {
|
|
58
|
+
price: 15,
|
|
59
|
+
credits: 0,
|
|
60
|
+
window5h: 0,
|
|
61
|
+
windowWeek: 0,
|
|
62
|
+
display: "Provider"
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
var DEAL_SOURCE_URL = "https://commandcode.ai/docs/resources/pricing-limits";
|
|
66
|
+
|
|
67
|
+
// src/deals/tui.tsx
|
|
68
|
+
function planDisplay(plan) {
|
|
69
|
+
return PLAN_CATALOG[plan]?.display ?? plan;
|
|
70
|
+
}
|
|
71
|
+
var TIER_DISPLAY = {
|
|
72
|
+
opensource: "Open Source",
|
|
73
|
+
premium: "Premium"
|
|
74
|
+
};
|
|
75
|
+
function tierDisplay(tier) {
|
|
76
|
+
return TIER_DISPLAY[tier] ?? tier;
|
|
77
|
+
}
|
|
78
|
+
function rateString(rates) {
|
|
79
|
+
if (typeof rates.input !== "number" || typeof rates.output !== "number")
|
|
80
|
+
return;
|
|
81
|
+
return `$${rates.input}/$${rates.output} in/out`;
|
|
82
|
+
}
|
|
83
|
+
function dealsRows(model) {
|
|
84
|
+
const cmd = model?.options?.cmd;
|
|
85
|
+
if (!cmd)
|
|
86
|
+
return [];
|
|
87
|
+
if (cmd.unavailable === true) {
|
|
88
|
+
return [[`Deals unavailable \u2014 ${DEAL_SOURCE_URL}`, ""], ["Tier", "\u2014"], ["Intelligence", "\u2014"], ["Tok/s", "\u2014"]];
|
|
89
|
+
}
|
|
90
|
+
const rows = [];
|
|
91
|
+
if (typeof cmd.tier === "string")
|
|
92
|
+
rows.push(["Tier", tierDisplay(cmd.tier)]);
|
|
93
|
+
if (cmd.free === true)
|
|
94
|
+
rows.push(["Status", "FREE"]);
|
|
95
|
+
if (cmd.allowance) {
|
|
96
|
+
for (const [plan, value] of Object.entries(cmd.allowance)) {
|
|
97
|
+
if (typeof value === "number")
|
|
98
|
+
rows.push([`${planDisplay(plan)} allowance`, `$${value}/mo`]);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
if (cmd.discount && typeof cmd.discount.pct === "number") {
|
|
102
|
+
rows.push(["Deal", `${cmd.discount.pct}% off${typeof cmd.discount.endsAt === "string" ? ` until ${cmd.discount.endsAt}` : ""}`]);
|
|
103
|
+
}
|
|
104
|
+
const was = cmd.was ? rateString(cmd.was) : undefined;
|
|
105
|
+
const now = cmd.now ? rateString(cmd.now) : undefined;
|
|
106
|
+
if (was)
|
|
107
|
+
rows.push(["Was", was]);
|
|
108
|
+
if (now)
|
|
109
|
+
rows.push(["Now", now]);
|
|
110
|
+
if (cmd.benchmark) {
|
|
111
|
+
rows.push(["Intelligence", typeof cmd.benchmark.intelligence === "number" ? String(cmd.benchmark.intelligence) : "\u2014"]);
|
|
112
|
+
rows.push(["Tok/s", typeof cmd.benchmark.tokPerSec === "number" ? String(cmd.benchmark.tokPerSec) : "\u2014"]);
|
|
113
|
+
}
|
|
114
|
+
if (cmd.peakOffPeak) {
|
|
115
|
+
rows.push(["Rates", `peak/off-peak${typeof cmd.peakOffPeak.windows === "string" ? ` (${cmd.peakOffPeak.windows})` : ""}`]);
|
|
116
|
+
}
|
|
117
|
+
return rows;
|
|
118
|
+
}
|
|
119
|
+
var id = "commandcode.deals";
|
|
120
|
+
var tui = async (api) => {
|
|
121
|
+
api.slots.register({
|
|
122
|
+
order: 200,
|
|
123
|
+
slots: {
|
|
124
|
+
sidebar_content(_ctx, props) {
|
|
125
|
+
return _$createComponent(DealsPanel, {
|
|
126
|
+
api,
|
|
127
|
+
get session_id() {
|
|
128
|
+
return props.session_id;
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
};
|
|
135
|
+
function DealsPanel(props) {
|
|
136
|
+
const theme = () => props.api.theme.current;
|
|
137
|
+
const model = createMemo(() => {
|
|
138
|
+
const current = props.api.state.session.get(props.session_id)?.model;
|
|
139
|
+
if (!current)
|
|
140
|
+
return;
|
|
141
|
+
return props.api.state.provider.find((provider) => provider.id === current.providerID)?.models[current.id];
|
|
142
|
+
});
|
|
143
|
+
const rows = createMemo(() => dealsRows(model()));
|
|
144
|
+
return _$createComponent(Show, {
|
|
145
|
+
get when() {
|
|
146
|
+
return rows().length > 0;
|
|
147
|
+
},
|
|
148
|
+
get children() {
|
|
149
|
+
var _el$ = _$createElement("box"), _el$2 = _$createElement("text"), _el$3 = _$createElement("b");
|
|
150
|
+
_$insertNode(_el$, _el$2);
|
|
151
|
+
_$insertNode(_el$2, _el$3);
|
|
152
|
+
_$insertNode(_el$3, _$createTextNode(`Command Code`));
|
|
153
|
+
_$insert(_el$, _$createComponent(For, {
|
|
154
|
+
get each() {
|
|
155
|
+
return rows();
|
|
156
|
+
},
|
|
157
|
+
children: (row) => (() => {
|
|
158
|
+
var _el$5 = _$createElement("text");
|
|
159
|
+
_$insert(_el$5, (() => {
|
|
160
|
+
var _c$ = _$memo(() => !!row[1]);
|
|
161
|
+
return () => _c$() ? `${row[0]}: ${row[1]}` : row[0];
|
|
162
|
+
})());
|
|
163
|
+
_$effect((_$p) => _$setProp(_el$5, "fg", theme().textMuted, _$p));
|
|
164
|
+
return _el$5;
|
|
165
|
+
})()
|
|
166
|
+
}), null);
|
|
167
|
+
_$effect((_$p) => _$setProp(_el$2, "fg", theme().text, _$p));
|
|
168
|
+
return _el$;
|
|
169
|
+
}
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
var plugin = {
|
|
173
|
+
id,
|
|
174
|
+
tui
|
|
175
|
+
};
|
|
176
|
+
var tui_default = plugin;
|
|
177
|
+
export {
|
|
178
|
+
dealsRows,
|
|
179
|
+
tui_default as default
|
|
180
|
+
};
|
package/package.json
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opencode-cmd-provider",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.1",
|
|
4
4
|
"description": "Command Code provider + plugin for opencode",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
7
7
|
"exports": {
|
|
8
8
|
".": "./dist/index.js",
|
|
9
|
-
"./server": "./dist/index.js"
|
|
9
|
+
"./server": "./dist/index.js",
|
|
10
|
+
"./tui": "./dist/tui.js"
|
|
10
11
|
},
|
|
11
12
|
"files": [
|
|
12
13
|
"dist",
|
|
@@ -15,14 +16,17 @@
|
|
|
15
16
|
"CHANGELOG.md"
|
|
16
17
|
],
|
|
17
18
|
"scripts": {
|
|
18
|
-
"build": "tsc -p tsconfig.json",
|
|
19
|
+
"build": "tsc -p tsconfig.json && npm run build:tui",
|
|
19
20
|
"typecheck": "tsc --noEmit",
|
|
20
21
|
"refresh:snapshot": "node scripts/refresh-snapshot.mjs",
|
|
22
|
+
"refresh:deals": "node scripts/refresh-deals.mjs",
|
|
23
|
+
"refresh": "npm run refresh:snapshot && npm run refresh:deals -- --fixtures",
|
|
21
24
|
"test": "npm run typecheck && npm run test:unit && npm run test:integration && npm run test:contract && npm run format:check",
|
|
22
|
-
"test:unit": "tsx tests/env.test.ts && tsx tests/auth-key.test.ts && tsx tests/converters.test.ts && tsx tests/stream.test.ts && tsx tests/redact.test.ts && tsx tests/cost.test.ts && tsx tests/retry.test.ts && tsx tests/reasoning.test.ts && tsx tests/modalities.test.ts && tsx tests/snapshot.test.ts && tsx tests/refresh-snapshot.test.ts && tsx tests/oauth.test.ts && tsx tests/parse-facts.test.ts && tsx tests/parse-modalities.test.ts && tsx tests/catalog-metadata.test.ts && tsx tests/plugin-models.test.ts",
|
|
25
|
+
"test:unit": "tsx tests/env.test.ts && tsx tests/auth-key.test.ts && tsx tests/converters.test.ts && tsx tests/stream.test.ts && tsx tests/redact.test.ts && tsx tests/cost.test.ts && tsx tests/retry.test.ts && tsx tests/reasoning.test.ts && tsx tests/modalities.test.ts && tsx tests/snapshot.test.ts && tsx tests/refresh-snapshot.test.ts && tsx tests/oauth.test.ts && tsx tests/parse-facts.test.ts && tsx tests/parse-modalities.test.ts && tsx tests/catalog-metadata.test.ts && tsx tests/plugin-models.test.ts && tsx tests/vendor.test.ts && tsx tests/deals-enrichment.test.ts && tsx tests/plan-summary.test.ts && tsx tests/html-tables.test.ts && tsx tests/parse-docs.test.ts && tsx tests/refresh-deals.test.ts && tsx tests/tui-deals-panel.test.ts && tsx tests/plugin-install.test.ts",
|
|
23
26
|
"test:integration": "tsx tests/integration-do-stream.test.ts && tsx tests/integration-do-generate.test.ts",
|
|
24
27
|
"test:contract": "tsx tests/contract.test.ts",
|
|
25
28
|
"test:e2e": "node tests/e2e-opencode.mjs",
|
|
29
|
+
"build:tui": "bun scripts/build-tui.ts",
|
|
26
30
|
"format": "prettier --write '**/*.{ts,mjs,json,md}'",
|
|
27
31
|
"format:check": "prettier --check '**/*.{ts,mjs,json,md}'"
|
|
28
32
|
},
|
|
@@ -39,7 +43,11 @@
|
|
|
39
43
|
"url": "git+https://github.com/rashidrazak/opencode-cmd-provider.git"
|
|
40
44
|
},
|
|
41
45
|
"dependencies": {
|
|
42
|
-
"@ai-sdk/provider": "^4.0.7"
|
|
46
|
+
"@ai-sdk/provider": "^4.0.7",
|
|
47
|
+
"@opentui/core": "^0.5.4",
|
|
48
|
+
"@opentui/solid": "^0.5.4",
|
|
49
|
+
"solid-js": "1.9.12",
|
|
50
|
+
"zod": "^4.1.8"
|
|
43
51
|
},
|
|
44
52
|
"devDependencies": {
|
|
45
53
|
"@opencode-ai/plugin": "*",
|