opencode-ufr 0.2.3 → 0.2.5

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
@@ -23,6 +23,23 @@ opencode plugin add opencode-ufr # registers the plugin
23
23
 
24
24
  (or add `"plugins": ["opencode-ufr"]` to your `opencode.json` manually.)
25
25
 
26
+ ## Updating
27
+
28
+ opencode loads plugins inside a background server (`opencode serve --service`)
29
+ that keeps running after you close the TUI, and it keeps using the plugin
30
+ version it loaded when it started — a new release only arrives once that server
31
+ restarts:
32
+
33
+ ```bash
34
+ bun add -g opencode-ufr@latest # update the `ufr` CLI
35
+ pkill -f "opencode serve --service" # stop opencode's background server
36
+ ```
37
+
38
+ The next opencode start installs the latest plugin. If it still loads the old
39
+ one, delete opencode's cached copy (`~/.cache/opencode/npm/opencode-ufr@latest`)
40
+ and start opencode again. On Windows, end the `opencode` server process in Task
41
+ Manager instead of `pkill`.
42
+
26
43
  ## Setup
27
44
 
28
45
  **Inside opencode — the normal way:** open `/connect`, pick **Uni Freiburg**,
@@ -66,9 +83,9 @@ ufr disconnect # remove everything: keys, uni login, config, gateway
66
83
  Removing the **unifreiburg** integration in opencode's `/connect` panel does the
67
84
  same as `ufr disconnect`: the stored keys and uni login are wiped, the gateway
68
85
  stops, and the models disappear from the picker on the next opencode start.
69
- While opencode runs, the removal is noticed within a minute. If opencode was
70
- closed when you removed the integration, run `ufr disconnect` once to wipe what
71
- is left.
86
+ While opencode runs, connecting and removing are noticed within seconds. If
87
+ opencode was closed when you removed the integration, run `ufr disconnect` once
88
+ to wipe what is left.
72
89
 
73
90
  ## The built-in VPN
74
91
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-ufr",
3
- "version": "0.2.3",
3
+ "version": "0.2.5",
4
4
  "description": "Uni Freiburg (UFR) Open WebUI models in opencode — local gateway with key pool, UFR-aware rate limiting, circuit breaker and fallbacks",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -13,6 +13,9 @@ import { applyKeyChange, disconnectAll } from "../cli/keys"
13
13
 
14
14
  export const UFR_INTEGRATION_ID = "unifreiburg"
15
15
 
16
+ /** opencode has no connection events for plugins, so the watcher polls; seconds keep /connect feeling immediate. */
17
+ export const CONNECT_POLL_MS = 5_000
18
+
16
19
  export type ConnectDeps = {
17
20
  ctx: any // opencode plugin context (integration + storage domains)
18
21
  paths: Paths
@@ -23,6 +26,8 @@ export type ConnectDeps = {
23
26
  register?: () => Promise<boolean | void> | boolean | void
24
27
  /** Whether setup() already registered the provider (it does when a connection exists at startup). */
25
28
  providerRegistered?: boolean
29
+ /** Poll interval override (tests). */
30
+ pollMs?: number
26
31
  }
27
32
 
28
33
  type CredentialValue = { type?: string; key?: string; configuration?: Record<string, unknown> } | undefined
@@ -131,7 +136,18 @@ export async function registerConnect(d: ConnectDeps): Promise<ConnectRegistrati
131
136
  }
132
137
 
133
138
  await applyOnce()
134
- const timer = setInterval(() => void applyOnce(), 60_000)
139
+ // A registration can take ~40 s (gateway boot) — never start a second one while it runs.
140
+ let busy = false
141
+ const tick = async () => {
142
+ if (busy) return
143
+ busy = true
144
+ try {
145
+ await applyOnce()
146
+ } finally {
147
+ busy = false
148
+ }
149
+ }
150
+ const timer = setInterval(() => void tick(), d.pollMs ?? CONNECT_POLL_MS)
135
151
  ;(timer as { unref?: () => void }).unref?.()
136
152
  return {
137
153
  stop: () => clearInterval(timer),
@@ -10,6 +10,11 @@ import { toModelInfo } from "./model-info"
10
10
 
11
11
  const DAEMON_ENTRY = fileURLToPath(new URL("../daemon/main.ts", import.meta.url))
12
12
 
13
+ // opencode takes a request's key from the provider's integration (default: the one with the provider's
14
+ // id), and a key credential there replaces settings.apiKey. The /connect credential on "unifreiburg"
15
+ // holds the UFR keys, not the gateway token — so link the provider to an integration with no credential.
16
+ const GATEWAY_INTEGRATION_ID = "opencode-ufr-gateway"
17
+
13
18
  function log(msg: string): void {
14
19
  console.warn(`[opencode-ufr] ${msg}`)
15
20
  }
@@ -58,6 +63,7 @@ async function registerProvider(
58
63
  info: {
59
64
  id: providerId,
60
65
  name: providerName,
66
+ integrationID: GATEWAY_INTEGRATION_ID,
61
67
  activation: "enabled",
62
68
  package: "@opencode/ai/providers/openai-compatible",
63
69
  settings: { baseURL: `http://127.0.0.1:${conn.port}/v1`, apiKey: conn.token },