@billjr99/pi-openai-compat 1.1.15 → 1.1.17

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.
Files changed (3) hide show
  1. package/README.md +30 -0
  2. package/index.ts +49 -11
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -56,8 +56,29 @@ If pi is already running when you install, type `/reload` first.
56
56
  | **OpenCode Zen** | `https://opencode.ai/zen/v1` | API key from opencode.ai |
57
57
  | **Ollama (local)** | `http://localhost:11434/v1` | Keyless |
58
58
  | **Ollama Cloud** | `https://ollama.com/v1` | Ollama Cloud API key from ollama.com |
59
+ | **llmproxy** | `http://localhost:8080/v1` (editable) | Keyless by default; bearer token if your instance requires one |
59
60
  | **Custom** | Any URL you supply | Optional bearer token |
60
61
 
62
+ > **The `__` → `/` model-id rewrite (display compatibility).**
63
+ > Some providers advertise model ids in a `provider__model` form. For example,
64
+ > [llmproxy](https://github.com/BillJr99/llmproxy) exposes ids like
65
+ > `openrouter__gpt-4` and virtual models such as `llmproxy__free` and
66
+ > `llmproxy__loadbalanced`. The extension can optionally rewrite the first `__`
67
+ > of each id to `/` (e.g. `openrouter__gpt-4` → `openrouter/gpt-4`,
68
+ > `llmproxy__free` → `llmproxy/free`) before registering with pi, so the ids
69
+ > display in the more familiar `provider/model` slash form. Upstreams that use
70
+ > the `__` convention canonicalize the slash form back to `__` on each request,
71
+ > so routing still works.
72
+ >
73
+ > This is a presentation-only convenience controlled by a per-provider config
74
+ > field (`rewriteDoubleUnderscore`, default **false**). It is enabled
75
+ > automatically for the llmproxy template purely for display consistency — other
76
+ > providers are unaffected, and you can turn it on or off freely. If you added a
77
+ > provider as a **Custom** endpoint and want the slash-form display, set
78
+ > `"rewriteDoubleUnderscore": true` on that provider in
79
+ > `~/.config/pi-openai-compat/config.json` (or re-run `/compat-login` and pick a
80
+ > template that enables it). Leaving it off keeps the original `__` ids.
81
+
61
82
  > **Providers whose model catalog lives at a non-standard `/models` path (as of June 2026)**
62
83
  > Some providers don't return models at `<base_url>/models`. The extension
63
84
  > handles them in one of two ways:
@@ -327,6 +348,15 @@ For Ollama: pull at least one model first (`ollama pull llama3`).
327
348
  For OpenRouter: some keys are restricted to free-tier models only.
328
349
  For NIM: confirm your account has inference access enabled.
329
350
 
351
+ **`/compat-login` reports N models but far fewer appear in `/model`**
352
+ The usual cause is a **scoped or restricted API key/account** — the catalog
353
+ endpoint advertises the full model list, but your credentials are only entitled
354
+ to a subset, so pi only surfaces the ones you can actually use. Check whether your
355
+ key is limited to specific models or a free tier on the provider's dashboard.
356
+ (This is unrelated to the `__` → `/` rewrite, which is a display-only convenience —
357
+ see the note in **Supported providers** above. Toggling `rewriteDoubleUnderscore`
358
+ only changes how ids are displayed, not how many appear.)
359
+
330
360
  **Models appear in `/model` but requests fail**
331
361
  Check `/compat-login` ran successfully (no error message).
332
362
  Verify Ollama is still running if using a local endpoint.
package/index.ts CHANGED
@@ -48,6 +48,13 @@ interface ProviderConfig {
48
48
  // matches its template and can't be auto-migrated, so the notice is shown
49
49
  // only once rather than on every session_start while the config stays stale.
50
50
  staleNotified?: boolean;
51
+ // When true, rewrite the FIRST "__" of every model id to "/" before
52
+ // registering with pi, so ids from providers that use a "provider__model"
53
+ // convention (e.g. llmproxy) display in the more familiar "provider/model"
54
+ // slash form. This is a display-only convenience: such upstreams canonicalize
55
+ // the slash form back to "__" on each request, so requests still round-trip.
56
+ // Defaults false; the wizard turns it on only for the llmproxy template.
57
+ rewriteDoubleUnderscore?: boolean;
51
58
  }
52
59
 
53
60
  interface ExtensionConfig {
@@ -97,6 +104,13 @@ const TEMPLATES: Record<string, {
97
104
  modelsIdField?: string;
98
105
  /** Keep only models whose task.name matches this string (case-insensitive). */
99
106
  modelsKeepTask?: string;
107
+ /**
108
+ * Rewrite the first "__" of each model id to "/" before registering with pi,
109
+ * for display compatibility — ids from "provider__model" upstreams (e.g.
110
+ * llmproxy) then show in the familiar "provider/model" slash form. Display-only;
111
+ * default false; set true where you want the slash-form presentation.
112
+ */
113
+ rewriteDoubleUnderscore?: boolean;
100
114
  }> = {
101
115
  openrouter: {
102
116
  displayName: "OpenRouter",
@@ -288,6 +302,10 @@ const TEMPLATES: Record<string, {
288
302
  baseUrl: "http://localhost:8080/v1",
289
303
  keyless: true,
290
304
  promptUrl: true,
305
+ // llmproxy advertises "provider__model" ids (and virtuals like
306
+ // "llmproxy__free"). Rewrite the first "__" to "/" here so they display in
307
+ // the familiar slash form; llmproxy canonicalizes it back on requests.
308
+ rewriteDoubleUnderscore: true,
291
309
  },
292
310
  vercel: {
293
311
  displayName: "Vercel AI Gateway",
@@ -535,16 +553,31 @@ async function fetchModels(
535
553
  // Provider registration helpers
536
554
  // ─────────────────────────────────────────────────────────────────────────────
537
555
 
538
- function buildProviderModels(models: CachedModel[]) {
539
- return models.map((m) => ({
540
- id: m.id,
541
- name: m.id,
542
- reasoning: false,
543
- input: ["text"] as string[],
544
- cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
545
- contextWindow: m.contextWindow ?? 128_000,
546
- maxTokens: m.maxTokens ?? 4_096,
547
- }));
556
+ /**
557
+ * Rewrite the FIRST "__" of a model id to "/". Only the first occurrence is
558
+ * touched: the provider/virtual segment that precedes it never contains "__" or
559
+ * "/", so this is the exact inverse of the slash→"__" canonicalization llmproxy
560
+ * applies on the request side, keeping ids round-trippable. Ids without "__" are
561
+ * returned unchanged.
562
+ */
563
+ function rewriteFirstDoubleUnderscore(id: string): string {
564
+ const i = id.indexOf("__");
565
+ return i === -1 ? id : `${id.slice(0, i)}/${id.slice(i + 2)}`;
566
+ }
567
+
568
+ function buildProviderModels(models: CachedModel[], rewriteDoubleUnderscore = false) {
569
+ return models.map((m) => {
570
+ const id = rewriteDoubleUnderscore ? rewriteFirstDoubleUnderscore(m.id) : m.id;
571
+ return {
572
+ id,
573
+ name: id,
574
+ reasoning: false,
575
+ input: ["text"] as string[],
576
+ cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
577
+ contextWindow: m.contextWindow ?? 128_000,
578
+ maxTokens: m.maxTokens ?? 4_096,
579
+ };
580
+ });
548
581
  }
549
582
 
550
583
  function compatKey(key: string): string {
@@ -552,12 +585,16 @@ function compatKey(key: string): string {
552
585
  }
553
586
 
554
587
  function registerProvider(pi: ExtensionAPI, key: string, p: ProviderConfig): void {
588
+ // Honor the persisted per-provider flag; fall back to the template default for
589
+ // this key so providers logged in before the flag existed (e.g. an existing
590
+ // llmproxy provider) still get the rewrite without a re-login.
591
+ const rewrite = p.rewriteDoubleUnderscore ?? TEMPLATES[key]?.rewriteDoubleUnderscore ?? false;
555
592
  pi.registerProvider(compatKey(key), {
556
593
  name: `compat/${key.replace(/_/g, "-")}`,
557
594
  baseUrl: p.baseUrl,
558
595
  apiKey: p.apiKey ?? (isLocalUrl(p.baseUrl) ? "local" : ""),
559
596
  api: "openai-completions" as const,
560
- models: buildProviderModels(p.cachedModels),
597
+ models: buildProviderModels(p.cachedModels, rewrite),
561
598
  });
562
599
  }
563
600
 
@@ -800,6 +837,7 @@ export default async function (pi: ExtensionAPI) {
800
837
  modelsUrl,
801
838
  modelsIdField: tpl.modelsIdField,
802
839
  modelsKeepTask: tpl.modelsKeepTask,
840
+ rewriteDoubleUnderscore: tpl.rewriteDoubleUnderscore,
803
841
  };
804
842
  saveConfig(config);
805
843
  registerProvider(pi, key, config.providers[key]);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@billjr99/pi-openai-compat",
3
- "version": "1.1.15",
3
+ "version": "1.1.17",
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",