Provider-agnostic LLM registry, config check, migration runbook

[llm] assigns the bulk and editor roles by name over a [providers.*]
registry (kind = openai | anthropic, per-provider model, effort, daily
ceiling and price table); DeepseekBackend becomes OpenAiCompatibleBackend
(reasoning_effort passthrough), AnthropicBackend builds from the same
ProviderConfig, meters and provider_costs are keyed by provider name.
Gemini 3.8 Flash is declared via Google's OpenAI-compatible endpoint so
switching the editor is one line (or DAILY_EPUB_LLM__EDITOR=gemini for an
A/B dry run). Stale [deepseek]/[anthropic] tables, the top-level
max_daily_usd and the old key env vars fail loudly.

daily-epub config check validates and prints the resolved roles, models,
key presence and paths without opening the database.

docs/runbooks/curation-v2-migration.md walks the server upgrade from v1.

Registry implemented by a Claude agent from an orchestrator brief;
verified fmt/clippy(-W dead_code)/test green.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A1rCLQeKBgnBo3oTgHuTMe
This commit is contained in:
2026-09-02 18:44:57 +00:00
co-authored by Claude Fable 5.1
parent 5edbeb509f
commit 99d1338890
19 changed files with 2136 additions and 620 deletions
+3 -2
View File
@@ -1370,7 +1370,7 @@ mod tests {
"2026-09-01",
"2026-09-01T09:30:00Z",
"2026-09-01T09:45:00Z",
r#"{"deepseek":{"input_tokens":1,"cached_tokens":0,"cache_write_tokens":0,"output_tokens":1,"cost_usd":0.14}}"#,
r#"{"deepseek":{"input_tokens":1,"cached_tokens":0,"cache_write_tokens":0,"output_tokens":1,"cost_usd":0.14},"gemini":{"input_tokens":1,"cached_tokens":0,"cache_write_tokens":0,"output_tokens":1,"cost_usd":0.07}}"#,
),
] {
let run_id = db
@@ -1452,8 +1452,9 @@ mod tests {
"exploration rated positively: 1",
"cost per day (anthropic): $0.043",
"cost per day (deepseek): $0.020",
"cost per day (gemini): $0.005",
"cost per day (voyage): $0.001",
"cost per day (total): $0.064",
"cost per day (total): $0.069",
"mean generation time: 15m00s (3 runs)",
] {
assert!(text.contains(line), "missing {line:?} in:\n{text}");