Skip to content

Opencode supports OpenAI SDK and Anthropic SDK API. Favorites ids are…#408

Open
eli-l wants to merge 2 commits into
Gitlawb:mainfrom
eli-l:feat/opencode-self-discovery
Open

Opencode supports OpenAI SDK and Anthropic SDK API. Favorites ids are…#408
eli-l wants to merge 2 commits into
Gitlawb:mainfrom
eli-l:feat/opencode-self-discovery

Conversation

@eli-l

@eli-l eli-l commented Jul 2, 2026

Copy link
Copy Markdown

… unique following / format

Summary

Linked issue

Fixes #

Checklist

  • The linked issue already has the issue-approved label.
  • go build ./..., go vet ./..., and go test ./... pass locally.
  • gofmt clean.
  • Tests added/updated for the change (and run under -race where relevant).
  • UI changes include screenshots or a short recording where possible.

Summary by CodeRabbit

  • New Features

    • First-run setup and provider setup now persist the full discovered model list, including per-model overrides.
    • Added two new provider options and expanded curated model groups.
    • Model discovery and catalog results now apply provider-specific filtering.
  • Bug Fixes

    • Discovered model persistence is non-blocking if it fails (a warning is shown instead).
    • Favorite models and the picker are now provider-qualified, keeping model identities distinct across providers and showing correct identifiers.
    • Saved provider details refresh immediately after wizard completion.

Copilot AI review requested due to automatic review settings July 2, 2026 19:18

Copilot AI left a comment

Copy link
Copy Markdown

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 expands provider/model handling to support additional OpenCode provider variants, makes model favorites provider-qualified (<provider>/<model>), and persists live-discovered model lists into config so they can be reused across sessions.

Changes:

  • Add new provider catalog entries for OpenCode-Go OpenAI SDK and Anthropic SDK variants, plus curated model lists and provider-specific model filtering.
  • Persist live-discovered models into config (ProviderProfile.Models) and thread discovered models through setup flows.
  • Update TUI model picker and provider wizard UI behavior to prefer raw model IDs and provider-qualified favorites.

Reviewed changes

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

Show a summary per file
File Description
internal/tui/provider_wizard.go Persist discovered models after saving provider; refresh savedProviders; adjust model label/secondary text rendering; add mergeSavedProvider.
internal/tui/provider_wizard_test.go Update assertions to match new model label/secondary text behavior.
internal/tui/picker.go Provider-qualified favorite keys, updated picker labeling, and favorite toggling keyed by provider/model.
internal/tui/picker_test.go Update picker tests for provider-qualified favorites and raw ID rendering.
internal/tui/options.go Add SetupSelection.Models to carry discovered models through setup.
internal/tui/onboarding.go Include discovered models in setup save request.
internal/providermodeldiscovery/discovery.go Filter discovered/catalog models through provider-specific allow/block logic.
internal/providermodelcatalog/filter.go Add provider-aware model ID allow/block and filtering helpers.
internal/providermodelcatalog/catalog.go Add curated models for new OpenCode-Go providers; apply provider-specific filtering.
internal/providercatalog/catalog.go Register new OpenCode-Go OpenAI/Anthropic SDK provider descriptors.
internal/providercatalog/catalog_test.go Update expected catalog IDs and per-transport ordering.
internal/config/writer.go Add persistence/merge logic for discovered models; add profile-splitting helper; add dedupe helper.
internal/config/writer_test.go Add tests for discovered model persistence/merge behavior and validation.
internal/config/types.go Introduce DiscoveredModel; add ProviderProfile.Models and unmarshal support.
internal/config/types_test.go Add JSON round-trip tests for DiscoveredModel and ProviderProfile.Models.
internal/cli/setup.go Persist discovered models after provider setup (non-fatal on failure).

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

Comment thread internal/tui/picker.go Outdated
item.Favorite = m.favoriteModels[item.Value]
item.Favorite = m.favoriteModels[modelFavoriteKey(item)]
result = append(result, item)
seen[item.Value] = true
Comment thread internal/config/writer.go Outdated
Comment on lines +139 to +161
// Build a lookup of existing models by id to preserve APIModel overrides.
existing := map[string]string{}
for _, m := range cfg.Providers[index].Models {
if id := strings.TrimSpace(m.ID); id != "" {
if m.APIModel != "" {
existing[id] = m.APIModel
}
}
}

