opencode-tweaks 0.6.0 → 0.6.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/README.md CHANGED
@@ -179,11 +179,12 @@ Costs come from what the provider reports, so a free plan shows `$0.00`. `Cost d
179
179
  `Auto` keeps two decimals and grows until the first significant digit shows (`$0.004`), while a fixed choice always
180
180
  keeps that many (`$0.0042`).
181
181
 
182
- `Price windows` marks the provider's higher rate beside the cost. DeepSeek doubles its listed prices from 01:00 to
182
+ `Price windows` marks rounds billed above the catalog price. DeepSeek doubles its listed prices from 01:00 to
183
183
  04:00 and from 06:00 to 10:00 UTC, Monday to Friday, and bills Chinese public holidays at the off-peak rate; OpenCode
184
- Go publishes the same DeepSeek rates. The badge reads `peak ×2` while that rate applies. The catalog already holds the
185
- off-peak price, so the figures themselves stay the listed rate. The 2026 and 2027 holidays are built in; update
186
- `src/usage/price-window.ts` when the next calendar is published.
184
+ Go publishes the same DeepSeek rates. The badge reads `peak ×2` beside the turn cost when that round started in a peak
185
+ window, so it stays on screen after the window closes. The catalog already holds the off-peak price, so the figures
186
+ themselves stay the listed rate. The 2026 and 2027 holidays are built in; update `src/usage/price-window.ts` when the
187
+ next calendar is published.
187
188
 
188
189
  `Estimate cost` fills in a price the provider did not report, using the model's list price from the same catalog
189
190
  OpenCode already synced: prompt, completion, reasoning billed as output, and cache traffic, each at its own rate. A
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://json.schemastore.org/package.json",
3
3
  "name": "opencode-tweaks",
4
- "version": "0.6.0",
4
+ "version": "0.6.1",
5
5
  "description": "Opt-in settings and small interface improvements for OpenCode.",
6
6
  "type": "module",
7
7
  "license": "MIT",
@@ -60,8 +60,7 @@ export const costTweak = defineTweak({
60
60
  {
61
61
  id: "windows",
62
62
  title: "Price windows",
63
- description:
64
- "Mark when the provider charges more than the catalog price, such as DeepSeek peak hours.",
63
+ description: "Mark rounds billed above the catalog price, such as DeepSeek peak hours.",
65
64
  category: "Display",
66
65
  kind: "boolean",
67
66
  default: true,
@@ -10,7 +10,7 @@ import {
10
10
  priceFor,
11
11
  } from "../usage/cost"
12
12
  import { footerMessage, replyTurnMessages, type UsageMessage } from "../usage/messages"
13
- import { formatPriceWindow, type PriceModel, priceWindowFor } from "../usage/price-window"
13
+ import { formatPriceWindow, type PriceModel, turnPriceWindow } from "../usage/price-window"
14
14
  import { formatTokens, sessionTokens, type TokenCount, usageTokens } from "../usage/tokens"
15
15
  import { costTweak } from "./cost"
16
16
  import type { SettingsStore } from "./settings"
@@ -84,19 +84,6 @@ function useUsage(props: {
84
84
 
85
85
  const sessions = props.context.data.session
86
86
 
87
- const [now, setNow] = createSignal(Date.now())
88
-
89
- // The badge follows the clock, not the transcript: a line that stays on
90
- // screen across a rate change has to update on its own. The timer exists
91
- // only while the cost tweak and its price windows are on.
92
- createEffect(() => {
93
- if (!costOn() || !windowsOn() || (!promptCost() && !totalCost())) return
94
-
95
- setNow(Date.now())
96
- const timer = setInterval(() => setNow(Date.now()), 30_000)
97
- onCleanup(() => clearInterval(timer))
98
- })
99
-
100
87
  // List prices come from the model catalog the TUI already synced, so an
101
88
  // estimate costs nothing but a lookup.
102
89
  const prices = createMemo(() => {
@@ -205,27 +192,17 @@ function useUsage(props: {
205
192
  return sessions.get(sessionID)?.model ?? lastModel(messages(sessionID))
206
193
  }
207
194
 
208
- /** The higher price that applies right now, when the cost figures are on. */
209
- const windowFor = (model: PriceModel | undefined) =>
210
- costOn() && windowsOn() ? priceWindowFor(model, new Date(now())) : undefined
211
-
212
195
  const promptCostLabel = () => {
213
196
  const value = cost()
214
197
  if (!costOn() || !promptCost() || value === undefined) return undefined
215
198
 
216
199
  const label = formatCost(value, costDigits())
217
- const window = windowFor(lastModel(turn()) ?? sessionModel())
200
+ const window = windowsOn() ? turnPriceWindow(turn(), sessionModel()) : undefined
218
201
  return window ? `${label} · ${formatPriceWindow(window)}` : label
219
202
  }
220
203
 
221
- const totalCostLabel = () => {
222
- if (!costOn() || !totalCost()) return undefined
223
-
224
- const value = sessionCost()
225
- const label = formatCost(value, costDigits())
226
- const window = value > 0 ? windowFor(sessionModel()) : undefined
227
- return window ? `${label} · ${formatPriceWindow(window)}` : label
228
- }
204
+ const totalCostLabel = () =>
205
+ costOn() && totalCost() ? formatCost(sessionCost(), costDigits()) : undefined
229
206
 
230
207
  return {
231
208
  enabled: () => tokensOn() || costOn(),
@@ -13,6 +13,8 @@ export interface UsageMessage {
13
13
  readonly tokens?: MessageTokens
14
14
  readonly cost?: number
15
15
  readonly model?: { readonly providerID: string; readonly id: string }
16
+ /** When the step was created, which is when the provider started billing it. */
17
+ readonly time?: { readonly created?: number }
16
18
  /** How the step ended; the host only draws a footer for a finished one. */
17
19
  readonly finish?: string | null
18
20
  readonly error?: unknown
@@ -1,3 +1,5 @@
1
+ import type { UsageMessage } from "./messages"
2
+
1
3
  /** The provider and model a message belongs to. */
2
4
  export interface PriceModel {
3
5
  readonly providerID: string
@@ -134,3 +136,22 @@ export function priceWindowFor(model: PriceModel | undefined, at: Date): PriceWi
134
136
  export function formatPriceWindow(window: PriceWindow): string {
135
137
  return `${window.label} ×${window.multiplier}`
136
138
  }
139
+
140
+ /**
141
+ * The window that applied to a round, from the step that started it. Each
142
+ * request is billed when it starts, so a round that ran during peak keeps its
143
+ * marker after the window closes. Only assistant steps carry a start time.
144
+ */
145
+ export function turnPriceWindow(
146
+ messages: readonly UsageMessage[],
147
+ fallback?: PriceModel,
148
+ ): PriceWindow | undefined {
149
+ const step = messages.find(
150
+ (message) => message.type === "assistant" && message.time?.created !== undefined,
151
+ )
152
+ const created = step?.time?.created
153
+ const model = step?.model ?? fallback
154
+ if (!model || created === undefined) return undefined
155
+
156
+ return priceWindowFor(model, new Date(created))
157
+ }