better-custom-provider

Pi and Oh My Pi extension for managing custom providers through an interactive wizard.

Packages

Package details

extension

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.4.0
Published
Aug 24, 2026
Downloads
1,225/mo · 1,225/wk
Author
wudaoshi
License
MIT
Types
extension
Size
130.1 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 /responses endpoint
    • 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
  • Uses the running host's agent directory automatically
    • Pi: models.json
    • OMP: models.yml / models.yaml
  • API key modes:
    • API key (stored verbatim in the active models config)
    • none (writes a placeholder so the provider still loads)
    • existing $ENV and !command keys are still resolved when re-probing
  • Auto-probe /models for OpenAI-compatible and Gemini endpoints
  • Provider catalog from models.dev — pick a known API site (OpenRouter, DeepSeek, Groq, xAI, ...) and skip probing entirely: the model list and metadata come straight from the catalog (via the official SDK, with a jsDelivr freshness layer and a bundled offline snapshot)
  • Custom endpoints are probed with a single auto-detect profile — every known metadata source is tried automatically. The probe is path-adaptive: if /models doesn't answer on the given base, the variant with /v1 added or removed is tried automatically (some gateways, e.g. USTC's LiteLLM, hang on /v1/models while serving /models at the root), and non-local http:// URLs fall back to https://. On failure you can retry or switch to manual entry.
  • Auto-detects model metadata while probing:
    • context window, max output tokens, image/video input, and reasoning support/levels
    • sources: OpenAI GET /models/{id} (incl. capabilities.reasoning.effort_options), inline /models list metadata (OpenRouter etc.), LiteLLM proxy GET /model/info (one call covers every model), One API / New API meta fields + supported_endpoint_types, Gemini's native /v1beta/models (inputTokenLimit), and Ollama's native /api/tags + /api/show
    • every field is resolved in four tiers — detected from the gateway, then the models.dev catalog (tagged [models.dev], exact per-model entries), then the built-in known-model rules (tagged [local rules]), then the defaults (image: false, video: false, reasoning: true); detected values are written into the model entries
  • Multi-select model picker for probed models, showing metadata inline — only values that differ from the defaults are shown (e.g. a model probed as text-only gets no tag)
  • Unique provider names — the wizard refuses to overwrite an existing provider
  • Developer-role support is probed automatically on add (one tiny chat completion): endpoints that reject the OpenAI developer role (e.g. Kimi's subscription endpoint) get compat.supportsDeveloperRole: false, so pi keeps sending system instead of failing with a 400. When the probe is inconclusive it defaults to off, which every endpoint accepts
  • Image input follows the probe (default off): image-capable models get input: ["text", "image"], everything else stays text-only. Video input is tracked too and shown as a picker tag, but pi's model config has no video slot, so it is display-only
  • Reasoning enabled by default at the xhigh ceiling 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 install over copying the folder into ~/.pi/agent/extensions/ manually: pi install runs npm install, so the yaml runtime dependency (used for OMP's models.yml) is installed automatically. A manual copy still works — the extension loads without yaml and falls back to JSON, which is a valid YAML subset (see Configuration).

Usage

After installing, reload pi if needed, then run:

/custom-provider

The wizard offers three actions:

  1. Add a provider
  2. Edit a provider
  3. Delete a provider

Add a provider

First choice: From models.dev catalog or Custom endpoint.

From the models.dev catalog (via the official SDK):

  • pick a known API provider (OpenRouter, DeepSeek, Groq, xAI, ...) — its base URL, env var names, and full model list come from the catalog
  • provider name (defaults to the catalog id, must be unique)
  • API key method (API key or none)
  • multi-select model picker with catalog metadata inline — no probing at all; when models.dev is unreachable, a jsDelivr-served fresh snapshot and the SDK's bundled snapshot keep this path working

Custom endpoint:

  • provider style (OpenAI Chat Completions / OpenAI Responses / Anthropic / Gemini / Ollama)
  • endpoint URL
  • provider name (must be unique)
  • API key method (API key or none)
  • model discovery: auto-detect (probes /models plus every known metadata source) or manual entry. On failure you can retry or switch to manual entry. Manual entry walks one model at a time: type the id, land in its metadata menu (reasoning / image input / context window, each pre-filled from the resolved values), confirm, then enter the next id — blank or esc finishes.

Newly added models resolve every field in four tiers: values detected from the gateway win, then the models.dev catalog (exact entries), then the built-in known-model rules, then the defaults — text-only input and reasoning: true at the xhigh ceiling. Tune any of this later via Edit provider.

Edit a provider

Pick a provider, then choose:

  • Re-probe for models — query /models again and reconcile everything in one tri-state list: new models, already-configured models, and stored models the endpoint no longer lists (flagged unsupported). Space cycles each row: [x] keep/add with the latest metadata, [-] keep but don't touch the metadata (only offered on rows with changes), [ ] remove/skip. Metadata diffs show inline — context 128000 -> 1000000 for the context window, image [+] / reasoning [-] for booleans — and only authoritative values (gateway-detected or models.dev) are offered; local-rule guesses never rewrite stored entries
  • API flavor — switch the provider between Chat Completions, the Responses API, Anthropic Messages, and Gemini
  • Endpoint — change the provider's baseUrl (normalized the same way as at add time)
  • Edit per model — pick a model and edit a single field:
    • Reasoning ceiling (offmax)
    • Image input (text+image vs text-only)
    • Context window
    • Max output tokens
    • Headers / endpoint override (per-model baseUrl and JSON headers)
    • Delete this model
  • Delete models — multi-select several models and remove them at once
  • Add models manually — one model at a time: type the id, adjust its metadata in the per-model menu (reasoning, image input, context window, pre-filled from the resolved values), confirm, then enter the next id; blank or esc finishes
  • 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 (mapped to image input), 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 these fields: contextWindow, image, video, and reasoning:

  • OpenAI — gpt-5.x (272K), gpt-5-mini (128K), gpt-4o (128K, image), gpt-4.1 (1M), o1/o3/o4 (200K, reasoning)
  • Anthropic — claude-4/4.5/4.6 (1M or 200K, image, 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-thinking variants flagged as non-reasoning, -vl variants image, 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 (image), 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 known-model rules, then the defaults — text-only 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-formatted models.yml) work fully.
  • Genuine YAML models.yml shows a clear error telling you to run npm install in the extension folder or reinstall with pi 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 /custom-provider command)
  • src/types.ts — shared types and constants
  • src/config.ts — models config discovery + JSON/YAML load/save
  • src/url.ts — endpoint normalization and other small helpers
  • src/api-key.ts — API key resolve/serialize helpers
  • src/presets.ts — the probe profile (which metadata sources auto-detect tries)
  • src/model-entry.ts — build/read/mutate model entries and provider configs
  • src/ui/select.ts — searchable single/multi-select pickers
  • src/ui/prompts.ts — wizard input prompts
  • src/flows/shared.ts — shared config-mutation helpers for the flows
  • src/flows/add.ts — add-provider flow
  • src/flows/edit.ts — edit-provider flow (incl. re-probe and per-model edits)
  • src/flows/delete.ts — delete-provider flow

License

MIT