@billjr99/pi-openai-compat 1.1.14 → 1.1.15
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 +10 -5
- package/index.ts +45 -13
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -311,11 +311,16 @@ parse error.
|
|
|
311
311
|
**Cloudflare AI Gateway returns 401 Unauthorized**
|
|
312
312
|
Check both: (1) the API token has `AI Gateway: Run` *and* `Workers AI: Read`
|
|
313
313
|
under "Permissions", scoped to the correct account; and (2) the gateway slug
|
|
314
|
-
in the URL actually exists under that account — list with
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
314
|
+
in the URL actually exists under that account — list them with:
|
|
315
|
+
|
|
316
|
+
```bash
|
|
317
|
+
curl https://api.cloudflare.com/client/v4/accounts/$ACCOUNT_ID/ai-gateway/gateways \
|
|
318
|
+
-H "Authorization: Bearer $TOKEN"
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
An empty `result` array means the gateway isn't there (you may need to create it
|
|
322
|
+
in **dash.cloudflare.com → AI → AI Gateway**, or you're querying the wrong
|
|
323
|
+
account).
|
|
319
324
|
|
|
320
325
|
**No models appear after login**
|
|
321
326
|
For Ollama: pull at least one model first (`ollama pull llama3`).
|
package/index.ts
CHANGED
|
@@ -44,6 +44,10 @@ interface ProviderConfig {
|
|
|
44
44
|
modelsUrl?: string;
|
|
45
45
|
modelsIdField?: string;
|
|
46
46
|
modelsKeepTask?: string;
|
|
47
|
+
// Set once we've warned the user that this provider's saved baseUrl no longer
|
|
48
|
+
// matches its template and can't be auto-migrated, so the notice is shown
|
|
49
|
+
// only once rather than on every session_start while the config stays stale.
|
|
50
|
+
staleNotified?: boolean;
|
|
47
51
|
}
|
|
48
52
|
|
|
49
53
|
interface ExtensionConfig {
|
|
@@ -376,19 +380,24 @@ function escapeRegex(s: string): string {
|
|
|
376
380
|
* treat as "this provider can't be healed automatically".
|
|
377
381
|
*/
|
|
378
382
|
function recoverPlaceholders(templateUrl: string, savedUrl: string): Record<string, string> | null {
|
|
383
|
+
// Tolerate trailing-slash differences the same way the rest of the code does
|
|
384
|
+
// (e.g. baseUrl.replace(/\/+$/, "")), so a saved URL that differs only by a
|
|
385
|
+
// trailing slash still heals instead of being marked stale.
|
|
386
|
+
const tpl = templateUrl.replace(/\/+$/, "");
|
|
387
|
+
const saved = savedUrl.replace(/\/+$/, "");
|
|
379
388
|
const order: string[] = [];
|
|
380
389
|
const placeholderRe = new RegExp(URL_PLACEHOLDERS.join("|"), "g");
|
|
381
390
|
let source = "^";
|
|
382
391
|
let lastIndex = 0;
|
|
383
392
|
let m: RegExpExecArray | null;
|
|
384
|
-
while ((m = placeholderRe.exec(
|
|
385
|
-
source += escapeRegex(
|
|
393
|
+
while ((m = placeholderRe.exec(tpl)) !== null) {
|
|
394
|
+
source += escapeRegex(tpl.slice(lastIndex, m.index)) + "([^/]+)";
|
|
386
395
|
order.push(m[0]);
|
|
387
396
|
lastIndex = m.index + m[0].length;
|
|
388
397
|
}
|
|
389
|
-
source += escapeRegex(
|
|
398
|
+
source += escapeRegex(tpl.slice(lastIndex)) + "$";
|
|
390
399
|
|
|
391
|
-
const match = new RegExp(source).exec(
|
|
400
|
+
const match = new RegExp(source).exec(saved);
|
|
392
401
|
if (!match) return null;
|
|
393
402
|
const out: Record<string, string> = {};
|
|
394
403
|
order.forEach((name, i) => { out[name] = match[i + 1]; });
|
|
@@ -404,7 +413,8 @@ function applyPlaceholders(url: string, values: Record<string, string>): string
|
|
|
404
413
|
/**
|
|
405
414
|
* Backfill missing discovery fields on saved providers from their template.
|
|
406
415
|
* Mutates `config` in place; the caller is responsible for persisting when
|
|
407
|
-
* `healed` is non-empty. `stale` lists
|
|
416
|
+
* `healed` is non-empty. `stale` lists the provider *keys* that need a manual
|
|
417
|
+
* re-login (the caller resolves display names and dedupes notifications).
|
|
408
418
|
*/
|
|
409
419
|
function migrateDiscoveryFields(config: ExtensionConfig): { healed: string[]; stale: string[] } {
|
|
410
420
|
const healed: string[] = [];
|
|
@@ -416,7 +426,7 @@ function migrateDiscoveryFields(config: ExtensionConfig): { healed: string[]; st
|
|
|
416
426
|
if (p.modelsUrl) continue; // already set (fresh login or manual edit) — never clobber
|
|
417
427
|
|
|
418
428
|
const values = recoverPlaceholders(tpl.baseUrl, p.baseUrl);
|
|
419
|
-
if (!values) { stale.push(
|
|
429
|
+
if (!values) { stale.push(key); continue; }
|
|
420
430
|
|
|
421
431
|
p.modelsUrl = applyPlaceholders(tpl.modelsUrl, values);
|
|
422
432
|
p.modelsIdField = tpl.modelsIdField;
|
|
@@ -493,8 +503,10 @@ async function fetchModels(
|
|
|
493
503
|
else if (Array.isArray(obj.result)) raw = obj.result as RawModel[];
|
|
494
504
|
}
|
|
495
505
|
if (!raw) {
|
|
506
|
+
// `url` may be an override (e.g. /catalog/models, /ai/models/search), so
|
|
507
|
+
// keep the wording generic rather than referring specifically to /models.
|
|
496
508
|
throw new Error(
|
|
497
|
-
`Unexpected
|
|
509
|
+
`Unexpected model catalog payload shape from ${url} ` +
|
|
498
510
|
`(expected an array or an object with a "data" or "result" array).`
|
|
499
511
|
);
|
|
500
512
|
}
|
|
@@ -506,8 +518,14 @@ async function fetchModels(
|
|
|
506
518
|
return taskName.toLowerCase() === keepTask.toLowerCase();
|
|
507
519
|
})
|
|
508
520
|
.map((m) => {
|
|
509
|
-
|
|
510
|
-
|
|
521
|
+
// Coerce the id field defensively: some upstreams expose a numeric id,
|
|
522
|
+
// and storing a non-string would break the localeCompare sort below.
|
|
523
|
+
const rawId = (m as Record<string, unknown>)[idField];
|
|
524
|
+
const id =
|
|
525
|
+
typeof rawId === "string" ? rawId :
|
|
526
|
+
typeof rawId === "number" ? String(rawId) :
|
|
527
|
+
"";
|
|
528
|
+
return { id, contextWindow: m.context_window, maxTokens: m.max_tokens };
|
|
511
529
|
})
|
|
512
530
|
.filter((m) => Boolean(m.id))
|
|
513
531
|
.sort((a, b) => a.id.localeCompare(b.id));
|
|
@@ -615,12 +633,19 @@ export default async function (pi: ExtensionAPI) {
|
|
|
615
633
|
"warning"
|
|
616
634
|
);
|
|
617
635
|
}
|
|
618
|
-
|
|
636
|
+
// Warn about stale providers only once: re-notifying on every session_start
|
|
637
|
+
// while the config stays stale would be noise. Persist the flag so the
|
|
638
|
+
// notice survives restarts.
|
|
639
|
+
const toNotify = stale.filter((key) => !config.providers[key]?.staleNotified);
|
|
640
|
+
if (toNotify.length > 0) {
|
|
641
|
+
const names = toNotify.map((key) => config.providers[key].displayName);
|
|
619
642
|
ctx.ui.notify(
|
|
620
|
-
`OpenAI-compat: ${
|
|
643
|
+
`OpenAI-compat: ${names.join(", ")} ${names.length === 1 ? "has" : "have"} an out-of-date ` +
|
|
621
644
|
`base URL — run /compat-login to update (its endpoint changed and can't be migrated automatically).`,
|
|
622
645
|
"warning"
|
|
623
646
|
);
|
|
647
|
+
for (const key of toNotify) config.providers[key].staleNotified = true;
|
|
648
|
+
saveConfig(config);
|
|
624
649
|
}
|
|
625
650
|
});
|
|
626
651
|
|
|
@@ -807,10 +832,17 @@ export default async function (pi: ExtensionAPI) {
|
|
|
807
832
|
keys = providerKeys;
|
|
808
833
|
} else {
|
|
809
834
|
const ALL = "All providers";
|
|
810
|
-
|
|
835
|
+
// Embed the internal provider key in each label so duplicate
|
|
836
|
+
// displayNames (or a provider literally named "All providers") can't
|
|
837
|
+
// collide with each other or the special "All providers" option.
|
|
838
|
+
const options = providerKeys.map((k) => ({
|
|
839
|
+
key: k,
|
|
840
|
+
label: `${config.providers[k].displayName} [${k}]`,
|
|
841
|
+
}));
|
|
842
|
+
const labels = [ALL, ...options.map((o) => o.label)];
|
|
811
843
|
const chosen = await ctx.ui.select("Refresh which provider?", labels);
|
|
812
844
|
if (!chosen) { ctx.ui.notify("Cancelled.", "info"); return; }
|
|
813
|
-
keys = chosen === ALL ? providerKeys : [
|
|
845
|
+
keys = chosen === ALL ? providerKeys : [options[labels.indexOf(chosen) - 1].key];
|
|
814
846
|
}
|
|
815
847
|
|
|
816
848
|
const refreshed: string[] = [];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@billjr99/pi-openai-compat",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.15",
|
|
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",
|