char-fork/llama.cpp

git clone https://git.t4t.associates/char-fork/llama.cpp

Aleksander Grygierui: Replace per-conversation MCP overrides with per-conversation tool policy (#27745)fe235f434

char/aliceai-t5-moe
5.6 KiB162 linesraw
1import { CLI_FLAGS } from '$lib/constants';
2import { ToolSource } from '$lib/enums';
3import { conversationsStore, mcpStore, toolsStore } from '$lib/stores';
4import type { ToolEntry, ToolGroup } from '$lib/types';
5import { SvelteSet } from 'svelte/reactivity';
6
7export interface UseToolsPanelReturn {
8	readonly expandedGroups: SvelteSet<string>;
9	readonly categoryGroups: ToolGroup[];
10	readonly mcpGroups: ToolGroup[];
11	readonly totalToolCount: number;
12	readonly noToolsInfoMessage: string | null;
13	isGroupChecked(group: ToolGroup): boolean;
14	getEnabledToolCount(group: ToolGroup): number;
15	getGroupCheckState(group: ToolGroup): { checked: boolean; indeterminate: boolean };
16	getFavicon(group: ToolGroup): string | null;
17	isGroupDisabled(group: ToolGroup): boolean;
18	isToolEnabled(entry: ToolEntry): boolean;
19	isToolParentDisabled(entry: ToolEntry): boolean;
20	toggleTool(entry: ToolEntry): void;
21	toggleGroupExpanded(key: string): void;
22	/** Toggle all tools in a group by its stable key (avoids stale group object references). */
23	toggleGroupByKey(key: string): void;
24	handleOpen(): void;
25}
26
27/**
28 * Shared reactive state and helpers for the tools panel UI.
29 *
30 * Used by both the desktop dropdown (`ChatFormActionAddToolsSubmenu`)
31 * and the mobile sheet (`ChatFormActionAddSheet`) to avoid
32 * duplicating group filtering, checked-state derivation, and favicon logic.
33 *
34 * All toggle state routes through `conversationsStore.preferences`: with an
35 * active conversation it edits that conversation's tool policy, on the
36 * new-chat screen it edits the global defaults seeded into new conversations.
37 */
38export function useToolsPanel(): UseToolsPanelReturn {
39	const expandedGroups = new SvelteSet<string>();
40	const groups = $derived(toolsStore.toolGroups);
41	// non-MCP groups are 1:1 with tool categories; MCP tools group per server
42	const categoryGroups = $derived(groups.filter((g) => g.source !== ToolSource.MCP));
43	const mcpGroups = $derived(groups.filter((g) => g.source === ToolSource.MCP));
44	const totalToolCount = $derived(groups.reduce((n, g) => n + g.tools.length, 0));
45	const noToolsInfoMessage = $derived.by(() => {
46		if (toolsStore.loading) return null;
47
48		if (toolsStore.toolGroups.length > 0) return null;
49
50		// Tools endpoint is unreachable (404) — server started without --tools
51		if (toolsStore.isToolsEndpointUnreachable) {
52			return `To enable Server Tools you need to run llama-server with ${CLI_FLAGS.TOOLS} all or ${CLI_FLAGS.TOOLS} <name> flag. To see MCP Tools you need to add / enable MCP Server(s).`;
53		}
54
55		// Other errors — return null so UI shows "Failed to load tools"
56		if (toolsStore.error) return null;
57
58		return `To enable Server Tools you need to run llama-server with ${CLI_FLAGS.TOOLS} all or ${CLI_FLAGS.TOOLS} <name> flag. To see MCP Tools you need to add / enable MCP Server(s).`;
59	});
60
61	function isGroupChecked(group: ToolGroup): boolean {
62		return conversationsStore.preferences.isGroupChecked(group);
63	}
64
65	function getEnabledToolCount(group: ToolGroup): number {
66		return group.tools.filter((tool) => conversationsStore.preferences.isToolActive(tool)).length;
67	}
68
69	/**
70	 * Group checkbox state: checked is the parent flag (category on, or the
71	 * server key on for MCP groups); indeterminate marks the mixed case where
72	 * the parent is on but nothing or only part of the group is enabled.
73	 * isToolActive folds the parent gates into the count, so a disabled parent
74	 * always yields plain unchecked.
75	 */
76	function getGroupCheckState(group: ToolGroup): { checked: boolean; indeterminate: boolean } {
77		const checked = isGroupChecked(group);
78		const enabledCount = getEnabledToolCount(group);
79		const indeterminate =
80			group.tools.length > 0 && (enabledCount === 0 ? checked : enabledCount < group.tools.length);
81
82		return { checked, indeterminate };
83	}
84
85	function getFavicon(group: ToolGroup): string | null {
86		if (group.source !== ToolSource.MCP || !group.serverId) return null;
87
88		return mcpStore.getServerFavicon(group.serverId);
89	}
90
91	function isGroupDisabled(group: ToolGroup): boolean {
92		// MCP server groups gray out while the whole MCP category is off
93		return (
94			group.source === ToolSource.MCP &&
95			!conversationsStore.preferences.isCategoryEnabled(ToolSource.MCP)
96		);
97	}
98
99	function isToolEnabled(entry: ToolEntry): boolean {
100		return conversationsStore.preferences.isToolEnabled(entry.key);
101	}
102
103	function isToolParentDisabled(entry: ToolEntry): boolean {
104		return conversationsStore.preferences.isToolParentDisabled(entry);
105	}
106
107	function toggleTool(entry: ToolEntry): void {
108		void conversationsStore.preferences.toggleTool(entry.key);
109	}
110
111	function toggleGroupExpanded(key: string): void {
112		if (expandedGroups.has(key)) {
113			expandedGroups.delete(key);
114		} else {
115			expandedGroups.add(key);
116		}
117	}
118
119	function toggleGroupByKey(key: string): void {
120		// Find current group by key to get up-to-date tool references
121		const group = groups.find((g) => g.key === key);
122
123		if (!group) return;
124
125		void conversationsStore.preferences.toggleGroup(group);
126	}
127
128	function handleOpen(): void {
129		if (toolsStore.serverTools.length === 0 && !toolsStore.loading) {
130			toolsStore.fetchServerTools();
131		}
132
133		mcpStore.runHealthChecksForServers(mcpStore.getServers().filter((s) => s.enabled));
134	}
135
136	return {
137		get categoryGroups() {
138			return categoryGroups;
139		},
140		expandedGroups,
141		getEnabledToolCount,
142		getFavicon,
143		getGroupCheckState,
144		handleOpen,
145		isGroupChecked,
146		isGroupDisabled,
147		isToolEnabled,
148		isToolParentDisabled,
149		get mcpGroups() {
150			return mcpGroups;
151		},
152		get noToolsInfoMessage() {
153			return noToolsInfoMessage;
154		},
155		toggleGroupByKey,
156		toggleGroupExpanded,
157		toggleTool,
158		get totalToolCount() {
159			return totalToolCount;
160		}
161	};
162}