The workbench page reached back into resource.raw with brand-specific parsing to derive model names and priority, bypassing the adapter layer. Normalize both in the adapters and consume the typed fields.
isRecord was declared locally in 15 modules (with two divergent shapes) and getErrorMessage in 7. Move a single canonical pair into utils/helpers and import it everywhere. The shared isRecord excludes arrays; the call sites that previously allowed them only read named properties, so behavior is unchanged.
snapshot.issues was always empty and group.issue always null, so the issue badges and warning panels could never render. group.path / PROVIDER_PATHS were written but never read. Strip the fields, their render branches and the orphan styles.
- Implemented PluginStorePage component for displaying available plugins.
- Added functionality to install and update plugins with user confirmation.
- Integrated plugin store API for fetching plugin data and handling installations.
- Enhanced PluginsPage to navigate to the new Plugin Store.
- Updated localization files for new plugin store strings in English, Russian, and Chinese.
- Added new types for plugin store entries and responses in TypeScript.
- Improved UI components and styles for better user experience in the plugin store.
- Introduced new plugin management section in the application.
- Added translations for English, Russian, Simplified Chinese, and Traditional Chinese.
- Created new API endpoints for managing plugins, including listing, enabling/disabling, and configuring plugins.
- Updated routing to include a dedicated PluginsPage.
- Defined new types for plugin configuration and metadata in TypeScript.
- Enhanced the existing API client to handle plugin-related requests.
feat(authFiles): refactor OAuth provider handling and improve alias validation
feat(config): implement unsaved changes guard in ConfigPage
feat(dashboard): extend provider stats to include vertex and ampcode
fix(logs): enhance incremental log merging and improve error handling
feat(system): format build time display using utility functions
feat(api): normalize OAuth provider keys in authFiles API
feat(provider): update OpenAI provider deletion method to use index
feat(format): add date formatting utilities for better date handling
The backend OpenAICompatibilityAPIKey struct only has api-key and
proxy-url (internal/config/config.go:570-577). PUT /openai-compatibility
unmarshals into this struct (config_lists.go:439) and discards any
unknown field, so per-entry headers submitted from the UI never persist
and never reach the runtime request path. The GET response wrapper
also has no headers field (config_auth_index.go:31-34).
Drop the per-entry headers input from BaseProviderForm and the
ApiKeyEntry/ApiKeyEntryInput types. Connectivity test and model
discovery stop reading entry-level headers — they continue to apply
provider-level headers, which is the supported contract.
Prepending new API key entries shifted all existing indices, causing useConnectivityTest index-based status tracking to desync.
Fix: keep array operations as append (stable indices) and reverse the rendering order so new entries appear at the top visually. Use realIdx for status lookups, React keys, and all updateField operations.
Add eye toggle button for all API key input fields so users can verify their keys before saving.
Changes:
- BaseProviderForm: wrap single API key field (Gemini/Codex/Claude/Vertex) with passwordField + toggle button
- BaseProviderForm: wrap per-entry API key fields (OpenAI-compatible) with passwordField + toggle button
- BaseProviderForm: extract applyRawApiKey helper to populate apiKey from resource.raw in edit mode
- BaseProviderForm: sync initialFormSignature with applyRawApiKey to prevent false isDirty
- sharedForm.module.scss: add .passwordField, .passwordInput, .passwordToggle styles
- i18n: add showApiKey/hideApiKey keys to en, zh-CN, zh-TW, ru locale files
- accessibility: add aria-label and title attributes on both toggle buttons
Previously confirmDiscardIfDirty only fired for Sheet-driven close paths
(Cancel button, backdrop, escape). Clicking a different provider in the
left rail bypassed the prompt and silently dropped the form changes.
ProviderSheet now exposes the existing guard via an imperative handle,
which the workbench page calls before swapping the active brand.
- useModelDiscovery: openaiCompatibility branch now forwards
resolvedAuthIndex into modelsApi.fetchModelsViaApiCall, mirroring the
gemini/codex/claude branches. Configs that rely on a backend-resolved
auth index without a local plaintext key can now list models.
- useConnectivityTest: runOpenAIKey accepts an authIndex fallback when
entry.apiKey is empty, sets Authorization: Bearer $TOKEN$ in that
case, and forwards authIndex on the /api-call request so the gateway
can substitute the upstream token.
- useModelDiscovery: reset stale results when baseUrl / apiKey /
apiKeyEntries / headers / authIndex change, so reopening the discovery
panel after editing fields re-fetches instead of showing the previous
endpoint's models.
fix(provider): update button icon styles in ProviderHeaderCard
style(provider): enhance layout and typography in ProviderHeaderCard and ProviderResourcePanel
fix(provider): update status styles in ProviderResourceTable and sharedForm
The detail metadata section was rendering authIndex with the
status.notAvailable label (zh: "不可用"), which reads like the
provider doesn't support the feature at all. Every provider supports
authIndex; it's just absent until you link an auth file. Reuse the
status.notSet label so the wording matches baseUrl / proxyUrl and
makes it clear that the entry simply doesn't have a value yet.
The form's apiKey field is intentionally empty in edit mode (the
placeholder reads "leave blank to keep unchanged"), so model discovery
and the Claude connectivity test were sending requests with no
credentials and getting 401 for Codex / Claude / Gemini providers.
- BaseProviderForm derives a fallbackAuthIndex from resource.raw.authIndex
alongside the existing fallbackApiKey and passes both into the
discovery and connectivity hooks
- modelsApi.fetch{V1,Claude,Gemini,}ModelsViaApiCall accept an optional
authIndex and forward it on apiCallApi.request so the backend can
inject the OAuth/auth-file token for providers that store the key
externally
- useModelDiscovery threads the authIndex through to every brand
- useConnectivityTest threads authIndex through the Claude path and no
longer rejects the request as "API key required" when an authIndex is
available
Replace the free-form test-model input on the OpenAI-compatible and
Claude edit sheets with a Select whose options are derived from the
already-added models. An Auto entry (annotated with the first model
name) maps to an empty form.testModel so connectivity tests keep
falling back to the first configured model when the user hasn't made
an explicit pick. If an existing config carried a test model that
isn't in form.models, surface it as a custom option so the value is
preserved instead of silently dropped.
- BaseProviderForm and AmpcodeForm now snapshot the initial form
signature on mount and report dirty/clean state to the parent via a
new onDirtyChange callback
- ProviderSheet tracks the form's dirty flag and wraps the cancel
button plus the new Sheet.confirmClose hook with a confirmation
prompt; the prompt is skipped during submit and in detail mode
- Sheet gains an optional confirmClose hook so that Escape, overlay
click and the close button all route through the same async check
before starting the close animation
- Add i18n keys (en/zh-CN/zh-TW/ru) for the unsaved-changes prompt
When the OpenAI-compatible endpoint rejects the first authenticated
/models request (some upstreams expose the route unauthenticated, or
the configured key only covers chat completions), retry once with no
key and no custom headers before surfacing the original error so the
discovery panel can populate.
ModelDiscoveryPanel now hands the full ModelInfo objects to onApply
instead of just the model names, and BaseProviderForm copies each
incoming alias into the new form.models row so suggestions from
endpoints that return alias metadata are no longer silently dropped.
- Wire useProviderRecentRequests into ProvidersWorkbenchPage and pass
usageByProvider down to the resource table; the header refresh now
refreshes recent requests in parallel with the provider snapshot
- ProviderResourceTable status column hosts the existing status badge
plus an inline ProviderStatusBar driven by the recent-requests usage;
column widths rebalanced so the bar fits on a single row
- New OpenAIBrandToolbar gives the OpenAI-compatible brand a sort
control (name / priority / recent-success with direction) and a
multi-select model filter dropdown; ProviderResourcePanel renders it
via the new openaiControls prop
- ProvidersWorkbenchPage computes the available model union, sorts /
filters resources when the OpenAI brand is active, and resets the
selected models when switching brands
- Add providerStatusBar.module.scss for the in-row status bar styling
and i18n keys (en/zh-CN/zh-TW/ru) for the new toolbar