@billjr99/pi-openai-compat 1.0.3 → 1.1.9
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 +82 -0
- package/index.ts +182 -16
- package/package.json +8 -4
package/README.md
CHANGED
|
@@ -40,9 +40,32 @@ If pi is already running when you install, type `/reload` first.
|
|
|
40
40
|
| **OpenRouter** | `https://openrouter.ai/api/v1` | `sk-or-...` from openrouter.ai/keys |
|
|
41
41
|
| **NVIDIA NIM** | `https://integrate.api.nvidia.com/v1` | `nvapi-...` from build.nvidia.com |
|
|
42
42
|
| **Nous Research Portal** | `https://inference-api.nousresearch.com/v1` | Nous Portal API key |
|
|
43
|
+
| **DeepSeek** | `https://api.deepseek.com/v1` | API key from platform.deepseek.com |
|
|
44
|
+
| **xAI (Grok)** | `https://api.x.ai/v1` | API key from console.x.ai |
|
|
45
|
+
| **Hugging Face** | `https://router.huggingface.co/v1` | `hf_...` token from huggingface.co/settings/tokens |
|
|
46
|
+
| **Moonshot (Kimi)** | `https://api.moonshot.ai/v1` | `sk-...` key from platform.moonshot.ai |
|
|
47
|
+
| **MiniMax** | `https://api.minimax.io/v1` | API key from platform.minimax.io |
|
|
48
|
+
| **Z.ai** | `https://api.z.ai/api/paas/v4` | API key from z.ai |
|
|
49
|
+
| **Venice AI** | `https://api.venice.ai/api/v1` | API key from venice.ai/settings/api |
|
|
50
|
+
| **Cloudflare Workers AI** | `https://api.cloudflare.com/client/v4/accounts/{account}/ai/v1` | API token from dash.cloudflare.com |
|
|
51
|
+
| **Cloudflare AI Gateway** | `https://gateway.ai.cloudflare.com/v1/{account}/{gateway}/openai` | API token from dash.cloudflare.com |
|
|
52
|
+
| **Vercel AI Gateway** | `https://ai-gateway.vercel.sh/v1` | API key from vercel.com |
|
|
53
|
+
| **OpenCode Zen** | `https://opencode.ai/zen/v1` | API key from opencode.ai |
|
|
43
54
|
| **Ollama (local)** | `http://localhost:11434/v1` | Keyless |
|
|
55
|
+
| **Ollama Cloud** | `https://ollama.com/v1` | Ollama Cloud API key from ollama.com |
|
|
44
56
|
| **Custom** | Any URL you supply | Optional bearer token |
|
|
45
57
|
|
|
58
|
+
> **Providers with built-in fallback model lists (as of May 2026)**
|
|
59
|
+
> Some providers do not support `GET /v1/models` or return a non-JSON response.
|
|
60
|
+
> For these, the extension substitutes a built-in list and shows a warning during
|
|
61
|
+
> `/compat-login`.
|
|
62
|
+
>
|
|
63
|
+
> | Provider | Reason |
|
|
64
|
+
> |---|---|
|
|
65
|
+
> | **Cloudflare Workers AI** | Returns HTTP 405 — method not supported |
|
|
66
|
+
> | **Cloudflare AI Gateway** | Returns HTTP 401 — no anonymous model enumeration |
|
|
67
|
+
> | **Hugging Face** | Returns HTML rather than JSON for `/v1/models` |
|
|
68
|
+
|
|
46
69
|
---
|
|
47
70
|
|
|
48
71
|
## Commands
|
|
@@ -171,6 +194,65 @@ npm publish --access public
|
|
|
171
194
|
|
|
172
195
|
`npm version` also creates a git tag, so GitHub gets release tags automatically.
|
|
173
196
|
|
|
197
|
+
### Automated publishing via GitHub Actions
|
|
198
|
+
|
|
199
|
+
Every push to `main` automatically bumps the patch version and publishes to npm
|
|
200
|
+
via `.github/workflows/publish.yml`. This workflow uses **npm trusted publishing**
|
|
201
|
+
(OIDC) — no long-lived token or secret is needed anywhere.
|
|
202
|
+
|
|
203
|
+
#### Required one-time setup on npmjs.com
|
|
204
|
+
|
|
205
|
+
> **Do this before the first automated publish or it will fail.**
|
|
206
|
+
|
|
207
|
+
1. Go to **npmjs.com** → sign in → click your avatar → **Packages** →
|
|
208
|
+
`@billjr99/pi-openai-compat`
|
|
209
|
+
2. Click **Settings** (in the left sidebar of the package page)
|
|
210
|
+
3. Scroll to **Publishing** → enable **Trusted Publishing**
|
|
211
|
+
4. Add a publisher with these exact values:
|
|
212
|
+
|
|
213
|
+
| Field | Value |
|
|
214
|
+
|---|---|
|
|
215
|
+
| Repository owner | `BillJr99` |
|
|
216
|
+
| Repository name | `pi-openai-compat` |
|
|
217
|
+
| Workflow filename | `publish.yml` |
|
|
218
|
+
| Environment | *(leave blank)* |
|
|
219
|
+
|
|
220
|
+
5. Click **Save**
|
|
221
|
+
|
|
222
|
+
No token to copy, no secret to rotate. That's the only setup step.
|
|
223
|
+
|
|
224
|
+
#### What the workflow does
|
|
225
|
+
|
|
226
|
+
```yaml
|
|
227
|
+
# .github/workflows/publish.yml
|
|
228
|
+
on:
|
|
229
|
+
push:
|
|
230
|
+
branches: [main]
|
|
231
|
+
|
|
232
|
+
permissions:
|
|
233
|
+
contents: write # push the version-bump commit back to main
|
|
234
|
+
id-token: write # request an OIDC token from GitHub for npm auth
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
On every push to `main` the workflow:
|
|
238
|
+
|
|
239
|
+
1. Checks out the repo with `GITHUB_TOKEN` so it can push back
|
|
240
|
+
2. Runs `npm version patch --no-git-tag-version` to increment the patch number
|
|
241
|
+
in `package.json` (e.g. `1.1.0` → `1.1.1`)
|
|
242
|
+
3. Commits the updated `package.json` with `[skip ci]` in the message so the
|
|
243
|
+
commit does not re-trigger the workflow
|
|
244
|
+
4. Pushes the bump commit to `main`
|
|
245
|
+
5. Runs `npm publish --access public --provenance`
|
|
246
|
+
- GitHub mints a short-lived OIDC token proving the job is running from
|
|
247
|
+
this exact repo and workflow
|
|
248
|
+
- npm validates it against the trusted publisher config above and allows
|
|
249
|
+
the publish — no `NPM_TOKEN` secret required
|
|
250
|
+
- `--provenance` attaches a signed build attestation to the package,
|
|
251
|
+
visible on the npmjs.com package page under **Provenance**
|
|
252
|
+
|
|
253
|
+
The published package is immediately available at
|
|
254
|
+
https://www.npmjs.com/package/@billjr99/pi-openai-compat.
|
|
255
|
+
|
|
174
256
|
### Installing from npm
|
|
175
257
|
|
|
176
258
|
```bash
|
package/index.ts
CHANGED
|
@@ -60,67 +60,155 @@ const TEMPLATES: Record<string, {
|
|
|
60
60
|
modelFilter?: string[];
|
|
61
61
|
/** If true, prompt the user to confirm/edit the base URL (like ollama/custom). */
|
|
62
62
|
promptUrl?: boolean;
|
|
63
|
+
/** Used when the provider does not support GET /v1/models (e.g. returns 405). */
|
|
64
|
+
fallbackModels?: string[];
|
|
65
|
+
/** Where to obtain the API key; presence implies the key is required. */
|
|
66
|
+
keyHint?: string;
|
|
63
67
|
}> = {
|
|
64
68
|
openrouter: {
|
|
65
69
|
displayName: "OpenRouter",
|
|
66
70
|
baseUrl: "https://openrouter.ai/api/v1",
|
|
67
71
|
keyless: false,
|
|
72
|
+
keyHint: "openrouter.ai/keys",
|
|
68
73
|
},
|
|
69
74
|
nvidia_nim: {
|
|
70
75
|
displayName: "NVIDIA NIM",
|
|
71
76
|
baseUrl: "https://integrate.api.nvidia.com/v1",
|
|
72
77
|
keyless: false,
|
|
78
|
+
keyHint: "build.nvidia.com",
|
|
73
79
|
},
|
|
74
80
|
nous: {
|
|
75
81
|
displayName: "Nous Research Portal",
|
|
76
82
|
baseUrl: "https://inference-api.nousresearch.com/v1",
|
|
77
83
|
keyless: false,
|
|
84
|
+
keyHint: "nousresearch.com",
|
|
78
85
|
},
|
|
79
86
|
google: {
|
|
80
87
|
displayName: "Google Gemini",
|
|
81
88
|
baseUrl: "https://generativelanguage.googleapis.com/v1beta/openai",
|
|
82
89
|
keyless: false,
|
|
90
|
+
keyHint: "aistudio.google.com/apikey",
|
|
83
91
|
},
|
|
84
92
|
cerebras: {
|
|
85
93
|
displayName: "Cerebras",
|
|
86
94
|
baseUrl: "https://api.cerebras.ai/v1",
|
|
87
95
|
keyless: false,
|
|
96
|
+
keyHint: "cloud.cerebras.ai",
|
|
88
97
|
},
|
|
89
98
|
github_models: {
|
|
90
99
|
displayName: "GitHub Models",
|
|
91
100
|
baseUrl: "https://models.inference.ai.azure.com",
|
|
92
101
|
keyless: false,
|
|
102
|
+
keyHint: "github.com/settings/tokens",
|
|
93
103
|
},
|
|
94
104
|
sambanova: {
|
|
95
105
|
displayName: "SambaNova",
|
|
96
106
|
baseUrl: "https://api.sambanova.ai/v1",
|
|
97
107
|
keyless: false,
|
|
108
|
+
keyHint: "cloud.sambanova.ai",
|
|
98
109
|
},
|
|
99
110
|
mistral: {
|
|
100
111
|
displayName: "Mistral",
|
|
101
112
|
baseUrl: "https://api.mistral.ai/v1",
|
|
102
113
|
keyless: false,
|
|
114
|
+
keyHint: "console.mistral.ai/api-keys",
|
|
103
115
|
},
|
|
104
116
|
groq: {
|
|
105
117
|
displayName: "Groq",
|
|
106
118
|
baseUrl: "https://api.groq.com/openai/v1",
|
|
107
119
|
keyless: false,
|
|
120
|
+
keyHint: "console.groq.com/keys",
|
|
108
121
|
},
|
|
109
|
-
|
|
122
|
+
cloudflare_workers: {
|
|
110
123
|
displayName: "Cloudflare Workers AI",
|
|
111
124
|
baseUrl: "https://api.cloudflare.com/client/v4/accounts/YOUR_ACCOUNT_ID/ai/v1",
|
|
112
125
|
keyless: false,
|
|
113
126
|
promptUrl: true,
|
|
127
|
+
keyHint: "dash.cloudflare.com → My Profile → API Tokens",
|
|
128
|
+
// Cloudflare Workers AI returns 405 for GET /v1/models; use a curated list.
|
|
129
|
+
fallbackModels: [
|
|
130
|
+
"@cf/meta/llama-4-scout-17b-16e-instruct",
|
|
131
|
+
"@cf/meta/llama-3.3-70b-instruct-fp8-fast",
|
|
132
|
+
"@cf/meta/llama-3.1-8b-instruct",
|
|
133
|
+
"@cf/qwen/qwen3-30b-a3b-fp8",
|
|
134
|
+
"@cf/deepseek-ai/deepseek-r1-distill-qwen-32b",
|
|
135
|
+
],
|
|
136
|
+
},
|
|
137
|
+
cloudflare_ai_gateway: {
|
|
138
|
+
displayName: "Cloudflare AI Gateway",
|
|
139
|
+
// YOUR_PROVIDER is the upstream slug (e.g. "workers-ai", "openai"). /v1 is
|
|
140
|
+
// appended so that fetchModels and chat completions hit the correct path.
|
|
141
|
+
baseUrl: "https://gateway.ai.cloudflare.com/v1/YOUR_ACCOUNT_ID/YOUR_GATEWAY_SLUG/YOUR_PROVIDER/v1",
|
|
142
|
+
keyless: false,
|
|
143
|
+
promptUrl: true,
|
|
144
|
+
keyHint: "dash.cloudflare.com → My Profile → API Tokens",
|
|
145
|
+
fallbackModels: [
|
|
146
|
+
"@cf/meta/llama-4-scout-17b-16e-instruct",
|
|
147
|
+
"@cf/meta/llama-3.3-70b-instruct-fp8-fast",
|
|
148
|
+
"@cf/meta/llama-3.1-8b-instruct",
|
|
149
|
+
"@cf/qwen/qwen3-30b-a3b-fp8",
|
|
150
|
+
"@cf/deepseek-ai/deepseek-r1-distill-qwen-32b",
|
|
151
|
+
],
|
|
114
152
|
},
|
|
115
153
|
zhipu: {
|
|
116
154
|
displayName: "Zhipu (Z.ai / BigModel)",
|
|
117
155
|
baseUrl: "https://open.bigmodel.cn/api/paas/v4",
|
|
118
156
|
keyless: false,
|
|
157
|
+
keyHint: "open.bigmodel.cn/usercenter/apikeys",
|
|
158
|
+
},
|
|
159
|
+
zai: {
|
|
160
|
+
displayName: "Z.ai",
|
|
161
|
+
baseUrl: "https://api.z.ai/api/paas/v4",
|
|
162
|
+
keyless: false,
|
|
163
|
+
keyHint: "platform.z.ai",
|
|
119
164
|
},
|
|
120
165
|
cohere: {
|
|
121
166
|
displayName: "Cohere",
|
|
122
167
|
baseUrl: "https://api.cohere.com/compatibility/v1",
|
|
123
168
|
keyless: false,
|
|
169
|
+
keyHint: "dashboard.cohere.com/api-keys",
|
|
170
|
+
},
|
|
171
|
+
deepseek: {
|
|
172
|
+
displayName: "DeepSeek",
|
|
173
|
+
baseUrl: "https://api.deepseek.com/v1",
|
|
174
|
+
keyless: false,
|
|
175
|
+
keyHint: "platform.deepseek.com/api_keys",
|
|
176
|
+
},
|
|
177
|
+
xai: {
|
|
178
|
+
displayName: "xAI (Grok)",
|
|
179
|
+
baseUrl: "https://api.x.ai/v1",
|
|
180
|
+
keyless: false,
|
|
181
|
+
keyHint: "console.x.ai",
|
|
182
|
+
},
|
|
183
|
+
huggingface: {
|
|
184
|
+
displayName: "Hugging Face",
|
|
185
|
+
baseUrl: "https://router.huggingface.co/v1",
|
|
186
|
+
keyless: false,
|
|
187
|
+
keyHint: "huggingface.co/settings/tokens",
|
|
188
|
+
fallbackModels: [
|
|
189
|
+
"meta-llama/Llama-3.3-70B-Instruct",
|
|
190
|
+
"meta-llama/Meta-Llama-3-8B-Instruct",
|
|
191
|
+
"mistralai/Mistral-7B-Instruct-v0.3",
|
|
192
|
+
"Qwen/Qwen2.5-72B-Instruct",
|
|
193
|
+
],
|
|
194
|
+
},
|
|
195
|
+
moonshot: {
|
|
196
|
+
displayName: "Moonshot (Kimi)",
|
|
197
|
+
baseUrl: "https://api.moonshot.ai/v1",
|
|
198
|
+
keyless: false,
|
|
199
|
+
keyHint: "platform.moonshot.ai/console/api-key",
|
|
200
|
+
},
|
|
201
|
+
minimax: {
|
|
202
|
+
displayName: "MiniMax",
|
|
203
|
+
baseUrl: "https://api.minimax.io/v1",
|
|
204
|
+
keyless: false,
|
|
205
|
+
keyHint: "platform.minimax.io",
|
|
206
|
+
},
|
|
207
|
+
venice: {
|
|
208
|
+
displayName: "Venice AI",
|
|
209
|
+
baseUrl: "https://api.venice.ai/api/v1",
|
|
210
|
+
keyless: false,
|
|
211
|
+
keyHint: "venice.ai/settings/api",
|
|
124
212
|
},
|
|
125
213
|
ollama: {
|
|
126
214
|
displayName: "Ollama (local, keyless)",
|
|
@@ -128,6 +216,32 @@ const TEMPLATES: Record<string, {
|
|
|
128
216
|
keyless: true,
|
|
129
217
|
promptUrl: true,
|
|
130
218
|
},
|
|
219
|
+
ollama_cloud: {
|
|
220
|
+
displayName: "Ollama Cloud",
|
|
221
|
+
baseUrl: "https://ollama.com/v1",
|
|
222
|
+
keyless: false,
|
|
223
|
+
keyHint: "ollama.com/settings/api-keys",
|
|
224
|
+
},
|
|
225
|
+
llmproxy: {
|
|
226
|
+
displayName: "llmproxy (local)",
|
|
227
|
+
baseUrl: "http://localhost:8080/v1",
|
|
228
|
+
keyless: true,
|
|
229
|
+
promptUrl: true,
|
|
230
|
+
},
|
|
231
|
+
vercel: {
|
|
232
|
+
displayName: "Vercel AI Gateway",
|
|
233
|
+
baseUrl: "https://ai-gateway.vercel.sh/v1",
|
|
234
|
+
keyless: false,
|
|
235
|
+
keyHint: "vercel.com/account/tokens",
|
|
236
|
+
},
|
|
237
|
+
opencode_zen: {
|
|
238
|
+
displayName: "OpenCode Zen",
|
|
239
|
+
baseUrl: "https://opencode.ai/zen/v1",
|
|
240
|
+
keyless: false,
|
|
241
|
+
keyHint: "opencode.ai",
|
|
242
|
+
// Verify current model IDs via GET /v1/models — the free model list may change.
|
|
243
|
+
modelFilter: ["big-pickle", "deepseek-v4-flash-free", "minimax-m2.5-free", "nemotron-3-super-free"],
|
|
244
|
+
},
|
|
131
245
|
custom: {
|
|
132
246
|
displayName: "Custom Endpoint",
|
|
133
247
|
baseUrl: "",
|
|
@@ -182,6 +296,21 @@ function saveConfig(config: ExtensionConfig): void {
|
|
|
182
296
|
// Networking
|
|
183
297
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
184
298
|
|
|
299
|
+
function isLocalUrl(url: string): boolean {
|
|
300
|
+
try {
|
|
301
|
+
const { hostname } = new URL(url);
|
|
302
|
+
return (
|
|
303
|
+
hostname === "localhost" ||
|
|
304
|
+
hostname === "127.0.0.1" ||
|
|
305
|
+
hostname === "::1" ||
|
|
306
|
+
hostname.endsWith(".local") ||
|
|
307
|
+
hostname.endsWith(".localhost")
|
|
308
|
+
);
|
|
309
|
+
} catch {
|
|
310
|
+
return false;
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
185
314
|
async function fetchModels(baseUrl: string, apiKey: string | null): Promise<CachedModel[]> {
|
|
186
315
|
const url = `${baseUrl.replace(/\/+$/, "")}/models`;
|
|
187
316
|
const headers: Record<string, string> = { Accept: "application/json" };
|
|
@@ -224,9 +353,9 @@ function compatKey(key: string): string {
|
|
|
224
353
|
|
|
225
354
|
function registerProvider(pi: ExtensionAPI, key: string, p: ProviderConfig): void {
|
|
226
355
|
pi.registerProvider(compatKey(key), {
|
|
227
|
-
name:
|
|
356
|
+
name: `compat/${key.replace(/_/g, "-")}`,
|
|
228
357
|
baseUrl: p.baseUrl,
|
|
229
|
-
apiKey: p.apiKey ?? "
|
|
358
|
+
apiKey: p.apiKey ?? (isLocalUrl(p.baseUrl) ? "local" : ""),
|
|
230
359
|
api: "openai-completions" as const,
|
|
231
360
|
models: buildProviderModels(p.cachedModels),
|
|
232
361
|
});
|
|
@@ -315,8 +444,7 @@ export default async function (pi: ExtensionAPI) {
|
|
|
315
444
|
|
|
316
445
|
// Step 2 — base URL
|
|
317
446
|
let baseUrl = tpl.baseUrl;
|
|
318
|
-
if (key === "
|
|
319
|
-
// Ask for just the account ID and splice it into the template URL.
|
|
447
|
+
if (key === "cloudflare_workers") {
|
|
320
448
|
const entered = await ctx.ui.input(
|
|
321
449
|
"Account ID",
|
|
322
450
|
"Your Cloudflare Account ID (find it on the Cloudflare dashboard overview page):",
|
|
@@ -326,10 +454,41 @@ export default async function (pi: ExtensionAPI) {
|
|
|
326
454
|
const accountId = entered.trim();
|
|
327
455
|
if (!accountId) { ctx.ui.notify("Account ID cannot be empty.", "error"); return; }
|
|
328
456
|
baseUrl = tpl.baseUrl.replace("YOUR_ACCOUNT_ID", accountId);
|
|
457
|
+
} else if (key === "cloudflare_ai_gateway") {
|
|
458
|
+
const accountIdInput = await ctx.ui.input(
|
|
459
|
+
"Account ID",
|
|
460
|
+
"Your Cloudflare Account ID (find it on the Cloudflare dashboard overview page):",
|
|
461
|
+
""
|
|
462
|
+
);
|
|
463
|
+
if (accountIdInput == null) { ctx.ui.notify("Login cancelled.", "info"); return; }
|
|
464
|
+
const accountId = accountIdInput.trim();
|
|
465
|
+
if (!accountId) { ctx.ui.notify("Account ID cannot be empty.", "error"); return; }
|
|
466
|
+
|
|
467
|
+
const gatewayInput = await ctx.ui.input(
|
|
468
|
+
"Gateway Name",
|
|
469
|
+
"Your AI Gateway name/slug (find it under AI → AI Gateway in the Cloudflare dashboard):",
|
|
470
|
+
""
|
|
471
|
+
);
|
|
472
|
+
if (gatewayInput == null) { ctx.ui.notify("Login cancelled.", "info"); return; }
|
|
473
|
+
const gatewaySlug = gatewayInput.trim();
|
|
474
|
+
if (!gatewaySlug) { ctx.ui.notify("Gateway name cannot be empty.", "error"); return; }
|
|
475
|
+
|
|
476
|
+
const providerInput = await ctx.ui.input(
|
|
477
|
+
"Provider",
|
|
478
|
+
"Upstream provider slug (e.g. openai, workers-ai, anthropic — must match your gateway config):",
|
|
479
|
+
"openai"
|
|
480
|
+
);
|
|
481
|
+
if (providerInput == null) { ctx.ui.notify("Login cancelled.", "info"); return; }
|
|
482
|
+
const provider = providerInput.trim() || "openai";
|
|
483
|
+
|
|
484
|
+
baseUrl = tpl.baseUrl
|
|
485
|
+
.replace("YOUR_ACCOUNT_ID", accountId)
|
|
486
|
+
.replace("YOUR_GATEWAY_SLUG", gatewaySlug)
|
|
487
|
+
.replace("YOUR_PROVIDER", provider);
|
|
329
488
|
} else if (tpl.promptUrl) {
|
|
330
489
|
const defaultUrl = tpl.baseUrl;
|
|
331
|
-
const prompt =
|
|
332
|
-
? `
|
|
490
|
+
const prompt = isLocalUrl(defaultUrl)
|
|
491
|
+
? `Base URL — press Enter for default (${defaultUrl}):`
|
|
333
492
|
: "Base URL of your endpoint (e.g. https://api.example.com/v1):";
|
|
334
493
|
const entered = await ctx.ui.input("Base URL", prompt, defaultUrl);
|
|
335
494
|
if (entered == null) { ctx.ui.notify("Login cancelled.", "info"); return; }
|
|
@@ -337,14 +496,13 @@ export default async function (pi: ExtensionAPI) {
|
|
|
337
496
|
if (!baseUrl) { ctx.ui.notify("Base URL cannot be empty.", "error"); return; }
|
|
338
497
|
}
|
|
339
498
|
|
|
340
|
-
// Step 3 — API key
|
|
499
|
+
// Step 3 — API key (skipped for keyless templates and detected local URLs)
|
|
341
500
|
let apiKey: string | null = null;
|
|
342
|
-
if (!tpl.keyless) {
|
|
343
|
-
const
|
|
344
|
-
|
|
345
|
-
"Your API key — leave blank if keyless:"
|
|
346
|
-
|
|
347
|
-
);
|
|
501
|
+
if (!tpl.keyless && !isLocalUrl(baseUrl)) {
|
|
502
|
+
const keyPrompt = tpl.keyHint
|
|
503
|
+
? `Your API key (required) — get it at ${tpl.keyHint}:`
|
|
504
|
+
: "Your API key — leave blank if keyless:";
|
|
505
|
+
const entered = await ctx.ui.input("API Key", keyPrompt, "");
|
|
348
506
|
if (entered == null) { ctx.ui.notify("Login cancelled.", "info"); return; }
|
|
349
507
|
apiKey = entered.trim() || null;
|
|
350
508
|
}
|
|
@@ -355,8 +513,16 @@ export default async function (pi: ExtensionAPI) {
|
|
|
355
513
|
try {
|
|
356
514
|
models = await fetchModels(baseUrl, apiKey);
|
|
357
515
|
} catch (err) {
|
|
358
|
-
|
|
359
|
-
|
|
516
|
+
if (tpl.fallbackModels && tpl.fallbackModels.length > 0) {
|
|
517
|
+
ctx.ui.notify(
|
|
518
|
+
`Could not fetch model list from ${tpl.displayName} (${err}).\nUsing built-in model list instead.`,
|
|
519
|
+
"warning"
|
|
520
|
+
);
|
|
521
|
+
models = tpl.fallbackModels.map((id) => ({ id }));
|
|
522
|
+
} else {
|
|
523
|
+
ctx.ui.notify(`Connection failed — not saved.\n${err}`, "error");
|
|
524
|
+
return;
|
|
525
|
+
}
|
|
360
526
|
}
|
|
361
527
|
if (!models.length) {
|
|
362
528
|
ctx.ui.notify("Connected but no models returned. Check URL and key.", "error");
|
package/package.json
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@billjr99/pi-openai-compat",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.9",
|
|
4
4
|
"description": "pi-coding-agent extension: OpenAI-compatible endpoint support (OpenRouter, NVIDIA NIM, Nous Portal, Ollama, custom)",
|
|
5
5
|
"author": "Bill Mongan <https://github.com/BillJr99>",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"homepage": "https://github.com/BillJr99/pi-openai-compat#readme",
|
|
8
8
|
"repository": {
|
|
9
9
|
"type": "git",
|
|
10
|
-
"url": "https://github.com/BillJr99/pi-openai-compat.git"
|
|
10
|
+
"url": "git+https://github.com/BillJr99/pi-openai-compat.git"
|
|
11
11
|
},
|
|
12
12
|
"bugs": {
|
|
13
13
|
"url": "https://github.com/BillJr99/pi-openai-compat/issues"
|
|
@@ -17,7 +17,11 @@
|
|
|
17
17
|
"README.md"
|
|
18
18
|
],
|
|
19
19
|
"pi": {
|
|
20
|
-
"extensions": [
|
|
20
|
+
"extensions": [
|
|
21
|
+
"./index.ts"
|
|
22
|
+
]
|
|
21
23
|
},
|
|
22
|
-
"
|
|
24
|
+
"devDependencies": {
|
|
25
|
+
"typescript": "^6.0.3"
|
|
26
|
+
}
|
|
23
27
|
}
|