Tool picker
Choosing what an agent can use: tools grouped by app with a permission for each, a quick tools menu from the composer, and a permissions table for settings.
Loading preview…
Install
- 1
Run this command in your Tailwind CSS project. It adds
components/ai/tool-picker-grouped.tsxand installs its dependencies.$ npx shadcn@latest add https://gradientdeck.com/ai-components/r/tool-picker-grouped.json
Usage & API
Example
import { ToolPickerGrouped } from "@/components/ai/tool-picker-grouped"
<ToolPickerGrouped
apps={[]}
permissions={permissions}
onPermissionChange={() => {}}
onPermissionsChange={() => {}}
onConnect={() => {}}
query="…"
onQueryChange={() => {}}
expanded={[]}
onExpandedChange={() => {}}
/>Props ToolPickerGrouped
| Prop | Type & description | Required |
|---|---|---|
apps | ToolApp[] | Required |
permissions | Record<string, ToolPermission> | Required |
onPermissionChange | (toolId: string, value: ToolPermission) => void | Required |
onPermissionsChange | (changes: Record<string, ToolPermission>) => void | Required |
onConnect | (appId: string) => void | Required |
connecting | string | null | Optional |
query | string | Required |
onQueryChange | (query: string) => void | Required |
expanded | string[] | Required |
onExpandedChange | (ids: string[]) => void | Required |
Dependencies
- React
- Tailwind CSS
- motion
- lucide-react
Implementation notes
By app starts with a Search tools field and a count of tools that are on, counting only connected apps whose permission is not Off. Each app row has an icon, the account or Connected, or Not connected, a chevron, and either an n of n tools count plus a group checkbox, or Connect. The checkbox is mixed when only some tools are on. Expanded, every tool shows its name, a description on wider screens, and a segmented control labelled with the tool name: Allow, Ask, Off. Arrow keys move that control. Disconnected apps still list their tools, but the segments stay disabled. Write tools are the ones marked writes. They are the ones that land on Ask when the group is switched on from empty.
Example
import { ComposerToolsMenu } from "@/components/ai/tool-picker-composer"
<ComposerToolsMenu
tools={[]}
connectors={[]}
enabled={[]}
onToggle={() => {}}
open={true}
onOpenChange={() => {}}
/>Props ComposerToolsMenu
| Prop | Type & description | Required |
|---|---|---|
tools | ToolToggle[] | Required |
connectors | Connector[] | Required |
enabled | string[]Ids of enabled tools, connectors, and connector sources ("connectorId:sourceId"). | Required |
onToggle | (id: string) => void | Required |
onManage | () => void | Optional |
open | boolean | Required |
onOpenChange | (open: boolean) => void | Required |
placement | "button" | "container""button" opens above the + button. "container" opens above the nearest positioned parent, e.g. the whole composer. | Optional |
Dependencies
- React
- Tailwind CSS
- motion
- lucide-react
Implementation notes
Composer menu is a round plus button, labelled Tools and connectors, that rotates into a close icon while open. The menu opens upward. The first block is the tool list, each a menuitem checkbox with a check when its id is enabled. A Connectors label splits the next block. A connector with sources shows how many are on and opens a submenu. One without sources toggles in place, with a small switch. The submenu has a back row named for the connector, then each source with its detail line and its own switch. Manage connectors is the last row of the root menu. Escape steps back from a submenu, then closes. Enabled ids can be drawn as removable chips by the screen around the menu. The menu itself only reports toggles through onToggle.
Example
import { ToolPermissions } from "@/components/ai/tool-picker-permissions"
<ToolPermissions
tools={[]}
value={value}
onChange={() => {}}
onReset={() => {}}
/>Props ToolPermissions
| Prop | Type & description | Required |
|---|---|---|
tools | PermissionTool[] | Required |
value | Record<string, Permission> | Required |
onChange | (id: string, permission: Permission) => void | Required |
onReset | () => void | Required |
Dependencies
- React
- Tailwind CSS
- motion
- lucide-react
Implementation notes
Permissions is headed Tool permissions, with the line about what the assistant can run on its own and what it checks first. Reset to defaults sits at the right, and a changed count appears beside it once any value differs from that tool's default. The table columns are Tool, Server, Last used and Permission. Rows are grouped: Read-only with the note that it looks things up and changes nothing, Writes for creating or editing, and Destructive for deletes or overwrites that are hard to undo. Each permission control is a listbox. The long labels are Always allow, Ask every time and Never. Narrow screens shorten them to Allow, Ask and Never. A changed selection is tinted indigo, and the row's name gets a dot titled Changed from default. The menu opens upward when the row is near the bottom of the scroll area.
Choosing a layout
Use the grouped picker while someone is setting up an agent and still deciding which apps to connect. The per-tool Allow, Ask and Off control is the right grain there, because a write such as merging a pull request should not inherit the same permission as a search. Use the permissions table later, on a settings page, when the tools already exist and the question is risk: what may run alone, what must stop for a person, and what must never run. The risk headings are part of the layout, so pass a risk on each tool.
Use the composer menu on the message box, for the duration of one chat, when the person is turning Web search or a single repo on without opening settings. Manage connectors closes the menu and calls onManage, which is where you would send them to the grouped picker or the permissions table. Do not use the composer menu as the only place write tools can be gated. It has a check or a switch, not Ask. Pair it with the permissions table if a tool can change data. Memory and MCP servers are separate screens. This picker does not store memories or edit a server command.
What is the difference between Ask and Never?
In the grouped picker, Ask means the tool is on but should check first, and Off means it is not available. In the permissions table the three choices are Always allow, Ask every time and Never. Never is the strong refusal. Off and Never are not stored as the same value. Each variant has its own union, and you map them when you save.
Does Connect sign the user into an app?
No. Connect calls onConnect with the app id and shows a spinner while you pass that id as connecting. The picker never opens a login window. When you set the app's connected flag and account, the row swaps the button for the Allow, Ask and Off controls.