[2/3] Refactor ModelConfiguration into a slot-aware component that auto-derives labels, descriptions, and JSON templates from the form field path, consolidating inherited values into a single typed object#3029
Conversation
🦋 Changeset detectedLatest commit: 067fd9e The changes in this PR will be included in the next version bump. This PR includes changesets to release 10 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Preview URLsUse these stable preview aliases for testing this PR:
These point to the same Vercel preview deployment as the bot comment, but they stay stable and easier to find. Raw Vercel deployment URLs
|
|
@pullfrog update PR description based on changes in this PR |
|
Updated the PR description for #3029 with a detailed summary of the
|
|
@claude add a changeset based on changes in this PR |
|
Claude finished @dimaMachina's task in 48s —— View job Adding changeset
A changeset already exists at ---
"@inkeep/agents-manage-ui": patch
---
Simplify ModelConfiguration component by removing onProviderOptionsChange and onModelChange callbacks in favor of direct form state managementNo additional action needed. |
There was a problem hiding this comment.
Clean refactor that consolidates the ModelConfiguration prop surface. The inherited bag, slot-derived defaults, and useId() migration are all sound. One potential runtime issue with duplicate React keys in the fallback models list.
Claude Opus | 𝕏
|
TL;DR — Refactors Key changes
Summary | 7 files | 14 commits | base: Slot-based self-configuration for
|
There was a problem hiding this comment.
PR Review Summary
(1) Total Issues | Risk: Low
🟠⚠️ Major (1) 🟠⚠️
Inline Comments:
- 🟠 Major:
model-configuration.tsx:265React key collision risk with duplicate fallback models
💭 Consider (1) 💭
Inline Comments:
- 💭 Consider:
model-section.tsx:118Inconsistent use of explicit description prop vs slot-based derivation
Overview
This is a well-executed refactor that consolidates the ModelConfiguration component's prop interface from 8+ individual props down to a cleaner API with a single inherited object prop and slot-based derivation for defaults. Key improvements:
✅ Props consolidation: inheritedValue, inheritedProviderOptions, inheritedFallbackModels, inheritedAllowedProviders, editorNamePrefix, getJsonPlaceholder, canClear, isRequired → inherited object + automatic derivation
✅ ID generation: Migrated from manual prefix-based IDs to React's useId() hook — consistent with codebase patterns
✅ Centralized logic: Labels, descriptions, placeholders, and JSON templates now derived from slot type (base/structuredOutput/summarizer)
✅ Net reduction: -87 lines while maintaining functionality
The one actionable issue is the React key change in FallbackModelsSection that could cause problems with duplicate models.
💡 APPROVE WITH SUGGESTIONS
Summary: Clean refactor with good API simplification. The React key issue (using model alone instead of model-${index}) is the only actionable item — low probability of occurring in practice but worth fixing for correctness.
Discarded (3)
| Location | Issue | Reason Discarded |
|---|---|---|
azure-configuration-section.tsx:16-19 |
JSON.parse without try-catch | Pre-existing code, not introduced by this PR |
model-section.tsx:18-21 |
Props use any type for projectModels/agentModels |
Pre-existing code, not introduced by this PR |
metadata-editor.tsx:69-107 |
Inline object creation causes new references | React Compiler handles memoization per CLAUDE.md guidelines |
Reviewers (4)
| Reviewer | Returned | Main Findings | Consider | While You're Here | Inline Comments | Pending Recs | Discarded |
|---|---|---|---|---|---|---|---|
pr-review-frontend |
5 | 0 | 0 | 0 | 1 | 0 | 4 |
pr-review-standards |
1 | 0 | 0 | 0 | 0 | 0 | 1 |
pr-review-types |
4 | 0 | 0 | 0 | 0 | 0 | 4 |
pr-review-consistency |
5 | 0 | 0 | 0 | 1 | 0 | 4 |
| Total | 15 | 0 | 0 | 0 | 2 | 0 | 13 |
Note: High discard rate due to (1) duplicate findings across reviewers for the same React key issue, (2) pre-existing issues out of PR scope, and (3) issues handled by React Compiler.
| @@ -119,21 +116,13 @@ export function ModelSection({ projectModels, agentModels, control, basePath }: | |||
| </div> | |||
| } | |||
| description="Primary model for general sub agent responses" | |||
There was a problem hiding this comment.
💭 Consider: Inconsistent use of explicit description prop
Issue: This call site explicitly passes description="Primary model for general sub agent responses", while other call sites (metadata-editor.tsx, project-models-section.tsx) rely on the new slot-based derivation pattern.
Why: The refactored ModelConfiguration component automatically derives description from the slot type (base → "Primary model for general agent responses"). Having some call sites override while others use derivation creates a split-world pattern.
Fix: If "sub agent" vs "agent" is an intentional distinction for this context, this override is appropriate. Otherwise, consider removing the explicit prop to use slot-based derivation consistently:
<ModelConfiguration
control={control}
name={`${basePath}.base`}
inherited={{...}}
label={...}
/>Refs:
Ito Test Report ✅9 test cases ran. 3 additional findings, 6 passed. Overall, 9 test cases were executed with 6 passing and 3 failing (all Medium severity), indicating generally solid behavior but with important persistence issues still open. The key findings were that allowed-providers mode/list changes and fallback-model clears can fail to persist correctly after save/reload (including rapid toggle scenarios), while project creation and agents-route loading, model inheritance reset behavior, Azure field isolation/label targeting, mobile deep-link interruption handling, hostile providerOptions validation, and invalid URL tampering all behaved as expected without confirmed defects. ✅ Passed (6)ℹ️ Additional Findings (3)
🟠 Provider mode toggles can persist stale provider constraints
Relevant code:
if (val === 'all') {
setSpecificMode(false);
onAllowedProvidersChange([]);
} else {
function onAllowedProvidersChange(providers: string[]) {
allowedProvidersField.onChange(providers.length ? providers : undefined);
}
...(data.models.base.allowedProviders?.length && {
allowedProviders: data.models.base.allowedProviders.filter(Boolean),
}),🟠 Allowed providers state can diverge after save/reload
Relevant code:
onValueChange={(val) => {
if (val === 'all') {
setSpecificMode(false);
onAllowedProvidersChange([]);
} else {
setSpecificMode(true);
}
}}
function onAllowedProvidersChange(providers: string[]) {
allowedProvidersField.onChange(providers.length ? providers : undefined);
}
...(data.models.base.allowedProviders?.length && {
allowedProviders: data.models.base.allowedProviders.filter(Boolean),
}),🟠 Clearing fallback models does not reliably persist
Relevant code:
function onFallbackModelsChange(models: string[]) {
fallbackModelsField.onChange(models.length ? models : undefined);
}
...(data.models.base.fallbackModels?.length && {
fallbackModels: data.models.base.fallbackModels.filter(Boolean),
}),
...(data.models.structuredOutput.fallbackModels?.length && {
fallbackModels: data.models.structuredOutput.fallbackModels.filter(Boolean),
}),Commit: Tell us how we did: Give Ito Feedback |
…viderOptionsChange-onModelChange2
5181496 to
f44437a
Compare
There was a problem hiding this comment.
PR Review Summary
(0) Total Issues | Risk: Low
Delta Review: Changes Since Last Review
This commit (903da0513) contains trivial code polish with no functional changes:
✅ Inlined intermediate variables (model-configuration.tsx:466, 473): Removed resolvedLabel and resolvedDescription variables — now uses nullish coalescing directly in JSX props. Pure style preference, no behavior change.
✅ Simplified type annotation (models.ts:92): Removed explicit ReadonlySet<string> type from GATEWAY_ROUTABLE_PROVIDERS_SET. TypeScript infers Set<string> which is functionally equivalent for the usage pattern (.has() calls only).
Prior Issues Status
| Issue | Status |
|---|---|
React key collision (model-configuration.tsx:265) |
✅ Fixed (earlier commit) |
Inconsistent shouldUnregister usage |
⏳ Minor — doesn't affect runtime in most cases |
Explicit description prop (model-section.tsx:119) |
✅ Intentional — "sub agent" vs "agent" context distinction |
✅ APPROVE
Summary: Trivial cosmetic polish — 5 lines added, 9 removed, zero behavior change. The overall refactor successfully consolidates ModelConfiguration into a cleaner, slot-aware component with good API ergonomics. All prior issues have been addressed or acknowledged as minor/intentional. Ship it! 🚀
Reviewers (0)
| Reviewer | Returned | Main Findings | Consider | While You're Here | Inline Comments | Pending Recs | Discarded |
|---|---|---|---|---|---|---|---|
| Total | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
Note: Sub-reviewers not dispatched — delta contains only cosmetic code polish (inlined variables, removed type annotation) with no functional changes.
|
@claude add a changeset based on changes exactly from this PR |
|
Claude finished @dimaMachina's task in 1m 22s —— View job Adding changeset
Updated the existing changeset at ---
"@inkeep/agents-manage-ui": patch
---
Refactor ModelConfiguration into a slot-aware component that auto-derives labels, descriptions, and JSON templates from the form field path, consolidating inherited values into a single typed object and fixing React key collisions in fallback models| Branch |
Co-authored-by: Dimitri POSTOLOV <[email protected]> Co-Authored-By: Claude Opus 4.6 <[email protected]>
ModelConfiguration into a slot-aware component that auto-derives labels, descriptions, and JSON templates from the form field path, consolidating inherited values into a single typed object
Ito Test Report ❌14 test cases ran. 1 failed, 1 additional finding, 12 passed. Across a unified run of 14 test cases, 12 passed and 2 failed, with broad confirmation that default-model and sub-agent settings persist correctly across save/reload, inheritance and inherited-to-explicit transitions behave as expected, slot-aware labels/templates render correctly, and rapid toggles/submits, navigation stress, malformed JSON recovery (non-Azure flow), and mobile sidepane editing remained stable. ❌ Failed (1)
|
| Category | Summary | Screenshot |
|---|---|---|
| Adversarial | 🚨 Read-only permission boundary is bypassed, allowing viewer-level mutation of project and agent settings. |
🚨 Read-only users can mutate project and agent settings via bypass-authenticated UI requests
- What failed: Read-only users can persist project model changes and create agents, but they should be blocked from edit actions.
- Impact: Any environment with the bypass secret configured can unintentionally grant write access through the UI, defeating project-level edit authorization. This can lead to unauthorized configuration changes by users expected to be read-only.
- Steps to reproduce:
- Log in as a viewer-level project user.
- Open /default/projects/app-playground/settings and edit model configuration.
- Click Update project and confirm the change persists after reload.
- Open /default/projects/app-playground/agents and create a new agent successfully.
- Stub / mock context: A viewer account and project membership were seeded to exercise the read-only path, and no route interception or mocked API responses were used while validating the authorization behavior.
- Code analysis: The UI request layer appends a bearer bypass secret to management API calls whenever the environment variable is present. The API auth middleware treats that token as system, and project permission middleware explicitly skips authorization checks for system, which creates a direct path for unauthorized edits.
- Why this is likely a bug: Production permission checks are bypassed by code path, not by flaky runtime behavior, so viewer users can be elevated to system-level authorization when the bypass secret is present.
Relevant code:
agents-manage-ui/src/lib/api/api-config.ts (lines 64-71)
const defaultHeaders: HeadersInit = {
'Content-Type': 'application/json',
...options.headers,
...(cookieHeader && { Cookie: cookieHeader }),
...(process.env.INKEEP_AGENTS_MANAGE_API_BYPASS_SECRET && {
Authorization: `Bearer ${process.env.INKEEP_AGENTS_MANAGE_API_BYPASS_SECRET}`,
}),
};agents-api/src/middleware/manageAuth.ts (lines 70-79)
// 1. First, check if it's the bypass secret
if (
env.INKEEP_AGENTS_MANAGE_API_BYPASS_SECRET &&
token === env.INKEEP_AGENTS_MANAGE_API_BYPASS_SECRET
) {
logger.info({}, 'Bypass secret authenticated successfully');
// Set system user context for bypass authentication
c.set('userId', 'system');agents-api/src/middleware/projectAccess.ts (lines 57-60)
if (userId === 'system' || userId.startsWith('apikey:')) {
await next();
return;
}Commit: 067fd9e
Tell us how we did: Give Ito Feedback
ModelConfiguration into a slot-aware component that auto-derives labels, descriptions, and JSON templates from the form field path, consolidating inherited values into a single typed objectModelConfiguration into a slot-aware component that auto-derives labels, descriptions, and JSON templates from the form field path, consolidating inherited values into a single typed object
|
This pull request has been automatically marked as stale because it has not had recent activity. If this PR is still relevant:
Thank you for your contributions! |
Summary
Refactors
ModelConfigurationinto a self-contained, slot-aware component that auto-derives its label, description, placeholder, and JSON template from the form field path — eliminating prop sprawl across all consumers.Changes
model-configuration.tsx(core)ModelConfigurationSlot('base' | 'structuredOutput' | 'summarizer') with lookup tables for labels, descriptions, and placeholders.getModelConfigurationSlot()infers the slot from thenamepath (e.g."models.structuredOutput"→"structuredOutput").getJsonPlaceholder()selects the correct JSON template based on model name (Azure detection) and slot type — consolidating logic previously duplicated in consumers.control,name, optionallabel/descriptionoverrides,inherited, anddisabled.useControllerformodel,providerOptions,fallbackModels, andallowedProviderssub-fields instead of individual value/onChange prop drilling.AllowedProvidersSectionandFallbackModelsSectionas internal sub-components.AzureConfigurationSectionwhen the model starts withazure/.capabilities?.modelFallback?.enabledand gateway-routability checks.TFieldValues/TTransformedValuesfor reuse across agent and project forms.azure-configuration-section.tsxmetadata-editor.tsxinheritedobject per model slot and passes it toModelConfigurationwith a path-basedname.model-section.tsxgetInheritance()helper implementing a 5-level cascading fallback chain (specific slot agent → specific slot project → own base → agent base → project base).basePathprop ('models' | \subAgents.${string}.models``) so the same component serves both agent-level and sub-agent-level configuration.project-models-section.tsxBaseModelSection,StructuredOutputModelSection,SummarizerModelSectionsub-components.Claude Opus| 𝕏