PermissionModeSelector

Switch the agent permission mode: Default, Accept edits, Plan or Bypass. Each mode shows its description, and dangerous modes show a warning. variant="auto" renders a segmented control when there is room and a select in narrow containers.

Getting Started
$pnpm add @sinups/ai-kit @mantine/core @mantine/hooks @tabler/icons-react
Examples
Segmented
Select
Source

The component source as it ships in the package. Useful as a reference, or as a starting point if you want to fork a piece.

permissions/PermissionModeSelector.tsx
import React, { memo, useId } from 'react';
import { Alert, Box, Input, SegmentedControl, Select, Stack, Text } from '@mantine/core';
import { useElementSize } from '@mantine/hooks';
import { IconAlertTriangle } from '@tabler/icons-react';
import { cx } from '../utils/cx';
import {
  PERMISSION_MODES,
  PERMISSION_MODE_ORDER,
  type PermissionMode,
  type PermissionModeMeta,
} from './types';
import classes from './PermissionModeSelector.module.css';

export interface PermissionModeSelectorLabels {
  /** Label, description and warning flag of each mode */
  modes: Partial<Record<PermissionMode, Partial<PermissionModeMeta>>>;
  /** Warning shown when a dangerous mode is selected */
  dangerWarning: string;
}

export const DEFAULT_PERMISSION_MODE_SELECTOR_LABELS: PermissionModeSelectorLabels = {
  modes: {},
  dangerWarning:
    'Tool calls run without approval, including commands that change files or reach the network. Use only in an isolated environment.',
};

export interface PermissionModeSelectorProps {
  /** Selected mode */
  value: PermissionMode;
  /** Called with the mode the user picked */
  onChange: (mode: PermissionMode) => void;
  /** Modes offered, all modes by default */
  modes?: PermissionMode[];
  /** Field label, `Permission mode` by default */
  label?: React.ReactNode;
  /** `segmented` shows all modes at once, `select` a dropdown, `auto` picks by component width, `auto` by default */
  variant?: 'auto' | 'segmented' | 'select';
  /** Disables the control */
  disabled?: boolean;
  /** Overrides of the default English labels */
  labels?: Partial<PermissionModeSelectorLabels>;
  /** Class name added to the root element */
  className?: string;
  /** Inline styles added to the root element */
  style?: React.CSSProperties;
}

const SEGMENTED_MIN_WIDTH = 440;

/** Permission mode picker with a description of the selected mode and a warning for modes that skip approvals */
export const PermissionModeSelector = memo(function PermissionModeSelector({
  value,
  onChange,
  modes = PERMISSION_MODE_ORDER,
  label = 'Permission mode',
  variant = 'auto',
  disabled = false,
  labels: labelsProp,
  className,
  style,
}: PermissionModeSelectorProps) {
  const labels = { ...DEFAULT_PERMISSION_MODE_SELECTOR_LABELS, ...labelsProp };
  const { ref, width } = useElementSize();
  const labelId = useId();
  const metaOf = (mode: PermissionMode): PermissionModeMeta => ({
    ...PERMISSION_MODES[mode],
    ...labels.modes[mode],
  });
  const selected = metaOf(value);
  const data = modes.map((mode) => ({ value: mode, label: metaOf(mode).label }));
  const segmented = variant === 'segmented' || (variant === 'auto' && width >= SEGMENTED_MIN_WIDTH);
  const handleChange = (next: string | null) => {
    if (next) {
      onChange(next as PermissionMode);
    }
  };

  return (
    <Stack
      ref={ref}
      gap="xs"
      className={cx(classes.root, className)}
      style={style}
      data-measuring={(variant === 'auto' && width === 0) || undefined}
    >
      {segmented ? (
        <Input.Wrapper label={label} labelProps={{ id: labelId }}>
          <Box mt={4}>
            <SegmentedControl
              fullWidth
              aria-labelledby={labelId}
              data={data}
              value={value}
              onChange={handleChange}
              disabled={disabled}
              color={selected.dangerous ? 'red' : undefined}
            />
          </Box>
        </Input.Wrapper>
      ) : (
        <Select
          label={label}
          data={data}
          value={value}
          onChange={handleChange}
          allowDeselect={false}
          disabled={disabled}
        />
      )}
      <Text size="xs" c="dimmed" aria-live="polite">
        {selected.description}
      </Text>
      {selected.dangerous && (
        <Alert color="red" variant="light" icon={<IconAlertTriangle size={16} />} p="xs">
          <Text size="xs">{labels.dangerWarning}</Text>
        </Alert>
      )}
    </Stack>
  );
});

PermissionModeSelector.displayName = 'PermissionModeSelector';
API reference
Prop
Type
Required
value
PermissionMode
Yes
Selected mode
onChange
(mode: PermissionMode) => void
Yes
Called with the mode the user picked
modes
PermissionMode[]
No
Modes offered, all modes by default
label
React.ReactNode
No
Field label, `Permission mode` by default
variant
'auto' | 'segmented' | 'select'
No
`segmented` shows all modes at once, `select` a dropdown, `auto` picks by component width, `auto` by default
disabled
boolean
No
Disables the control
labels
Partial<PermissionModeSelectorLabels>
No
Overrides of the default English labels
className
string
No
Class name added to the root element
style
React.CSSProperties
No
Inline styles added to the root element