@billjr99/pi-openai-compat 1.0.0 → 1.0.2

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 (2) hide show
  1. package/index.ts +129 -16
  2. package/package.json +1 -1
package/index.ts CHANGED
@@ -56,36 +56,127 @@ const TEMPLATES: Record<string, {
56
56
  displayName: string;
57
57
  baseUrl: string;
58
58
  keyless: boolean;
59
- keyHint?: string;
59
+ /** If set, only models whose id appears in this list are kept after fetching. */
60
+ modelFilter?: string[];
61
+ /** If true, prompt the user to confirm/edit the base URL (like ollama/custom). */
62
+ promptUrl?: boolean;
60
63
  }> = {
61
64
  openrouter: {
62
65
  displayName: "OpenRouter",
63
66
  baseUrl: "https://openrouter.ai/api/v1",
64
67
  keyless: false,
65
- keyHint: "sk-or-... from openrouter.ai/keys",
66
68
  },
67
69
  nvidia_nim: {
68
70
  displayName: "NVIDIA NIM",
69
71
  baseUrl: "https://integrate.api.nvidia.com/v1",
70
72
  keyless: false,
71
- keyHint: "nvapi-... from build.nvidia.com",
72
73
  },
73
74
  nous: {
74
75
  displayName: "Nous Research Portal",
75
76
  baseUrl: "https://inference-api.nousresearch.com/v1",
76
77
  keyless: false,
77
- keyHint: "Your Nous Portal API key",
78
+ },
79
+ google: {
80
+ displayName: "Google Gemini",
81
+ baseUrl: "https://generativelanguage.googleapis.com/v1beta/openai",
82
+ keyless: false,
83
+ modelFilter: [
84
+ "gemini-2.5-flash",
85
+ "gemini-2.5-flash-lite",
86
+ "gemini-3-flash-preview",
87
+ "gemini-3.1-flash-lite-preview",
88
+ "gemini-3.1-pro-preview",
89
+ ],
90
+ },
91
+ cerebras: {
92
+ displayName: "Cerebras",
93
+ baseUrl: "https://api.cerebras.ai/v1",
94
+ keyless: false,
95
+ modelFilter: ["qwen3-235b"],
96
+ },
97
+ github_models: {
98
+ displayName: "GitHub Models",
99
+ baseUrl: "https://models.inference.ai.azure.com",
100
+ keyless: false,
101
+ modelFilter: ["gpt-4o", "openai/gpt-4.1"],
102
+ },
103
+ sambanova: {
104
+ displayName: "SambaNova",
105
+ baseUrl: "https://api.sambanova.ai/v1",
106
+ keyless: false,
107
+ modelFilter: [
108
+ "Meta-Llama-3.3-70B-Instruct",
109
+ "DeepSeek-V3.1",
110
+ "DeepSeek-V3.2",
111
+ "gpt-oss-120b",
112
+ "Llama-4-Maverick-17B-128E-Instruct",
113
+ "gemma-3-12b-it",
114
+ ],
115
+ },
116
+ mistral: {
117
+ displayName: "Mistral",
118
+ baseUrl: "https://api.mistral.ai/v1",
119
+ keyless: false,
120
+ modelFilter: [
121
+ "mistral-large-latest",
122
+ "mistral-medium-latest",
123
+ "magistral-medium-latest",
124
+ "codestral-latest",
125
+ "devstral-latest",
126
+ ],
127
+ },
128
+ groq: {
129
+ displayName: "Groq",
130
+ baseUrl: "https://api.groq.com/openai/v1",
131
+ keyless: false,
132
+ modelFilter: [
133
+ "llama-3.3-70b-versatile",
134
+ "meta-llama/llama-4-scout-17b-16e-instruct",
135
+ "openai/gpt-oss-120b",
136
+ "openai/gpt-oss-20b",
137
+ "qwen/qwen3-32b",
138
+ "llama-3.1-8b-instant",
139
+ ],
140
+ },
141
+ cloudflare: {
142
+ displayName: "Cloudflare Workers AI",
143
+ baseUrl: "https://api.cloudflare.com/client/v4/accounts/YOUR_ACCOUNT_ID/ai/v1",
144
+ keyless: false,
145
+ promptUrl: true,
146
+ modelFilter: [
147
+ "@cf/meta/llama-3.3-70b-instruct-fp8-fast",
148
+ "@cf/meta/llama-4-scout-17b-16e-instruct",
149
+ "@cf/openai/gpt-oss-120b",
150
+ "@cf/zai-org/glm-4.7-flash",
151
+ "@cf/moonshotai/kimi-k2.5",
152
+ "@cf/moonshotai/kimi-k2.6",
153
+ "@cf/qwen/qwen3-30b-a3b-fp8",
154
+ "@cf/deepseek-ai/deepseek-r1-distill-qwen-32b",
155
+ "@cf/ibm-granite/granite-4.0-h-micro",
156
+ ],
157
+ },
158
+ zhipu: {
159
+ displayName: "Zhipu (Z.ai / BigModel)",
160
+ baseUrl: "https://open.bigmodel.cn/api/paas/v4",
161
+ keyless: false,
162
+ modelFilter: ["glm-4.5-flash", "glm-4.7-flash"],
163
+ },
164
+ cohere: {
165
+ displayName: "Cohere",
166
+ baseUrl: "https://api.cohere.com/compatibility/v1",
167
+ keyless: false,
78
168
  },
79
169
  ollama: {
80
170
  displayName: "Ollama (local, keyless)",
81
171
  baseUrl: "http://localhost:11434/v1",
82
172
  keyless: true,
173
+ promptUrl: true,
83
174
  },
84
175
  custom: {
85
176
  displayName: "Custom Endpoint",
86
177
  baseUrl: "",
87
178
  keyless: false,
88
- keyHint: "Bearer token or API key (leave blank if keyless)",
179
+ promptUrl: true,
89
180
  },
90
181
  };
