From f601b57be622a11ff282e1ae4a69589b151bc190 Mon Sep 17 00:00:00 2001 From: Kaosiso Ezealigo Date: Sat, 1 Aug 2026 15:59:56 +0200 Subject: [PATCH 1/2] feat: add connect-key section to model harness with diff-aware variant --- .../SchemaControls/AgentTemplateControl.tsx | 43 +++++++++++++++++-- .../agentTemplate/useModelHarness.tsx | 4 ++ 2 files changed, 44 insertions(+), 3 deletions(-) diff --git a/web/packages/agenta-entity-ui/src/DrillInView/SchemaControls/AgentTemplateControl.tsx b/web/packages/agenta-entity-ui/src/DrillInView/SchemaControls/AgentTemplateControl.tsx index 3c354709cc..28c34b6b95 100644 --- a/web/packages/agenta-entity-ui/src/DrillInView/SchemaControls/AgentTemplateControl.tsx +++ b/web/packages/agenta-entity-ui/src/DrillInView/SchemaControls/AgentTemplateControl.tsx @@ -121,12 +121,15 @@ const ModelHarnessSectionBody = ({ section, ...params }: { - section: "model-harness" | "advanced" + section: "model-harness" | "advanced" | "connect-key" } & Parameters[0]) => { const mh = useModelHarness(params) if (section === "advanced") { return <>{mh.advancedDrawerBody} } + if (section === "connect-key") { + return <>{mh.providerCredentialsSection} + } return <>{mh.modelHarnessDrawerBody} } @@ -490,6 +493,19 @@ export const AgentTemplateControl = memo(function AgentTemplateControl({ }), [sectionChanges.draft, onChange, config, committed], ) + // Whether THIS session's edit touched the provider/connection specifically (`llm.connection.*` / + // `llm.provider`), not just some other model-harness field (e.g. `harness.kind`). Gates the + // connect-key pane's diff-aware variant below — narrower than "the section has any change" so a + // harness-only edit that happens to also need a key keeps its plain (model-picker-inclusive) pane + // instead of losing the picker to a body that only knows how to show the credentials group. + const credentialsPathChanged = useMemo( + () => + (sectionChanges.draft.sectionsByKey.get("model-harness")?.scalarChanges ?? []).some( + (c) => c.key === "llm.provider" || c.key.startsWith("llm.connection"), + ), + [sectionChanges.draft], + ) + // The inline body for a drawer-backed section: its own controls, narrowed to what changed — the // same affordance as the Connect-key field, with a different filter (see SectionChangeBody). // The body is a COMPONENT rendered inside the providers, never `mh`'s pre-built output: the hook @@ -817,7 +833,13 @@ export const AgentTemplateControl = memo(function AgentTemplateControl({ // What the section surfaces inline, in precedence order. Dropping `onOpen` is what makes // a section expand inline instead of routing to the drawer. // 1. Required info missing (no provider key) — BLOCKING, so it wins: the same key field - // the drawer uses, right here. + // the drawer uses, right here. If THIS SESSION also moved the connection/provider + // itself, swap the bare (headerless) pane for the changed-aware accordion variant — + // otherwise that diff has no affordance anywhere: the bare pane never mounts under a + // ChangedPathsProvider (`mh.providerCredentialsInline` here is the parent's + // uncontexted instance) and structurally has no indicator/badge/revert chrome. A + // change to some OTHER model-harness field (e.g. harness.kind) keeps the plain pane, + // so it doesn't lose the inline model picker for a diff it can't represent anyway. // 2. Uncommitted changes — informational: what changed (see `changeBodyFor`). // 3. Neither — the plain drawer row it has always been. ...(showKeyPane @@ -832,7 +854,22 @@ export const AgentTemplateControl = memo(function AgentTemplateControl({ onOpenDetails={() => openSectionDrawer("model-harness")} disabled={disabled} > - {mh.providerCredentialsInline} + {credentialsPathChanged ? ( + + + + ) : ( + mh.providerCredentialsInline + )} diff --git a/web/packages/agenta-entity-ui/src/DrillInView/SchemaControls/agentTemplate/useModelHarness.tsx b/web/packages/agenta-entity-ui/src/DrillInView/SchemaControls/agentTemplate/useModelHarness.tsx index 545b00ac6b..80eb96b132 100644 --- a/web/packages/agenta-entity-ui/src/DrillInView/SchemaControls/agentTemplate/useModelHarness.tsx +++ b/web/packages/agenta-entity-ui/src/DrillInView/SchemaControls/agentTemplate/useModelHarness.tsx @@ -1094,6 +1094,10 @@ export function useModelHarness({ // self-managed card; no nested header/badge, no rail) for the inline "Connect key" // quick-action under the section header — aligned with the drawer without duplicating it. providerCredentialsInline, + // The full accordion variant (header, "Connect key" badge, changed-path indicator, group + // revert) — used instead of the bare pane when the connect-key state coincides with an + // uncommitted change, so the change stays visible rather than being silently dropped. + providerCredentialsSection, // A model is selected but the chosen harness can't run it — a *model* problem (the harness // itself stays valid), so the config panel flags the Model & harness section as invalid. modelUnsupported: !!modelId && !selectedKeepsModel, From 18aef9438b68b58ea7d58e37165dc0e8d4961c98 Mon Sep 17 00:00:00 2001 From: Kaosiso Ezealigo Date: Sat, 1 Aug 2026 18:51:35 +0200 Subject: [PATCH 2/2] fix(frontend): track llm.provider in credentials revert, revert model atomically, trim comment Addresses review: a provider-only change (llm.provider without llm.connection) now gets the same changed indicator and Restore as a connection change. The group Restore also reverts llm.model alongside provider/connection, since writeModel can move all three together and a partial revert reattaches a stale connection slug to the new model (rejected by the backend). Also trims a comment that grew past the one-line convention. Co-Authored-By: Claude Sonnet 5 --- .../SchemaControls/AgentTemplateControl.tsx | 15 +++----------- .../agentTemplate/useModelHarness.tsx | 20 ++++++++++++++++--- 2 files changed, 20 insertions(+), 15 deletions(-) diff --git a/web/packages/agenta-entity-ui/src/DrillInView/SchemaControls/AgentTemplateControl.tsx b/web/packages/agenta-entity-ui/src/DrillInView/SchemaControls/AgentTemplateControl.tsx index 28c34b6b95..d34fe1768d 100644 --- a/web/packages/agenta-entity-ui/src/DrillInView/SchemaControls/AgentTemplateControl.tsx +++ b/web/packages/agenta-entity-ui/src/DrillInView/SchemaControls/AgentTemplateControl.tsx @@ -493,11 +493,7 @@ export const AgentTemplateControl = memo(function AgentTemplateControl({ }), [sectionChanges.draft, onChange, config, committed], ) - // Whether THIS session's edit touched the provider/connection specifically (`llm.connection.*` / - // `llm.provider`), not just some other model-harness field (e.g. `harness.kind`). Gates the - // connect-key pane's diff-aware variant below — narrower than "the section has any change" so a - // harness-only edit that happens to also need a key keeps its plain (model-picker-inclusive) pane - // instead of losing the picker to a body that only knows how to show the credentials group. + // Whether this edit touched the provider/connection specifically, not just any model-harness field. const credentialsPathChanged = useMemo( () => (sectionChanges.draft.sectionsByKey.get("model-harness")?.scalarChanges ?? []).some( @@ -833,13 +829,8 @@ export const AgentTemplateControl = memo(function AgentTemplateControl({ // What the section surfaces inline, in precedence order. Dropping `onOpen` is what makes // a section expand inline instead of routing to the drawer. // 1. Required info missing (no provider key) — BLOCKING, so it wins: the same key field - // the drawer uses, right here. If THIS SESSION also moved the connection/provider - // itself, swap the bare (headerless) pane for the changed-aware accordion variant — - // otherwise that diff has no affordance anywhere: the bare pane never mounts under a - // ChangedPathsProvider (`mh.providerCredentialsInline` here is the parent's - // uncontexted instance) and structurally has no indicator/badge/revert chrome. A - // change to some OTHER model-harness field (e.g. harness.kind) keeps the plain pane, - // so it doesn't lose the inline model picker for a diff it can't represent anyway. + // the drawer uses, right here, swapped for the changed-aware variant when this edit + // touched the provider/connection (`credentialsPathChanged`) so that diff isn't lost. // 2. Uncommitted changes — informational: what changed (see `changeBodyFor`). // 3. Neither — the plain drawer row it has always been. ...(showKeyPane diff --git a/web/packages/agenta-entity-ui/src/DrillInView/SchemaControls/agentTemplate/useModelHarness.tsx b/web/packages/agenta-entity-ui/src/DrillInView/SchemaControls/agentTemplate/useModelHarness.tsx index 80eb96b132..69254115e8 100644 --- a/web/packages/agenta-entity-ui/src/DrillInView/SchemaControls/agentTemplate/useModelHarness.tsx +++ b/web/packages/agenta-entity-ui/src/DrillInView/SchemaControls/agentTemplate/useModelHarness.tsx @@ -454,10 +454,24 @@ export function useModelHarness({ // `harness.kind` (NOT all of `harness`) so a permissions edit doesn't light up the Harness header. const harnessKindChanged = useHasChangedUnder("harness.kind") const modelChanged = useHasChangedUnder("llm.model") - const credentialsChanged = useHasChangedUnder("llm.connection") + // Provider credentials cover both the connection (mode/slug) and the bare `llm.provider` field a + // catalog model switch can move on its own — track both, or a provider-only change gets a "Connect + // key" badge with no changed indicator or Restore to go with it. + const connectionChanged = useHasChangedUnder("llm.connection") + const providerFieldChanged = useHasChangedUnder("llm.provider") + const credentialsChanged = connectionChanged || providerFieldChanged const revertHarnessKind = useRevertUnder("harness.kind") const revertModel = useRevertUnder("llm.model") - const revertCredentials = useRevertUnder("llm.connection") + const revertConnection = useRevertUnder("llm.connection") + const revertProviderField = useRevertUnder("llm.provider") + // `writeModel` can move `llm.model` atomically with `llm.provider`/`llm.connection.slug` (a + // catalog pick clears the old custom slug) — revert all three together or the group's Restore + // reattaches a stale slug to the new model, a combination the backend rejects. + const revertCredentials = useCallback(() => { + revertConnection?.() + revertProviderField?.() + revertModel?.() + }, [revertConnection, revertProviderField, revertModel]) // Confirmed, because unlike the per-row undo (which is reached THROUGH the popover showing the // exact value it restores — see `RailField`) this one discards every change in the group at once // and names none of them. @@ -722,7 +736,7 @@ export function useModelHarness({ disabled, revisionId: revisionId ?? null, indicator: changedIndicator(credentialsChanged), - revertControl: revertAction(revertCredentials), + revertControl: credentialsChanged ? revertAction(revertCredentials) : undefined, } : null // The full pane (own header + rail) for the drawer body; the `bare` variant (toggle + key form /