- JavaScript 59.5%
- TypeScript 40.5%
- Introduce `discovery.type` (`openai` default, `auto`, `cliproxyapi`) with auto-detection of CLIProxyAPI servers - Supplement rosters with sanitized `supported_reasoning_levels` as `reasoning_options` without adding models or importing other metadata - Keep discovery modes cache-isolated while preserving legacy OpenAI cache keys - Add example config, README docs, and unit/RPC tests |
||
|---|---|---|
| data | ||
| dist | ||
| examples | ||
| licenses | ||
| scripts | ||
| src | ||
| test | ||
| .gitignore | ||
| LICENSE | ||
| package-lock.json | ||
| package.json | ||
| README.md | ||
| tsconfig.json | ||
pi-live-discovery
Discover models from an OpenAI-compatible endpoint and register them in Pi, with live models.dev reference metadata, explicit thinking levels, and offline caches.
Requires Pi 0.85.1 or newer (tested with 0.85.1), and Node 22.19+. Version 0.1 supports the openai-completions inference API only. It does not install, import, or run any OMP package or require Bun.
Install from this checkout
npm ci --ignore-scripts
npm run build
pi install /absolute/path/to/pi-live-discovery
Compiled dist/ files are included in the package and repository, so end users do not need TypeScript. The npm name is provisional; this package has not been published. Do not run npm publish until you have chosen an available name/scope.
Configure an endpoint
Create ~/.pi/agent/live-discovery.json:
{
"providers": {
"my-gateway": {
"name": "My gateway",
"baseUrl": "https://api.example.com/v1",
"apiKeyEnv": "MY_GATEWAY_API_KEY"
}
}
}
Then set MY_GATEWAY_API_KEY in the environment used to launch Pi, or run /login my-gateway to store an API key in Pi's credential store. Stored credentials take priority over apiKeyEnv. After login, use /discovery-refresh my-gateway if needed, then select a model with /model.
Use a unique provider ID. Do not reuse an ID owned by another extension or models.json. Built-in IDs and cpa are rejected, but Pi's factory API cannot reliably detect arbitrary registrations queued by other extensions. You can keep an existing provider installed under a different ID; this extension never modifies its configuration.
PI_CODING_AGENT_DIR changes the Pi agent directory normally. PI_LIVE_DISCOVERY_CONFIG=/absolute/path/config.json optionally selects another config file. No project-local configuration is loaded implicitly.
The base URL is the inference API root. https://host/v1 probes https://host/v1/models and sends inference to https://host/v1/chat/completions. /v1 is not automatically inserted. Optional discovery.path is an absolute, same-origin pathname for unusual discovery routes.
Commands
/discovery-status: models.dev metadata status plus provider model counts and live/cache/stale/auth status./discovery-refresh [provider]: fetch current models.dev metadata and force a network refresh of one or all configured providers. Other providers are re-enriched from their cached rosters without probing their endpoints./reload: reread configuration, including added/removed providers and overrides; fresh caches are reused./logout <provider>: use Pi's normal logout. If an environment key is still configured, that remains a valid fallback.
Remove the provider from the configuration and run /reload to unregister it. To remove the extension itself, use pi remove /absolute/path/to/pi-live-discovery.
Local servers without authentication
{
"providers": {
"local-discovery": {
"baseUrl": "http://127.0.0.1:1234/v1",
"auth": "none"
}
}
}
No authorization header is sent for a keyless endpoint. Pi receives a non-secret SDK placeholder, not a real credential.
CLIProxyAPI thinking discovery (opt-in)
The default discovery.type is "openai": existing providers only request their ordinary model list. For CLIProxyAPI, opt into a supplemental effort-list adapter:
{
"providers": {
"cliproxy": {
"name": "CLIProxyAPI",
"baseUrl": "http://127.0.0.1:8317/v1",
"apiKeyEnv": "CLIPROXY_API_KEY",
"discovery": { "type": "auto" }
}
}
}
Use a normal CLIProxyAPI client API key, not its management key. You can also use /login cliproxy. After changing configuration, run /reload; /discovery-refresh cliproxy forces another probe.
discovery.type |
Behavior |
|---|---|
"openai" (default) |
Existing OpenAI-compatible discovery only; no recognition or supplementary requests. |
"auto" |
After fetching a nonempty roster, probe GET / on the inference origin without credentials or configured headers. Require the exact message: "CLI Proxy API Server" and the three advertised model/completion routes before requesting additional metadata. |
"cliproxyapi" |
Skip recognition and explicitly request CLIProxyAPI metadata. Use this when a reverse proxy hides the root or mounts the API under a path prefix. |
When enabled/recognized, the adapter requests the configured models URL with ?client_version=cpa (normally /v1/models?client_version=cpa), using the same client credentials as ordinary discovery. It matches models[].slug to exact IDs already in the ordinary roster and normalizes supported_reasoning_levels[].effort into reasoning_options. It never adds models or imports Codex prompts, routing, headers, pricing, or template limits/modalities. Explicit ordinary endpoint reasoning_options and reasoning: false take precedence over the supplement; user overrides remain highest priority.
Nonempty supported effort lists replace the reference ladder; none/off permits disabling reasoning. Empty, malformed, ambiguous duplicate, or unknown-only lists leave existing metadata unchanged: an empty list can represent budget-based thinking, not lack of reasoning. Unknown levels such as ultra are not mapped to max; known levels in a mixed list can still be used.
Recognition is a compatibility heuristic, not authenticated server identity. It does not rely on hostname, port, provider name, or management endpoints. Root probes have a timeout of at most 2 seconds (bounded by discovery.timeoutMs); the supplemental request has its own discovery.timeoutMs budget. All requests stay on the configured origin and reject redirects. Hidden roots, older servers, timeouts, and malformed/failed supplemental responses silently fall back to the successful ordinary roster and normal catalog enrichment. Caller cancellation still stops the refresh.
Positive/negative recognition results are effectively cached with the enriched roster for discovery.cacheTtlMs: fresh-cache reloads and offline restarts make no discovery requests. A forced refresh or expired roster retries recognition and metadata. Discovery modes have separate cache identities, so switching modes requires an online refresh to populate that mode's cache. Default OpenAI mode still reads existing caches.
Limitation: CLIProxyAPI generates its Codex-client metadata partly from templates. Published efforts for unknown/custom models can be generic fallbacks, not verified backend capabilities. This adapter trusts advertised nonempty effort lists; use modelOverrides for exceptions. It does not probe inference to verify support or implement budget/adaptive thinking. The integration is based on upstream commit ffe6ad3c5fcf0a5eedd2198cd2e04b0249dc5063; older releases may not expose this response.
Options
{
"providers": {
"my-gateway": {
"baseUrl": "https://api.example.com/v1",
"api": "openai-completions",
"apiKeyEnv": "MY_GATEWAY_API_KEY",
"headers": { "X-Tenant": "$GATEWAY_TENANT" },
"discovery": {
"type": "openai",
"path": "/v1/models",
"timeoutMs": 10000,
"cacheTtlMs": 86400000
},
"compat": { "supportsDeveloperRole": false },
"modelOverrides": {
"private-astra-alias": {
"catalogId": "gpt-6-astra",
"catalogProvider": "openai",
"contextWindow": 262144,
"thinkingLevelMap": { "off": null, "minimal": null }
},
"unknown-reasoning-model": {
"reasoning": true,
"thinkingLevelMap": {
"minimal": null,
"low": null,
"medium": null,
"high": "high",
"xhigh": null,
"max": "max"
}
}
}
}
}
}
Headers support $NAME, ${NAME}, and $$ for a literal dollar. Missing referenced variables fail closed. Shell commands (!command) in credentials are intentionally unsupported. Prefer /login or apiKeyEnv; do not put secrets in the project or example files.
Overrides can set catalogId, catalogProvider, name, reasoning, input, contextWindow, maxTokens, thinkingLevelMap, cost, and compat. Maps merge per key. catalogId changes metadata matching only, never the model ID sent on the wire. Provider-level catalogProvider restricts all reference matching to that reference source.
The supported compat subset is validated in src/config.ts: store/developer-role/reasoning-effort/stream-usage/strict-mode flags, tool-result-name and assistant-after-tool flags, reasoning-content replay, maxTokensField, and common thinkingFormat values. Unknown options are rejected instead of silently ignored. Pi's own models.json overrides still compose above the registered provider.
Metadata behavior
Precedence:
- Explicit model overrides.
- Metadata advertised by the endpoint.
- Live models.dev metadata (or its last successful cached copy).
- The bundled reference snapshot, for models/fields missing from models.dev.
- Defaults: text input, no reasoning, 128K context, 16K output, unknown/zero cost.
Output is always capped at the effective context window. Proxy pricing is never borrowed from a reference: supply cost overrides if you want estimates.
The snapshot contains 245 public/first-party reference rows derived from the OMP 18.2.6 model catalog. This is data only, not the OMP rule engine. Provenance and the input SHA-256 are in data/catalog.json; license attribution is in licenses/.
Matching tries exact IDs, a leading bracket prefix, and an unambiguous namespace-free ID. It deliberately does not guess new model generations, quantized variants, opaque gateway IDs starting with @, or ambiguous catalog matches. Use catalogId/catalogProvider for these cases.
For effort-based reference models, models.dev's pure reasoning_options effort lists update Pi's thinkingLevelMap, including xhigh and max; otherwise the bundled snapshot remains the fallback. Reference effort lists alone do not establish that reasoning is mandatory. Mixed budget/toggle policies are not translated into an effort map. In particular, gpt-6-astra has low/medium/high/xhigh/max. The mere presence of a level in the catalog does not prove a gateway accepts it; endpoint metadata or your overrides can restrict it.
An explicit endpoint reasoning_options effort list replaces the reference ladder. Unsupported levels become null. none or off enables the off level with that wire value; an effort list without either is treated as mandatory reasoning. Explicit reasoning: false disables reasoning regardless of reference metadata.
Budget/adaptive thinking policies, provider-specific model-ID routing, and OMP wire quirks are not copied into a Chat Completions effort map. Those models can still inherit reasoning/input/limit metadata but need endpoint effort metadata or an explicit override for extended levels. This is not full OMP parity.
Live discovery and caching
A bounded startup probe populates models before Pi selection and RPC access. The package first fetches https://models.dev/api.json when its shared metadata cache is missing or expired (default TTL: one hour, timeout: 10 seconds). Fresh metadata caches skip that request. Endpoint rosters retain their separate cache TTL (default: one day). /reload follows the same cache rules; /discovery-refresh bypasses both TTLs. There is no continuous polling or push subscription.
models.dev refreshes are enabled by default when at least one discovery provider is configured. Optional top-level configuration in live-discovery.json:
{
"modelsDev": {
"enabled": true,
"timeoutMs": 10000,
"cacheTtlMs": 3600000
},
"providers": {
"my-gateway": {
"baseUrl": "https://api.example.com/v1",
"apiKeyEnv": "MY_GATEWAY_API_KEY"
}
}
}
Set modelsDev.enabled to false to use only the bundled references (also disables manual models.dev refresh). Set cacheTtlMs to 0 to fetch metadata on every startup/reload. No package update or rebuild is needed for live metadata changes.
The shared catalog imports first-party references from OpenAI, Anthropic, Google, DeepSeek, xAI, Mistral, Moonshot AI, MiniMax, Z.AI, Xiaomi, Meta, and Sakana. Moonshot AI uses the existing catalogProvider: "moonshot" name. Reseller catalogs are excluded to avoid ambiguous matches and proxy-specific limits. models.dev enriches only IDs returned by your endpoint; it does not register its entire catalog or change inference routing. Pricing, headers, and provider-specific transport settings are never imported.
Failed, malformed, or empty metadata responses keep the last good catalog, falling back to bundled data on a cold start. Status includes a warning, and endpoint discovery continues independently. Successful empty endpoint lists still remove previous models.
Pi's native provider refresh/auth hooks are used; this package keeps a separate raw, sanitized discovery cache so references and overrides can be reapplied after upgrades without storing credentials or transport settings. Files live under ~/.pi/agent/cache/pi-live-discovery/. Cache identity includes the endpoint, opt-in discovery mode, and a hash of resolved headers/credentials; different accounts do not share a roster. CLIProxyAPI effort supplements are stored only as sanitized, normalized reasoning_options alongside the ordinary model records. Writes are atomic with owner-only permissions. Cache writes failing do not discard a successful live result.
The separate public metadata cache is ~/.pi/agent/cache/pi-live-discovery/models-dev.json. It stores only sanitized reference fields and a check timestamp, and uses the same atomic, owner-only write policy. If saving fails, the live result remains usable in memory. No endpoint credentials or custom headers are sent to models.dev.
PI_OFFLINE=1 or Pi's --offline prevents automatic startup probes to both models.dev and endpoints; cached metadata and bundled references remain available. An explicit /discovery-refresh opts into network access. Error bodies and underlying network error strings are not logged. Redirects are rejected. No Stencil/OMP runtime metadata service is contacted.
Development
npm ci --ignore-scripts
npm run typecheck
npm test
npm pack --dry-run
Tests include actual Pi RPC subprocesses for default, auto-detected, and explicit CLIProxyAPI discovery against a local fixture HTTP server, with models.dev requests redirected to a local catalog fixture. They verify live metadata changes, discovered thinking levels, inference reasoning_effort: "max", auth isolation, roster additions/removals, and offline restart. CLIProxyAPI tests also cover credential-free recognition, real HTTP redirect rejection, metadata sanitization, cache mode isolation, fallback behavior, and supplementary request cancellation/timeouts. Unit tests also cover metadata TTL/forced refresh, shared requests, parsing, precedence, timeouts, cancellation, corrupt caches, and stale fallback. They use temporary HOME/config directories, no real credentials or inference services.
To update the data snapshot from an independently obtained upstream JSON file:
npm run catalog:import -- /path/to/models.json 18.2.6
npm test
The importer neither installs nor imports an OMP package. Review upstream licensing, provenance, model changes, and licenses/ before distributing an updated snapshot. Build and commit dist/ alongside source changes so Git-based installs work without dev-dependencies. npm pack rebuilds automatically.
License
Original code: MIT. Derived catalog data: upstream MIT attribution in licenses/oh-my-pi-MIT.txt. See licenses/NOTICE.md for the exact scope. No OMP runtime or package dependency is included.