Skip to content

feat(coach): live model lists per provider + iOS #140 model catalog and per-model reasoning levels - #120

Merged
foureight84 merged 6 commits into
mainfrom
feat/coach-model-catalog
Oct 15, 2026
Merged

foureight84 merged 6 commits into
mainfrom
feat/coach-model-catalog

Conversation

@foureight84

@foureight84 foureight84 commented Oct 15, 2026 •

Copy link
Copy Markdown
Owner

Ports iOS #140 (coach model catalog, 8833ee0) and adds what the iOS change doesn't have: the coach now reads each provider's own model list instead of relying only on preset model names that go stale.

Live model lists

Once a cloud provider is selected and its key is saved, Settings fetches the provider's model list (CoachModelCatalog):

Provider Endpoint What it gives
OpenAI GET /v1/models ids; filtered to chat models (no embeddings, TTS, image, realtime, dated snapshots or retired slugs)
Gemini GET v1beta/models (paged, key in header) models that support generateContent, with display names
OpenRouter GET /api/v1/models tool-capable models only, with each model's reasoning levels and retirement date
MiniMax GET /v1/models ids

The picker shows the curated presets first, then "All models from …" with a filter (OpenRouter lists ~390 tool-capable models). Under the picker:

  • a status line, with a Refresh/Retry button;
  • a warning when the stored model isn't listed any more (probably retired), so every turn doesn't fail with a 404;
  • a note when OpenRouter has announced a retirement date.

The listing is advisory: the stored model stays a free string, and the presets are the fallback when the list can't be fetched. It is cached in memory for 6 h, keyed per provider and key. All four endpoints were checked to exist; OpenRouter's fields were read from the live response.

From iOS #140

  • Presets:
    • OpenAI: GPT-6 Luna / Sol (default) / Astra.
    • OpenRouter: Claude Sonnet / Opus / Haiku 5.5, GPT-6.1 Sol and GPT-6 Luna, Gemini 4 Argon and 3.8 Flash, DeepSeek V4.1 Flash.
    • MiniMax: M2.5 and M2.7 are dropped. MiniMax closes them to new users Oct 19 and retires them Nov 18. A stored choice keeps working until then, and the warning flags it afterwards.
  • Per-model reasoning levels (ReasoningEffort, none to max). The Reasoning picker lists only the levels the selected model accepts. The stored level is dropped when the model rejects it (ReasoningEfforts.resolve, applied in CoachClientResolver.coachSettings and meal analysis), because an unsupported level is an HTTP 400 on every turn. OpenRouter models outside the presets use the live reasoning.supported_efforts. The picker is hidden for MiniMax and local, whose clients send no reasoning field.
  • Gemini now sends the level as thinkingConfig.thinkingLevel, only for Gemini 3 and later, since 2.x rejects it. Before this, Android ignored the setting for Gemini.
  • Prices for the new models, cross-checked against OpenRouter's live pricing on 2026-10-14. Old entries are kept so stored choices still price.

Android differences

  • Gemini presets: Android keeps its rolling -latest aliases (issue Chat Issues Gemini Model Old #22) instead of iOS's versioned slugs.
  • Gemini thoughtSignature: Android already echoes it back.
  • Upstream gap: iOS could adopt the live lists too.

Review

An independent read-only review found no blocking issues. Fixed in 189fecf:

  • a superseded fetch could overwrite a newer result;
  • offline/proxy modes dropped the effort;
  • the Gemini page token wasn't URL-encoded;
  • possible false "unlisted" warnings for MiniMax presets.

Left as is, and noted in the code: for a custom OpenRouter model, the live effort levels apply only after Settings has fetched the list in that session. Until then the stored effort is sent unchanged, as before.

Also: Today Activity tile clipping (4621312)

A user reported that the Activity tile cut off steps and distance and dropped calories. We don't know their build or phone, and I couldn't reproduce it: the tile has sized to its content since 6be5d1c. So the values column now makes clipping impossible. It still reports its full height so the tile grows when it can; if it gets less space anyway, it's drawn scaled down to fit instead of cut off. Not checked on a device.

Tests

./gradlew :app:testDebugUnitTest: 1519 tests, 0 failures. New CoachModelCatalogTest covers:

  • the parsers (with an OpenRouter fixture trimmed from the live response);
  • the "unlisted model" rule;
  • reasoning-level resolution.

GeminiClientTest covers thinkingLevel. Existing fixtures were updated for the new defaults.

Not checked on a device, and no live call was made with a real key.

🤖 Generated with Claude Code

…nd per-model reasoning levels

Settings now reads each cloud provider's own model list once the provider is selected with a saved
key (OpenAI /v1/models, Gemini v1beta/models, OpenRouter /api/v1/models, MiniMax /v1/models), shows
it under the curated picks, and flags a stored model the provider no longer lists or is retiring,
instead of every coach turn failing on a stale preset. The stored model stays a free string; the
presets remain the fallback when the list can't be fetched.

Ports iOS #140 (8833ee0):
- presets: GPT-6 Luna / Sol (default) / Astra; OpenRouter Claude 5.5, GPT-6, Gemini 4 Argon / 3.8
  Flash, DeepSeek V4.1; MiniMax drops M2.5/M2.7 (retiring Nov 18 2026). Gemini keeps the rolling
  -latest aliases (issue #22), which iOS doesn't use.
- ReasoningEffort none..max with each model's accepted levels; the stored effort is dropped when the
  selected model rejects it (an HTTP 400 on every turn otherwise). OpenRouter models outside the
  presets use the live `reasoning.supported_efforts`.
- Gemini sends the level as thinkingConfig.thinkingLevel (3+ only; 2.x rejects it).
- Prices for the new models, checked against OpenRouter's live listing.
- A cancelled or superseded model-list fetch could land after a newer one (CancellationException
  was swallowed into a Failure, and Refresh wasn't cancelled on a key change). One load job at a
  time now, and cancellation propagates.
- Offline / backend-proxy modes send OpenAI requests; they resolve reasoning effort by the OpenAI
  rules again instead of always dropping it.
- Gemini page tokens are URL-encoded.
- MiniMax presets are never flagged as unlisted (whether /v1/models lists the -highspeed variants
  is unverified); a retired M2.5/M2.7 choice still is.
…ng them

Reported on an unknown build and phone: the Activity tile cut off steps and distance and lost the
calories row. The tile already sizes to its content (6be5d1c), but nothing guaranteed it when the
grid row ends up shorter than the values column. The column now reports its full height as
before, and if it still gets less, it is drawn scaled down to fit (anchored left, centred) rather
than clipped. The rings already shrink to the height they get.
…e's loading state

The superseded job's finally could run after its replacement had set loading, so the picker showed
the old status (and a Refresh button) while the new fetch was still in flight.
@foureight84
foureight84 merged commit a552179 into main Oct 15, 2026
1 check passed
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.

1 participant