From 8172f41c56a315d07656fb30a41f40b19adbdc89 Mon Sep 17 00:00:00 2001 From: Benjamin Hackl Date: Tue, 3 Nov 2026 21:02:17 +0100 Subject: [PATCH] Split README into user guide and DEVELOPMENT.md --- DEVELOPMENT.md | 78 +++++++++++++++++++++++++++++++ README.md | 123 ++++++++++++++++++++++++++----------------------- package.json | 3 +- 3 files changed, 145 insertions(+), 59 deletions(-) create mode 100644 DEVELOPMENT.md diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md new file mode 100644 index 0000000..d9ebc1d --- /dev/null +++ b/DEVELOPMENT.md @@ -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. diff --git a/README.md b/README.md index 0fc67d3..775a040 100644 --- a/README.md +++ b/README.md @@ -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 +**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`: -- **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`: - - ```json - { "providers": { "unigpt": { "modelOverrides": { "gpt-5.4": { "contextWindow": 200000 } } } } } - ``` - -## Troubleshooting - -- **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`. - -Report bugs at . -Include the `/unigpt-refresh` output and the model you used, never your -API key. - -## Development - -```sh -pnpm install -pnpm run ci # type check + tests +```json +{ "providers": { "unigpt": { "modelOverrides": { "gpt-5.4": { "contextWindow": 200000 } } } } } ``` -Tests use fake keys and a local server; they never contact uniGPT. +**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`. + +## Contributing + +See [DEVELOPMENT.md](DEVELOPMENT.md). ## License diff --git a/package.json b/package.json index 183e2fa..37a522f 100644 --- a/package.json +++ b/package.json @@ -24,7 +24,8 @@ "files": [ "src", "README.md", - "LICENSE" + "LICENSE", + "DEVELOPMENT.md" ], "engines": { "node": ">=22.19.0"