mirror of
https://github.com/Kilo-Org/kilocode.git
synced 2026-09-19 01:51:21 +08:00
docs(jetbrains): add provider settings plans
This commit is contained in:
@@ -0,0 +1,97 @@
|
||||
# JetBrains Provider Descriptions
|
||||
|
||||
## Goal
|
||||
|
||||
Show CLI-provided provider descriptions in JetBrains provider settings, especially for popular catalog rows like OpenAI, without duplicating provider-description data in Kotlin.
|
||||
|
||||
## Findings
|
||||
|
||||
- JetBrains already has most of the infrastructure:
|
||||
- `ProviderSettingsProviderDto.description` and `metadata` in `packages/kilo-jetbrains/shared/src/main/kotlin/ai/kilocode/rpc/dto/ProviderSettingsDto.kt`.
|
||||
- `/provider` parsing in `KiloCliDataParser.parseProviderSettingsProviders()` preserves `description`, `metadata.noteKey`, `metadata.note`, and `metadata.icon`.
|
||||
- `ProviderCatalog.providerDescription()` already prefers `description`, then `metadata.noteKey`, then `metadata.note`.
|
||||
- `ProviderListRenderer` already has a secondary `desc` label and tests for metadata-driven notes.
|
||||
- `KiloBundle.properties` already contains note strings such as `settings.providers.note.openai`.
|
||||
- The CLI already owns popular-provider display metadata in `packages/opencode/src/kilocode/provider/metadata.ts`.
|
||||
- The `/provider` handler attaches that metadata in `packages/opencode/src/server/routes/instance/httpapi/handlers/provider.ts` with `metadata: providerMetadata(item.id)`.
|
||||
- VS Code still has a client-side fallback mapping in `provider-catalog.ts`, but duplicating that in Kotlin should not be the primary fix because it creates another provider-description source of truth.
|
||||
- If JetBrains is not showing descriptions, the likely issue is one of:
|
||||
- the JetBrains-launched CLI binary is stale and does not include provider metadata,
|
||||
- the `/provider` response reaching JetBrains does not include `description` or `metadata`,
|
||||
- the JetBrains parser is not seeing the actual field shape returned by the endpoint,
|
||||
- the renderer/layout hides the existing description label.
|
||||
|
||||
## Plan
|
||||
|
||||
1. Verify the data path before changing UI behavior.
|
||||
- Use existing JetBrains logs from `KiloBackendProviderSettingsManager.state()` to check, for a provider such as `openai`, whether these flags are true:
|
||||
- `description=...`
|
||||
- `note=...`
|
||||
- `noteKey=settings.providers.note.openai`
|
||||
- If logs are inconclusive, add a temporary local inspection while implementing, or use the backend HTTP endpoint directly in the sandbox, to confirm the raw `GET /provider?directory=...` response contains `metadata`.
|
||||
- Confirm the JetBrains run is using a freshly built CLI binary that includes `packages/opencode/src/kilocode/provider/metadata.ts` and the `/provider` handler metadata attachment.
|
||||
|
||||
2. Fix the source if the response is missing metadata.
|
||||
- Do not add Kotlin fallback descriptions first.
|
||||
- If the packaged/dev JetBrains CLI binary is stale, fix the dev/run/build flow so the JetBrains plugin launches the current CLI artifact.
|
||||
- Regenerate server/API artifacts if the provider schema or endpoint metadata contract changed:
|
||||
- Run `./script/generate.ts` from the repo root after any `/provider` schema or endpoint changes.
|
||||
- Confirm generated SDK/OpenAPI outputs include the optional provider `metadata` field if they are affected.
|
||||
- Rebuild the CLI binary used by JetBrains so the running plugin includes `packages/opencode/src/kilocode/provider/metadata.ts` and the `/provider` handler metadata attachment.
|
||||
- From `packages/kilo-jetbrains/`, use the existing build flow that prepares CLI binaries for the plugin, such as `bun run build --prepare-cli` when only refreshing generated CLI binaries is needed.
|
||||
- For a full plugin build, use `bun run build` from `packages/kilo-jetbrains/`.
|
||||
- If using a sandbox run configuration, verify it points at the rebuilt/generated CLI artifact rather than an older installed `kilo`.
|
||||
- If the `/provider` handler is not the endpoint being hit, update the JetBrains backend to call the metadata-bearing endpoint.
|
||||
- If the handler returns `metadata` but the schema strips it, fix the CLI schema/serialization path in `packages/opencode`, with narrow `kilocode_change` markers as already done for the existing metadata fields.
|
||||
|
||||
3. Fix the parser only if the raw payload includes metadata but the DTO does not.
|
||||
- `KiloCliDataParser.parseProviderSettingsProviders()` already parses `metadata.noteKey`, `metadata.note`, and `metadata.icon`; keep this path as-is unless the actual wire field names differ.
|
||||
- Add/adjust parser tests only for the observed wire shape.
|
||||
|
||||
4. Fix the renderer only if the DTO has a description but the UI hides it.
|
||||
- `ProviderListRenderer` already sets `desc.text = providerDescription(value.provider)` and hides it only when empty.
|
||||
- Inspect whether row height/layout/action overlay causes the label to be clipped in the actual settings list.
|
||||
- Keep the existing description priority and only adjust Swing layout if the value is present but not visible.
|
||||
|
||||
5. Keep the existing data priority intact.
|
||||
- `provider.description` from the CLI should still win over note metadata.
|
||||
- A localized bundle value from `noteKey` should win over the English `metadata.note` fallback.
|
||||
- The English `metadata.note` should still work if a bundle key is missing.
|
||||
|
||||
6. Add a Kotlin fallback mapping only as an explicit compatibility decision.
|
||||
- This fallback would mirror VS Code’s older client-side note-key mapping.
|
||||
- Only add it if product wants JetBrains to show notes when connected to an older/stale CLI that cannot provide metadata.
|
||||
- If added, keep it small, document it as compatibility-only, and continue to prefer CLI `description` and `metadata`.
|
||||
|
||||
7. Align row rendering with the visible VS Code behavior.
|
||||
- Show notes for catalog rows such as `Popular providers` and `All providers`.
|
||||
- Consider hiding generic note text for `Connected providers` if strict VS Code parity is desired, because VS Code connected rows show connection/source controls rather than provider marketing notes.
|
||||
- Keep Kilo Gateway special behavior unchanged: connected Kilo has no actions.
|
||||
|
||||
8. Update tests based on the actual fix.
|
||||
- If the fix is source/data-path related, keep or add backend/parser tests proving provider metadata reaches `ProviderSettingsProviderDto`.
|
||||
- If the fix is renderer/layout related, add a frontend test proving a provider with CLI metadata renders the expected text.
|
||||
- Only if a compatibility Kotlin fallback is added, add a renderer/helper test proving `OpenAI` with no metadata still renders `GPT and Codex models with API key or ChatGPT login`.
|
||||
- Keep the existing test that unknown providers do not receive invented descriptions.
|
||||
- Keep the existing test that explicit `provider.description` overrides metadata/fallback notes.
|
||||
- If connected-row notes are intentionally hidden, add a small test for connected rows.
|
||||
|
||||
9. Run focused verification from `packages/kilo-jetbrains/`.
|
||||
- Refresh the CLI binary first when testing runtime behavior:
|
||||
- `bun run build --prepare-cli`
|
||||
- `./gradlew :frontend:test --tests ai.kilocode.client.settings.providers.ProvidersSettingsUiTest`
|
||||
- `./gradlew typecheck`
|
||||
- If a CLI/server source fix is needed, also run from repo root:
|
||||
- `bun run script/check-opencode-annotations.ts`
|
||||
- `./script/generate.ts`
|
||||
- If a new opencode test is added, run the targeted `bun test` from `packages/opencode/`.
|
||||
|
||||
## Expected Outcome
|
||||
|
||||
Popular provider rows in JetBrains settings show the same descriptive text users see in VS Code by consuming the CLI-owned provider metadata. Kotlin remains a renderer/client of provider descriptions, not a duplicated source of provider-description truth.
|
||||
|
||||
## Risks
|
||||
|
||||
- The screenshot may be from a stale dev CLI/backend binary. Fixing the run/build path is better than duplicating metadata in Kotlin.
|
||||
- Adding fallback descriptions for connected rows could make JetBrains diverge from the VS Code connected-provider section. Prefer catalog-row descriptions only if exact visual parity is the goal.
|
||||
- A Kotlin fallback mapping is acceptable only as compatibility with older CLI responses, not as the primary architecture.
|
||||
@@ -0,0 +1,307 @@
|
||||
# JetBrains Provider Metadata And Icons
|
||||
|
||||
## Goal
|
||||
|
||||
Make VS Code and JetBrains use the same provider display metadata for the Providers settings UI:
|
||||
|
||||
- Provider display name from the CLI provider catalog.
|
||||
- Provider description/note from a shared metadata contract.
|
||||
- Provider icon ID from a shared metadata contract, with each client rendering the icon using its native UI stack.
|
||||
|
||||
The shared source of truth should be the CLI `/provider` response. VS Code and JetBrains should not maintain separate provider-description switch statements.
|
||||
|
||||
## Current State
|
||||
|
||||
- VS Code provider settings render provider names from the provider list response.
|
||||
- VS Code provider icons and notes are client-side in `packages/kilo-vscode/webview-ui/src/components/settings/provider-catalog.ts`.
|
||||
- VS Code provider note strings are mostly in `packages/kilo-vscode/webview-ui/src/i18n/*`; `dialog.provider.kilo.note` already exists in `packages/kilo-i18n/src/*` and is merged into VS Code translations.
|
||||
- Provider SVG source assets live in `packages/ui/src/assets/icons/provider/*.svg` and are compiled into the web sprite used by `@kilocode/kilo-ui/provider-icon`.
|
||||
- JetBrains provider settings parse `/provider` in `packages/kilo-jetbrains/backend/src/main/kotlin/ai/kilocode/backend/cli/KiloCliDataParser.kt` and pass data through `ProviderSettingsProviderDto`.
|
||||
- JetBrains provider descriptions and icons are currently hardcoded in `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/settings/providers/ProviderCatalog.kt`.
|
||||
|
||||
## Architecture
|
||||
|
||||
Use this ownership split:
|
||||
|
||||
- CLI owns provider display metadata: `noteKey`, English fallback `note`, and `icon` ID.
|
||||
- `packages/kilo-i18n` owns shared localized note strings for clients that can translate `noteKey`.
|
||||
- `packages/ui/src/assets/icons/provider` remains the icon asset source.
|
||||
- VS Code keeps rendering with `ProviderIcon` and localizes `noteKey` through the webview i18n context.
|
||||
- JetBrains copies the same SVG assets into generated plugin resources and renders them with `IconLoader`.
|
||||
- JetBrains initially localizes only if a matching bundle key exists; otherwise it falls back to CLI `metadata.note`.
|
||||
|
||||
This avoids extracting UI code across clients while still sharing the data contract.
|
||||
|
||||
## Data Contract
|
||||
|
||||
Add optional metadata to provider list items:
|
||||
|
||||
```ts
|
||||
metadata?: {
|
||||
noteKey?: string
|
||||
note?: string
|
||||
icon?: string
|
||||
}
|
||||
```
|
||||
|
||||
Semantics:
|
||||
|
||||
- `noteKey`: stable translation key, e.g. `dialog.provider.openai.note`.
|
||||
- `note`: English fallback text for clients without that translation key.
|
||||
- `icon`: provider icon ID matching `packages/ui/src/assets/icons/provider/<icon>.svg`; fallback is `synthetic`.
|
||||
|
||||
## Implementation Steps
|
||||
|
||||
### 1. Add Kilo-Owned CLI Metadata Helper
|
||||
|
||||
Create `packages/opencode/src/kilocode/provider/metadata.ts`.
|
||||
|
||||
Responsibilities:
|
||||
|
||||
- Define the metadata shape with `noteKey`, `note`, and `icon`.
|
||||
- Export a helper such as `providerMetadata(providerID: string)`.
|
||||
- Include current popular providers:
|
||||
- `kilo`
|
||||
- `opencode`
|
||||
- `anthropic`
|
||||
- `deepseek`
|
||||
- `github-copilot*` mapped to icon `github-copilot`
|
||||
- `openai`
|
||||
- `google`
|
||||
- `openrouter`
|
||||
- `vercel`
|
||||
- Return `icon: providerID` when the icon exists, with explicit exceptions for aliases and fallback `synthetic`.
|
||||
- Keep the English fallback strings here so JetBrains can show the same descriptions before full Kotlin resource generation exists.
|
||||
|
||||
Keep this file under `kilocode` so no `kilocode_change` markers are needed.
|
||||
|
||||
### 2. Expose Metadata On `/provider`
|
||||
|
||||
Touch shared upstream files only where required, with narrow `kilocode_change` markers.
|
||||
|
||||
Likely files:
|
||||
|
||||
- `packages/opencode/src/provider/provider.ts`
|
||||
- Add optional `metadata` to `Provider.Info` schema.
|
||||
- Keep this as a small schema-only change.
|
||||
- `packages/opencode/src/server/routes/instance/httpapi/handlers/provider.ts`
|
||||
- Import the Kilo metadata helper.
|
||||
- When building the public `all` array for `/provider`, attach `metadata` to each public provider item.
|
||||
|
||||
Prefer not to put Kilo metadata inside `Provider.toPublicInfo()` unless necessary. Applying metadata at the HTTP boundary keeps the shared provider core closer to upstream and avoids affecting internal provider/model setup paths.
|
||||
|
||||
Run the annotation checker after implementation:
|
||||
|
||||
```bash
|
||||
bun run script/check-opencode-annotations.ts
|
||||
```
|
||||
|
||||
### 3. Regenerate SDK/OpenAPI
|
||||
|
||||
After changing the HTTP schema, run from repo root:
|
||||
|
||||
```bash
|
||||
./script/generate.ts
|
||||
```
|
||||
|
||||
Expected generated outputs include SDK/OpenAPI type changes under `packages/sdk/`, especially provider list response types. Do not hand-edit generated files.
|
||||
|
||||
### 4. Move Shared Provider Notes Into `packages/kilo-i18n`
|
||||
|
||||
Add provider note keys to `packages/kilo-i18n/src/*.ts`:
|
||||
|
||||
- `dialog.provider.opencode.note`
|
||||
- `dialog.provider.anthropic.note`
|
||||
- `dialog.provider.deepseek.note`
|
||||
- `dialog.provider.copilot.note`
|
||||
- `dialog.provider.openai.note`
|
||||
- `dialog.provider.google.note`
|
||||
- `dialog.provider.openrouter.note`
|
||||
- `dialog.provider.vercel.note`
|
||||
|
||||
`dialog.provider.kilo.note` already exists there.
|
||||
|
||||
Then remove duplicate provider-note strings from `packages/kilo-vscode/webview-ui/src/i18n/*` where they are now supplied by `packages/kilo-i18n`.
|
||||
|
||||
VS Code merges `packages/kilo-i18n` after app/UI dictionaries in `packages/kilo-vscode/webview-ui/src/context/language.tsx`, so existing `language.t(noteKey)` lookups continue to work.
|
||||
|
||||
### 5. Update VS Code To Consume Metadata
|
||||
|
||||
Files:
|
||||
|
||||
- `packages/kilo-vscode/webview-ui/src/types/messages/providers.ts`
|
||||
- Add optional `metadata` to the webview provider type.
|
||||
- `packages/kilo-vscode/webview-ui/src/components/settings/provider-catalog.ts`
|
||||
- Replace ID-only icon/note helpers with metadata-aware helpers.
|
||||
- Prefer `provider.metadata.icon` when valid, then provider ID, then `synthetic`.
|
||||
- Prefer `provider.metadata.noteKey`, then `provider.metadata.note`, then old local fallback if needed for compatibility.
|
||||
- `packages/kilo-vscode/webview-ui/src/components/settings/ProvidersTab.tsx`
|
||||
- Use metadata-aware description rendering for popular provider notes.
|
||||
- Continue translating note keys with `language.t`.
|
||||
- `packages/kilo-vscode/webview-ui/src/components/settings/ProviderSelectDialog.tsx`
|
||||
- Use metadata-aware icon lookup if the provider item carries metadata; otherwise keep the ID fallback.
|
||||
|
||||
Keep a small compatibility fallback during rollout so older CLI/provider responses without metadata still render correctly.
|
||||
|
||||
### 6. Add Metadata DTOs To JetBrains Shared RPC
|
||||
|
||||
File:
|
||||
|
||||
- `packages/kilo-jetbrains/shared/src/main/kotlin/ai/kilocode/rpc/dto/ProviderSettingsDto.kt`
|
||||
|
||||
Add a serializable nested DTO, for example:
|
||||
|
||||
```kotlin
|
||||
@Serializable
|
||||
data class ProviderMetadataDto(
|
||||
val noteKey: String? = null,
|
||||
val note: String? = null,
|
||||
val icon: String? = null,
|
||||
)
|
||||
```
|
||||
|
||||
Add `metadata: ProviderMetadataDto? = null` to `ProviderSettingsProviderDto`.
|
||||
|
||||
### 7. Parse Metadata In JetBrains Backend
|
||||
|
||||
File:
|
||||
|
||||
- `packages/kilo-jetbrains/backend/src/main/kotlin/ai/kilocode/backend/cli/KiloCliDataParser.kt`
|
||||
|
||||
Update `parseProviderSettingsProviders()` to parse `item["metadata"]` into `ProviderMetadataDto`.
|
||||
|
||||
Keep unknown-field tolerance. If metadata is missing or malformed, preserve current behavior by using `null` metadata and generic fallbacks.
|
||||
|
||||
### 8. Generate JetBrains Provider Icon Resources
|
||||
|
||||
File:
|
||||
|
||||
- `packages/kilo-jetbrains/frontend/build.gradle.kts`
|
||||
|
||||
Add a Gradle task that copies provider SVGs from:
|
||||
|
||||
```text
|
||||
../../ui/src/assets/icons/provider/*.svg
|
||||
```
|
||||
|
||||
Into generated resources, for example:
|
||||
|
||||
```text
|
||||
frontend/build/generated/provider-icons/icons/providers/
|
||||
```
|
||||
|
||||
Add that generated directory to `sourceSets.main.resources`, and make `processResources` depend on the copy task.
|
||||
|
||||
Normalize SVGs that use `currentColor` because IntelliJ SVG loading does not theme inherited `currentColor` reliably:
|
||||
|
||||
- Light variant: replace `currentColor` with a neutral icon color such as `#6E6E6E`.
|
||||
- Dark variant: emit `<name>_dark.svg` with a neutral dark icon color such as `#CED0D6`.
|
||||
|
||||
Generated SVG resources should not be committed.
|
||||
|
||||
### 9. Update JetBrains Provider Rendering
|
||||
|
||||
File:
|
||||
|
||||
- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/settings/providers/ProviderCatalog.kt`
|
||||
|
||||
Change `providerDescription(provider)`:
|
||||
|
||||
- If `provider.metadata?.noteKey` exists and `KiloBundle` can resolve it, use the localized bundle message.
|
||||
- Otherwise if `provider.metadata?.note` exists, use it.
|
||||
- Otherwise keep the generic fallback: `source · N models`.
|
||||
|
||||
Change `providerIcon(provider)`:
|
||||
|
||||
- Use `provider.metadata?.icon ?: provider.id`.
|
||||
- Load `/icons/providers/<icon>.svg` with `IconLoader`.
|
||||
- Fallback to `/icons/providers/synthetic.svg`.
|
||||
- Final fallback to `AllIcons.Nodes.Plugin` if the generated resource is missing.
|
||||
- Cache icons by icon ID to avoid repeated `IconLoader` work during list rendering.
|
||||
|
||||
Remove the hardcoded `providerNoteKey()` switch once metadata is the source of truth.
|
||||
|
||||
### 10. Optional JetBrains Localization Generation
|
||||
|
||||
If full localized JetBrains descriptions are required in this change, add a generation step that creates JetBrains resource bundle entries from `packages/kilo-i18n/src/*.ts`.
|
||||
|
||||
Suggested approach:
|
||||
|
||||
- Generate `.properties` files under `packages/kilo-jetbrains/frontend/build/generated/i18n/`.
|
||||
- Include generated resources in `sourceSets.main.resources`.
|
||||
- Use the existing `KiloBundle` lookup path.
|
||||
- Fall back to CLI `metadata.note` for missing keys.
|
||||
|
||||
If this is deferred, JetBrains still displays the same English descriptions via `metadata.note`, while VS Code remains localized through `metadata.noteKey`.
|
||||
|
||||
### 11. Tests
|
||||
|
||||
CLI:
|
||||
|
||||
- Add a Kilo-owned test under `packages/opencode/test/kilocode/` for `providerMetadata()` or `/provider` metadata shape.
|
||||
- Prefer testing the helper directly if starting a full provider list test is heavier than needed.
|
||||
|
||||
VS Code:
|
||||
|
||||
- Update/add unit coverage for metadata-aware icon and description helpers if an existing test location fits.
|
||||
- At minimum rely on `bun run typecheck` for provider type propagation.
|
||||
|
||||
JetBrains backend:
|
||||
|
||||
- Update `packages/kilo-jetbrains/backend/src/test/kotlin/ai/kilocode/backend/cli/KiloCliDataParserTest.kt`.
|
||||
- Add coverage that `parseProviderSettingsProviders()` preserves `metadata.noteKey`, `metadata.note`, and `metadata.icon`.
|
||||
- Add coverage that unknown fields remain tolerated.
|
||||
|
||||
JetBrains frontend:
|
||||
|
||||
- Update `packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/settings/providers/ProvidersSettingsUiTest.kt`.
|
||||
- Assert metadata notes render in the provider list.
|
||||
- Assert generic fallback remains for providers without metadata.
|
||||
- Assert provider icons are visible using metadata-driven icon selection.
|
||||
|
||||
### 12. Changeset
|
||||
|
||||
Add a patch changeset because this is user-facing UI behavior:
|
||||
|
||||
- Mention JetBrains provider settings now use shared provider descriptions and provider icons.
|
||||
- Keep the text user-facing, not implementation-specific.
|
||||
|
||||
## Verification
|
||||
|
||||
Run the smallest relevant checks after implementation:
|
||||
|
||||
```bash
|
||||
bun run script/check-opencode-annotations.ts
|
||||
./script/generate.ts
|
||||
```
|
||||
|
||||
From `packages/opencode/`:
|
||||
|
||||
```bash
|
||||
bun run typecheck
|
||||
bun test ./test/kilocode/<new-provider-metadata-test>.test.ts
|
||||
```
|
||||
|
||||
From `packages/kilo-vscode/`:
|
||||
|
||||
```bash
|
||||
bun run typecheck
|
||||
```
|
||||
|
||||
From `packages/kilo-jetbrains/`:
|
||||
|
||||
```bash
|
||||
./gradlew :backend:test --tests ai.kilocode.backend.cli.KiloCliDataParserTest
|
||||
./gradlew :frontend:test --tests ai.kilocode.client.settings.providers.ProvidersSettingsUiTest
|
||||
./gradlew typecheck
|
||||
```
|
||||
|
||||
If JetBrains icon resource generation is implemented in Gradle, also verify `processResources` includes generated provider icons in the frontend resource output.
|
||||
|
||||
## Risks And Constraints
|
||||
|
||||
- Shared upstream-owned CLI files must have narrow `kilocode_change` markers. Keep Kilo-specific logic in `packages/opencode/src/kilocode/`.
|
||||
- JetBrains cannot consume the Solid `ProviderIcon` component or the SVG sprite directly; it needs generated IntelliJ resources.
|
||||
- Some provider SVGs use `currentColor`; generated JetBrains icon resources need literal color values and dark variants.
|
||||
- If JetBrains localization generation is deferred, JetBrains descriptions will be shared but English-only. VS Code remains localized through `noteKey`.
|
||||
- Keep compatibility fallbacks in VS Code and JetBrains so older CLI responses without metadata still render usable provider rows.
|
||||
@@ -0,0 +1,246 @@
|
||||
# JetBrains provider settings: "Kilo backend is not ready" after OAuth/connect
|
||||
|
||||
## Symptom
|
||||
|
||||
User clicks OAuth (e.g. OpenAI), authorizes in the browser, switches back, and the
|
||||
settings UI shows an error with no connected provider. The OAuth itself actually
|
||||
succeeded on the CLI (auth was saved), but the action RPC fails with:
|
||||
|
||||
```
|
||||
RpcException: Remote call KiloProviderRpcApi#callback has failed:
|
||||
IllegalStateException: Kilo backend is not ready
|
||||
at KiloBackendAppService.requireReady(KiloBackendAppService.kt:213)
|
||||
at KiloBackendProviderSettingsManager.state(KiloBackendProviderSettingsManager.kt:47)
|
||||
at KiloBackendProviderSettingsManager.callback(KiloBackendProviderSettingsManager.kt:102)
|
||||
```
|
||||
|
||||
The user is left with providers=0 and an error overlay even though the connection worked.
|
||||
|
||||
## Root cause — a self-inflicted race between `dispose()` and `state()`
|
||||
|
||||
Every mutating provider action funnels through the same tail in
|
||||
`KiloBackendProviderSettingsManager` (`packages/kilo-jetbrains/backend/src/main/kotlin/ai/kilocode/backend/provider/KiloBackendProviderSettingsManager.kt`):
|
||||
|
||||
- `connect()` (84–89), `callback()` (98–103), `disconnect()` (105–134),
|
||||
`enable()` (136–141), `saveCustom()` (143–155) all end with:
|
||||
**`<mutate>` → `dispose()` (`POST /global/dispose`) → `state(directory)`**.
|
||||
- `state()` begins with `app.requireReady()` (line 47), which throws immediately
|
||||
when `appState` is not `Ready`.
|
||||
|
||||
`dispose()` exists to force the CLI to drop its cached global App so the next reads
|
||||
reflect the change just made. But the CLI also emits a `global.disposed` /
|
||||
`server.instance.disposed` SSE event in response. The backend watches those events in
|
||||
`KiloBackendAppService.startWatchingGlobalSseEvents()` (675–703) and, when the current
|
||||
state is `Ready`, calls `load()` (689–698).
|
||||
|
||||
`load()` (293–426) immediately flips `_appState` to `KiloAppState.Loading` (line 301)
|
||||
while it re-fetches config/profile/notifications, then returns to `Ready` ~0.5s later.
|
||||
|
||||
So the ordering that breaks:
|
||||
|
||||
1. Action mutates auth/config, then `dispose()` returns `200`.
|
||||
2. The CLI's `global.disposed` SSE event is processed by the event watcher →
|
||||
`load()` → `_appState = Loading`.
|
||||
3. The action's trailing `state(directory)` runs → `app.requireReady()` sees `Loading`
|
||||
→ throws `Kilo backend is not ready`.
|
||||
4. The whole action RPC fails. Frontend shows an error and no connected provider,
|
||||
even though the auth write succeeded.
|
||||
|
||||
The frontend logs confirm the backend recovers to `Ready` (`WorkspaceReady`) ~0.5s
|
||||
after the failure — it was a transient `Loading` window, not a real outage.
|
||||
|
||||
This is racy for every action, but surfaces most reliably for OAuth because the
|
||||
callback round-trip is slow (~8.8s observed), which widens the window for the
|
||||
SSE-triggered `load()` to land exactly before the trailing `state()`.
|
||||
|
||||
### Why the earlier fixes in this session don't cover it
|
||||
|
||||
- The renderer `ClassCastException` fix and the frontend `action()` try/catch
|
||||
(returning a `ProviderActionResultDto(state(dir), error=…)`) are still correct and
|
||||
should stay. But the frontend fallback re-calls `state(dir)`, which **also** races
|
||||
with the same `Loading` window and fails again (the logs show the fallback `state`
|
||||
RPC failing too). The fix must live on the backend.
|
||||
|
||||
## Primary fix — await readiness through the transient `Loading` window
|
||||
|
||||
Add a bounded `awaitReady()` to `KiloBackendAppService` and use it in the provider
|
||||
manager's `state()` instead of the immediate `requireReady()`. Because every action
|
||||
ends by calling `state()`, fixing the single chokepoint covers connect / callback /
|
||||
disconnect / enable / saveCustom **and** the direct UI load path.
|
||||
|
||||
### 1. `KiloBackendAppService.awaitReady()` (new)
|
||||
|
||||
In `packages/kilo-jetbrains/backend/src/main/kotlin/ai/kilocode/backend/app/KiloBackendAppService.kt`,
|
||||
next to `requireReady()` (209–215). Mirrors the existing `awaitLoadResult()` pattern
|
||||
(620–624) and the workspace RPC's "wait for Ready" approach
|
||||
(`KiloWorkspaceRpcApiImpl.state`, 94–102).
|
||||
|
||||
```kotlin
|
||||
suspend fun awaitReady(timeoutMs: Long = READY_TIMEOUT_MS) {
|
||||
when (_appState.value) {
|
||||
is KiloAppState.Ready -> return
|
||||
is KiloAppState.MigrationRequired -> throw IllegalStateException("Migration required")
|
||||
// Transient post-dispose / startup states — wait for them to settle.
|
||||
is KiloAppState.Loading, KiloAppState.Connecting -> {
|
||||
val settled = withTimeoutOrNull(timeoutMs) {
|
||||
appState.first { it !is KiloAppState.Loading && it !is KiloAppState.Connecting }
|
||||
}
|
||||
when (settled) {
|
||||
is KiloAppState.Ready -> return
|
||||
is KiloAppState.MigrationRequired -> throw IllegalStateException("Migration required")
|
||||
else -> throw IllegalStateException("Kilo backend is not ready")
|
||||
}
|
||||
}
|
||||
// Genuinely down — fail fast, same as requireReady().
|
||||
else -> throw IllegalStateException("Kilo backend is not ready")
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- Add `private const val READY_TIMEOUT_MS = 5_000L` to the companion (91–103).
|
||||
- Add `import kotlinx.coroutines.withTimeoutOrNull` (`withTimeout`/`first` already imported).
|
||||
- Rationale for 5s: observed recovery is ~0.5s, giving ~10× margin, and it stays well
|
||||
under the frontend RPC budget (`KiloProviderService.RPC_TIMEOUT_MS = 20_000`) even
|
||||
after adding the OAuth round-trip and the post-ready provider fetches. Only the
|
||||
transient `Loading`/`Connecting` states wait; `Disconnected`/`Error` still fail fast
|
||||
so a genuinely-down backend doesn't hang the UI.
|
||||
|
||||
### 2. Use it in the provider manager
|
||||
|
||||
`KiloBackendProviderSettingsManager.state()` (line 47):
|
||||
|
||||
```kotlin
|
||||
- app.requireReady()
|
||||
+ app.awaitReady()
|
||||
```
|
||||
|
||||
No other manager methods change — they reach readiness through `state()`.
|
||||
|
||||
### Why this is correct and minimal
|
||||
|
||||
- The only transient state that `dispose()` induces is `Loading` (the SSE handler only
|
||||
calls `load()` when currently `Ready`; the HTTP server and SSE stay connected, so the
|
||||
connection state does not drop). Waiting `Loading → Ready` is exactly the gap.
|
||||
- If `state()` happens to run *before* the SSE event is processed, `appState` is still
|
||||
`Ready` → `awaitReady()` returns instantly and proceeds; any per-resource fetch that
|
||||
races the dispose is already caught by `state()`'s `load(resource, errors){}` wrapper
|
||||
(178–190) and returned as a soft `errors` entry, not a hard RPC failure.
|
||||
- If the reload fails (`Loading → Error`), `awaitReady()` returns from `first{…}` on the
|
||||
`Error` state and throws promptly — no full-timeout stall.
|
||||
|
||||
### Scope decision
|
||||
|
||||
Keep the change limited to the provider manager's `state()`. The session/workspace RPCs
|
||||
also use `requireReady()`, but they are out of scope for this bug and have their own
|
||||
readiness handling (workspace emits `PENDING`). Broadening `requireReady → awaitReady`
|
||||
everywhere is a larger behavior change and not needed here.
|
||||
|
||||
## Secondary fix (included) — OAuth authorize/callback HTTP timeout
|
||||
|
||||
The first OAuth attempt failed earlier with `Read timed out` after 15s; the retry
|
||||
succeeded at 8.8s. The manager's `request()` helper hardcodes a 15s call/read timeout
|
||||
(`CALL_TIMEOUT_SECONDS = 15`, used in `request()` 207–220). OAuth `authorize`/`callback`
|
||||
do a provider-side code exchange that can exceed 15s under load.
|
||||
|
||||
The frontend bounds the whole action at `RPC_TIMEOUT_MS = 20_000`
|
||||
(`KiloProviderService`), so the backend timeout and the frontend RPC budget must be
|
||||
raised **together** — a longer backend timeout alone would just trip the frontend RPC
|
||||
timeout first.
|
||||
|
||||
### Backend — per-call timeout for the OAuth paths
|
||||
|
||||
In `KiloBackendProviderSettingsManager.kt`:
|
||||
|
||||
- Companion (33–42): add `private const val OAUTH_CALL_TIMEOUT_SECONDS = 60L` next to
|
||||
`CALL_TIMEOUT_SECONDS`.
|
||||
- `request()` (207): add `timeoutSeconds: Long = CALL_TIMEOUT_SECONDS` and use it for
|
||||
both `callTimeout(...)` and `readTimeout(...)` (211–212).
|
||||
- `post()` (193): add `timeoutSeconds: Long = CALL_TIMEOUT_SECONDS`, forwarded to
|
||||
`request(...)`. `get`/`put`/`patch`/`deleteAuth`/`dispose` keep the default.
|
||||
- `authorize()` (91–96): `post(".../oauth/authorize…", body, OAUTH_CALL_TIMEOUT_SECONDS)`.
|
||||
- `callback()` (98–103): `post(".../oauth/callback…", body, OAUTH_CALL_TIMEOUT_SECONDS)`.
|
||||
The trailing `dispose()`/`state()` keep their defaults.
|
||||
|
||||
### Frontend — matching RPC budget for the OAuth paths
|
||||
|
||||
In `KiloProviderService.kt`:
|
||||
|
||||
- Companion (32–35): add `private const val OAUTH_RPC_TIMEOUT_MS = 90_000L` next to
|
||||
`RPC_TIMEOUT_MS`.
|
||||
- `call()` (37): add `timeoutMs: Long = RPC_TIMEOUT_MS`, used in `withTimeout(timeoutMs)`.
|
||||
- `action()` (68): add `timeoutMs: Long = RPC_TIMEOUT_MS`, forwarded to `call(...)`.
|
||||
- `authorize(...)` (61): `call("authorize…", OAUTH_RPC_TIMEOUT_MS) { authorize(input) }`.
|
||||
- `callback(...)` (62): `action(input.directory, OAUTH_RPC_TIMEOUT_MS) { callback(input) }`.
|
||||
|
||||
Budget check: backend `callback` worst case ≈ `OAUTH_CALL_TIMEOUT_SECONDS` (60s) +
|
||||
`awaitReady` (≤5s) + provider fetches (~1s) ≈ ~66s, so the frontend OAuth budget of 90s
|
||||
leaves margin. All four values are constants and easy to tune later.
|
||||
|
||||
### Optional refinement — surface timeouts instead of swallowing them
|
||||
|
||||
`withTimeout` throws `TimeoutCancellationException`, a `CancellationException` subclass.
|
||||
`ProvidersSettingsUi.launch()` (170–192) currently treats all `CancellationException` as
|
||||
silent (logs "cancelled", shows nothing) — correct for stale/disposed cancellation, but
|
||||
it means a true RPC timeout shows no message. Optionally add a `catch
|
||||
(e: TimeoutCancellationException)` **before** the `CancellationException` catch that calls
|
||||
`showError(...)` (guarded by `active(id)` on the EDT) and does not rethrow. Keeps genuine
|
||||
cancellation silent while making a real timeout visible. Low risk; include if desired.
|
||||
|
||||
## Tests
|
||||
|
||||
Backend, real `KiloBackendAppService` + `MockCliServer` + `FakeCliServer`
|
||||
(`packages/kilo-jetbrains/backend/src/test/kotlin/ai/kilocode/backend/provider/KiloBackendProviderSettingsManagerTest.kt`
|
||||
and/or `app/KiloBackendAppServiceTest.kt`). No mocking of threading — drive the real
|
||||
SSE event + REST gate, matching existing tests that already use
|
||||
`MockCliServer.pushEvent(...)` and `responseGate`.
|
||||
|
||||
1. **`state()` waits through a dispose-triggered reload instead of failing** (the
|
||||
regression). Build app, connect, await `Ready`. Install `mock.responseGate =
|
||||
CountDownLatch(1)` so REST blocks; `mock.pushEvent("global.disposed", "{}")` to drive
|
||||
`load()` → `Loading`. Launch `manager.state("/test")` in `async`; assert it is **not**
|
||||
completed after a short settle (still awaiting). Release the gate; await the result
|
||||
and assert it returns a valid `ProviderSettingsDto` (no throw) with the mock's
|
||||
providers. Fails today (throws `Kilo backend is not ready`), passes after the fix.
|
||||
|
||||
2. **`awaitReady()` returns immediately when already Ready** — sanity/fast path.
|
||||
|
||||
3. **`awaitReady()` fails fast when Disconnected/Error** — assert it throws promptly
|
||||
(well under the timeout) so a genuinely-down backend doesn't hang.
|
||||
|
||||
4. (Optional) **End-to-end action**: with the app forced into `Loading` via gated reload,
|
||||
a `connect()`/`callback()` returns a populated result rather than throwing — confirms
|
||||
all actions inherit the fix via `state()`.
|
||||
|
||||
## Validation
|
||||
|
||||
From `packages/kilo-jetbrains/`:
|
||||
|
||||
- `./gradlew :backend:test --tests ai.kilocode.backend.provider.KiloBackendProviderSettingsManagerTest`
|
||||
- `./gradlew :backend:test --tests ai.kilocode.backend.app.KiloBackendAppServiceTest` (if a test is added there)
|
||||
- `./gradlew typecheck`
|
||||
|
||||
## Files touched
|
||||
|
||||
- `backend/.../app/KiloBackendAppService.kt` — add `awaitReady()` + `READY_TIMEOUT_MS` + `withTimeoutOrNull` import.
|
||||
- `backend/.../provider/KiloBackendProviderSettingsManager.kt` — `requireReady()` → `awaitReady()` in `state()`; add `OAUTH_CALL_TIMEOUT_SECONDS`, per-call timeout on `request()`/`post()`, and apply it in `authorize()`/`callback()`.
|
||||
- `frontend/.../app/KiloProviderService.kt` — add `OAUTH_RPC_TIMEOUT_MS`, `timeoutMs` params on `call()`/`action()`, apply to `authorize()`/`callback()`.
|
||||
- (Optional) `frontend/.../settings/providers/ProvidersSettingsUi.kt` — surface `TimeoutCancellationException` in `launch()`.
|
||||
- `backend/src/test/.../provider/KiloBackendProviderSettingsManagerTest.kt` (and/or `app/KiloBackendAppServiceTest.kt`) — new tests.
|
||||
- Reuse the existing changeset `.changeset/fix-jetbrains-provider-settings.md` (extend its text to mention the connect/OAuth not-ready fix and the OAuth timeout bump).
|
||||
|
||||
## Risks
|
||||
|
||||
- A real reload that never reaches `Ready` within 5s makes `state()` throw after the
|
||||
timeout (same user-visible "not ready" error as today, just delayed up to 5s) — only
|
||||
in a genuinely-broken backend, which already errors.
|
||||
- `state()` is now `suspend`-blocking up to 5s in the worst transient case; bounded and
|
||||
well under the (non-OAuth) 20s RPC budget.
|
||||
- The OAuth budget bump lets the UI wait up to ~90s for a genuinely stuck OAuth exchange.
|
||||
Mitigated by the loading overlay and the existing job-cancel on dispose/new action; the
|
||||
optional timeout-surfacing refinement makes a true timeout visible.
|
||||
|
||||
## Decisions
|
||||
|
||||
- Include the OAuth authorize/callback timeout bump together with the not-ready race fix
|
||||
(per user), coupling the backend per-call timeout (60s) with the frontend OAuth RPC
|
||||
budget (90s) so they cannot trip each other.
|
||||
@@ -0,0 +1,137 @@
|
||||
# JetBrains Provider Settings Filterable List Plan
|
||||
|
||||
## Goal
|
||||
|
||||
Replace the JetBrains provider settings page's current row-per-provider sections with a searchable provider list that mirrors the model picker's list/section/renderer pattern. The list should filter by provider name, keep action affordances (`Connect`, `OAuth`, `Disconnect`, `Enable`) visible per row, and group rows into `Popular providers` and `All providers`.
|
||||
|
||||
## Current State
|
||||
|
||||
- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/settings/providers/ProvidersSettingsUi.kt` renders providers into retained `SettingsRows` sections: `Connected providers`, `Available providers`, and `Disabled providers`.
|
||||
- The file already owns the correct action callbacks and dialogs: API-key connect, OAuth authorize/callback, disconnect, enable, custom provider, and reload.
|
||||
- Button visibility is already mostly correct via `buttons(provider, state, disabled)` and `configured(provider, state, ids)`:
|
||||
- disabled provider -> `Enable`
|
||||
- configured/connected provider -> `Disconnect`
|
||||
- available provider -> `Connect` and/or `OAuth`, with default API-key fallback when auth methods are absent
|
||||
- The JetBrains model picker provides the list pattern to reuse:
|
||||
- `ModelPicker.kt`: `SearchTextField`, `JBList`, `CollectionListModel`, keyboard navigation, mouse activation, row syncing after filtering
|
||||
- `ModelPickerRows.kt`: filtered row construction, section title calculation
|
||||
- `ModelPickerRenderer.kt`: `GroupHeaderSeparator`, row renderer, active affordance hit testing with static helpers
|
||||
- VS Code popular provider order comes from `packages/kilo-vscode/src/shared/provider-model.ts`:
|
||||
- `kilo`, `anthropic`, `deepseek`, `openai`, `google`, `openrouter`, `vercel`
|
||||
- VS Code excludes Kilo Gateway from its `Popular providers` section and treats it as a separate top card. For this change, exclude `kilo` from the JetBrains popular section too, but do not add a separate Kilo card unless product asks for full VS Code parity.
|
||||
|
||||
## Implementation
|
||||
|
||||
1. Add provider list model helpers in the JetBrains provider settings package.
|
||||
|
||||
Files:
|
||||
- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/settings/providers/ProviderCatalog.kt`
|
||||
- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/settings/providers/ProviderListRows.kt`
|
||||
|
||||
Shape:
|
||||
- Define `POPULAR_PROVIDER_IDS = listOf("kilo", "anthropic", "deepseek", "openai", "google", "openrouter", "vercel")`.
|
||||
- Define `isPopularProvider(id)` and `popularProviderIndex(id)` matching VS Code ordering.
|
||||
- Add `ProviderListRow(provider, section, action)` and an action enum such as `CONNECT`, `OAUTH`, `DISCONNECT`, `ENABLE`, or `NONE` if needed.
|
||||
- Build rows from `ProviderSettingsDto` and query text:
|
||||
- filter by provider name first; include `id` as a secondary match only if it feels useful, but the user specifically requested provider-name filtering
|
||||
- exclude disabled providers from `Popular providers`; show them in `All providers` with `Enable`
|
||||
- exclude configured/connected providers from `Popular providers`; show them in `All providers` with `Disconnect`
|
||||
- exclude `kilo` from `Popular providers`
|
||||
- popular section contains unconfigured, enabled providers in `POPULAR_PROVIDER_IDS` order
|
||||
- all section contains every remaining provider sorted by name/id
|
||||
- Use the existing `ModelSearch.matches(...)` if package visibility is acceptable; otherwise add a small local `ProviderSearch.matches(query, name)` helper to avoid broadening model-picker APIs.
|
||||
- Provide `providerListSectionTitle(rows, index)` similar to `modelPickerSectionTitle`.
|
||||
|
||||
2. Replace `ProvidersContent` section rows with a searchable `JBList`.
|
||||
|
||||
File:
|
||||
- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/settings/providers/ProvidersSettingsUi.kt`
|
||||
|
||||
Changes:
|
||||
- Keep the top `Add custom provider`, `Refresh`, and `status` controls.
|
||||
- Add a `SearchTextField(false)` above the list with placeholder `settings.providers.search`.
|
||||
- Use `CollectionListModel<ProviderListRow>` plus `JBList` inside a `JBScrollPane` or `ScrollPaneFactory.createScrollPane`.
|
||||
- On `update(state, error)`, store the current state, rebuild rows with the current search query, replace the model, and preserve selection where practical.
|
||||
- On search document changes, rebuild rows without reloading backend state.
|
||||
- Keep `ProvidersSettingsUi`'s existing async callbacks and `content.loading()` behavior.
|
||||
|
||||
3. Add a provider list renderer with button-like actions.
|
||||
|
||||
File:
|
||||
- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/settings/providers/ProviderListRenderer.kt`
|
||||
|
||||
Renderer behavior:
|
||||
- Follow `ModelPickerRenderer` rather than `SettingsRow`: `JPanel(BorderLayout())`, `GroupHeaderSeparator`, `PickerRow` or equivalent row background handling, `SimpleColoredComponent`/`JBLabel` for provider name and description.
|
||||
- Show provider name as the primary text.
|
||||
- Show secondary text using the current description logic: `source · N models`.
|
||||
- Render one or more button-shaped labels/components for actions:
|
||||
- `Connect` when API-key method is available or default fallback applies
|
||||
- `OAuth` when OAuth method is available
|
||||
- `Disconnect` for configured providers, disabled when `source == "env"`
|
||||
- `Enable` for disabled providers
|
||||
- Since Swing renderers are paint-only, do not rely on actual `JButton` action listeners inside the renderer. Instead, expose hit-test helpers similar to `ModelPickerRenderer.isFavoriteClick(...)`, such as `actionAt(list, bounds, point, row)`.
|
||||
- In the `JBList` mouse listener, map the clicked row/action to the existing callbacks: `connect(provider)`, `oauth(provider)`, `disconnect(provider)`, or `enable(provider)`.
|
||||
- Add keyboard activation for the selected row. If a row has one action, Enter triggers it. If a row has multiple actions (`Connect` and `OAuth`), Enter should trigger `Connect` and keyboard users can still tab/search/select; optional follow-up can add an action popup.
|
||||
|
||||
4. Preserve existing provider action semantics.
|
||||
|
||||
File:
|
||||
- `ProvidersSettingsUi.kt`
|
||||
|
||||
Keep or move these helpers without changing behavior:
|
||||
- `description(provider)`
|
||||
- `methods(provider, state)` with API-key fallback
|
||||
- `configured(provider, state, ids)`
|
||||
- action callback methods in `ProvidersSettingsUi`
|
||||
|
||||
Avoid backend/RPC changes unless implementation reveals a missing field. Current DTOs already contain providers, connected IDs, auth methods, config, disabled IDs, and source/key fields.
|
||||
|
||||
5. Update strings.
|
||||
|
||||
File:
|
||||
- `packages/kilo-jetbrains/frontend/src/main/resources/messages/KiloBundle.properties`
|
||||
|
||||
Add:
|
||||
- `settings.providers.popular=Popular providers`
|
||||
- `settings.providers.all=All providers`
|
||||
- `settings.providers.search=Filter providers`
|
||||
- `settings.providers.noMatches=No matching providers`
|
||||
|
||||
6. Update tests.
|
||||
|
||||
File:
|
||||
- `packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/settings/providers/ProvidersSettingsUiTest.kt`
|
||||
|
||||
Add/adjust tests to cover:
|
||||
- available catalog provider without explicit auth still exposes `Connect`
|
||||
- provider with API and OAuth methods exposes both actions
|
||||
- configured custom provider exposes only `Disconnect`
|
||||
- popular rows use VS Code order: Anthropic, DeepSeek, OpenAI, Google, OpenRouter, Vercel
|
||||
- connected popular providers are not duplicated in `Popular providers`
|
||||
- disabled popular providers appear in `All providers` with `Enable`
|
||||
- non-popular providers appear in `All providers` alphabetically
|
||||
- filtering by provider name hides non-matching rows and section headers update correctly
|
||||
- Kilo is excluded from `Popular providers`
|
||||
- renderer/hit-test helpers map click areas to expected actions without invoking backend services
|
||||
|
||||
## Verification
|
||||
|
||||
Run from `packages/kilo-jetbrains/`:
|
||||
|
||||
- `./gradlew test --tests ai.kilocode.client.settings.providers.ProvidersSettingsUiTest`
|
||||
- `./gradlew typecheck`
|
||||
|
||||
Manual sandbox check:
|
||||
|
||||
- Open Settings -> Tools -> Kilo Code -> Providers.
|
||||
- Type in the provider filter and confirm only provider-name matches remain.
|
||||
- Confirm `Popular providers` shows the VS Code popular providers that are not connected/disabled, in VS Code order.
|
||||
- Confirm `All providers` contains the rest plus connected/disabled rows with the correct actions.
|
||||
- Click `Connect`, `OAuth`, `Disconnect`, and `Enable` from list rows and verify existing dialogs/flows still run.
|
||||
|
||||
## Risks
|
||||
|
||||
- Swing list renderers cannot contain live `JButton` controls. The implementation must render button-like controls and use explicit mouse hit testing, like the model picker favorite icon.
|
||||
- Multiple actions per row need clear hit areas. Keep layout simple and test hit detection.
|
||||
- Retained `SettingsRows` currently make action-button discovery easy in tests; tests will need to inspect the `JBList` renderer/model instead of walking actual `JButton` instances.
|
||||
- Full VS Code parity for a separate Kilo Gateway row is out of scope for this filterable-list change unless requested separately.
|
||||
@@ -0,0 +1,55 @@
|
||||
# JetBrains Provider Settings Fixed Toolbar And Dialog Width
|
||||
|
||||
## Goal
|
||||
|
||||
Apply the follow-up provider settings UI adjustments: keep the provider toolbar outside the scrollable settings body, and make the custom-provider dialog wider by default by giving the API key field a 50-column preferred width.
|
||||
|
||||
## Current State
|
||||
|
||||
- `ProvidersSettingsUi` extends `SettingsPanel`, whose `content` is a `BorderLayoutPanel` inherited from `LayeredOverlayPanel`.
|
||||
- `SettingsPanel` currently installs one `JBScrollPane` in `content` `BorderLayout.CENTER`. Its scroll body contains `top`, a gap, and the `settings` stack.
|
||||
- `ProvidersContent` currently creates the add/refresh action toolbar inside its own `BaseContentPanel` stack, so the toolbar scrolls with the search field and provider list.
|
||||
- `ProvidersContent` constructor owns the toolbar action callbacks and registers add/refresh shortcuts on itself.
|
||||
- `CustomProviderDialog` creates `id`, `name`, `url`, `key`, `env`, and `models` fields with default columns. The API key field is a `JBPasswordField`.
|
||||
|
||||
## Implementation Plan
|
||||
|
||||
1. Make the provider toolbar fixed above the scrollable body.
|
||||
- Move add/refresh action creation out of `ProvidersContent` and into `ProvidersSettingsUi`, or create a small private toolbar factory there.
|
||||
- Add the toolbar component to `content` with `BorderLayout.NORTH` after `SettingsPanel` has installed its scrollpane in `BorderLayout.CENTER`.
|
||||
- Keep `setContent(view)` for the scrollable search/list body so the rest remains in the center scroll area.
|
||||
- Keep `toolbar.targetComponent` scoped to the provider settings UI or provider content.
|
||||
|
||||
2. Keep the scrollable body top-aligned in the center.
|
||||
- Leave the existing `SettingsPanel` center scrollpane in place.
|
||||
- Ensure `ProvidersContent` only contains the search field and direct `JBList`, preserving `BaseContentPanel`/`Stack` top-to-bottom behavior.
|
||||
- Do not reintroduce a nested list `JScrollPane`.
|
||||
|
||||
3. Preserve standard shortcuts after moving actions.
|
||||
- Register add with `CommonShortcuts.getNewForDialogs()`.
|
||||
- Register refresh with `ActionManager.getInstance().getAction("Refresh")?.shortcutSet` when available.
|
||||
- Register shortcuts against a root component that remains present while focus is in the toolbar, search field, or list.
|
||||
|
||||
4. Widen the custom provider dialog through field columns.
|
||||
- Set the custom-provider API key `JBPasswordField` to `columns = 50`.
|
||||
- Prefer setting columns on the existing field over hardcoded dialog dimensions.
|
||||
- If the API key field alone does not influence the full form width because of the custom `Stack` layout, set the relevant text fields to the same columns only as needed so the dialog expands naturally.
|
||||
|
||||
5. Update tests.
|
||||
- Adjust `ProvidersSettingsUiTest` layout coverage to assert the toolbar is not inside `ProvidersContent` and the scrollable content still has one `SearchTextField`, one direct `JBList`, and no nested `JScrollPane`.
|
||||
- Add or update a test that inspects the `ProvidersSettingsUi.content` layout children to confirm the toolbar is in `BorderLayout.NORTH` and the scrollpane remains in `BorderLayout.CENTER`.
|
||||
- Add a dialog-width test if practical by instantiating `CustomProviderDialog` on the EDT and checking the API key/password field columns, without showing the modal dialog.
|
||||
|
||||
6. Verification.
|
||||
- From `packages/kilo-jetbrains/`, run `./gradlew :frontend:test --tests ai.kilocode.client.settings.providers.ProvidersSettingsUiTest`.
|
||||
- From `packages/kilo-jetbrains/`, run `./gradlew typecheck`.
|
||||
|
||||
## Expected Outcome
|
||||
|
||||
Provider settings shows a fixed add/refresh toolbar at the top of the settings panel while the search field and provider list scroll below it. The custom-provider dialog opens wider by default because the API key field requests 50 columns.
|
||||
|
||||
## Risks
|
||||
|
||||
- `SettingsPanel.top` remains inside the scroll body, so using it for this toolbar would not satisfy the fixed-toolbar requirement; the toolbar must be added directly to `content` `BorderLayout.NORTH`.
|
||||
- Toolbar component internals are IntelliJ implementation details, so tests should prefer layout-region and behavior assertions over exact toolbar child classes.
|
||||
- If only the password field gets columns and the surrounding form layout does not propagate that width, other custom-provider text fields may also need matching columns to make the whole dialog consistently wider.
|
||||
@@ -0,0 +1,118 @@
|
||||
# JetBrains Provider Settings Layout And Progress Plan
|
||||
|
||||
## Goal
|
||||
|
||||
Update the JetBrains provider settings UI added in the filterable-list work so it uses a `BorderLayout` root with a north toolbar and center provider list, reuses the same progress/error overlay approach as model settings, and renders row actions as standard button-style affordances with `OAuth` before `Connect`.
|
||||
|
||||
## Current State
|
||||
|
||||
- `ProvidersSettingsUi` is a `JPanel(BorderLayout())` that hosts `ProvidersContent` in the center.
|
||||
- `ProvidersContent` currently extends `BaseContentPanel`, which is a vertical `Stack` intended for settings row sections.
|
||||
- `ProvidersContent` currently lays out toolbar, status label, search field, and list as vertical stack children.
|
||||
- Loading and error state are shown through a `JBLabel status`; this differs from model settings, where `SettingsPanel` and `SettingsProgressOverlay` provide floating progress/error messages through `showProgress`, `showError`, and `clearProgress`.
|
||||
- Provider list row actions are rendered as bordered `JBLabel`s and currently use link foreground styling for enabled actions.
|
||||
- `providerActions` currently returns API `CONNECT` before `OAUTH`.
|
||||
|
||||
## Implementation
|
||||
|
||||
1. Replace the provider content root layout.
|
||||
|
||||
File: `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/settings/providers/ProvidersSettingsUi.kt`
|
||||
|
||||
- Change `ProvidersContent` from `BaseContentPanel`/vertical stack to a `JPanel(BorderLayout())` or `BorderLayoutPanel`.
|
||||
- Keep `ProvidersSettingsUi` as the outer disposable component unless a larger refactor becomes necessary.
|
||||
- Build a north toolbar containing only:
|
||||
- `Add custom provider`
|
||||
- `Refresh`
|
||||
- Put the provider list area in the center.
|
||||
- Keep the search field as part of the center list area, directly above the scrollable `JBList`, because the user requested north toolbar only for `Add custom` and `Refresh`.
|
||||
- Remove the inline status label from the provider content tree.
|
||||
|
||||
2. Reuse the model settings progress/error overlay approach.
|
||||
|
||||
Files:
|
||||
- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/settings/base/SettingsPanel.kt`
|
||||
- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/settings/providers/ProvidersSettingsUi.kt`
|
||||
- Optional new helper in `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/settings/base/`
|
||||
|
||||
Preferred minimal approach:
|
||||
- Extract the common progress overlay methods from `SettingsPanel` into a small reusable base such as `SettingsOverlayPanel : LayeredOverlayPanel` that owns `SettingsProgressOverlay`, registers it with the same bounds logic, and exposes `showProgress`, `showError`, and `clearProgress`.
|
||||
- Make `SettingsPanel` extend `SettingsOverlayPanel` instead of duplicating overlay ownership.
|
||||
- Make `ProvidersSettingsUi` extend `SettingsOverlayPanel` and add `ProvidersContent` to `content` using `BorderLayout.CENTER`.
|
||||
- Preserve the same overlay visual position as model settings: centered horizontally, padded from the top.
|
||||
- Avoid converting providers to `BaseSettingsUi`; providers are action-driven and do not need draft/save/modified behavior.
|
||||
|
||||
Provider state handling:
|
||||
- `content.loading()` should be removed or changed into outer `showProgress(KiloBundle.message("settings.providers.loading"))` calls from `ProvidersSettingsUi`.
|
||||
- `content.error(...)` should be removed or changed into outer `showError(...)` calls from `ProvidersSettingsUi`.
|
||||
- `content.update(state, error)` should update rows only, then:
|
||||
- call `showError(error)` if the action result has an error message
|
||||
- call `showError(joined provider load errors)` if `state.errors` is not empty
|
||||
- call `clearProgress()` otherwise
|
||||
- When starting reload/connect/oauth/disconnect/enable/custom actions, call `showProgress(settings.providers.loading)` before launching the coroutine.
|
||||
- On coroutine exceptions, call `showError("${e::class.simpleName}: ${e.message}")` on EDT and leave existing list rows intact.
|
||||
|
||||
3. Keep center provider list behavior.
|
||||
|
||||
File: `ProvidersSettingsUi.kt`
|
||||
|
||||
- Retain `SearchTextField(false)`, `CollectionListModel<ProviderListRow>`, `JBList`, keyboard navigation, Enter activation, mouse hit testing, and scroll pane setup.
|
||||
- Put these in a center panel such as `BorderLayoutPanel`:
|
||||
- `NORTH`: search field
|
||||
- `CENTER`: scroll pane wrapping the list
|
||||
- Preserve selection across filtering and updates using current `sync(prefer, at)` logic.
|
||||
- Keep no-results text on the `JBList`.
|
||||
|
||||
4. Show `OAuth` before `Connect`.
|
||||
|
||||
File: `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/settings/providers/ProviderListRows.kt`
|
||||
|
||||
- Change `providerActions` for unconfigured providers to add `OAUTH` first, then `CONNECT`.
|
||||
- Update primary Enter activation to use the first action, so rows with both methods now default to OAuth.
|
||||
- Preserve existing behavior for disabled providers (`Enable`) and configured providers (`Disconnect`).
|
||||
|
||||
5. Render standard button-style actions, not link style.
|
||||
|
||||
File: `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/settings/providers/ProviderListRenderer.kt`
|
||||
|
||||
- Remove enabled link foreground usage from action labels.
|
||||
- Keep renderer actions paint-only; do not put live `JButton` instances in the renderer.
|
||||
- Make the `ActionLabel` look like a small standard button using platform-derived button colors/borders where available.
|
||||
- Use standard enabled/disabled label foregrounds rather than link colors.
|
||||
- Keep `actionAt` and `actionBounds` helpers aligned with rendered button geometry.
|
||||
- Keep disabled `Disconnect` for environment-backed providers non-clickable through `row.enabled(action)`.
|
||||
|
||||
6. Update tests.
|
||||
|
||||
File: `packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/settings/providers/ProvidersSettingsUiTest.kt`
|
||||
|
||||
Add or adjust tests for:
|
||||
- `ProvidersContent` has a `BorderLayout` root.
|
||||
- The north toolbar contains `Add custom provider` and `Refresh` buttons.
|
||||
- The center area contains the provider list and search field.
|
||||
- Provider rows with API and OAuth methods return actions in `OAUTH`, `CONNECT` order.
|
||||
- Renderer action labels expose `OAuth`, `Connect` order.
|
||||
- Renderer does not use link styling for enabled actions.
|
||||
- Existing hit testing still maps each rendered button area to the correct action.
|
||||
- Existing filtering/grouping/configured/disabled behavior remains covered.
|
||||
|
||||
## Verification
|
||||
|
||||
Run from `packages/kilo-jetbrains/`:
|
||||
|
||||
- `./gradlew :frontend:test --tests ai.kilocode.client.settings.providers.ProvidersSettingsUiTest`
|
||||
- `./gradlew typecheck`
|
||||
|
||||
Manual check:
|
||||
|
||||
- Open Settings -> Tools -> Kilo Code -> Providers.
|
||||
- Confirm only `Add custom provider` and `Refresh` are in the top toolbar.
|
||||
- Confirm the search field and provider list fill the center area.
|
||||
- Trigger refresh/connect/oauth/disconnect/enable flows and confirm loading/errors appear as the same floating overlay style used by model settings.
|
||||
- Confirm OAuth appears before Connect and clicking each action still invokes the correct existing flow.
|
||||
|
||||
## Risks
|
||||
|
||||
- `SettingsProgressOverlay` is currently wired only by `SettingsPanel`, so extracting a shared overlay base must avoid changing model settings behavior.
|
||||
- Renderer button styling must remain theme-safe and avoid live `JButton` instances in a `ListCellRenderer`.
|
||||
- Changing primary action order means Enter on a row with both OAuth and API now starts OAuth; this matches the requested action order but changes keyboard default behavior.
|
||||
@@ -0,0 +1,117 @@
|
||||
# JetBrains Provider Settings Renderer Updates
|
||||
|
||||
## Goal
|
||||
Update the JetBrains provider settings list so it matches the requested provider organization and action visibility rules:
|
||||
- Add a `Connected providers` section and place configured/connected providers there.
|
||||
- Hide custom provider creation/catalog rows.
|
||||
- Do not allow Kilo Gateway to be removed/disconnected.
|
||||
- Show action buttons only for the selected row, except connected rows show only `Disconnect` even when not selected.
|
||||
- Fix row layout so trailing actions/favorite controls are right-aligned and vertically centered through `PickerRow`, and migrate the model picker favorite star onto that support.
|
||||
- Add provider icons and VS Code-style provider notes where available.
|
||||
|
||||
## Findings
|
||||
- JetBrains provider UI is centered in:
|
||||
- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/settings/providers/ProviderListRows.kt`
|
||||
- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/settings/providers/ProviderListRenderer.kt`
|
||||
- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/settings/providers/ProvidersSettingsUi.kt`
|
||||
- Shared picker row layout is `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/session/ui/PickerRow.kt`.
|
||||
- Model picker renderer currently owns its own right-side favorite star placement in `ModelPickerRenderer.kt`.
|
||||
- VS Code uses `providerIcon(providerID)` in `webview-ui/src/components/settings/provider-catalog.ts`, mapping known provider ids to bundled icon names and falling back to `synthetic`.
|
||||
- VS Code provider list response does not expose provider descriptions. The server schema `Provider.Info` has `id`, `name`, `source`, `env`, `key`, `options`, and `models`, but no `description`. VS Code uses hardcoded note strings for popular providers in settings.
|
||||
- JetBrains already has a secondary provider line via `providerDescription(provider)`, currently `source · N models`.
|
||||
|
||||
## Assumptions
|
||||
- "Don't show custom" means remove the visible custom provider creation path and hide unconnected custom catalog rows. Already configured/connected custom providers should still remain visible in `Connected providers` so existing user state is not orphaned.
|
||||
- Kilo Gateway is an exception to the connected-row disconnect rule: it may appear as connected, but must not show or execute `Disconnect`.
|
||||
- Provider descriptions should use VS Code-style popular-provider notes when known and fall back to the existing source/model-count text because the CLI does not provide a provider description field today.
|
||||
|
||||
## Implementation Plan
|
||||
1. Add shared provider constants/metadata in `ProviderCatalog.kt`.
|
||||
- Add `KILO_PROVIDER_ID = "kilo"` and `CUSTOM_PROVIDER_PACKAGE = "@ai-sdk/openai-compatible"` to avoid repeated literals.
|
||||
- Add `providerNoteKey(id)` or `providerNote(provider)` for popular-provider descriptions matching VS Code notes for Anthropic, DeepSeek, OpenAI, Google, OpenRouter, Vercel, and Copilot-prefix providers.
|
||||
- Keep `providerDescription(provider)` as the public renderer helper, returning the localized note when present, otherwise the current source/model count.
|
||||
- Add a `providerIcon(provider)` helper returning a Swing `Icon` from bundled JetBrains resources, with Kilo and known popular/provider ids mapped first and a generic fallback.
|
||||
|
||||
2. Add provider icon resources minimally.
|
||||
- Bundle popular provider SVGs under `packages/kilo-jetbrains/frontend/src/main/resources/icons/providers/` where practical, copied from the existing provider icon sprite source in `packages/ui/src/components/provider-icons/sprite.svg`.
|
||||
- Use the existing `/icons/kilo.svg` for Kilo Gateway.
|
||||
- Use a platform/generic fallback icon for provider ids without a bundled resource.
|
||||
- Do not touch `packages/ui` or shared `packages/opencode` for icon support.
|
||||
|
||||
3. Rework provider row modeling in `ProviderListRows.kt`.
|
||||
- Add row metadata such as `connected: Boolean` and possibly `removable: Boolean`.
|
||||
- Define "connected" with existing `configured(provider, state, connectedIds)` so auth, env, config, key, and custom-config providers all group consistently.
|
||||
- Build sections in order: `Connected providers`, `Popular providers`, `All providers`.
|
||||
- Exclude connected/configured rows from popular/all sections.
|
||||
- Exclude unconnected custom rows from popular/all sections.
|
||||
- Keep disabled rows available with `ENABLE` actions unless they are custom rows hidden by the custom rule.
|
||||
- For Kilo Gateway, return no `DISCONNECT` action when connected/configured.
|
||||
- Preserve env disconnect protection by keeping `enabled(DISCONNECT) == false` for env rows or hiding the action if we choose a stricter UI guard.
|
||||
|
||||
4. Update provider action visibility and hit testing in `ProviderListRenderer.kt`.
|
||||
- Add a single `visibleActions(row, selected)` helper.
|
||||
- If `row.connected` and row can be disconnected, return only `DISCONNECT` regardless of selection.
|
||||
- If the row is not connected, return `row.actions` only when selected.
|
||||
- If the row is Kilo Gateway connected, return no actions.
|
||||
- Use `visibleActions` for rendering, `actionBounds`, and `actionAt` so hidden actions are not clickable.
|
||||
- Render provider icon to the left of provider name, then title/description text, then trailing actions.
|
||||
- Keep action labels styled as lightweight buttons using platform colors.
|
||||
|
||||
5. Extend `PickerRow.kt` for trailing controls.
|
||||
- Add a compatible `setContent(content: JComponent, trailing: JComponent? = null, border: Border? = null)` or equivalent API.
|
||||
- Internally place content in center and optional trailing component at the right, vertically centered using the existing `Align` layout helper.
|
||||
- Keep existing `setContent(component)` behavior for mode picker and any other caller.
|
||||
- Avoid hardcoded raw Swing dimensions/colors; use `UiStyle.Gap`, `JBUI`, and platform colors.
|
||||
|
||||
6. Migrate renderers to the new `PickerRow` trailing support.
|
||||
- Provider renderer: pass its action panel as `PickerRow` trailing content instead of using `FlowLayout` in `BorderLayout.EAST` inside the renderer row.
|
||||
- Model picker renderer: move the favorite star out of the internal `row.add(star, BorderLayout.EAST)` and pass it as `PickerRow` trailing content.
|
||||
- Keep model picker favorite visibility behavior and click hit testing unchanged from the user's perspective.
|
||||
- Leave mode picker on the backward-compatible `setContent(row)` path unless a trivial migration is needed.
|
||||
|
||||
7. Remove visible custom-provider entry points in `ProvidersSettingsUi.kt`.
|
||||
- Remove the `Add custom provider` toolbar button from `ProvidersContent`.
|
||||
- Keep backend/custom dialog code in place unless it becomes unused enough to fail lint/typecheck; this minimizes behavioral churn and preserves future reuse.
|
||||
- Update constructor/test helpers for the removed custom callback if needed.
|
||||
|
||||
8. Add backend Kilo Gateway disconnect guard in `KiloBackendProviderSettingsManager.kt`.
|
||||
- Change `disconnect(providerId = "kilo")` from logout/profile clearing to returning the current state with a user-facing error such as `Kilo Gateway cannot be disconnected from provider settings.`
|
||||
- Keep profile logout available through the profile flow, not provider settings.
|
||||
|
||||
9. Update localized strings in `KiloBundle.properties`.
|
||||
- Add provider note strings mirroring VS Code's English notes.
|
||||
- Add the Kilo Gateway disconnect guard error string if surfaced from backend/frontend.
|
||||
- Remove or stop using `settings.providers.addCustom` in this UI path.
|
||||
|
||||
10. Add/update tests.
|
||||
- `ProvidersSettingsUiTest`:
|
||||
- connected/configured providers appear under `Connected providers` first.
|
||||
- connected rows are not duplicated in popular/all.
|
||||
- unconnected custom rows and the add-custom button are hidden.
|
||||
- connected rows render only `Disconnect` when not selected.
|
||||
- unselected unconnected rows render no actions and hit testing returns null.
|
||||
- selected unconnected rows render their connect/oauth/enable actions.
|
||||
- Kilo Gateway connected rows render no disconnect and cannot activate disconnect.
|
||||
- provider renderer exposes icon + description/note text as expected.
|
||||
- action label bounds are vertically centered in row bounds.
|
||||
- `ModelPickerTest`:
|
||||
- favorite star behavior and click hit testing remain unchanged after moving star to `PickerRow` trailing support.
|
||||
- `KiloBackendProviderSettingsManagerTest`:
|
||||
- disconnecting `kilo` returns an error/no-op result and does not call logout/auth removal.
|
||||
- Parser/DTO tests only if provider DTO/icon metadata requires DTO changes; current plan avoids DTO changes.
|
||||
|
||||
11. Add a changeset.
|
||||
- Create a new `.changeset/*.md` because this is user-facing JetBrains provider settings behavior.
|
||||
- Patch entry should describe the user-visible provider settings list/action changes.
|
||||
|
||||
## Verification
|
||||
Run the smallest relevant checks from `packages/kilo-jetbrains/`:
|
||||
- `./gradlew :frontend:test --tests ai.kilocode.client.settings.providers.ProvidersSettingsUiTest`
|
||||
- `./gradlew :frontend:test --tests ai.kilocode.client.session.ui.model.ModelPickerTest`
|
||||
- `./gradlew :backend:test --tests ai.kilocode.backend.provider.KiloBackendProviderSettingsManagerTest`
|
||||
- `./gradlew typecheck`
|
||||
|
||||
## Risks
|
||||
- Bundling many provider brand icons could create a noisy diff. Start with Kilo/popular icons plus a fallback, then expand only if needed.
|
||||
- If "don't show custom" is intended to hide already-connected custom providers too, the row filtering rule will need one small adjustment.
|
||||
- `PickerRow` is shared by provider, model, and mode pickers; keep its existing single-content API backward-compatible and verify model picker tests after migration.
|
||||
@@ -0,0 +1,66 @@
|
||||
# Fix JetBrains Provider Settings Threading
|
||||
|
||||
## Goal
|
||||
Fix the JetBrains Provider Settings configurable so it stops showing `Loading providers` once provider state has loaded, does not repaint or mutate Swing from the wrong thread, and ignores stale async work after reloads, actions, or configurable disposal.
|
||||
|
||||
## Findings
|
||||
- `ProvidersSettingsUi` uses `Dispatchers.Main` for Swing work, while JetBrains package guidance and existing settings code use `Dispatchers.EDT + ModalityState.any().asContextElement()`.
|
||||
- `ProvidersSettingsUi.connect()` and `custom()` read dialog Swing fields from a background coroutine after `showAndGet()` returns.
|
||||
- `ProvidersSettingsUi.launch()` catches all `Exception`, including `CancellationException`, and has no current-request or disposed guard before applying provider state.
|
||||
- `ProvidersConfigurable.disposeUIResources()` delays cancellation when called off EDT because scope cancellation happens inside `invokeLater`, leaving a window for stale provider coroutines to repaint or keep the loading overlay visible.
|
||||
- Provider settings tests currently exercise Swing components directly instead of consistently using the real EDT, so they do not protect the threading contract described in `packages/kilo-jetbrains/AGENTS.md`.
|
||||
|
||||
## Implementation Plan
|
||||
1. Keep provider RPC off the EDT.
|
||||
- Continue launching provider state/action work from the configurable-owned `Dispatchers.Default` scope.
|
||||
- Do not call `KiloProviderService` from EDT.
|
||||
|
||||
2. Move all provider Swing work to the IntelliJ EDT dispatcher.
|
||||
- In `ProvidersSettingsUi.kt`, add an `edt` coroutine context using `Dispatchers.EDT + ModalityState.any().asContextElement()`.
|
||||
- Replace every `withContext(Dispatchers.Main)` with `withContext(edt)`.
|
||||
- Add `@RequiresEdt` plus a small runtime EDT check to provider UI methods that create, mutate, or read Swing state, especially `reload`, `syncLoading`, `apply`, error display, `ProvidersContent.update`, and list/search selection helpers touched by the fix.
|
||||
|
||||
3. Snapshot dialog input before background work starts.
|
||||
- In `connect()`, read `dialog.key()` and `dialog.metadata()` immediately after `showAndGet()` on EDT, then pass plain values into the coroutine.
|
||||
- In `custom()`, build `CustomProviderSaveDto` on EDT before launching `saveCustom`.
|
||||
- Keep OAuth browser and code prompt inside `withContext(edt)`.
|
||||
|
||||
4. Add stale request and disposal protection.
|
||||
- Track a monotonically increasing request token in `ProvidersSettingsUi`.
|
||||
- Increment the token when a reload or provider action starts.
|
||||
- Cancel the previous provider job when a new reload/action starts.
|
||||
- In `apply` and error handling, update the UI only when the token is still current and the UI has not been disposed.
|
||||
- Catch `CancellationException` before broad `Exception` and rethrow or return without showing an error overlay.
|
||||
|
||||
5. Fix configurable disposal ordering.
|
||||
- In `ProvidersConfigurable.disposeUIResources()`, cancel the coroutine scope immediately when disposal starts.
|
||||
- Dispose the Swing panel on EDT, setting the provider UI disposed flag and cancelling its current job.
|
||||
- Preserve the existing rule that Swing disposal itself runs on EDT.
|
||||
|
||||
6. Optionally annotate shared overlay helpers if required by the new checks.
|
||||
- Add `@RequiresEdt` to `SettingsOverlayPanel.showProgress`, `showError`, `clearProgress`, and `syncOverlay` if provider checks expose unannotated UI mutation paths.
|
||||
- Keep this minimal and avoid behavior changes outside threading guarantees.
|
||||
|
||||
7. Add focused tests.
|
||||
- Add `FakeProviderRpcApi` under `frontend/src/test/kotlin/ai/kilocode/client/testing/`, using `assertNotEdt` for every RPC method and `CompletableDeferred` gates for delayed provider state responses.
|
||||
- Update `ProvidersSettingsUiTest` so Swing creation, mutation, and inspection happen via `ApplicationManager.getApplication().invokeAndWait` plus EDT event draining, matching existing models/settings tests.
|
||||
- Add a provider UI lifecycle test that completes a provider state load and asserts the loading overlay clears and provider rows appear.
|
||||
- Add a stale response test where an older gated reload completes after a newer reload and is ignored.
|
||||
- Add a dispose test where an in-flight provider reload is cancelled or ignored after `dispose()`, with no error overlay or late UI mutation.
|
||||
|
||||
8. Add release note coverage.
|
||||
- Add a patch changeset for `@kilocode/kilo-jetbrains` unless an existing JetBrains provider settings changeset on this branch should be extended instead.
|
||||
- Suggested wording: `Fix JetBrains provider settings loading and navigation stability.`
|
||||
|
||||
## Verification
|
||||
- Run the targeted provider settings tests from `packages/kilo-jetbrains/`, for example `./gradlew test --tests ai.kilocode.client.settings.providers.ProvidersSettingsUiTest`.
|
||||
- Run `./gradlew typecheck` from `packages/kilo-jetbrains/`.
|
||||
- If the targeted changes touch shared settings base classes, run the affected settings tests as well, such as models/settings UI tests.
|
||||
|
||||
## Expected Files
|
||||
- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/settings/providers/ProvidersSettingsUi.kt`
|
||||
- `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/settings/providers/ProvidersConfigurable.kt`
|
||||
- `packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/settings/providers/ProvidersSettingsUiTest.kt`
|
||||
- `packages/kilo-jetbrains/frontend/src/test/kotlin/ai/kilocode/client/testing/FakeProviderRpcApi.kt`
|
||||
- Optional: `packages/kilo-jetbrains/frontend/src/main/kotlin/ai/kilocode/client/settings/base/SettingsOverlayPanel.kt`
|
||||
- Optional: `.changeset/<slug>.md`
|
||||
@@ -0,0 +1,73 @@
|
||||
# JetBrains Provider Settings Toolbar
|
||||
|
||||
## Goal
|
||||
|
||||
Update JetBrains provider settings to match the model settings chrome and use standard IntelliJ toolbar actions for provider refresh and adding a custom provider.
|
||||
|
||||
## Current State
|
||||
|
||||
- `ProvidersSettingsUi` currently extends `SettingsOverlayPanel` directly and places `ProvidersContent` into `content` with `BorderLayout.CENTER`.
|
||||
- `ProvidersContent` adds its own full-panel padding border, a custom text-only `JButton` refresh row, and a nested `ScrollPaneFactory.createScrollPane(list)` around the provider list.
|
||||
- `ModelsSettingsUi` goes through `BaseSettingsUi` -> `SettingsPanel`, which uses one outer `JBScrollPane` with `border = null` and `HORIZONTAL_SCROLLBAR_NEVER`.
|
||||
- `ProvidersConfigurable` already implements `Configurable.NoScroll`, so the provider screen is expected to provide its own scroll behavior rather than relying on the settings dialog wrapper.
|
||||
- The add-custom-provider flow already exists as `ProvidersSettingsUi.custom()`, but it is not wired into the visible provider toolbar.
|
||||
- IntelliJ source confirms standard add shortcuts come from `CommonShortcuts.getNewForDialogs()` / action id `NewElement`, and standard refresh shortcuts come from action id `Refresh`. Platform refresh icon is `AllIcons.Actions.Refresh`; plus/add icon is available as `AllIcons.General.Add`.
|
||||
|
||||
## Implementation Plan
|
||||
|
||||
1. Align provider settings with the model settings scroll/container pattern.
|
||||
- Change `ProvidersSettingsUi` to extend `SettingsPanel` instead of `SettingsOverlayPanel`.
|
||||
- In `init`, call `setContent(view)` instead of adding `view` directly to `content`.
|
||||
- Keep `ProvidersConfigurable : Configurable.NoScroll` unchanged so the provider settings UI remains responsible for its own scrolling.
|
||||
- Keep loading/error overlay behavior through `SettingsPanel` inheritance.
|
||||
|
||||
2. Remove the nested list scrollpane and list border chrome.
|
||||
- Change `ProvidersContent` to extend `BaseContentPanel` or otherwise reuse the same visual pattern used by model settings content.
|
||||
- Remove the current full-panel `border = JBUI.Borders.empty(...)` on `ProvidersContent`.
|
||||
- Remove `ScrollPaneFactory.createScrollPane(list)` and the list-specific scrollpane policy.
|
||||
- Add the provider `JBList` directly to the content area so the outer `SettingsPanel` `JBScrollPane` is the only scrollbar.
|
||||
- Preserve `ScrollingUtil.installActions(list)`, selection handling, search keyboard navigation, section headers, and row action hit testing.
|
||||
|
||||
3. Replace the ad hoc refresh button row with an IntelliJ action toolbar.
|
||||
- Remove the `Stack.horizontal` top row and `JButton("Refresh")`.
|
||||
- Add two local `DumbAwareAction`/`AnAction` classes or private action instances in `ProvidersSettingsUi.kt`:
|
||||
- refresh provider settings: text/description from bundle, icon `AllIcons.Actions.Refresh`, invokes `reload()`.
|
||||
- add custom provider: text/description from bundle, icon `AllIcons.General.Add`, invokes `custom()`.
|
||||
- Build a `DefaultActionGroup` with add then refresh, and create a horizontal toolbar via `ActionManager.getInstance().createActionToolbar(ActionPlaces.TOOLBAR, group, true)`.
|
||||
- Set `toolbar.targetComponent = this` or the provider content panel so data context and shortcut handling are scoped to the settings page.
|
||||
- Put the toolbar at the top of `ProvidersContent`, alongside or just above the search field, using existing Swing layout/`Stack` patterns and without adding extra decorative borders.
|
||||
|
||||
4. Install standard IntelliJ shortcuts on those provider actions.
|
||||
- Add action shortcut for add using `CommonShortcuts.getNewForDialogs()` to match dialog/list add behavior (`NewElement`, typically `Alt+Insert`/mac equivalent but keymap-aware).
|
||||
- Add action shortcut for refresh using `ActionManager.getInstance().getAction("Refresh")?.shortcutSet` when available.
|
||||
- Register both shortcut sets on the provider content root via `registerCustomShortcutSet(...)` so shortcuts work when focus is in the search field or list.
|
||||
- Keep existing `Enter`, up, and down behavior for search/list navigation.
|
||||
|
||||
5. Update bundle strings only if needed.
|
||||
- Reuse `settings.providers.addCustom` and `settings.providers.refresh` for action text.
|
||||
- Add descriptions such as `settings.providers.addCustom.description` and `settings.providers.refresh.description` only if the action constructors/tooltips need distinct descriptions.
|
||||
- Keep all user-visible strings in `KiloBundle.properties`.
|
||||
|
||||
6. Update frontend tests in `ProvidersSettingsUiTest`.
|
||||
- Replace the current assertion that the north area contains a text `JButton("Refresh")`.
|
||||
- Add assertions that the provider content contains one `SearchTextField`, one `JBList<ProviderListRow>`, no nested `JScrollPane` dedicated to the list, and an action toolbar/button area with add and refresh icons/actions.
|
||||
- Add a behavior test that triggers the refresh toolbar action and verifies `reload()`/state RPC is called again, using the existing `FakeProviderRpcApi` flow.
|
||||
- Add a behavior test for the add-custom action only if it can be exercised without opening a modal dialog; otherwise validate action presence/shortcut registration and leave dialog flow unchanged.
|
||||
- Keep existing renderer, row ordering, metadata, and stale reload tests intact.
|
||||
|
||||
7. Verification.
|
||||
- From `packages/kilo-jetbrains/`, run the focused provider UI tests:
|
||||
- `./gradlew :frontend:test --tests ai.kilocode.client.settings.providers.ProvidersSettingsUiTest`
|
||||
- Run JetBrains typecheck:
|
||||
- `./gradlew typecheck`
|
||||
- If tests expose brittle toolbar internals, prefer behavior assertions over production-only test accessors.
|
||||
|
||||
## Expected Outcome
|
||||
|
||||
Provider settings uses the same clean outer scrollpane and borderless settings chrome as model settings, with a standard IntelliJ toolbar containing plus/add and refresh icons. The toolbar actions use standard IntelliJ add and refresh shortcuts, and the existing provider connection/list behavior remains unchanged.
|
||||
|
||||
## Risks
|
||||
|
||||
- Removing the nested list scrollpane means the provider list height will contribute to the outer settings page height. This is intended for parity with model settings, but should be checked with large provider lists.
|
||||
- Toolbar button component classes are IntelliJ implementation details, so tests should avoid depending on exact toolbar child classes where possible.
|
||||
- The custom-provider dialog is modal, so automated add-action testing should avoid invoking the dialog unless there is already a safe test seam.
|
||||
Reference in New Issue
Block a user