pi-rozalia 0.1.0
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/LICENSE +21 -0
- package/README.md +78 -0
- package/index.ts +344 -0
- package/package.json +18 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Maciej Borzecki
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# pi-rozalia
|
|
2
|
+
|
|
3
|
+
An extension for the [Pi coding agent](https://github.com/microsoft/pi-coding-agent) that connects to a [Rozalia AI](https://ai.zygoon.pl) server via the OpenAI-compatible Chat Completions API.
|
|
4
|
+
|
|
5
|
+
## Prerequisites
|
|
6
|
+
|
|
7
|
+
1. **Pi Coding Agent** installed.
|
|
8
|
+
2. A **Rozalia AI server** running and accessible.
|
|
9
|
+
|
|
10
|
+
## Installation
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
pi install npm:pi-rozalia
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Configuration
|
|
17
|
+
|
|
18
|
+
### Interactive setup (recommended)
|
|
19
|
+
|
|
20
|
+
Use Pi's built-in `/login` command to configure your server and API key:
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
pi
|
|
24
|
+
/login
|
|
25
|
+
→ pick "Rozalia"
|
|
26
|
+
→ enter server URL (default: https://ai.zygoon.pl/v1)
|
|
27
|
+
→ enter API key (optional)
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
The server URL and API key are stored in `~/.pi/agent/auth.json` and reused on subsequent logins.
|
|
31
|
+
|
|
32
|
+
### Environment variables (alternative)
|
|
33
|
+
|
|
34
|
+
| Variable | Description | Default |
|
|
35
|
+
|----------|-------------|---------|
|
|
36
|
+
| `ROZALIA_BASE_URL` | Rozalia AI server base URL | `https://ai.zygoon.pl/v1` |
|
|
37
|
+
| `ROZALIA_API_KEY` | API key for authentication | *(none)* |
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
export ROZALIA_API_KEY="your-api-key"
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### Custom server
|
|
44
|
+
|
|
45
|
+
Point to any OpenAI-compatible server:
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
ROZALIA_BASE_URL="https://my-server.example.com/v1" \
|
|
49
|
+
ROZALIA_API_KEY="my-key" \
|
|
50
|
+
pi
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Usage
|
|
54
|
+
|
|
55
|
+
1. **Launch Pi** — the provider loads automatically on startup.
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
pi
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
2. **Select a Model** — use `/model` or `Ctrl+P` to pick from the discovered models.
|
|
62
|
+
|
|
63
|
+
## How It Works
|
|
64
|
+
|
|
65
|
+
On startup, the extension fetches the model list from the server's `/v1/models` endpoint and registers each model with Pi. If discovery fails, a single fallback model is registered so you can still try to connect.
|
|
66
|
+
|
|
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
|
+
|
|
69
|
+
## TODO
|
|
70
|
+
|
|
71
|
+
- [ ] 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
|
|
72
|
+
- [ ] Model discovery health check — skip servers that fail to respond rather than showing fallback models
|
|
73
|
+
- [ ] `ROZALIA_TIMEOUT` env var — configure model discovery timeout (currently hardcoded to 5s)
|
|
74
|
+
- [ ] Support for `ROZALIA_MODELS` env var — allow overriding the discovered model list with a static list
|
|
75
|
+
|
|
76
|
+
## License
|
|
77
|
+
|
|
78
|
+
[MIT](LICENSE) — © 2026 Maciej Borzecki
|
package/index.ts
ADDED
|
@@ -0,0 +1,344 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rozalia AI Provider Extension
|
|
3
|
+
*
|
|
4
|
+
* Registers an OpenAI-compatible provider for the Pi coding agent,
|
|
5
|
+
* connecting to a Rozalia AI server.
|
|
6
|
+
*
|
|
7
|
+
* Configuration (in order of precedence):
|
|
8
|
+
* 1. /login → OAuth flow (stores baseUrl + apiKey in ~/.pi/agent/auth.json)
|
|
9
|
+
* 2. ROZALIA_BASE_URL env var (default: https://ai.zygoon.pl/v1)
|
|
10
|
+
* 3. ROZALIA_API_KEY env var
|
|
11
|
+
*
|
|
12
|
+
* Usage:
|
|
13
|
+
* # Quick: set env vars
|
|
14
|
+
* export ROZALIA_API_KEY="your-api-key"
|
|
15
|
+
* pi
|
|
16
|
+
*
|
|
17
|
+
* # Full: use /login to configure server URL + API key interactively
|
|
18
|
+
* pi
|
|
19
|
+
* /login
|
|
20
|
+
* → pick "rozalia"
|
|
21
|
+
* → enter server URL (default: https://ai.zygoon.pl/v1)
|
|
22
|
+
* → enter API key
|
|
23
|
+
*
|
|
24
|
+
* # Point to a custom server
|
|
25
|
+
* ROZALIA_BASE_URL="https://my-server.example.com/v1" \
|
|
26
|
+
* ROZALIA_API_KEY="my-key" \
|
|
27
|
+
* pi
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
import type { ExtensionAPI, OAuthCredentials, OAuthLoginCallbacks, ProviderModelConfig } from "@earendil-works/pi-coding-agent";
|
|
31
|
+
import * as fs from "node:fs";
|
|
32
|
+
|
|
33
|
+
// ---------------------------------------------------------------------------
|
|
34
|
+
// Credential payload (stored in OAuth refresh field)
|
|
35
|
+
// ---------------------------------------------------------------------------
|
|
36
|
+
|
|
37
|
+
interface CredsPayload {
|
|
38
|
+
baseUrl: string;
|
|
39
|
+
apiKey: string;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function encodeCreds(payload: CredsPayload): OAuthCredentials {
|
|
43
|
+
return {
|
|
44
|
+
refresh: JSON.stringify(payload),
|
|
45
|
+
access: payload.apiKey,
|
|
46
|
+
expires: Date.now() + 24 * 60 * 60 * 1000,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function decodeCreds(creds: OAuthCredentials): CredsPayload {
|
|
51
|
+
try {
|
|
52
|
+
const parsed = JSON.parse(creds.refresh ?? "");
|
|
53
|
+
return {
|
|
54
|
+
baseUrl: typeof parsed.baseUrl === "string" ? parsed.baseUrl : "",
|
|
55
|
+
apiKey: typeof parsed.apiKey === "string" ? parsed.apiKey : (creds.access ?? ""),
|
|
56
|
+
};
|
|
57
|
+
} catch {
|
|
58
|
+
return { baseUrl: "", apiKey: creds.access ?? "" };
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// ---------------------------------------------------------------------------
|
|
63
|
+
// Configuration fallback (env vars)
|
|
64
|
+
// ---------------------------------------------------------------------------
|
|
65
|
+
|
|
66
|
+
const DEFAULT_BASE_URL = "https://ai.zygoon.pl/v1";
|
|
67
|
+
|
|
68
|
+
function getEnvBaseUrl(): string {
|
|
69
|
+
return process.env.ROZALIA_BASE_URL ?? DEFAULT_BASE_URL;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function getEnvApiKey(): string | undefined {
|
|
73
|
+
return process.env.ROZALIA_API_KEY;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// ---------------------------------------------------------------------------
|
|
77
|
+
// Model discovery
|
|
78
|
+
// ---------------------------------------------------------------------------
|
|
79
|
+
|
|
80
|
+
async function fetchModels(
|
|
81
|
+
baseUrl: string,
|
|
82
|
+
apiKey: string | undefined,
|
|
83
|
+
signal: AbortSignal,
|
|
84
|
+
): Promise<ProviderModelConfig[]> {
|
|
85
|
+
const url = new URL("/v1/models", baseUrl);
|
|
86
|
+
|
|
87
|
+
const headers: Record<string, string> = { "Content-Type": "application/json" };
|
|
88
|
+
if (apiKey) {
|
|
89
|
+
headers["Authorization"] = `Bearer ${apiKey}`;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Use a manual timeout via AbortController for Node.js compatibility
|
|
93
|
+
// (AbortSignal.timeout / AbortSignal.any may not be available on older versions)
|
|
94
|
+
const controller = new AbortController();
|
|
95
|
+
const timer = setTimeout(() => controller.abort(), 5_000);
|
|
96
|
+
try {
|
|
97
|
+
const response = await fetch(url.toString(), { signal: controller.signal, headers });
|
|
98
|
+
|
|
99
|
+
if (!response.ok) {
|
|
100
|
+
const text = await response.text().catch(() => "");
|
|
101
|
+
throw new Error(`Model discovery failed (${response.status}): ${text.slice(0, 200)}`);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const data = (await response.json()) as {
|
|
105
|
+
data?: Array<Record<string, unknown>>;
|
|
106
|
+
models?: Array<Record<string, unknown>>;
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
const entries = data.data ?? data.models ?? [];
|
|
110
|
+
if (!Array.isArray(entries)) {
|
|
111
|
+
throw new Error("Unexpected /v1/models response format");
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return entries.map((entry) => mapModel(entry));
|
|
115
|
+
} finally {
|
|
116
|
+
clearTimeout(timer);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function mapModel(entry: Record<string, unknown>): ProviderModelConfig {
|
|
121
|
+
const id =
|
|
122
|
+
(entry.id as string) ??
|
|
123
|
+
(entry.modelId as string) ??
|
|
124
|
+
(entry.model_id as string) ??
|
|
125
|
+
(entry.root as string) ??
|
|
126
|
+
"unknown";
|
|
127
|
+
|
|
128
|
+
const name =
|
|
129
|
+
(entry.name as string) ??
|
|
130
|
+
(entry.displayName as string) ??
|
|
131
|
+
(entry.display_name as string) ??
|
|
132
|
+
id;
|
|
133
|
+
|
|
134
|
+
// context_length may be nested inside a custom provider object (e.g. rozalia.context_length)
|
|
135
|
+
const customObj = entry.rozalia as Record<string, unknown> | undefined;
|
|
136
|
+
const contextWindow =
|
|
137
|
+
(entry.context_window as number) ??
|
|
138
|
+
(entry.context as number) ??
|
|
139
|
+
(entry.max_context_length as number) ??
|
|
140
|
+
(entry.maxContextLength as number) ??
|
|
141
|
+
(customObj?.context_length as number) ??
|
|
142
|
+
128_000;
|
|
143
|
+
|
|
144
|
+
const maxTokens =
|
|
145
|
+
(entry.max_tokens as number) ??
|
|
146
|
+
(entry.maxTokens as number) ??
|
|
147
|
+
(entry.max_completion_tokens as number) ??
|
|
148
|
+
8_192;
|
|
149
|
+
|
|
150
|
+
return {
|
|
151
|
+
id,
|
|
152
|
+
name,
|
|
153
|
+
reasoning: false,
|
|
154
|
+
input: ["text"],
|
|
155
|
+
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
156
|
+
contextWindow,
|
|
157
|
+
maxTokens,
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function getFallbackModels(): ProviderModelConfig[] {
|
|
162
|
+
return [
|
|
163
|
+
{
|
|
164
|
+
id: "unknown",
|
|
165
|
+
name: "Rozalia Model (discovery failed)",
|
|
166
|
+
reasoning: false,
|
|
167
|
+
input: ["text"],
|
|
168
|
+
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
|
169
|
+
contextWindow: 128_000,
|
|
170
|
+
maxTokens: 8_192,
|
|
171
|
+
},
|
|
172
|
+
];
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// ---------------------------------------------------------------------------
|
|
176
|
+
// Provider registration helper
|
|
177
|
+
// ---------------------------------------------------------------------------
|
|
178
|
+
|
|
179
|
+
async function registerRozaliaProvider(
|
|
180
|
+
pi: ExtensionAPI,
|
|
181
|
+
creds: CredsPayload,
|
|
182
|
+
oauthBlock: {
|
|
183
|
+
name: string;
|
|
184
|
+
login: (callbacks: OAuthLoginCallbacks) => Promise<OAuthCredentials>;
|
|
185
|
+
refreshToken: (creds: OAuthCredentials, signal: AbortSignal) => Promise<OAuthCredentials>;
|
|
186
|
+
getApiKey: (creds: OAuthCredentials) => string;
|
|
187
|
+
},
|
|
188
|
+
): Promise<void> {
|
|
189
|
+
const { baseUrl, apiKey } = creds;
|
|
190
|
+
if (!baseUrl) return;
|
|
191
|
+
|
|
192
|
+
let models: ProviderModelConfig[];
|
|
193
|
+
try {
|
|
194
|
+
const controller = new AbortController();
|
|
195
|
+
models = await fetchModels(baseUrl, apiKey, controller.signal);
|
|
196
|
+
if (models.length === 0) {
|
|
197
|
+
models = getFallbackModels();
|
|
198
|
+
}
|
|
199
|
+
} catch {
|
|
200
|
+
models = getFallbackModels();
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const config: Record<string, unknown> = {
|
|
204
|
+
name: "Rozalia",
|
|
205
|
+
baseUrl,
|
|
206
|
+
api: "openai-completions",
|
|
207
|
+
models,
|
|
208
|
+
oauth: oauthBlock,
|
|
209
|
+
};
|
|
210
|
+
|
|
211
|
+
if (apiKey) {
|
|
212
|
+
config.apiKey = apiKey;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
pi.registerProvider("rozalia", config);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// ---------------------------------------------------------------------------
|
|
219
|
+
// OAuth login / refresh
|
|
220
|
+
// ---------------------------------------------------------------------------
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Create a login flow that captures `pi` in the closure so it can
|
|
224
|
+
* call registerRozaliaProvider before returning the credential.
|
|
225
|
+
*/
|
|
226
|
+
function createLoginFlow(
|
|
227
|
+
defaultUrl: string,
|
|
228
|
+
defaultApiKey: string | undefined,
|
|
229
|
+
): (pi: ExtensionAPI, callbacks: OAuthLoginCallbacks) => Promise<OAuthCredentials> {
|
|
230
|
+
return async (pi: ExtensionAPI, callbacks: OAuthLoginCallbacks) => {
|
|
231
|
+
const inputUrl = await callbacks.onPrompt({
|
|
232
|
+
message: `Enter Rozalia server URL (press Enter for ${defaultUrl}):`,
|
|
233
|
+
});
|
|
234
|
+
const trimmedUrl = inputUrl.trim();
|
|
235
|
+
const baseUrl = trimmedUrl ? trimmedUrl : defaultUrl;
|
|
236
|
+
|
|
237
|
+
const inputKey = await callbacks.onPrompt({
|
|
238
|
+
message: `Enter API key (optional — press Enter to skip):`,
|
|
239
|
+
});
|
|
240
|
+
const apiKey = inputKey.trim() || defaultApiKey || "";
|
|
241
|
+
|
|
242
|
+
const creds: CredsPayload = { baseUrl, apiKey };
|
|
243
|
+
|
|
244
|
+
// Verify connectivity and fetch models
|
|
245
|
+
try {
|
|
246
|
+
const controller = new AbortController();
|
|
247
|
+
await fetchModels(baseUrl, apiKey, controller.signal);
|
|
248
|
+
} catch {
|
|
249
|
+
// Still register — models will be discovered later or show fallback
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// Actually register the provider so models appear immediately
|
|
253
|
+
const oauthBlock = buildOauthBlock(defaultUrl, defaultApiKey);
|
|
254
|
+
await registerRozaliaProvider(pi, creds, oauthBlock);
|
|
255
|
+
|
|
256
|
+
return encodeCreds(creds);
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// ---------------------------------------------------------------------------
|
|
261
|
+
// OAuth block factory (shared between /login and startup)
|
|
262
|
+
// ---------------------------------------------------------------------------
|
|
263
|
+
|
|
264
|
+
function buildOauthBlock(
|
|
265
|
+
defaultUrl: string,
|
|
266
|
+
defaultApiKey: string | undefined,
|
|
267
|
+
) {
|
|
268
|
+
return {
|
|
269
|
+
name: "Rozalia",
|
|
270
|
+
login: createLoginFlow(defaultUrl, defaultApiKey),
|
|
271
|
+
refreshToken: async (creds: OAuthCredentials, _signal: AbortSignal) => {
|
|
272
|
+
const payload = decodeCreds(creds);
|
|
273
|
+
try {
|
|
274
|
+
const ctrl = new AbortController();
|
|
275
|
+
await fetchModels(payload.baseUrl, payload.apiKey, ctrl.signal);
|
|
276
|
+
} catch {
|
|
277
|
+
// network blip
|
|
278
|
+
}
|
|
279
|
+
return creds;
|
|
280
|
+
},
|
|
281
|
+
getApiKey: (creds: OAuthCredentials) => decodeCreds(creds).apiKey || "",
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// ---------------------------------------------------------------------------
|
|
286
|
+
// Extension entry point
|
|
287
|
+
// ---------------------------------------------------------------------------
|
|
288
|
+
|
|
289
|
+
export default async function (pi: ExtensionAPI) {
|
|
290
|
+
const envBaseUrl = getEnvBaseUrl();
|
|
291
|
+
const envApiKey = getEnvApiKey();
|
|
292
|
+
|
|
293
|
+
// Try to restore saved credentials from auth.json.
|
|
294
|
+
// Handles both our custom OAuth format ({ refresh, access }) and
|
|
295
|
+
// Pi's built-in api_key format ({ type: "api_key", key }).
|
|
296
|
+
let storedCreds: CredsPayload | null = null;
|
|
297
|
+
try {
|
|
298
|
+
const authPath = `${process.env.HOME ?? "/"}/.pi/agent/auth.json`;
|
|
299
|
+
const raw = fs.readFileSync(authPath, "utf-8");
|
|
300
|
+
const auth = JSON.parse(raw) as Record<string, unknown>;
|
|
301
|
+
const cred = auth["rozalia"] as Record<string, unknown> | undefined;
|
|
302
|
+
if (cred) {
|
|
303
|
+
// Built-in api_key format
|
|
304
|
+
if (cred.type === "api_key" && typeof cred.key === "string" && cred.key) {
|
|
305
|
+
storedCreds = { baseUrl: envBaseUrl, apiKey: cred.key };
|
|
306
|
+
}
|
|
307
|
+
// Custom OAuth format
|
|
308
|
+
else {
|
|
309
|
+
const parsed = decodeCreds(cred as OAuthCredentials);
|
|
310
|
+
if (parsed.baseUrl) storedCreds = parsed;
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
} catch {
|
|
314
|
+
// No saved credential — will use env vars or prompt
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
const oauthBlock = buildOauthBlock(envBaseUrl, envApiKey);
|
|
318
|
+
|
|
319
|
+
// Initial stub registration so "Rozalia" appears in /login selector
|
|
320
|
+
pi.registerProvider("rozalia", {
|
|
321
|
+
name: "Rozalia",
|
|
322
|
+
baseUrl: envBaseUrl,
|
|
323
|
+
api: "openai-completions",
|
|
324
|
+
models: [],
|
|
325
|
+
oauth: oauthBlock,
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
// Best-effort: if env vars OR saved credentials provide a key,
|
|
329
|
+
// register eagerly so models appear without waiting for /login.
|
|
330
|
+
let credsToUse: CredsPayload | null = null;
|
|
331
|
+
if (envApiKey) {
|
|
332
|
+
credsToUse = { baseUrl: envBaseUrl, apiKey: envApiKey };
|
|
333
|
+
} else if (storedCreds?.apiKey) {
|
|
334
|
+
credsToUse = storedCreds;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
if (credsToUse) {
|
|
338
|
+
try {
|
|
339
|
+
await registerRozaliaProvider(pi, credsToUse, oauthBlock);
|
|
340
|
+
} catch {
|
|
341
|
+
// ignore — will retry on next call
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "pi-rozalia",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "OpenAI-compatible provider extension for the Pi coding agent — connects to Rozalia AI",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"pi-package",
|
|
7
|
+
"rozalia",
|
|
8
|
+
"ai",
|
|
9
|
+
"openai",
|
|
10
|
+
"provider"
|
|
11
|
+
],
|
|
12
|
+
"license": "MIT",
|
|
13
|
+
"type": "module",
|
|
14
|
+
"peerDependencies": {
|
|
15
|
+
"@earendil-works/pi-coding-agent": "*",
|
|
16
|
+
"@earendil-works/pi-ai": "*"
|
|
17
|
+
}
|
|
18
|
+
}
|