@danielbdev/opencode-go-usage 0.1.2 → 0.1.3
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 +10 -3
- package/package.json +1 -1
- package/src/core/usage.test.ts +49 -6
- package/src/core/usage.ts +28 -8
- package/src/index.ts +15 -8
package/README.md
CHANGED
|
@@ -18,11 +18,14 @@ A `Go usage` section at the top of the sidebar content region, above the context
|
|
|
18
18
|
|
|
19
19
|
```
|
|
20
20
|
Go usage
|
|
21
|
-
5h
|
|
22
|
-
wk
|
|
23
|
-
mo
|
|
21
|
+
5h 2% · resets in 50m
|
|
22
|
+
wk 0% · resets in 4d 3h
|
|
23
|
+
mo 0% · resets in 25d 20h
|
|
24
24
|
```
|
|
25
25
|
|
|
26
|
+
A window whose provider status is anything other than `ok` renders that status after the countdown, so
|
|
27
|
+
an exhausted or over-limit window is visible instead of showing a bare percentage.
|
|
28
|
+
|
|
26
29
|
`/usage` (or the command palette) opens the same data as a detail dialog and forces a refresh.
|
|
27
30
|
|
|
28
31
|
## How it works
|
|
@@ -127,6 +130,10 @@ The workflow checks that the tag matches the version in the manifest, so a misty
|
|
|
127
130
|
instead of publishing the wrong thing. It runs the tests and the typecheck before publishing, and npm
|
|
128
131
|
attaches a provenance attestation automatically because the repository and the package are public.
|
|
129
132
|
|
|
133
|
+
After a successful publish the workflow opens a **release page for the tag**, with notes generated from the
|
|
134
|
+
commits since the previous release. That is where "what changed in this version" gets answered; edit the
|
|
135
|
+
notes afterwards when the generated list is broader than the package being released.
|
|
136
|
+
|
|
130
137
|
Versions are **independent per package**: touching one plugin never forces a release of another.
|
|
131
138
|
|
|
132
139
|
The repository root stays at `0.0.0` and is never published; it only holds the workspace configuration.
|
package/package.json
CHANGED
package/src/core/usage.test.ts
CHANGED
|
@@ -1,14 +1,19 @@
|
|
|
1
1
|
import { test } from "node:test"
|
|
2
2
|
import assert from "node:assert/strict"
|
|
3
3
|
|
|
4
|
+
import pkg from "../../package.json" with { type: "json" }
|
|
5
|
+
|
|
4
6
|
import {
|
|
5
7
|
errorSnapshot,
|
|
6
8
|
formatPercent,
|
|
7
9
|
formatReset,
|
|
8
|
-
|
|
10
|
+
formatStatus,
|
|
9
11
|
formatWindow,
|
|
12
|
+
formatWindowDetail,
|
|
10
13
|
parseSnapshot,
|
|
11
14
|
parseUsage,
|
|
15
|
+
USER_AGENT,
|
|
16
|
+
type UsageWindow,
|
|
12
17
|
} from "./usage.ts"
|
|
13
18
|
|
|
14
19
|
const NOW = Date.parse("2026-10-14T20:35:38.000Z")
|
|
@@ -85,6 +90,15 @@ test("parseUsage reports an error snapshot for a non-object payload", () => {
|
|
|
85
90
|
}
|
|
86
91
|
})
|
|
87
92
|
|
|
93
|
+
test("parseUsage carries the provider status through untouched", () => {
|
|
94
|
+
const snapshot = parseUsage(
|
|
95
|
+
{ usage: { rolling: { status: "overage", percent: 100, resetsAt: "2026-10-14T21:25:45.000Z" } } },
|
|
96
|
+
NOW,
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
assert.equal(snapshot.windows[0]?.status, "overage")
|
|
100
|
+
})
|
|
101
|
+
|
|
88
102
|
test("formatPercent renders an integer percent or an em dash", () => {
|
|
89
103
|
assert.equal(formatPercent(2), "2%")
|
|
90
104
|
assert.equal(formatPercent(0), "0%")
|
|
@@ -109,19 +123,44 @@ test("formatReset handles the last minutes, the expired case and unknown input",
|
|
|
109
123
|
assert.equal(formatReset("not-a-date", now), "unknown")
|
|
110
124
|
})
|
|
111
125
|
|
|
112
|
-
test("formatWindow and
|
|
126
|
+
test("formatWindow and formatWindowDetail compose the rendered line", () => {
|
|
113
127
|
const snapshot = parseUsage(LIVE_PAYLOAD, NOW)
|
|
114
128
|
|
|
115
129
|
assert.equal(formatWindow(snapshot.windows[0]!, NOW), "5h 2% · resets in 50m")
|
|
116
130
|
assert.equal(formatWindow(snapshot.windows[1]!, NOW), "wk 0% · resets in 4d 3h")
|
|
117
|
-
assert.equal(
|
|
131
|
+
assert.equal(formatWindowDetail(snapshot.windows[0]!, NOW), "5h window 2% resets in 50m")
|
|
118
132
|
})
|
|
119
133
|
|
|
120
|
-
test("
|
|
134
|
+
test("formatStatus stays silent in the steady state and surfaces anything else", () => {
|
|
135
|
+
// `ok` is the steady state, so it adds nothing to the line.
|
|
136
|
+
assert.equal(formatStatus("ok"), "")
|
|
137
|
+
assert.equal(formatStatus(null), "")
|
|
138
|
+
assert.equal(formatStatus(""), " ()")
|
|
139
|
+
|
|
140
|
+
// Any other status is unknown territory on an undocumented endpoint: show it
|
|
141
|
+
// verbatim rather than guessing a friendlier wording.
|
|
142
|
+
assert.equal(formatStatus("overage"), " (overage)")
|
|
143
|
+
assert.equal(formatStatus("exceeded"), " (exceeded)")
|
|
144
|
+
})
|
|
145
|
+
|
|
146
|
+
test("formatWindow and formatWindowDetail surface a non-ok provider status", () => {
|
|
147
|
+
const window: UsageWindow = {
|
|
148
|
+
key: "rolling",
|
|
149
|
+
short: "5h",
|
|
150
|
+
long: "5h window",
|
|
151
|
+
percent: 100,
|
|
152
|
+
resetsAt: "2026-10-14T21:25:45.000Z",
|
|
153
|
+
status: "overage",
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
assert.equal(formatWindow(window, NOW), "5h 100% · resets in 50m (overage)")
|
|
157
|
+
assert.equal(formatWindowDetail(window, NOW), "5h window 100% resets in 50m (overage)")
|
|
158
|
+
})
|
|
159
|
+
|
|
160
|
+
test("an error snapshot renders as an unknown window, not fabricated percentages", () => {
|
|
121
161
|
const snapshot = errorSnapshot("Go integration is not connected", NOW)
|
|
122
162
|
|
|
123
163
|
assert.equal(snapshot.ok, false)
|
|
124
|
-
assert.equal(formatSummary(snapshot, NOW), "GO usage unavailable")
|
|
125
164
|
assert.equal(formatWindow(snapshot.windows[0]!, NOW), "5h — · unknown")
|
|
126
165
|
})
|
|
127
166
|
|
|
@@ -138,7 +177,6 @@ test("parseSnapshot accepts a snapshot that crossed the RPC boundary", () => {
|
|
|
138
177
|
)
|
|
139
178
|
assert.equal(received!.windows[0]!.short, "5h")
|
|
140
179
|
assert.equal(received!.windows[0]!.long, "5h window")
|
|
141
|
-
assert.equal(formatSummary(received!, NOW), "GO 5h 2% · wk 0% · mo 0%")
|
|
142
180
|
})
|
|
143
181
|
|
|
144
182
|
test("parseSnapshot rejects anything that is not a snapshot object", () => {
|
|
@@ -165,3 +203,8 @@ test("parseSnapshot degrades a malformed window entry instead of throwing", () =
|
|
|
165
203
|
assert.equal(received!.windows[2]!.key, "monthly")
|
|
166
204
|
})
|
|
167
205
|
|
|
206
|
+
test("USER_AGENT tracks the published package version", () => {
|
|
207
|
+
// Guards the drift a hardcoded literal invites: bump the manifest without
|
|
208
|
+
// touching the constant and this fails before the package is published.
|
|
209
|
+
assert.equal(USER_AGENT, `opencode-go-usage/${pkg.version}`)
|
|
210
|
+
})
|
package/src/core/usage.ts
CHANGED
|
@@ -17,6 +17,17 @@
|
|
|
17
17
|
* plain `node --test`.
|
|
18
18
|
*/
|
|
19
19
|
|
|
20
|
+
/**
|
|
21
|
+
* User agent sent to the usage endpoint.
|
|
22
|
+
*
|
|
23
|
+
* The version travels with the request so a provider-side failure is
|
|
24
|
+
* attributable to a build. It stays a literal deliberately: `usage.test.ts`
|
|
25
|
+
* pins it to `package.json`, so a release bump fails the suite before publish
|
|
26
|
+
* (bump this line alongside the manifest) instead of adding a JSON import to
|
|
27
|
+
* the server plugin's load path.
|
|
28
|
+
*/
|
|
29
|
+
export const USER_AGENT = "opencode-go-usage/0.1.3"
|
|
30
|
+
|
|
20
31
|
export const WINDOW_KEYS = ["rolling", "weekly", "monthly"] as const
|
|
21
32
|
|
|
22
33
|
export type WindowKey = (typeof WINDOW_KEYS)[number]
|
|
@@ -173,9 +184,24 @@ export function formatReset(resetsAt: string | null, now: number): string {
|
|
|
173
184
|
return `resets in ${minutes}m`
|
|
174
185
|
}
|
|
175
186
|
|
|
187
|
+
/**
|
|
188
|
+
* Provider status that qualifies the percentage, e.g. ` (overage)`.
|
|
189
|
+
*
|
|
190
|
+
* `ok` is the steady state and renders as nothing, so the common line is
|
|
191
|
+
* unchanged. Any other non-null status is surfaced verbatim: the endpoint is
|
|
192
|
+
* undocumented, so a status is the only signal that a window needs attention and
|
|
193
|
+
* it is not ours to reinterpret.
|
|
194
|
+
*/
|
|
195
|
+
export function formatStatus(status: string | null): string {
|
|
196
|
+
return status === null || status === "ok" ? "" : ` (${status})`
|
|
197
|
+
}
|
|
198
|
+
|
|
176
199
|
/** One window as a single line, e.g. `5h 2% · resets in 50m`. */
|
|
177
200
|
export function formatWindow(window: UsageWindow, now: number): string {
|
|
178
|
-
return `${window.short} ${formatPercent(window.percent)} · ${formatReset(
|
|
201
|
+
return `${window.short} ${formatPercent(window.percent)} · ${formatReset(
|
|
202
|
+
window.resetsAt,
|
|
203
|
+
now,
|
|
204
|
+
)}${formatStatus(window.status)}`
|
|
179
205
|
}
|
|
180
206
|
|
|
181
207
|
/** One window as a detail row, e.g. `5h window 2% resets in 50m`. */
|
|
@@ -183,11 +209,5 @@ export function formatWindowDetail(window: UsageWindow, now: number): string {
|
|
|
183
209
|
return `${window.long.padEnd(10)} ${formatPercent(window.percent).padStart(4)} ${formatReset(
|
|
184
210
|
window.resetsAt,
|
|
185
211
|
now,
|
|
186
|
-
)}`
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
/** Compact status line for the TUI slot, e.g. `GO 5h 2% · wk 0% · mo 0%`. */
|
|
190
|
-
export function formatSummary(snapshot: UsageSnapshot, _now: number): string {
|
|
191
|
-
if (!snapshot.ok) return "GO usage unavailable"
|
|
192
|
-
return `GO ${snapshot.windows.map((w) => `${w.short} ${formatPercent(w.percent)}`).join(" · ")}`
|
|
212
|
+
)}${formatStatus(window.status)}`
|
|
193
213
|
}
|
package/src/index.ts
CHANGED
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
import { Plugin } from "@opencode/plugin"
|
|
2
2
|
|
|
3
|
-
import { errorSnapshot, parseUsage, type UsageSnapshot } from "./core/usage.ts"
|
|
3
|
+
import { errorSnapshot, parseUsage, USER_AGENT, type UsageSnapshot } from "./core/usage.ts"
|
|
4
4
|
import { UsageRpc } from "./rpc.ts"
|
|
5
5
|
|
|
6
6
|
const INTEGRATION_ID = "opencode-go"
|
|
7
7
|
const USAGE_URL = "https://opencode.ai/inference/go/v1/usage"
|
|
8
|
-
const USER_AGENT = "opencode-go-usage/0.1.0"
|
|
9
8
|
|
|
10
9
|
/** Usage moves slowly; the endpoint is undocumented, so we stay conservative. */
|
|
11
10
|
const REFRESH_MS = 5 * 60_000
|
|
@@ -75,12 +74,20 @@ export default Plugin.define({
|
|
|
75
74
|
inFlight = null
|
|
76
75
|
}
|
|
77
76
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
77
|
+
// Publishing must not fail the refresh: the snapshot is already read, and
|
|
78
|
+
// `refresh()` is awaited by setup and fired-and-forgotten by the interval,
|
|
79
|
+
// where a rejection would surface as an unhandled rejection.
|
|
80
|
+
try {
|
|
81
|
+
await registration.events.emit("updated", {
|
|
82
|
+
fetchedAt: snapshot.fetchedAt,
|
|
83
|
+
ok: snapshot.ok,
|
|
84
|
+
error: snapshot.error,
|
|
85
|
+
windows: snapshot.windows.map((window) => ({ ...window })),
|
|
86
|
+
})
|
|
87
|
+
} catch {
|
|
88
|
+
// Subscribers are optional; the caller still gets the fresh snapshot.
|
|
89
|
+
}
|
|
90
|
+
|
|
84
91
|
return snapshot
|
|
85
92
|
}
|
|
86
93
|
|