opencode-plugboard 0.21.1 → 0.22.0
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 +24 -2
- package/package.json +2 -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.
|
|
46
|
+
"package": "opencode-plugboard@0.22.0",
|
|
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.
|
|
393
|
+
"package": "opencode-plugboard@0.22.0",
|
|
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 },
|
|
@@ -460,6 +460,17 @@ One page per client, with the config, a chat, a tool call, structured output and
|
|
|
460
460
|
| pydantic-ai | [pydantic-ai][client-pydantic-ai] | pass with `OpenAIChatModel` |
|
|
461
461
|
| Open WebUI | [open-webui][client-open-webui] | pass over its HTTP API; no browser, no structured output in its UI |
|
|
462
462
|
| n8n | [n8n][client-n8n] | pass with "Use Responses API" off |
|
|
463
|
+
| Claude Code | [claude-code][client-claude-code] | pass: text and a tool turn through `/v1/messages`; needs `maxTools` above 36 |
|
|
464
|
+
| Codex CLI | [codex-cli][client-codex-cli] | docs only, not run yet (Responses API) |
|
|
465
|
+
| Continue | [continue][client-continue] | docs only, not run yet |
|
|
466
|
+
| Kilo Code | [kilo-code][client-kilo-code] | docs only, not run yet |
|
|
467
|
+
| LibreChat | [librechat][client-librechat] | docs only, not run yet |
|
|
468
|
+
| Zed | [zed][client-zed] | docs only, not run yet |
|
|
469
|
+
| OpenAI Agents SDK (Python) | [openai-agents][client-openai-agents] | pass on Responses and Chat Completions: text, stream, tools, structured output |
|
|
470
|
+
| LiteLLM (as a client) | [litellm-client][client-litellm] | pass: text, stream, tools |
|
|
471
|
+
| Instructor | [instructor][client-instructor] | pass: TOOLS, JSON_SCHEMA and partial streaming modes |
|
|
472
|
+
| DSPy | [dspy][client-dspy] | pass: Predict, ChainOfThought, typed output |
|
|
473
|
+
| Aider | [aider][client-aider] | pass: one scripted `--message` run |
|
|
463
474
|
| Anything else | [any-app][client-any-app] | base URL, key, model id |
|
|
464
475
|
|
|
465
476
|
Each run drove the client's own SDK or API against a dev server at an earlier commit: a plain chat, a tool round trip, streaming, an image and structured output on `fast`, a Claude model and a ChatGPT model. A pass is not a clean sweep, and the notes say where it was not. The latest full run is in [compat/results/release-candidate.md][release-candidate].
|
|
@@ -538,4 +549,15 @@ Not affiliated with or endorsed by OpenAI, Anthropic, Google, GitHub or Microsof
|
|
|
538
549
|
[client-pydantic-ai]: https://jjjermiah.github.io/opencode-plugboard/clients/pydantic-ai
|
|
539
550
|
[client-open-webui]: https://jjjermiah.github.io/opencode-plugboard/clients/open-webui
|
|
540
551
|
[client-n8n]: https://jjjermiah.github.io/opencode-plugboard/clients/n8n
|
|
552
|
+
[client-claude-code]: https://jjjermiah.github.io/opencode-plugboard/clients/claude-code
|
|
553
|
+
[client-codex-cli]: https://jjjermiah.github.io/opencode-plugboard/clients/codex-cli
|
|
554
|
+
[client-continue]: https://jjjermiah.github.io/opencode-plugboard/clients/continue
|
|
555
|
+
[client-kilo-code]: https://jjjermiah.github.io/opencode-plugboard/clients/kilo-code
|
|
556
|
+
[client-librechat]: https://jjjermiah.github.io/opencode-plugboard/clients/librechat
|
|
557
|
+
[client-zed]: https://jjjermiah.github.io/opencode-plugboard/clients/zed
|
|
558
|
+
[client-openai-agents]: https://jjjermiah.github.io/opencode-plugboard/clients/openai-agents
|
|
559
|
+
[client-litellm]: https://jjjermiah.github.io/opencode-plugboard/clients/litellm-client
|
|
560
|
+
[client-instructor]: https://jjjermiah.github.io/opencode-plugboard/clients/instructor
|
|
561
|
+
[client-dspy]: https://jjjermiah.github.io/opencode-plugboard/clients/dspy
|
|
562
|
+
[client-aider]: https://jjjermiah.github.io/opencode-plugboard/clients/aider
|
|
541
563
|
[client-any-app]: https://jjjermiah.github.io/opencode-plugboard/clients/any-app
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opencode-plugboard",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.22.0",
|
|
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.",
|
|
@@ -32,6 +32,7 @@
|
|
|
32
32
|
"flake-hunt": "bun .github/scripts/flake-hunt.ts",
|
|
33
33
|
"coverage-manifest": "bun .github/scripts/coverage-manifest.ts",
|
|
34
34
|
"live-matrix": "bun test/live/matrix.ts",
|
|
35
|
+
"compat:python": "uv run --project compat/py python compat/py/clients/run_all.py",
|
|
35
36
|
"build:tui": "bun .github/scripts/build-tui.ts",
|
|
36
37
|
"prepack": "bun run build:tui",
|
|
37
38
|
"smoke:pack": "bun .github/scripts/smoke-pack.ts",
|
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) }))
|