pi-rozalia 0.1.2 → 0.1.3

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
@@ -66,6 +66,10 @@ On startup, the extension fetches the model list from the server's `/v1/models`
66
66
 
67
67
  The `/login` flow stores your server URL and API key in Pi's credential store (`~/.pi/agent/auth.json`). On subsequent logins the stored URL is pre-filled, so you only need to re-enter it if your server changes.
68
68
 
69
+ ## Known Limitations
70
+
71
+ - **Models stay listed after `/logout`.** Pi's extension API has no logout callback/event for OAuth-backed providers, so this extension has no way to detect that `/logout` ran and revert the provider to its empty stub. The model list only clears on the next Pi restart. This is a Pi platform limitation, not something this extension can currently work around.
72
+
69
73
  ## TODO
70
74
 
71
75
  - [ ] Multi-server support — register each configured server as its own provider with a derived name (e.g. `rozalia-localhost-1234`), so models from different servers are unambiguous in the picker
@@ -212,6 +212,15 @@ async function registerRozaliaProvider(
212
212
  config.apiKey = apiKey;
213
213
  }
214
214
 
215
+ // Unregister first so Pi replaces the stub (empty models) with
216
+ // the real model list. Without this, registerProvider keeps
217
+ // the stub's models: [] on an already-registered provider.
218
+ try {
219
+ pi.unregisterProvider("rozalia");
220
+ } catch {
221
+ // not previously registered; ignore
222
+ }
223
+
215
224
  pi.registerProvider("rozalia", config);
216
225
  }
217
226
 
@@ -226,6 +235,7 @@ async function registerRozaliaProvider(
226
235
  function createLoginFlow(
227
236
  defaultUrl: string,
228
237
  defaultApiKey: string | undefined,
238
+ pi: ExtensionAPI,
229
239
  ): (callbacks: OAuthLoginCallbacks) => Promise<OAuthCredentials> {
230
240
  return async (callbacks: OAuthLoginCallbacks) => {
231
241
  const inputUrl = await callbacks.onPrompt({
@@ -249,6 +259,10 @@ function createLoginFlow(
249
259
  // Still register — models will be discovered later or show fallback
250
260
  }
251
261
 
262
+ // Actually register the provider so models appear immediately
263
+ const oauthBlock = buildOauthBlock(defaultUrl, defaultApiKey, pi);
264
+ await registerRozaliaProvider(pi, creds, oauthBlock);
265
+
252
266
  return encodeCreds(creds);
253
267
  };
254
268
  }
@@ -260,24 +274,43 @@ function createLoginFlow(
260
274
  function buildOauthBlock(
261
275
  defaultUrl: string,
262
276
  defaultApiKey: string | undefined,
277
+ pi: ExtensionAPI,
263
278
  ) {
264
279
  return {
265
280
  name: "Rozalia",
266
- login: createLoginFlow(defaultUrl, defaultApiKey),
267
- refreshToken: async (creds: OAuthCredentials, _signal: AbortSignal) => {
281
+ login: createLoginFlow(defaultUrl, defaultApiKey, pi),
282
+ refreshToken: async (creds: OAuthCredentials, signal: AbortSignal) => {
268
283
  const payload = decodeCreds(creds);
284
+ if (!payload.baseUrl) return creds;
285
+ // Re-register with fresh models so the picker updates without restart
269
286
  try {
270
- const ctrl = new AbortController();
271
- await fetchModels(payload.baseUrl, payload.apiKey, ctrl.signal);
287
+ await registerRozaliaProvider(pi, payload, {
288
+ name: "Rozalia",
289
+ login: createLoginFlow(defaultUrl, defaultApiKey, pi),
290
+ refreshToken: async (c: OAuthCredentials, s: AbortSignal) =>
291
+ refreshTokenRozalia(c, s, payload, defaultUrl, defaultApiKey, pi),
292
+ getApiKey: (c: OAuthCredentials) => decodeCreds(c).apiKey || "",
293
+ });
272
294
  } catch {
273
- // network blip
295
+ // network blip — keep creds, retry on next call
274
296
  }
275
- return creds;
297
+ return encodeCreds(payload);
276
298
  },
277
299
  getApiKey: (creds: OAuthCredentials) => decodeCreds(creds).apiKey || "",
278
300
  };
279
301
  }
280
302
 
303
+ async function refreshTokenRozalia(
304
+ creds: OAuthCredentials,
305
+ _signal: AbortSignal,
306
+ _payload: CredsPayload,
307
+ _defaultUrl: string,
308
+ _defaultApiKey: string | undefined,
309
+ _pi: ExtensionAPI,
310
+ ): Promise<OAuthCredentials> {
311
+ return encodeCreds(decodeCreds(creds));
312
+ }
313
+
281
314
  // ---------------------------------------------------------------------------
282
315
  // Extension entry point
283
316
  // ---------------------------------------------------------------------------
@@ -310,7 +343,9 @@ export default async function (pi: ExtensionAPI) {
310
343
  // No saved credential — will use env vars or prompt
311
344
  }
312
345
 
313
- const oauthBlock = buildOauthBlock(envBaseUrl, envApiKey);
346
+ // Capture pi in a closure so the login flow can call registerRozaliaProvider
347
+ // (Pi only passes callbacks to login, not pi).
348
+ const oauthBlock = buildOauthBlock(envBaseUrl, envApiKey, pi);
314
349
 
315
350
  // Initial stub registration so "Rozalia" appears in /login selector
316
351
  pi.registerProvider("rozalia", {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-rozalia",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "description": "OpenAI-compatible provider extension for the Pi coding agent — connects to Rozalia AI",
5
5
  "keywords": [
6
6
  "pi-package",