Keep provider diagnostics and model lists in sync#1660
Conversation
|
| Filename | Overview |
|---|---|
| packages/server/src/server/agent/provider-snapshot-manager.ts | Diagnostic now force-refreshes via a single fetchCatalog call and appends Models/Status from the same snapshot. refreshSnapshotForCwd always calls refreshProviders with force: true, so the force-refresh comment is accurate. Sequencing is correct: refreshSnapshotForCwd awaits the full refresh before getProvider reads the entry. |
| packages/server/src/server/agent/provider-registry.ts | Adds required fetchCatalog to ProviderDefinition. createRegistryEntry.fetchCatalog correctly uses resolved.profileModels and resolved.profileModelsAreAdditive for catalog merging; wrapClientProvider also correctly merges. The hasReplacementModels fast-path skips runtime for model discovery while still fetching dynamic modes. |
| packages/app/src/components/provider-diagnostic-sheet.tsx | Moves stableDiscoveredRef update from a synchronous render-body mutation into a useEffect. Fixes the React rule violation, but introduces a timing gap: if two renders are batched before the effect fires, the ref may not reflect the latest models in the second render's useMemo. |
| packages/server/src/server/agent/providers/acp-agent.ts | Adds fetchCatalog that spawns a single ACP probe process to discover both models and modes, replacing the two-call listModels+listModes pattern used by the snapshot manager. Clean implementation. |
| packages/server/src/server/agent/providers/opencode-agent.ts | Extracts fetchModelsFromClient and fetchModesFromClient private helpers; fetchCatalog acquires the server once and fetches both in parallel. Diagnostic strips the Server and model-count rows (now centralised in snapshot manager). Clean refactor. |
| packages/server/src/server/agent/providers/pi/agent.ts | fetchCatalog is the new single-path for model discovery (with a dedicated 120s timeout). listModels delegates to it. isAvailable simplified to binary-presence check. Pi-specific Configured providers / Paseo MCP tools rows removed from getDiagnostic. |
| packages/server/src/server/agent/providers/generic-acp-agent.ts | Removes runDiagnosticACPProbe and its ACP process spawning from getDiagnostic. Diagnostic now only checks binary availability; models/status come from the centralized snapshot. Significant reduction in diagnostic complexity. |
| packages/server/src/server/agent/provider-registry.test.ts | New fetchCatalog tests cover replacement-model fast-path, additionalModels merging, and injected client dispatch. Key assertions verify catalog content (behavioral), but several tests also assert toHaveBeenCalledTimes for call-count verification of the optimization path. |
| packages/server/src/server/agent/providers/generic-acp-agent.diagnostic.test.ts | Removes three tests that spun up fake ACP process probes and simplifies the remaining test to verify only binary-presence diagnostic rows. Matches the new lightweight getDiagnostic contract. |
| packages/server/src/server/agent/agent-sdk-types.ts | Adds FetchCatalogOptions, ProviderCatalog, and optional fetchCatalog? to AgentClient. Types are clean and well-documented. |
Sequence Diagram
%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
participant App as ProviderDiagnosticSheet
participant PSM as ProviderSnapshotManager
participant Def as ProviderDefinition (createRegistryEntry)
participant Client as AgentClient (e.g. ACPAgentClient)
Note over App,Client: Model list refresh path
App->>PSM: getSnapshot(cwd)
PSM->>PSM: "refreshProviders(force=true)"
PSM->>Def: "fetchCatalog({cwd, force})"
alt client.fetchCatalog exists
Def->>Client: client.fetchCatalog(options)
Client-->>Def: "{models, modes}"
else fallback
Def->>Client: listModels + listModes (parallel)
Client-->>Def: models[], modes[]
end
Def->>Def: mergeModels(profileModels, additionalModels, rawModels)
Def-->>PSM: ProviderCatalog
PSM-->>App: "snapshot entry {models, modes, status}"
Note over App,Client: Diagnostic path (NEW - synced with model list)
App->>PSM: getProviderDiagnostic(provider)
PSM->>PSM: "refreshSnapshotForCwd → force=true via refreshProviders"
PSM->>Def: "fetchCatalog({cwd, force=true})"
Def-->>PSM: ProviderCatalog (same authority as model list)
PSM->>Client: getDiagnostic() [availability + auth only]
Client-->>PSM: baseDiagnostic string
PSM->>PSM: append Models N / Status
PSM-->>App: "{provider, diagnostic}"
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
participant App as ProviderDiagnosticSheet
participant PSM as ProviderSnapshotManager
participant Def as ProviderDefinition (createRegistryEntry)
participant Client as AgentClient (e.g. ACPAgentClient)
Note over App,Client: Model list refresh path
App->>PSM: getSnapshot(cwd)
PSM->>PSM: "refreshProviders(force=true)"
PSM->>Def: "fetchCatalog({cwd, force})"
alt client.fetchCatalog exists
Def->>Client: client.fetchCatalog(options)
Client-->>Def: "{models, modes}"
else fallback
Def->>Client: listModels + listModes (parallel)
Client-->>Def: models[], modes[]
end
Def->>Def: mergeModels(profileModels, additionalModels, rawModels)
Def-->>PSM: ProviderCatalog
PSM-->>App: "snapshot entry {models, modes, status}"
Note over App,Client: Diagnostic path (NEW - synced with model list)
App->>PSM: getProviderDiagnostic(provider)
PSM->>PSM: "refreshSnapshotForCwd → force=true via refreshProviders"
PSM->>Def: "fetchCatalog({cwd, force=true})"
Def-->>PSM: ProviderCatalog (same authority as model list)
PSM->>Client: getDiagnostic() [availability + auth only]
Client-->>PSM: baseDiagnostic string
PSM->>PSM: append Models N / Status
PSM-->>App: "{provider, diagnostic}"
Reviews (2): Last reviewed commit: "fix(server): preserve catalog profile mo..." | Re-trigger Greptile
|
On the Pi-specific diagnostic rows: removing those runtime-derived rows is intentional for this PR. |
|
Confirming this resolves a concrete, reproducible case on Windows. Symptom: Pi showed as not installed / no models in the provider list, while the diagnostic panel simultaneously reported Root cause (matches this PR): Environment / measurements:
Confirms the fix's direction: as a stopgap I raised only that single literal ( Closely related: #1541 (its "Root Cause 1" is this same Thanks for the fix! |
Linked issue
N/A
Type of change
What does this PR do
Provider diagnostics now refresh and report from the same provider snapshot used by the model list. Availability checks stay cheap and only answer whether the provider binary is present; model and mode discovery happen through one catalog refresh path.
This also removes provider-specific diagnostic model probes, lets providers fetch models and modes together where possible, keeps custom added models visible when replacement models are configured, and prevents the model sheet from showing stale discovered models after a terminal refresh error or empty catalog.
How did you verify it
npm run formatnpm run typechecknpm run typechecknpm run lintpackages/server/src/server/agent/provider-registry.test.tspackages/server/src/server/agent/provider-snapshot-manager.test.tspackages/server/src/server/agent/providers/opencode-agent.test.tspackages/server/src/server/agent/providers/generic-acp-agent.diagnostic.test.tsNo screenshot attached: the app change is state consistency in the existing provider model sheet, not a visual layout change.
Checklist
npm run typecheckpassesnpm run lintpassesnpm run formatran (Biome)