feat(llm): dynamic model discovery with per-provider API listing and 1-day cache (#991)#1000
Merged
feat(llm): dynamic model discovery with per-provider API listing and 1-day cache (#991)#1000
Conversation
Each Status loopback event now emits a leading newline chunk before the status text so that consecutive calls (matching skills / building context / thinking) accumulate as separate lines rather than a single concatenated string in the ACP thought bubble. Closes #988
ToolCall.title was always set to the tool name ("bash"), making all
shell invocations indistinguishable in the Zed ACP chat UI.
Changes:
- Add `params: Option<serde_json::Value>` to `LoopbackEvent::ToolStart`
so the raw input is forwarded to the ACP layer without extra queries.
- Pass `tc.input` as params in `send_tool_start` at the call site.
- In `loopback_event_to_updates`: derive title from `command`, `path`,
or `url` key in params (truncated to 120 chars); set `raw_input` on
the ToolCall so the IDE can display it in "View Raw Input".
- Fix `permission_denied_returns_blocked_error` test to use an isolated
temp permission file, preventing pollution from a pre-existing
`bash = "allow"` entry in the default config.
…ams' into worktree-991
Implements issue #992 and #993 from epic #991. - Add zeph-llm/src/model_cache.rs: disk-backed cache for remote model listings with 24-hour TTL, atomic write via tmp-rename, invalidate(). - Add OllamaProvider::list_models_remote via ollama_rs list_local_models. - Add CompatibleProvider::list_models_remote delegating to inner OpenAi. - Add AnyProvider::list_models_remote dispatch; Router and Orchestrator fall back to list_models() until their own implementations land. - Add orchestrator SubProvider::list_models_remote for Claude/Ollama/OpenAI. - Add Agent::set_model stub and handle_model_command with cache-first listing, /model refresh to invalidate caches, and /model <id> switch. - Fix post-merge clippy: redundant closures, let-else, doc sections, map_unwrap_or, duplicate set_status_tx in CompatibleProvider.
…e paths Use an atomic counter to generate unique temp directories for each model_cache test, eliminating race conditions from shared path under parallel test execution. Also apply nightly fmt to affected files (any.rs, claude.rs, openai.rs, agent/mod.rs).
- cache_slug: enforce [a-zA-Z0-9_] allowlist, fallback to 'unknown' - claude/openai: log API error body at debug level, expose status code only - agent set_model: validate non-empty, max 256 chars, ASCII printable - any: add debug log for Router/Orchestrator list_models() fallback - agent /model refresh: clear all ~/.cache/zeph/models/*.json files - acp: fix UTF-8 panic in title truncation via chars().take() - model_cache for_slug: sanitize input slug as defense-in-depth - claude list_models_remote: add # Panics doc for static URL expect - CHANGELOG: document all epic #991 additions
This was
linked to
issues
Feb 26, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Parent Epic
#991
Summary
Implements full dynamic model discovery for all providers with disk-backed 24h cache.
Closes #992, #993, #994, #995, #996, #997.
Changes
Infrastructure (#992)
RemoteModelInfostruct:id,display_name,context_window,created_atModelCacheinzeph-llm/src/model_cache.rs: per-provider JSON cache at~/.cache/zeph/models/{slug}.json, 24h TTL, atomic writes via.tmp+ renameLlmProvider::list_models_remote()async trait method with default fallback tolist_models()Provider Implementations
list_models_remoteviaollama_rs::list_local_models; maps parameter size + quantization intodisplay_name; cache slug"ollama"GET /v1/models; 401/403 errors do not overwrite valid cache; cache slug"claude"GET {base_url}/v1/modelswith Bearer auth; cache slug derived from sanitized hostnameOpenAiProviderDispatch Layer (#996)
AnyProvider::list_models_remote()— dispatches to active variantRouterProvider::list_models_remote()— aggregates from all fallback providers, deduplicates byidModelOrchestrator::list_models_remote()— aggregates across all sub-providersUser-Facing (#997)
Agent::set_model(model_id)— validates input, hot-swaps provider model/model— lists discovered models with display names and cache age/model <id>— switches active model/model refresh— clears all~/.cache/zeph/models/*.jsonand re-fetchesAvailableCommandsUpdatepopulated with model list on session startSecurity
cache_slug()enforces[a-zA-Z0-9_]allowlist (path traversal prevention)debugonly; status code exposed to userset_model()validates: non-empty, max 256 chars, ASCII printablechars().take()Tests
/modelcommand handlers,set_modelvalidation