@billjr99/pi-openai-compat 1.1.24 → 1.1.26

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 +3 -0
  2. package/index.ts +22 -14
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -213,6 +213,9 @@ provider's `/models` catalog does not report them:
213
213
  | `maxTokens` | number | `4096` | Maximum output tokens per response. |
214
214
  | `reasoning` | boolean | `false` | Enables pi's thinking mode for the model. |
215
215
  | `input` | `["text"]` or `["text","image"]` | `["text"]` | Modalities pi may send; `image` lets pi attach image blocks. Any other value is ignored. |
216
+ | `thinkingLevelMap` | object | omitted | pi thinking-level remap, passed through to the registered model. Keys are pi thinking levels (`off`, `minimal`, `low`, `medium`, `high`, `xhigh`, `max`); string values are sent to the provider, `null` hides an unsupported level. See [pi's docs](https://pi.dev/docs/latest/models#thinking-level-map). |
217
+ | `samplingParams` | object | omitted | Free-form object merged verbatim into every request body for the model (e.g. `top_k`, `min_p`, `presence_penalty`). Only OpenAI-compatible APIs apply it. |
218
+ | `compat` | object | omitted | OpenAI compatibility flags for the model (`thinkingFormat`, `chatTemplateKwargs`, `maxTokensField`, `supportsDeveloperRole`, …). See [pi's docs](https://pi.dev/docs/latest/models#openai-compatibility). |
216
219
 
217
220
  Most catalogs report none of these beyond the ID, so aggregators and proxies
218
221
  (CLIProxyAPI, for example) register every model as a 128K, text-only,
package/index.ts CHANGED
@@ -37,6 +37,11 @@ interface CachedModel {
37
37
  // omits them (see README "Config file"); /compat-refresh preserves them.
38
38
  reasoning?: boolean;
39
39
  input?: ModelInput[];
40
+ // Optional provider-model passthrough (mirrors pi's ProviderModelConfig):
41
+ // thinking-level remap, verbatim sampling params, and OpenAI compat flags.
42
+ thinkingLevelMap?: Record<string, string | null>;
43
+ samplingParams?: Record<string, unknown>;
44
+ compat?: Record<string, unknown>;
40
45
  }
41
46
 
42
47
  interface ProviderConfig {
@@ -602,6 +607,9 @@ function buildProviderModels(models: CachedModel[]) {
602
607
  cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
603
608
  contextWindow: m.contextWindow ?? 128_000,
604
609
  maxTokens: m.maxTokens ?? 4_096,
610
+ ...(m.thinkingLevelMap ? { thinkingLevelMap: m.thinkingLevelMap } : {}),
611
+ ...(m.samplingParams ? { samplingParams: m.samplingParams } : {}),
612
+ ...(m.compat ? { compat: m.compat } : {}),
605
613
  };
606
614
  });
607
615
  }
@@ -623,6 +631,9 @@ function mergeModelMetadata(previous: CachedModel[], fetched: CachedModel[]): Ca
623
631
  maxTokens: m.maxTokens ?? old.maxTokens,
624
632
  reasoning: m.reasoning ?? old.reasoning,
625
633
  input: m.input ?? old.input,
634
+ thinkingLevelMap: m.thinkingLevelMap ?? old.thinkingLevelMap,
635
+ samplingParams: m.samplingParams ?? old.samplingParams,
636
+ compat: m.compat ?? old.compat,
626
637
  };
627
638
  });
628
639
  }
