@billjr99/pi-openai-compat 1.0.0 → 1.0.1

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 +137 -10
  2. package/package.json +1 -1
package/index.ts CHANGED
@@ -57,6 +57,10 @@ const TEMPLATES: Record<string, {
57
57
  baseUrl: string;
58
58
  keyless: boolean;
59
59
  keyHint?: string;
60
+ /** If set, only models whose id appears in this list are kept after fetching. */
61
+ modelFilter?: string[];
62
+ /** If true, prompt the user to confirm/edit the base URL (like ollama/custom). */
63
+ promptUrl?: boolean;
60
64
  }> = {
61
65
  openrouter: {
62
66
  displayName: "OpenRouter",
@@ -76,15 +80,116 @@ const TEMPLATES: Record<string, {
76
80
  keyless: false,
77
81
  keyHint: "Your Nous Portal API key",
78
82
  },
83
+ google: {
84
+ displayName: "Google Gemini",
85
+ baseUrl: "https://generativelanguage.googleapis.com/v1beta/openai",
86
+ keyless: false,
87
+ keyHint: "Free key from aistudio.google.com (free Google account, no credit card)",
88
+ modelFilter: [
89
+ "gemini-2.5-flash",
90
+ "gemini-2.5-flash-lite",
91
+ "gemini-3-flash-preview",
92
+ "gemini-3.1-flash-lite-preview",
93
+ "gemini-3.1-pro-preview",
94
+ ],
95
+ },
96
+ cerebras: {
97
+ displayName: "Cerebras",
98
+ baseUrl: "https://api.cerebras.ai/v1",
99
+ keyless: false,
100
+ keyHint: "Free account at cerebras.ai (no credit card)",
101
+ modelFilter: ["qwen3-235b"],
102
+ },
103
+ github_models: {
104
+ displayName: "GitHub Models",
105
+ baseUrl: "https://models.inference.ai.azure.com",
106
+ keyless: false,
107
+ keyHint: "GitHub Personal Access Token (any free GitHub account, no special scopes needed)",
108
+ modelFilter: ["gpt-4o", "openai/gpt-4.1"],
109
+ },
110
+ sambanova: {
111
+ displayName: "SambaNova",
112
+ baseUrl: "https://api.sambanova.ai/v1",
113
+ keyless: false,
114
+ keyHint: "Free account at cloud.sambanova.ai (no credit card)",
115
+ modelFilter: [
116
+ "Meta-Llama-3.3-70B-Instruct",
117
+ "DeepSeek-V3.1",
118
+ "DeepSeek-V3.2",
119
+ "gpt-oss-120b",
120
+ "Llama-4-Maverick-17B-128E-Instruct",
121
+ "gemma-3-12b-it",
122
+ ],
123
+ },
124
+ mistral: {
125
+ displayName: "Mistral",
126
+ baseUrl: "https://api.mistral.ai/v1",
127
+ keyless: false,
128
+ keyHint: 'Free "Experiment" tier at console.mistral.ai (no credit card)',
129
+ modelFilter: [
130
+ "mistral-large-latest",
131
+ "mistral-medium-latest",
132
+ "magistral-medium-latest",
133
+ "codestral-latest",
134
+ "devstral-latest",
135
+ ],
136
+ },
137
+ groq: {
138
+ displayName: "Groq",
139
+ baseUrl: "https://api.groq.com/openai/v1",
140
+ keyless: false,
141
+ keyHint: "Free account at console.groq.com (no credit card)",
142
+ modelFilter: [
143
+ "llama-3.3-70b-versatile",
144
+ "meta-llama/llama-4-scout-17b-16e-instruct",
145
+ "openai/gpt-oss-120b",
146
+ "openai/gpt-oss-20b",
147
+ "qwen/qwen3-32b",
148
+ "llama-3.1-8b-instant",
149
+ ],
150
+ },
151
+ cloudflare: {
152
+ displayName: "Cloudflare Workers AI",
153
+ baseUrl: "https://api.cloudflare.com/client/v4/accounts/YOUR_ACCOUNT_ID/ai/v1",
154
+ keyless: false,
155
+ promptUrl: true,
156
+ keyHint: 'Cloudflare API Token with "Workers AI Read" permission (free account at cloudflare.com)',
157
+ modelFilter: [
158
+ "@cf/meta/llama-3.3-70b-instruct-fp8-fast",
159
+ "@cf/meta/llama-4-scout-17b-16e-instruct",
160
+ "@cf/openai/gpt-oss-120b",
161
+ "@cf/zai-org/glm-4.7-flash",
162
+ "@cf/moonshotai/kimi-k2.5",
163
+ "@cf/moonshotai/kimi-k2.6",
164
+ "@cf/qwen/qwen3-30b-a3b-fp8",
165
+ "@cf/deepseek-ai/deepseek-r1-distill-qwen-32b",
166
+ "@cf/ibm-granite/granite-4.0-h-micro",
167
+ ],
168
+ },
169
+ zhipu: {
170
+ displayName: "Zhipu (Z.ai / BigModel)",
171
+ baseUrl: "https://open.bigmodel.cn/api/paas/v4",
172
+ keyless: false,
173
+ keyHint: "Free account at open.bigmodel.cn (no credit card)",
174
+ modelFilter: ["glm-4.5-flash", "glm-4.7-flash"],
175
+ },
176
+ cohere: {
177
+ displayName: "Cohere",
178
+ baseUrl: "https://api.cohere.com/compatibility/v1",
179
+ keyless: false,
180
+ keyHint: "Your Cohere API key",
181
+ },
79
182
  ollama: {
80
183
  displayName: "Ollama (local, keyless)",
81
184
  baseUrl: "http://localhost:11434/v1",
82
185
  keyless: true,
186
+ promptUrl: true,
83
187
  },
84
188
  custom: {
85
189
  displayName: "Custom Endpoint",
86
190
  baseUrl: "",
87
191
  keyless: false,
192
+ promptUrl: true,
88
193
  keyHint: "Bearer token or API key (leave blank if keyless)",
89
194
  },
90
195
  };
@@ -266,18 +371,26 @@ export default async function (pi: ExtensionAPI) {
266
371
  const key = keys[labels.indexOf(selectedLabel)];
267
372
  const tpl = TEMPLATES[key];
268
373
 
269
- // Step 2 — base URL (prompted for ollama / custom)
374
+ // Step 2 — base URL
270
375
  let baseUrl = tpl.baseUrl;
271
- if (key === "ollama" || key === "custom") {
272
- const defaultUrl = tpl.baseUrl;
376
+ if (key === "cloudflare") {
377
+ // Ask for just the account ID and splice it into the template URL.
273
378
  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
379
+ "Account ID",
380
+ "Your Cloudflare Account ID (find it on the Cloudflare dashboard overview page):",
381
+ ""
279
382
  );
280
- if (entered === null) { ctx.ui.notify("Login cancelled.", "info"); return; }
383
+ if (entered == null) { ctx.ui.notify("Login cancelled.", "info"); return; }
384
+ const accountId = entered.trim();
385
+ if (!accountId) { ctx.ui.notify("Account ID cannot be empty.", "error"); return; }
386
+ baseUrl = tpl.baseUrl.replace("YOUR_ACCOUNT_ID", accountId);
387
+ } else if (tpl.promptUrl) {
388
+ const defaultUrl = tpl.baseUrl;
389
+ const prompt = key === "ollama"
390
+ ? `Ollama base URL — press Enter for default (${defaultUrl}):`
391
+ : "Base URL of your endpoint (e.g. https://api.example.com/v1):";
392
+ const entered = await ctx.ui.input("Base URL", prompt, defaultUrl);
393
+ if (entered == null) { ctx.ui.notify("Login cancelled.", "info"); return; }
281
394
  baseUrl = (entered.trim() || defaultUrl).replace(/\/+$/, "");
282
395
  if (!baseUrl) { ctx.ui.notify("Base URL cannot be empty.", "error"); return; }
283
396
  }
@@ -290,7 +403,7 @@ export default async function (pi: ExtensionAPI) {
290
403
  `${tpl.keyHint ?? "Your API key"} — leave blank if keyless:`,
291
404
  ""
292
405
  );
293
- if (entered === null) { ctx.ui.notify("Login cancelled.", "info"); return; }
406
+ if (entered == null) { ctx.ui.notify("Login cancelled.", "info"); return; }
294
407
  apiKey = entered.trim() || null;
295
408
  }
296
409
 
@@ -308,6 +421,20 @@ export default async function (pi: ExtensionAPI) {
308
421
  return;
309
422
  }
310
423
 
424
+ // Apply model filter if the template defines one (keeps only free/known models).
425
+ if (tpl.modelFilter && tpl.modelFilter.length > 0) {
426
+ const filterSet = new Set(tpl.modelFilter);
427
+ const filtered = models.filter((m) => filterSet.has(m.id));
428
+ if (filtered.length > 0) {
429
+ models = filtered;
430
+ } else {
431
+ ctx.ui.notify(
432
+ `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.`,
433
+ "warning"
434
+ );
435
+ }
436
+ }
437
+
311
438
  // Step 5 — save to config and register with pi
312
439
  config.providers[key] = {
313
440
  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.1",
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",