Skip to content

fix(tui): include OpenCode runtime custom models in picker#358

Open
jatroconis wants to merge 1 commit intoGentleman-Programming:mainfrom
jatroconis:fix/google-custom-model-picker
Open

fix(tui): include OpenCode runtime custom models in picker#358
jatroconis wants to merge 1 commit intoGentleman-Programming:mainfrom
jatroconis:fix/google-custom-model-picker

Conversation

@jatroconis
Copy link
Copy Markdown

🔗 Linked Issue

Closes #357


🏷️ PR Type

What kind of change does this PR introduce?

  • type:bug — Bug fix (non-breaking change that fixes an issue)
  • type:feature — New feature (non-breaking change that adds functionality)
  • type:docs — Documentation only
  • type:refactor — Code refactoring (no functional changes)
  • type:chore — Build, CI, or tooling changes
  • type:breaking-change — Breaking change (fix or feature that changes existing behavior)

📝 Summary

  • Fixes the OpenCode model picker so runtime-visible custom provider models are selectable in the Gentle AI TUI.
  • Merges opencode models <provider> output into the cached provider catalog before rendering the picker.
  • Adds a regression test covering missing custom Google models such as antigravity-claude-*.

📂 Changes

File / Area What Changed
internal/opencode/models.go Added runtime model enrichment from opencode models <provider> and merged missing models into the cached provider catalog.
internal/opencode/models_test.go Added regression coverage for custom Google/OpenCode models missing from cache.
internal/tui/screens/model_picker.go Enriched providers before filtering/rendering model choices in the TUI picker.

🧪 Test Plan

Unit Tests

go test ./...

E2E Tests (Docker required)

cd e2e && ./docker-test.sh
  • Unit tests pass (go test ./...)
  • E2E tests pass (cd e2e && ./docker-test.sh)
  • Manually tested locally

Manual verification:

  • reproduced the bug in the real gentle-ai TUI before the fix
  • ran the local patched binary and confirmed the Google provider model count increased from 27 to 33, consistent with the missing Antigravity entries being included

🤖 Automated Checks

The following checks run automatically on this PR:

Check Status Description
Check Issue Reference PR body must contain Closes/Fixes/Resolves #N
Check Issue Has status:approved Linked issue must have been approved before work began
Check PR Has type:* Label Exactly one type:* label must be applied
Unit Tests go test ./... must pass
E2E Tests cd e2e && ./docker-test.sh must pass

✅ Contributor Checklist

  • PR is linked to an issue with status:approved
  • I have added the appropriate type:* label to this PR
  • Unit tests pass (go test ./...)
  • E2E tests pass (cd e2e && ./docker-test.sh)
  • I have updated documentation if necessary
  • My commits follow Conventional Commits format
  • My commits do not include Co-Authored-By trailers

💬 Notes for Reviewers

  • I was able to create the issue and PR, but I do not have maintainer permissions to add status:approved to the issue or apply the type:bug label on the PR.
  • The fix is intentionally minimal: it preserves cached model metadata when available and only fills gaps from the OpenCode runtime catalog.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR fixes the OpenCode model picker in the TUI so that runtime-visible custom provider models (e.g., custom Google models returned by opencode models <provider>) are selectable even when they’re missing from the cached ~/.cache/opencode/models.json.

Changes:

  • Add runtime model enrichment by invoking opencode models <provider> and merging missing models into the cached provider catalog.
  • Update the TUI model picker initialization to enrich providers before filtering/rendering selectable models.
  • Add a regression unit test ensuring missing custom Google models are merged and become SDD-selectable.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 3 comments.

