Skip to content

feat: dynamic model discovery from provider APIs#109

Closed
tim4net wants to merge 8 commits intoPizzaface:mainfrom
tim4net:feat/model-discovery
Closed

feat: dynamic model discovery from provider APIs#109
tim4net wants to merge 8 commits intoPizzaface:mainfrom
tim4net:feat/model-discovery

Conversation

@tim4net
Copy link
Copy Markdown
Contributor

@tim4net tim4net commented Mar 13, 2026

Summary

  • Adds an extension that fetches available models from provider APIs (OpenAI, Anthropic) on session start
  • Registers newly discovered models under synthetic provider names (e.g., openai-discovered) to avoid replacing curated built-in models
  • Caches results to ~/.pizzapi/discovered-models-cache.json (24h TTL) to avoid hitting APIs every session
  • Adds skipModelDiscovery flag to BuildExtensionFactoriesOptions for safe mode

Motivation

When providers release new models (e.g., GPT-5.4), PizzaPi users can't see or use them until the upstream pi-ai package manually updates its hardcoded registry. This extension bridges that gap by querying provider APIs directly.

Built-in models are never replaced — their curated settings (context window caps, compat flags, costs) are preserved. Only models with no existing registry entry are added with conservative defaults.

New files

  • packages/cli/src/extensions/model-discovery-providers.ts — OpenAI and Anthropic API fetchers
  • packages/cli/src/extensions/model-discovery.ts — Extension factory, caching, orchestration
  • packages/cli/src/extensions/model-discovery.test.ts — 16 tests using Bun.serve() for HTTP mocking

Test plan

  • 16 new unit tests covering provider fetching, caching, filtering, and error handling
  • Updated factories.test.ts with skipModelDiscovery flag test
  • Full test suite passes (1099 tests across all packages)
  • Typecheck passes
  • Manual verification: run pizza models and confirm newly released models appear

🤖 Generated with Claude Code

tfournet and others added 2 commits March 13, 2026 12:21
Adds an extension that fetches available models from provider APIs
(OpenAI, Anthropic) on session start and registers any that are missing
from the built-in registry. This ensures newly released models (e.g.,
GPT-5.4) are available without waiting for an upstream pi-ai update.

Discovered models are registered under synthetic provider names
(e.g., "openai-discovered") to avoid replacing curated built-in models.
Results are cached to disk for 24 hours to avoid hitting APIs every
session.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The web server's /api/models endpoint previously only returned the
hardcoded pi-ai model list. Now it also reads an optional models.json
from the data directory (/app/data/models.json in Docker, configurable
via PIZZAPI_CUSTOM_MODELS_PATH env var) and merges custom models into
the response. Built-in models are never overridden.

Also adds modelRegistry.refresh() to the relay's get_available_models
handler so the web UI picks up models.json changes without a CLI
restart.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@Pizzaface
Copy link
Copy Markdown
Owner

@codex review

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: af5fd46543

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +132 to +134
newModels = await discoverNewModels({ provider, baseUrl, apiKey, existingModelIds: existingIds });
// Store ALL fetched models in cache (before filtering)
const allFetched = await PROVIDER_FETCHERS[provider].fetch(baseUrl, apiKey);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Reuse discovery results instead of refetching providers

The cache-miss path calls provider discovery twice (discoverNewModels(...) and then PROVIDER_FETCHERS[provider].fetch(...)), which doubles startup latency/API traffic and can produce inconsistent behavior when one call fails and the other succeeds (e.g., models are discovered but not registered this session, or registered but cached as empty). This is especially painful on timeout/error paths because each provider can incur two 10s waits.

Useful? React with 👍 / 👎.

);
const customModelsPath = process.env.PIZZAPI_CUSTOM_MODELS_PATH
?? join(process.env.AUTH_DB_PATH ? join(process.env.AUTH_DB_PATH, "..") : "/app/data", "models.json");
const models = getAllModels(customModelsPath);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Keep listed models compatible with chat model resolution

GET /api/models now returns custom entries from models.json, but POST /api/chat still resolves models through getModel(provider, modelId) from the built-in pi-ai registry. Any custom id not present upstream can be returned as “available” and then fail at chat time with a 500, so the two endpoints no longer agree on what is usable.

Useful? React with 👍 / 👎.

tfournet and others added 6 commits March 13, 2026 14:05
Patches ModelRegistry.registerProvider() to merge models additively
instead of replacing all models for the provider. New models are only
added if their ID isn't already registered; existing curated models
are untouched. Also falls back to an existing model's baseUrl when
none is provided, so callers don't need to repeat the endpoint.

This allows the model discovery extension to register under the real
provider name (e.g. "openai" instead of "openai-discovered"), giving
correct sort order and provider labeling in the UI.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The built-in OpenAI baseUrl is https://api.openai.com/v1, so appending
/v1/models produces a double-path. Now detects if baseUrl already ends
with /v1 and uses /models directly.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Adds fetchOllamaModels() and wires it into the discovery extension.
Set PIZZAPI_OLLAMA_URL to automatically scan an Ollama server for
all available models on session start (cached 24h).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- zAI: JWT HS256 auth from ZAI_API_KEY env var, discovers glm-4.x/5 models
- Ollama: plain fetch from PIZZAPI_OLLAMA_URL, all local models

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@Pizzaface
Copy link
Copy Markdown
Owner

I majorly broke apart remote.ts - so this will likely need some changes to match - should be easier for agents to edit tho :P

@Pizzaface Pizzaface closed this Apr 27, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants