Download src/hooks/mutation/use-switch-acp-model.ts from SaylorTwift/openhands: direct link, hf CLI and curl.
- Browser
- Download file 6.16 kB
-
https://huggingface.co/SaylorTwift/openhands/resolve/main/src/hooks/mutation/use-switch-acp-model.ts
- Command line
-
hf download hf://SaylorTwift/openhands/src/hooks/mutation/use-switch-acp-model.ts
-
curl -L -o use-switch-acp-model.ts https://huggingface.co/SaylorTwift/openhands/resolve/main/src/hooks/mutation/use-switch-acp-model.ts
6.16 kB
| import { useMutation, useQueryClient } from "@tanstack/react-query"; | |
| import AgentServerConversationService from "#/api/conversation-service/agent-server-conversation-service.api"; | |
| import AgentProfilesService, { | |
| type AgentProfileListResponse, | |
| } from "#/api/agent-profiles-service/agent-profiles-service.api"; | |
| import SettingsService from "#/api/settings-service/settings-service.api"; | |
| import { mergeAgentProfileSaveInput } from "#/components/features/settings/agent-profiles/merge-agent-profile-save-input"; | |
| import { useActiveBackend } from "#/contexts/active-backend-context"; | |
| import { | |
| AGENT_PROFILES_QUERY_KEYS, | |
| AGENT_PROFILES_RETRY_OPTIONS, | |
| SETTINGS_QUERY_KEYS, | |
| } from "#/hooks/query/query-keys"; | |
| import { agentProfileDetailQueryKey } from "#/hooks/query/use-active-acp-profile-detail"; | |
| import { invalidateConversationQueries } from "./conversation-mutation-utils"; | |
| interface SwitchAcpModelVars { | |
| /** | |
| * When set, the ACP conversation's running model is swapped live via the | |
| * wrapper's ``session/set_model`` (POST /switch_acp_model) and the user's | |
| * saved default is untouched. When null (home page / no session), the model | |
| * is persisted as the default the next conversation launches with. | |
| */ | |
| conversationId: string | null; | |
| model: string; | |
| } | |
| /** | |
| * ACP analog of {@link useSwitchLlmProfile}. Switches the ACP model | |
| * per-conversation when called from inside a conversation (live in-place model | |
| * switch; a created-but-not-yet-run conversation is a persist-only deferral on | |
| * the server, so blank conversations work too); persists it as the launch | |
| * default when called from the home page. | |
| * | |
| * The home-page persist target is the **active ACP AgentProfile**: profile | |
| * launches resolve the agent server-side purely from the stored profile | |
| * (``agent_profile_id`` and ``agent_settings`` are mutually exclusive launch | |
| * sources), so writing ``agent_settings.acp_model`` would not affect them. | |
| * Only when the profile list resolves with no active ACP profile does the | |
| * write fall back to the ``agent_settings_diff`` the legacy launch path | |
| * reads. A failed profile discovery propagates instead of downgrading | |
| * (#16523): an active-profile launch ignores agent_settings, so persisting | |
| * the pick there would silently drop it. | |
| * | |
| * Invalidates the same conversation/settings query keys the profile hook does | |
| * so the chat-input model chip + conversation chip refresh with the new model. | |
| */ | |
| export const useSwitchAcpModel = () => { | |
| const queryClient = useQueryClient(); | |
| const { backend, orgId } = useActiveBackend(); | |
| return useMutation({ | |
| mutationFn: async ({ conversationId, model }: SwitchAcpModelVars) => { | |
| if (conversationId) { | |
| await AgentServerConversationService.switchAcpModel( | |
| conversationId, | |
| model, | |
| ); | |
| return; | |
| } | |
| // Home page: resolve the active profile from the shared query cache | |
| // (same keys/retry policy as useCreateConversation's launch path, so a | |
| // pick can't disagree with what a subsequent send would launch). Like | |
| // that launch path, do not fall back to the legacy agent_settings | |
| // persist when profile discovery fails (#16523): an active-profile | |
| // launch ignores agent_settings, so the pick would be silently dropped. | |
| // The rejection surfaces via the global mutation error toast. | |
| const agentProfiles: AgentProfileListResponse = | |
| await queryClient.ensureQueryData({ | |
| queryKey: [...AGENT_PROFILES_QUERY_KEYS.all, backend.id, orgId], | |
| queryFn: AgentProfilesService.listProfiles, | |
| ...AGENT_PROFILES_RETRY_OPTIONS, | |
| }); | |
| const activeProfile = agentProfiles.profiles?.find( | |
| (profile) => | |
| profile.id != null && | |
| profile.id === agentProfiles.active_agent_profile_id, | |
| ); | |
| if (activeProfile?.agent_kind === "acp") { | |
| // Merge over the stored profile so a model-only pick can't wipe the | |
| // fields this surface doesn't model (command, session mode, …). | |
| const detail = await queryClient.ensureQueryData({ | |
| queryKey: agentProfileDetailQueryKey( | |
| backend.id, | |
| orgId, | |
| activeProfile.name, | |
| ), | |
| queryFn: () => AgentProfilesService.getProfile(activeProfile.name), | |
| ...AGENT_PROFILES_RETRY_OPTIONS, | |
| }); | |
| await AgentProfilesService.saveProfile( | |
| activeProfile.name, | |
| mergeAgentProfileSaveInput(detail.profile, { | |
| agent_kind: "acp", | |
| acp_model: model, | |
| }), | |
| ); | |
| return; | |
| } | |
| // No active ACP profile: persist as the agent-settings default the | |
| // legacy launch path reads. The backend deep-merges | |
| // ``agent_settings_diff`` into the existing ``agent_settings`` dict, so | |
| // a scalar ``acp_model`` diff updates only the model and preserves the | |
| // selected provider + command. | |
| await SettingsService.saveSettings({ | |
| agent_settings_diff: { acp_model: model }, | |
| }); | |
| }, | |
| onSuccess: (_data, { conversationId }) => { | |
| if (conversationId) { | |
| invalidateConversationQueries(queryClient, conversationId); | |
| } else { | |
| // Mirror useSwitchLlmProfile's home-page path: clear the stale settings | |
| // cache so the next conversation-start reads the newly saved default, | |
| // and refetch the settings query so the home-page chip updates. The | |
| // agent-profiles prefix covers both the list and the detail entry the | |
| // profile-persist path just rewrote. | |
| SettingsService.invalidateCache(); | |
| queryClient.invalidateQueries({ | |
| queryKey: SETTINGS_QUERY_KEYS.personal(), | |
| }); | |
| queryClient.invalidateQueries({ | |
| queryKey: AGENT_PROFILES_QUERY_KEYS.all, | |
| }); | |
| } | |
| }, | |
| // No meta.disableToast: unlike useSwitchLlmProfile (which tailors a | |
| // "Switched to {name} failed" message via its own onError), there's no | |
| // per-action message worth tailoring here, so the global mutation error | |
| // toast reports a failed switch / profile or settings write rather than | |
| // swallowing it. | |
| }); | |
| }; | |