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 +4 -0
- package/extensions/index.ts +42 -7
- package/package.json +1 -1
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
|
package/extensions/index.ts
CHANGED
|
@@ -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,
|
|
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
|
-
|
|
271
|
-
|
|
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
|
|
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
|
-
|
|
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", {
|