91
182
 
@@ -266,18 +357,26 @@ export default async function (pi: ExtensionAPI) {
266
357
  const key = keys[labels.indexOf(selectedLabel)];
267
358
  const tpl = TEMPLATES[key];
268
359
 
269
- // Step 2 — base URL (prompted for ollama / custom)
360
+ // Step 2 — base URL
270
361
  let baseUrl = tpl.baseUrl;
271
- if (key === "ollama" || key === "custom") {
272
- const defaultUrl = tpl.baseUrl;
362
+ if (key === "cloudflare") {
363
+ // Ask for just the account ID and splice it into the template URL.
273
364
  const entered = await ctx.ui.input(
274
- "Base URL",
275
- key === "ollama"
276
- ? `Ollama base URL — press Enter for default (${defaultUrl}):`
277
- : "Base URL of your endpoint (e.g. https://api.example.com/v1):",
278
- defaultUrl
365
+ "Account ID",
366
+ "Your Cloudflare Account ID (find it on the Cloudflare dashboard overview page):",
367
+ ""
279
368
  );
280
- if (entered === null) { ctx.ui.notify("Login cancelled.", "info"); return; }
369
+ if (entered == null) { ctx.ui.notify("Login cancelled.", "info"); return; }
370
+ const accountId = entered.trim();
371
+ if (!accountId) { ctx.ui.notify("Account ID cannot be empty.", "error"); return; }
372
+ baseUrl = tpl.baseUrl.replace("YOUR_ACCOUNT_ID", accountId);
373
+ } else if (tpl.promptUrl) {
374
+ const defaultUrl = tpl.baseUrl;
375
+ const prompt = key === "ollama"
376
+ ? `Ollama base URL — press Enter for default (${defaultUrl}):`
377
+ : "Base URL of your endpoint (e.g. https://api.example.com/v1):";
378
+ const entered = await ctx.ui.input("Base URL", prompt, defaultUrl);
379
+ if (entered == null) { ctx.ui.notify("Login cancelled.", "info"); return; }
281
380
  baseUrl = (entered.trim() || defaultUrl).replace(/\/+$/, "");
282
381
  if (!baseUrl) { ctx.ui.notify("Base URL cannot be empty.", "error"); return; }
283
382
  }
@@ -287,10 +386,10 @@ export default async function (pi: ExtensionAPI) {
287
386
  if (!tpl.keyless) {
288
387
  const entered = await ctx.ui.input(
289
388
  "API Key",
290
- `${tpl.keyHint ?? "Your API key"} — leave blank if keyless:`,
389
+ "Your API key — leave blank if keyless:",
291
390
  ""
292
391
  );
293
- if (entered === null) { ctx.ui.notify("Login cancelled.", "info"); return; }
392
+ if (entered == null) { ctx.ui.notify("Login cancelled.", "info"); return; }
294
393
  apiKey = entered.trim() || null;
295
394
  }
296
395
 
@@ -308,6 +407,20 @@ export default async function (pi: ExtensionAPI) {
308
407
  return;
309
408
  }
310
409
 
410
+ // Apply model filter if the template defines one (keeps only free/known models).
411
+ if (tpl.modelFilter && tpl.modelFilter.length > 0) {
412
+ const filterSet = new Set(tpl.modelFilter);
413
+ const filtered = models.filter((m) => filterSet.has(m.id));
414
+ if (filtered.length > 0) {
415
+ models = filtered;
416
+ } else {
417
+ ctx.ui.notify(
418
+ `None of the expected free models were found — registering all ${models.length} model(s) returned by the provider. The provider may have renamed their models.`,
419
+ "warning"
420
+ );
421
+ }
422
+ }
423
+
311
424
  // Step 5 — save to config and register with pi
312
425
  config.providers[key] = {
313
426
  displayName: tpl.displayName,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@billjr99/pi-openai-compat",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
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",