@@ -760,8 +771,7 @@ export default async function (pi: ExtensionAPI) {
760
771
  let modelsUrl: string | undefined = tpl.modelsUrl;
761
772
  if (key === "cloudflare_workers") {
762
773
  const entered = await ctx.ui.input(
763
- "Account ID",
764
- "Your Cloudflare Account ID (find it on the Cloudflare dashboard overview page):"
774
+ "Account ID: your Cloudflare Account ID (find it on the Cloudflare dashboard overview page)"
765
775
  );
766
776
  if (entered == null) { ctx.ui.notify("Login cancelled.", "info"); return; }
767
777
  const accountId = entered.trim();
@@ -770,24 +780,21 @@ export default async function (pi: ExtensionAPI) {
770
780
  if (modelsUrl) modelsUrl = modelsUrl.replace("YOUR_ACCOUNT_ID", accountId);
771
781
  } else if (key === "cloudflare_ai_gateway") {
772
782
  const accountIdInput = await ctx.ui.input(
773
- "Account ID",
774
- "Your Cloudflare Account ID (find it on the Cloudflare dashboard overview page):"
783
+ "Account ID: your Cloudflare Account ID (find it on the Cloudflare dashboard overview page)"
775
784
  );
776
785
  if (accountIdInput == null) { ctx.ui.notify("Login cancelled.", "info"); return; }
777
786
  const accountId = accountIdInput.trim();
778
787
  if (!accountId) { ctx.ui.notify("Account ID cannot be empty.", "error"); return; }
779
788
 
780
789
  const gatewayInput = await ctx.ui.input(
781
- "Gateway Name",
782
- "Your AI Gateway name/slug (find it under AI → AI Gateway in the Cloudflare dashboard):"
790
+ "Gateway Name: your AI Gateway name/slug (find it under AI → AI Gateway in the Cloudflare dashboard)"
783
791
  );
784
792
  if (gatewayInput == null) { ctx.ui.notify("Login cancelled.", "info"); return; }
785
793
  const gatewaySlug = gatewayInput.trim();
786
794
  if (!gatewaySlug) { ctx.ui.notify("Gateway name cannot be empty.", "error"); return; }
787
795
 
788
796
  const providerInput = await ctx.ui.input(
789
- "Provider",
790
- "Upstream provider slug (e.g. openai, workers-ai, anthropic — must match your gateway config):"
797
+ "Provider: upstream provider slug (e.g. openai, workers-ai, anthropic — must match your gateway config; press Enter for openai)"
791
798
  );
792
799
  if (providerInput == null) { ctx.ui.notify("Login cancelled.", "info"); return; }
793
800
  const provider = providerInput.trim() || "openai";
@@ -804,10 +811,11 @@ export default async function (pi: ExtensionAPI) {
804
811
  }
805
812
  } else if (tpl.promptUrl) {
806
813
  const defaultUrl = tpl.baseUrl;
814
+ // pi renders only the dialog title, so the guidance goes there.
807
815
  const prompt = isLocalUrl(defaultUrl)
808
- ? `Base URL press Enter for default (${defaultUrl}):`
809
- : "Base URL of your endpoint (e.g. https://api.example.com/v1):";
810
- const entered = await ctx.ui.input("Base URL", prompt);
816
+ ? `Base URL: press Enter for default (${defaultUrl})`
817
+ : "Base URL: your endpoint (e.g. https://api.example.com/v1)";
818
+ const entered = await ctx.ui.input(prompt);
811
819
  if (entered == null) { ctx.ui.notify("Login cancelled.", "info"); return; }
812
820
  baseUrl = (entered.trim() || defaultUrl).replace(/\/+$/, "");
813
821
  if (!baseUrl) { ctx.ui.notify("Base URL cannot be empty.", "error"); return; }
@@ -817,9 +825,9 @@ export default async function (pi: ExtensionAPI) {
817
825
  let apiKey: string | null = null;
818
826
  if (!tpl.keyless && !isLocalUrl(baseUrl)) {
819
827
  const keyPrompt = tpl.keyHint
820
- ? `Your API key (required) — get it at ${tpl.keyHint}:`
821
- : "Your API key leave blank if keyless:";
822
- const entered = await ctx.ui.input("API Key", keyPrompt);
828
+ ? `API Key: required — get it at ${tpl.keyHint}`
829
+ : "API Key: leave blank if keyless";
830
+ const entered = await ctx.ui.input(keyPrompt);
823
831
  if (entered == null) { ctx.ui.notify("Login cancelled.", "info"); return; }
824
832
  apiKey = entered.trim() || null;
825
833
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@billjr99/pi-openai-compat",
3
- "version": "1.1.24",
3
+ "version": "1.1.26",
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",