File size: 4,650 Bytes
a070805 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 | 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,
};
}
|