Split README into user guide and DEVELOPMENT.md
Some checks failed
CI / test (22) (push) Failing after 35s
CI / test (24) (push) Failing after 36s

This commit is contained in:
Benjamin Hackl 2026-11-03 21:02:17 +01:00
commit 8172f41c56
3 changed files with 144 additions and 58 deletions

78
DEVELOPMENT.md Normal file
View file

@ -0,0 +1,78 @@
# Development
## Setup
```sh
pnpm install
pnpm run ci # type check + tests
```
To test your checkout in pi, install it from the local path:
```sh
pi install /absolute/path/to/pi-unigpt
```
Changes take effect after `/reload`.
Tests use fake keys, mocked discovery, and a local HTTP server. They never
contact uniGPT or read your pi configuration.
## Layout
| File | Purpose |
| --- | --- |
| `src/provider.ts` | The pi provider: login, model discovery, model metadata |
| `src/index.ts` | Extension entry point and the `/unigpt-refresh` command |
| `test/provider.test.ts` | Discovery, login, caching, and transport tests |
| `test/command.test.ts` | `/unigpt-refresh` behavior |
## How it works
**Endpoints.** The API base is
`https://api.mygpt.uni-graz.at/api/v1/llms/openai`. Discovery calls
`GET /models` with bearer auth; inference uses pi's built-in OpenAI
Responses transport on `POST /responses`. Every request must also carry a
`deployment-id` header naming the model, which each discovered model sets.
**Model metadata.** `/models` returns only IDs (e.g. `gpt-5.4`). Those IDs
are OpenAI model names, so `lookupCatalogMetadata` reads name, context
window, output limit, and image support from pi's bundled catalog
(`openai` and `azure` providers), taking the smaller value where the two
differ. This keeps the extension free of a hardcoded model table: new
models get correct limits when pi updates. Unknown IDs fall back to pi's
defaults for custom models (128k context, 16k output, text only).
The uniGPT web app has richer endpoints
(`/api/v1/tenants/uni/ai-models[/detailed]`) and an OpenAPI spec
(`/api/v1/docs/openapi.yaml`), but they require a browser session token
and reject API keys. If uniGPT ever exposes limits to API keys, prefer
them over the catalog lookup.
**Reasoning.** uniGPT ignores `reasoning.effort` and applies a fixed effort
per deployment (verified against the live API), so every model sets
`reasoning: false` to avoid offering thinking levels that do nothing.
**Caching and errors.** pi persists the discovered catalog and restores it
offline. A failed or malformed refresh keeps the previous catalog; single
malformed entries are skipped. Error messages never include the API key or
server response bodies.
**Login.** `/login unigpt` validates the key against `/models` before pi
stores it. Validation always uses the default endpoint, even if a user
overrides `baseUrl` in `models.json`.
## Known server-side issues
As of November 2026, `gpt-5-nano` and `gpt-4.1-nano` return HTTP 500
(`'NoneType' object has no attribute 'decrypt'`) for every request.
## Releasing
1. Run `pnpm run ci`.
2. Bump `version` in `package.json` and commit.
3. Tag and push: `git tag vX.Y.Z && git push origin main vX.Y.Z`.
4. Update the version in the README's "Pinning a version" example.
CI is defined in `.gitea/workflows/ci.yml` and runs once Gitea Actions are
enabled for the repository.

115
README.md
View file

