pi-rozalia 0.1.1 → 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 +41 -10
- 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,8 +235,9 @@ async function registerRozaliaProvider(
|
|
|
226
235
|
function createLoginFlow(
|
|
227
236
|
defaultUrl: string,
|
|
228
237
|
defaultApiKey: string | undefined,
|
|
229
|
-
|
|
230
|
-
|
|
238
|
+
pi: ExtensionAPI,
|
|
239
|
+
): (callbacks: OAuthLoginCallbacks) => Promise<OAuthCredentials> {
|
|
240
|
+
return async (callbacks: OAuthLoginCallbacks) => {
|
|
231
241
|
const inputUrl = await callbacks.onPrompt({
|
|
232
242
|
message: `Enter Rozalia server URL (press Enter for ${defaultUrl}):`,
|
|
233
243
|
});
|
|
@@ -250,7 +260,7 @@ function createLoginFlow(
|
|
|
250
260
|
}
|
|
251
261
|
|
|
252
262
|
// Actually register the provider so models appear immediately
|
|
253
|
-
const oauthBlock = buildOauthBlock(defaultUrl, defaultApiKey);
|
|
263
|
+
const oauthBlock = buildOauthBlock(defaultUrl, defaultApiKey, pi);
|
|
254
264
|
await registerRozaliaProvider(pi, creds, oauthBlock);
|
|
255
265
|
|
|
256
266
|
return encodeCreds(creds);
|
|
@@ -264,24 +274,43 @@ function createLoginFlow(
|
|
|
264
274
|
function buildOauthBlock(
|
|
265
275
|
defaultUrl: string,
|
|
266
276
|
defaultApiKey: string | undefined,
|
|
277
|
+
pi: ExtensionAPI,
|
|
267
278
|
) {
|
|
268
279
|
return {
|
|
269
280
|
name: "Rozalia",
|
|
270
|
-
login: createLoginFlow(defaultUrl, defaultApiKey),
|
|
271
|
-
refreshToken: async (creds: OAuthCredentials,
|
|
281
|
+
login: createLoginFlow(defaultUrl, defaultApiKey, pi),
|
|
282
|
+
refreshToken: async (creds: OAuthCredentials, signal: AbortSignal) => {
|
|
272
283
|
const payload = decodeCreds(creds);
|
|
284
|
+
if (!payload.baseUrl) return creds;
|
|
285
|
+
// Re-register with fresh models so the picker updates without restart
|
|
273
286
|
try {
|
|
274
|
-
|
|
275
|
-
|
|
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
|
+
});
|
|
276
294
|
} catch {
|
|
277
|
-
// network blip
|
|
295
|
+
// network blip — keep creds, retry on next call
|
|
278
296
|
}
|
|
279
|
-
return
|
|
297
|
+
return encodeCreds(payload);
|
|
280
298
|
},
|
|
281
299
|
getApiKey: (creds: OAuthCredentials) => decodeCreds(creds).apiKey || "",
|
|
282
300
|
};
|
|
283
301
|
}
|
|
284
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
|
+
|
|
285
314
|
// ---------------------------------------------------------------------------
|
|
286
315
|
// Extension entry point
|
|
287
316
|
// ---------------------------------------------------------------------------
|
|
@@ -314,7 +343,9 @@ export default async function (pi: ExtensionAPI) {
|
|
|
314
343
|
// No saved credential — will use env vars or prompt
|
|
315
344
|
}
|
|
316
345
|
|
|
317
|
-
|
|
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);
|
|
318
349
|
|
|
319
350
|
// Initial stub registration so "Rozalia" appears in /login selector
|
|
320
351
|
pi.registerProvider("rozalia", {
|