Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file removed .pr/mcp-evidence/mcp-catalog-all-43-entries.png
Binary file not shown.
Binary file removed .pr/mcp-evidence/mcp-marketplace-all-entries.png
Binary file not shown.
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@ import { AxiosError } from "axios";
import { screen, waitFor } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { renderWithProviders } from "test-utils";
import { LlmSettingsLocalView } from "#/components/features/settings/llm-profiles/llm-settings-local-view";
import {
LlmSettingsLocalView,
shouldReapplyProfileAfterSave,
} from "#/components/features/settings/llm-profiles/llm-settings-local-view";
import * as useLlmProfilesHook from "#/hooks/query/use-llm-profiles";
import * as useActivateLlmProfileHook from "#/hooks/mutation/use-activate-llm-profile";
import * as useSaveLlmProfileHook from "#/hooks/mutation/use-save-llm-profile";
Expand Down Expand Up @@ -487,8 +490,7 @@ describe("LlmSettingsLocalView", () => {
message: "Profile renamed",
});

// Mock activateProfile
vi.mocked(ProfilesService.activateProfile).mockResolvedValue({
mockActivateMutateAsync.mockResolvedValue({
name: "my-renamed-profile",
message: "Profile activated",
llm_applied: true,
Expand Down Expand Up @@ -516,9 +518,9 @@ describe("LlmSettingsLocalView", () => {
// Click save
await user.click(screen.getByTestId("save-profile-btn"));

// Verify activateProfile was called after rename and save
// Verify activation mutation was called after rename and save
await waitFor(() => {
expect(ProfilesService.activateProfile).toHaveBeenCalledWith(
expect(mockActivateMutateAsync).toHaveBeenCalledWith(
"my-renamed-profile",
);
});
Expand All @@ -538,3 +540,45 @@ describe("LlmSettingsLocalView", () => {
});
});
});

describe("shouldReapplyProfileAfterSave", () => {
it("reapplies when saving the active profile without renaming", () => {
expect(
shouldReapplyProfileAfterSave({
activeProfileName: "gpt-4-profile",
originalName: "gpt-4-profile",
savedName: "gpt-4-profile",
}),
).toBe(true);
});

it("reapplies when the active profile was renamed", () => {
expect(
shouldReapplyProfileAfterSave({
activeProfileName: "gpt-4-profile",
originalName: "gpt-4-profile",
savedName: "my-renamed-profile",
}),
).toBe(true);
});

it("reapplies when creating a profile with the active profile name", () => {
expect(
shouldReapplyProfileAfterSave({
activeProfileName: "gpt-4-profile",
originalName: null,
savedName: "gpt-4-profile",
}),
).toBe(true);
});

it("does not reapply inactive profiles", () => {
expect(
shouldReapplyProfileAfterSave({
activeProfileName: "claude-profile",
originalName: "gpt-4-profile",
savedName: "gpt-4-profile",
}),
).toBe(false);
Comment thread
neubig marked this conversation as resolved.
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { ProfileNameInput } from "./profile-name-input";
import { BrandButton } from "#/components/features/settings/brand-button";
import { LlmSettingsScreen } from "#/routes/llm-settings";
import { useSaveLlmProfile } from "#/hooks/mutation/use-save-llm-profile";
import { useActivateLlmProfile } from "#/hooks/mutation/use-activate-llm-profile";
import { useLlmProfiles } from "#/hooks/query/use-llm-profiles";
import ProfilesService, {
ProfileInfo,
Expand All @@ -31,6 +32,20 @@ interface EditingProfile {
initialValues: SettingsFormValues;
}

export function shouldReapplyProfileAfterSave({
activeProfileName,
originalName,
savedName,
}: {
activeProfileName: string | null | undefined;
originalName: string | null | undefined;
savedName: string;
}): boolean {
if (!activeProfileName) return false;
if (originalName) return activeProfileName === originalName;
return activeProfileName === savedName;
}

/**
* LlmSettingsLocalView provides an integrated view for managing LLM profiles
* in local agent-server mode. It supports listing, creating, and editing profiles.
Expand All @@ -45,6 +60,7 @@ export function LlmSettingsLocalView() {
const { t } = useTranslation("openhands");
const { setHideSectionHeader } = useSettingsSectionHeader();
const saveProfile = useSaveLlmProfile();
const activateProfile = useActivateLlmProfile();
const { data: profilesData } = useLlmProfiles();

const [viewMode, setViewMode] = useState<ViewMode>("list");
Expand Down Expand Up @@ -173,7 +189,11 @@ export function LlmSettingsLocalView() {
const originalName = editingProfile?.profile.name;
const isRename =
viewMode === "edit" && originalName && originalName !== trimmedName;
const wasActive = profilesData?.active_profile === originalName;
const shouldReapplyActiveProfile = shouldReapplyProfileAfterSave({
activeProfileName: profilesData?.active_profile,
originalName,
savedName: trimmedName,
});

setIsSaving(true);
try {
Expand Down Expand Up @@ -220,10 +240,11 @@ export function LlmSettingsLocalView() {
},
});

// If the renamed profile was the active profile, re-activate it
// (the rename operation doesn't automatically update active_profile)
if (isRename && wasActive) {
await ProfilesService.activateProfile(trimmedName);
// Conversation start uses agent_settings.llm, not the profile row
// directly. Re-applying an active profile keeps those settings in sync
// after editing or recreating a profile with the active profile name.
if (shouldReapplyActiveProfile) {
await activateProfile.mutateAsync(trimmedName);
}

displaySuccessToast(
Expand All @@ -246,6 +267,7 @@ export function LlmSettingsLocalView() {
editingProfile,
profilesData?.active_profile,
saveProfile,
activateProfile,
t,
handleBackToList,
]);
Expand Down
Loading