@ -1,16 +1,16 @@
# pi-unigpt
Use [uniGPT](https://unigpt.uni-graz.at), the University of Graz's AI
platform, as a model provider in [pi](https://pi.dev).
assistant, inside the [pi](https://pi.dev) coding agent.
Models are discovered automatically, and their limits come from pi's model
catalog, so there is no model list to maintain. Tool calling, streaming, and
image input work.
Once set up, every model your uniGPT account offers appears in pi's model
picker, including GPT-4o, GPT-4.1, and the GPT-5 and GPT-6 families. Tools,
streaming responses, and image input all work.
## Requirements
## What you need
- pi **1.5.x** and Node.js **22.19+**
- A uniGPT **API key**. If you don't have one, ask the uniGPT team at the
- **pi 1.5 or newer** ([installation guide](https://pi.dev))
- **A uniGPT API key.** If you don't have one, ask the uniGPT team at the
[IDea_Lab](https://idea-lab.uni-graz.at/en/digital-innovation-lab/unigpt-and-studigpt).
## Setup
@ -21,27 +21,25 @@ image input work.
pi install git:imsc.uni-graz.at/git/behackl/pi-unigpt
```
To stay on a fixed release instead of following the latest version, add the
tag: `…/pi-unigpt@v0.1.0`.
**2. Log in**
Start pi (or run `/reload` if it is already running) and enter:
Start pi (or type `/reload` if it is already running), then type:
```text
/login unigpt
```
Paste your API key into the masked dialog, **not into the chat**. Pi checks
the key and reports how many models it can use.
A dialog asks for your API key. Paste it there, **not into the chat**. Pi
checks the key and tells you how many models you can use.
**3. Pick a model**
Run `/model` and choose one of the models listed under `unigpt`.
Type `/model` and choose one of the models listed under **unigpt**.
**4. Optional: make uniGPT your default**
That's it. Pi remembers your key and your last model for next time.
Add this to `~/.pi/agent/settings.json` to start pi with a uniGPT model:
**Optional: start pi with uniGPT by default.** Add this to
`~/.pi/agent/settings.json`, using any model from the list:
```json
{
@ -52,18 +50,48 @@ Add this to `~/.pi/agent/settings.json` to start pi with a uniGPT model:
## Commands
| Command | Purpose |
| Command | What it does |
| --- | --- |
| `/login unigpt` | Save and verify your API key |
| `/model` | Pick a model (also reloads the model list) |
| `/unigpt-refresh` | Reload the model list and report any problem |
| `/logout` | Remove the saved key from pi (it stays valid at uniGPT) |
| `/login unigpt` | Save your API key (or replace it with a new one) |
| `/model` | Choose a model |
| `/unigpt-refresh` | Reload the list of models, e.g. after uniGPT adds new ones |
| `/logout` | Remove your key from pi (the key itself stays valid) |
## Keeping your key out of auth.json
## Updating
Pi saves the key unencrypted in `~/.pi/agent/auth.json`, readable only by
your user account. To avoid that, edit the `unigpt` entry there to read the
key from an environment variable or a password manager:
```sh
pi update --extensions
```
## Things to know
- **The thinking level setting has no effect.** uniGPT decides how much each
model reasons; models that reason still do so.
- **Costs are shown as zero.** Pi doesn't track uniGPT usage; this does not
mean usage is unlimited. Your university's usage rules still apply.
## Troubleshooting
- **No uniGPT models in `/model`:** type `/unigpt-refresh`. It tells you
what is wrong, e.g. a rejected key or no network connection.
- **One model always fails with an error:** that model may be unavailable
on uniGPT's side. Pick another one and let the uniGPT team know.
- **`pi -p "…"` says a uniGPT model was not found:** start pi normally once
after logging in, so it can load the model list.
- **Installation fails with `spawn npm ENOENT`:** you use pnpm instead of
npm. Add `"npmCommand": ["pnpm"]` to `~/.pi/agent/settings.json` and try
again.
Still stuck? [Open an issue](https://imsc.uni-graz.at/git/behackl/pi-unigpt/issues)
with the output of `/unigpt-refresh` and the model you used. **Never include
your API key.**
## Advanced
**Storing the key elsewhere.** Pi saves your key unencrypted in
`~/.pi/agent/auth.json`, readable only by your user account. To keep it out
of that file, change the `unigpt` entry there to read it from an environment
variable or a password manager command:
```json
{ "unigpt": { "type": "api_key", "key": "${UNIGPT_API_KEY}" } }
@ -73,42 +101,21 @@ key from an environment variable or a password manager:
{ "unigpt": { "type": "api_key", "key": "!op read op://Private/uniGPT/credential" } }
```
## Good to know
- **Thinking levels are off.** uniGPT applies a fixed reasoning effort per
model and ignores pi's setting. Reasoning models still reason.
- **Costs show as zero.** That means "not tracked", not "free".
- **Model limits** come from pi's catalog. Models pi doesn't know get 128k
context and 16k output; update pi to pick up newer ones. To override a
value, use `modelOverrides` in `~/.pi/agent/models.json`:
**Adjusting model limits.** Pi knows the context size and output limit of
common models. For a model it doesn't recognize, it assumes 128k tokens of
context and 16k of output. If you know better, override them in
`~/.pi/agent/models.json`:
```json
{ "providers": { "unigpt": { "modelOverrides": { "gpt-5.4": { "contextWindow": 200000 } } } } }
```
## Troubleshooting
**Pinning a version.** To stay on one release, install with its tag, e.g.
`pi install git:imsc.uni-graz.at/git/behackl/pi-unigpt@v0.1.0`.
- **No uniGPT models in `/model`:** run `/unigpt-refresh`; it names the
problem (rejected key, unreachable server, empty model list).
- **`pi -p` can't find a uniGPT model:** print mode doesn't fetch model
lists. Start pi interactively once after logging in.
- **One model fails with HTTP 500:** that deployment is broken on the
server; pick another model and tell the uniGPT team.
- **Install fails with `spawn npm ENOENT`:** you use pnpm instead of npm.
Add `"npmCommand": ["pnpm"]` to `~/.pi/agent/settings.json`.
## Contributing
Report bugs at <https://imsc.uni-graz.at/git/behackl/pi-unigpt/issues>.
Include the `/unigpt-refresh` output and the model you used, never your
API key.
## Development
```sh
pnpm install
pnpm run ci # type check + tests
```
Tests use fake keys and a local server; they never contact uniGPT.
See [DEVELOPMENT.md](DEVELOPMENT.md).
## License

View file

@ -24,7 +24,8 @@
"files": [
"src",
"README.md",
"LICENSE"
"LICENSE",
"DEVELOPMENT.md"
],
"engines": {
"node": ">=22.19.0"