opencode-ufr 0.2.0 → 0.2.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 +82 -59
- package/package.json +37 -9
package/README.md
CHANGED
|
@@ -1,71 +1,100 @@
|
|
|
1
1
|
# opencode-ufr
|
|
2
2
|
|
|
3
|
-
An [opencode](https://opencode.ai) provider for Uni Freiburg's Open WebUI
|
|
4
|
-
models
|
|
3
|
+
An [opencode](https://opencode.ai) provider for **Uni Freiburg's Open WebUI
|
|
4
|
+
models**. A small local gateway runs on your machine, pools your UFR API
|
|
5
5
|
key(s), and handles UFR's rate limits and outages so opencode doesn't have to.
|
|
6
|
+
Off campus, it connects through the uni's Fortinet VPN **by itself** — with no
|
|
7
|
+
TUN device, no admin rights and no external tools.
|
|
6
8
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
login once (`ufr login add <user>`) and the plugin tunnels its UFR calls
|
|
15
|
-
through `fortivpn.uni-freiburg.de` — with no TUN device, no admin rights, no
|
|
16
|
-
openconnect and no changes to your routing. Your own VPNs keep working
|
|
17
|
-
untouched in parallel. See the RZ guide if you prefer a system VPN:
|
|
18
|
-
https://wiki.uni-freiburg.de/rz/doku.php?id=vpn
|
|
9
|
+
```
|
|
10
|
+
opencode ⇄ gateway (127.0.0.1) ⇄ [Fortinet tunnel] ⇄ openwebui.uni-freiburg.de
|
|
11
|
+
│
|
|
12
|
+
├─ key pool (1…n UFR accounts)
|
|
13
|
+
├─ rate-limit pacing, circuit breakers, fallbacks
|
|
14
|
+
└─ VPN when off campus (userspace TCP/IP stack)
|
|
15
|
+
```
|
|
19
16
|
|
|
20
17
|
## Install
|
|
21
18
|
|
|
22
|
-
Not on npm yet — install straight from GitHub:
|
|
23
|
-
|
|
24
19
|
```bash
|
|
25
|
-
bun add -g
|
|
26
|
-
ufr
|
|
20
|
+
bun add -g opencode-ufr # puts `ufr` on PATH
|
|
21
|
+
opencode plugin add opencode-ufr # registers the plugin
|
|
27
22
|
```
|
|
28
23
|
|
|
29
|
-
`ufr
|
|
30
|
-
around keys is filtered, aliases (key1, key2, …) are assigned automatically —
|
|
31
|
-
and optionally your uni login for the built-in VPN. Everything is verified
|
|
32
|
-
against UFR and stored in the OS keyring (never on disk).
|
|
24
|
+
(or add `"plugins": ["opencode-ufr"]` to your `opencode.json` manually.)
|
|
33
25
|
|
|
34
|
-
|
|
35
|
-
`/connect` panel's **Uni Freiburg** entry. It shows the API-key(s) field plus
|
|
36
|
-
**Uni login (optional)** and **Uni password (optional)** — both only needed on
|
|
37
|
-
a machine outside the uni network, where the built-in VPN uses them. Submitted
|
|
38
|
-
credentials land in the OS keyring automatically and the gateway restarts with
|
|
39
|
-
them.
|
|
26
|
+
## Setup
|
|
40
27
|
|
|
41
|
-
|
|
28
|
+
**Inside opencode — the normal way:** open `/connect`, pick **Uni Freiburg**,
|
|
29
|
+
and fill in:
|
|
42
30
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
31
|
+
| Field | Needed? |
|
|
32
|
+
|---|---|
|
|
33
|
+
| **API keys** | always — paste one key per UFR account, comma-separated (whitespace is filtered) |
|
|
34
|
+
| **Uni login** | only off campus — e.g. `fl240@uni-freiburg.de`, empty on the uni network |
|
|
35
|
+
| **Uni password** | only with a login above |
|
|
36
|
+
|
|
37
|
+
Submitted credentials land in your OS keyring (never on disk), the gateway
|
|
38
|
+
restarts with them, and the `unifreiburg/…` models appear in the model picker.
|
|
39
|
+
|
|
40
|
+
**In a terminal — the fallback:** works before opencode ever starts.
|
|
46
41
|
|
|
47
|
-
|
|
48
|
-
|
|
42
|
+
```bash
|
|
43
|
+
ufr connect --keys "<key1>,<key2>" # keys only
|
|
44
|
+
ufr connect --login fl240@uni-freiburg.de --password … --keys "…" # + VPN login
|
|
45
|
+
```
|
|
49
46
|
|
|
50
|
-
|
|
51
|
-
|
|
47
|
+
`ufr connect` verifies every key against UFR, assigns aliases (`key1`, `key2`,
|
|
48
|
+
…) automatically and accepts keys comma- or line-separated.
|
|
52
49
|
|
|
53
50
|
## Use
|
|
54
51
|
|
|
55
52
|
Models show up in opencode as `unifreiburg/<model>`, e.g.
|
|
56
|
-
`unifreiburg/ufr/coding-complex
|
|
53
|
+
`unifreiburg/ufr/coding-complex` or `unifreiburg/glm-5.3-flash-llmlb`. The
|
|
54
|
+
gateway starts itself the first time opencode needs it (the plugin waits up to
|
|
55
|
+
40 s) and shuts down after 5 minutes idle. You never manage it directly.
|
|
57
56
|
|
|
58
57
|
```bash
|
|
59
|
-
ufr status # gateway, keys, limits, breakers, spend today
|
|
58
|
+
ufr status # gateway, keys, limits, breakers, vpn, spend today
|
|
60
59
|
ufr stats # requests, tokens and cost
|
|
61
|
-
ufr keys
|
|
62
|
-
ufr keys
|
|
60
|
+
ufr keys list # stored aliases
|
|
61
|
+
ufr keys test # check your keys against UFR
|
|
63
62
|
ufr login show # the stored uni login
|
|
64
63
|
```
|
|
65
64
|
|
|
66
|
-
The
|
|
67
|
-
|
|
68
|
-
|
|
65
|
+
## The built-in VPN
|
|
66
|
+
|
|
67
|
+
On the campus network (or the VPN you already run) UFR is reached directly and
|
|
68
|
+
no tunnel opens. Off campus, the gateway:
|
|
69
|
+
|
|
70
|
+
1. logs in to `fortivpn.uni-freiburg.de` with your stored uni login (TLS),
|
|
71
|
+
2. negotiates PPP/IPCP over the Fortinet SSL-VPN channel (this is the same
|
|
72
|
+
protocol `openconnect --protocol=fortinet` speaks, implemented in pure
|
|
73
|
+
TypeScript — see `docs/fortinet-protocol.md`),
|
|
74
|
+
3. runs a **userspace TCP/IP stack inside the gateway process** and routes its
|
|
75
|
+
UFR calls through it via a localhost CONNECT proxy.
|
|
76
|
+
|
|
77
|
+
What that means in practice:
|
|
78
|
+
|
|
79
|
+
- **No TUN device, no routing table changes, no admin rights** — identical
|
|
80
|
+
code on Linux, macOS and Windows.
|
|
81
|
+
- **Your own VPNs keep working untouched in parallel** — the plugin's tunnel
|
|
82
|
+
exists only inside its own process and rides on whatever network the OS
|
|
83
|
+
provides.
|
|
84
|
+
- **Everything is encrypted twice**: the tunnel itself is TLS 1.3, and your
|
|
85
|
+
API calls are HTTPS end-to-end to `openwebui.uni-freiburg.de` inside it.
|
|
86
|
+
- `vpn.mode: "auto"` (default) only tunnels when UFR is unreachable directly;
|
|
87
|
+
`"always"` forces the tunnel (useful behind firewalls that block the campus
|
|
88
|
+
route).
|
|
89
|
+
- If the tunnel dies, it reconnects with backoff — no session is lost, the
|
|
90
|
+
gateway just waits until the path is back.
|
|
91
|
+
|
|
92
|
+
## More than one key
|
|
93
|
+
|
|
94
|
+
UFR allows one API key per account, so one key is one account's worth of
|
|
95
|
+
throughput. The key pool round-robins across all stored keys and keeps each
|
|
96
|
+
under UFR's per-key limits; adding more keys only helps if they belong to
|
|
97
|
+
different UFR accounts.
|
|
69
98
|
|
|
70
99
|
## What the gateway does
|
|
71
100
|
|
|
@@ -74,12 +103,6 @@ don't run or manage it directly.
|
|
|
74
103
|
- Enforces a pool-wide cap of 800 requests/hour across all keys and models —
|
|
75
104
|
UFR walls a model group for hours once it sees sustained traffic above
|
|
76
105
|
roughly 900/hour.
|
|
77
|
-
- **Connects to UFR on its own when you are off campus.** It logs in to the
|
|
78
|
-
uni's Fortinet gateway with your stored uni login, negotiates PPP/IPCP and
|
|
79
|
-
runs a userspace TCP/IP stack inside the gateway process: no TUN device, no
|
|
80
|
-
routes, no admin rights, identical on Linux, macOS and Windows. On campus it
|
|
81
|
-
talks to UFR directly and never opens the tunnel; `vpn.mode: "always"`
|
|
82
|
-
forces the tunnel.
|
|
83
106
|
- Runs a circuit breaker per model: after repeated failures it backs off from
|
|
84
107
|
30 s up to 60 minutes before probing again, instead of hammering a walled
|
|
85
108
|
model.
|
|
@@ -97,15 +120,10 @@ don't run or manage it directly.
|
|
|
97
120
|
unclean shutdown, the next start detects it's stale and takes it over
|
|
98
121
|
rather than refusing to start.
|
|
99
122
|
|
|
100
|
-
## More than one key
|
|
101
|
-
|
|
102
|
-
UFR allows one API key per account, so one key is one account's worth of
|
|
103
|
-
throughput. Adding more keys only helps if they belong to different UFR
|
|
104
|
-
accounts — it does not raise any one account's limit.
|
|
105
|
-
|
|
106
123
|
## Configuration
|
|
107
124
|
|
|
108
|
-
Non-secret settings live in a JSON file; keys always stay in
|
|
125
|
+
Non-secret settings live in a JSON file; keys and the uni login always stay in
|
|
126
|
+
the OS keyring.
|
|
109
127
|
|
|
110
128
|
- **Linux / macOS:** `~/.config/opencode-ufr/config.json` (state in
|
|
111
129
|
`~/.local/state/opencode-ufr`, cache in `~/.cache/opencode-ufr`, data in
|
|
@@ -152,9 +170,14 @@ except `port`, which is written on first start):
|
|
|
152
170
|
|
|
153
171
|
`port` is chosen once (preferred `47300`, next free port if taken) and then
|
|
154
172
|
stays fixed across restarts. `poolPerHour: 0` disables the pool limiter.
|
|
155
|
-
`transport.type: "direct"` never opens the tunnel
|
|
156
|
-
|
|
157
|
-
|
|
173
|
+
`transport.type: "direct"` never opens the tunnel.
|
|
174
|
+
|
|
175
|
+
## Releases
|
|
176
|
+
|
|
177
|
+
Releases are tag-driven: `scripts/release.sh 0.2.1` bumps, commits, tags and
|
|
178
|
+
pushes; the workflow then verifies on Ubuntu, macOS and Windows, stages the
|
|
179
|
+
package on npm (trusted publishing via OIDC — no stored tokens), and creates
|
|
180
|
+
the GitHub release after a maintainer approves the staged version with 2FA.
|
|
158
181
|
|
|
159
182
|
## Model data
|
|
160
183
|
|
package/package.json
CHANGED
|
@@ -1,16 +1,44 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opencode-ufr",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.1",
|
|
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",
|
|
7
7
|
"main": "./src/plugin/index.ts",
|
|
8
|
-
"exports": {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
"
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
"
|
|
8
|
+
"exports": {
|
|
9
|
+
".": "./src/plugin/index.ts"
|
|
10
|
+
},
|
|
11
|
+
"bin": {
|
|
12
|
+
"ufr": "bin/ufr.ts",
|
|
13
|
+
"opencode-ufr": "bin/ufr.ts"
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"bin",
|
|
17
|
+
"src",
|
|
18
|
+
"models.json",
|
|
19
|
+
"README.md",
|
|
20
|
+
"LICENSE"
|
|
21
|
+
],
|
|
22
|
+
"engines": {
|
|
23
|
+
"bun": ">=1.3.0"
|
|
24
|
+
},
|
|
25
|
+
"repository": {
|
|
26
|
+
"type": "git",
|
|
27
|
+
"url": "git+https://github.com/FinleyLaempe/opencode-ufr.git"
|
|
28
|
+
},
|
|
29
|
+
"keywords": [
|
|
30
|
+
"opencode",
|
|
31
|
+
"opencode-plugin",
|
|
32
|
+
"uni-freiburg",
|
|
33
|
+
"open-webui"
|
|
34
|
+
],
|
|
35
|
+
"scripts": {
|
|
36
|
+
"test": "bun test",
|
|
37
|
+
"typecheck": "tsc --noEmit",
|
|
38
|
+
"prepublishOnly": "bun run typecheck && bun test"
|
|
39
|
+
},
|
|
40
|
+
"devDependencies": {
|
|
41
|
+
"@types/bun": "^1.3.0",
|
|
42
|
+
"typescript": "^5.9.0"
|
|
43
|
+
}
|
|
16
44
|
}
|