From bef786cd5ba4e04cea6d4d9ce8bf298a8308d3b8 Mon Sep 17 00:00:00 2001 From: Theodore Li Date: Sat, 4 Apr 2026 17:21:14 -0700 Subject: [PATCH 1/4] Add credential prompting for google service accounts --- .../server/blocks/get-blocks-metadata-tool.ts | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/apps/sim/lib/copilot/tools/server/blocks/get-blocks-metadata-tool.ts b/apps/sim/lib/copilot/tools/server/blocks/get-blocks-metadata-tool.ts index b15785f9fd4..eba9ef0b50a 100644 --- a/apps/sim/lib/copilot/tools/server/blocks/get-blocks-metadata-tool.ts +++ b/apps/sim/lib/copilot/tools/server/blocks/get-blocks-metadata-tool.ts @@ -10,6 +10,7 @@ import { AuthMode, type BlockConfig, isHiddenFromDisplay } from '@/blocks/types' import { getUserPermissionConfig } from '@/ee/access-control/utils/permission-check' import { PROVIDER_DEFINITIONS } from '@/providers/models' import { tools as toolsRegistry } from '@/tools/registry' +import { getServiceAccountProviderForProviderId } from '@/lib/oauth/utils' import { getTrigger, isTriggerValid } from '@/triggers' import { SYSTEM_SUBBLOCK_IDS } from '@/triggers/constants' @@ -337,11 +338,25 @@ function transformBlockMetadata(metadata: CopilotBlockMetadata): any { transformed.authType = metadata.authType if (metadata.authType === 'OAuth') { + // Resolve the actual OAuth service ID from the oauth-input subblock + const oauthSubBlock = metadata.inputSchema?.find( + (sb: CopilotSubblockMetadata) => sb.type === 'oauth-input' && sb.serviceId + ) + const serviceId = oauthSubBlock?.serviceId ?? metadata.id + transformed.requiredCredentials = { type: 'oauth', - service: metadata.id, // e.g., 'gmail', 'slack', etc. + service: serviceId, description: `OAuth authentication required for ${metadata.name}`, } + + // Check if this service also supports service account credentials + const serviceAccountProviderId = getServiceAccountProviderForProviderId(serviceId) + if (serviceAccountProviderId) { + transformed.requiredCredentials.serviceAccountType = serviceAccountProviderId + transformed.requiredCredentials.description = + `OAuth or service account authentication supported for ${metadata.name}` + } } else if (metadata.authType === 'API Key') { transformed.requiredCredentials = { type: 'api_key', From fac34dc65880b06a8cee1731e1176c2f83d16cfb Mon Sep 17 00:00:00 2001 From: Theodore Li Date: Sat, 4 Apr 2026 18:54:37 -0700 Subject: [PATCH 2/4] Add service account credential block prompting for google service account --- .../hooks/use-editor-subblock-layout.ts | 61 +----------------- .../workflow-block/workflow-block.tsx | 10 +++ apps/sim/hooks/use-reactive-conditions.ts | 62 +++++++++++++++++++ .../server/blocks/get-blocks-metadata-tool.ts | 5 +- 4 files changed, 75 insertions(+), 63 deletions(-) create mode 100644 apps/sim/hooks/use-reactive-conditions.ts diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/hooks/use-editor-subblock-layout.ts b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/hooks/use-editor-subblock-layout.ts index 2db80532508..6f9d22a7841 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/hooks/use-editor-subblock-layout.ts +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/hooks/use-editor-subblock-layout.ts @@ -1,77 +1,18 @@ import { useCallback, useMemo } from 'react' -import type { CanonicalModeOverrides } from '@/lib/workflows/subblocks/visibility' import { buildCanonicalIndex, evaluateSubBlockCondition, isSubBlockFeatureEnabled, isSubBlockHidden, isSubBlockVisibleForMode, - resolveDependencyValue, } from '@/lib/workflows/subblocks/visibility' import type { BlockConfig, SubBlockConfig, SubBlockType } from '@/blocks/types' -import { useWorkspaceCredential } from '@/hooks/queries/credentials' import { usePermissionConfig } from '@/hooks/use-permission-config' +import { useReactiveConditions } from '@/hooks/use-reactive-conditions' import { useWorkflowDiffStore } from '@/stores/workflow-diff' -import { useSubBlockStore } from '@/stores/workflows/subblock/store' import { mergeSubblockState } from '@/stores/workflows/utils' import { useWorkflowStore } from '@/stores/workflows/workflow/store' -/** - * Evaluates reactive conditions for subblocks. Always calls the same hooks - * regardless of whether a reactive condition exists (Rules of Hooks). - * - * Returns a Set of subblock IDs that should be hidden. - */ -function useReactiveConditions( - subBlocks: SubBlockConfig[], - blockId: string, - activeWorkflowId: string | null, - canonicalModeOverrides?: CanonicalModeOverrides -): Set { - const reactiveSubBlock = useMemo(() => subBlocks.find((sb) => sb.reactiveCondition), [subBlocks]) - const reactiveCond = reactiveSubBlock?.reactiveCondition - - const canonicalIndex = useMemo(() => buildCanonicalIndex(subBlocks), [subBlocks]) - - // Resolve watchFields through canonical index to get the active credential value - const watchedCredentialId = useSubBlockStore( - useCallback( - (state) => { - if (!reactiveCond || !activeWorkflowId) return '' - const blockValues = state.workflowValues[activeWorkflowId]?.[blockId] ?? {} - for (const field of reactiveCond.watchFields) { - const val = resolveDependencyValue( - field, - blockValues, - canonicalIndex, - canonicalModeOverrides - ) - if (val && typeof val === 'string') return val - } - return '' - }, - [reactiveCond, activeWorkflowId, blockId, canonicalIndex, canonicalModeOverrides] - ) - ) - - // Always call useWorkspaceCredential (stable hook count), disable when not needed - const { data: credential } = useWorkspaceCredential( - watchedCredentialId || undefined, - Boolean(reactiveCond && watchedCredentialId) - ) - - return useMemo(() => { - const hidden = new Set() - if (!reactiveSubBlock || !reactiveCond) return hidden - - const conditionMet = credential?.type === reactiveCond.requiredType - if (!conditionMet) { - hidden.add(reactiveSubBlock.id) - } - return hidden - }, [reactiveSubBlock, reactiveCond, credential?.type]) -} - /** * Custom hook for computing subblock layout in the editor panel. * Determines which subblocks should be visible based on mode, conditions, and feature flags. diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/workflow-block.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/workflow-block.tsx index b7b04f38bb2..970b572f4ee 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/workflow-block.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/workflow-block/workflow-block.tsx @@ -47,6 +47,7 @@ import { useReactivateSchedule, useScheduleInfo } from '@/hooks/queries/schedule import { useSkills } from '@/hooks/queries/skills' import { useTablesList } from '@/hooks/queries/tables' import { useWorkflowMap } from '@/hooks/queries/workflows' +import { useReactiveConditions } from '@/hooks/use-reactive-conditions' import { useSelectorDisplayName } from '@/hooks/use-selector-display-name' import { useVariablesStore } from '@/stores/panel' import { useSubBlockStore } from '@/stores/workflows/subblock/store' @@ -942,6 +943,13 @@ export const WorkflowBlock = memo(function WorkflowBlock({ const canonicalIndex = useMemo(() => buildCanonicalIndex(config.subBlocks), [config.subBlocks]) const canonicalModeOverrides = currentStoreBlock?.data?.canonicalModes + const hiddenByReactiveCondition = useReactiveConditions( + config.subBlocks, + id, + activeWorkflowId, + canonicalModeOverrides + ) + const subBlockRowsData = useMemo(() => { const rows: SubBlockConfig[][] = [] let currentRow: SubBlockConfig[] = [] @@ -979,6 +987,7 @@ export const WorkflowBlock = memo(function WorkflowBlock({ const visibleSubBlocks = config.subBlocks.filter((block) => { if (block.hidden) return false if (block.hideFromPreview) return false + if (hiddenByReactiveCondition.has(block.id)) return false if (!isSubBlockFeatureEnabled(block)) return false if (isSubBlockHidden(block)) return false @@ -1047,6 +1056,7 @@ export const WorkflowBlock = memo(function WorkflowBlock({ canonicalModeOverrides, userPermissions.canEdit, canonicalIndex, + hiddenByReactiveCondition, blockSubBlockValues, activeWorkflowId, ]) diff --git a/apps/sim/hooks/use-reactive-conditions.ts b/apps/sim/hooks/use-reactive-conditions.ts new file mode 100644 index 00000000000..0971eee2cd3 --- /dev/null +++ b/apps/sim/hooks/use-reactive-conditions.ts @@ -0,0 +1,62 @@ +import { useCallback, useMemo } from 'react' +import type { CanonicalModeOverrides } from '@/lib/workflows/subblocks/visibility' +import { buildCanonicalIndex, resolveDependencyValue } from '@/lib/workflows/subblocks/visibility' +import type { SubBlockConfig } from '@/blocks/types' +import { useWorkspaceCredential } from '@/hooks/queries/credentials' +import { useSubBlockStore } from '@/stores/workflows/subblock/store' + +/** + * Evaluates reactive conditions for subblocks. Always calls the same hooks + * regardless of whether a reactive condition exists (Rules of Hooks). + * + * Returns a Set of subblock IDs that should be hidden. + */ +export function useReactiveConditions( + subBlocks: SubBlockConfig[], + blockId: string, + activeWorkflowId: string | null, + canonicalModeOverrides?: CanonicalModeOverrides +): Set { + const reactiveSubBlock = useMemo(() => subBlocks.find((sb) => sb.reactiveCondition), [subBlocks]) + const reactiveCond = reactiveSubBlock?.reactiveCondition + + const canonicalIndex = useMemo(() => buildCanonicalIndex(subBlocks), [subBlocks]) + + // Resolve watchFields through canonical index to get the active credential value + const watchedCredentialId = useSubBlockStore( + useCallback( + (state) => { + if (!reactiveCond || !activeWorkflowId) return '' + const blockValues = state.workflowValues[activeWorkflowId]?.[blockId] ?? {} + for (const field of reactiveCond.watchFields) { + const val = resolveDependencyValue( + field, + blockValues, + canonicalIndex, + canonicalModeOverrides + ) + if (val && typeof val === 'string') return val + } + return '' + }, + [reactiveCond, activeWorkflowId, blockId, canonicalIndex, canonicalModeOverrides] + ) + ) + + // Always call useWorkspaceCredential (stable hook count), disable when not needed + const { data: credential } = useWorkspaceCredential( + watchedCredentialId || undefined, + Boolean(reactiveCond && watchedCredentialId) + ) + + return useMemo(() => { + const hidden = new Set() + if (!reactiveSubBlock || !reactiveCond) return hidden + + const conditionMet = credential?.type === reactiveCond.requiredType + if (!conditionMet) { + hidden.add(reactiveSubBlock.id) + } + return hidden + }, [reactiveSubBlock, reactiveCond, credential?.type]) +} diff --git a/apps/sim/lib/copilot/tools/server/blocks/get-blocks-metadata-tool.ts b/apps/sim/lib/copilot/tools/server/blocks/get-blocks-metadata-tool.ts index eba9ef0b50a..01ad4b8147c 100644 --- a/apps/sim/lib/copilot/tools/server/blocks/get-blocks-metadata-tool.ts +++ b/apps/sim/lib/copilot/tools/server/blocks/get-blocks-metadata-tool.ts @@ -5,12 +5,12 @@ import { getCopilotToolDescription } from '@/lib/copilot/tool-descriptions' import type { BaseServerTool } from '@/lib/copilot/tools/server/base-tool' import { GetBlocksMetadataInput, GetBlocksMetadataResult } from '@/lib/copilot/tools/shared/schemas' import { getAllowedIntegrationsFromEnv, isHosted } from '@/lib/core/config/feature-flags' +import { getServiceAccountProviderForProviderId } from '@/lib/oauth/utils' import { registry as blockRegistry } from '@/blocks/registry' import { AuthMode, type BlockConfig, isHiddenFromDisplay } from '@/blocks/types' import { getUserPermissionConfig } from '@/ee/access-control/utils/permission-check' import { PROVIDER_DEFINITIONS } from '@/providers/models' import { tools as toolsRegistry } from '@/tools/registry' -import { getServiceAccountProviderForProviderId } from '@/lib/oauth/utils' import { getTrigger, isTriggerValid } from '@/triggers' import { SYSTEM_SUBBLOCK_IDS } from '@/triggers/constants' @@ -354,8 +354,7 @@ function transformBlockMetadata(metadata: CopilotBlockMetadata): any { const serviceAccountProviderId = getServiceAccountProviderForProviderId(serviceId) if (serviceAccountProviderId) { transformed.requiredCredentials.serviceAccountType = serviceAccountProviderId - transformed.requiredCredentials.description = - `OAuth or service account authentication supported for ${metadata.name}` + transformed.requiredCredentials.description = `OAuth or service account authentication supported for ${metadata.name}` } } else if (metadata.authType === 'API Key') { transformed.requiredCredentials = { From 0a29cbe865dbecbd7c8b6c76d7772ba1aadaed6b Mon Sep 17 00:00:00 2001 From: Theodore Li Date: Mon, 6 Apr 2026 09:35:43 -0700 Subject: [PATCH 3/4] Revert requiredCredentials change --- .../server/blocks/get-blocks-metadata-tool.ts | 24 ++++++++++--------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/apps/sim/lib/copilot/tools/server/blocks/get-blocks-metadata-tool.ts b/apps/sim/lib/copilot/tools/server/blocks/get-blocks-metadata-tool.ts index 01ad4b8147c..784e4fdbe08 100644 --- a/apps/sim/lib/copilot/tools/server/blocks/get-blocks-metadata-tool.ts +++ b/apps/sim/lib/copilot/tools/server/blocks/get-blocks-metadata-tool.ts @@ -338,23 +338,25 @@ function transformBlockMetadata(metadata: CopilotBlockMetadata): any { transformed.authType = metadata.authType if (metadata.authType === 'OAuth') { - // Resolve the actual OAuth service ID from the oauth-input subblock - const oauthSubBlock = metadata.inputSchema?.find( - (sb: CopilotSubblockMetadata) => sb.type === 'oauth-input' && sb.serviceId - ) - const serviceId = oauthSubBlock?.serviceId ?? metadata.id - transformed.requiredCredentials = { type: 'oauth', - service: serviceId, + service: metadata.id, // e.g., 'gmail', 'slack', etc. description: `OAuth authentication required for ${metadata.name}`, } // Check if this service also supports service account credentials - const serviceAccountProviderId = getServiceAccountProviderForProviderId(serviceId) - if (serviceAccountProviderId) { - transformed.requiredCredentials.serviceAccountType = serviceAccountProviderId - transformed.requiredCredentials.description = `OAuth or service account authentication supported for ${metadata.name}` + const oauthSubBlock = metadata.inputSchema?.find( + (sb: CopilotSubblockMetadata) => sb.type === 'oauth-input' && sb.serviceId + ) + if (oauthSubBlock?.serviceId) { + const serviceAccountProviderId = getServiceAccountProviderForProviderId( + oauthSubBlock.serviceId + ) + if (serviceAccountProviderId) { + transformed.requiredCredentials.serviceAccountType = serviceAccountProviderId + transformed.requiredCredentials.description = + `OAuth or service account authentication supported for ${metadata.name}` + } } } else if (metadata.authType === 'API Key') { transformed.requiredCredentials = { From db6632393428335d355f16a44b5dfaf2a9290687 Mon Sep 17 00:00:00 2001 From: Theodore Li Date: Mon, 6 Apr 2026 14:05:33 -0700 Subject: [PATCH 4/4] Fix lint --- .../copilot/tools/server/blocks/get-blocks-metadata-tool.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/apps/sim/lib/copilot/tools/server/blocks/get-blocks-metadata-tool.ts b/apps/sim/lib/copilot/tools/server/blocks/get-blocks-metadata-tool.ts index 784e4fdbe08..aec442ceff6 100644 --- a/apps/sim/lib/copilot/tools/server/blocks/get-blocks-metadata-tool.ts +++ b/apps/sim/lib/copilot/tools/server/blocks/get-blocks-metadata-tool.ts @@ -354,8 +354,7 @@ function transformBlockMetadata(metadata: CopilotBlockMetadata): any { ) if (serviceAccountProviderId) { transformed.requiredCredentials.serviceAccountType = serviceAccountProviderId - transformed.requiredCredentials.description = - `OAuth or service account authentication supported for ${metadata.name}` + transformed.requiredCredentials.description = `OAuth or service account authentication supported for ${metadata.name}` } } } else if (metadata.authType === 'API Key') {