better-custom-provider
Pi and Oh My Pi extension for managing custom providers through an interactive wizard.
Package details
Install better-custom-provider from npm and Pi will load the resources declared by the package manifest.
$ pi install npm:better-custom-provider- Package
better-custom-provider- Version
1.1.1- Published
- Aug 20, 2026
- Downloads
- 238/mo · 238/wk
- Author
- wudaoshi
- License
- MIT
- Types
- extension
- Size
- 108.6 KB
- Dependencies
- 2 dependencies · 2 peers
Pi manifest JSON
{
"extensions": [
"./index.ts"
]
}Security note
Pi packages can execute code and influence agent behavior. Review the source before installing third-party packages.
README
better-custom
A better way to add custom providers for Pi and Oh My Pi (OMP).
An interactive wizard that adds, edits, and deletes custom LLM providers in the
running host's models config — no hand-editing of models.json / models.yml
required.
Features
- Add, edit, or delete custom providers from an interactive wizard
- Supported provider styles:
- OpenAI-compatible endpoints — Chat Completions (
openai-completions) - OpenAI Responses API (
openai-responses) — the newer/responsesendpoint - Anthropic-compatible endpoints
- Gemini endpoints (
google-generative-ai) — native Gemini format; the baseUrl includes the version path (e.g.https://generativelanguage.googleapis.com/v1beta) - Ollama-compatible endpoints
- OpenAI-compatible endpoints — Chat Completions (
- Uses the running host's agent directory automatically
- Pi:
models.json - OMP:
models.yml/models.yaml
- Pi:
- API key modes:
- API key (stored verbatim in the active models config)
- none (writes a placeholder so the provider still loads)
- existing
$ENVand!commandkeys are still resolved when re-probing
- Auto-probe
/modelsfor OpenAI-compatible and Gemini endpoints - Gateway presets inside the probe step — LiteLLM, One API, New API,
OpenRouter, or generic OpenAI-compatible (vLLM, LM Studio, ...) — so the
wizard only probes the metadata sources that gateway actually exposes
(Auto-detect tries everything). Presets apply to any API flavor — gateways
like New API serve completions, responses, and anthropic formats behind one
endpoint — and One API / New API accept a bare host (
/v1is added automatically). If a probe fails you can retry with a different gateway type or switch to manual entry. The probe itself is path-adaptive: if/modelsdoesn't answer on the given base, the variant with/v1added or removed is tried automatically (some gateways, e.g. USTC's LiteLLM, hang on/v1/modelswhile serving/modelsat the root), and non-localhttp://URLs fall back tohttps:// - Auto-detects model metadata while probing:
- context window, max output tokens, vision, and reasoning support/levels
- sources: OpenAI
GET /models/{id}(incl.capabilities.reasoning.effort_options), inline/modelslist metadata (OpenRouter etc.), LiteLLM proxyGET /model/info(one call covers every model), One API / New APImetafields +supported_endpoint_types, Gemini's native/v1beta/models(inputTokenLimit), and Ollama's native/api/tags+/api/show - detected values are written into the model entries; when nothing is detected the wizard falls back to its defaults
- Multi-select model picker for probed models, showing detected metadata inline
- Unique provider names — the wizard refuses to overwrite an existing provider
- Image input enabled by default (
input: ["text", "image"]) so vision-capable models receive images instead of having them silently dropped - Reasoning enabled by default at the
xhighceiling for newly added models - Safe delete flow for whole providers or individual models
Install
From npm:
pi install npm:better-custom-provider
From GitHub:
pi install https://github.com/real-wudaoshi/better-custom
From a local checkout:
pi install /path/to/better-custom
Prefer
pi installover copying the folder into~/.pi/agent/extensions/manually:pi installrunsnpm install, so theyamlruntime dependency (used for OMP'smodels.yml) is installed automatically. A manual copy still works — the extension loads withoutyamland falls back to JSON, which is a valid YAML subset (see Configuration).
Usage
After installing, reload pi if needed, then run:
/better-custom
The wizard offers three actions:
- Add a provider
- Edit a provider
- Delete a provider
Add a provider
Guides you through:
- provider style (OpenAI Chat Completions / OpenAI Responses / Anthropic / Gemini / Ollama)
- endpoint (bare hosts work — the probe adapts ±
/v1automatically) - provider name (must be unique)
- API key method (API key or none)
- model discovery (auto-probe
/models) or manual model entry- auto-probe asks for the gateway type first (all styles except Ollama and
Gemini): Auto-detect, LiteLLM, One API, New API, OpenRouter, or generic —
controls which metadata sources are probed and whether a bare host gets
/v1. On failure you can retry with a different gateway type or switch to manual entry.
- auto-probe asks for the gateway type first (all styles except Ollama and
Gemini): Auto-detect, LiteLLM, One API, New API, OpenRouter, or generic —
controls which metadata sources are probed and whether a bare host gets
Newly added models default to input: ["text", "image"] and reasoning: true
at the xhigh ceiling. When the probe detects real metadata — context window,
max output tokens, vision, and the provider's reasoning levels (e.g. OpenAI's
effort_options) — the detected values are written instead. Tune any of this
later via Edit provider.
Edit a provider
Pick a provider, then choose:
- Re-probe for new models — query
/modelsagain and add ones not yet configured - Set context window (all models) — apply one
contextWindowto every model - API flavor — switch the provider between Chat Completions, the Responses API, Anthropic Messages, and Gemini
- Edit per model — pick a model and edit a single field:
- Reasoning ceiling (
off→max) - Vision (text+image vs text-only)
- Context window
- Max output tokens
- Headers / endpoint override (per-model
baseUrland JSONheaders) - Delete this model
- Reasoning ceiling (
- Add models manually
- Rename provider — change the provider name (key) in the active models config
- Delete provider — remove the whole provider from the active models config (also available as a standalone wizard action)
Per-model edits change one field in place, so untouched fields (cost, headers, overrides) are preserved.
Delete a provider
Lists configured providers and removes the selected one after confirmation.
How reasoning maps to pi
pi exposes seven thinking levels: off, minimal, low, medium, high, xhigh, max.
When a model has reasoning: true, pi treats minimal through high as
available. xhigh and max are opt-in and only unlocked when explicitly
mapped, and any level set to null is removed. The wizard writes a
thinkingLevelMap to unlock xhigh/max or to cap reasoning below high.
Auto-detected model metadata
When probing /models (new provider or re-probe), the wizard tries to learn
real per-model values before writing the config:
| Source | What it provides |
|---|---|
OpenAI GET /models/{id} |
context_window, max_output_tokens, capabilities.vision, capabilities.reasoning (type + effort_options) |
Inline /models list entries |
OpenRouter (context_length, reasoning, architecture.input_modalities), OpenModels/Epithre-style fields, LiteLLM max_input_tokens/max_output_tokens |
LiteLLM GET /model/info |
One call returns model_info for every model: context_window, max_tokens/max_output_tokens, supports_vision, supports_reasoning — tried at the baseUrl's origin first (/v1/model/info is 404 on LiteLLM), then under the base URL |
Site catalog GET {site}/api/models/public |
No-auth authoritative context_window (plus capability flags when published) for every published model (USTC-style sites; api. → llm. host fallback) — overrides LiteLLM-reported values |
LiteLLM GET /model_group/info |
Server-root endpoint (requires api key) with per-model_group capabilities: max_input_tokens, supports_reasoning, supports_vision — tried at the baseUrl's origin first (/v1/model_group/info is 404) |
| One API / New API | supported_endpoint_types (chat/embeddings/…) shown in the picker; fork/meta fields (context_window, max_tokens, capabilities.vision/reasoning, supports_vision/supports_reasoning) parsed from list entries and GET /models/{id} |
Ollama /api/tags + /api/show |
vision capability, model_info context length (.context_length keys) |
LiteLLM proxies are detected automatically: the wizard calls GET /model/info
first (a single request covering all models — at the server root, then under
the base URL), then GET /model_group/info at the server root when an api key
is available, then the site's no-auth public catalog
GET {site}/api/models/public (whose context_window values override
LiteLLM's — they are often more complete and accurate), and only falls back to
per-model GET /models/{id} fetches when none of those are available.
Note on One API / New API: the stock gateways return model ids only, so context
windows can't be discovered from them alone. If your deployment (or a fork)
exposes meta fields — context_window, max_tokens, capabilities.vision /
capabilities.reasoning — the wizard picks those up automatically.
Known-model fallback (local rules)
When a gateway exposes no metadata at all (stock One API / New API, bare
proxies, manually added models), the wizard classifies the model id against a
built-in rule table covering the major families and presets three fields:
contextWindow, vision, and reasoning:
- OpenAI — gpt-5.x (272K), gpt-5-mini (128K), gpt-4o (128K, vision), gpt-4.1 (1M), o1/o3/o4 (200K, reasoning)
- Anthropic — claude-4/4.5/4.6 (1M or 200K, vision, reasoning), claude-3.7-sonnet (200K, reasoning), claude-3.x (200K)
- DeepSeek — v4 (1M, reasoning), v3/chat/reasoner/r1 (128K, reasoning)
- Qwen — qwen3.x / qwen2.5 (128K, reasoning for thinking variants),
-non-thinkingvariants flagged as non-reasoning,-vlvariants vision, qwen2.5-turbo (1M), qwen-long (10M) - Kimi — kimi-k2/k2.5 (256K, reasoning), kimi-k1.5, moonshot-v1
- GLM — glm-5/4.5/z1 (reasoning), glm-4 (128K), glm-4v (vision), glm-4-long (1M)
- plus Gemini, Llama, Mistral, GPT-OSS
The rules only fill fields the gateway left unknown — real detected values
always win — and unknown ids are left unset. In the picker, values detected
from the gateway are shown untagged, while values filled by these local rules
are tagged [local rules]. The save notification tells you
exactly which models were detected, inferred, or left unset.
maxTokens is deliberately not preset: pi sends it to the API as the output
cap (max_completion_tokens / max_tokens), and values above a model's real
maximum cause an API error. If you want to cap a model's output, set it per
model via Edit provider → Edit a model → Max output tokens.
Everything is best-effort: unknown fields (404s, bare vLLM/LM Studio responses,
missing capabilities) fall back to the wizard defaults — text+image input,
reasoning on at the xhigh ceiling, no contextWindow/maxTokens set.
When the probe finds the provider's reasoning levels (e.g. OpenAI
effort_options: ["none", "low", "medium", "high"]), the wizard writes a
thinkingLevelMap matching those levels exactly: supported levels map to the
provider's own strings, unsupported ones are null, and the reasoning ceiling
is set to the highest supported level. Models whose thinking cannot be disabled
(reasoning.type: "minimal") get off: null so pi never sends a no-thinking
request.
Configuration
The extension uses the host-provided agent directory instead of hard-coding
~/.pi/agent. Existing models.yml, models.yaml, or models.json files are
kept in their current format. A fresh OMP config is created as models.yml;
normal Pi continues to use models.json.
Saving YAML rewrites its formatting and does not preserve comments.
When the yaml package is missing
The yaml dependency is only required for OMP's models.yml. pi install
installs it automatically; if the folder was copied into
~/.pi/agent/extensions/ manually, the extension still starts:
- JSON configs (
models.json, or JSON-formattedmodels.yml) work fully. - Genuine YAML
models.ymlshows a clear error telling you to runnpm installin the extension folder or reinstall withpi install— it never guesses or corrupts the file. - New OMP configs are written as JSON, which every YAML parser (including OMP and pi) reads fine.
Development
The extension is plain TypeScript loaded directly by pi — no build step. Model
probing (/models + metadata enrichment + known-model rules) lives in the
separate model-probe package.
npm run check # syntax-check every source file with node --check
Project layout
index.ts— extension entry point (registers the/better-customcommand)src/types.ts— shared types and constantssrc/config.ts— models config discovery + JSON/YAML load/savesrc/url.ts— endpoint normalization and other small helperssrc/api-key.ts— API key resolve/serialize helperssrc/presets.ts— gateway presets (LiteLLM, One API, New API, ...)src/model-entry.ts— build/read/mutate model entries and provider configssrc/ui/select.ts— searchable single/multi-select pickerssrc/ui/prompts.ts— wizard input promptssrc/flows/shared.ts— shared config-mutation helpers for the flowssrc/flows/add.ts— add-provider flowsrc/flows/edit.ts— edit-provider flow (incl. re-probe and per-model edits)src/flows/delete.ts— delete-provider flow
License
MIT