@billjr99/pi-openai-compat 1.1.32 → 1.1.34

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.
package/README.md CHANGED
@@ -72,6 +72,7 @@ If pi is already running when you install, type `/reload` first.
72
72
  | **ModelScope** | `https://api-inference.modelscope.cn/v1` | `ms-...` SDK token from modelscope.cn/my/myaccesstoken |
73
73
  | **Aion Labs** | `https://api.aionlabs.ai/v1` | API key from aionlabs.ai/app/api-keys (aionlabs.ai/docs) |
74
74
  | **Agnes AI** | `https://apihub.agnes-ai.com/v1` | `sk-...` key from the Agnes console at platform.agnes-ai.com (wiki.agnes-ai.com) |
75
+ | **Xiaomi MiMo** | `https://api.xiaomimimo.com/v1` | API key from the Xiaomi MiMo console at platform.xiaomimimo.com → API Keys (mimo.mi.com/docs) |
75
76
  | **Ollama (local)** | `http://localhost:11434/v1` (editable) | Optional bearer token; leave blank for a default local install |
76
77
  | **Ollama Cloud** | `https://ollama.com/v1` | Ollama Cloud API key from ollama.com |
77
78
  | **llmproxy** | `http://localhost:8080/v1` (editable) | Keyless by default; bearer token if your instance requires one |
@@ -88,7 +89,12 @@ If pi is already running when you install, type `/reload` first.
88
89
  > 2. **Built-in fallback list** when no working catalog endpoint exists. Used
89
90
  > when discovery fails or when the upstream simply has no `/models` at all
90
91
  > (e.g. **Hugging Face** returns HTML, **Cloudflare AI Gateway** has no
91
- > catalog endpoint).
92
+ > catalog endpoint, **Unbiased AI** has no such route).
93
+ >
94
+ > A fallback list stands in for a missing endpoint, never for a rejected
95
+ > credential: a catalog fetch that fails with 401 or 403 aborts the login
96
+ > instead, so a mistyped key cannot be saved as a provider that looks healthy
97
+ > in `/model` and then fails on every completion.
92
98
  >
93
99
  > | Provider | Default `/models` symptom | Handling |
94
100
  > |---|---|---|
@@ -96,6 +102,7 @@ If pi is already running when you install, type `/reload` first.
96
102
  > | **Cloudflare Workers AI** | HTTP 405 (no `GET /v1/models`; real catalog at `/ai/models/search`, id field is `name`, mixed task types) | Live discovery — `modelsUrl: …/ai/models/search`, `modelsIdField: name`, `modelsKeepTask: "Text Generation"` |
97
103
  > | **Cloudflare AI Gateway** | HTTP 401 (token missing `AI Gateway: Run`) or HTTP 400 *"Please configure AI Gateway"* (gateway slug doesn't exist, or the upstream isn't configured on it) | Built-in fallback list |
98
104
  > | **Hugging Face** | Returns HTML rather than JSON | Built-in fallback list |
105
+ > | **Unbiased AI** | HTTP 404 `unknown_url` with a valid key (no `/v1/models` at all; an *invalid* key returns 401 on every path, including ones that do not exist, so a 401 here proves nothing) | Built-in fallback list |
99
106
  >
100
107
  > **Auto-heal for older configs:** if you logged in before live discovery
101
108
  > existed, your saved provider is missing these `modelsUrl`/`modelsIdField`/
package/add-provider.sh CHANGED
@@ -96,6 +96,12 @@ const BUILTIN = {
96
96
  keyless: false,
97
97
  keyHint: "platform.agnes-ai.com → API Key management (keys look like sk-...; docs at wiki.agnes-ai.com)",
98
98
  },
99
+ xiaomi_mimo: {
100
+ displayName: "Xiaomi MiMo",
101
+ baseUrl: "https://api.xiaomimimo.com/v1",
102
+ keyless: false,
103
+ keyHint: "platform.xiaomimimo.com → API Keys (docs at mimo.mi.com/docs)",
104
+ },
99
105
  };
