openhands / src /hooks /use-chat-input-model-state.ts
SaylorTwift's picture
SaylorTwift HF Staff
Add files using upload-large-folder tool
a070805 verified
Raw History Blame Contribute Delete
4.65 kB
import { useActiveConversation } from "#/hooks/query/use-active-conversation";
import { useSettings } from "#/hooks/query/use-settings";
import {
type AcpModelContext,
useAcpModelContext,
} from "#/hooks/use-acp-model-context";
import { useActiveBackend } from "#/contexts/active-backend-context";
import { useCanManageOrgProfiles } from "#/hooks/use-can-manage-org-profiles";
import { useActiveAcpProfileDetail } from "#/hooks/query/use-active-acp-profile-detail";
import { useOptionalConversationId } from "#/hooks/use-conversation-id";
import {
getAcpPreferredDefaultModel,
getAcpProvider,
labelForAcpModel,
resolveEffectiveAcpModel,
type ACPModelOption,
} from "#/constants/acp-providers";
export interface ChatInputModelState {
isAcpContext: boolean;
displayModel: string | null;
currentModelId: string | null;
availableAcpModels: ACPModelOption[];
showAcpPicker: boolean;
switchConversationId: string | null;
destinationPath: AcpModelContext["destinationPath"];
destinationLabel: string;
}
export function useChatInputModelState(): ChatInputModelState {
const { data: conversation } = useActiveConversation();
const { data: settings } = useSettings();
const { conversationId } = useOptionalConversationId();
const { backend } = useActiveBackend();
const canManageOrgProfiles = useCanManageOrgProfiles();
// The active ACP AgentProfile's own fields are the conversation launch
// source (activation never writes agent_settings, so the global settings
// may describe a different provider). Null in a conversation, while
// loading, when the active profile isn't ACP, or on legacy backends
// without the profiles surface — settings are the fallback in that window.
const activeAcpProfile = useActiveAcpProfileDetail();
const {
isActiveAcpConversation,
isHomeAcp,
isAcpContext,
destinationPath,
destinationLabel,
} = useAcpModelContext();
const settingsAcpServerKey =
typeof settings?.agent_settings?.acp_server === "string"
? settings.agent_settings.acp_server
: null;
const acpServerKey = isActiveAcpConversation
? conversation?.acp_server
: isHomeAcp
? (activeAcpProfile?.acp_server ?? settingsAcpServerKey)
: null;
const acpProvider = isAcpContext ? getAcpProvider(acpServerKey) : undefined;
const settingsAcpModel =
typeof settings?.agent_settings?.acp_model === "string"
? settings.agent_settings.acp_model
: null;
// Home: read the model from the same source as the server key — mixing the
// profile's provider with a stale settings model could pair e.g. a codex
// provider with a claude model.
const acpConfiguredModel =
isHomeAcp && activeAcpProfile
? activeAcpProfile.acp_model
: settingsAcpModel;
let currentModelId: string | null = null;
if (isActiveAcpConversation) {
// ACP conversations store llm_model as the acp_model (persisted at
// creation time). Use it directly if available; fall back to the
// settings-configured model or provider default so the chip stays visible.
currentModelId =
conversation?.llm_model ??
resolveEffectiveAcpModel({
configured: acpConfiguredModel,
providerDefault: getAcpPreferredDefaultModel(acpServerKey),
});
} else if (isHomeAcp) {
currentModelId = resolveEffectiveAcpModel({
configured: acpConfiguredModel,
// Preferred default (Vertex-safe for Gemini) — must match what the
// start request would substitute for an unconfigured model.
providerDefault: getAcpPreferredDefaultModel(acpServerKey),
});
} else {
currentModelId = conversation?.llm_model ?? settings?.llm_model ?? null;
}
const displayModel =
currentModelId && isAcpContext
? (labelForAcpModel(acpServerKey, currentModelId) ?? currentModelId)
: currentModelId;
const availableAcpModels = acpProvider?.available_models ?? [];
// A home-page pick persists into the active ACP profile, which on cloud is
// org-owned — hide the selectable rows from members who'd only get a 403.
// Conversation-scoped switches (blank or started) stay member-allowed.
const canPersistHomeAcpModel =
!isHomeAcp || backend.kind !== "cloud" || canManageOrgProfiles;
const showAcpPicker =
isAcpContext && availableAcpModels.length > 0 && canPersistHomeAcpModel;
const switchConversationId = isActiveAcpConversation
? (conversationId ?? null)
: null;
return {
isAcpContext,
displayModel,
currentModelId,
availableAcpModels,
showAcpPicker,
switchConversationId,
destinationPath,
destinationLabel,
};
}