@billjr99/pi-openai-compat 1.1.15 → 1.1.16

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 +28 -0
  2. package/index.ts +48 -11
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -56,8 +56,27 @@ 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
+ > **llmproxy and the `__` model-id rewrite.**
63
+ > [llmproxy](https://github.com/BillJr99/llmproxy) advertises model ids in the
64
+ > `provider__model` form (e.g. `openrouter__gpt-4`) and exposes virtual models
65
+ > such as `llmproxy__free` and `llmproxy__loadbalanced`. pi rejects model ids that
66
+ > contain `__`, so without special handling **every llmproxy model is silently
67
+ > dropped from `/model`**. The llmproxy template therefore sets
68
+ > `rewriteDoubleUnderscore: true`: the extension rewrites the first `__` of each id
69
+ > to `/` (e.g. `openrouter__gpt-4` → `openrouter/gpt-4`, `llmproxy__free` →
70
+ > `llmproxy/free`) before registering with pi. llmproxy canonicalizes that slash
71
+ > form back to `__` on each request, so routing still works.
72
+ >
73
+ > The flag is a per-provider config field (`rewriteDoubleUnderscore`, default
74
+ > **false**) and is enabled automatically only for the llmproxy template — other
75
+ > providers are unaffected. If you added llmproxy as a **Custom** endpoint instead
76
+ > of via the llmproxy template, set `"rewriteDoubleUnderscore": true` on that
77
+ > provider in `~/.config/pi-openai-compat/config.json` (or re-run `/compat-login`
78
+ > and pick **llmproxy (local)**).
79
+
61
80
  > **Providers whose model catalog lives at a non-standard `/models` path (as of June 2026)**
62
81
  > Some providers don't return models at `<base_url>/models`. The extension
63
82
  > handles them in one of two ways:
@@ -327,6 +346,15 @@ For Ollama: pull at least one model first (`ollama pull llama3`).
327
346
  For OpenRouter: some keys are restricted to free-tier models only.
328
347
  For NIM: confirm your account has inference access enabled.
329
348
 
349
+ **`/compat-login` reports N models but far fewer appear in `/model`**
350
+ This is the classic llmproxy symptom: pi drops every model id containing `__`, so
351
+ llmproxy's `provider__model` ids (and `llmproxy__free` / `llmproxy__loadbalanced`)
352
+ never show. Make sure you logged in via the **llmproxy (local)** template (it sets
353
+ `rewriteDoubleUnderscore: true` automatically). If you used **Custom**, add
354
+ `"rewriteDoubleUnderscore": true` to that provider in
355
+ `~/.config/pi-openai-compat/config.json` and re-run `/compat-refresh`. After the
356
+ fix the ids appear in slash form (`openrouter/gpt-4`, `llmproxy/free`).
357
+
330
358
  **Models appear in `/model` but requests fail**
331
359
  Check `/compat-login` ran successfully (no error message).
332
360
  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. pi rejects model ids containing "__", so providers like
53
+ // llmproxy (which uses "provider__model" ids) would otherwise have their entire
54
+ // catalog dropped from /model. The rewrite is the inverse of llmproxy's own
55
+ // request-side canonicalization, so requests still round-trip. Defaults false;
56
+ // the wizard turns it on only for the llmproxy template.
57
+ rewriteDoubleUnderscore?: boolean;
51
58
  }
52
59
 
53
60
  interface ExtensionConfig {
@@ -97,6 +104,12 @@ 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
+ * pi drops model ids containing "__", so this is required for llmproxy (whose
110
+ * ids are "provider__model"). Default false; set true only where pi needs it.
111
+ */
112
+ rewriteDoubleUnderscore?: boolean;
100
113
  }> = {
101
114
  openrouter: {
102
115
  displayName: "OpenRouter",
@@ -288,6 +301,10 @@ const TEMPLATES: Record<string, {
288
301
  baseUrl: "http://localhost:8080/v1",
289
302
  keyless: true,
290
303
  promptUrl: true,
304
+ // llmproxy advertises "provider__model" ids (and virtuals like
305
+ // "llmproxy__free"). pi rejects "__" in model ids, so rewrite the first
306
+ // "__" to "/" here; llmproxy canonicalizes the slash form back on requests.
307
+ rewriteDoubleUnderscore: true,
291
308
  },
292
309
  vercel: {
293
310
  displayName: "Vercel AI Gateway",
@@ -535,16 +552,31 @@ async function fetchModels(
535
552
  // Provider registration helpers
536
553
  // ─────────────────────────────────────────────────────────────────────────────
537
554
 
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
- }));
555
+ /**
556
+ * Rewrite the FIRST "__" of a model id to "/". Only the first occurrence is
557
+ * touched: the provider/virtual segment that precedes it never contains "__" or
558
+ * "/", so this is the exact inverse of the slash→"__" canonicalization llmproxy
559
+ * applies on the request side, keeping ids round-trippable. Ids without "__" are
560
+ * returned unchanged.
561
+ */
562
+ function rewriteFirstDoubleUnderscore(id: string): string {
563
+ const i = id.indexOf("__");
564
+ return i === -1 ? id : `${id.slice(0, i)}/${id.slice(i + 2)}`;
565
+ }
566
+
567
+ function buildProviderModels(models: CachedModel[], rewriteDoubleUnderscore = false) {
568
+ return models.map((m) => {
569
+ const id = rewriteDoubleUnderscore ? rewriteFirstDoubleUnderscore(m.id) : m.id;
570
+ return {
571
+ id,
572
+ name: id,
573
+ reasoning: false,
574
+ input: ["text"] as string[],
575
+ cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
576
+ contextWindow: m.contextWindow ?? 128_000,
577
+ maxTokens: m.maxTokens ?? 4_096,
578
+ };
579
+ });
548
580
  }
549
581
 
550
582
  function compatKey(key: string): string {
@@ -552,12 +584,16 @@ function compatKey(key: string): string {
552
584
  }
553
585
 
554
586
  function registerProvider(pi: ExtensionAPI, key: string, p: ProviderConfig): void {
587
+ // Honor the persisted per-provider flag; fall back to the template default for
588
+ // this key so providers logged in before the flag existed (e.g. an existing
589
+ // llmproxy provider) still get the rewrite without a re-login.
590
+ const rewrite = p.rewriteDoubleUnderscore ?? TEMPLATES[key]?.rewriteDoubleUnderscore ?? false;
555
591
  pi.registerProvider(compatKey(key), {
556
592
  name: `compat/${key.replace(/_/g, "-")}`,
557
593
  baseUrl: p.baseUrl,
558
594
  apiKey: p.apiKey ?? (isLocalUrl(p.baseUrl) ? "local" : ""),
559
595
  api: "openai-completions" as const,
560
- models: buildProviderModels(p.cachedModels),
596
+ models: buildProviderModels(p.cachedModels, rewrite),
561
597
  });
562
598
  }
563
599
 
@@ -800,6 +836,7 @@ export default async function (pi: ExtensionAPI) {
800
836
  modelsUrl,
801
837
  modelsIdField: tpl.modelsIdField,
802
838
  modelsKeepTask: tpl.modelsKeepTask,
839
+ rewriteDoubleUnderscore: tpl.rewriteDoubleUnderscore,
803
840
  };
804
841
  saveConfig(config);
805
842
  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.16",
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",