Skip to content

Commit c3db3dc

Browse files
committed
feat(agent): allow variable tool permission modes
1 parent b4f42b3 commit c3db3dc

21 files changed

Lines changed: 1134 additions & 108 deletions

File tree

apps/docs/openapi-v2-workflows.json

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7995,6 +7995,11 @@
79957995
"enum": ["auto", "force", "none"],
79967996
"description": "When the Agent may call the tool: `auto` lets the model decide, `force` requires a call, and `none` disables it. Omitted means `auto`."
79977997
},
7998+
"usageControlExpression": {
7999+
"type": "string",
8000+
"maxLength": 2048,
8001+
"description": "Variable-capable tool mode value used when the matching canonical mode is `advanced`. It must resolve to `auto`, `force`, or `none` at execution time."
8002+
},
79988003
"params": {
79998004
"type": "object",
80008005
"propertyNames": {
@@ -8041,6 +8046,11 @@
80418046
"type": "string",
80428047
"enum": ["auto", "force", "none"],
80438048
"description": "When the Agent may call the tool: `auto` lets the model decide, `force` requires a call, and `none` disables it. Omitted means `auto`."
8049+
},
8050+
"usageControlExpression": {
8051+
"type": "string",
8052+
"maxLength": 2048,
8053+
"description": "Variable-capable tool mode value used when the matching canonical mode is `advanced`. It must resolve to `auto`, `force`, or `none` at execution time."
80448054
}
80458055
},
80468056
"required": ["type", "customToolId"],
@@ -8109,6 +8119,11 @@
81098119
"type": "string",
81108120
"enum": ["auto", "force", "none"],
81118121
"description": "When the Agent may call the tool: `auto` lets the model decide, `force` requires a call, and `none` disables it. Omitted means `auto`."
8122+
},
8123+
"usageControlExpression": {
8124+
"type": "string",
8125+
"maxLength": 2048,
8126+
"description": "Variable-capable tool mode value used when the matching canonical mode is `advanced`. It must resolve to `auto`, `force`, or `none` at execution time."
81128127
}
81138128
},
81148129
"required": ["type", "schema", "code"],
@@ -8174,6 +8189,11 @@
81748189
"type": "string",
81758190
"enum": ["auto", "force", "none"],
81768191
"description": "When the Agent may call the tool: `auto` lets the model decide, `force` requires a call, and `none` disables it. Omitted means `auto`."
8192+
},
8193+
"usageControlExpression": {
8194+
"type": "string",
8195+
"maxLength": 2048,
8196+
"description": "Variable-capable tool mode value used when the matching canonical mode is `advanced`. It must resolve to `auto`, `force`, or `none` at execution time."
81778197
}
81788198
},
81798199
"required": ["type", "params"],
@@ -8282,6 +8302,11 @@
82828302
"type": "string",
82838303
"enum": ["auto", "force", "none"],
82848304
"description": "When the Agent may call the tool: `auto` lets the model decide, `force` requires a call, and `none` disables it. Omitted means `auto`."
8305+
},
8306+
"usageControlExpression": {
8307+
"type": "string",
8308+
"maxLength": 2048,
8309+
"description": "Variable-capable tool mode value used when the matching canonical mode is `advanced`. It must resolve to `auto`, `force`, or `none` at execution time."
82858310
}
82868311
},
82878312
"required": ["type", "params"],
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
import { Button, ChipCombobox, cn, Label, Tooltip } from '@sim/emcn'
2+
import { ArrowLeftRight } from '@sim/emcn/icons'
3+
import type { CanonicalMode } from '@/lib/workflows/subblocks/visibility'
4+
import type { StoredTool } from '@/lib/workflows/tool-input/types'
5+
import { ShortInput } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/short-input'
6+
7+
interface ToolUsageControlProps {
8+
blockId: string
9+
aggregateSubBlockId: string
10+
toolIndex: number
11+
tool: StoredTool
12+
mode: CanonicalMode
13+
supportsForce: boolean
14+
disabled: boolean
15+
onFixedChange: (value: NonNullable<StoredTool['usageControl']>) => void
16+
onExpressionChange: (value: string) => void
17+
onModeToggle: () => void
18+
}
19+
20+
const MODE_OPTIONS = [
21+
{
22+
value: 'auto',
23+
label: 'Auto',
24+
suffixElement: <span className='text-[var(--text-tertiary)]'>(model decides)</span>,
25+
},
26+
{
27+
value: 'force',
28+
label: 'Force',
29+
suffixElement: <span className='text-[var(--text-tertiary)]'>(always use)</span>,
30+
},
31+
{
32+
value: 'none',
33+
label: 'None',
34+
suffixElement: <span className='text-[var(--text-tertiary)]'>(disable tool)</span>,
35+
},
36+
] as const
37+
38+
export function ToolUsageControl({
39+
blockId,
40+
aggregateSubBlockId,
41+
toolIndex,
42+
tool,
43+
mode,
44+
supportsForce,
45+
disabled,
46+
onFixedChange,
47+
onExpressionChange,
48+
onModeToggle,
49+
}: ToolUsageControlProps) {
50+
const toggleLabel = mode === 'advanced' ? 'Switch to selector' : 'Switch to variable'
51+
52+
return (
53+
<div className='subblock-content flex w-full min-w-0 flex-col gap-2.5'>
54+
<div className='flex items-center justify-between gap-1.5 pl-0.5'>
55+
<Label>Permission Mode</Label>
56+
<Tooltip.Root>
57+
<Tooltip.Trigger asChild>
58+
<Button
59+
type='button'
60+
variant='ghost'
61+
size='icon'
62+
className='shrink-0'
63+
onClick={onModeToggle}
64+
disabled={disabled}
65+
aria-label={toggleLabel}
66+
>
67+
<ArrowLeftRight
68+
className={cn(
69+
'size-[12px]!',
70+
mode === 'advanced'
71+
? 'text-[var(--text-primary)]'
72+
: 'text-[var(--text-secondary)]'
73+
)}
74+
/>
75+
</Button>
76+
</Tooltip.Trigger>
77+
<Tooltip.Content side='top'>{toggleLabel}</Tooltip.Content>
78+
</Tooltip.Root>
79+
</div>
80+
{mode === 'advanced' ? (
81+
<ShortInput
82+
blockId={blockId}
83+
subBlockId={aggregateSubBlockId}
84+
config={{
85+
id: 'usageControlExpression',
86+
title: 'Permission Mode',
87+
type: 'short-input',
88+
}}
89+
value={tool.usageControlExpression ?? ''}
90+
onChange={onExpressionChange}
91+
placeholder='"auto", "force", or "none"'
92+
disabled={disabled}
93+
workflowSearchValuePath={[toolIndex, 'usageControlExpression']}
94+
/>
95+
) : (
96+
<ChipCombobox
97+
options={MODE_OPTIONS.map((option) => ({
98+
...option,
99+
disabled: option.value === 'force' && !supportsForce,
100+
suffixElement:
101+
option.value === 'force' && !supportsForce ? (
102+
<span className='text-[var(--text-tertiary)]'>(not supported by model)</span>
103+
) : (
104+
option.suffixElement
105+
),
106+
onSelect: () => onFixedChange(option.value),
107+
}))}
108+
value={tool.usageControl ?? 'auto'}
109+
disabled={disabled}
110+
aria-label='Permission Mode'
111+
/>
112+
)}
113+
</div>
114+
)
115+
}

0 commit comments

Comments
 (0)