File Description
internal/opencode/models.go Adds runtime model listing via opencode models <provider> and merges missing runtime models into cached provider data.
internal/opencode/models_test.go Adds a unit test validating enrichment merges missing custom models and they survive SDD filtering/sorting.
internal/tui/screens/model_picker.go Calls provider enrichment during model picker state initialization so the picker reflects runtime-visible models.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +125 to +130
var listModelsForProvider = func(providerID string) ([]string, error) {
cmd := exec.Command("opencode", "models", providerID)
output, err := cmd.Output()
if err != nil {
return nil, err
}
Copy link

Copilot AI Apr 21, 2026

Choose a reason for hiding this comment

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

listModelsForProvider runs opencode models <provider> with exec.Command(...).Output() and no timeout/context. Because this is invoked during TUI initialization, a slow/hung opencode process can freeze the UI. Consider using exec.CommandContext with a short timeout (and optionally exec.LookPath upfront) and capturing stderr (e.g., via CombinedOutput) so failures return quickly and are diagnosable.

Copilot uses AI. Check for mistakes.
Comment on lines +122 to +147
// listModelsForProvider returns runtime-visible model IDs for a provider using
// the installed `opencode` binary. The CLI is the source of truth for custom
// models that may not appear in ~/.cache/opencode/models.json yet.
var listModelsForProvider = func(providerID string) ([]string, error) {
cmd := exec.Command("opencode", "models", providerID)
output, err := cmd.Output()
if err != nil {
return nil, err
}

var models []string
for _, line := range strings.Split(string(output), "\n") {
line = strings.TrimSpace(line)
if line == "" {
continue
}
parts := strings.SplitN(line, "/", 2)
if len(parts) != 2 || parts[0] != providerID || parts[1] == "" {
continue
}
models = append(models, parts[1])
}

return models, nil
}

Copy link

Copilot AI Apr 21, 2026

Choose a reason for hiding this comment

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

The parsing of opencode models output is embedded in listModelsForProvider and currently isn’t covered by tests (the added test stubs listModelsForProvider, so it doesn’t validate the output format assumptions). To prevent regressions if the CLI output changes (headers, extra columns, missing provider prefix, etc.), consider factoring the parsing into a small helper and adding table-driven tests for representative outputs.

Suggested change
// listModelsForProvider returns runtime-visible model IDs for a provider using
// the installed `opencode` binary. The CLI is the source of truth for custom
// models that may not appear in ~/.cache/opencode/models.json yet.
var listModelsForProvider = func(providerID string) ([]string, error) {
cmd := exec.Command("opencode", "models", providerID)
output, err := cmd.Output()
if err != nil {
return nil, err
}
var models []string
for _, line := range strings.Split(string(output), "\n") {
line = strings.TrimSpace(line)
if line == "" {
continue
}
parts := strings.SplitN(line, "/", 2)
if len(parts) != 2 || parts[0] != providerID || parts[1] == "" {
continue
}
models = append(models, parts[1])
}
return models, nil
}
// parseModelsForProviderOutput extracts model IDs for a provider from
// `opencode models` CLI output. Lines that are empty, malformed, for a
// different provider, or have an empty model suffix are ignored.
func parseModelsForProviderOutput(providerID, output string) []string {
var models []string
for _, line := range strings.Split(output, "\n") {
line = strings.TrimSpace(line)
if line == "" {
continue
}
parts := strings.SplitN(line, "/", 2)
if len(parts) != 2 || parts[0] != providerID || parts[1] == "" {
continue
}
models = append(models, parts[1])
}
return models
}
// listModelsForProvider returns runtime-visible model IDs for a provider using
// the installed `opencode` binary. The CLI is the source of truth for custom
// models that may not appear in ~/.cache/opencode/models.json yet.
var listModelsForProvider = func(providerID string) ([]string, error) {
cmd := exec.Command("opencode", "models", providerID)
output, err := cmd.Output()
if err != nil {
return nil, err
}
return parseModelsForProviderOutput(providerID, string(output)), nil
}

Copilot uses AI. Check for mistakes.
Comment on lines 63 to 65
available := opencode.DetectAvailableProviders(providers)
opencode.EnrichProvidersWithRuntimeModels(providers, available)

Copy link

Copilot AI Apr 21, 2026

Choose a reason for hiding this comment

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

NewModelPickerState now enriches providers by running the opencode CLI during state construction. This codepath is triggered synchronously on key navigation (see callers in internal/tui/model.go), so it can introduce noticeable latency. Consider making runtime enrichment lazy (only when entering a provider) or performing it asynchronously with a loading state/spinner, especially if multiple providers are available.

Copilot uses AI. Check for mistakes.
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.

fix(tui): include OpenCode custom Google models in model picker

2 participants