100
106
 
101
107
  // README "Auth" column text for the built-in providers, used only by patchreadme.
@@ -110,6 +116,7 @@ const BUILTIN_AUTH = {
110
116
  modelscope: "`ms-...` SDK token from modelscope.cn/my/myaccesstoken",
111
117
  aion_labs: "API key from aionlabs.ai/app/api-keys (aionlabs.ai/docs)",
112
118
  agnes_ai: "`sk-...` key from the Agnes console at platform.agnes-ai.com (wiki.agnes-ai.com)",
119
+ xiaomi_mimo: "API key from the Xiaomi MiMo console at platform.xiaomimimo.com → API Keys (mimo.mi.com/docs)",
113
120
  };
114
121
 
115
122
  /**
@@ -465,6 +472,17 @@ fi
465
472
  MODEL_COUNT=0
466
473
  [ -n "$MODELS_JSON" ] && MODEL_COUNT="$(printf '%s' "$MODELS_JSON" | node -e 'let s="";process.stdin.on("data",d=>s+=d).on("end",()=>process.stdout.write(String(JSON.parse(s).length)))')"
467
474
 
475
+ # Mirrors isAuthFailure() in index.ts: a fallback list stands in for an endpoint
476
+ # that is not there, never for a credential that was refused. Falling back on a
477
+ # 401/403 would write a provider that looks healthy in /model and fails on every
478
+ # completion, which is worse than saying the key was rejected.
479
+ if [ "$MODEL_COUNT" = "0" ] && { [ "$HTTP_CODE" = "401" ] || [ "$HTTP_CODE" = "403" ]; }; then
480
+ echo " The provider rejected the API key (HTTP $HTTP_CODE)."
481
+ echo " Not using the built-in model list: that would save a provider whose"
482
+ echo " every request fails. Re-run with a valid key."
483
+ exit 1
484
+ fi
485
+
468
486
  if [ "$MODEL_COUNT" = "0" ]; then
469
487
  if [ -n "$FALLBACK_MODELS" ]; then
470
488
  echo " Using this template's built-in model list instead."
package/index.ts CHANGED
@@ -413,9 +413,21 @@ export const TEMPLATES: Record<string, {
413
413
  // answers both /v1/chat/completions and Anthropic's /v1/messages and takes
414
414
  // either Authorization: Bearer or x-api-key; pi is registered with the
415
415
  // OpenAI pair, which is what registerProvider and fetchModels already send.
416
- // GET /v1/models is registered and key-gated (a well-formed sk_ key gets
417
- // "Invalid API key" rather than a format complaint), so no modelsUrl or
418
- // fallbackModels override is needed.
416
+ //
417
+ // GET /v1/models does NOT exist: with a valid key it returns 404
418
+ // "unknown_url". An earlier note here claimed the endpoint was registered
419
+ // and key-gated, reasoning from a well-formed but invalid key getting
420
+ // "Invalid API key" rather than a 404. That inference was wrong. The
421
+ // gateway authenticates before it routes, so a path that certainly does
422
+ // not exist returns the same 401: GET /v1/definitely-not-a-real-endpoint
423
+ // answers "Invalid API key" with a bad key and "Missing API key" without
424
+ // one, exactly as /v1/models does. A 401 here says nothing about routing.
425
+ //
426
+ // Discovery therefore finds nothing, which is what fallbackModels is for.
427
+ // It is safe here because the catch that consumes it now refuses to fall
428
+ // back on a 401/403 (see isAuthFailure), so a mistyped key still fails the
429
+ // login instead of being saved as a provider that fails every completion.
430
+ fallbackModels: ["pareto"],
419
431
  keyHint: "platform.unbiased.ai (signup is reviewed by hand; keys look like sk_...)",
420
432
  },
421
433
  kilo: {
@@ -464,6 +476,19 @@ export const TEMPLATES: Record<string, {
464
476
  // without a key, and a filter that misses would register nothing.
465
477
  keyHint: "platform.agnes-ai.com → API Key management (keys look like sk-...; docs at wiki.agnes-ai.com)",
466
478
  },
479
+ xiaomi_mimo: {
480
+ displayName: "Xiaomi MiMo",
481
+ baseUrl: "https://api.xiaomimimo.com/v1",
482
+ keyless: false,
483
+ // Xiaomi MiMo API Open Platform. GET /v1/models is key-gated (it answers
484
+ // 401 "invalid_key" JSON without a key, where an unknown /v1 path answers
485
+ // a 404 HTML page), which is exactly what fetchModels already sends, so no
486
+ // modelsUrl or fallbackModels override is needed. The catalog may also
487
+ // carry non-chat ids (e.g. mimo-v2-tts); no modelFilter is set because the
488
+ // live id list could not be confirmed without a key, and a filter that
489
+ // misses would register nothing.
490
+ keyHint: "platform.xiaomimimo.com → API Keys (docs at mimo.mi.com/docs)",
491
+ },
467
492
  custom: {
468
493
  displayName: "Custom Endpoint",
469
494
  baseUrl: "",
@@ -656,6 +681,24 @@ export function isLocalUrl(url: string): boolean {
656
681
  /** Cap on how much of an upstream error body is surfaced to the user. */
