import { ChevronDownIcon, PlugIcon } from "lucide-react";
import { Suspense } from "react";
import type { FirstPartyMcpToolName } from "@opengeni/contracts";
import {
ModelPolicyPicker,
type ModelPolicyPickerGroupPresentation,
type ModelPolicyPickerProps,
} from "../../components/model-policy-picker";
import { ModelPolicyPickerMenu } from "../../components/model-policy-picker-menu";
import { Button } from "../../ui/button";
import { DropdownMenu, DropdownMenuContent, DropdownMenuTrigger } from "../../ui/dropdown-menu";
import type { McpServerOption } from "../lib/session-tools";
import { capabilityGroupSelection, sessionCapabilityGroupsFor } from "../lib/session-capabilities";
import { cn } from "../lib/utils";
import { openGeniGroupPresentation } from "./billing-class-mark";
export {
PickerAnimatedPage,
PickerBackHeader,
PickerNavRow,
} from "../../components/model-policy-picker";
export type { ModelPolicyPickerProps as ModelPickerProps } from "../../components/model-policy-picker";
export type { PickerModelRow } from "../../model-policy";
function firstPartyGroupPresentation(
presentation: ModelPolicyPickerGroupPresentation | undefined,
): ModelPolicyPickerGroupPresentation {
return {
...presentation,
opengeni_credits: {
...openGeniGroupPresentation.opengeni_credits,
...presentation?.opengeni_credits,
label:
presentation?.opengeni_credits?.label ?? openGeniGroupPresentation.opengeni_credits?.label,
icon:
presentation?.opengeni_credits?.icon === undefined
? openGeniGroupPresentation.opengeni_credits?.icon
: presentation.opengeni_credits.icon,
},
};
}
export function ModelPicker(props: ModelPolicyPickerProps) {
return (
);
}
export function ModelPickerMenu(props: ModelPolicyPickerProps) {
return (
);
}
function pillClass(active: boolean, className?: string): string {
return cn(
"h-8 max-w-[12rem] gap-1.5 rounded-full border px-2.5 text-xs",
active
? "border-brand/35 bg-brand/10 text-fg"
: "border-transparent text-fg-muted hover:border-border hover:bg-surface-2 hover:text-fg",
className,
);
}
export type SessionToolSelection = {
mcpServerIds: Set;
firstPartyToolIds: Set;
};
export function visibleSessionToolSelection(
selection: SessionToolSelection,
servers: ReadonlyArray>,
firstPartyTools: ReadonlyArray>,
): SessionToolSelection {
const visibleMcpIds = new Set(servers.map((server) => server.id));
const visibleFirstPartyIds = new Set(firstPartyTools.map((tool) => tool.id));
return {
mcpServerIds: new Set([...selection.mcpServerIds].filter((id) => visibleMcpIds.has(id))),
firstPartyToolIds: new Set(
[...selection.firstPartyToolIds].filter((id) => visibleFirstPartyIds.has(id)),
),
};
}
import type { SessionConnectorsMenuProps } from "./session-connectors-menu-body";
import {
COMPOSER_MENU_PANEL_CLASS,
ComposerMenuHeader,
ComposerMenuRowsSkeleton,
lazyComposerPanel,
} from "../../ui/composer-menu";
import { isComposerConnector } from "../lib/session-tools";
const loadSessionConnectorsMenu = () => import("./session-connectors-menu-body");
const LazySessionConnectorsMenuBody = lazyComposerPanel(() =>
loadSessionConnectorsMenu().then((module) => module.SessionConnectorsMenuBody),
);
/** Secondary connector/account controls load only when their menu is opened. */
export function SessionToolsMenuBody(props: SessionConnectorsMenuProps) {
// Same header and one row per known connector, so the menu keeps its size.
const rows = Math.min(6, Math.max(1, props.servers.filter(isComposerConnector).length));
return (
>
}
>
);
}
export const SESSION_TOOLS_PANEL_CLASS = COMPOSER_MENU_PANEL_CLASS;
export function sessionToolSelectionSummary(props: {
servers: McpServerOption[];
firstPartyTools: ReadonlyArray<{ id: FirstPartyMcpToolName; name: string }>;
selection: SessionToolSelection;
}) {
const visibleSelection = visibleSessionToolSelection(
props.selection,
props.servers,
props.firstPartyTools,
);
const total = props.servers.length + props.firstPartyTools.length;
const selected = visibleSelection.mcpServerIds.size + visibleSelection.firstPartyToolIds.size;
const groups = sessionCapabilityGroupsFor(props.firstPartyTools);
const selectedGroups = groups.filter(
(group) => capabilityGroupSelection(group, visibleSelection.firstPartyToolIds) !== "none",
).length;
const selectedCapabilities = visibleSelection.mcpServerIds.size + selectedGroups;
return {
total,
selected,
selectedCapabilities,
label: selected === total ? "All" : String(selectedCapabilities),
};
}
export function SessionToolPicker(props: {
servers: McpServerOption[];
firstPartyTools: ReadonlyArray<{ id: FirstPartyMcpToolName; name: string }>;
selection: SessionToolSelection;
disabled?: boolean | undefined;
saving?: boolean | undefined;
customizing?: boolean | undefined;
onCustomizingChange?: ((customizing: boolean) => void) | undefined;
/** Prefer `bottom` on home/new-chat; `top` when composer is docked at bottom. */
menuSide?: "top" | "bottom" | undefined;
/** Extra classes on the bar trigger (e.g. `max-sm:hidden` when opened from +). */
triggerClassName?: string | undefined;
onChange: (selection: SessionToolSelection) => void;
}) {
const { total, selected, label } = sessionToolSelectionSummary(props);
const menuSide = props.menuSide ?? "bottom";
if (total === 0) return null;
return (
);
}