feat: Providers page with backend rework -- health, CRUD, subscription auth#893
feat: Providers page with backend rework -- health, CRUD, subscription auth#893
Conversation
…n auth (#785) Rework the provider system for best-in-class UX: Backend: - Add `litellm_provider` field to decouple display name from LiteLLM routing - Add `AuthType.SUBSCRIPTION` for OAuth bearer tokens (Claude Pro/Max) - Add `subscription_token` + `tos_accepted_at` fields (encrypted at rest) - Expand presets from 4 to 11 (Anthropic, OpenAI, Google AI, Mistral, Groq, DeepSeek, Azure + existing local presets) - Add `supported_auth_types` to presets for multi-auth UI - Update LiteLLM driver routing to use `litellm_provider` fallback - Handle SUBSCRIPTION auth via `extra_headers` (Authorization: Bearer) - Refactor ProviderConfig validation to table-driven pattern - Update DTOs, management helpers, and response serialization Frontend: - Full Providers page with health status, CRUD, model listing - Provider detail page with health metrics, model table, test connection - Guided multi-step creation: preset picker -> auth config -> name/URL - Subscription auth flow with ToS warning dialog - ProviderHealthBadge shared component (up/degraded/down) - Zustand store with parallel health fetching, optimistic deletes - Data hooks with 30s polling (no WebSocket per design spec) - Provider utilities (normalize, filter, sort, format) - Storybook stories for all new sub-components - Page-level and utility unit tests Related: #887 (multi-provider model resolution, follow-up) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Pre-reviewed by 6 agents, 26 findings addressed: - subscription_token: str -> NotBlankStr (prevents empty bearer tokens) - Add ProviderPreset validators (auth_type in supported, min_length=1) - Add AuthType.NONE to _AUTH_OWNED_FIELDS for explicitness - Remove vendor name from schema docstring - Trim DTO docstrings to bring dto.py under 800 lines (822 -> 764) - Add CLAUDE.md exception for vendor names in provider presets - Fix stale getState() in ProvidersPage render path (use selector) - Add aria-labels to filter inputs and preset picker buttons - Add stale-request guard + health error logging to fetchProviders - Defer listLoading: false until health fetch completes - Fix misleading "optimistic" comment in deleteProvider - Remove dead catch block in ProviderFormDrawer - Add form reset on mode switch in ProviderFormDrawer - Add preset cache documentation comment - Update operations.md: 5 auth types, 11 presets, litellm_provider, subscription_token/tos_accepted_at fields - Add ProviderHealthBadge to component inventories (brand-and-ux.md, CLAUDE.md) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
WalkthroughThis PR adds subscription-based authentication and LiteLLM routing across backend and frontend. Backend: new Suggested labels
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. Comment |
Dependency Review✅ No vulnerabilities or license issues or OpenSSF Scorecard issues found.Snapshot WarningsEnsure that dependencies are being submitted on PR branches. Re-running this action after a short time may resolve the issue. See the documentation for more information and troubleshooting advice. Scanned FilesNone |
There was a problem hiding this comment.
Code Review
This pull request implements a comprehensive provider management system, introducing a new SUBSCRIPTION authentication type and a litellm_provider routing key to decouple display names from LiteLLM identifiers. The backend updates include enhanced configuration schemas, validation logic for credential safety, and an expanded library of provider presets. On the frontend, new pages and components are added to support provider listing, detailed health metrics, and a streamlined creation/editing workflow managed via a Zustand store. Review feedback suggests correcting a potential typo in a model ID and restoring detailed attribute descriptions in DTO docstrings to improve long-term maintainability.
src/synthorg/api/dto.py
Outdated
| custom_header_value: Custom auth header value. | ||
| models: Model configurations. | ||
| """ | ||
| """Payload for creating a new provider.""" |
There was a problem hiding this comment.
The docstrings for CreateProviderRequest and other DTOs in this file (UpdateProviderRequest, ProviderResponse, CreateFromPresetRequest) have been shortened, removing the detailed attribute descriptions that were present before. While this makes the code more compact, it removes valuable inline documentation that helps developers understand each field's purpose and constraints. For long-term maintainability, it's better to have these attribute descriptions. Please consider restoring them.
| max_context=200_000, | ||
| ), | ||
| ProviderModelConfig( | ||
| id="claude-haiku-4-5-20251001", |
There was a problem hiding this comment.
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #893 +/- ##
=======================================
Coverage 92.16% 92.16%
=======================================
Files 592 592
Lines 31174 31216 +42
Branches 3029 3031 +2
=======================================
+ Hits 28730 28770 +40
- Misses 1930 1932 +2
Partials 514 514 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
Actionable comments posted: 23
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/synthorg/providers/management/service.py (1)
390-404:⚠️ Potential issue | 🟠 MajorApply the auth override before deciding on preset auto-discovery.
Line 390 calls
_maybe_discover_preset_models()before Line 396 resolves the effectiveauth_type. With the new override path, a preset switched toAuthType.NONEwill never auto-discover, while aNONEpreset switched to another auth mode is still probed as if it were unauthenticated.Suggested fix
- models = await self._maybe_discover_preset_models( + auth_type = request.auth_type or preset.auth_type + models = await self._maybe_discover_preset_models( preset, base_url, models, + auth_type=auth_type, ) - - auth_type = request.auth_type or preset.auth_type create_request = CreateProviderRequest( name=request.name, driver=preset.driver, litellm_provider=preset.litellm_provider, auth_type=auth_type, @@ async def _maybe_discover_preset_models( self, preset: ProviderPreset, base_url: str | None, models: tuple[ProviderModelConfig, ...], + *, + auth_type: AuthType, ) -> tuple[ProviderModelConfig, ...]: - if models or preset.auth_type != AuthType.NONE or not base_url: + if models or auth_type != AuthType.NONE or not base_url: return models🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/synthorg/providers/management/service.py` around lines 390 - 404, Calculate the effective auth_type (auth_type = request.auth_type or preset.auth_type) before calling _maybe_discover_preset_models and pass that resolved auth context into the discovery call (or adjust preset/auth args) so discovery uses the overridden auth mode; specifically move the auth_type resolution above the call to _maybe_discover_preset_models and ensure CreateProviderRequest still uses that resolved auth_type when constructing the provider (symbols: _maybe_discover_preset_models, auth_type, request.auth_type, preset.auth_type, CreateProviderRequest).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@docs/design/operations.md`:
- Line 121: Update the docs to remove vLLM from the list of auto-probed local
providers and explicitly state that vLLM is not auto-detected because
_VLLM.candidate_urls is intentionally empty; change the sentence describing
"Preset auto-probe" (the POST /api/v1/providers/probe-preset entry) to say it
probes only Ollama and LM Studio (using candidate_urls) with the 5-second
timeout and note that vLLM requires a manual URL configuration instead of being
auto-probed.
In `@src/synthorg/config/schema.py`:
- Around line 91-94: The docstring for the litellm_provider config contains real
vendor example names; replace any concrete vendor/model names (e.g.,
"anthropic", "openai", "claude", "gpt"-style tokens) with the project-approved
generic placeholders used elsewhere such as "example-provider" for provider and
"example-large-001", "example-medium-001", "example-small-001" (or the simple
aliases "large"/"medium"/"small"); update the examples in the litellm_provider
description (and the other example block referenced around the same area) so
they only use those generic placeholders while preserving the explanatory text
and behavior.
In `@src/synthorg/providers/management/_helpers.py`:
- Around line 28-35: The code currently sets tos_accepted_at whenever
request.tos_accepted is true; change this to only stamp consent for
subscription-based providers by setting tos_accepted_at = datetime.now(UTC) only
when request.tos_accepted is true AND request.auth_type == AuthType.SUBSCRIPTION
(or the equivalent enum/value used in your code), otherwise set it to None;
update the logic where tos_accepted_at is created (the tos_accepted_at local and
the ProviderConfig construction) so non-subscription auth types cannot carry a
stale consent timestamp.
In `@src/synthorg/providers/presets.py`:
- Around line 64-279: The diff exposes hardcoded vendor/model literals in
ProviderPreset instances (_ANTHROPIC, _OPENAI, _GEMINI, _MISTRAL, _GROQ,
_DEEPSEEK, _AZURE_OPENAI, _OLLAMA, _LM_STUDIO, _VLLM, _OPENROUTER and the
PROVIDER_PRESETS tuple); move all vendor-specific strings (vendor names,
display_name, description, model ids, aliases, litellm_provider,
default_base_url, candidate_urls) into a single approved adapter/constants
boundary (e.g., a new module like provider_adapters or provider_constants) and
replace the values in src/synthorg/providers/presets.py with generic
placeholders (example-provider, example-large-001, example-medium-001,
example-small-001, large/medium/small aliases, and generic
descriptions/default_urls), ensuring ProviderPreset and ProviderModelConfig
usage stays the same and importing concrete vendor literals only from the new
adapter module where interop truly requires them.
In `@tests/unit/config/test_provider_auth.py`:
- Around line 121-160: Replace vendor-specific fixtures in the tests by using
neutral test strings: update tests that construct ProviderConfig (e.g.,
test_subscription_valid, test_subscription_missing_token_raises,
test_subscription_missing_tos_raises, test_subscription_missing_both_raises,
test_litellm_provider_field) to use generic tokens and provider names (for
example "test-provider" or "test-small-001") instead of
"sk-ant-oat01-test-token" and "anthropic"; keep all assertions and validation
expectations the same and only change the literal values passed to
subscription_token and litellm_provider to the neutral fixtures, referencing
ProviderConfig, AuthType.SUBSCRIPTION and _TOS_ACCEPTED.
In `@web/src/components/ui/provider-health-badge.stories.tsx`:
- Around line 4-8: Update the Storybook meta for ProviderHealthBadge to add the
accessibility gate: modify the existing meta object (the const named meta for
ProviderHealthBadge) to include a parameters property with a11y.test set to true
(e.g., parameters: { a11y: { test: true } }) so Storybook 10 will enforce WCAG
checks for this component; keep the existing title, component, and tags
unchanged and merge the new parameters into the same meta object.
In `@web/src/components/ui/provider-health-badge.tsx`:
- Around line 17-22: Change ProviderHealthBadge to be a thin wrapper that
composes the existing StatusBadge component instead of hand-rolling dot markup:
export the props as "export interface ProviderHealthBadgeProps" matching the
current fields (status, label?, pulse?, className?), and have the
ProviderHealthBadge component accept ProviderHealthBadgeProps and forward them
to StatusBadge (mapping status to StatusBadge's status prop, passing
label/pulse/className through). Update the file to import StatusBadge and remove
inline dot markup; ensure the component uses design tokens and add/export the
props type so other modules can consume it.
In `@web/src/pages/ProviderDetailPage.tsx`:
- Around line 16-18: The decodedName assignment redundantly calls
decodeURIComponent on a value returned by useParams (providerName is already
decoded); change uses of decodedName to use providerName directly or remove
decodeURIComponent so decodedName = providerName ?? '' (or eliminate decodedName
and reference providerName where needed). Update any references in
ProviderDetailPage to use the param variable (providerName) instead of
re-decoding it.
In `@web/src/pages/providers/PresetPicker.tsx`:
- Around line 25-48: The JSX for each preset option inside presets.map in
PresetPicker.tsx is too long and duplicated for the Custom button; extract it
into a new PresetOptionCard component (e.g., PresetOptionCard) that accepts
props like name, display_name, description, selected, and onSelect and
encapsulates the button markup, aria attributes, className logic
(pressed-state), Server icon and labels; replace the inline button in
presets.map and the separate Custom button with calls to PresetOptionCard to
keep .map() JSX under 8 lines and centralize styling/behavior.
In `@web/src/pages/providers/ProviderCard.tsx`:
- Around line 18-24: ProviderCard is reimplementing the standard
card-with-header styling and hardcoding a hover shadow; replace the outer div
with the shared SectionCard component (use SectionCard instead of the
hand-rolled div) and remove the custom shadow class
(shadow-[0_4px_24px_var(--so-accent-8)]) and hardcoded hover rules, relying on
the dashboard tokens (e.g., var(--so-shadow-card-hover)) and border tokens
(border-border / border-bright) so spacing, borders, and hover treatment match
other cards; update usage of className props to pass through to SectionCard and
apply any additional layout classes there (affects ProviderCard component and
its wrapper around the header/body).
In `@web/src/pages/providers/ProviderDetailSkeleton.tsx`:
- Around line 21-26: ProviderDetailSkeleton is manually recreating a
card-with-header using a div and Skeleton elements; replace that manual block
with the shared SectionCard component: remove the outer div (the rounded-lg
border bg-card block) and wrap the skeleton elements inside <SectionCard> so the
header slot contains the header Skeleton (Skeleton className="h-5 w-24 mb-4")
and the body/children contain the list Skeleton rows (the Array.from 3 rows).
Keep the same Skeleton components and keys but move them into SectionCard’s
header/body slots to preserve styling and behavior (update imports to include
SectionCard if not already).
In `@web/src/pages/providers/ProviderFilters.tsx`:
- Around line 31-38: Replace the native <input> and any native <select> usage in
ProviderFilters.tsx with the shared UI primitives from web/src/components/ui/
(e.g., the Input and Select components) so styling, focus behavior, and sizing
are consistent; specifically, change the search box that currently uses
value={searchQuery} and onChange={(e) => setSearchQuery(e.target.value)} to use
the shared Input component wiring its value and onChange handler (call
setSearchQuery with the component’s onChange value), and swap any provider
filter selects in the 40-64 region to the shared Select primitive while
preserving current props (placeholder/aria-label/className semantics) and state
handlers.
In `@web/src/pages/providers/ProviderFormDrawer.tsx`:
- Around line 12-18: The auth selector exposes unsupported options causing
broken configs; update AUTH_OPTIONS in ProviderFormDrawer to remove or hide the
'oauth' and 'custom_header' entries (leave 'api_key', 'subscription', and
'none'), and ensure the selector/control that reads AUTH_OPTIONS (in
ProviderFormDrawer) uses the updated array so only supported auth modes can be
selected until the drawer is extended to collect and submit oauth_* and
custom_header_* fields.
- Around line 148-155: The edit form currently never signals intent to remove
persisted secrets or overrides when a field is cleared; update the payload sent
to useProvidersStore.getState().updateProvider(provider.name, ...) to set
clear_api_key: true when auth_type !== 'api_key' or when apiKey is empty, and
clear_subscription_token: true when auth_type !== 'subscription' or when
subscriptionToken is empty; additionally, when the user clears litellmProvider
or baseUrl in the form, send explicit null (or the API's designated "clear"
sentinel) for litellm_provider and base_url instead of undefined so the backend
will remove those overrides; adjust the logic around api_key and
subscription_token to only send their values when present and set the
corresponding clear_* flags otherwise.
- Around line 72-82: The effect that syncs form fields from preset currently
returns early when preset is undefined, leaving previous preset values when the
user picks the “__custom__” option; update the useEffect that references preset
(and/or selectedPreset) so that when preset is falsy AND selectedPreset ===
"__custom__" it explicitly resets preset-derived state by calling setName(''),
setAuthType(''), setBaseUrl(''), setLitellmProvider('') (and also clear
setTosAccepted(false), setSubscriptionToken(''), setApiKey('')), otherwise keep
the existing behavior of populating fields when preset exists.
- Around line 247-256: The Base URL input is hidden when
preset?.default_base_url === null even though some presets (like Azure) use null
to force the user to enter a URL; update the visibility logic around the
InputField so the field is shown whenever a preset is selected (preset != null),
when isCustom is true, or when mode === 'edit' (replace the current condition
(isCustom || preset?.default_base_url !== null || mode === 'edit') with
(isCustom || preset != null || mode === 'edit')), and adjust the hint logic to
treat preset?.default_base_url === null as "user must enter URL" (i.e., do not
display the 'Optional for known cloud providers' hint when
preset?.default_base_url === null).
In `@web/src/pages/providers/ProviderGridView.tsx`:
- Around line 34-49: The JSX inside providers.map in ProviderGridView is too
large—extract it into a small child component (e.g., ProviderGridItem or
ProviderCardLink) that accepts props { provider, health } and returns the
current StaggerItem > Link > ProviderCard structure using ROUTES.PROVIDER_DETAIL
with encodeURIComponent(provider.name) and passing healthMap[provider.name] (or
null) through; then replace the inline map body with a single <ProviderGridItem
key={provider.name} provider={provider} health={healthMap[provider.name] ??
null} /> to keep ProviderGridView readable and comply with the inline-JSX rule.
In `@web/src/pages/providers/ProviderHealthMetrics.tsx`:
- Around line 10-12: The code computes lastCheck from
health.last_check_timestamp using toLocaleTimeString(), which omits the date;
update the calculation in ProviderHealthMetrics (the lastCheck variable
assignment that reads health.last_check_timestamp) to format both date and time
(for example using new Date(health.last_check_timestamp).toLocaleString() or an
equivalent date+time formatter) so the rendered last-check shows full date and
time instead of only the time.
In `@web/src/pages/providers/ProviderModelList.tsx`:
- Around line 32-53: Extract the JSX inside models.map in ProviderModelList into
a new small functional component named ProviderModelRow that accepts a single
prop (model: ProviderModel or the existing model type); move the <tr> markup and
all uses of model.id, model.alias, model.max_context, model.cost_per_1k_input,
and model.cost_per_1k_output into that component (preserve className and
formatting like (model.max_context / 1000).toFixed(0) and toFixed(4) for costs),
render ProviderModelRow inside the map using <ProviderModelRow key={model.id}
model={model} /> and export/import the component as needed so the map body in
ProviderModelList is a single-line component call.
In `@web/src/pages/providers/TestConnectionResult.tsx`:
- Around line 13-33: The async result banner is not announced to assistive tech
and error text can be clipped; update the banner container (the div using
cn(...)) to be a polite live region (add aria-live="polite" and
aria-atomic="true") so screen readers announce updates, and in the failure
branch replace the truncating span (the span with className="truncate") with a
wrapping-friendly style (remove "truncate" and use classes that allow wrapping,
e.g., whitespace-normal or no text-truncation) so the full error (result.error)
is visible and readable; keep existing icons (CheckCircle2/XCircle) and use
result/model_tested/formatLatency logic unchanged.
In `@web/src/pages/ProvidersPage.tsx`:
- Around line 16-17: The deleteTarget state is never set so the confirm dialog
flow is dead; either remove the unused delete flow or wire an onDelete handler
into ProviderGridView that calls setDeleteTarget(providerName). Update
ProviderGridView usage to pass onDelete={setDeleteTarget} (or a wrapper that
calls setDeleteTarget(id)), ensure the confirm dialog component reads
deleteTarget and the confirm action calls the delete routine then
setDeleteTarget(null) (and cancel should also clear it), and remove any
unreachable delete-target code if you prefer to delete the flow instead of
wiring it.
In `@web/src/stores/providers.ts`:
- Around line 331-351: Add a loading flag for discoverModels similar to
testingConnection: introduce a boolean state property (e.g., discoveringModels)
and set it true at the start of the discoverModels action and false in both
success and error paths; update useToastStore and the early fetchProviderDetail
call to run while awaiting the API, and ensure discoverModels (the async
function) returns after resetting discoveringModels so the UI can reliably
show/hide a spinner during apiDiscoverModels, fetching provider detail via
fetchProviderDetail, and on error paths that call getErrorMessage.
In `@web/src/utils/providers.ts`:
- Around line 91-96: In the 'health' sort case, replace the fallback -1 used for
unknown health with a trailing-bucket value so "unknown" sorts after "down":
compute a constant UNKNOWN_ORDER = Math.max(...Object.values(HEALTH_ORDER)) + 1
(or derive from HEALTH_ORDER length) and use it for oa and ob fallback instead
of -1 (change oa/ob assignment in the case 'health' block that reads const oa =
ha ? HEALTH_ORDER[ha] : -1 and const ob = hb ? HEALTH_ORDER[hb] : -1), then
return dir * (oa - ob) as before.
---
Outside diff comments:
In `@src/synthorg/providers/management/service.py`:
- Around line 390-404: Calculate the effective auth_type (auth_type =
request.auth_type or preset.auth_type) before calling
_maybe_discover_preset_models and pass that resolved auth context into the
discovery call (or adjust preset/auth args) so discovery uses the overridden
auth mode; specifically move the auth_type resolution above the call to
_maybe_discover_preset_models and ensure CreateProviderRequest still uses that
resolved auth_type when constructing the provider (symbols:
_maybe_discover_preset_models, auth_type, request.auth_type, preset.auth_type,
CreateProviderRequest).
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: 1f73a807-f982-4358-b7a4-e5ec6cfd5229
📒 Files selected for processing (42)
CLAUDE.mddocs/design/brand-and-ux.mddocs/design/operations.mdsrc/synthorg/api/dto.pysrc/synthorg/config/schema.pysrc/synthorg/providers/drivers/litellm_driver.pysrc/synthorg/providers/enums.pysrc/synthorg/providers/management/_helpers.pysrc/synthorg/providers/management/service.pysrc/synthorg/providers/presets.pytests/unit/config/test_provider_auth.pytests/unit/providers/management/test_service_discovery.pyweb/src/__tests__/pages/ProviderDetailPage.test.tsxweb/src/__tests__/pages/ProvidersPage.test.tsxweb/src/__tests__/utils/providers.test.tsweb/src/__tests__/utils/setup-validation.property.test.tsweb/src/__tests__/utils/setup-validation.test.tsweb/src/api/endpoints/providers.tsweb/src/api/types.tsweb/src/components/ui/provider-health-badge.stories.tsxweb/src/components/ui/provider-health-badge.tsxweb/src/hooks/useProviderDetailData.tsweb/src/hooks/useProvidersData.tsweb/src/pages/ProviderDetailPage.tsxweb/src/pages/ProvidersPage.tsxweb/src/pages/providers/PresetPicker.stories.tsxweb/src/pages/providers/PresetPicker.tsxweb/src/pages/providers/ProviderCard.stories.tsxweb/src/pages/providers/ProviderCard.tsxweb/src/pages/providers/ProviderDetailHeader.tsxweb/src/pages/providers/ProviderDetailSkeleton.tsxweb/src/pages/providers/ProviderFilters.tsxweb/src/pages/providers/ProviderFormDrawer.tsxweb/src/pages/providers/ProviderGridView.tsxweb/src/pages/providers/ProviderHealthMetrics.tsxweb/src/pages/providers/ProviderModelList.tsxweb/src/pages/providers/ProvidersSkeleton.tsxweb/src/pages/providers/TestConnectionResult.stories.tsxweb/src/pages/providers/TestConnectionResult.tsxweb/src/stores/providers.tsweb/src/utils/provider-status.tsweb/src/utils/providers.ts
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (7)
- GitHub Check: Dashboard Test
- GitHub Check: Test (Python 3.14)
- GitHub Check: Build Backend
- GitHub Check: Build Sandbox
- GitHub Check: Build Web
- GitHub Check: Dependency Review
- GitHub Check: Analyze (python)
🧰 Additional context used
📓 Path-based instructions (11)
**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
**/*.py: No 'from future import annotations' -- Python 3.14 has PEP 649
Use PEP 758 except syntax: 'except A, B:' (no parentheses) in Python 3.14
Add type hints to all public functions; enforce mypy strict mode
Use Google-style docstrings on all public classes and functions; enforce via ruff D rules
Create new objects rather than mutating existing ones; use copy.deepcopy() at construction for non-Pydantic internal collections and MappingProxyType wrapping for read-only enforcement
Distinguish config (frozen Pydantic models) from runtime state (mutable-via-copy models using model_copy); never mix static config fields with mutable runtime fields
Use Pydantic v2 (BaseModel, model_validator, computed_field, ConfigDict); use@computed_fieldfor derived values instead of redundant stored fields
Use NotBlankStr (from core.types) for all identifier/name fields in Pydantic models, including optional and tuple variants, instead of manual whitespace validators
Prefer asyncio.TaskGroup for fan-out/fan-in parallel operations in new code; use structured concurrency over bare create_task
Enforce 88-character line length via ruff
Keep functions under 50 lines; keep files under 800 lines
Handle errors explicitly; never silently swallow exceptions
Validate at system boundaries: user input, external APIs, config files
Files:
src/synthorg/providers/enums.pytests/unit/providers/management/test_service_discovery.pysrc/synthorg/providers/management/service.pytests/unit/config/test_provider_auth.pysrc/synthorg/providers/drivers/litellm_driver.pysrc/synthorg/providers/management/_helpers.pysrc/synthorg/config/schema.pysrc/synthorg/providers/presets.pysrc/synthorg/api/dto.py
src/synthorg/**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
src/synthorg/**/*.py: Every module with business logic must import and use logger from synthorg.observability.get_logger(name); variable name must always be 'logger'
Use constants from domain-specific modules under synthorg.observability.events for event names; import directly from events.
Always use structured logging: 'logger.info(EVENT, key=value)'; never use 'logger.info("msg %s", val)'
All error paths must log at WARNING or ERROR with context before raising
All state transitions must log at INFO level
Use DEBUG logging for object creation, internal flow, and entry/exit of key functions
Never use real vendor names (Anthropic, OpenAI, Claude, GPT, etc.) in project-owned code, docstrings, comments, tests, or config examples; use generic names: example-provider, example-large-001, example-medium-001, example-small-001, large/medium/small as aliases
Files:
src/synthorg/providers/enums.pysrc/synthorg/providers/management/service.pysrc/synthorg/providers/drivers/litellm_driver.pysrc/synthorg/providers/management/_helpers.pysrc/synthorg/config/schema.pysrc/synthorg/providers/presets.pysrc/synthorg/api/dto.py
src/synthorg/!(observability)/**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
Never use 'import logging', 'logging.getLogger()', or 'print()' in application code (except in observability/setup.py and observability/sinks.py for bootstrap code)
Files:
src/synthorg/providers/enums.pysrc/synthorg/providers/management/service.pysrc/synthorg/providers/drivers/litellm_driver.pysrc/synthorg/providers/management/_helpers.pysrc/synthorg/config/schema.pysrc/synthorg/providers/presets.pysrc/synthorg/api/dto.py
src/synthorg/providers/**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
src/synthorg/providers/**/*.py: All provider calls go through BaseCompletionProvider which applies retry + rate limiting automatically; never implement retry logic in driver subclasses or calling code
Set RetryConfig and RateLimiterConfig per-provider in ProviderConfig
Retryable errors (is_retryable=True): RateLimitError, ProviderTimeoutError, ProviderConnectionError, ProviderInternalError; non-retryable errors raise immediately
Rate limiter respects RateLimitError.retry_after from providers -- automatically pauses future requests
Files:
src/synthorg/providers/enums.pysrc/synthorg/providers/management/service.pysrc/synthorg/providers/drivers/litellm_driver.pysrc/synthorg/providers/management/_helpers.pysrc/synthorg/providers/presets.py
web/src/**/*.{tsx,ts}
📄 CodeRabbit inference engine (CLAUDE.md)
web/src/**/*.{tsx,ts}: Always reuse existing components from web/src/components/ui/ before creating new ones
Use semantic Tailwind classes (text-foreground, bg-card, text-accent, text-success, bg-danger) or CSS variables (var(--so-*)); never hardcode hex values in .tsx/.ts files
Use font-sans or font-mono (maps to Geist tokens); never set fontFamily directly
Use density-aware tokens (p-card, gap-section-gap, gap-grid-gap) or standard Tailwind spacing; never hardcode pixel values for layout spacing
Use token variables (var(--so-shadow-card-hover), border-border, border-bright) for shadows/borders; never hardcode values
Do not recreate status dots inline -- use
Do not build card-with-header layouts from scratch -- use
Do not create metric displays with 'text-metric font-bold' -- use
Do not render initials circles manually -- use
Do not create complex (>8 line) JSX inside .map() -- extract to a shared component
Do not use rgba() with hardcoded values -- use design token variables
CSS side-effect imports need type declarations; Vite's '/// ' covers this in TS 6
Files:
web/src/pages/providers/ProvidersSkeleton.tsxweb/src/utils/provider-status.tsweb/src/__tests__/utils/setup-validation.test.tsweb/src/api/endpoints/providers.tsweb/src/__tests__/utils/setup-validation.property.test.tsweb/src/pages/providers/ProviderDetailSkeleton.tsxweb/src/pages/providers/TestConnectionResult.tsxweb/src/components/ui/provider-health-badge.tsxweb/src/pages/providers/ProviderGridView.tsxweb/src/pages/providers/ProviderFilters.tsxweb/src/pages/providers/ProviderHealthMetrics.tsxweb/src/pages/ProviderDetailPage.tsxweb/src/__tests__/pages/ProviderDetailPage.test.tsxweb/src/pages/providers/ProviderModelList.tsxweb/src/pages/ProvidersPage.tsxweb/src/components/ui/provider-health-badge.stories.tsxweb/src/pages/providers/TestConnectionResult.stories.tsxweb/src/pages/providers/ProviderCard.stories.tsxweb/src/hooks/useProviderDetailData.tsweb/src/__tests__/utils/providers.test.tsweb/src/pages/providers/PresetPicker.stories.tsxweb/src/hooks/useProvidersData.tsweb/src/pages/providers/ProviderCard.tsxweb/src/__tests__/pages/ProvidersPage.test.tsxweb/src/pages/providers/ProviderDetailHeader.tsxweb/src/utils/providers.tsweb/src/stores/providers.tsweb/src/api/types.tsweb/src/pages/providers/ProviderFormDrawer.tsxweb/src/pages/providers/PresetPicker.tsx
web/src/**/*
📄 CodeRabbit inference engine (CLAUDE.md)
PostToolUse hook (scripts/check_web_design_system.py) runs automatically on every Edit/Write to web/src/ files; fix all violations before proceeding
Files:
web/src/pages/providers/ProvidersSkeleton.tsxweb/src/utils/provider-status.tsweb/src/__tests__/utils/setup-validation.test.tsweb/src/api/endpoints/providers.tsweb/src/__tests__/utils/setup-validation.property.test.tsweb/src/pages/providers/ProviderDetailSkeleton.tsxweb/src/pages/providers/TestConnectionResult.tsxweb/src/components/ui/provider-health-badge.tsxweb/src/pages/providers/ProviderGridView.tsxweb/src/pages/providers/ProviderFilters.tsxweb/src/pages/providers/ProviderHealthMetrics.tsxweb/src/pages/ProviderDetailPage.tsxweb/src/__tests__/pages/ProviderDetailPage.test.tsxweb/src/pages/providers/ProviderModelList.tsxweb/src/pages/ProvidersPage.tsxweb/src/components/ui/provider-health-badge.stories.tsxweb/src/pages/providers/TestConnectionResult.stories.tsxweb/src/pages/providers/ProviderCard.stories.tsxweb/src/hooks/useProviderDetailData.tsweb/src/__tests__/utils/providers.test.tsweb/src/pages/providers/PresetPicker.stories.tsxweb/src/hooks/useProvidersData.tsweb/src/pages/providers/ProviderCard.tsxweb/src/__tests__/pages/ProvidersPage.test.tsxweb/src/pages/providers/ProviderDetailHeader.tsxweb/src/utils/providers.tsweb/src/stores/providers.tsweb/src/api/types.tsweb/src/pages/providers/ProviderFormDrawer.tsxweb/src/pages/providers/PresetPicker.tsx
web/src/**/__tests__/**/*.{tsx,ts}
📄 CodeRabbit inference engine (CLAUDE.md)
Property-based testing in React uses fast-check (fc.assert + fc.property); integrated with Vitest
Files:
web/src/__tests__/utils/setup-validation.test.tsweb/src/__tests__/utils/setup-validation.property.test.tsweb/src/__tests__/pages/ProviderDetailPage.test.tsxweb/src/__tests__/utils/providers.test.tsweb/src/__tests__/pages/ProvidersPage.test.tsx
tests/**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
tests/**/*.py: Use@pytest.mark.unit,@pytest.mark.integration,@pytest.mark.e2e,@pytest.mark.slowmarkers for tests
Enforce 80% minimum code coverage in CI
30-second timeout per test (global in pyproject.toml); non-default overrides like timeout(60) allowed but no default per-file markers
Prefer@pytest.mark.parametrizefor testing similar cases
Never skip, dismiss, or ignore flaky tests -- always fix them fully; for timing-sensitive tests, mock time.monotonic() and asyncio.sleep() instead of widening timing margins
For tasks that must block indefinitely until cancelled, use asyncio.Event().wait() instead of asyncio.sleep(large_number) -- it is cancellation-safe
Files:
tests/unit/providers/management/test_service_discovery.pytests/unit/config/test_provider_auth.py
docs/design/*.md
📄 CodeRabbit inference engine (CLAUDE.md)
Update the relevant docs/design/ page to reflect approved deviations from the spec
Files:
docs/design/brand-and-ux.mddocs/design/operations.md
web/src/components/ui/*.{tsx,ts}
📄 CodeRabbit inference engine (CLAUDE.md)
For new shared React components: place in web/src/components/ui/ with kebab-case filename, create .stories.tsx with all states, export props as TypeScript interface, use design tokens exclusively
Files:
web/src/components/ui/provider-health-badge.tsxweb/src/components/ui/provider-health-badge.stories.tsx
web/src/**/*.stories.{tsx,ts}
📄 CodeRabbit inference engine (CLAUDE.md)
web/src/**/*.stories.{tsx,ts}: Use 'storybook/test' (not '@storybook/test'), 'storybook/actions' (not '@storybook/addon-actions') in Storybook 10
Use 'parameters.backgrounds.options' (object keyed by name) + 'initialGlobals.backgrounds.value' in Storybook 10 (replaces old default + values array)
Use 'parameters.a11y.test: "error" | "todo" | "off"' in Storybook 10 to enforce WCAG compliance
Files:
web/src/components/ui/provider-health-badge.stories.tsxweb/src/pages/providers/TestConnectionResult.stories.tsxweb/src/pages/providers/ProviderCard.stories.tsxweb/src/pages/providers/PresetPicker.stories.tsx
🧠 Learnings (38)
📓 Common learnings
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-20T08:28:32.845Z
Learning: Applies to src/synthorg/providers/**/*.py : Providers: LLM provider abstraction (LiteLLM adapter), auth types (api_key/oauth/custom_header/none), presets (PROVIDER_PRESETS), runtime CRUD (ProviderManagementService with asyncio.Lock serialization), hot-reload via AppState swap.
📚 Learning: 2026-03-20T08:28:32.845Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-20T08:28:32.845Z
Learning: Applies to src/synthorg/providers/**/*.py : Providers: LLM provider abstraction (LiteLLM adapter), auth types (api_key/oauth/custom_header/none), presets (PROVIDER_PRESETS), runtime CRUD (ProviderManagementService with asyncio.Lock serialization), hot-reload via AppState swap.
Applied to files:
src/synthorg/providers/enums.pytests/unit/providers/management/test_service_discovery.pysrc/synthorg/providers/management/service.pytests/unit/config/test_provider_auth.pysrc/synthorg/providers/drivers/litellm_driver.pysrc/synthorg/providers/management/_helpers.pysrc/synthorg/config/schema.pysrc/synthorg/providers/presets.pydocs/design/operations.mdsrc/synthorg/api/dto.pyweb/src/utils/providers.tsweb/src/stores/providers.tsweb/src/api/types.ts
📚 Learning: 2026-03-15T18:28:13.207Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:28:13.207Z
Learning: Applies to tests/**/*.py : Tests must use test-provider, test-small-001, etc. for vendor-agnostic test data.
Applied to files:
tests/unit/providers/management/test_service_discovery.pyCLAUDE.mdweb/src/__tests__/utils/providers.test.ts
📚 Learning: 2026-03-27T17:07:16.441Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T17:07:16.441Z
Learning: Applies to web/src/**/__tests__/**/*.{tsx,ts} : Property-based testing in React uses fast-check (fc.assert + fc.property); integrated with Vitest
Applied to files:
web/src/__tests__/utils/setup-validation.property.test.tsweb/src/__tests__/pages/ProviderDetailPage.test.tsxweb/src/__tests__/utils/providers.test.tsweb/src/__tests__/pages/ProvidersPage.test.tsx
📚 Learning: 2026-03-27T17:07:16.441Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T17:07:16.441Z
Learning: Applies to web/src/**/*.{tsx,ts} : Do not recreate status dots inline -- use <StatusBadge>
Applied to files:
docs/design/brand-and-ux.mdCLAUDE.mdweb/src/components/ui/provider-health-badge.tsxweb/src/components/ui/provider-health-badge.stories.tsx
📚 Learning: 2026-03-27T17:07:16.441Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T17:07:16.441Z
Learning: Applies to docs/design/*.md : Update the relevant docs/design/ page to reflect approved deviations from the spec
Applied to files:
docs/design/brand-and-ux.md
📚 Learning: 2026-03-27T17:07:16.441Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T17:07:16.441Z
Learning: Applies to web/src/components/ui/*.{tsx,ts} : For new shared React components: place in web/src/components/ui/ with kebab-case filename, create .stories.tsx with all states, export props as TypeScript interface, use design tokens exclusively
Applied to files:
docs/design/brand-and-ux.mdweb/src/pages/providers/ProviderDetailSkeleton.tsxCLAUDE.mdweb/src/pages/providers/TestConnectionResult.tsxweb/src/components/ui/provider-health-badge.tsxweb/src/pages/providers/ProviderFilters.tsxweb/src/pages/providers/ProviderModelList.tsxweb/src/components/ui/provider-health-badge.stories.tsxweb/src/pages/providers/TestConnectionResult.stories.tsxweb/src/pages/providers/ProviderCard.stories.tsxweb/src/pages/providers/PresetPicker.stories.tsxweb/src/pages/providers/ProviderCard.tsxweb/src/pages/providers/ProviderDetailHeader.tsxweb/src/pages/providers/ProviderFormDrawer.tsxweb/src/pages/providers/PresetPicker.tsx
📚 Learning: 2026-03-15T21:20:09.993Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T21:20:09.993Z
Learning: Applies to web/src/components/** : Vue components organized by feature (agents/, approvals/, budget/, common/, dashboard/, layout/, messages/, org-chart/, tasks/).
Applied to files:
docs/design/brand-and-ux.md
📚 Learning: 2026-03-27T17:07:16.441Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T17:07:16.441Z
Learning: Applies to web/src/**/*.{tsx,ts} : Always reuse existing components from web/src/components/ui/ before creating new ones
Applied to files:
docs/design/brand-and-ux.md
📚 Learning: 2026-03-19T11:33:01.580Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T11:33:01.580Z
Learning: Applies to web/**/*.vue : Never use real vendor names (Anthropic, OpenAI, Claude, GPT, etc.) in dashboard code — use generic names: `example-provider`, `example-large-001`, etc.
Applied to files:
CLAUDE.md
📚 Learning: 2026-03-27T17:07:16.441Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T17:07:16.441Z
Learning: Applies to web/src/**/*.{tsx,ts} : Do not use rgba() with hardcoded values -- use design token variables
Applied to files:
CLAUDE.md
📚 Learning: 2026-03-17T22:08:13.456Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T22:08:13.456Z
Learning: Applies to tests/**/*.py : Test markers: `pytest.mark.unit`, `pytest.mark.integration`, `pytest.mark.e2e`, `pytest.mark.slow`. Coverage: 80% minimum. Async: `asyncio_mode = 'auto'` — no manual `pytest.mark.asyncio` needed. Timeout: 30 seconds per test. Parallelism: `pytest-xdist` via `-n auto` — ALWAYS include `-n auto` when running pytest, never run tests sequentially.
Applied to files:
CLAUDE.md
📚 Learning: 2026-03-16T07:22:28.134Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T07:22:28.134Z
Learning: Applies to tests/**/*.py : NEVER skip, dismiss, or ignore flaky tests — always fix them fully and fundamentally. For timing-sensitive tests, mock `time.monotonic()` and `asyncio.sleep()` to make them deterministic instead of widening timing margins
Applied to files:
CLAUDE.md
📚 Learning: 2026-03-27T17:07:16.441Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T17:07:16.441Z
Learning: Applies to tests/**/*.py : 30-second timeout per test (global in pyproject.toml); non-default overrides like timeout(60) allowed but no default per-file markers
Applied to files:
CLAUDE.md
📚 Learning: 2026-03-27T17:07:16.441Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T17:07:16.441Z
Learning: Applies to tests/**/*.py : Never skip, dismiss, or ignore flaky tests -- always fix them fully; for timing-sensitive tests, mock time.monotonic() and asyncio.sleep() instead of widening timing margins
Applied to files:
CLAUDE.md
📚 Learning: 2026-03-15T18:28:13.207Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:28:13.207Z
Learning: Applies to tests/**/*.py : Test markers: pytest.mark.unit, pytest.mark.integration, pytest.mark.e2e, pytest.mark.slow. Coverage: 80% minimum (enforced in CI).
Applied to files:
CLAUDE.md
📚 Learning: 2026-03-16T07:02:24.531Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T07:02:24.531Z
Learning: Applies to tests/**/*.py : Test timeout: 30 seconds per test.
Applied to files:
CLAUDE.md
📚 Learning: 2026-03-15T18:28:13.207Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:28:13.207Z
Learning: Parallelism: pytest-xdist via `-n auto` — ALWAYS include `-n auto` when running pytest, never run tests sequentially.
Applied to files:
CLAUDE.md
📚 Learning: 2026-03-20T21:44:04.528Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-20T21:44:04.528Z
Learning: Applies to tests/**/*.py : Fix flaky tests completely and fundamentally; for timing-sensitive tests, mock `time.monotonic()` and `asyncio.sleep()` to make them deterministic instead of widening timing margins
Applied to files:
CLAUDE.md
📚 Learning: 2026-03-15T18:28:13.207Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:28:13.207Z
Learning: Property-based testing: Python uses Hypothesis (given + settings). Hypothesis profiles: ci (200 examples, default) and dev (1000 examples), controlled via HYPOTHESIS_PROFILE env var. Run dev profile: HYPOTHESIS_PROFILE=dev uv run python -m pytest tests/ -m unit -n auto -k properties.
Applied to files:
CLAUDE.md
📚 Learning: 2026-03-27T17:07:16.441Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T17:07:16.441Z
Learning: Applies to web/src/**/*.{tsx,ts} : Do not create metric displays with 'text-metric font-bold' -- use <MetricCard>
Applied to files:
web/src/pages/providers/ProviderHealthMetrics.tsxweb/src/pages/providers/ProviderCard.tsx
📚 Learning: 2026-03-20T08:28:32.845Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-20T08:28:32.845Z
Learning: Applies to web/src/__tests__/**/*.{ts,js} : Dashboard testing: Vitest unit tests organized by feature under `web/src/__tests__/`. Use fast-check for property-based testing (`fc.assert` + `fc.property`).
Applied to files:
web/src/__tests__/pages/ProviderDetailPage.test.tsxweb/src/__tests__/utils/providers.test.tsweb/src/__tests__/pages/ProvidersPage.test.tsx
📚 Learning: 2026-03-27T17:07:16.441Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T17:07:16.441Z
Learning: Applies to src/synthorg/providers/**/*.py : Set RetryConfig and RateLimiterConfig per-provider in ProviderConfig
Applied to files:
src/synthorg/providers/management/_helpers.pysrc/synthorg/config/schema.pysrc/synthorg/providers/presets.py
📚 Learning: 2026-03-20T11:18:48.128Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-20T11:18:48.128Z
Learning: Applies to src/synthorg/**/*.py : Set `RetryConfig` and `RateLimiterConfig` per-provider in `ProviderConfig`.
Applied to files:
src/synthorg/providers/management/_helpers.pysrc/synthorg/config/schema.pysrc/synthorg/providers/presets.py
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/api/**/*.py : API package (api/): Litestar REST + WebSocket with controllers, guards, channels, JWT + API key + WS ticket auth, approval gate integration, coordination endpoint, collaboration endpoint, settings endpoint, provider management endpoint (CRUD + test + presets), backup endpoint, RFC 9457 structured errors, AppState hot-reload slots, service auto-wiring (Phase 1 at construction, Phase 2 on startup), lifecycle helpers
Applied to files:
src/synthorg/providers/management/_helpers.pydocs/design/operations.md
📚 Learning: 2026-03-16T19:13:36.562Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T19:13:36.562Z
Learning: Applies to src/synthorg/providers/**/*.py : RetryConfig and RateLimiterConfig are set per-provider in ProviderConfig.
Applied to files:
src/synthorg/providers/management/_helpers.pysrc/synthorg/config/schema.pysrc/synthorg/providers/presets.py
📚 Learning: 2026-03-27T17:07:16.441Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T17:07:16.441Z
Learning: Applies to web/src/**/*.stories.{tsx,ts} : Use 'storybook/test' (not 'storybook/test'), 'storybook/actions' (not 'storybook/addon-actions') in Storybook 10
Applied to files:
web/src/components/ui/provider-health-badge.stories.tsxweb/src/pages/providers/TestConnectionResult.stories.tsxweb/src/pages/providers/ProviderCard.stories.tsxweb/src/pages/providers/PresetPicker.stories.tsxweb/src/__tests__/pages/ProvidersPage.test.tsx
📚 Learning: 2026-03-27T17:07:16.441Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T17:07:16.441Z
Learning: Applies to web/src/**/*.stories.{tsx,ts} : Use 'parameters.a11y.test: "error" | "todo" | "off"' in Storybook 10 to enforce WCAG compliance
Applied to files:
web/src/components/ui/provider-health-badge.stories.tsxweb/src/pages/providers/TestConnectionResult.stories.tsxweb/src/pages/providers/ProviderCard.stories.tsxweb/src/pages/providers/PresetPicker.stories.tsx
📚 Learning: 2026-03-27T17:07:16.441Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T17:07:16.441Z
Learning: Applies to web/src/**/*.stories.{tsx,ts} : Use 'parameters.backgrounds.options' (object keyed by name) + 'initialGlobals.backgrounds.value' in Storybook 10 (replaces old default + values array)
Applied to files:
web/src/components/ui/provider-health-badge.stories.tsxweb/src/pages/providers/ProviderCard.stories.tsxweb/src/pages/providers/PresetPicker.stories.tsx
📚 Learning: 2026-03-27T17:07:16.441Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T17:07:16.441Z
Learning: Applies to web/.storybook/**/*.{ts,tsx} : Use 'defineMain' from 'storybook/react-vite/node' and 'definePreview' from 'storybook/react-vite' in Storybook 10; include explicit 'framework' field
Applied to files:
web/src/components/ui/provider-health-badge.stories.tsxweb/src/pages/providers/TestConnectionResult.stories.tsxweb/src/pages/providers/ProviderCard.stories.tsxweb/src/pages/providers/PresetPicker.stories.tsx
📚 Learning: 2026-03-17T22:08:13.456Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T22:08:13.456Z
Learning: Applies to src/synthorg/**/*.py : Use Pydantic v2 conventions: `BaseModel`, `model_validator`, `computed_field`, `ConfigDict`. For derived values use `computed_field` instead of storing + validating redundant fields. Use `NotBlankStr` (from `core.types`) for all identifier/name fields — including optional (`NotBlankStr | None`) and tuple (`tuple[NotBlankStr, ...]`) variants — instead of manual whitespace validators.
Applied to files:
src/synthorg/config/schema.pysrc/synthorg/providers/presets.py
📚 Learning: 2026-03-15T19:14:27.144Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T19:14:27.144Z
Learning: Applies to src/synthorg/**/*.py : Use Pydantic v2 BaseModel, model_validator, computed_field, ConfigDict.
Applied to files:
src/synthorg/config/schema.pysrc/synthorg/providers/presets.py
📚 Learning: 2026-03-15T18:42:17.990Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:42:17.990Z
Learning: Applies to src/synthorg/**/*.py : Use Pydantic v2 conventions: `BaseModel`, `model_validator`, `computed_field`, `ConfigDict`
Applied to files:
src/synthorg/config/schema.pysrc/synthorg/providers/presets.py
📚 Learning: 2026-03-17T11:41:02.964Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T11:41:02.964Z
Learning: Applies to src/**/*.py : Models: Pydantic v2 (`BaseModel`, `model_validator`, `computed_field`, `ConfigDict`). Use `computed_field` for derived values instead of storing + validating redundant fields. Use `NotBlankStr` for all identifier/name fields — including optional (`NotBlankStr | None`) and tuple (`tuple[NotBlankStr, ...]`) variants — instead of manual whitespace validators.
Applied to files:
src/synthorg/providers/presets.py
📚 Learning: 2026-03-20T08:28:32.845Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-20T08:28:32.845Z
Learning: Applies to src/synthorg/**/*.py : `RetryConfig` and `RateLimiterConfig` are set per-provider in `ProviderConfig`. Retryable errors: `RateLimitError`, `ProviderTimeoutError`, `ProviderConnectionError`, `ProviderInternalError`. Non-retryable errors raise immediately.
Applied to files:
src/synthorg/providers/presets.py
📚 Learning: 2026-03-27T17:07:16.441Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T17:07:16.441Z
Learning: Applies to web/src/**/*.{tsx,ts} : Do not build card-with-header layouts from scratch -- use <SectionCard>
Applied to files:
web/src/pages/providers/ProviderCard.tsx
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/**/*.py : Package structure: src/synthorg/ organized as: api/ (REST+WebSocket, Litestar), auth/ (auth subpackage), backup/ (scheduled/manual backups), budget/ (cost tracking, CFO), cli/ (superseded by Go CLI), communication/ (message bus, meetings), config/ (YAML loading), core/ (domain models, resilience config), engine/ (orchestration, task state, coordination, approval gates, stagnation detection, context budget, compaction), hr/ (hiring, performance, promotion), memory/ (pluggable backend, Mem0, retrieval, consolidation), persistence/ (operational data, SQLite, settings), observability/ (logging, correlation, sinks), providers/ (LLM abstraction, LiteLLM, auth types, presets, runtime CRUD), settings/ (runtime-editable, typed definitions, encryption, config bridge), security/ (SecOps, rule engine, output scanning, progressive trust, autonomy levels), templates/ (company templates, personalities), tools/ (registry, built-in tools, git, sandbox, code_runner, MCP...
Applied to files:
docs/design/operations.md
📚 Learning: 2026-03-27T17:07:16.441Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T17:07:16.441Z
Learning: Applies to web/src/**/*.{tsx,ts} : CSS side-effect imports need type declarations; Vite's '/// <reference types="vite/client" />' covers this in TS 6
Applied to files:
web/src/utils/providers.ts
🪛 GitHub Check: Dashboard Lint
web/src/pages/providers/ProviderFormDrawer.tsx
[warning] 67-67:
Do not call the 'set' function 'setLitellmProvider' of 'useState' synchronously in an effect. This can lead to unnecessary re-renders and performance issues
[warning] 66-66:
Do not call the 'set' function 'setBaseUrl' of 'useState' synchronously in an effect. This can lead to unnecessary re-renders and performance issues
[warning] 65-65:
Do not call the 'set' function 'setAuthType' of 'useState' synchronously in an effect. This can lead to unnecessary re-renders and performance issues
[warning] 64-64:
Do not call the 'set' function 'setName' of 'useState' synchronously in an effect. This can lead to unnecessary re-renders and performance issues
🔇 Additional comments (24)
web/src/pages/providers/ProvidersSkeleton.tsx (1)
6-11: Good loading-state implementation with design-system reuse.Uses
SkeletonCard, tokenized spacing, and a clear loading label; this is consistent and maintainable.CLAUDE.md (1)
177-177: Docs update is aligned with this PR’s provider feature scope.The component inventory and provider preset policy note are consistent with the new Providers implementation.
Also applies to: 264-264
src/synthorg/providers/enums.py (1)
12-12:AuthTypeextension looks correct.Adding
SUBSCRIPTIONhere cleanly supports the new auth mode across provider flows.web/src/utils/provider-status.ts (1)
16-17: Subscription auth status mapping is consistent with existing branches.This keeps
getProviderStatusbehavior uniform across auth types and preserves exhaustiveness checks.web/src/__tests__/utils/setup-validation.test.ts (1)
37-45: Fixture update correctly tracks the expandedProviderConfigshape.Good update to keep setup-validation tests aligned with the new provider auth fields.
web/src/__tests__/utils/setup-validation.property.test.ts (1)
32-40: Property-test provider fixture is correctly extended for subscription fields.This keeps generated inputs in sync with current API types.
web/src/api/endpoints/providers.ts (1)
41-44:getProviderHealthendpoint wrapper is implemented correctly.It follows existing endpoint conventions and safely URL-encodes provider names.
docs/design/brand-and-ux.md (1)
210-210: Good design-doc sync for the new shared badge.Adding
ProviderHealthBadgeto the inventory keeps the design system docs aligned with the providers UI instead of leaving the shared component undocumented.As per coding guidelines, "Update the relevant docs/design/ page to reflect approved deviations from the spec".
src/synthorg/config/schema.py (1)
148-156: Confirmsubscription_tokenis covered by the existing secret-handling path.
repr=Falseonly hidesrepr()output; it does not keep the field out ofmodel_dump()or serialization. Verify this credential is encrypted/redacted using the same mechanism asapi_key,oauth_client_secret, andcustom_header_valuebefore reaching disk or logs.web/src/pages/providers/PresetPicker.stories.tsx (1)
12-17: Verify this file inherits ana11y.testpolicy.Storybook only runs/fails automated accessibility checks when
parameters.a11y.testis configured at the story, meta, or preview level. I don't see it in this file, so please confirm.storybook/previewalready supplies it globally; otherwise add it to thismeta. (storybook.js.org)As per coding guidelines "Use 'parameters.a11y.test: "error" | "todo" | "off"' in Storybook 10 to enforce WCAG compliance".
web/src/pages/providers/ProviderCard.stories.tsx (1)
35-40: Verify this file inherits ana11y.testpolicy.Storybook only runs/fails automated accessibility checks when
parameters.a11y.testis configured at the story, meta, or preview level. I don't see it in this file, so please confirm.storybook/previewalready supplies it globally; otherwise add it to thismeta. (storybook.js.org)As per coding guidelines "Use 'parameters.a11y.test: "error" | "todo" | "off"' in Storybook 10 to enforce WCAG compliance".
web/src/pages/providers/TestConnectionResult.stories.tsx (1)
4-9: Verify this file inherits ana11y.testpolicy.Storybook only runs/fails automated accessibility checks when
parameters.a11y.testis configured at the story, meta, or preview level. I don't see it in this file, so please confirm.storybook/previewalready supplies it globally; otherwise add it to thismeta. (storybook.js.org)As per coding guidelines "Use 'parameters.a11y.test: "error" | "todo" | "off"' in Storybook 10 to enforce WCAG compliance".
web/src/stores/providers.ts (7)
34-78: Well-structured state interface.The
ProvidersStateinterface is cleanly organized with logical groupings (list view, filters, detail view, CRUD/mutations, actions). Usingreadonlyarrays enforces immutability at the type level, which is a good practice for state management.
80-82: Module-level request tracking is reasonable but consider encapsulation.The stale-request guards using module-level variables work correctly. However, these variables persist across store resets (e.g., if the store were to be reset during logout). If
clearDetailis called,_detailRequestNameis reset (line 356), but_listRequestIdhas no corresponding reset mechanism. This is likely fine in practice since the incrementing ID naturally invalidates old requests.
111-146: LGTM with minor observation on loading state.The implementation correctly handles stale requests and partial health failures. The
listLoadingstate remainstrueuntil health fetching completes (line 141), which means the UI shows loading for the entire operation. This is a valid design choice that ensures consistent data presentation.
148-200: Good resilience with partial failure handling.The use of
Promise.allSettledallows the detail view to render even when models or health fetching fails, with clear error messaging about which data failed to load. The stale-request guard correctly invalidates the response if the user navigated to a different provider.
202-212: LGTM — caching with implicit retry on failure.The cache-once pattern is appropriate for static preset data. The early return when
presets.length > 0prevents redundant fetches, while a failed fetch (empty array) allows natural retries.
286-311: Good optimistic update pattern for delete.The
deleteProvideraction correctly removes the provider from local state after successful API deletion (line 291-293), avoiding an unnecessary refetch. On failure, it refreshes the list to restore accurate state. Themutatingflag prevents concurrent fetch operations from interfering.
353-374: LGTM — clean state clearing and filter setters.The
clearDetailaction properly resets both the module-level_detailRequestNameguard and all detail-related state, ensuring stale in-flight requests are ignored. Filter setters are simple and correct.web/src/api/types.ts (5)
607-617: LGTM — well-defined provider health types.The
AuthTypeextension with'subscription'aligns with the backend rework. TheProviderHealthSummaryinterface correctly models nullable fields (e.g.,last_check_timestamp,avg_response_time_ms) for providers that haven't been checked yet.
631-646: LGTM — ProviderConfig additions align with subscription auth feature.The new fields (
litellm_provider,has_subscription_token,tos_accepted_at) correctly extend the DTO to support subscription-based authentication and ToS tracking.
648-683: LGTM — request types follow established patterns.The
clear_subscription_tokenfield follows the same pattern asclear_api_keyfor credential management. Thetos_acceptedboolean allows the frontend to signal acceptance, with the backend presumably recording the timestamp.
696-707: LGTM — preset types support multi-auth workflows.The
supported_auth_typesarray enables the UI to present relevant authentication options based on the selected preset, supporting the guided multi-step creation flow described in PR objectives.
715-724: LGTM — CreateFromPresetRequest supports auth type override.Adding
auth_typeoverride allows users to select from the preset'ssupported_auth_types, enabling flexible provider creation from presets with different authentication methods.
| # ── Cloud providers ──────────────────────────────────────────── | ||
|
|
||
| _ANTHROPIC = ProviderPreset( | ||
| name="anthropic", | ||
| display_name="Anthropic", | ||
| description="Claude models (Opus, Sonnet, Haiku)", | ||
| driver="litellm", | ||
| litellm_provider="anthropic", | ||
| auth_type=AuthType.API_KEY, | ||
| supported_auth_types=(AuthType.API_KEY, AuthType.SUBSCRIPTION), | ||
| default_models=( | ||
| ProviderModelConfig( | ||
| id="claude-sonnet-4-20250514", | ||
| alias="sonnet", | ||
| cost_per_1k_input=0.003, | ||
| cost_per_1k_output=0.015, | ||
| max_context=200_000, | ||
| ), | ||
| ProviderModelConfig( | ||
| id="claude-haiku-4-5-20251001", | ||
| alias="haiku", | ||
| cost_per_1k_input=0.0008, | ||
| cost_per_1k_output=0.004, | ||
| max_context=200_000, | ||
| ), | ||
| default_models=(), | ||
| ), | ||
| ProviderPreset( | ||
| name="lm-studio", | ||
| display_name="LM Studio", | ||
| description="Local LLM development environment", | ||
| driver="litellm", | ||
| auth_type=AuthType.NONE, | ||
| default_base_url="http://localhost:1234/v1", | ||
| candidate_urls=( | ||
| "http://host.docker.internal:1234/v1", | ||
| "http://172.17.0.1:1234/v1", | ||
| "http://localhost:1234/v1", | ||
| ) | ||
|
|
||
| _OPENAI = ProviderPreset( | ||
| name="openai", | ||
| display_name="OpenAI", | ||
| description="GPT and o-series models", | ||
| driver="litellm", | ||
| litellm_provider="openai", | ||
| auth_type=AuthType.API_KEY, | ||
| supported_auth_types=(AuthType.API_KEY,), | ||
| default_models=( | ||
| ProviderModelConfig( | ||
| id="gpt-4.1", | ||
| alias="gpt4", | ||
| cost_per_1k_input=0.002, | ||
| cost_per_1k_output=0.008, | ||
| max_context=1_047_576, | ||
| ), | ||
| ProviderModelConfig( | ||
| id="gpt-4.1-mini", | ||
| alias="gpt4-mini", | ||
| cost_per_1k_input=0.0004, | ||
| cost_per_1k_output=0.0016, | ||
| max_context=1_047_576, | ||
| ), | ||
| ProviderModelConfig( | ||
| id="o3", | ||
| alias="o3", | ||
| cost_per_1k_input=0.002, | ||
| cost_per_1k_output=0.008, | ||
| max_context=200_000, | ||
| ), | ||
| default_models=(), | ||
| ), | ||
| ProviderPreset( | ||
| name="openrouter", | ||
| display_name="OpenRouter", | ||
| description="Multi-provider API gateway", | ||
| driver="litellm", | ||
| auth_type=AuthType.API_KEY, | ||
| default_base_url="https://openrouter.ai/api/v1", | ||
| default_models=(), | ||
| ) | ||
|
|
||
| _GEMINI = ProviderPreset( | ||
| name="gemini", | ||
| display_name="Google AI Studio", | ||
| description="Gemini models via Google AI", | ||
| driver="litellm", | ||
| litellm_provider="gemini", | ||
| auth_type=AuthType.API_KEY, | ||
| supported_auth_types=(AuthType.API_KEY,), | ||
| default_models=( | ||
| ProviderModelConfig( | ||
| id="gemini-2.5-pro", | ||
| alias="gemini-pro", | ||
| cost_per_1k_input=0.00125, | ||
| cost_per_1k_output=0.01, | ||
| max_context=1_048_576, | ||
| ), | ||
| ProviderModelConfig( | ||
| id="gemini-2.5-flash", | ||
| alias="gemini-flash", | ||
| cost_per_1k_input=0.00015, | ||
| cost_per_1k_output=0.0006, | ||
| max_context=1_048_576, | ||
| ), | ||
| ), | ||
| ProviderPreset( | ||
| name="vllm", | ||
| display_name="vLLM", | ||
| description="High-throughput local inference engine", | ||
| driver="litellm", | ||
| auth_type=AuthType.NONE, | ||
| default_base_url="http://localhost:8000/v1", | ||
| # candidate_urls intentionally empty: vLLM's default port (8000) | ||
| # is a common collision risk (the SynthOrg backend formerly used | ||
| # 8000). Users must specify the vLLM URL explicitly or remap | ||
| # vLLM to a non-colliding port. | ||
| default_models=(), | ||
| ) | ||
|
|
||
| _MISTRAL = ProviderPreset( | ||
| name="mistral", | ||
| display_name="Mistral AI", | ||
| description="Mistral and Codestral models", | ||
| driver="litellm", | ||
| litellm_provider="mistral", | ||
| auth_type=AuthType.API_KEY, | ||
| supported_auth_types=(AuthType.API_KEY,), | ||
| default_models=(), | ||
| ) | ||
|
|
||
| _GROQ = ProviderPreset( | ||
| name="groq", | ||
| display_name="Groq", | ||
| description="Ultra-fast inference (LPU)", | ||
| driver="litellm", | ||
| litellm_provider="groq", | ||
| auth_type=AuthType.API_KEY, | ||
| supported_auth_types=(AuthType.API_KEY,), | ||
| default_models=(), | ||
| ) | ||
|
|
||
| _DEEPSEEK = ProviderPreset( | ||
| name="deepseek", | ||
| display_name="DeepSeek", | ||
| description="DeepSeek reasoning and chat models", | ||
| driver="litellm", | ||
| litellm_provider="deepseek", | ||
| auth_type=AuthType.API_KEY, | ||
| supported_auth_types=(AuthType.API_KEY,), | ||
| default_models=(), | ||
| ) | ||
|
|
||
| _AZURE_OPENAI = ProviderPreset( | ||
| name="azure", | ||
| display_name="Azure OpenAI", | ||
| description="OpenAI models via Azure", | ||
| driver="litellm", | ||
| litellm_provider="azure", | ||
| auth_type=AuthType.API_KEY, | ||
| supported_auth_types=(AuthType.API_KEY,), | ||
| # Azure requires a per-deployment base_url | ||
| default_base_url=None, | ||
| default_models=(), | ||
| ) | ||
|
|
||
| # ── Self-hosted / local ──────────────────────────────────────── | ||
|
|
||
| _OLLAMA = ProviderPreset( | ||
| name="ollama", | ||
| display_name="Ollama", | ||
| description="Local LLM inference server", | ||
| driver="litellm", | ||
| litellm_provider="ollama", | ||
| auth_type=AuthType.NONE, | ||
| supported_auth_types=(AuthType.NONE,), | ||
| default_base_url="http://localhost:11434", | ||
| candidate_urls=( | ||
| "http://host.docker.internal:11434", | ||
| "http://172.17.0.1:11434", | ||
| "http://localhost:11434", | ||
| ), | ||
| default_models=(), | ||
| ) | ||
|
|
||
| _LM_STUDIO = ProviderPreset( | ||
| name="lm-studio", | ||
| display_name="LM Studio", | ||
| description="Local LLM development environment", | ||
| driver="litellm", | ||
| litellm_provider="openai", | ||
| auth_type=AuthType.NONE, | ||
| supported_auth_types=(AuthType.NONE,), | ||
| default_base_url="http://localhost:1234/v1", | ||
| candidate_urls=( | ||
| "http://host.docker.internal:1234/v1", | ||
| "http://172.17.0.1:1234/v1", | ||
| "http://localhost:1234/v1", | ||
| ), | ||
| default_models=(), | ||
| ) | ||
|
|
||
| _VLLM = ProviderPreset( | ||
| name="vllm", | ||
| display_name="vLLM", | ||
| description="High-throughput local inference engine", | ||
| driver="litellm", | ||
| litellm_provider="openai", | ||
| auth_type=AuthType.NONE, | ||
| supported_auth_types=(AuthType.NONE,), | ||
| default_base_url="http://localhost:8000/v1", | ||
| # candidate_urls intentionally empty: vLLM's default port (8000) | ||
| # is a common collision risk (the SynthOrg backend formerly used | ||
| # 8000). Users must specify the vLLM URL explicitly or remap | ||
| # vLLM to a non-colliding port. | ||
| default_models=(), | ||
| ) | ||
|
|
||
| # ── Gateways ─────────────────────────────────────────────────── | ||
|
|
||
| _OPENROUTER = ProviderPreset( | ||
| name="openrouter", | ||
| display_name="OpenRouter", | ||
| description="Multi-provider API gateway", | ||
| driver="litellm", | ||
| litellm_provider="openrouter", | ||
| auth_type=AuthType.API_KEY, | ||
| supported_auth_types=(AuthType.API_KEY,), | ||
| default_base_url="https://openrouter.ai/api/v1", | ||
| default_models=(), | ||
| ) | ||
|
|
||
|
|
||
| PROVIDER_PRESETS: tuple[ProviderPreset, ...] = ( | ||
| # Cloud (alphabetical) | ||
| _ANTHROPIC, | ||
| _AZURE_OPENAI, | ||
| _DEEPSEEK, | ||
| _GEMINI, | ||
| _GROQ, | ||
| _MISTRAL, | ||
| _OPENAI, | ||
| # Self-hosted | ||
| _LM_STUDIO, | ||
| _OLLAMA, | ||
| _VLLM, | ||
| # Gateways | ||
| _OPENROUTER, | ||
| ) |
There was a problem hiding this comment.
🛠️ Refactor suggestion | 🟠 Major
Confine the new vendor literals to an approved exception boundary.
This module now hardcodes vendor names, model IDs, and vendor-specific descriptions throughout project-owned Python code. If LiteLLM interop truly requires those literals, please keep them at a narrow adapter/constants boundary and leave project-owned labels/comments/examples generic; otherwise this change conflicts with the repo rule for src/synthorg/**/*.py.
As per coding guidelines, "Never use real vendor names (Anthropic, OpenAI, Claude, GPT, etc.) in project-owned code, docstrings, comments, tests, or config examples; use generic names: example-provider, example-large-001, example-medium-001, example-small-001, large/medium/small as aliases".
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/synthorg/providers/presets.py` around lines 64 - 279, The diff exposes
hardcoded vendor/model literals in ProviderPreset instances (_ANTHROPIC,
_OPENAI, _GEMINI, _MISTRAL, _GROQ, _DEEPSEEK, _AZURE_OPENAI, _OLLAMA,
_LM_STUDIO, _VLLM, _OPENROUTER and the PROVIDER_PRESETS tuple); move all
vendor-specific strings (vendor names, display_name, description, model ids,
aliases, litellm_provider, default_base_url, candidate_urls) into a single
approved adapter/constants boundary (e.g., a new module like provider_adapters
or provider_constants) and replace the values in
src/synthorg/providers/presets.py with generic placeholders (example-provider,
example-large-001, example-medium-001, example-small-001, large/medium/small
aliases, and generic descriptions/default_urls), ensuring ProviderPreset and
ProviderModelConfig usage stays the same and importing concrete vendor literals
only from the new adapter module where interop truly requires them.
Backend: - Replace vendor names in tests with generic test-provider/test-subscription-token - Wrap _AUTH_OWNED_FIELDS in MappingProxyType for immutability - Guard ToS consent stamping to subscription auth only - Resolve auth_type override before preset discovery decision - Fix litellm_driver class docstring (litellm_provider routing key) - Consistent extra_headers assignment in _build_kwargs - Rename _validate_api_key_clear_consistency to _validate_credential_clear_consistency - Restore DTO Attributes docstrings (CreateProviderRequest, ProviderResponse, CreateFromPresetRequest) - Remove vendor names from schema.py litellm_provider docstring - Fix USD -> base currency in cost docstrings - Fix subscription_token docstring (not OAuth) - Update Anthropic preset Sonnet model to claude-sonnet-4-6-20250514 Tests: - Add SUBSCRIPTION auth tests for _build_kwargs, build_discovery_headers - Add _apply_credential_updates SUBSCRIPTION transition tests - Add auth-type-switch cleanup tests (subscription <-> api_key) - Add ProviderPreset validator rejection test - Remove redundant @pytest.mark.unit class decorators Frontend: - Remove double-fetch on mount in useProvidersData/useProviderDetailData - Remove dead deleteTarget code from ProvidersPage - Use shared InputField/SelectField in ProviderFilters - Export ProviderHealthBadgeProps, add a11y.test to stories - ProviderFormDrawer: hide unsupported auth options, reset Custom state, send clear_* flags in edit mode, fix base URL visibility, show presetsError - Extract PresetOptionCard, ProviderGridItem, ProviderModelRow components - Use SectionCard in ProviderDetailSkeleton - Add aria-live to TestConnectionResult, fix error truncation - Fix replaceAll for auth_type display - Replace hardcoded hover shadow with design token - EmptyState for missing provider instead of null - Remove redundant decodeURIComponent - Fix last-check date format (add date, not just time) - Fix health sort: unknown sorts after down - Remove hardcoded $ currency symbol - Add discoveringModels loading flag to store Docs: - Add missing API endpoints to operations.md - Add supported_auth_types and fix vLLM auto-probe in operations.md - Add ProviderHealthStatus type and getProviderHealthColor to brand-and-ux.md - Expand Providers description in page-structure.md Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
@coderabbitai review |
✅ Actions performedReview triggered.
|
There was a problem hiding this comment.
Actionable comments posted: 16
♻️ Duplicate comments (4)
src/synthorg/providers/presets.py (1)
25-29:⚠️ Potential issue | 🟠 MajorKeep vendor literals behind a dedicated adapter boundary.
This docstring and the preset catalog below hardcode provider names, model IDs, URLs, and vendor-specific descriptions directly in project-owned Python. If the LiteLLM routing keys are unavoidable, isolate only those identifiers in a thin adapter/constants layer and keep the preset metadata exposed to the rest of the app generic. As per coding guidelines, "Never use real vendor names (Anthropic, OpenAI, Claude, GPT, etc.) in project-owned code, docstrings, comments, tests, or config examples; use generic names: example-provider, example-large-001, example-medium-001, example-small-001, large/medium/small as aliases".
Also applies to: 64-279
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/synthorg/providers/presets.py` around lines 25 - 29, The docstring and preset catalog in src/synthorg/providers/presets.py currently hardcode vendor names and model identifiers (see symbols like litellm_provider, auth_type, supported_auth_types and the preset entries between lines 64-279); extract all vendor-specific literals (provider routing keys, model IDs, URLs, vendor descriptions) into a new thin adapter/constants module (e.g., synthorg.providers.adapters or synthorg.providers.constants) and replace the hardcoded values and docstring examples with generic aliases (example-provider, example-large-001, large/medium/small) so presets.py only references generic keys and the adapter maps those keys to real vendor routing identifiers internally.web/src/components/ui/provider-health-badge.tsx (1)
32-59: 🛠️ Refactor suggestion | 🟠 MajorCompose
StatusBadgehere instead of duplicating the badge markup.This component still owns its own dot markup, color-class map, and label semantics, so it will drift from the shared badge behavior the next time that design-system component changes. As per coding guidelines, "Do not recreate status dots inline -- use " and "Always reuse existing components from web/src/components/ui/ before creating new ones".
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@web/src/components/ui/provider-health-badge.tsx` around lines 32 - 59, Replace the duplicated dot/label markup in ProviderHealthBadge with the shared StatusBadge component: import StatusBadge, stop using DOT_COLOR_CLASSES and the inline dot span, and instead render StatusBadge from ProviderHealthBadge (still passing through className, label/pulse props and the resolved status or color). Use getProviderHealthColor(status) or STATUS_LABELS only to map/derive the status value if StatusBadge expects a color/variant prop, and pass that into StatusBadge; remove any duplicate label semantics and dot markup so ProviderHealthBadge becomes a thin wrapper that composes StatusBadge.web/src/pages/providers/ProviderCard.tsx (1)
18-63: 🛠️ Refactor suggestion | 🟠 MajorUse
SectionCardfor this provider tile.This still reimplements the shared card shell/header/body layout locally, so spacing and card behavior will drift again as the dashboard evolves.
As per coding guidelines, "Do not build card-with-header layouts from scratch -- use ".
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@web/src/pages/providers/ProviderCard.tsx` around lines 18 - 63, ProviderCard is reimplementing the common card shell/header/body instead of using the shared SectionCard; import SectionCard and replace the outer div and manual header/body markup with SectionCard to preserve shared spacing/behavior. Specifically: in ProviderCard, add an import for SectionCard, replace the root <div className={cn(...)}> wrapper with <SectionCard className={className} title={displayName} subtitle={subtitle} icon={<Server className="size-4 shrink-0 text-text-secondary" />}> (or use SectionCard’s header/children slots as available), remove the duplicated header block and render ProviderHealthBadge into the SectionCard header or a headerAction prop, and move the body nodes (auth_type formatting provider.auth_type.replaceAll('_', ' '), provider.base_url, model count, and health.calls_last_24h) into the SectionCard children so all content and logic (provider.models.length pluralization and conditional health/base_url rendering) remain unchanged.web/src/pages/providers/ProviderFormDrawer.tsx (1)
265-274:⚠️ Potential issue | 🟡 MinorHint text is misleading for presets without a default URL.
The visibility condition is now correct (field shows for all presets). However, the hint says "Optional for known cloud providers" even when
preset?.default_base_urlisnull—which indicates the URL is required, not optional. Presets like Azure usenullto force users to enter their deployment URL.🔧 Suggested fix to clarify the hint
<InputField label="Base URL" value={baseUrl} onChange={(e) => setBaseUrl(e.target.value)} placeholder={preset?.default_base_url ?? 'https://api.example.com/v1'} - hint={!preset?.default_base_url && !isCustom ? 'Optional for known cloud providers' : undefined} + hint={ + preset && !preset.default_base_url + ? 'Required — this preset has no built-in URL' + : !isCustom + ? undefined + : 'Optional override for the provider endpoint' + } />🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@web/src/pages/providers/ProviderFormDrawer.tsx` around lines 265 - 274, The hint text on the Base URL InputField is misleading for presets that explicitly set default_base_url to null (which means the URL is required); update the hint logic used in the InputField (the hint prop next to InputField, inside the conditional that checks isCustom || preset != null || mode === 'edit') so it shows 'Optional for known cloud providers' only when preset?.default_base_url is truthy, and for presets that exist but have preset.default_base_url === null show a clear required hint such as 'Required for this provider' (or omit the optional hint when the URL is required).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@docs/design/operations.md`:
- Around line 46-51: The docs currently describe subscription_token as an "OAuth
bearer token (subscription auth only, encrypted at rest)" which is misleading;
update the wording near the auth_type field and the subscription_token comment
to clarify that "subscription" is a distinct auth_type (not OAuth) and that any
literal YAML value is stored in plaintext on disk, so operators should use
environment variables or secret management instead of embedding tokens in config
files; specifically edit the comments for auth_type, subscription_token and
tos_accepted_at in the snippet (symbols: auth_type, subscription_token,
subscription) to remove "encrypted at rest", state that subscription tokens are
sensitive and should be provided via env/secret storage, and avoid implying
subscription is OAuth.
- Line 1106: The API summary row is missing the provider health endpoint; update
the table row that lists provider endpoints to include `GET
/api/v1/providers/{name}/health` among the comma-separated endpoints (alongside
`GET /api/v1/providers/{name}` and `GET /api/v1/providers/{name}/models`) so the
API index correctly reflects provider health support.
In `@src/synthorg/config/schema.py`:
- Around line 208-240: The validation currently skips AuthType.API_KEY because
_AUTH_REQUIRED_FIELDS lacks an entry, allowing
ProviderConfig(auth_type=AuthType.API_KEY) with api_key=None; update
_AUTH_REQUIRED_FIELDS to include AuthType.API_KEY mapped to ("api_key",) so
_validate_auth_fields will detect a missing api_key and log/raise the same
ValueError as other auth types (keep the existing logger usage and message
construction in _validate_auth_fields).
In `@src/synthorg/providers/management/_helpers.py`:
- Around line 50-53: The update logic currently only merges non-None values and
thus cannot clear a previously set override for litellm_provider; modify the
partial-update/merge routine that uses UPDATE_FIELDS (and any update function
that iterates those fields) to treat an explicit None for "litellm_provider" as
a valid intent to clear the override (i.e., when the incoming payload has
litellm_provider present and is None, set the stored value to None) or add a
specific clear path for litellm_provider so saved overrides can be reverted to
default name-based routing; ensure UPDATE_FIELDS still contains
"litellm_provider" so it is processed and update any conditional that currently
skips None values to allow explicit null for this field.
In `@src/synthorg/providers/presets.py`:
- Around line 52-60: Import logger = get_logger(__name__) from
synthorg.observability at the top of the module and, inside the model_validator
method _validate_auth_type_in_supported, log a structured warning (using
logger.warning) immediately before raising the ValueError; include context
fields like the preset name (self.name), self.auth_type and
self.supported_auth_types in the log message so diagnostics capture the failing
preset metadata, then raise the same ValueError as currently done.
In `@tests/unit/providers/drivers/test_litellm_auth.py`:
- Around line 137-146: Update the
test_build_kwargs_subscription_no_token_skips_header to construct the config
with subscription_token=None via _make_config instead of mutating the frozen
model with object.__setattr__; call _build_kwargs(config) and assert that
"extra_headers" is not in the returned kwargs to confirm subscription auth
without a token omits the header.
In `@web/src/hooks/useProviderDetailData.ts`:
- Around line 26-44: The hook can return the previous provider's detail for one
render when providerName changes because clearDetail() runs in an effect; fix by
masking stale state early: compute a stale flag (e.g., const isStale =
provider?.name !== providerName) and when isStale is true return or expose
cleared/default values for provider, models, health, testConnectionResult and
related selectors instead of the raw store values; update the existing useEffect
cleanup to remain, but ensure useProviderDetailData's return/value logic uses
the isStale guard so UI never sees the prior provider's details during the
intermediate render.
In `@web/src/pages/providers/ProviderFilters.tsx`:
- Around line 23-29: The providers view never exposes or uses
sortDirection/setSortDirection from useProvidersStore, leaving the list pinned
to the store default; either expose and wire a sort direction control in
ProviderFilters (read sortDirection via useProvidersStore and call
setSortDirection when toggled) so the UI can change direction, or remove
sortDirection/setSortDirection from the store/hook if direction is unused.
Locate ProviderFilters and the second filter block (the other instance noted),
add useProvidersStore((s) => s.sortDirection) and useProvidersStore((s) =>
s.setSortDirection), and wire a small toggle/select to call setSortDirection
when the user changes direction (or remove the store fields and any references
to sortDirection/sortDirection in list pipeline if you choose to delete the dead
state).
In `@web/src/pages/providers/ProviderFormDrawer.tsx`:
- Around line 60-69: The effect that pre-fills the form (useEffect) calls
multiple individual setters (setName, setAuthType, setBaseUrl,
setLitellmProvider, setTosAccepted) causing multiple re-renders; replace these
with a single batched update by introducing a single form state (e.g., formState
+ setFormState) or a reducer (e.g., useReducer with an "init" action) and in the
effect set the whole form object once using setFormState({ name: provider.name,
auth_type: provider.auth_type, base_url: provider.base_url ?? '',
litellm_provider: provider.litellm_provider ?? '', tos_accepted:
provider.tos_accepted_at !== null }) or dispatch({ type: 'init', payload:
provider }) when mode === 'edit' and provider is present, then update the rest
of the component to read from that unified state instead of the individual
setters.
- Around line 156-167: The backend currently ignores attempts to clear
litellm_provider and base_url because UpdateProviderRequest lacks
clear_litellm_provider and clear_base_url flags and apply_update doesn't handle
them; add boolean fields clear_litellm_provider and clear_base_url to the
UpdateProviderRequest schema and implement the same clear logic in apply_update
as used for clear_api_key/clear_subscription_token (i.e., if the clear flag is
true, remove the stored override; otherwise set when a non-null value is
provided). Also update the frontend call site (updateProvider invoked in
ProviderFormDrawer.tsx) to send clear_litellm_provider and clear_base_url based
on the UI state so the backend can remove those overrides.
In `@web/src/pages/providers/ProviderHealthMetrics.tsx`:
- Around line 5-6: The ProviderHealthSummary type and the
ProviderHealthMetricsProps/ProviderHealthMetrics component currently only
include calls, latency, error rate, and last check; add totalTokens and
totalCost (or appropriately typed tokenCount and cost fields) to
ProviderHealthSummary, update the ProviderHealthMetricsProps interface to
include those new fields, and update the ProviderHealthMetrics component to
render total tokens and cost per provider (and ensure any places that construct
ProviderHealthSummary/consume ProviderHealthMetrics pass the new fields or
compute them). Also update any related tests/types/consumers so the new
properties are supplied and typed consistently.
In `@web/src/pages/providers/ProviderModelList.tsx`:
- Around line 28-30: The model list UI omits per-model capabilities because
ProviderModelConfig lacks a capabilities field and ProviderModelList (and its
prop type ProviderModelListProps) only renders id, alias, context, and costs;
add a capabilities property (e.g., string[] or a typed Capability enum) to the
ProviderModelConfig type, update ProviderModelListProps to accept models with
that field, and modify the ProviderModelList component rendering logic to add a
new "Capabilities" column/cell that maps each model's capabilities into
human-readable badges or text; also update any data mapping (where models are
constructed or fetched) to populate the new capabilities field.
In `@web/src/stores/providers.ts`:
- Around line 113-115: The fetchProviders function currently bails early when
get().mutating is true, which prevents callers like createProvider,
createFromPreset, updateProvider and the delete-error-recovery path from
triggering an immediate list refresh; change fetchProviders to accept a force
boolean (e.g., fetchProviders(force = false)) and only skip the refresh when
mutating is true AND force is false, then update the callers (createProvider,
createFromPreset, updateProvider and the delete-error-recovery code) to call
fetchProviders(true) after their operation so they always refresh the list
immediately; alternatively, remove the mutating guard entirely if you want every
caller to refresh regardless of mutating state.
- Around line 81-84: The detail stale-request guard currently keys only on
_detailRequestName so two concurrent fetchProviderDetail("x") calls can both
pass the early-return check and let an older response overwrite a newer one; fix
by mirroring the list request pattern: introduce a numeric request id (e.g.,
_detailRequestId) and in fetchProviderDetail increment and capture a local id
before the async call, then after awaiting compare the captured id against the
current _detailRequestId (and optionally _detailRequestName) and only apply
response state if they match; update all checks in fetchProviderDetail (the
guard at lines ~150-161 and the response handling) to use this id-based
comparison to prevent same-name overlaps.
- Around line 166-174: When getProvider(name) fails (providerResult.status ===
'rejected'), clear the previous provider detail snapshot and related fields so
old models/health do not persist; in the failure branch where set({
detailLoading: false, detailError: getErrorMessage(...) }) is called, also reset
the provider detail state (e.g., clear detailSnapshot, models, health, or any
detail.* fields) so the store no longer exposes the previous provider data. Use
the existing symbols providerResult, getErrorMessage, and set(...) to locate and
update the failure branch in the code that handles provider loading.
In `@web/src/utils/providers.ts`:
- Around line 91-97: In the 'health' comparator case in providers.ts (the block
using healthMap, HEALTH_ORDER and dir), handle undefined health values
explicitly before numeric comparison so unpolled/unknown providers always sort
last in both directions: first check if ha and/or hb are undefined and return 0
if both undefined, return 1 if ha is undefined (hb known) and -1 if hb is
undefined (ha known), and only if both are known fall back to the existing
numeric comparison using oa/ob and dir.
---
Duplicate comments:
In `@src/synthorg/providers/presets.py`:
- Around line 25-29: The docstring and preset catalog in
src/synthorg/providers/presets.py currently hardcode vendor names and model
identifiers (see symbols like litellm_provider, auth_type, supported_auth_types
and the preset entries between lines 64-279); extract all vendor-specific
literals (provider routing keys, model IDs, URLs, vendor descriptions) into a
new thin adapter/constants module (e.g., synthorg.providers.adapters or
synthorg.providers.constants) and replace the hardcoded values and docstring
examples with generic aliases (example-provider, example-large-001,
large/medium/small) so presets.py only references generic keys and the adapter
maps those keys to real vendor routing identifiers internally.
In `@web/src/components/ui/provider-health-badge.tsx`:
- Around line 32-59: Replace the duplicated dot/label markup in
ProviderHealthBadge with the shared StatusBadge component: import StatusBadge,
stop using DOT_COLOR_CLASSES and the inline dot span, and instead render
StatusBadge from ProviderHealthBadge (still passing through className,
label/pulse props and the resolved status or color). Use
getProviderHealthColor(status) or STATUS_LABELS only to map/derive the status
value if StatusBadge expects a color/variant prop, and pass that into
StatusBadge; remove any duplicate label semantics and dot markup so
ProviderHealthBadge becomes a thin wrapper that composes StatusBadge.
In `@web/src/pages/providers/ProviderCard.tsx`:
- Around line 18-63: ProviderCard is reimplementing the common card
shell/header/body instead of using the shared SectionCard; import SectionCard
and replace the outer div and manual header/body markup with SectionCard to
preserve shared spacing/behavior. Specifically: in ProviderCard, add an import
for SectionCard, replace the root <div className={cn(...)}> wrapper with
<SectionCard className={className} title={displayName} subtitle={subtitle}
icon={<Server className="size-4 shrink-0 text-text-secondary" />}> (or use
SectionCard’s header/children slots as available), remove the duplicated header
block and render ProviderHealthBadge into the SectionCard header or a
headerAction prop, and move the body nodes (auth_type formatting
provider.auth_type.replaceAll('_', ' '), provider.base_url, model count, and
health.calls_last_24h) into the SectionCard children so all content and logic
(provider.models.length pluralization and conditional health/base_url rendering)
remain unchanged.
In `@web/src/pages/providers/ProviderFormDrawer.tsx`:
- Around line 265-274: The hint text on the Base URL InputField is misleading
for presets that explicitly set default_base_url to null (which means the URL is
required); update the hint logic used in the InputField (the hint prop next to
InputField, inside the conditional that checks isCustom || preset != null ||
mode === 'edit') so it shows 'Optional for known cloud providers' only when
preset?.default_base_url is truthy, and for presets that exist but have
preset.default_base_url === null show a clear required hint such as 'Required
for this provider' (or omit the optional hint when the URL is required).
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: 3b21b95d-f3ab-488e-9600-b46b20b7795d
📒 Files selected for processing (33)
docs/design/brand-and-ux.mddocs/design/operations.mddocs/design/page-structure.mdsrc/synthorg/api/dto.pysrc/synthorg/config/schema.pysrc/synthorg/providers/drivers/litellm_driver.pysrc/synthorg/providers/management/_helpers.pysrc/synthorg/providers/management/service.pysrc/synthorg/providers/presets.pytests/unit/config/test_provider_auth.pytests/unit/providers/drivers/test_litellm_auth.pytests/unit/providers/management/test_helpers.pytests/unit/providers/management/test_service.pytests/unit/providers/management/test_service_discovery.pytests/unit/providers/test_presets.pyweb/src/components/ui/provider-health-badge.stories.tsxweb/src/components/ui/provider-health-badge.tsxweb/src/hooks/useProviderDetailData.tsweb/src/hooks/useProvidersData.tsweb/src/pages/ProviderDetailPage.tsxweb/src/pages/ProvidersPage.tsxweb/src/pages/providers/PresetPicker.tsxweb/src/pages/providers/ProviderCard.tsxweb/src/pages/providers/ProviderDetailHeader.tsxweb/src/pages/providers/ProviderDetailSkeleton.tsxweb/src/pages/providers/ProviderFilters.tsxweb/src/pages/providers/ProviderFormDrawer.tsxweb/src/pages/providers/ProviderGridView.tsxweb/src/pages/providers/ProviderHealthMetrics.tsxweb/src/pages/providers/ProviderModelList.tsxweb/src/pages/providers/TestConnectionResult.tsxweb/src/stores/providers.tsweb/src/utils/providers.ts
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: Test (Python 3.14)
🧰 Additional context used
📓 Path-based instructions (10)
**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
**/*.py: No 'from future import annotations' -- Python 3.14 has PEP 649
Use PEP 758 except syntax: 'except A, B:' (no parentheses) in Python 3.14
Add type hints to all public functions; enforce mypy strict mode
Use Google-style docstrings on all public classes and functions; enforce via ruff D rules
Create new objects rather than mutating existing ones; use copy.deepcopy() at construction for non-Pydantic internal collections and MappingProxyType wrapping for read-only enforcement
Distinguish config (frozen Pydantic models) from runtime state (mutable-via-copy models using model_copy); never mix static config fields with mutable runtime fields
Use Pydantic v2 (BaseModel, model_validator, computed_field, ConfigDict); use@computed_fieldfor derived values instead of redundant stored fields
Use NotBlankStr (from core.types) for all identifier/name fields in Pydantic models, including optional and tuple variants, instead of manual whitespace validators
Prefer asyncio.TaskGroup for fan-out/fan-in parallel operations in new code; use structured concurrency over bare create_task
Enforce 88-character line length via ruff
Keep functions under 50 lines; keep files under 800 lines
Handle errors explicitly; never silently swallow exceptions
Validate at system boundaries: user input, external APIs, config files
Files:
tests/unit/providers/test_presets.pytests/unit/providers/drivers/test_litellm_auth.pytests/unit/providers/management/test_service_discovery.pytests/unit/providers/management/test_service.pysrc/synthorg/providers/drivers/litellm_driver.pytests/unit/config/test_provider_auth.pytests/unit/providers/management/test_helpers.pysrc/synthorg/config/schema.pysrc/synthorg/providers/management/_helpers.pysrc/synthorg/providers/management/service.pysrc/synthorg/providers/presets.pysrc/synthorg/api/dto.py
tests/**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
tests/**/*.py: Use@pytest.mark.unit,@pytest.mark.integration,@pytest.mark.e2e,@pytest.mark.slowmarkers for tests
Enforce 80% minimum code coverage in CI
30-second timeout per test (global in pyproject.toml); non-default overrides like timeout(60) allowed but no default per-file markers
Prefer@pytest.mark.parametrizefor testing similar cases
Never skip, dismiss, or ignore flaky tests -- always fix them fully; for timing-sensitive tests, mock time.monotonic() and asyncio.sleep() instead of widening timing margins
For tasks that must block indefinitely until cancelled, use asyncio.Event().wait() instead of asyncio.sleep(large_number) -- it is cancellation-safe
Files:
tests/unit/providers/test_presets.pytests/unit/providers/drivers/test_litellm_auth.pytests/unit/providers/management/test_service_discovery.pytests/unit/providers/management/test_service.pytests/unit/config/test_provider_auth.pytests/unit/providers/management/test_helpers.py
web/src/**/*.{tsx,ts}
📄 CodeRabbit inference engine (CLAUDE.md)
web/src/**/*.{tsx,ts}: Always reuse existing components from web/src/components/ui/ before creating new ones
Use semantic Tailwind classes (text-foreground, bg-card, text-accent, text-success, bg-danger) or CSS variables (var(--so-*)); never hardcode hex values in .tsx/.ts files
Use font-sans or font-mono (maps to Geist tokens); never set fontFamily directly
Use density-aware tokens (p-card, gap-section-gap, gap-grid-gap) or standard Tailwind spacing; never hardcode pixel values for layout spacing
Use token variables (var(--so-shadow-card-hover), border-border, border-bright) for shadows/borders; never hardcode values
Do not recreate status dots inline -- use
Do not build card-with-header layouts from scratch -- use
Do not create metric displays with 'text-metric font-bold' -- use
Do not render initials circles manually -- use
Do not create complex (>8 line) JSX inside .map() -- extract to a shared component
Do not use rgba() with hardcoded values -- use design token variables
CSS side-effect imports need type declarations; Vite's '/// ' covers this in TS 6
Files:
web/src/pages/providers/ProviderDetailSkeleton.tsxweb/src/pages/providers/ProviderFilters.tsxweb/src/pages/providers/ProviderGridView.tsxweb/src/pages/providers/TestConnectionResult.tsxweb/src/pages/ProvidersPage.tsxweb/src/components/ui/provider-health-badge.tsxweb/src/pages/ProviderDetailPage.tsxweb/src/hooks/useProvidersData.tsweb/src/pages/providers/ProviderDetailHeader.tsxweb/src/components/ui/provider-health-badge.stories.tsxweb/src/pages/providers/PresetPicker.tsxweb/src/pages/providers/ProviderCard.tsxweb/src/pages/providers/ProviderModelList.tsxweb/src/hooks/useProviderDetailData.tsweb/src/pages/providers/ProviderHealthMetrics.tsxweb/src/pages/providers/ProviderFormDrawer.tsxweb/src/utils/providers.tsweb/src/stores/providers.ts
web/src/**/*
📄 CodeRabbit inference engine (CLAUDE.md)
PostToolUse hook (scripts/check_web_design_system.py) runs automatically on every Edit/Write to web/src/ files; fix all violations before proceeding
Files:
web/src/pages/providers/ProviderDetailSkeleton.tsxweb/src/pages/providers/ProviderFilters.tsxweb/src/pages/providers/ProviderGridView.tsxweb/src/pages/providers/TestConnectionResult.tsxweb/src/pages/ProvidersPage.tsxweb/src/components/ui/provider-health-badge.tsxweb/src/pages/ProviderDetailPage.tsxweb/src/hooks/useProvidersData.tsweb/src/pages/providers/ProviderDetailHeader.tsxweb/src/components/ui/provider-health-badge.stories.tsxweb/src/pages/providers/PresetPicker.tsxweb/src/pages/providers/ProviderCard.tsxweb/src/pages/providers/ProviderModelList.tsxweb/src/hooks/useProviderDetailData.tsweb/src/pages/providers/ProviderHealthMetrics.tsxweb/src/pages/providers/ProviderFormDrawer.tsxweb/src/utils/providers.tsweb/src/stores/providers.ts
docs/design/*.md
📄 CodeRabbit inference engine (CLAUDE.md)
Update the relevant docs/design/ page to reflect approved deviations from the spec
Files:
docs/design/page-structure.mddocs/design/brand-and-ux.mddocs/design/operations.md
src/synthorg/**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
src/synthorg/**/*.py: Every module with business logic must import and use logger from synthorg.observability.get_logger(name); variable name must always be 'logger'
Use constants from domain-specific modules under synthorg.observability.events for event names; import directly from events.
Always use structured logging: 'logger.info(EVENT, key=value)'; never use 'logger.info("msg %s", val)'
All error paths must log at WARNING or ERROR with context before raising
All state transitions must log at INFO level
Use DEBUG logging for object creation, internal flow, and entry/exit of key functions
Never use real vendor names (Anthropic, OpenAI, Claude, GPT, etc.) in project-owned code, docstrings, comments, tests, or config examples; use generic names: example-provider, example-large-001, example-medium-001, example-small-001, large/medium/small as aliases
Files:
src/synthorg/providers/drivers/litellm_driver.pysrc/synthorg/config/schema.pysrc/synthorg/providers/management/_helpers.pysrc/synthorg/providers/management/service.pysrc/synthorg/providers/presets.pysrc/synthorg/api/dto.py
src/synthorg/!(observability)/**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
Never use 'import logging', 'logging.getLogger()', or 'print()' in application code (except in observability/setup.py and observability/sinks.py for bootstrap code)
Files:
src/synthorg/providers/drivers/litellm_driver.pysrc/synthorg/config/schema.pysrc/synthorg/providers/management/_helpers.pysrc/synthorg/providers/management/service.pysrc/synthorg/providers/presets.pysrc/synthorg/api/dto.py
src/synthorg/providers/**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
src/synthorg/providers/**/*.py: All provider calls go through BaseCompletionProvider which applies retry + rate limiting automatically; never implement retry logic in driver subclasses or calling code
Set RetryConfig and RateLimiterConfig per-provider in ProviderConfig
Retryable errors (is_retryable=True): RateLimitError, ProviderTimeoutError, ProviderConnectionError, ProviderInternalError; non-retryable errors raise immediately
Rate limiter respects RateLimitError.retry_after from providers -- automatically pauses future requests
Files:
src/synthorg/providers/drivers/litellm_driver.pysrc/synthorg/providers/management/_helpers.pysrc/synthorg/providers/management/service.pysrc/synthorg/providers/presets.py
web/src/components/ui/*.{tsx,ts}
📄 CodeRabbit inference engine (CLAUDE.md)
For new shared React components: place in web/src/components/ui/ with kebab-case filename, create .stories.tsx with all states, export props as TypeScript interface, use design tokens exclusively
Files:
web/src/components/ui/provider-health-badge.tsxweb/src/components/ui/provider-health-badge.stories.tsx
web/src/**/*.stories.{tsx,ts}
📄 CodeRabbit inference engine (CLAUDE.md)
web/src/**/*.stories.{tsx,ts}: Use 'storybook/test' (not '@storybook/test'), 'storybook/actions' (not '@storybook/addon-actions') in Storybook 10
Use 'parameters.backgrounds.options' (object keyed by name) + 'initialGlobals.backgrounds.value' in Storybook 10 (replaces old default + values array)
Use 'parameters.a11y.test: "error" | "todo" | "off"' in Storybook 10 to enforce WCAG compliance
Files:
web/src/components/ui/provider-health-badge.stories.tsx
🧠 Learnings (42)
📓 Common learnings
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-20T08:28:32.845Z
Learning: Applies to src/synthorg/providers/**/*.py : Providers: LLM provider abstraction (LiteLLM adapter), auth types (api_key/oauth/custom_header/none), presets (PROVIDER_PRESETS), runtime CRUD (ProviderManagementService with asyncio.Lock serialization), hot-reload via AppState swap.
📚 Learning: 2026-03-20T08:28:32.845Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-20T08:28:32.845Z
Learning: Applies to src/synthorg/providers/**/*.py : Providers: LLM provider abstraction (LiteLLM adapter), auth types (api_key/oauth/custom_header/none), presets (PROVIDER_PRESETS), runtime CRUD (ProviderManagementService with asyncio.Lock serialization), hot-reload via AppState swap.
Applied to files:
tests/unit/providers/test_presets.pytests/unit/providers/drivers/test_litellm_auth.pydocs/design/page-structure.mdtests/unit/providers/management/test_service_discovery.pytests/unit/providers/management/test_service.pysrc/synthorg/providers/drivers/litellm_driver.pytests/unit/config/test_provider_auth.pytests/unit/providers/management/test_helpers.pysrc/synthorg/config/schema.pysrc/synthorg/providers/management/_helpers.pydocs/design/operations.mdweb/src/utils/providers.tssrc/synthorg/providers/management/service.pysrc/synthorg/providers/presets.pyweb/src/stores/providers.tssrc/synthorg/api/dto.py
📚 Learning: 2026-03-27T17:07:16.441Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T17:07:16.441Z
Learning: Applies to web/src/**/*.{tsx,ts} : Do not build card-with-header layouts from scratch -- use <SectionCard>
Applied to files:
web/src/pages/providers/ProviderDetailSkeleton.tsxweb/src/pages/providers/ProviderGridView.tsxweb/src/pages/providers/PresetPicker.tsxweb/src/pages/providers/ProviderCard.tsx
📚 Learning: 2026-03-15T18:28:13.207Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:28:13.207Z
Learning: Applies to tests/**/*.py : Test markers: pytest.mark.unit, pytest.mark.integration, pytest.mark.e2e, pytest.mark.slow. Coverage: 80% minimum (enforced in CI).
Applied to files:
tests/unit/providers/management/test_service_discovery.py
📚 Learning: 2026-03-17T22:08:13.456Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T22:08:13.456Z
Learning: Applies to tests/**/*.py : Test markers: `pytest.mark.unit`, `pytest.mark.integration`, `pytest.mark.e2e`, `pytest.mark.slow`. Coverage: 80% minimum. Async: `asyncio_mode = 'auto'` — no manual `pytest.mark.asyncio` needed. Timeout: 30 seconds per test. Parallelism: `pytest-xdist` via `-n auto` — ALWAYS include `-n auto` when running pytest, never run tests sequentially.
Applied to files:
tests/unit/providers/management/test_service_discovery.py
📚 Learning: 2026-03-27T17:07:16.441Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T17:07:16.441Z
Learning: Applies to tests/**/*.py : Use pytest.mark.unit, pytest.mark.integration, pytest.mark.e2e, pytest.mark.slow markers for tests
Applied to files:
tests/unit/providers/management/test_service_discovery.py
📚 Learning: 2026-03-15T18:28:13.207Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:28:13.207Z
Learning: Applies to tests/**/*.py : Tests must use test-provider, test-small-001, etc. for vendor-agnostic test data.
Applied to files:
tests/unit/providers/management/test_service_discovery.pytests/unit/config/test_provider_auth.pytests/unit/providers/management/test_helpers.pysrc/synthorg/config/schema.pysrc/synthorg/providers/presets.py
📚 Learning: 2026-03-17T18:52:05.142Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T18:52:05.142Z
Learning: Applies to src/synthorg/providers/**/*.py : All provider calls go through BaseCompletionProvider which applies retry + rate limiting automatically. Never implement retry logic in driver subclasses or calling code — it's handled by the base class.
Applied to files:
src/synthorg/providers/drivers/litellm_driver.py
📚 Learning: 2026-03-17T22:08:13.456Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T22:08:13.456Z
Learning: Applies to src/synthorg/**/*.py : All provider calls go through `BaseCompletionProvider` which applies retry + rate limiting automatically. Never implement retry logic in driver subclasses or calling code — it's handled by the base class.
Applied to files:
src/synthorg/providers/drivers/litellm_driver.py
📚 Learning: 2026-03-27T17:07:16.441Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T17:07:16.441Z
Learning: Applies to src/synthorg/providers/**/*.py : All provider calls go through BaseCompletionProvider which applies retry + rate limiting automatically; never implement retry logic in driver subclasses or calling code
Applied to files:
src/synthorg/providers/drivers/litellm_driver.py
📚 Learning: 2026-03-20T08:28:32.845Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-20T08:28:32.845Z
Learning: Applies to src/synthorg/**/*.py : All provider calls go through `BaseCompletionProvider` which applies retry + rate limiting automatically. Never implement retry logic in driver subclasses or calling code.
Applied to files:
src/synthorg/providers/drivers/litellm_driver.py
📚 Learning: 2026-03-27T17:07:16.441Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T17:07:16.441Z
Learning: Applies to web/src/**/*.{tsx,ts} : Always reuse existing components from web/src/components/ui/ before creating new ones
Applied to files:
web/src/pages/providers/ProviderFilters.tsxdocs/design/brand-and-ux.mdweb/src/pages/providers/PresetPicker.tsx
📚 Learning: 2026-03-27T17:07:16.441Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T17:07:16.441Z
Learning: Applies to web/src/components/ui/*.{tsx,ts} : For new shared React components: place in web/src/components/ui/ with kebab-case filename, create .stories.tsx with all states, export props as TypeScript interface, use design tokens exclusively
Applied to files:
web/src/pages/providers/ProviderFilters.tsxdocs/design/brand-and-ux.mdweb/src/components/ui/provider-health-badge.tsxweb/src/pages/providers/ProviderDetailHeader.tsxweb/src/components/ui/provider-health-badge.stories.tsxweb/src/pages/providers/PresetPicker.tsxweb/src/pages/providers/ProviderCard.tsxweb/src/pages/providers/ProviderModelList.tsxweb/src/pages/providers/ProviderFormDrawer.tsx
📚 Learning: 2026-03-27T17:07:16.441Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T17:07:16.441Z
Learning: Applies to web/src/**/*.{tsx,ts} : Use token variables (var(--so-shadow-card-hover), border-border, border-bright) for shadows/borders; never hardcode values
Applied to files:
web/src/pages/providers/ProviderFilters.tsxweb/src/pages/providers/PresetPicker.tsxweb/src/pages/providers/ProviderCard.tsx
📚 Learning: 2026-03-27T17:07:16.441Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T17:07:16.441Z
Learning: Applies to web/src/**/*.{tsx,ts} : Do not create complex (>8 line) JSX inside .map() -- extract to a shared component
Applied to files:
web/src/pages/providers/ProviderGridView.tsxweb/src/components/ui/provider-health-badge.tsxweb/src/pages/providers/PresetPicker.tsxweb/src/pages/providers/ProviderModelList.tsx
📚 Learning: 2026-03-15T18:28:13.207Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:28:13.207Z
Learning: Applies to {src,tests,web,cli,site}/**/*.{py,ts,tsx,go,astro} : Vendor-agnostic everywhere: NEVER use real vendor names (Anthropic, OpenAI, Claude, GPT, etc.) in project-owned code, docstrings, comments, tests, or config examples. Use generic names: example-provider, example-large-001, example-medium-001, example-small-001. Vendor names may only appear in: (1) Operations design page provider list (docs/design/operations.md), (2) .claude/ skill/agent files, (3) third-party import paths/module names.
Applied to files:
tests/unit/config/test_provider_auth.pysrc/synthorg/config/schema.pysrc/synthorg/providers/presets.py
📚 Learning: 2026-03-27T17:07:16.441Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T17:07:16.441Z
Learning: Applies to src/synthorg/**/*.py : Never use real vendor names (Anthropic, OpenAI, Claude, GPT, etc.) in project-owned code, docstrings, comments, tests, or config examples; use generic names: example-provider, example-large-001, example-medium-001, example-small-001, large/medium/small as aliases
Applied to files:
tests/unit/config/test_provider_auth.pysrc/synthorg/config/schema.pysrc/synthorg/providers/presets.py
📚 Learning: 2026-03-27T17:07:16.441Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T17:07:16.441Z
Learning: Applies to web/src/**/*.{tsx,ts} : Do not recreate status dots inline -- use <StatusBadge>
Applied to files:
docs/design/brand-and-ux.mdweb/src/components/ui/provider-health-badge.tsxweb/src/components/ui/provider-health-badge.stories.tsx
📚 Learning: 2026-03-15T21:20:09.993Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T21:20:09.993Z
Learning: Applies to web/src/components/** : Vue components organized by feature (agents/, approvals/, budget/, common/, dashboard/, layout/, messages/, org-chart/, tasks/).
Applied to files:
docs/design/brand-and-ux.md
📚 Learning: 2026-03-27T17:07:16.441Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T17:07:16.441Z
Learning: Applies to web/src/**/*.{tsx,ts} : Use semantic Tailwind classes (text-foreground, bg-card, text-accent, text-success, bg-danger) or CSS variables (var(--so-*)); never hardcode hex values in .tsx/.ts files
Applied to files:
docs/design/brand-and-ux.mdweb/src/pages/providers/ProviderCard.tsx
📚 Learning: 2026-03-20T11:18:48.128Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-20T11:18:48.128Z
Learning: Applies to src/synthorg/**/*.py : Set `RetryConfig` and `RateLimiterConfig` per-provider in `ProviderConfig`.
Applied to files:
src/synthorg/config/schema.pysrc/synthorg/providers/management/_helpers.pysrc/synthorg/providers/presets.py
📚 Learning: 2026-03-27T17:07:16.441Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T17:07:16.441Z
Learning: Applies to src/synthorg/providers/**/*.py : Set RetryConfig and RateLimiterConfig per-provider in ProviderConfig
Applied to files:
src/synthorg/config/schema.pysrc/synthorg/providers/management/_helpers.pysrc/synthorg/providers/presets.py
📚 Learning: 2026-03-16T19:13:36.562Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-16T19:13:36.562Z
Learning: Applies to src/synthorg/providers/**/*.py : RetryConfig and RateLimiterConfig are set per-provider in ProviderConfig.
Applied to files:
src/synthorg/config/schema.pysrc/synthorg/providers/management/_helpers.pysrc/synthorg/providers/presets.py
📚 Learning: 2026-03-17T22:08:13.456Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T22:08:13.456Z
Learning: Applies to src/synthorg/**/*.py : Use Pydantic v2 conventions: `BaseModel`, `model_validator`, `computed_field`, `ConfigDict`. For derived values use `computed_field` instead of storing + validating redundant fields. Use `NotBlankStr` (from `core.types`) for all identifier/name fields — including optional (`NotBlankStr | None`) and tuple (`tuple[NotBlankStr, ...]`) variants — instead of manual whitespace validators.
Applied to files:
src/synthorg/config/schema.pysrc/synthorg/providers/presets.py
📚 Learning: 2026-03-15T19:14:27.144Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T19:14:27.144Z
Learning: Applies to src/synthorg/**/*.py : Use Pydantic v2 BaseModel, model_validator, computed_field, ConfigDict.
Applied to files:
src/synthorg/config/schema.pysrc/synthorg/providers/presets.py
📚 Learning: 2026-03-19T11:33:01.580Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T11:33:01.580Z
Learning: Applies to web/**/*.vue : Never use real vendor names (Anthropic, OpenAI, Claude, GPT, etc.) in dashboard code — use generic names: `example-provider`, `example-large-001`, etc.
Applied to files:
src/synthorg/config/schema.pysrc/synthorg/providers/presets.py
📚 Learning: 2026-03-15T19:14:27.144Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T19:14:27.144Z
Learning: Applies to src/synthorg/**/*.py : Use frozen Pydantic models for config/identity; use separate mutable-via-copy models (using model_copy(update=...)) for runtime state that evolves. Never mix static config fields with mutable runtime fields in one model.
Applied to files:
src/synthorg/config/schema.py
📚 Learning: 2026-03-15T18:42:17.990Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T18:42:17.990Z
Learning: Applies to src/synthorg/**/*.py : Use Pydantic v2 conventions: `BaseModel`, `model_validator`, `computed_field`, `ConfigDict`
Applied to files:
src/synthorg/config/schema.pysrc/synthorg/providers/presets.py
📚 Learning: 2026-03-15T19:14:27.144Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T19:14:27.144Z
Learning: Applies to src/synthorg/**/*.py : Use Pydantic v2 with adopted conventions: use computed_field for derived values instead of storing + validating redundant fields; use NotBlankStr from core.types for all identifier/name fields (including optional and tuple variants) instead of manual whitespace validators.
Applied to files:
src/synthorg/config/schema.pysrc/synthorg/providers/presets.py
📚 Learning: 2026-03-27T17:07:16.441Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T17:07:16.441Z
Learning: Applies to web/src/**/*.stories.{tsx,ts} : Use 'storybook/test' (not 'storybook/test'), 'storybook/actions' (not 'storybook/addon-actions') in Storybook 10
Applied to files:
web/src/components/ui/provider-health-badge.stories.tsx
📚 Learning: 2026-03-27T17:07:16.441Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T17:07:16.441Z
Learning: Applies to web/src/**/*.stories.{tsx,ts} : Use 'parameters.a11y.test: "error" | "todo" | "off"' in Storybook 10 to enforce WCAG compliance
Applied to files:
web/src/components/ui/provider-health-badge.stories.tsx
📚 Learning: 2026-03-27T17:07:16.441Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T17:07:16.441Z
Learning: Applies to web/src/**/*.stories.{tsx,ts} : Use 'parameters.backgrounds.options' (object keyed by name) + 'initialGlobals.backgrounds.value' in Storybook 10 (replaces old default + values array)
Applied to files:
web/src/components/ui/provider-health-badge.stories.tsx
📚 Learning: 2026-03-27T17:07:16.441Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T17:07:16.441Z
Learning: Applies to web/.storybook/**/*.{ts,tsx} : Use 'defineMain' from 'storybook/react-vite/node' and 'definePreview' from 'storybook/react-vite' in Storybook 10; include explicit 'framework' field
Applied to files:
web/src/components/ui/provider-health-badge.stories.tsx
📚 Learning: 2026-03-27T17:07:16.441Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T17:07:16.441Z
Learning: Applies to web/src/**/*.{tsx,ts} : Do not create metric displays with 'text-metric font-bold' -- use <MetricCard>
Applied to files:
web/src/pages/providers/PresetPicker.tsxweb/src/pages/providers/ProviderCard.tsxweb/src/pages/providers/ProviderHealthMetrics.tsx
📚 Learning: 2026-03-27T17:07:16.441Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T17:07:16.441Z
Learning: Applies to web/src/**/*.{tsx,ts} : Use density-aware tokens (p-card, gap-section-gap, gap-grid-gap) or standard Tailwind spacing; never hardcode pixel values for layout spacing
Applied to files:
web/src/pages/providers/ProviderCard.tsx
📚 Learning: 2026-03-27T17:07:16.441Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T17:07:16.441Z
Learning: Applies to web/src/**/*.{tsx,ts} : Do not use rgba() with hardcoded values -- use design token variables
Applied to files:
web/src/pages/providers/ProviderCard.tsx
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/api/**/*.py : API package (api/): Litestar REST + WebSocket with controllers, guards, channels, JWT + API key + WS ticket auth, approval gate integration, coordination endpoint, collaboration endpoint, settings endpoint, provider management endpoint (CRUD + test + presets), backup endpoint, RFC 9457 structured errors, AppState hot-reload slots, service auto-wiring (Phase 1 at construction, Phase 2 on startup), lifecycle helpers
Applied to files:
docs/design/operations.md
📚 Learning: 2026-03-20T21:44:04.528Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-20T21:44:04.528Z
Learning: Applies to src/synthorg/**/*.py : Use `copy.deepcopy()` at system boundaries (tool execution, LLM provider serialization, inter-agent delegation, persistence serialization) for `dict`/`list` fields
Applied to files:
src/synthorg/providers/presets.py
📚 Learning: 2026-03-15T19:14:27.144Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T19:14:27.144Z
Learning: Applies to src/synthorg/**/*.py : For dict/list fields in frozen Pydantic models, rely on frozen=True for field reassignment prevention and copy.deepcopy() at system boundaries (tool execution, LLM provider serialization, inter-agent delegation, serializing for persistence).
Applied to files:
src/synthorg/providers/presets.py
📚 Learning: 2026-03-26T15:18:16.848Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-26T15:18:16.848Z
Learning: Applies to src/**/*.py : Use Pydantic v2 conventions: `BaseModel`, `model_validator`, `computed_field`, `ConfigDict`. Use `computed_field` for derived values instead of storing redundant fields. Use `NotBlankStr` for all identifier/name fields.
Applied to files:
src/synthorg/providers/presets.py
📚 Learning: 2026-03-17T11:41:02.964Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-17T11:41:02.964Z
Learning: Applies to src/**/*.py : Models: Pydantic v2 (`BaseModel`, `model_validator`, `computed_field`, `ConfigDict`). Use `computed_field` for derived values instead of storing + validating redundant fields. Use `NotBlankStr` for all identifier/name fields — including optional (`NotBlankStr | None`) and tuple (`tuple[NotBlankStr, ...]`) variants — instead of manual whitespace validators.
Applied to files:
src/synthorg/providers/presets.py
📚 Learning: 2026-03-20T08:28:32.845Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-20T08:28:32.845Z
Learning: Applies to src/synthorg/**/*.py : `RetryConfig` and `RateLimiterConfig` are set per-provider in `ProviderConfig`. Retryable errors: `RateLimitError`, `ProviderTimeoutError`, `ProviderConnectionError`, `ProviderInternalError`. Non-retryable errors raise immediately.
Applied to files:
src/synthorg/providers/presets.py
🪛 GitHub Check: Dashboard Lint
web/src/pages/providers/ProviderFormDrawer.tsx
[warning] 66-66:
Do not call the 'set' function 'setLitellmProvider' of 'useState' synchronously in an effect. This can lead to unnecessary re-renders and performance issues
[warning] 65-65:
Do not call the 'set' function 'setBaseUrl' of 'useState' synchronously in an effect. This can lead to unnecessary re-renders and performance issues
[warning] 64-64:
Do not call the 'set' function 'setAuthType' of 'useState' synchronously in an effect. This can lead to unnecessary re-renders and performance issues
[warning] 63-63:
Do not call the 'set' function 'setName' of 'useState' synchronously in an effect. This can lead to unnecessary re-renders and performance issues
🔇 Additional comments (18)
tests/unit/providers/test_presets.py (1)
76-92: Good invariant coverage.This catches preset auth metadata drift at construction time instead of later in the create-from-preset flow.
web/src/components/ui/provider-health-badge.stories.tsx (1)
4-9: Nice: the Storybook a11y gate is enabled.The shared component is now covered by Storybook's WCAG check path.
docs/design/page-structure.md (1)
100-107: LGTM!The Providers page documentation accurately reflects the new features: health status display (up/degraded/down), health metrics (response time, error rate, call count), subscription auth with ToS acceptance, and filtering/sorting capabilities. The API endpoint list is comprehensive.
src/synthorg/providers/drivers/litellm_driver.py (2)
147-147: LGTM!The routing key initialization correctly uses
config.litellm_providerwith a fallback toprovider_name, allowing decoupled display names from LiteLLM routing identifiers.
353-359: LGTM!The
AuthType.SUBSCRIPTIONbranch correctly sends the subscription token as a Bearer authorization header viaextra_headers. This aligns with standard token-based authentication patterns.tests/unit/providers/management/test_service.py (1)
442-524: LGTM!Excellent test coverage for
AuthType.SUBSCRIPTIONcredential transitions:
- Switching from SUBSCRIPTION to API_KEY correctly clears
subscription_tokenandtos_accepted_at- Switching to SUBSCRIPTION correctly sets
subscription_token, clearsapi_key, and stampstos_accepted_at- Re-accepting ToS correctly re-stamps the timestamp
- Validation correctly rejects clearing
subscription_tokenwhile remaining on SUBSCRIPTIONThe tests use vendor-neutral fixture values as per coding guidelines.
tests/unit/providers/management/test_service_discovery.py (1)
364-373: LGTM!The mock
ProviderPresetobjects correctly include the new required fieldslitellm_providerandsupported_auth_types. This keeps the test fixtures aligned with the updated preset schema while using vendor-neutral values ("test-local").web/src/pages/providers/ProviderGridView.tsx (1)
1-64: LGTM!Well-structured component with:
ProviderGridItemcorrectly extracted to keep the.map()body concise (addressing the prior review)- Proper URL encoding via
encodeURIComponent(provider.name)- Density-aware spacing with
gap-grid-gap- Responsive breakpoints matching the design spec
- Reuse of existing
EmptyStateandStaggerGroupcomponentstests/unit/config/test_provider_auth.py (1)
121-164: LGTM!Comprehensive validation tests for
AuthType.SUBSCRIPTION:
- Success case with both
subscription_tokenandtos_accepted_at- Failure cases for missing token, missing ToS, and missing both
litellm_providerfield storage and default behaviorTests correctly use vendor-neutral fixture values (
"test-subscription-token","test-provider") as per coding guidelines.web/src/pages/providers/TestConnectionResult.tsx (1)
1-41: LGTM!Well-implemented accessible status banner:
role="status",aria-live="polite",aria-atomic="true"ensure screen reader announcements- Semantic Tailwind classes (
bg-success/10,text-danger) for consistent theming- Error text uses
break-wordsinstead oftruncateto prevent clipping (addressing prior review)- Proper null-coalescing for optional fields
src/synthorg/providers/management/service.py (2)
359-410: LGTM!The
create_from_presetchanges correctly implement auth type override semantics:
request.auth_typetakes precedence overpreset.auth_typesubscription_tokenandtos_acceptedare forwarded for subscription auth flowslitellm_provideris passed from the preset for proper LiteLLM routing- Discovery condition uses the effective
auth_type, not the preset defaultThe docstring accurately describes the override behavior.
412-447: LGTM!The
_maybe_discover_preset_modelsrefactor correctly:
- Accepts
auth_typeas a keyword-only parameter- Uses the effective
auth_type(from request override or preset) for the discovery condition- Docstring accurately documents the parameter's purpose
src/synthorg/providers/management/_helpers.py (1)
28-38: Good subscription-only gating on create.
subscription_tokenandtos_accepted_atare now only materialized forAuthType.SUBSCRIPTION, which closes the stale-consent / stale-token carryover path on create.tests/unit/providers/management/test_helpers.py (1)
45-154: Nice regression coverage for subscription transitions.These cases pin the token-clearing and ToS-restamping behavior that is easiest to regress in
apply_update()and_apply_credential_updates().web/src/pages/providers/ProviderFormDrawer.tsx (4)
12-16: LGTM! Auth options correctly limited to implemented types.The AUTH_OPTIONS array now only includes authentication modes that the drawer can fully configure (
api_key,subscription,none), which prevents users from selecting OAuth or custom header auth that cannot be properly set up through this form.
71-104: LGTM! Preset changes and auth type handling are well-implemented.The effect correctly resets all form fields when switching to custom mode, and properly auto-fills from preset data otherwise. The
availableAuthTypesfiltering ensures users only see auth options supported by the selected preset.
131-155: LGTM! Create mode submission logic is correct.The payload construction correctly differentiates between preset-based and custom creation, conditionally includes credentials based on auth type, and properly handles the
tos_acceptedflag for subscription auth.
227-253: LGTM! Subscription auth flow properly gates token input behind ToS acceptance.The two-step flow (warning banner → accept dialog → token input) ensures users are aware of the Terms of Service implications before configuring subscription auth. The warning text appropriately communicates the risks.
Also applies to: 304-316
| # Required fields per auth type (field_name -> attribute_name). | ||
| _AUTH_REQUIRED_FIELDS: ClassVar[dict[AuthType, tuple[str, ...]]] = { | ||
| AuthType.OAUTH: ( | ||
| "oauth_token_url", | ||
| "oauth_client_id", | ||
| "oauth_client_secret", | ||
| ), | ||
| AuthType.CUSTOM_HEADER: ( | ||
| "custom_header_name", | ||
| "custom_header_value", | ||
| ), | ||
| AuthType.SUBSCRIPTION: ( | ||
| "subscription_token", | ||
| "tos_accepted_at", | ||
| ), | ||
| } | ||
|
|
||
| @model_validator(mode="after") | ||
| def _validate_auth_fields(self) -> Self: | ||
| """Validate auth fields based on auth_type.""" | ||
| if self.auth_type == AuthType.OAUTH: | ||
| missing: list[str] = [] | ||
| if self.oauth_token_url is None: | ||
| missing.append("oauth_token_url") | ||
| if self.oauth_client_id is None: | ||
| missing.append("oauth_client_id") | ||
| if self.oauth_client_secret is None: | ||
| missing.append("oauth_client_secret") | ||
| if missing: | ||
| msg = f"OAuth auth_type requires: {', '.join(missing)}" | ||
| logger.warning( | ||
| CONFIG_VALIDATION_FAILED, | ||
| model="ProviderConfig", | ||
| error=msg, | ||
| ) | ||
| raise ValueError(msg) | ||
| elif self.auth_type == AuthType.CUSTOM_HEADER: | ||
| missing = [] | ||
| if self.custom_header_name is None: | ||
| missing.append("custom_header_name") | ||
| if self.custom_header_value is None: | ||
| missing.append("custom_header_value") | ||
| if missing: | ||
| msg = f"Custom header auth_type requires: {', '.join(missing)}" | ||
| logger.warning( | ||
| CONFIG_VALIDATION_FAILED, | ||
| model="ProviderConfig", | ||
| error=msg, | ||
| ) | ||
| raise ValueError(msg) | ||
| required = self._AUTH_REQUIRED_FIELDS.get(self.auth_type) | ||
| if required is None: | ||
| return self | ||
| missing = [f for f in required if getattr(self, f) is None] | ||
| if missing: | ||
| label = self.auth_type.value.replace("_", " ").title() | ||
| msg = f"{label} auth_type requires: {', '.join(missing)}" | ||
| logger.warning( | ||
| CONFIG_VALIDATION_FAILED, | ||
| model="ProviderConfig", | ||
| error=msg, | ||
| ) | ||
| raise ValueError(msg) |
There was a problem hiding this comment.
AuthType.API_KEY now bypasses required-field validation.
Because _AUTH_REQUIRED_FIELDS has no API-key entry, ProviderConfig(auth_type=AuthType.API_KEY) returns early on Line 229 and accepts api_key=None. That silently reintroduces invalid provider configs and drifts from src/synthorg/providers/management/_helpers.py, which still treats api_key as the owned field for AuthType.API_KEY.
Suggested fix
_AUTH_REQUIRED_FIELDS: ClassVar[dict[AuthType, tuple[str, ...]]] = {
+ AuthType.API_KEY: ("api_key",),
+ AuthType.NONE: (),
AuthType.OAUTH: (
"oauth_token_url",
"oauth_client_id",
"oauth_client_secret",
),🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/synthorg/config/schema.py` around lines 208 - 240, The validation
currently skips AuthType.API_KEY because _AUTH_REQUIRED_FIELDS lacks an entry,
allowing ProviderConfig(auth_type=AuthType.API_KEY) with api_key=None; update
_AUTH_REQUIRED_FIELDS to include AuthType.API_KEY mapped to ("api_key",) so
_validate_auth_fields will detect a missing api_key and log/raise the same
ValueError as other auth types (keep the existing logger usage and message
construction in _validate_auth_fields).
| _UPDATE_FIELDS: tuple[str, ...] = ( | ||
| "driver", | ||
| "litellm_provider", | ||
| "base_url", |
There was a problem hiding this comment.
litellm_provider can be set but not cleared.
The partial-update merge only copies non-None values, so once an override is saved there is no way to revert to the default name-based routing behavior. Please add an explicit clear path for litellm_provider or honor an explicit null for that field.
Also applies to: 101-105
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/synthorg/providers/management/_helpers.py` around lines 50 - 53, The
update logic currently only merges non-None values and thus cannot clear a
previously set override for litellm_provider; modify the partial-update/merge
routine that uses UPDATE_FIELDS (and any update function that iterates those
fields) to treat an explicit None for "litellm_provider" as a valid intent to
clear the override (i.e., when the incoming payload has litellm_provider present
and is None, set the stored value to None) or add a specific clear path for
litellm_provider so saved overrides can be reverted to default name-based
routing; ensure UPDATE_FIELDS still contains "litellm_provider" so it is
processed and update any conditional that currently skips None values to allow
explicit null for this field.
| @model_validator(mode="after") | ||
| def _validate_auth_type_in_supported(self) -> Self: | ||
| """Ensure default auth_type is in the supported set.""" | ||
| if self.auth_type not in self.supported_auth_types: | ||
| msg = ( | ||
| f"auth_type {self.auth_type!r} not in " | ||
| f"supported_auth_types {self.supported_auth_types!r}" | ||
| ) | ||
| raise ValueError(msg) |
There was a problem hiding this comment.
Log this validation failure before raising.
Line 60 raises directly, so preset metadata drift disappears from backend diagnostics. Add the module logger and emit a structured warning with the preset name/auth fields before raising. As per coding guidelines, "Every module with business logic must import and use logger from synthorg.observability.get_logger(name); variable name must always be 'logger'" and "All error paths must log at WARNING or ERROR with context before raising".
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/synthorg/providers/presets.py` around lines 52 - 60, Import logger =
get_logger(__name__) from synthorg.observability at the top of the module and,
inside the model_validator method _validate_auth_type_in_supported, log a
structured warning (using logger.warning) immediately before raising the
ValueError; include context fields like the preset name (self.name),
self.auth_type and self.supported_auth_types in the log message so diagnostics
capture the failing preset metadata, then raise the same ValueError as currently
done.
| interface ProviderModelListProps { | ||
| models: readonly ProviderModelConfig[] | ||
| } |
There was a problem hiding this comment.
This table still omits per-model capabilities.
Linked issue #785 calls for model listings with capabilities, but the rendered contract/columns stop at ID, alias, context, and costs. The current ProviderModelConfig shape also has no capability field, so the detail view still cannot satisfy that requirement.
Also applies to: 43-57
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@web/src/pages/providers/ProviderModelList.tsx` around lines 28 - 30, The
model list UI omits per-model capabilities because ProviderModelConfig lacks a
capabilities field and ProviderModelList (and its prop type
ProviderModelListProps) only renders id, alias, context, and costs; add a
capabilities property (e.g., string[] or a typed Capability enum) to the
ProviderModelConfig type, update ProviderModelListProps to accept models with
that field, and modify the ProviderModelList component rendering logic to add a
new "Capabilities" column/cell that maps each model's capabilities into
human-readable badges or text; also update any data mapping (where models are
constructed or fetched) to populate the new capabilities field.
| // Track latest request IDs to prevent stale responses | ||
| let _listRequestId = 0 | ||
| let _detailRequestName = '' | ||
|
|
There was a problem hiding this comment.
The detail stale-request guard doesn't protect same-provider overlaps.
Because the guard only keys on name, two concurrent fetchProviderDetail("x") calls both pass Line 161. A slower older response can overwrite the newer one, which is likely with polling plus discoverModels() or a manual refresh.
Suggested fix
-// Track latest request IDs to prevent stale responses
+// Track latest request IDs to prevent stale responses
let _listRequestId = 0
-let _detailRequestName = ''
+let _detailRequestId = 0
...
fetchProviderDetail: async (name: string) => {
- _detailRequestName = name
+ const requestId = ++_detailRequestId
set({ detailLoading: true, detailError: null })
...
- if (_detailRequestName !== name) return
+ if (requestId !== _detailRequestId) return
...
- if (_detailRequestName !== name) return
+ if (requestId !== _detailRequestId) return
...
clearDetail: () => {
- _detailRequestName = ''
+ _detailRequestId++Also applies to: 150-161
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@web/src/stores/providers.ts` around lines 81 - 84, The detail stale-request
guard currently keys only on _detailRequestName so two concurrent
fetchProviderDetail("x") calls can both pass the early-return check and let an
older response overwrite a newer one; fix by mirroring the list request pattern:
introduce a numeric request id (e.g., _detailRequestId) and in
fetchProviderDetail increment and capture a local id before the async call, then
after awaiting compare the captured id against the current _detailRequestId (and
optionally _detailRequestName) and only apply response state if they match;
update all checks in fetchProviderDetail (the guard at lines ~150-161 and the
response handling) to use this id-based comparison to prevent same-name
overlaps.
| fetchProviders: async () => { | ||
| // Skip if a CRUD mutation is in flight | ||
| if (get().mutating) return |
There was a problem hiding this comment.
This guard drops every post-mutation list refresh.
createProvider, createFromPreset, updateProvider, and the delete-error recovery all call fetchProviders() before clearing mutating, so Line 115 returns immediately and the list stays stale until the next poll. Either remove this guard or add a forced-refresh path for those callers.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@web/src/stores/providers.ts` around lines 113 - 115, The fetchProviders
function currently bails early when get().mutating is true, which prevents
callers like createProvider, createFromPreset, updateProvider and the
delete-error-recovery path from triggering an immediate list refresh; change
fetchProviders to accept a force boolean (e.g., fetchProviders(force = false))
and only skip the refresh when mutating is true AND force is false, then update
the callers (createProvider, createFromPreset, updateProvider and the
delete-error-recovery code) to call fetchProviders(true) after their operation
so they always refresh the list immediately; alternatively, remove the mutating
guard entirely if you want every caller to refresh regardless of mutating state.
| case 'health': { | ||
| const ha = healthMap[a.name]?.health_status | ||
| const hb = healthMap[b.name]?.health_status | ||
| const unknownOrder = Object.keys(HEALTH_ORDER).length | ||
| const oa = ha ? HEALTH_ORDER[ha] : unknownOrder | ||
| const ob = hb ? HEALTH_ORDER[hb] : unknownOrder | ||
| return dir * (oa - ob) |
There was a problem hiding this comment.
Unknown health still sorts first in desc.
Using a trailing bucket only fixes ascending order. With dir = -1, the larger unknownOrder on Lines 94-96 moves unpolled providers ahead of healthy ones. Handle undefined separately so unknowns stay last in both directions.
Suggested fix
case 'health': {
const ha = healthMap[a.name]?.health_status
const hb = healthMap[b.name]?.health_status
- const unknownOrder = Object.keys(HEALTH_ORDER).length
- const oa = ha ? HEALTH_ORDER[ha] : unknownOrder
- const ob = hb ? HEALTH_ORDER[hb] : unknownOrder
+ if (!ha || !hb) {
+ if (!ha && !hb) return 0
+ return ha ? -1 : 1
+ }
+ const oa = HEALTH_ORDER[ha]
+ const ob = HEALTH_ORDER[hb]
return dir * (oa - ob)
}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@web/src/utils/providers.ts` around lines 91 - 97, In the 'health' comparator
case in providers.ts (the block using healthMap, HEALTH_ORDER and dir), handle
undefined health values explicitly before numeric comparison so unpolled/unknown
providers always sort last in both directions: first check if ha and/or hb are
undefined and return 0 if both undefined, return 1 if ha is undefined (hb known)
and -1 if hb is undefined (ha known), and only if both are known fall back to
the existing numeric comparison using oa/ob and dir.
- operations.md: fix subscription_token wording (not OAuth, not encrypted at rest)
- operations.md: add GET /providers/{name}/health to API Surface table
- stores/providers.ts: clear stale detail data when getProvider fails
- ProviderFormDrawer: show "Required" hint for presets with null base URL
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
@coderabbitai review |
|
@coderabbitai review |
✅ Actions performedReview triggered.
|
✅ Actions performedReview triggered.
|
There was a problem hiding this comment.
♻️ Duplicate comments (4)
web/src/stores/providers.ts (2)
113-115:⚠️ Potential issue | 🟠 MajorThis guard drops every post-mutation list refresh.
createProvider,createFromPreset,updateProvider, and delete-error-recovery all callfetchProviders()beforemutatingis set tofalse(infinally), so Line 115 returns immediately and the list stays stale until the next poll.Either remove the guard or add a force parameter:
- fetchProviders: async () => { - // Skip if a CRUD mutation is in flight - if (get().mutating) return + fetchProviders: async (force = false) => { + // Skip if a CRUD mutation is in flight (unless forced) + if (get().mutating && !force) returnThen update callers:
- await get().fetchProviders() + await get().fetchProviders(true)🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@web/src/stores/providers.ts` around lines 113 - 115, The fetchProviders guard using the mutating flag prevents post-mutation refreshes because callers (createProvider, createFromPreset, updateProvider, and delete-error-recovery) call fetchProviders() before mutating is cleared; change fetchProviders to accept an optional force boolean (e.g., fetchProviders(force = false)) and short-circuit only when mutating && !force, or remove the guard entirely; then update all callers (createProvider, createFromPreset, updateProvider, and the delete-error-recovery path) to call fetchProviders(true) when they need an immediate refresh so the list is not left stale.
81-84:⚠️ Potential issue | 🟠 MajorThe detail stale-request guard doesn't protect same-provider overlaps.
The guard keys only on
name, so two concurrentfetchProviderDetail("x")calls both pass Line 161. A slower older response can overwrite the newer one (common with polling plusdiscoverModels()or manual refresh).Use a numeric request ID instead:
-let _detailRequestName = '' +let _detailRequestId = 0Then in
fetchProviderDetail:fetchProviderDetail: async (name: string) => { - _detailRequestName = name + const requestId = ++_detailRequestId set({ detailLoading: true, detailError: null }) ... - if (_detailRequestName !== name) return + if (requestId !== _detailRequestId) return🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@web/src/stores/providers.ts` around lines 81 - 84, The stale-response guard for provider details currently uses _detailRequestName (string) and allows two concurrent fetchProviderDetail("x") calls to collide; replace that with a numeric request id: add a module-level _detailRequestId (number), increment it at the start of fetchProviderDetail, capture the current id in a local variable before awaiting the network call, and before applying the response verify the captured id still equals _detailRequestId; update fetchProviderDetail to stop relying on _detailRequestName and remove/replace any checks that compare names so only matching numeric ids permit state updates.web/src/pages/providers/ProviderFormDrawer.tsx (2)
60-69: 🧹 Nitpick | 🔵 TrivialConsider batching state updates to reduce re-renders.
Multiple synchronous
setStatecalls trigger separate re-renders. While React 18+ batches most updates automatically, effects are not always batched. A unified form state object would be cleaner:♻️ Optional: Batch with a form state object
- const [name, setName] = useState('') - const [authType, setAuthType] = useState<AuthType>('api_key') - const [baseUrl, setBaseUrl] = useState('') - const [litellmProvider, setLitellmProvider] = useState('') + const [formState, setFormState] = useState({ + name: '', + authType: 'api_key' as AuthType, + baseUrl: '', + litellmProvider: '', + }) // Then in the effect: useEffect(() => { if (mode === 'edit' && provider) { - setName(provider.name) - setAuthType(provider.auth_type) - setBaseUrl(provider.base_url ?? '') - setLitellmProvider(provider.litellm_provider ?? '') + setFormState({ + name: provider.name, + authType: provider.auth_type, + baseUrl: provider.base_url ?? '', + litellmProvider: provider.litellm_provider ?? '', + }) setTosAccepted(provider.tos_accepted_at !== null) } }, [mode, provider])🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@web/src/pages/providers/ProviderFormDrawer.tsx` around lines 60 - 69, The effect in ProviderFormDrawer uses multiple synchronous setters (setName, setAuthType, setBaseUrl, setLitellmProvider, setTosAccepted) causing multiple re-renders; modify the state to batch updates by replacing these individual setters with a single form state update (e.g., setForm or setProviderForm) inside the useEffect for mode === 'edit' and provider, or use a functional update that sets all fields at once; update references in the component to read from the unified form state instead of the separate state hooks and ensure useEffect depends on [mode, provider] only.
156-167:⚠️ Potential issue | 🟠 MajorEdit mode cannot clear
litellm_providerandbase_urloverrides.Sending
undefinedfor these fields has no effect—the backend'sapply_updateskipsNonevalues. Unlike credentials, there are noclear_litellm_providerorclear_base_urlflags inUpdateProviderRequest.Until the backend adds those flags, consider either:
- Documenting this limitation in the UI (hint text noting overrides cannot be removed)
- Sending
nullexplicitly if the backend evolves to support itThe
clear_api_keyandclear_subscription_tokenlogic on lines 161 and 163 is correctly implemented.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@web/src/pages/providers/ProviderFormDrawer.tsx` around lines 156 - 167, Edit mode currently sends undefined for litellm_provider and base_url when clearing them, but the backend ignores None values so overrides cannot be removed; update ProviderFormDrawer (the edit branch that calls useProvidersStore.getState().updateProvider) to either (A) surface this limitation in the UI by adding hint/help text next to the litellm_provider and baseUrl inputs informing users that overrides cannot be removed via edit, or (B) if you prefer to attempt future-proofing, send explicit null for those fields instead of undefined (i.e., set litellm_provider: litellmProvider === '' ? null : litellmProvider and base_url: baseUrl === '' ? null : baseUrl) so the payload will carry null if backend later honors it; update the code around the updateProvider call accordingly and keep the existing clear_api_key / clear_subscription_token logic unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Duplicate comments:
In `@web/src/pages/providers/ProviderFormDrawer.tsx`:
- Around line 60-69: The effect in ProviderFormDrawer uses multiple synchronous
setters (setName, setAuthType, setBaseUrl, setLitellmProvider, setTosAccepted)
causing multiple re-renders; modify the state to batch updates by replacing
these individual setters with a single form state update (e.g., setForm or
setProviderForm) inside the useEffect for mode === 'edit' and provider, or use a
functional update that sets all fields at once; update references in the
component to read from the unified form state instead of the separate state
hooks and ensure useEffect depends on [mode, provider] only.
- Around line 156-167: Edit mode currently sends undefined for litellm_provider
and base_url when clearing them, but the backend ignores None values so
overrides cannot be removed; update ProviderFormDrawer (the edit branch that
calls useProvidersStore.getState().updateProvider) to either (A) surface this
limitation in the UI by adding hint/help text next to the litellm_provider and
baseUrl inputs informing users that overrides cannot be removed via edit, or (B)
if you prefer to attempt future-proofing, send explicit null for those fields
instead of undefined (i.e., set litellm_provider: litellmProvider === '' ? null
: litellmProvider and base_url: baseUrl === '' ? null : baseUrl) so the payload
will carry null if backend later honors it; update the code around the
updateProvider call accordingly and keep the existing clear_api_key /
clear_subscription_token logic unchanged.
In `@web/src/stores/providers.ts`:
- Around line 113-115: The fetchProviders guard using the mutating flag prevents
post-mutation refreshes because callers (createProvider, createFromPreset,
updateProvider, and delete-error-recovery) call fetchProviders() before mutating
is cleared; change fetchProviders to accept an optional force boolean (e.g.,
fetchProviders(force = false)) and short-circuit only when mutating && !force,
or remove the guard entirely; then update all callers (createProvider,
createFromPreset, updateProvider, and the delete-error-recovery path) to call
fetchProviders(true) when they need an immediate refresh so the list is not left
stale.
- Around line 81-84: The stale-response guard for provider details currently
uses _detailRequestName (string) and allows two concurrent
fetchProviderDetail("x") calls to collide; replace that with a numeric request
id: add a module-level _detailRequestId (number), increment it at the start of
fetchProviderDetail, capture the current id in a local variable before awaiting
the network call, and before applying the response verify the captured id still
equals _detailRequestId; update fetchProviderDetail to stop relying on
_detailRequestName and remove/replace any checks that compare names so only
matching numeric ids permit state updates.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: c09d12e5-b34a-4208-95e0-1dae0d8907b5
📒 Files selected for processing (3)
docs/design/operations.mdweb/src/pages/providers/ProviderFormDrawer.tsxweb/src/stores/providers.ts
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
- GitHub Check: Build Backend
- GitHub Check: Dashboard Test
- GitHub Check: Test (Python 3.14)
- GitHub Check: Dependency Review
🧰 Additional context used
📓 Path-based instructions (3)
docs/design/*.md
📄 CodeRabbit inference engine (CLAUDE.md)
Update the relevant docs/design/ page to reflect approved deviations from the spec
Files:
docs/design/operations.md
web/src/**/*.{tsx,ts}
📄 CodeRabbit inference engine (CLAUDE.md)
web/src/**/*.{tsx,ts}: Always reuse existing components from web/src/components/ui/ before creating new ones
Use semantic Tailwind classes (text-foreground, bg-card, text-accent, text-success, bg-danger) or CSS variables (var(--so-*)); never hardcode hex values in .tsx/.ts files
Use font-sans or font-mono (maps to Geist tokens); never set fontFamily directly
Use density-aware tokens (p-card, gap-section-gap, gap-grid-gap) or standard Tailwind spacing; never hardcode pixel values for layout spacing
Use token variables (var(--so-shadow-card-hover), border-border, border-bright) for shadows/borders; never hardcode values
Do not recreate status dots inline -- use
Do not build card-with-header layouts from scratch -- use
Do not create metric displays with 'text-metric font-bold' -- use
Do not render initials circles manually -- use
Do not create complex (>8 line) JSX inside .map() -- extract to a shared component
Do not use rgba() with hardcoded values -- use design token variables
CSS side-effect imports need type declarations; Vite's '/// ' covers this in TS 6
Files:
web/src/pages/providers/ProviderFormDrawer.tsxweb/src/stores/providers.ts
web/src/**/*
📄 CodeRabbit inference engine (CLAUDE.md)
PostToolUse hook (scripts/check_web_design_system.py) runs automatically on every Edit/Write to web/src/ files; fix all violations before proceeding
Files:
web/src/pages/providers/ProviderFormDrawer.tsxweb/src/stores/providers.ts
🧠 Learnings (4)
📓 Common learnings
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-20T08:28:32.845Z
Learning: Applies to src/synthorg/providers/**/*.py : Providers: LLM provider abstraction (LiteLLM adapter), auth types (api_key/oauth/custom_header/none), presets (PROVIDER_PRESETS), runtime CRUD (ProviderManagementService with asyncio.Lock serialization), hot-reload via AppState swap.
📚 Learning: 2026-03-20T08:28:32.845Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-20T08:28:32.845Z
Learning: Applies to src/synthorg/providers/**/*.py : Providers: LLM provider abstraction (LiteLLM adapter), auth types (api_key/oauth/custom_header/none), presets (PROVIDER_PRESETS), runtime CRUD (ProviderManagementService with asyncio.Lock serialization), hot-reload via AppState swap.
Applied to files:
docs/design/operations.mdweb/src/stores/providers.ts
📚 Learning: 2026-03-19T07:12:14.508Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-19T07:12:14.508Z
Learning: Applies to src/synthorg/api/**/*.py : API package (api/): Litestar REST + WebSocket with controllers, guards, channels, JWT + API key + WS ticket auth, approval gate integration, coordination endpoint, collaboration endpoint, settings endpoint, provider management endpoint (CRUD + test + presets), backup endpoint, RFC 9457 structured errors, AppState hot-reload slots, service auto-wiring (Phase 1 at construction, Phase 2 on startup), lifecycle helpers
Applied to files:
docs/design/operations.md
📚 Learning: 2026-03-27T17:07:16.441Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T17:07:16.441Z
Learning: Applies to web/src/components/ui/*.{tsx,ts} : For new shared React components: place in web/src/components/ui/ with kebab-case filename, create .stories.tsx with all states, export props as TypeScript interface, use design tokens exclusively
Applied to files:
web/src/pages/providers/ProviderFormDrawer.tsx
🪛 GitHub Check: Dashboard Lint
web/src/pages/providers/ProviderFormDrawer.tsx
[warning] 66-66:
Do not call the 'set' function 'setLitellmProvider' of 'useState' synchronously in an effect. This can lead to unnecessary re-renders and performance issues
[warning] 65-65:
Do not call the 'set' function 'setBaseUrl' of 'useState' synchronously in an effect. This can lead to unnecessary re-renders and performance issues
[warning] 64-64:
Do not call the 'set' function 'setAuthType' of 'useState' synchronously in an effect. This can lead to unnecessary re-renders and performance issues
[warning] 63-63:
Do not call the 'set' function 'setName' of 'useState' synchronously in an effect. This can lead to unnecessary re-renders and performance issues
🔇 Additional comments (11)
docs/design/operations.md (3)
46-51: LGTM!The provider configuration documentation accurately reflects the new subscription auth type and associated fields. The security warning about using env vars or secret management for
subscription_tokenis appropriate.
120-125: LGTM!The provider management documentation is comprehensive and accurate:
- Presets expanded to 11 with
supported_auth_typesfield- vLLM auto-probe limitation correctly noted (empty
candidate_urls)- Subscription auth with ToS requirement documented
litellm_providerrouting key behavior explained- Credential safety indicators include
has_subscription_token
1106-1106: LGTM!The API surface table now correctly includes
GET /api/v1/providers/{name}/healthalongside the other provider endpoints, and documents all 5 auth types.web/src/stores/providers.ts (3)
150-206: LGTM!The detail fetching logic properly:
- Fetches provider, models, and health in parallel using
Promise.allSettled- Clears stale data (
selectedProvider,selectedProviderModels,selectedProviderHealth,testConnectionResult) on failure- Aggregates partial errors for models/health failures while still displaying the provider
- Guards against stale responses (though with the name-based limitation noted above)
319-335: LGTM!The
testConnectionaction correctly:
- Sets loading state and clears previous result
- Returns a synthetic error result on failure (instead of
null), enabling consistent UI handling- Always resets
testingConnectionflag
337-360: LGTM!The
discoverModelsaction properly:
- Tracks loading state via
discoveringModelsflag- Shows toast notifications for success/failure
- Refreshes detail view if the discovered provider is currently selected
- Resets loading state in
finallyblockweb/src/pages/providers/ProviderFormDrawer.tsx (5)
12-16: LGTM!Auth options correctly limited to the three supported modes (
api_key,subscription,none). OAuth and custom_header are appropriately excluded until the drawer can collect their required fields.
71-91: LGTM!The preset change effect properly:
- Resets all form fields when switching to custom (
__custom__)- Auto-fills fields from preset data when a preset is selected
- Clears credentials and ToS state on preset changes
265-282: LGTM!Base URL visibility logic correctly shows the field when:
- Custom mode (
isCustom)- Any preset is selected (
preset != null)- Edit mode
The hint logic properly indicates when the URL is required (presets without
default_base_url).
128-172: LGTM!The submit handler correctly:
- Routes to
createFromPresetvscreateProviderbased on preset selection- Includes
subscription_tokenandtos_acceptedconditionally for subscription auth- Uses
clear_api_key/clear_subscription_tokenflags in edit mode- Closes drawer on success and resets
submittinginfinally
133-142: TheCreateFromPresetRequestinterface already supports all the fields being passed tocreateFromPreset. The interface definition includesauth_type,api_key, andbase_urlas optional fields, along withmodels, so no verification or changes are needed.> Likely an incorrect or invalid review comment.
🤖 I have created a release *beep* *boop* --- #MAJOR CHANGES; We got a somewhat working webui :) ## [0.5.0](v0.4.9...v0.5.0) (2026-03-30) ### Features * add analytics trends and budget forecast API endpoints ([#798](#798)) ([16b61f5](16b61f5)) * add department policies to default templates ([#852](#852)) ([7a41548](7a41548)) * add remaining activity event types (task_started, tool_used, delegation, cost_incurred) ([#832](#832)) ([4252fac](4252fac)) * agent performance, activity, and history API endpoints ([#811](#811)) ([9b75c1d](9b75c1d)) * Agent Profiles and Detail pages (biography, career, performance) ([#874](#874)) ([62d7880](62d7880)) * app shell, Storybook, and CI/CD pipeline ([#819](#819)) ([d4dde90](d4dde90)) * Approvals page with risk grouping, urgency indicators, batch actions ([#889](#889)) ([4e9673d](4e9673d)) * Budget Panel page (P&L dashboard, breakdown charts, forecast) ([#890](#890)) ([b63b0f1](b63b0f1)) * build infrastructure layer (API client, auth, WebSocket) ([#815](#815)) ([9f01d3e](9f01d3e)) * CLI global options infrastructure, UI modes, exit codes, env vars ([#891](#891)) ([fef4fc5](fef4fc5)) * CodeMirror editor and theme preferences toggle ([#905](#905), [#807](#807)) ([#909](#909)) ([41fbedc](41fbedc)) * Company page (department/agent management) ([#888](#888)) ([cfb88b0](cfb88b0)) * comprehensive hint coverage across all CLI commands ([#900](#900)) ([937974e](937974e)) * config system extensions, per-command flags for init/start/stop/status/logs ([#895](#895)) ([32f83fe](32f83fe)) * configurable currency system replacing hardcoded USD ([#854](#854)) ([b372551](b372551)) * Dashboard page (metric cards, activity feed, budget burn) ([#861](#861)) ([7d519d5](7d519d5)) * department health, provider status, and activity feed endpoints ([#818](#818)) ([6d5f196](6d5f196)) * design tokens and core UI components ([#833](#833)) ([ed887f2](ed887f2)) * extend approval, meeting, and budget API responses ([#834](#834)) ([31472bf](31472bf)) * frontend polish -- real-time UX, accessibility, responsive, performance ([#790](#790), [#792](#792), [#791](#791), [#793](#793)) ([#917](#917)) ([f04a537](f04a537)) * implement human roles and access control levels ([#856](#856)) ([d6d8a06](d6d8a06)) * implement semantic conflict detection in workspace merge ([#860](#860)) ([d97283b](d97283b)) * interaction components and animation patterns ([#853](#853)) ([82d4b01](82d4b01)) * Login page + first-run bootstrap + Company page ([#789](#789), [#888](#888)) ([#896](#896)) ([8758e8d](8758e8d)) * Meetings page with timeline viz, token bars, contribution formatting ([#788](#788)) ([#904](#904)) ([b207f46](b207f46)) * Messages page with threading, channel badges, sender indicators ([#787](#787)) ([#903](#903)) ([28293ad](28293ad)) * Org Chart force-directed view and drag-drop reassignment ([#872](#872), [#873](#873)) ([#912](#912)) ([a68a938](a68a938)) * Org Chart page (living nodes, status, CRUD, department health) ([#870](#870)) ([0acbdae](0acbdae)) * per-command flags for remaining commands, auto-behavior wiring, help/discoverability ([#897](#897)) ([3f7afa2](3f7afa2)) * Providers page with backend rework -- health, CRUD, subscription auth ([#893](#893)) ([9f8dd98](9f8dd98)) * scaffold React + Vite + TypeScript + Tailwind project ([#799](#799)) ([bd151aa](bd151aa)) * Settings page with search, dependency indicators, grouped rendering ([#784](#784)) ([#902](#902)) ([a7b9870](a7b9870)) * Setup Wizard rebuild with template comparison, cost estimator, theme customization ([#879](#879)) ([ae8b50b](ae8b50b)) * setup wizard UX -- template filters, card metadata, provider form reuse ([#910](#910)) ([7f04676](7f04676)) * setup wizard UX overhaul -- mode choice, step reorder, provider fixes ([#907](#907)) ([ee964c4](ee964c4)) * structured ModelRequirement in template agent configs ([#795](#795)) ([7433548](7433548)) * Task Board page (rich Kanban, filtering, dependency viz) ([#871](#871)) ([04a19b0](04a19b0)) ### Bug Fixes * align frontend types with backend and debounce WS refetches ([#916](#916)) ([134c11b](134c11b)) * auto-cleanup targets newly pulled images instead of old ones ([#884](#884)) ([50e6591](50e6591)) * correct wipe backup-skip flow and harden error handling ([#808](#808)) ([c05860f](c05860f)) * improve provider setup in wizard, subscription auth, dashboard bugs ([#914](#914)) ([87bf8e6](87bf8e6)) * improve update channel detection and add config get command ([#814](#814)) ([6b137f0](6b137f0)) * resolve all ESLint warnings, add zero-warnings enforcement ([#899](#899)) ([079b46a](079b46a)) * subscription auth uses api_key, base URL optional for cloud providers ([#915](#915)) ([f0098dd](f0098dd)) ### Refactoring * semantic analyzer cleanup -- shared filtering, concurrency, extraction ([#908](#908)) ([81372bf](81372bf)) ### Documentation * brand identity and UX design system from [#765](#765) exploration ([#804](#804)) ([389a9f4](389a9f4)) * page structure and information architecture for v0.5.0 dashboard ([#809](#809)) ([f8d6d4a](f8d6d4a)) * write UX design guidelines with WCAG-verified color system ([#816](#816)) ([4a4594e](4a4594e)) ### Tests * add unit tests for agent hooks and page components ([#875](#875)) ([#901](#901)) ([1d81546](1d81546)) ### CI/CD * bump actions/deploy-pages from 4.0.5 to 5.0.0 in the major group ([#831](#831)) ([01c19de](01c19de)) * bump astral-sh/setup-uv from 7.6.0 to 8.0.0 in /.github/actions/setup-python-uv in the all group ([#920](#920)) ([5f6ba54](5f6ba54)) * bump codecov/codecov-action from 5.5.3 to 6.0.0 in the major group ([#868](#868)) ([f22a181](f22a181)) * bump github/codeql-action from 4.34.1 to 4.35.0 in the all group ([#883](#883)) ([87a4890](87a4890)) * bump sigstore/cosign-installer from 4.1.0 to 4.1.1 in the minor-and-patch group ([#830](#830)) ([7a69050](7a69050)) * bump the all group with 3 updates ([#923](#923)) ([ff27c8e](ff27c8e)) * bump wrangler from 4.76.0 to 4.77.0 in /.github in the minor-and-patch group ([#822](#822)) ([07d43eb](07d43eb)) * bump wrangler from 4.77.0 to 4.78.0 in /.github in the all group ([#882](#882)) ([f84118d](f84118d)) ### Maintenance * add design system enforcement hook and component inventory ([#846](#846)) ([15abc43](15abc43)) * add dev-only auth bypass for frontend testing ([#885](#885)) ([6cdcd8a](6cdcd8a)) * add pre-push rebase check hook ([#855](#855)) ([b637a04](b637a04)) * backend hardening -- eviction/size-caps and model validation ([#911](#911)) ([81253d9](81253d9)) * bump axios from 1.13.6 to 1.14.0 in /web in the all group across 1 directory ([#922](#922)) ([b1b0232](b1b0232)) * bump brace-expansion from 5.0.4 to 5.0.5 in /web ([#862](#862)) ([ba4a565](ba4a565)) * bump eslint-plugin-react-refresh from 0.4.26 to 0.5.2 in /web ([#801](#801)) ([7574bb5](7574bb5)) * bump faker from 40.11.0 to 40.11.1 in the minor-and-patch group ([#803](#803)) ([14d322e](14d322e)) * bump https://github.com/astral-sh/ruff-pre-commit from v0.15.7 to 0.15.8 ([#864](#864)) ([f52901e](f52901e)) * bump nginxinc/nginx-unprivileged from `6582a34` to `f99cc61` in /docker/web in the all group ([#919](#919)) ([df85e4f](df85e4f)) * bump nginxinc/nginx-unprivileged from `ccbac1a` to `6582a34` in /docker/web ([#800](#800)) ([f4e9450](f4e9450)) * bump node from `44bcbf4` to `71be405` in /docker/sandbox ([#827](#827)) ([91bec67](91bec67)) * bump node from `5209bca` to `cf38e1f` in /docker/web ([#863](#863)) ([66d6043](66d6043)) * bump picomatch in /site ([#842](#842)) ([5f20bcc](5f20bcc)) * bump recharts 2->3 and @types/node 22->25 in /web ([#802](#802)) ([a908800](a908800)) * Bump requests from 2.32.5 to 2.33.0 ([#843](#843)) ([41daf69](41daf69)) * bump smol-toml from 1.6.0 to 1.6.1 in /site ([#826](#826)) ([3e5dbe4](3e5dbe4)) * bump the all group with 3 updates ([#921](#921)) ([7bace0b](7bace0b)) * bump the minor-and-patch group across 1 directory with 2 updates ([#829](#829)) ([93e611f](93e611f)) * bump the minor-and-patch group across 1 directory with 3 updates ([#841](#841)) ([7010c8e](7010c8e)) * bump the minor-and-patch group across 1 directory with 3 updates ([#869](#869)) ([548cee5](548cee5)) * bump the minor-and-patch group in /site with 2 updates ([#865](#865)) ([9558101](9558101)) * bump the minor-and-patch group with 2 updates ([#867](#867)) ([4830706](4830706)) * consolidate Dependabot groups to 1 PR per ecosystem ([06d2556](06d2556)) * consolidate Dependabot groups to 1 PR per ecosystem ([#881](#881)) ([06d2556](06d2556)) * improve worktree skill with full dep sync and status enhancements ([#906](#906)) ([772c625](772c625)) * remove Vue remnants and document framework decision ([#851](#851)) ([bf2adf6](bf2adf6)) * update web dependencies and fix brace-expansion CVE ([#880](#880)) ([a7a0ed6](a7a0ed6)) * upgrade to Storybook 10 and TypeScript 6 ([#845](#845)) ([52d95f2](52d95f2)) --- This PR was generated with [Release Please](https://github.com/googleapis/release-please). See [documentation](https://github.com/googleapis/release-please#release-please). --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Summary
litellm_providerfield; addAuthType.SUBSCRIPTIONfor OAuth bearer tokens (Claude Pro/Max style); expand presets from 4 to 11 (Anthropic, OpenAI, Google AI, Mistral, Groq, DeepSeek, Azure + existing local presets); addsupported_auth_typesto presets for multi-auth UI; refactor validation to table-driven patternBackend changes
providers/enums.pyAuthType.SUBSCRIPTIONconfig/schema.pylitellm_provider,subscription_token(NotBlankStr),tos_accepted_at; table-driven auth validationproviders/presets.pylitellm_provider,supported_auth_typeswith validators; 7 new cloud presetsproviders/drivers/litellm_driver.pylitellm_providerfor routing; handle SUBSCRIPTION viaextra_headersapi/dto.pyto_provider_responsewith new fieldsproviders/management/_helpers.pyAuthType.NONEproviders/management/service.pylitellm_provider+ auth overrides increate_from_presetFrontend changes (39 files, 22 new)
stores/providers.ts-- Full Zustand store (CRUD, health, presets, test connection, filters)hooks/useProvidersData.ts+useProviderDetailData.ts-- Data composition hookspages/ProvidersPage.tsx+ProviderDetailPage.tsx-- Replace placeholderspages/providers/-- 11 sub-components (card, grid, filters, form drawer, preset picker, detail header, health metrics, model list, skeletons, test connection result)components/ui/provider-health-badge.tsx-- Shared health status componentTest plan
npm --prefix web run dev-- provider cards, CRUD, health badges, test connection, model list, skeletons, empty statenpm --prefix web run storybook-- ProviderHealthBadge, ProviderCard, PresetPicker, TestConnectionResultRelated
Closes #785
🤖 Generated with Claude Code