Skip to content

Commit 37fd21e

Browse files
authored
fix(agent): tool credential dropdown (#1884)
* Checkpoint * Fix dropdown
1 parent 8273185 commit 37fd21e

File tree

6 files changed

+110
-32
lines changed

6 files changed

+110
-32
lines changed

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel-new/components/editor/components/sub-block/components/channel-selector/channel-selector-input.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ export function ChannelSelectorInput({
5151
const { finalDisabled, dependsOn, dependencyValues } = useDependsOnGate(blockId, subBlock, {
5252
disabled,
5353
isPreview,
54+
previewContextValues,
5455
})
5556

5657
// Choose credential strictly based on auth method - use effective values

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel-new/components/editor/components/sub-block/components/file-selector/file-selector-input.tsx

Lines changed: 45 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,11 @@ export function FileSelectorInput({
4242
const params = useParams()
4343
const workflowIdFromUrl = (params?.workflowId as string) || activeWorkflowId || ''
4444
// Central dependsOn gating for this selector instance
45-
const { finalDisabled } = useDependsOnGate(blockId, subBlock, { disabled, isPreview })
45+
const { finalDisabled, dependsOn } = useDependsOnGate(blockId, subBlock, {
46+
disabled,
47+
isPreview,
48+
previewContextValues,
49+
})
4650

4751
// Helper to coerce various preview value shapes into a string ID
4852
const coerceToIdString = (val: unknown): string => {
@@ -63,20 +67,28 @@ export function FileSelectorInput({
6367

6468
// Use the proper hook to get the current value and setter
6569
const [storeValue, setStoreValue] = useSubBlockValue(blockId, subBlock.id)
66-
const [connectedCredential] = useSubBlockValue(blockId, 'credential')
67-
const [domainValue] = useSubBlockValue(blockId, 'domain')
68-
const [projectIdValue] = useSubBlockValue(blockId, 'projectId')
69-
const [planIdValue] = useSubBlockValue(blockId, 'planId')
70-
const [teamIdValue] = useSubBlockValue(blockId, 'teamId')
71-
const [operationValue] = useSubBlockValue(blockId, 'operation')
70+
const [connectedCredentialFromStore] = useSubBlockValue(blockId, 'credential')
71+
const [domainValueFromStore] = useSubBlockValue(blockId, 'domain')
72+
const [projectIdValueFromStore] = useSubBlockValue(blockId, 'projectId')
73+
const [planIdValueFromStore] = useSubBlockValue(blockId, 'planId')
74+
const [teamIdValueFromStore] = useSubBlockValue(blockId, 'teamId')
75+
const [operationValueFromStore] = useSubBlockValue(blockId, 'operation')
76+
77+
// Use previewContextValues if provided (for tools inside agent blocks), otherwise use store values
78+
const connectedCredential = previewContextValues?.credential ?? connectedCredentialFromStore
79+
const domainValue = previewContextValues?.domain ?? domainValueFromStore
80+
const projectIdValue = previewContextValues?.projectId ?? projectIdValueFromStore
81+
const planIdValue = previewContextValues?.planId ?? planIdValueFromStore
82+
const teamIdValue = previewContextValues?.teamId ?? teamIdValueFromStore
83+
const operationValue = previewContextValues?.operation ?? operationValueFromStore
7284

7385
// Determine if the persisted credential belongs to the current viewer
7486
// Use service providerId where available (e.g., onedrive/sharepoint) instead of base provider ("microsoft")
7587
const foreignCheckProvider = subBlock.serviceId
7688
? getProviderIdFromServiceId(subBlock.serviceId)
7789
: (subBlock.provider as string) || ''
7890
const { isForeignCredential } = useForeignCredential(
79-
foreignCheckProvider,
91+
subBlock.provider || subBlock.serviceId || 'outlook',
8092
(connectedCredential as string) || ''
8193
)
8294

@@ -109,6 +121,20 @@ export function FileSelectorInput({
109121
// Use preview value when in preview mode, otherwise use store value
110122
const value = isPreview ? previewValue : storeValue
111123

124+
const credentialDependencySatisfied = (() => {
125+
if (!dependsOn.includes('credential')) return true
126+
const normalizedCredential = coerceToIdString(connectedCredential)
127+
if (!normalizedCredential || normalizedCredential.trim().length === 0) {
128+
return false
129+
}
130+
if (isForeignCredential) {
131+
return false
132+
}
133+
return true
134+
})()
135+
136+
const shouldForceDisable = !credentialDependencySatisfied
137+
112138
// For Google Drive
113139
const clientId = getEnv('NEXT_PUBLIC_GOOGLE_CLIENT_ID') || ''
114140
const apiKey = getEnv('NEXT_PUBLIC_GOOGLE_API_KEY') || ''
@@ -132,7 +158,7 @@ export function FileSelectorInput({
132158
collaborativeSetSubblockValue(blockId, subBlock.id, val)
133159
}}
134160
label={subBlock.placeholder || 'Select Google Calendar'}
135-
disabled={finalDisabled}
161+
disabled={finalDisabled || shouldForceDisable}
136162
showPreview={true}
137163
credentialId={credential}
138164
workflowId={workflowIdFromUrl}
@@ -166,7 +192,7 @@ export function FileSelectorInput({
166192
requiredScopes={subBlock.requiredScopes || []}
167193
serviceId={subBlock.serviceId}
168194
label={subBlock.placeholder || 'Select Confluence page'}
169-
disabled={finalDisabled}
195+
disabled={finalDisabled || shouldForceDisable}
170196
showPreview={true}
171197
credentialId={credential}
172198
workflowId={workflowIdFromUrl}
@@ -200,7 +226,7 @@ export function FileSelectorInput({
200226
requiredScopes={subBlock.requiredScopes || []}
201227
serviceId={subBlock.serviceId}
202228
label={subBlock.placeholder || 'Select Jira issue'}
203-
disabled={finalDisabled}
229+
disabled={finalDisabled || shouldForceDisable}
204230
showPreview={true}
205231
credentialId={credential}
206232
projectId={(projectIdValue as string) || ''}
@@ -230,7 +256,7 @@ export function FileSelectorInput({
230256
requiredScopes={subBlock.requiredScopes || []}
231257
serviceId={subBlock.serviceId}
232258
label={subBlock.placeholder || 'Select Microsoft Excel file'}
233-
disabled={finalDisabled}
259+
disabled={finalDisabled || shouldForceDisable}
234260
showPreview={true}
235261
workflowId={activeWorkflowId || ''}
236262
credentialId={credential}
@@ -260,7 +286,7 @@ export function FileSelectorInput({
260286
requiredScopes={subBlock.requiredScopes || []}
261287
serviceId={subBlock.serviceId}
262288
label={subBlock.placeholder || 'Select Microsoft Word document'}
263-
disabled={finalDisabled}
289+
disabled={finalDisabled || shouldForceDisable}
264290
showPreview={true}
265291
/>
266292
</div>
@@ -288,7 +314,7 @@ export function FileSelectorInput({
288314
serviceId={subBlock.serviceId}
289315
mimeType={subBlock.mimeType}
290316
label={subBlock.placeholder || 'Select OneDrive folder'}
291-
disabled={finalDisabled}
317+
disabled={finalDisabled || shouldForceDisable}
292318
showPreview={true}
293319
workflowId={activeWorkflowId || ''}
294320
credentialId={credential}
@@ -318,7 +344,7 @@ export function FileSelectorInput({
318344
requiredScopes={subBlock.requiredScopes || []}
319345
serviceId={subBlock.serviceId}
320346
label={subBlock.placeholder || 'Select SharePoint site'}
321-
disabled={finalDisabled}
347+
disabled={finalDisabled || shouldForceDisable}
322348
showPreview={true}
323349
workflowId={activeWorkflowId || ''}
324350
credentialId={credential}
@@ -354,7 +380,7 @@ export function FileSelectorInput({
354380
requiredScopes={subBlock.requiredScopes || []}
355381
serviceId='microsoft-planner'
356382
label={subBlock.placeholder || 'Select task'}
357-
disabled={finalDisabled}
383+
disabled={finalDisabled || shouldForceDisable}
358384
showPreview={true}
359385
planId={planId}
360386
workflowId={activeWorkflowId || ''}
@@ -412,7 +438,7 @@ export function FileSelectorInput({
412438
requiredScopes={subBlock.requiredScopes || []}
413439
serviceId={subBlock.serviceId}
414440
label={subBlock.placeholder || 'Select Teams message location'}
415-
disabled={finalDisabled}
441+
disabled={finalDisabled || shouldForceDisable}
416442
showPreview={true}
417443
credential={credential}
418444
selectionType={selectionType}
@@ -455,7 +481,7 @@ export function FileSelectorInput({
455481
requiredScopes={subBlock.requiredScopes || []}
456482
serviceId={subBlock.serviceId}
457483
label={subBlock.placeholder || `Select ${itemType}`}
458-
disabled={finalDisabled}
484+
disabled={finalDisabled || shouldForceDisable}
459485
showPreview={true}
460486
credentialId={credential}
461487
itemType={itemType}
@@ -496,7 +522,7 @@ export function FileSelectorInput({
496522
provider={provider}
497523
requiredScopes={subBlock.requiredScopes || []}
498524
label={subBlock.placeholder || 'Select file'}
499-
disabled={finalDisabled}
525+
disabled={finalDisabled || shouldForceDisable}
500526
serviceId={subBlock.serviceId}
501527
mimeTypeFilter={subBlock.mimeType}
502528
showPreview={true}

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel-new/components/editor/components/sub-block/components/project-selector/project-selector-input.tsx

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ interface ProjectSelectorInputProps {
2828
onProjectSelect?: (projectId: string) => void
2929
isPreview?: boolean
3030
previewValue?: any | null
31+
previewContextValues?: Record<string, any>
3132
}
3233

3334
export function ProjectSelectorInput({
@@ -37,30 +38,40 @@ export function ProjectSelectorInput({
3738
onProjectSelect,
3839
isPreview = false,
3940
previewValue,
41+
previewContextValues,
4042
}: ProjectSelectorInputProps) {
4143
const { collaborativeSetSubblockValue } = useCollaborativeWorkflow()
4244
const [selectedProjectId, setSelectedProjectId] = useState<string>('')
4345
const [_projectInfo, setProjectInfo] = useState<any | null>(null)
4446
// Use the proper hook to get the current value and setter
4547
const [storeValue, setStoreValue] = useSubBlockValue(blockId, subBlock.id)
46-
const [connectedCredential] = useSubBlockValue(blockId, 'credential')
48+
const [connectedCredentialFromStore] = useSubBlockValue(blockId, 'credential')
49+
const [linearTeamIdFromStore] = useSubBlockValue(blockId, 'teamId')
50+
const [jiraDomainFromStore] = useSubBlockValue(blockId, 'domain')
51+
52+
// Use previewContextValues if provided (for tools inside agent blocks), otherwise use store values
53+
const connectedCredential = previewContextValues?.credential ?? connectedCredentialFromStore
54+
const linearCredential = previewContextValues?.credential ?? connectedCredentialFromStore
55+
const linearTeamId = previewContextValues?.teamId ?? linearTeamIdFromStore
56+
const jiraDomain = previewContextValues?.domain ?? jiraDomainFromStore
57+
4758
const { isForeignCredential } = useForeignCredential(
4859
subBlock.provider || subBlock.serviceId || 'jira',
4960
(connectedCredential as string) || ''
5061
)
51-
// Reactive dependencies from store for Linear
52-
const [linearCredential] = useSubBlockValue(blockId, 'credential')
53-
const [linearTeamId] = useSubBlockValue(blockId, 'teamId')
5462
const activeWorkflowId = useWorkflowRegistry((s) => s.activeWorkflowId) as string | null
55-
const { finalDisabled } = useDependsOnGate(blockId, subBlock, { disabled, isPreview })
63+
const { finalDisabled } = useDependsOnGate(blockId, subBlock, {
64+
disabled,
65+
isPreview,
66+
previewContextValues,
67+
})
5668

5769
// Get provider-specific values
5870
const provider = subBlock.provider || 'jira'
5971
const isLinear = provider === 'linear'
6072

61-
// Jira/Discord upstream fields
62-
const [jiraDomain] = useSubBlockValue(blockId, 'domain')
63-
const [jiraCredential] = useSubBlockValue(blockId, 'credential')
73+
// Jira/Discord upstream fields - use values from previewContextValues or store
74+
const jiraCredential = connectedCredential
6475
const domain = (jiraDomain as string) || ''
6576

6677
// Verify Jira credential belongs to current user; if not, treat as absent

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel-new/components/editor/components/sub-block/components/tool-input/tool-input.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,7 @@ function FileSelectorSyncWrapper({
168168
mimeType: uiComponent.mimeType,
169169
requiredScopes: uiComponent.requiredScopes || [],
170170
placeholder: uiComponent.placeholder,
171+
dependsOn: uiComponent.dependsOn,
171172
}}
172173
disabled={disabled}
173174
previewContextValues={previewContextValues}
@@ -433,6 +434,7 @@ function ChannelSelectorSyncWrapper({
433434
title: paramId,
434435
provider: uiComponent.provider || 'slack',
435436
placeholder: uiComponent.placeholder,
437+
dependsOn: uiComponent.dependsOn,
436438
}}
437439
onChannelSelect={onChange}
438440
disabled={disabled}
@@ -1174,9 +1176,11 @@ export function ToolInput({
11741176
serviceId: uiComponent.serviceId,
11751177
placeholder: uiComponent.placeholder,
11761178
requiredScopes: uiComponent.requiredScopes,
1179+
dependsOn: uiComponent.dependsOn,
11771180
}}
11781181
onProjectSelect={onChange}
11791182
disabled={disabled}
1183+
previewContextValues={currentToolParams as any}
11801184
/>
11811185
)
11821186

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel-new/components/editor/components/sub-block/hooks/use-depends-on-gate.ts

Lines changed: 38 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,29 +13,62 @@ import { useSubBlockStore } from '@/stores/workflows/subblock/store'
1313
export function useDependsOnGate(
1414
blockId: string,
1515
subBlock: SubBlockConfig,
16-
opts?: { disabled?: boolean; isPreview?: boolean }
16+
opts?: { disabled?: boolean; isPreview?: boolean; previewContextValues?: Record<string, any> }
1717
) {
1818
const disabledProp = opts?.disabled ?? false
1919
const isPreview = opts?.isPreview ?? false
20+
const previewContextValues = opts?.previewContextValues
2021

2122
const activeWorkflowId = useWorkflowRegistry((s) => s.activeWorkflowId)
2223

2324
// Use only explicit dependsOn from block config. No inference.
2425
const dependsOn: string[] = (subBlock.dependsOn as string[] | undefined) || []
2526

27+
const normalizeDependencyValue = (rawValue: unknown): unknown => {
28+
if (rawValue === null || rawValue === undefined) return null
29+
30+
if (typeof rawValue === 'object') {
31+
if (Array.isArray(rawValue)) {
32+
if (rawValue.length === 0) return null
33+
return rawValue.map((item) => normalizeDependencyValue(item))
34+
}
35+
36+
const record = rawValue as Record<string, any>
37+
if ('value' in record) {
38+
return normalizeDependencyValue(record.value)
39+
}
40+
if ('id' in record) {
41+
return record.id
42+
}
43+
44+
return record
45+
}
46+
47+
return rawValue
48+
}
49+
2650
const dependencyValues = useSubBlockStore((state) => {
2751
if (dependsOn.length === 0) return [] as any[]
52+
53+
// If previewContextValues are provided (e.g., tool parameters), use those first
54+
if (previewContextValues) {
55+
return dependsOn.map((depKey) => normalizeDependencyValue(previewContextValues[depKey]))
56+
}
57+
2858
if (!activeWorkflowId) return dependsOn.map(() => null)
2959
const workflowValues = state.workflowValues[activeWorkflowId] || {}
3060
const blockValues = (workflowValues as any)[blockId] || {}
31-
return dependsOn.map((depKey) => (blockValues as any)[depKey] ?? null)
61+
return dependsOn.map((depKey) => normalizeDependencyValue((blockValues as any)[depKey]))
3262
}) as any[]
3363

3464
const depsSatisfied = useMemo(() => {
3565
if (dependsOn.length === 0) return true
36-
return dependencyValues.every((v) =>
37-
typeof v === 'string' ? v.trim().length > 0 : v !== null && v !== undefined && v !== ''
38-
)
66+
return dependencyValues.every((value) => {
67+
if (value === null || value === undefined) return false
68+
if (typeof value === 'string') return value.trim().length > 0
69+
if (Array.isArray(value)) return value.length > 0
70+
return value !== ''
71+
})
3972
}, [dependencyValues, dependsOn])
4073

4174
// Block everything except the credential field itself until dependencies are set

apps/sim/tools/params.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ export interface UIComponentConfig {
3636
acceptedTypes?: string[]
3737
multiple?: boolean
3838
maxSize?: number
39+
dependsOn?: string[]
3940
}
4041

4142
export interface SubBlockConfig {
@@ -61,6 +62,7 @@ export interface SubBlockConfig {
6162
acceptedTypes?: string[]
6263
multiple?: boolean
6364
maxSize?: number
65+
dependsOn?: string[]
6466
}
6567

6668
export interface BlockConfig {
@@ -236,6 +238,7 @@ export function getToolParametersConfig(
236238
acceptedTypes: subBlock.acceptedTypes,
237239
multiple: subBlock.multiple,
238240
maxSize: subBlock.maxSize,
241+
dependsOn: subBlock.dependsOn,
239242
}
240243
}
241244
}

0 commit comments

Comments
 (0)