opencode-plugboard 0.21.1 → 0.21.2
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 +2 -2
- package/package.json +1 -1
- package/src/usage.ts +24 -23
package/README.md
CHANGED
|
@@ -43,7 +43,7 @@ export PLUGBOARD_KEY_MY_APP=choose-a-long-random-string
|
|
|
43
43
|
{
|
|
44
44
|
"plugins": [
|
|
45
45
|
{
|
|
46
|
-
"package": "opencode-plugboard@0.21.
|
|
46
|
+
"package": "opencode-plugboard@0.21.2",
|
|
47
47
|
"options": {
|
|
48
48
|
"keys": { "my-app": "{env:PLUGBOARD_KEY_MY_APP}" },
|
|
49
49
|
"models": ["anthropic/claude-sonnet-4-6", "github-copilot/*", "opencode-go/glm-5.3-flash"],
|
|
@@ -390,7 +390,7 @@ A server shared by a Discord bot, n8n, Open WebUI and a coding agent, with most
|
|
|
390
390
|
{
|
|
391
391
|
"plugins": [
|
|
392
392
|
{
|
|
393
|
-
"package": "opencode-plugboard@0.21.
|
|
393
|
+
"package": "opencode-plugboard@0.21.2",
|
|
394
394
|
"options": {
|
|
395
395
|
// Reachable from the LAN. Non-loopback hosts refuse to start without keys.
|
|
396
396
|
"listen": { "host": "10.0.1.50", "port": 4010 },
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opencode-plugboard",
|
|
3
|
-
"version": "0.21.
|
|
3
|
+
"version": "0.21.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"description": "Use the ChatGPT, Claude, Copilot and OpenCode Go logins in your OpenCode server from any app: an OpenCode V2 plugin serving OpenAI, Anthropic and Gemini compatible APIs.",
|
package/src/usage.ts
CHANGED
|
@@ -9,10 +9,11 @@
|
|
|
9
9
|
* Where it lives: the live aggregate is in memory on the process runtime (`src/runtime.ts`), so it
|
|
10
10
|
* survives a plugin generation, and each day is persisted to the plugin's host storage as JSON under
|
|
11
11
|
* `usage/<app>/<YYYY-MM-DD>`. Every write comes from request handling (`withUsage` and the route's
|
|
12
|
-
* request log point), never from plugin setup or a finalizer (docs/architecture.md, I23). A
|
|
13
|
-
* written
|
|
14
|
-
*
|
|
15
|
-
*
|
|
12
|
+
* request log point), never from plugin setup or a finalizer (docs/architecture.md, I23). A change is
|
|
13
|
+
* written as it lands; while a write of that day is in flight, later changes wait for it and go out
|
|
14
|
+
* in the next write, so the stored copy keeps up with every change (#241: a debounce kept the request
|
|
15
|
+
* count of a burst in memory, and a restart dropped it). Queries read memory first, so a
|
|
16
|
+
* report is never stale.
|
|
16
17
|
*
|
|
17
18
|
* Retention: days older than `usage.retentionDays` are deleted from request handling, one bounded
|
|
18
19
|
* page of keys at a time and at most once per `PRUNE_EVERY_MS`.
|
|
@@ -48,8 +49,6 @@ export const GENERATION_ROUTES: ReadonlySet<string> = new Set([
|
|
|
48
49
|
|
|
49
50
|
const DAY_MS = 86_400_000
|
|
50
51
|
const KEY_PREFIX = "usage/"
|
|
51
|
-
/** A changed day is written at most this often. */
|
|
52
|
-
const FLUSH_MS = 10_000
|
|
53
52
|
/** Retention runs at most this often, and each run reads one page of keys. */
|
|
54
53
|
const PRUNE_EVERY_MS = 10 * 60_000
|
|
55
54
|
const PRUNE_PAGE = 200
|
|
@@ -417,7 +416,6 @@ interface Entry {
|
|
|
417
416
|
/** Bumped by every change; `flushedVersion` is the version last persisted. */
|
|
418
417
|
version: number
|
|
419
418
|
flushedVersion: number
|
|
420
|
-
flushedAt: number
|
|
421
419
|
flushing?: Promise<void>
|
|
422
420
|
}
|
|
423
421
|
|
|
@@ -517,28 +515,31 @@ export function createUsageRecorder(deps: UsageRecorderDeps): UsageRecorder {
|
|
|
517
515
|
// Another request may have loaded the day while this one read storage: keep the first entry.
|
|
518
516
|
const current = state.entries.get(key)
|
|
519
517
|
if (current) return current
|
|
520
|
-
const entry: Entry = { key, app, date, day: stored ?? emptyDay(), version: 0, flushedVersion: 0
|
|
518
|
+
const entry: Entry = { key, app, date, day: stored ?? emptyDay(), version: 0, flushedVersion: 0 }
|
|
521
519
|
state.entries.set(key, entry)
|
|
522
520
|
return entry
|
|
523
521
|
}
|
|
524
522
|
|
|
525
|
-
/**
|
|
526
|
-
|
|
527
|
-
|
|
523
|
+
/**
|
|
524
|
+
* Persists a changed day. The JSON is taken now. A write already in flight is not raced: when it lands,
|
|
525
|
+
* a change made meanwhile is written next. A failed write is logged and the day stays changed, so the
|
|
526
|
+
* next change tries again.
|
|
527
|
+
*/
|
|
528
|
+
function flushIfDue(entry: Entry): void {
|
|
529
|
+
if (entry.version === entry.flushedVersion || entry.flushing !== undefined) return
|
|
528
530
|
const version = entry.version
|
|
529
531
|
const json = JSON.stringify(entry.day)
|
|
530
|
-
entry.
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
.then(
|
|
534
|
-
() => {
|
|
535
|
-
entry.flushedVersion = version
|
|
536
|
-
},
|
|
537
|
-
(error) => logError("usage.flush_failed", { app: entry.app, date: entry.date, error: errorMessage(error) }),
|
|
538
|
-
)
|
|
539
|
-
.finally(() => {
|
|
532
|
+
entry.flushing = storage.put(entry.key, json).then(
|
|
533
|
+
() => {
|
|
534
|
+
entry.flushedVersion = version
|
|
540
535
|
entry.flushing = undefined
|
|
541
|
-
|
|
536
|
+
flushIfDue(entry)
|
|
537
|
+
},
|
|
538
|
+
(error) => {
|
|
539
|
+
entry.flushing = undefined
|
|
540
|
+
logError("usage.flush_failed", { app: entry.app, date: entry.date, error: errorMessage(error) })
|
|
541
|
+
},
|
|
542
|
+
)
|
|
542
543
|
}
|
|
543
544
|
|
|
544
545
|
/** Deletes the days older than the retention window: one page of keys per run, at most once per `PRUNE_EVERY_MS`. */
|
|
@@ -576,7 +577,7 @@ export function createUsageRecorder(deps: UsageRecorderDeps): UsageRecorder {
|
|
|
576
577
|
if (!entry) return
|
|
577
578
|
update(entry.day)
|
|
578
579
|
entry.version++
|
|
579
|
-
flushIfDue(entry
|
|
580
|
+
flushIfDue(entry)
|
|
580
581
|
maybePrune(at)
|
|
581
582
|
})
|
|
582
583
|
.catch((error) => logError("usage.record_failed", { app, error: errorMessage(error) }))
|