657
682
  export const MAX_ERROR_BODY = 500;
658
683
 
684
+ /** An Error from fetchModels carrying the upstream HTTP status, when it had one. */
685
+ export type CatalogError = Error & { status?: number };
686
+
687
+ /**
688
+ * True when a catalog fetch failed because the credential was rejected.
689
+ *
690
+ * This is the distinction that makes fallbackModels safe to use. A fallback
691
+ * list exists for endpoints that publish no catalog, so it should stand in for
692
+ * a missing endpoint and never for a bad key: falling back on a 401 would save
693
+ * a provider that looks healthy in /model and fails on every completion, which
694
+ * is worse than refusing the login. A network error (no status) is treated as
695
+ * non-auth, so an offline or unreachable host still gets the fallback.
696
+ */
697
+ export function isAuthFailure(err: unknown): boolean {
698
+ const status = (err as CatalogError | null)?.status;
699
+ return status === 401 || status === 403;
700
+ }
701
+
659
702
  /** Optional per-provider overrides controlling how /models is fetched. */
660
703
  interface FetchOverrides {
661
704
  /** Full URL to fetch instead of `<baseUrl>/models`. */
@@ -692,7 +735,12 @@ export async function fetchModels(
692
735
  const snippet = body.length > MAX_ERROR_BODY
693
736
  ? `${body.slice(0, MAX_ERROR_BODY)}… (truncated)`
694
737
  : body;
695
- throw new Error(`HTTP ${resp.status} from ${url}: ${snippet}`);
738
+ // Carry the status on the error. Callers need to tell "this endpoint is
739
+ // not there" from "your key is wrong", and parsing it back out of the
740
+ // message would break the moment the wording changes.
741
+ const err = new Error(`HTTP ${resp.status} from ${url}: ${snippet}`) as CatalogError;
742
+ err.status = resp.status;
743
+ throw err;
696
744
  }
697
745
 
698
746
  // Normalize the various shapes /models can return:
@@ -1032,7 +1080,7 @@ export default async function (pi: ExtensionAPI) {
1032
1080
  keepTask: tpl.modelsKeepTask,
1033
1081
  });
1034
1082
  } catch (err) {
1035
- if (tpl.fallbackModels && tpl.fallbackModels.length > 0) {
1083
+ if (tpl.fallbackModels && tpl.fallbackModels.length > 0 && !isAuthFailure(err)) {
1036
1084
  ctx.ui.notify(
1037
1085
  `Could not fetch model list from ${tpl.displayName} (${err}).\nUsing built-in model list instead.`,
1038
1086
  "warning"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@billjr99/pi-openai-compat",
3
- "version": "1.1.32",
3
+ "version": "1.1.34",
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",