fix(v2): refresh connected providers after late host registration - #373
Conversation
OpenCode v2 registers user-config providers a few seconds after plugin setup. At setup, ctx.provider.list() only returns the built-in `opencode` provider; configured providers appear later together with provider.updated/model.updated events. The plugin read the directory once during init, so a configured provider such as `newapi` stayed "not connected" for the lifetime of the process and every capture/profile request failed before generation. - Refresh the connected-provider snapshot on provider.updated and model.updated, coalescing bursts into at most one trailing refresh and stopping after dispose. - Before failing the connectivity gate, refresh once and re-check (ensureProviderConnected). The error message is unchanged. - Refresh errors are logged and keep the previous snapshot. A disposed instance only clears its own refresher.
718ad2f to
c576c1b
Compare
EyJunge1
left a comment
There was a problem hiding this comment.
Review summary
Approve — the miss-refresh path (ensureProviderConnected) correctly fixes the late provider-registration bug for auto-capture and profile learning. CI is green and the regression coverage for the gate path looks solid.
Not fully optimal: the event-driven refresh is defense-in-depth but lightly guaranteed, and a couple of comments/wiring details don't match the code. None of that blocks the bugfix.
What works well
- Refresh-on-miss before failing the connectivity gate (auto-capture + profile LLM)
- Snapshot retained on refresh errors; existing error strings unchanged
- Dispose clears the refresher by function identity (multi-instance safe)
- Activation /
provider.list()semantics from #369 left alone - Subprocess fixture covers miss, ghost, capture-error, refresh-error, dispose
Nits (non-blocking follow-ups)
-
Event path vs
eventBelongsToLocation
V2 adapter drops events with neitherlocation.directorynor a resolvablesessionID(src/v2/legacy-client.ts).provider.updated/model.updatedare ephemeral with optionallocation. If the host publishes without location, the event handler never runs and only miss-refresh saves you. Worth confirming real host payloads includelocation, and giving burst-fixture events a directory so the coalescing test actually exercises the adapter path. -
Comment vs code — gate bypasses coalescing
Comment says ensureProviderConnected reusesrunCoalescedRefresh, butproviderRefreshercallsrefreshConnectedProviders()directly → parallellist()possible under event + miss. Either wire through coalescing or fix the comment. -
Init refresh outside the mutex
void refreshConnectedProviders()doesn't setrefreshInFlight. PreferrunCoalescedRefresh()for the initial call so early events don't race a second list. -
Minor
- Log still says "Failed to initialize…" on later refreshes
- Burst assertion
listCalls < 8is soft; a tighter coalesce bound (or location-bearing events) would be stronger - Optional unit tests for
ensureProviderConnected/ clear-by-identity
Verdict
Merge as-is is fine. The gate miss-refresh is the load-bearing fix; the nits above are polish / hardening for a follow-up if you want them.
| })(); | ||
| }; | ||
|
|
||
| void refreshConnectedProviders(); |
There was a problem hiding this comment.
Nit: This init call bypasses the coalesce mutex below (refreshInFlight is still unset). Prefer runCoalescedRefresh() so a concurrent provider.updated during init cannot start a second parallel provider.list().
Share one awaitable coalesced refresh for init, events and gate misses; forward location-less provider/model inventory events; tighten burst coverage.
Summary
On OpenCode v2, auto-capture and profile learning can report a configured provider as "not connected" for the lifetime of the server process.
The plugin reads the provider directory once during setup. At that point OpenCode has only registered its built-in
opencodeprovider; user-configured providers appear about two seconds later, together withprovider.updatedandmodel.updatedevents. The snapshot therefore stays at["opencode"], so a provider configured viaopencodeProvider(for example a custom OpenAI-compatible provider) fails the connection gate before any generation request is made.This was reproduced with a probe plugin against an isolated OpenCode 2.0.21 server:
ctx.provider.list()returned onlyopencodeduring setup andopencodeplus the configured provider two seconds later. HTTP/api/providershowed the configured provider as enabled while the plugin kept reporting it as disconnected.Changes
provider.updatedandmodel.updated. Bursts are coalesced into at most one trailing refresh; refreshes stop after dispose.ensureProviderConnected). The error message is unchanged.Test plan
tests/v2-provider-refresh.test.tsuses the real plugin, V2 adapter event path, provider module state and auto-capture gate. Covers late registration, refresh-on-miss, unknown providers, refresh errors, event bursts and dispose.mainand pass with this change.ensureProviderConnected.Checklist
main(a24d79e, v2.28.3).bun testpasses (672 pass / 0 fail / 4 platform skips).bun run typecheckpasses.bun run checkpasses.