// Merge: keep existing APIModel for surviving ids, use fresh id order.
merged := make([]DiscoveredModel, 0, len(models))
for _, m := range models {
id := strings.TrimSpace(m.ID)
if id == "" {
continue
}
dm := DiscoveredModel{ID: id}
if override, ok := existing[id]; ok {
dm.APIModel = override
}
merged = append(merged, dm)
}
Comment thread internal/config/writer.go
Comment on lines +238 to +249
func dedupDiscoveredModels(models []DiscoveredModel) []DiscoveredModel {
seen := map[string]bool{}
out := make([]DiscoveredModel, 0, len(models))
for _, m := range models {
if seen[m.ID] {
continue
}
seen[m.ID] = true
out = append(out, m)
}
return out
}
Comment thread internal/tui/provider_wizard.go Outdated
if _, err := config.SetProviderDiscoveredModels(m.userConfigPath, profile.Name, discovered); err != nil {
// Non-fatal: the provider was already saved. Log but don't block.
// (Redact the path in case it contains identifying info.)
wizard.err = strings.TrimSpace("saved provider but could not persist models")
@coderabbitai

coderabbitai Bot commented Jul 2, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 532173c4-6951-4065-9357-6ae3eaba7561

📥 Commits

Reviewing files that changed from the base of the PR and between 5457147 and 61e3f23.

📒 Files selected for processing (4)
  • internal/cli/setup.go
  • internal/config/writer.go
  • internal/tui/picker.go
  • internal/tui/provider_wizard.go
🚧 Files skipped from review as they are similar to previous changes (4)
  • internal/cli/setup.go
  • internal/tui/provider_wizard.go
  • internal/tui/picker.go
  • internal/config/writer.go

Walkthrough

Adds discovered-model persistence to setup and wizard flows, extends provider and model catalogs with new OpenCode-Go entries and provider-specific filtering, and changes picker favorites and labels to use provider-qualified model IDs.

Changes

Discovered model persistence and provider-qualified UI

Layer / File(s) Summary
DiscoveredModel type and profile field
internal/config/types.go, internal/config/types_test.go
Adds DiscoveredModel{ID, APIModel} and ProviderProfile.Models, wired into JSON unmarshal, with round-trip tests.
Config persistence functions
internal/config/writer.go, internal/config/writer_test.go
Adds SetProviderDiscoveredModels, PersistTwoProfiles, persistProfileGroup, and dedupDiscoveredModels, preserving APIModel overrides and dedup/sort behavior, with tests for merge, error, and validation cases.
CLI/onboarding/wizard wiring
internal/cli/setup.go, internal/tui/onboarding.go, internal/tui/options.go, internal/tui/provider_wizard.go, internal/tui/provider_wizard_test.go
saveSetupProvider, completeSetup, and applyProviderWizard build discovered model lists and persist them non-fatally; SetupSelection gains a Models field; wizard merges saved profile into m.savedProviders and changes displayLabel/secondaryText to prefer raw model IDs.
Catalog descriptors and model filtering
internal/providercatalog/catalog.go, internal/providercatalog/catalog_test.go, internal/providermodelcatalog/catalog.go, internal/providermodelcatalog/filter.go, internal/providermodeldiscovery/discovery.go
Adds opencode-go-openai/opencode-go-anthropic descriptors and curated models; adds ModelIDAllowedForProvider/FilterModelsForProvider and applies filtering in Models() and DiscoverCatalog.
Picker favorites keying and labels
internal/tui/picker.go, internal/tui/picker_test.go
Introduces modelFavoriteKey (provider-qualified), updates favorite toggling/grouping and recent-item construction, switches displayed labels to raw model IDs, with tests updated to match.

Estimated code review effort: 4 (Complex) | ~60 minutes

Possibly related PRs

  • Gitlawb/zero#141: Both PRs modify internal/config/types.go’s ProviderProfile JSON handling, including UnmarshalJSON.
  • Gitlawb/zero#161: Both PRs touch the TUI provider wizard onboarding flow and its config-save path.
  • Gitlawb/zero#168: Both PRs overlap in internal/tui/* model discovery, picker behavior, and provider wizard state.

Suggested reviewers: Vasanthdev2004, gnanam1990, anandh8x, kevincodex1

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title matches the main changes: OpenAI/Anthropic SDK support and provider-qualified favorite IDs.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
internal/tui/provider_wizard.go (1)

906-934: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Non-fatal warning is set but immediately discarded — never reaches the user.

wizard.err is set on line 930 when SetProviderDiscoveredModels fails, but applyProviderWizard unconditionally executes m.providerWizard = nil at line 958 before returning, in the same call and with no render step in between. Since wizard is m.providerWizard (a pointer, confirmed by the nil-check at function entry), setting it to nil discards the entire wizard state — including the just-set err — before it can ever be displayed. The intended non-fatal warning is effectively silent, defeating the purpose of setting it at all.

Store the warning somewhere that survives past m.providerWizard = nil (e.g. a persistent status/toast field on model), or surface it before nilling the wizard.

🐛 Illustrative fix (adjust to the model's actual status-message mechanism)
 			if len(discovered) > 0 {
 				if _, err := config.SetProviderDiscoveredModels(m.userConfigPath, profile.Name, discovered); err != nil {
-					// Non-fatal: the provider was already saved. Log but don't block.
-					// (Redact the path in case it contains identifying info.)
-					wizard.err = strings.TrimSpace("saved provider but could not persist models")
+					// Non-fatal: the provider was already saved. Surface via a field
+					// that outlives the wizard, since m.providerWizard is nilled below.
+					m.postSetupWarning = "saved provider but could not persist models"
 				}
 			}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/tui/provider_wizard.go` around lines 906 - 934, The non-fatal
warning assigned in applyProviderWizard is lost because m.providerWizard is
cleared before the UI can render it. Update the applyProviderWizard flow in
provider_wizard.go so the SetProviderDiscoveredModels failure message is stored
in a model-level status/toast field or otherwise surfaced before
m.providerWizard is set to nil. Keep the existing warning text generation near
the wizard.err assignment, but move delivery of that message to a state that
survives beyond the wizard pointer reset.
🧹 Nitpick comments (3)
internal/providermodelcatalog/catalog.go (1)

169-178: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consolidate the repeated FilterModelsForProvider wrapping.

All three return branches now separately call FilterModelsForProvider(provider.ID, dedupeModels(...)). Collapsing this into a single filtered return at the end reduces duplication and removes the risk that a future new branch forgets to wrap the result in the filter.

♻️ Proposed refactor
 func Models(provider providercatalog.Descriptor) []Model {
-	if models, ok := curatedModels[provider.ID]; ok {
-		return FilterModelsForProvider(provider.ID, dedupeModels(provider.DefaultModel, models))
-	}
-	models := registryModels(provider)
-	if len(models) > 0 {
-		return FilterModelsForProvider(provider.ID, dedupeModels(provider.DefaultModel, models))
-	}
-	return FilterModelsForProvider(provider.ID, dedupeModels(provider.DefaultModel, nil))
+	models, ok := curatedModels[provider.ID]
+	if !ok {
+		models = registryModels(provider)
+	}
+	return FilterModelsForProvider(provider.ID, dedupeModels(provider.DefaultModel, models))
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/providermodelcatalog/catalog.go` around lines 169 - 178, The Models
function repeats the same FilterModelsForProvider(provider.ID,
dedupeModels(...)) wrapping in every branch, so refactor it to compute the
models slice once from curatedModels or registryModels and then apply the filter
in a single return at the end. Keep using provider.DefaultModel with
dedupeModels and preserve the existing fallback to nil when no registry models
are found, but centralize the final FilterModelsForProvider call to avoid
duplicated logic in Models.
internal/providermodelcatalog/filter.go (1)

3-24: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Hardcoded provider allow-list logic will get harder to maintain as more providers need filtering.

opencode-go-anthropic is special-cased directly by ID string inside the switch. If another provider needs a similar allow-list later, this pattern requires another hardcoded case here, disconnected from the provider's own descriptor/catalog entry. Consider making the allow-list rule part of the provider data (e.g., an AllowedModelSubstrings/AllowedModelPattern field on the catalog descriptor or curated model set) so the constraint lives next to the provider definition instead of in a separate switch statement.

Also, this new exported logic (ModelIDAllowedForProvider/FilterModelsForProvider) doesn't appear to have its own dedicated unit tests in this diff — only catalog_test.go's ID/order checks exercise it indirectly. A few direct test cases (e.g., asserting qwen/minimax pass and other models are rejected for opencode-go-anthropic, and that all models pass through for unrelated providers) would guard against regressions in this filter contract.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/providermodelcatalog/filter.go` around lines 3 - 24, The hardcoded
provider-specific allow-list in ModelIDAllowedForProvider should be moved out of
the switch and into the provider’s catalog data so filtering rules live with the
provider descriptor instead of being special-cased by normalized provider ID.
Update the provider catalog/descriptors to carry an allow-list field (or
equivalent curated model rule) and have ModelIDAllowedForProvider consult that
data rather than strings like opencode-go-anthropic directly. Also add dedicated
unit tests for this filter contract covering qwen/minimax acceptance, rejection
of other models for opencode-go-anthropic, and passthrough behavior for
unrelated providers.
internal/config/writer.go (1)

184-235: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

No test coverage for PersistTwoProfiles/persistProfileGroup.

writer_test.go only adds tests for SetProviderDiscoveredModels; the new profile-splitting logic (classifier partitioning, single-vs-both-format no-op behavior, API key vs. APIKeyEnv branch) has no tests in this diff, despite being the mechanism that implements the PR's core "OpenAI SDK + Anthropic SDK" split.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/config/writer.go` around lines 184 - 235, Add tests for
PersistTwoProfiles and persistProfileGroup to cover the new split logic: verify
the classifier partitions models into OpenAI-compatible vs Anthropic-compatible
groups, that the function is a no-op when only one format is present, and that
both profiles are persisted only when both groups exist. Use the existing
symbols PersistTwoProfiles, persistProfileGroup, and the provider profile
helpers to assert the APIKey vs APIKeyEnv branch and the correct profile
names/format values are written.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@internal/cli/setup.go`:
- Around line 271-279: The empty error branch in the SetProviderDiscoveredModels
call is triggering SA9003 and hides useful diagnostics. Update the
selection.Models persistence block in setup.go so the err from
config.SetProviderDiscoveredModels is actually handled, ideally by logging a
non-fatal warning with profile.Name and configPath while still allowing setup to
continue. Keep the existing non-blocking behavior, but replace the empty if body
with an explicit use of err so failures are visible.

In `@internal/tui/picker.go`:
- Around line 350-369: The dedup logic in the picker assembly is using the wrong
key and never applies to catalog items, so the same provider+model can be
emitted twice. Update the recent/catalog merge in the function that builds the
picker list to use modelFavoriteKey(item) consistently for both seen checks and
writes, and apply the same dedup check before appending catalog entries while
still allowing cross-provider duplicates. Add a regression test around the
picker logic to cover an item appearing in both recent and catalog for the same
provider.

---

Outside diff comments:
In `@internal/tui/provider_wizard.go`:
- Around line 906-934: The non-fatal warning assigned in applyProviderWizard is
lost because m.providerWizard is cleared before the UI can render it. Update the
applyProviderWizard flow in provider_wizard.go so the
SetProviderDiscoveredModels failure message is stored in a model-level
status/toast field or otherwise surfaced before m.providerWizard is set to nil.
Keep the existing warning text generation near the wizard.err assignment, but
move delivery of that message to a state that survives beyond the wizard pointer
reset.

---

Nitpick comments:
In `@internal/config/writer.go`:
- Around line 184-235: Add tests for PersistTwoProfiles and persistProfileGroup
to cover the new split logic: verify the classifier partitions models into
OpenAI-compatible vs Anthropic-compatible groups, that the function is a no-op
when only one format is present, and that both profiles are persisted only when
both groups exist. Use the existing symbols PersistTwoProfiles,
persistProfileGroup, and the provider profile helpers to assert the APIKey vs
APIKeyEnv branch and the correct profile names/format values are written.

In `@internal/providermodelcatalog/catalog.go`:
- Around line 169-178: The Models function repeats the same
FilterModelsForProvider(provider.ID, dedupeModels(...)) wrapping in every
branch, so refactor it to compute the models slice once from curatedModels or
registryModels and then apply the filter in a single return at the end. Keep
using provider.DefaultModel with dedupeModels and preserve the existing fallback
to nil when no registry models are found, but centralize the final
FilterModelsForProvider call to avoid duplicated logic in Models.

In `@internal/providermodelcatalog/filter.go`:
- Around line 3-24: The hardcoded provider-specific allow-list in
ModelIDAllowedForProvider should be moved out of the switch and into the
provider’s catalog data so filtering rules live with the provider descriptor
instead of being special-cased by normalized provider ID. Update the provider
catalog/descriptors to carry an allow-list field (or equivalent curated model
rule) and have ModelIDAllowedForProvider consult that data rather than strings
like opencode-go-anthropic directly. Also add dedicated unit tests for this
filter contract covering qwen/minimax acceptance, rejection of other models for
opencode-go-anthropic, and passthrough behavior for unrelated providers.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: fd6fb509-077f-48f2-a054-4b6dcd4ef28b

📥 Commits

Reviewing files that changed from the base of the PR and between cdf9d83 and 5457147.

📒 Files selected for processing (16)
  • internal/cli/setup.go
  • internal/config/types.go
  • internal/config/types_test.go
  • internal/config/writer.go
  • internal/config/writer_test.go
  • internal/providercatalog/catalog.go
  • internal/providercatalog/catalog_test.go
  • internal/providermodelcatalog/catalog.go
  • internal/providermodelcatalog/filter.go
  • internal/providermodeldiscovery/discovery.go
  • internal/tui/onboarding.go
  • internal/tui/options.go
  • internal/tui/picker.go
  • internal/tui/picker_test.go
  • internal/tui/provider_wizard.go
  • internal/tui/provider_wizard_test.go

Comment thread internal/cli/setup.go
Comment thread internal/tui/picker.go
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.

2 participants