improvement(selectors): make serviceId sole source of truth (#2128)

* improvement(serviceId): make serviceId sole source of truth

* incorrect gmail service id

* fix teams selectors

* fix linkedin
This commit is contained in:
Vikhyath Mondreti
2025-12-01 12:33:26 -08:00
committed by GitHub
parent 1e080e98e8
commit d22b21c8d1
56 changed files with 181 additions and 265 deletions

View File

@@ -3,6 +3,7 @@
import { useEffect, useMemo, useState } from 'react'
import { useParams } from 'next/navigation'
import { Tooltip } from '@/components/emcn'
import { getProviderIdFromServiceId } from '@/lib/oauth/oauth'
import { SelectorCombobox } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/selector-combobox/selector-combobox'
import { useDependsOnGate } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/hooks/use-depends-on-gate'
import { useForeignCredential } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/hooks/use-foreign-credential'
@@ -41,8 +42,11 @@ export function ChannelSelectorInput({
const effectiveCredential = previewContextValues?.credential ?? connectedCredential
const [_channelInfo, setChannelInfo] = useState<string | null>(null)
const provider = subBlock.provider || 'slack'
const isSlack = provider === 'slack'
// Use serviceId to identify the service and derive providerId for credential lookup
const serviceId = subBlock.serviceId || ''
const effectiveProviderId = useMemo(() => getProviderIdFromServiceId(serviceId), [serviceId])
const isSlack = serviceId === 'slack'
// Central dependsOn gating
const { finalDisabled, dependsOn } = useDependsOnGate(blockId, subBlock, {
disabled,
@@ -58,7 +62,7 @@ export function ChannelSelectorInput({
// Determine if connected OAuth credential is foreign (not applicable for bot tokens)
const { isForeignCredential } = useForeignCredential(
'slack',
effectiveProviderId,
(effectiveAuthMethod as string) === 'bot_token' ? '' : (effectiveCredential as string) || ''
)
@@ -87,11 +91,11 @@ export function ChannelSelectorInput({
<Tooltip.Root>
<Tooltip.Trigger asChild>
<div className='w-full rounded border border-dashed p-4 text-center text-muted-foreground text-sm'>
Channel selector not supported for provider: {provider}
Channel selector not supported for service: {serviceId || 'unknown'}
</div>
</Tooltip.Trigger>
<Tooltip.Content side='top'>
<p>This channel selector is not yet implemented for {provider}</p>
<p>This channel selector is not yet implemented for {serviceId || 'unknown'}</p>
</Tooltip.Content>
</Tooltip.Root>
)

View File

@@ -7,7 +7,6 @@ import { createLogger } from '@/lib/logs/console/logger'
import {
getCanonicalScopesForProvider,
getProviderIdFromServiceId,
getServiceIdFromScopes,
OAUTH_PROVIDERS,
type OAuthProvider,
parseProvider,
@@ -42,23 +41,19 @@ export function CredentialSelector({
const { activeWorkflowId } = useWorkflowRegistry()
const [storeValue, setStoreValue] = useSubBlockValue<string | null>(blockId, subBlock.id)
const provider = subBlock.provider as OAuthProvider
const requiredScopes = subBlock.requiredScopes || []
const label = subBlock.placeholder || 'Select credential'
const serviceId = subBlock.serviceId
const serviceId = subBlock.serviceId || ''
const effectiveValue = isPreview && previewValue !== undefined ? previewValue : storeValue
const selectedId = typeof effectiveValue === 'string' ? effectiveValue : ''
const effectiveServiceId = useMemo(
() => serviceId || getServiceIdFromScopes(provider, requiredScopes),
[provider, requiredScopes, serviceId]
)
// serviceId is now the canonical identifier - derive provider from it
const effectiveProviderId = useMemo(
() => getProviderIdFromServiceId(effectiveServiceId),
[effectiveServiceId]
() => getProviderIdFromServiceId(serviceId) as OAuthProvider,
[serviceId]
)
const provider = effectiveProviderId
const {
data: credentials = [],
@@ -259,7 +254,7 @@ export function CredentialSelector({
toolName={getProviderName(provider)}
requiredScopes={getCanonicalScopesForProvider(effectiveProviderId)}
newScopes={missingRequiredScopes}
serviceId={effectiveServiceId}
serviceId={serviceId}
/>
)}
</>

View File

@@ -3,6 +3,7 @@
import { useMemo } from 'react'
import { useParams } from 'next/navigation'
import { Tooltip } from '@/components/emcn'
import { getProviderIdFromServiceId } from '@/lib/oauth'
import { SelectorCombobox } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/selector-combobox/selector-combobox'
import { useDependsOnGate } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/hooks/use-depends-on-gate'
import { useForeignCredential } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/hooks/use-foreign-credential'
@@ -59,10 +60,11 @@ export function FileSelectorInput({
? ((connectedCredential as Record<string, any>).id ?? '')
: ''
const { isForeignCredential } = useForeignCredential(
subBlock.serviceId || subBlock.provider,
normalizedCredentialId
)
// Derive provider from serviceId using OAuth config (same pattern as credential-selector)
const serviceId = subBlock.serviceId || ''
const effectiveProviderId = useMemo(() => getProviderIdFromServiceId(serviceId), [serviceId])
const { isForeignCredential } = useForeignCredential(effectiveProviderId, normalizedCredentialId)
const selectorResolution = useMemo<SelectorResolution | null>(() => {
return resolveSelectorForSubBlock(subBlock, {
@@ -109,11 +111,11 @@ export function FileSelectorInput({
<Tooltip.Root>
<Tooltip.Trigger asChild>
<div className='w-full rounded border border-dashed p-4 text-center text-muted-foreground text-sm'>
File selector not supported for provider: {subBlock.provider || subBlock.serviceId}
File selector not supported for service: {serviceId || 'unknown'}
</div>
</Tooltip.Trigger>
<Tooltip.Content side='top'>
<p>This file selector is not implemented for {subBlock.provider || subBlock.serviceId}</p>
<p>This file selector is not implemented for {serviceId || 'unknown'}</p>
</Tooltip.Content>
</Tooltip.Root>
)

View File

@@ -1,6 +1,7 @@
'use client'
import { useCallback, useEffect, useMemo, useState } from 'react'
import { getProviderIdFromServiceId } from '@/lib/oauth'
import { SelectorCombobox } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/selector-combobox/selector-combobox'
import { useDependsOnGate } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/hooks/use-depends-on-gate'
import { useForeignCredential } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/hooks/use-foreign-credential'
@@ -30,14 +31,18 @@ export function FolderSelectorInput({
const { collaborativeSetSubblockValue } = useCollaborativeWorkflow()
const { activeWorkflowId } = useWorkflowRegistry()
const [selectedFolderId, setSelectedFolderId] = useState<string>('')
const providerKey = (subBlock.provider ?? subBlock.serviceId ?? '').toLowerCase()
const credentialProvider = subBlock.serviceId ?? subBlock.provider
// Derive provider from serviceId using OAuth config (same pattern as credential-selector)
const serviceId = subBlock.serviceId || ''
const effectiveProviderId = useMemo(() => getProviderIdFromServiceId(serviceId), [serviceId])
const providerKey = serviceId.toLowerCase()
const isCopyDestinationSelector =
subBlock.canonicalParamId === 'copyDestinationId' ||
subBlock.id === 'copyDestinationFolder' ||
subBlock.id === 'manualCopyDestinationFolder'
const { isForeignCredential } = useForeignCredential(
credentialProvider,
effectiveProviderId,
(connectedCredential as string) || ''
)

View File

@@ -3,6 +3,7 @@
import { useEffect, useMemo, useState } from 'react'
import { useParams } from 'next/navigation'
import { Tooltip } from '@/components/emcn'
import { getProviderIdFromServiceId } from '@/lib/oauth'
import { SelectorCombobox } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/selector-combobox/selector-combobox'
import { useDependsOnGate } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/hooks/use-depends-on-gate'
import { useForeignCredential } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/hooks/use-foreign-credential'
@@ -46,8 +47,13 @@ export function ProjectSelectorInput({
const linearTeamId = previewContextValues?.teamId ?? linearTeamIdFromStore
const jiraDomain = previewContextValues?.domain ?? jiraDomainFromStore
// Derive provider from serviceId using OAuth config
const serviceId = subBlock.serviceId || ''
const effectiveProviderId = useMemo(() => getProviderIdFromServiceId(serviceId), [serviceId])
const isLinear = serviceId === 'linear'
const { isForeignCredential } = useForeignCredential(
subBlock.provider || subBlock.serviceId || 'jira',
effectiveProviderId,
(connectedCredential as string) || ''
)
const activeWorkflowId = useWorkflowRegistry((s) => s.activeWorkflowId) as string | null
@@ -58,10 +64,6 @@ export function ProjectSelectorInput({
previewContextValues,
})
// Get provider-specific values
const provider = subBlock.provider || 'jira'
const isLinear = provider === 'linear'
// Jira/Discord upstream fields - use values from previewContextValues or store
const jiraCredential = connectedCredential
const domain = (jiraDomain as string) || ''
@@ -121,7 +123,7 @@ export function ProjectSelectorInput({
/>
) : (
<div className='w-full rounded border border-dashed p-4 text-center text-muted-foreground text-sm'>
Project selector not supported for provider: {subBlock.provider || 'unknown'}
Project selector not supported for service: {serviceId}
</div>
)}
</div>

View File

@@ -18,6 +18,7 @@ import { Toggle } from '@/components/ui/toggle'
import { createLogger } from '@/lib/logs/console/logger'
import {
getCanonicalScopesForProvider,
getProviderIdFromServiceId,
type OAuthProvider,
type OAuthService,
} from '@/lib/oauth/oauth'
@@ -168,7 +169,6 @@ function FileSelectorSyncWrapper({
id: paramId,
type: 'file-selector' as const,
title: paramId,
provider: uiComponent.provider,
serviceId: uiComponent.serviceId,
mimeType: uiComponent.mimeType,
requiredScopes: uiComponent.requiredScopes || [],
@@ -467,7 +467,7 @@ function ChannelSelectorSyncWrapper({
id: paramId,
type: 'channel-selector' as const,
title: paramId,
provider: uiComponent.provider || 'slack',
serviceId: uiComponent.serviceId,
placeholder: uiComponent.placeholder,
dependsOn: uiComponent.dependsOn,
}}
@@ -1404,7 +1404,6 @@ export function ToolInput({
id: `tool-${toolIndex || 0}-${param.id}`,
type: 'project-selector' as const,
title: param.id,
provider: uiComponent.provider || 'jira',
serviceId: uiComponent.serviceId,
placeholder: uiComponent.placeholder,
requiredScopes: uiComponent.requiredScopes,
@@ -1421,7 +1420,7 @@ export function ToolInput({
<ToolCredentialSelector
value={value}
onChange={onChange}
provider={(uiComponent.provider || uiComponent.serviceId) as OAuthProvider}
provider={getProviderIdFromServiceId(uiComponent.serviceId || '') as OAuthProvider}
serviceId={uiComponent.serviceId as OAuthService}
disabled={disabled}
requiredScopes={uiComponent.requiredScopes || []}

View File

@@ -3,12 +3,13 @@
import { useMemo } from 'react'
import { useParams } from 'next/navigation'
import { Tooltip } from '@/components/emcn'
import { getProviderIdFromServiceId } from '@/lib/oauth'
import { SelectorCombobox } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/selector-combobox/selector-combobox'
import { useDependsOnGate } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/hooks/use-depends-on-gate'
import { useForeignCredential } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/hooks/use-foreign-credential'
import { useSubBlockValue } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/hooks/use-sub-block-value'
import type { SubBlockConfig } from '@/blocks/types'
import type { SelectorContext, SelectorKey } from '@/hooks/selectors/types'
import { resolveSelectorForSubBlock, type SelectorResolution } from '@/hooks/selectors/resolution'
import { useCollaborativeWorkflow } from '@/hooks/use-collaborative-workflow'
import { useWorkflowRegistry } from '@/stores/workflows/registry/store'
@@ -59,27 +60,23 @@ export function FileSelectorInput({
? ((connectedCredential as Record<string, any>).id ?? '')
: ''
const { isForeignCredential } = useForeignCredential(
subBlock.provider || subBlock.serviceId || 'google-drive',
normalizedCredentialId
)
// Derive provider from serviceId using OAuth config
const serviceId = subBlock.serviceId || ''
const effectiveProviderId = useMemo(() => getProviderIdFromServiceId(serviceId), [serviceId])
const selectorResolution = useMemo(() => {
return resolveSelector({
provider: subBlock.provider || '',
serviceId: subBlock.serviceId,
mimeType: subBlock.mimeType,
const { isForeignCredential } = useForeignCredential(effectiveProviderId, normalizedCredentialId)
const selectorResolution = useMemo<SelectorResolution | null>(() => {
return resolveSelectorForSubBlock(subBlock, {
credentialId: normalizedCredentialId,
workflowId: workflowIdFromUrl,
domain: (domainValue as string) || '',
projectId: (projectIdValue as string) || '',
planId: (planIdValue as string) || '',
teamId: (teamIdValue as string) || '',
domain: (domainValue as string) || undefined,
projectId: (projectIdValue as string) || undefined,
planId: (planIdValue as string) || undefined,
teamId: (teamIdValue as string) || undefined,
})
}, [
subBlock.provider,
subBlock.serviceId,
subBlock.mimeType,
subBlock,
normalizedCredentialId,
workflowIdFromUrl,
domainValue,
@@ -90,15 +87,15 @@ export function FileSelectorInput({
const missingCredential = !normalizedCredentialId
const missingDomain =
selectorResolution.key &&
selectorResolution?.key &&
(selectorResolution.key === 'confluence.pages' || selectorResolution.key === 'jira.issues') &&
!selectorResolution.context.domain
const missingProject =
selectorResolution.key === 'jira.issues' &&
selectorResolution?.key === 'jira.issues' &&
subBlock.dependsOn?.includes('projectId') &&
!selectorResolution.context.projectId
!selectorResolution?.context.projectId
const missingPlan =
selectorResolution.key === 'microsoft.planner' && !selectorResolution.context.planId
selectorResolution?.key === 'microsoft.planner' && !selectorResolution?.context.planId
const disabledReason =
finalDisabled ||
@@ -107,18 +104,18 @@ export function FileSelectorInput({
missingDomain ||
missingProject ||
missingPlan ||
selectorResolution.key === null
!selectorResolution?.key
if (selectorResolution.key === null) {
if (!selectorResolution?.key) {
return (
<Tooltip.Root>
<Tooltip.Trigger asChild>
<div className='w-full rounded border border-dashed p-4 text-center text-muted-foreground text-sm'>
File selector not supported for provider: {subBlock.provider || subBlock.serviceId}
File selector not supported for service: {serviceId}
</div>
</Tooltip.Trigger>
<Tooltip.Content side='top'>
<p>This file selector is not implemented for {subBlock.provider || subBlock.serviceId}</p>
<p>This file selector is not implemented for {serviceId}</p>
</Tooltip.Content>
</Tooltip.Root>
)
@@ -143,69 +140,3 @@ export function FileSelectorInput({
/>
)
}
interface SelectorParams {
provider: string
serviceId?: string
mimeType?: string
credentialId: string
workflowId: string
domain?: string
projectId?: string
planId?: string
teamId?: string
}
function resolveSelector(params: SelectorParams): {
key: SelectorKey | null
context: SelectorContext
allowSearch: boolean
} {
const baseContext: SelectorContext = {
credentialId: params.credentialId,
workflowId: params.workflowId,
domain: params.domain,
projectId: params.projectId,
planId: params.planId,
teamId: params.teamId,
mimeType: params.mimeType,
}
switch (params.provider) {
case 'google-calendar':
return { key: 'google.calendar', context: baseContext, allowSearch: false }
case 'confluence':
return { key: 'confluence.pages', context: baseContext, allowSearch: true }
case 'jira':
return { key: 'jira.issues', context: baseContext, allowSearch: true }
case 'microsoft-teams':
return { key: 'microsoft.teams', context: baseContext, allowSearch: true }
case 'wealthbox':
return { key: 'wealthbox.contacts', context: baseContext, allowSearch: true }
case 'microsoft-planner':
return { key: 'microsoft.planner', context: baseContext, allowSearch: true }
case 'microsoft-excel':
return { key: 'microsoft.excel', context: baseContext, allowSearch: true }
case 'microsoft-word':
return { key: 'microsoft.word', context: baseContext, allowSearch: true }
case 'google-drive':
return { key: 'google.drive', context: baseContext, allowSearch: true }
default:
break
}
if (params.serviceId === 'onedrive') {
const key: SelectorKey = params.mimeType === 'file' ? 'onedrive.files' : 'onedrive.folders'
return { key, context: baseContext, allowSearch: true }
}
if (params.serviceId === 'sharepoint') {
return { key: 'sharepoint.sites', context: baseContext, allowSearch: true }
}
if (params.serviceId === 'google-drive') {
return { key: 'google.drive', context: baseContext, allowSearch: true }
}
return { key: null, context: baseContext, allowSearch: true }
}

View File

@@ -6,6 +6,7 @@ import { Tooltip } from '@/components/emcn/components/tooltip/tooltip'
import { getEnv, isTruthy } from '@/lib/env'
import { createLogger } from '@/lib/logs/console/logger'
import { createMcpToolId } from '@/lib/mcp/utils'
import { getProviderIdFromServiceId } from '@/lib/oauth'
import { cn } from '@/lib/utils'
import { useUserPermissionsContext } from '@/app/workspace/[workspaceId]/providers/workspace-permissions-provider'
import {
@@ -272,9 +273,12 @@ const SubBlockRow = ({
const credentialSourceId =
subBlock?.type === 'oauth-input' && typeof rawValue === 'string' ? rawValue : undefined
const credentialProviderId = subBlock?.serviceId
? getProviderIdFromServiceId(subBlock.serviceId)
: undefined
const { displayName: credentialName } = useCredentialName(
credentialSourceId,
subBlock?.provider,
credentialProviderId,
workflowId
)

View File

@@ -32,7 +32,6 @@ export const AirtableBlock: BlockConfig<AirtableResponse> = {
id: 'credential',
title: 'Airtable Account',
type: 'oauth-input',
provider: 'airtable',
serviceId: 'airtable',
requiredScopes: [
'data.records:read',

View File

@@ -34,7 +34,6 @@ export const AsanaBlock: BlockConfig<AsanaResponse> = {
type: 'oauth-input',
required: true,
provider: 'asana',
serviceId: 'asana',
requiredScopes: ['default'],
placeholder: 'Select Asana account',

View File

@@ -48,7 +48,6 @@ export const ConfluenceBlock: BlockConfig<ConfluenceResponse> = {
id: 'credential',
title: 'Confluence Account',
type: 'oauth-input',
provider: 'confluence',
serviceId: 'confluence',
requiredScopes: [
'read:confluence-content.all',
@@ -82,7 +81,6 @@ export const ConfluenceBlock: BlockConfig<ConfluenceResponse> = {
title: 'Select Page',
type: 'file-selector',
canonicalParamId: 'pageId',
provider: 'confluence',
serviceId: 'confluence',
placeholder: 'Select Confluence page',
dependsOn: ['credential', 'domain'],

View File

@@ -71,7 +71,6 @@ export const DiscordBlock: BlockConfig<DiscordResponse> = {
type: 'short-input',
placeholder: 'Enter Discord server ID',
required: true,
provider: 'discord',
serviceId: 'discord',
},
// Channel ID - for operations that need it
@@ -81,7 +80,6 @@ export const DiscordBlock: BlockConfig<DiscordResponse> = {
type: 'short-input',
placeholder: 'Enter Discord channel ID',
required: true,
provider: 'discord',
serviceId: 'discord',
condition: {
field: 'operation',

View File

@@ -43,7 +43,6 @@ export const GmailBlock: BlockConfig<GmailToolResponse> = {
id: 'credential',
title: 'Gmail Account',
type: 'oauth-input',
provider: 'google-email',
serviceId: 'gmail',
requiredScopes: [
'https://www.googleapis.com/auth/gmail.send',
@@ -157,7 +156,6 @@ export const GmailBlock: BlockConfig<GmailToolResponse> = {
title: 'Label',
type: 'folder-selector',
canonicalParamId: 'folder',
provider: 'google-email',
serviceId: 'gmail',
requiredScopes: ['https://www.googleapis.com/auth/gmail.labels'],
placeholder: 'Select Gmail label/folder',
@@ -232,7 +230,6 @@ export const GmailBlock: BlockConfig<GmailToolResponse> = {
title: 'Move To Label',
type: 'folder-selector',
canonicalParamId: 'addLabelIds',
provider: 'google-email',
serviceId: 'gmail',
requiredScopes: ['https://www.googleapis.com/auth/gmail.labels'],
placeholder: 'Select destination label',
@@ -258,7 +255,6 @@ export const GmailBlock: BlockConfig<GmailToolResponse> = {
title: 'Remove From Label',
type: 'folder-selector',
canonicalParamId: 'removeLabelIds',
provider: 'google-email',
serviceId: 'gmail',
requiredScopes: ['https://www.googleapis.com/auth/gmail.labels'],
placeholder: 'Select label to remove',
@@ -311,7 +307,6 @@ export const GmailBlock: BlockConfig<GmailToolResponse> = {
title: 'Label',
type: 'folder-selector',
canonicalParamId: 'labelIds',
provider: 'google-email',
serviceId: 'gmail',
requiredScopes: ['https://www.googleapis.com/auth/gmail.labels'],
placeholder: 'Select label',

View File

@@ -33,7 +33,6 @@ export const GoogleCalendarBlock: BlockConfig<GoogleCalendarResponse> = {
title: 'Google Calendar Account',
type: 'oauth-input',
required: true,
provider: 'google-calendar',
serviceId: 'google-calendar',
requiredScopes: ['https://www.googleapis.com/auth/calendar'],
placeholder: 'Select Google Calendar account',
@@ -44,7 +43,6 @@ export const GoogleCalendarBlock: BlockConfig<GoogleCalendarResponse> = {
title: 'Calendar',
type: 'file-selector',
canonicalParamId: 'calendarId',
provider: 'google-calendar',
serviceId: 'google-calendar',
requiredScopes: ['https://www.googleapis.com/auth/calendar'],
placeholder: 'Select calendar',

View File

@@ -33,7 +33,6 @@ export const GoogleDocsBlock: BlockConfig<GoogleDocsResponse> = {
title: 'Google Account',
type: 'oauth-input',
required: true,
provider: 'google-docs',
serviceId: 'google-docs',
requiredScopes: [
'https://www.googleapis.com/auth/drive.file',
@@ -47,7 +46,6 @@ export const GoogleDocsBlock: BlockConfig<GoogleDocsResponse> = {
title: 'Select Document',
type: 'file-selector',
canonicalParamId: 'documentId',
provider: 'google-docs',
serviceId: 'google-docs',
requiredScopes: [],
mimeType: 'application/vnd.google-apps.document',
@@ -82,7 +80,6 @@ export const GoogleDocsBlock: BlockConfig<GoogleDocsResponse> = {
title: 'Select Parent Folder',
type: 'file-selector',
canonicalParamId: 'folderId',
provider: 'google-docs',
serviceId: 'google-docs',
requiredScopes: [],
mimeType: 'application/vnd.google-apps.folder',

View File

@@ -34,7 +34,6 @@ export const GoogleDriveBlock: BlockConfig<GoogleDriveResponse> = {
title: 'Google Drive Account',
type: 'oauth-input',
required: true,
provider: 'google-drive',
serviceId: 'google-drive',
requiredScopes: [
'https://www.googleapis.com/auth/drive.file',
@@ -104,7 +103,6 @@ export const GoogleDriveBlock: BlockConfig<GoogleDriveResponse> = {
title: 'Select Parent Folder',
type: 'file-selector',
canonicalParamId: 'folderId',
provider: 'google-drive',
serviceId: 'google-drive',
requiredScopes: [
'https://www.googleapis.com/auth/drive.file',
@@ -177,7 +175,6 @@ export const GoogleDriveBlock: BlockConfig<GoogleDriveResponse> = {
title: 'Select Parent Folder',
type: 'file-selector',
canonicalParamId: 'folderId',
provider: 'google-drive',
serviceId: 'google-drive',
requiredScopes: [
'https://www.googleapis.com/auth/drive.file',
@@ -205,7 +202,6 @@ export const GoogleDriveBlock: BlockConfig<GoogleDriveResponse> = {
title: 'Select Folder',
type: 'file-selector',
canonicalParamId: 'folderId',
provider: 'google-drive',
serviceId: 'google-drive',
requiredScopes: [
'https://www.googleapis.com/auth/drive.file',
@@ -247,7 +243,6 @@ export const GoogleDriveBlock: BlockConfig<GoogleDriveResponse> = {
title: 'Select File',
type: 'file-selector',
canonicalParamId: 'fileId',
provider: 'google-drive',
serviceId: 'google-drive',
requiredScopes: [
'https://www.googleapis.com/auth/drive.file',

View File

@@ -18,7 +18,6 @@ export const GoogleFormsBlock: BlockConfig = {
title: 'Google Account',
type: 'oauth-input',
required: true,
provider: 'google-forms',
serviceId: 'google-forms',
requiredScopes: [
'https://www.googleapis.com/auth/userinfo.email',

View File

@@ -34,7 +34,6 @@ export const GoogleSheetsBlock: BlockConfig<GoogleSheetsResponse> = {
title: 'Google Account',
type: 'oauth-input',
required: true,
provider: 'google-sheets',
serviceId: 'google-sheets',
requiredScopes: [
'https://www.googleapis.com/auth/drive.file',
@@ -48,7 +47,6 @@ export const GoogleSheetsBlock: BlockConfig<GoogleSheetsResponse> = {
title: 'Select Sheet',
type: 'file-selector',
canonicalParamId: 'spreadsheetId',
provider: 'google-sheets',
serviceId: 'google-sheets',
requiredScopes: [
'https://www.googleapis.com/auth/drive.file',

View File

@@ -35,7 +35,6 @@ export const GoogleVaultBlock: BlockConfig = {
title: 'Google Vault Account',
type: 'oauth-input',
required: true,
provider: 'google-vault',
serviceId: 'google-vault',
requiredScopes: [
'https://www.googleapis.com/auth/ediscovery',

View File

@@ -39,7 +39,6 @@ export const HubSpotBlock: BlockConfig<HubSpotResponse> = {
id: 'credential',
title: 'HubSpot Account',
type: 'oauth-input',
provider: 'hubspot',
serviceId: 'hubspot',
requiredScopes: [
'crm.objects.contacts.read',

View File

@@ -58,7 +58,6 @@ export const JiraBlock: BlockConfig<JiraResponse> = {
title: 'Jira Account',
type: 'oauth-input',
required: true,
provider: 'jira',
serviceId: 'jira',
requiredScopes: [
'read:jira-work',
@@ -100,7 +99,6 @@ export const JiraBlock: BlockConfig<JiraResponse> = {
title: 'Select Project',
type: 'project-selector',
canonicalParamId: 'projectId',
provider: 'jira',
serviceId: 'jira',
placeholder: 'Select Jira project',
dependsOn: ['credential', 'domain'],
@@ -122,7 +120,6 @@ export const JiraBlock: BlockConfig<JiraResponse> = {
title: 'Select Issue',
type: 'file-selector',
canonicalParamId: 'issueKey',
provider: 'jira',
serviceId: 'jira',
placeholder: 'Select Jira issue',
dependsOn: ['credential', 'domain', 'projectId'],

View File

@@ -129,7 +129,6 @@ export const LinearBlock: BlockConfig<LinearResponse> = {
id: 'credential',
title: 'Linear Account',
type: 'oauth-input',
provider: 'linear',
serviceId: 'linear',
requiredScopes: ['read', 'write'],
placeholder: 'Select Linear account',
@@ -141,7 +140,6 @@ export const LinearBlock: BlockConfig<LinearResponse> = {
title: 'Team',
type: 'project-selector',
canonicalParamId: 'teamId',
provider: 'linear',
serviceId: 'linear',
placeholder: 'Select a team',
dependsOn: ['credential'],
@@ -218,7 +216,6 @@ export const LinearBlock: BlockConfig<LinearResponse> = {
title: 'Project',
type: 'project-selector',
canonicalParamId: 'projectId',
provider: 'linear',
serviceId: 'linear',
placeholder: 'Select a project',
dependsOn: ['credential', 'teamId'],

View File

@@ -32,7 +32,6 @@ export const LinkedInBlock: BlockConfig<LinkedInResponse> = {
id: 'credential',
title: 'LinkedIn Account',
type: 'oauth-input',
provider: 'linkedin',
serviceId: 'linkedin',
requiredScopes: ['profile', 'openid', 'email', 'w_member_social'],
placeholder: 'Select LinkedIn account',

View File

@@ -31,7 +31,6 @@ export const MicrosoftExcelBlock: BlockConfig<MicrosoftExcelResponse> = {
id: 'credential',
title: 'Microsoft Account',
type: 'oauth-input',
provider: 'microsoft-excel',
serviceId: 'microsoft-excel',
requiredScopes: [
'openid',
@@ -49,7 +48,6 @@ export const MicrosoftExcelBlock: BlockConfig<MicrosoftExcelResponse> = {
title: 'Select Sheet',
type: 'file-selector',
canonicalParamId: 'spreadsheetId',
provider: 'microsoft-excel',
serviceId: 'microsoft-excel',
requiredScopes: [],
mimeType: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',

View File

@@ -61,7 +61,6 @@ export const MicrosoftPlannerBlock: BlockConfig<MicrosoftPlannerResponse> = {
id: 'credential',
title: 'Microsoft Account',
type: 'oauth-input',
provider: 'microsoft-planner',
serviceId: 'microsoft-planner',
requiredScopes: [
'openid',
@@ -94,7 +93,7 @@ export const MicrosoftPlannerBlock: BlockConfig<MicrosoftPlannerResponse> = {
title: 'Task ID',
type: 'file-selector',
placeholder: 'Select a task',
provider: 'microsoft-planner',
serviceId: 'microsoft-planner',
condition: { field: 'operation', value: ['read_task'] },
dependsOn: ['credential', 'planId'],
mode: 'basic',

View File

@@ -43,7 +43,6 @@ export const MicrosoftTeamsBlock: BlockConfig<MicrosoftTeamsResponse> = {
id: 'credential',
title: 'Microsoft Account',
type: 'oauth-input',
provider: 'microsoft-teams',
serviceId: 'microsoft-teams',
requiredScopes: [
'openid',
@@ -75,7 +74,6 @@ export const MicrosoftTeamsBlock: BlockConfig<MicrosoftTeamsResponse> = {
title: 'Select Team',
type: 'file-selector',
canonicalParamId: 'teamId',
provider: 'microsoft-teams',
serviceId: 'microsoft-teams',
requiredScopes: [],
placeholder: 'Select a team',
@@ -119,7 +117,6 @@ export const MicrosoftTeamsBlock: BlockConfig<MicrosoftTeamsResponse> = {
title: 'Select Chat',
type: 'file-selector',
canonicalParamId: 'chatId',
provider: 'microsoft-teams',
serviceId: 'microsoft-teams',
requiredScopes: [],
placeholder: 'Select a chat',
@@ -147,7 +144,6 @@ export const MicrosoftTeamsBlock: BlockConfig<MicrosoftTeamsResponse> = {
title: 'Select Channel',
type: 'file-selector',
canonicalParamId: 'channelId',
provider: 'microsoft-teams',
serviceId: 'microsoft-teams',
requiredScopes: [],
placeholder: 'Select a channel',

View File

@@ -34,7 +34,6 @@ export const NotionBlock: BlockConfig<NotionResponse> = {
id: 'credential',
title: 'Notion Account',
type: 'oauth-input',
provider: 'notion',
serviceId: 'notion',
requiredScopes: ['workspace.content', 'workspace.name', 'page.read', 'page.write'],
placeholder: 'Select Notion account',

View File

@@ -38,7 +38,6 @@ export const OneDriveBlock: BlockConfig<OneDriveResponse> = {
id: 'credential',
title: 'Microsoft Account',
type: 'oauth-input',
provider: 'onedrive',
serviceId: 'onedrive',
requiredScopes: [
'openid',
@@ -144,7 +143,6 @@ export const OneDriveBlock: BlockConfig<OneDriveResponse> = {
title: 'Select Parent Folder',
type: 'file-selector',
canonicalParamId: 'folderId',
provider: 'microsoft',
serviceId: 'onedrive',
requiredScopes: [
'openid',
@@ -182,7 +180,6 @@ export const OneDriveBlock: BlockConfig<OneDriveResponse> = {
title: 'Select Parent Folder',
type: 'file-selector',
canonicalParamId: 'folderId',
provider: 'microsoft',
serviceId: 'onedrive',
requiredScopes: [
'openid',
@@ -215,7 +212,6 @@ export const OneDriveBlock: BlockConfig<OneDriveResponse> = {
title: 'Select Folder',
type: 'file-selector',
canonicalParamId: 'folderId',
provider: 'microsoft',
serviceId: 'onedrive',
requiredScopes: [
'openid',
@@ -262,7 +258,6 @@ export const OneDriveBlock: BlockConfig<OneDriveResponse> = {
title: 'Select File',
type: 'file-selector',
canonicalParamId: 'fileId',
provider: 'microsoft',
serviceId: 'onedrive',
requiredScopes: [
'openid',
@@ -302,7 +297,6 @@ export const OneDriveBlock: BlockConfig<OneDriveResponse> = {
title: 'Select File to Delete',
type: 'file-selector',
canonicalParamId: 'fileId',
provider: 'microsoft',
serviceId: 'onedrive',
requiredScopes: [
'openid',

View File

@@ -38,7 +38,6 @@ export const OutlookBlock: BlockConfig<OutlookResponse> = {
id: 'credential',
title: 'Microsoft Account',
type: 'oauth-input',
provider: 'outlook',
serviceId: 'outlook',
requiredScopes: [
'Mail.ReadWrite',
@@ -175,7 +174,6 @@ export const OutlookBlock: BlockConfig<OutlookResponse> = {
title: 'Folder',
type: 'folder-selector',
canonicalParamId: 'folder',
provider: 'outlook',
serviceId: 'outlook',
requiredScopes: ['Mail.ReadWrite', 'Mail.ReadBasic', 'Mail.Read'],
placeholder: 'Select Outlook folder',
@@ -221,7 +219,6 @@ export const OutlookBlock: BlockConfig<OutlookResponse> = {
title: 'Move To Folder',
type: 'folder-selector',
canonicalParamId: 'destinationId',
provider: 'outlook',
serviceId: 'outlook',
requiredScopes: ['Mail.ReadWrite', 'Mail.ReadBasic', 'Mail.Read'],
placeholder: 'Select destination folder',
@@ -268,7 +265,6 @@ export const OutlookBlock: BlockConfig<OutlookResponse> = {
title: 'Copy To Folder',
type: 'folder-selector',
canonicalParamId: 'copyDestinationId',
provider: 'outlook',
serviceId: 'outlook',
requiredScopes: ['Mail.ReadWrite', 'Mail.ReadBasic', 'Mail.Read'],
placeholder: 'Select destination folder',

View File

@@ -45,7 +45,6 @@ export const PipedriveBlock: BlockConfig<PipedriveResponse> = {
id: 'credential',
title: 'Pipedrive Account',
type: 'oauth-input',
provider: 'pipedrive',
serviceId: 'pipedrive',
requiredScopes: [
'base',

View File

@@ -42,7 +42,6 @@ export const RedditBlock: BlockConfig<RedditResponse> = {
id: 'credential',
title: 'Reddit Account',
type: 'oauth-input',
provider: 'reddit',
serviceId: 'reddit',
requiredScopes: [
'identity',

View File

@@ -51,7 +51,6 @@ export const SalesforceBlock: BlockConfig<SalesforceResponse> = {
id: 'credential',
title: 'Salesforce Account',
type: 'oauth-input',
provider: 'salesforce',
serviceId: 'salesforce',
requiredScopes: ['api', 'refresh_token', 'openid'],
placeholder: 'Select Salesforce account',

View File

@@ -37,7 +37,6 @@ export const SharepointBlock: BlockConfig<SharepointResponse> = {
id: 'credential',
title: 'Microsoft Account',
type: 'oauth-input',
provider: 'sharepoint',
serviceId: 'sharepoint',
requiredScopes: [
'openid',
@@ -56,7 +55,6 @@ export const SharepointBlock: BlockConfig<SharepointResponse> = {
title: 'Select Site',
type: 'file-selector',
canonicalParamId: 'siteId',
provider: 'microsoft',
serviceId: 'sharepoint',
requiredScopes: [
'openid',

View File

@@ -48,7 +48,6 @@ export const SlackBlock: BlockConfig<SlackResponse> = {
id: 'credential',
title: 'Slack Account',
type: 'oauth-input',
provider: 'slack',
serviceId: 'slack',
requiredScopes: [
'channels:read',
@@ -85,7 +84,7 @@ export const SlackBlock: BlockConfig<SlackResponse> = {
title: 'Channel',
type: 'channel-selector',
canonicalParamId: 'channel',
provider: 'slack',
serviceId: 'slack',
placeholder: 'Select Slack channel',
mode: 'basic',
dependsOn: ['credential', 'authMethod'],

View File

@@ -41,7 +41,6 @@ export const TrelloBlock: BlockConfig<ToolResponse> = {
id: 'credential',
title: 'Trello Account',
type: 'oauth-input',
provider: 'trello',
serviceId: 'trello',
requiredScopes: ['read', 'write'],
placeholder: 'Select Trello account',

View File

@@ -33,7 +33,6 @@ export const WealthboxBlock: BlockConfig<WealthboxResponse> = {
id: 'credential',
title: 'Wealthbox Account',
type: 'oauth-input',
provider: 'wealthbox',
serviceId: 'wealthbox',
requiredScopes: ['login', 'data'],
placeholder: 'Select Wealthbox account',
@@ -50,7 +49,6 @@ export const WealthboxBlock: BlockConfig<WealthboxResponse> = {
id: 'contactId',
title: 'Select Contact',
type: 'file-selector',
provider: 'wealthbox',
serviceId: 'wealthbox',
requiredScopes: ['login', 'data'],
placeholder: 'Enter Contact ID',

View File

@@ -34,7 +34,6 @@ export const WebflowBlock: BlockConfig<WebflowResponse> = {
id: 'credential',
title: 'Webflow Account',
type: 'oauth-input',
provider: 'webflow',
serviceId: 'webflow',
requiredScopes: ['sites:read', 'sites:write', 'cms:read', 'cms:write'],
placeholder: 'Select Webflow account',

View File

@@ -90,7 +90,6 @@ export const WebhookBlock: BlockConfig = {
id: 'gmailCredential',
title: 'Gmail Account',
type: 'oauth-input',
provider: 'google-email',
serviceId: 'gmail',
requiredScopes: [
'https://www.googleapis.com/auth/gmail.modify',
@@ -104,7 +103,6 @@ export const WebhookBlock: BlockConfig = {
id: 'outlookCredential',
title: 'Microsoft Account',
type: 'oauth-input',
provider: 'outlook',
serviceId: 'outlook',
requiredScopes: [
'Mail.ReadWrite',

View File

@@ -31,7 +31,6 @@ export const XBlock: BlockConfig<XResponse> = {
id: 'credential',
title: 'X Account',
type: 'oauth-input',
provider: 'x',
serviceId: 'x',
requiredScopes: ['tweet.read', 'tweet.write', 'users.read', 'offline.access'],
placeholder: 'Select X account',

View File

@@ -222,8 +222,7 @@ export interface SubBlockConfig {
generationType?: GenerationType
collapsible?: boolean // Whether the code block can be collapsed
defaultCollapsed?: boolean // Whether the code block is collapsed by default
// OAuth specific properties
provider?: string
// OAuth specific properties - serviceId is the canonical identifier for OAuth services
serviceId?: string
requiredScopes?: string[]
// File selector specific properties

View File

@@ -138,6 +138,52 @@ const registry: Record<SelectorKey, SelectorDefinition> = {
}))
},
},
'microsoft.chats': {
key: 'microsoft.chats',
staleTime: SELECTOR_STALE,
getQueryKey: ({ context }: SelectorQueryArgs) => [
'selectors',
'microsoft.chats',
context.credentialId ?? 'none',
],
enabled: ({ context }) => Boolean(context.credentialId),
fetchList: async ({ context }: SelectorQueryArgs) => {
const body = JSON.stringify({ credential: context.credentialId })
const data = await fetchJson<{ chats: { id: string; displayName: string }[] }>(
'/api/tools/microsoft-teams/chats',
{ method: 'POST', body }
)
return (data.chats || []).map((chat) => ({
id: chat.id,
label: chat.displayName,
}))
},
},
'microsoft.channels': {
key: 'microsoft.channels',
staleTime: SELECTOR_STALE,
getQueryKey: ({ context }: SelectorQueryArgs) => [
'selectors',
'microsoft.channels',
context.credentialId ?? 'none',
context.teamId ?? 'none',
],
enabled: ({ context }) => Boolean(context.credentialId && context.teamId),
fetchList: async ({ context }: SelectorQueryArgs) => {
const body = JSON.stringify({
credential: context.credentialId,
teamId: context.teamId,
})
const data = await fetchJson<{ channels: { id: string; displayName: string }[] }>(
'/api/tools/microsoft-teams/channels',
{ method: 'POST', body }
)
return (data.channels || []).map((channel) => ({
id: channel.id,
label: channel.displayName,
}))
},
},
'wealthbox.contacts': {
key: 'wealthbox.contacts',
staleTime: SELECTOR_STALE,

View File

@@ -64,9 +64,10 @@ function resolveFileSelector(
mimeType: subBlock.mimeType,
})
const provider = subBlock.provider || subBlock.serviceId || ''
// Use serviceId as the canonical identifier
const serviceId = subBlock.serviceId || ''
switch (provider) {
switch (serviceId) {
case 'google-calendar':
return { key: 'google.calendar', context, allowSearch: false }
case 'confluence':
@@ -74,7 +75,15 @@ function resolveFileSelector(
case 'jira':
return { key: 'jira.issues', context, allowSearch: true }
case 'microsoft-teams':
return { key: 'microsoft.teams', context, allowSearch: true }
// Route to the correct selector based on what type of resource is being selected
if (subBlock.id === 'chatId') {
return { key: 'microsoft.chats', context, allowSearch: false }
}
if (subBlock.id === 'channelId') {
return { key: 'microsoft.channels', context, allowSearch: false }
}
// Default to teams selector for teamId
return { key: 'microsoft.teams', context, allowSearch: false }
case 'wealthbox':
return { key: 'wealthbox.contacts', context, allowSearch: true }
case 'microsoft-planner':
@@ -89,36 +98,33 @@ function resolveFileSelector(
return { key: 'google.drive', context, allowSearch: true }
case 'google-docs':
return { key: 'google.drive', context, allowSearch: true }
case 'onedrive': {
const key: SelectorKey = subBlock.mimeType === 'file' ? 'onedrive.files' : 'onedrive.folders'
return { key, context, allowSearch: true }
}
case 'sharepoint':
return { key: 'sharepoint.sites', context, allowSearch: true }
default:
break
return { key: null, context, allowSearch: true }
}
if (subBlock.serviceId === 'onedrive') {
const key: SelectorKey = subBlock.mimeType === 'file' ? 'onedrive.files' : 'onedrive.folders'
return { key, context, allowSearch: true }
}
if (subBlock.serviceId === 'sharepoint') {
return { key: 'sharepoint.sites', context, allowSearch: true }
}
if (subBlock.serviceId === 'google-sheets') {
return { key: 'google.drive', context, allowSearch: true }
}
return { key: null, context, allowSearch: true }
}
function resolveFolderSelector(
subBlock: SubBlockConfig,
args: SelectorResolutionArgs
): SelectorResolution {
const provider = (subBlock.provider || subBlock.serviceId || 'gmail').toLowerCase()
const key: SelectorKey = provider === 'outlook' ? 'outlook.folders' : 'gmail.labels'
return {
key,
context: buildBaseContext(args),
allowSearch: true,
const serviceId = subBlock.serviceId?.toLowerCase()
if (!serviceId) {
return { key: null, context: buildBaseContext(args), allowSearch: true }
}
switch (serviceId) {
case 'gmail':
return { key: 'gmail.labels', context: buildBaseContext(args), allowSearch: true }
case 'outlook':
return { key: 'outlook.folders', context: buildBaseContext(args), allowSearch: true }
default:
return { key: null, context: buildBaseContext(args), allowSearch: true }
}
}
@@ -126,8 +132,8 @@ function resolveChannelSelector(
subBlock: SubBlockConfig,
args: SelectorResolutionArgs
): SelectorResolution {
const provider = subBlock.provider || 'slack'
if (provider !== 'slack') {
const serviceId = subBlock.serviceId
if (serviceId !== 'slack') {
return { key: null, context: buildBaseContext(args), allowSearch: true }
}
return {
@@ -141,22 +147,18 @@ function resolveProjectSelector(
subBlock: SubBlockConfig,
args: SelectorResolutionArgs
): SelectorResolution {
const provider = subBlock.provider || 'jira'
const serviceId = subBlock.serviceId
const context = buildBaseContext(args)
if (provider === 'linear') {
const key: SelectorKey = subBlock.id === 'teamId' ? 'linear.teams' : 'linear.projects'
return {
key,
context,
allowSearch: true,
switch (serviceId) {
case 'linear': {
const key: SelectorKey = subBlock.id === 'teamId' ? 'linear.teams' : 'linear.projects'
return { key, context, allowSearch: true }
}
}
return {
key: 'jira.projects',
context,
allowSearch: true,
case 'jira':
return { key: 'jira.projects', context, allowSearch: true }
default:
return { key: null, context, allowSearch: true }
}
}

View File

@@ -12,6 +12,8 @@ export type SelectorKey =
| 'linear.teams'
| 'confluence.pages'
| 'microsoft.teams'
| 'microsoft.chats'
| 'microsoft.channels'
| 'wealthbox.contacts'
| 'onedrive.files'
| 'onedrive.folders'
@@ -33,7 +35,6 @@ export interface SelectorContext {
workspaceId?: string
workflowId?: string
credentialId?: string
provider?: string
serviceId?: string
domain?: string
teamId?: string

View File

@@ -39,7 +39,6 @@ export interface CopilotSubblockMetadata {
language?: string
generationType?: string
// OAuth/credential properties
provider?: string
serviceId?: string
requiredScopes?: string[]
// File properties
@@ -627,7 +626,6 @@ function processSubBlock(sb: any): CopilotSubblockMetadata {
generationType: sb.generationType,
// OAuth/credential properties
provider: sb.provider,
serviceId: sb.serviceId,
requiredScopes: sb.requiredScopes,

View File

@@ -11,7 +11,7 @@ export enum CredentialType {
// Type for credential requirement
export interface CredentialRequirement {
type: CredentialType
provider?: string // For OAuth (e.g., 'google-drive', 'slack')
serviceId?: string // For OAuth (e.g., 'google-drive', 'slack')
label: string // Human-readable label
blockType: string // The block type that requires this
subBlockId: string // The subblock ID for reference
@@ -72,7 +72,7 @@ export function extractRequiredCredentials(state: any): CredentialRequirement[]
seen.add(key)
credentials.push({
type: CredentialType.OAUTH,
provider: block.type,
serviceId: block.type,
label: `Credential for ${blockName}`,
blockType: block.type,
subBlockId: 'oauth',

View File

@@ -1,5 +1,5 @@
import { createLogger } from '@/lib/logs/console/logger'
import { getProviderIdFromServiceId, getServiceIdFromScopes } from '@/lib/oauth/oauth'
import { getProviderIdFromServiceId } from '@/lib/oauth/oauth'
import { getBlock } from '@/blocks/index'
import type { SubBlockConfig } from '@/blocks/types'
import type { BlockState } from '@/stores/workflows/workflow/types'
@@ -52,7 +52,7 @@ export async function resolveCredentialsForWorkflow(
logger.debug(`Checking credential for ${blockId}.${subBlockId}`, {
blockType: blockState.type,
provider: subBlockConfig.provider,
serviceId: subBlockConfig.serviceId,
hasExistingValue: !!existingValue,
existingValue,
})
@@ -70,13 +70,13 @@ export async function resolveCredentialsForWorkflow(
resolvedValues[blockId][subBlockId] = credentialId
logger.info(`Auto-selected credential for ${blockId}.${subBlockId}`, {
blockType: blockState.type,
provider: subBlockConfig.provider,
serviceId: subBlockConfig.serviceId,
credentialId,
})
} else {
logger.info(`No credential auto-selected for ${blockId}.${subBlockId}`, {
blockType: blockState.type,
provider: subBlockConfig.provider,
serviceId: subBlockConfig.serviceId,
})
}
}
@@ -102,7 +102,6 @@ export async function resolveCredentialsForWorkflow(
*/
async function resolveCredentialForSubBlock(
subBlockConfig: SubBlockConfig & {
provider?: string
requiredScopes?: string[]
serviceId?: string
},
@@ -110,29 +109,26 @@ async function resolveCredentialForSubBlock(
userId?: string
): Promise<string | null> {
try {
const provider = subBlockConfig.provider
const requiredScopes = subBlockConfig.requiredScopes || []
const serviceId = subBlockConfig.serviceId
logger.debug('Resolving credential for subblock', {
blockType: blockState.type,
provider,
serviceId,
requiredScopes,
userId,
})
if (!provider) {
logger.debug('No provider specified, skipping credential resolution')
if (!serviceId) {
logger.debug('No serviceId specified, skipping credential resolution')
return null
}
// Derive service and provider IDs
const effectiveServiceId = serviceId || getServiceIdFromScopes(provider as any, requiredScopes)
const effectiveProviderId = getProviderIdFromServiceId(effectiveServiceId)
// Derive providerId from serviceId using OAuth config
const effectiveProviderId = getProviderIdFromServiceId(serviceId)
logger.debug('Derived provider info', {
effectiveServiceId,
serviceId,
effectiveProviderId,
})

View File

@@ -23,7 +23,6 @@ export interface UIComponentConfig {
condition?: ComponentCondition
title?: string
value?: unknown
provider?: string
serviceId?: string
requiredScopes?: string[]
mimeType?: string
@@ -50,7 +49,6 @@ export interface SubBlockConfig {
password?: boolean
condition?: ComponentCondition
value?: unknown
provider?: string
serviceId?: string
requiredScopes?: string[]
mimeType?: string
@@ -277,7 +275,6 @@ export function getToolParametersConfig(
condition: subBlock.condition,
title: subBlock.title,
value: subBlock.value,
provider: subBlock.provider,
serviceId: subBlock.serviceId,
requiredScopes: subBlock.requiredScopes,
mimeType: subBlock.mimeType,

View File

@@ -16,7 +16,7 @@ export const airtableWebhookTrigger: TriggerConfig = {
title: 'Credentials',
type: 'oauth-input',
description: 'This trigger requires airtable credentials to access your account.',
provider: 'airtable',
serviceId: 'airtable',
requiredScopes: [],
required: true,
mode: 'trigger',

View File

@@ -19,7 +19,7 @@ export const gmailPollingTrigger: TriggerConfig = {
title: 'Credentials',
type: 'oauth-input',
description: 'This trigger requires google email credentials to access your account.',
provider: 'google-email',
serviceId: 'gmail',
requiredScopes: [],
required: true,
mode: 'trigger',

View File

@@ -22,7 +22,6 @@ export const jiraWebhookSubBlocks: SubBlockConfig[] = [
id: 'triggerCredentials',
title: 'Jira Credentials',
type: 'oauth-input',
provider: 'jira',
serviceId: 'jira',
requiredScopes: [
'read:jira-work',

View File

@@ -16,7 +16,7 @@ export const microsoftTeamsChatSubscriptionTrigger: TriggerConfig = {
title: 'Credentials',
type: 'oauth-input',
description: 'This trigger requires microsoft teams credentials to access your account.',
provider: 'microsoft-teams',
serviceId: 'microsoft-teams',
requiredScopes: [
'openid',
'profile',

View File

@@ -19,7 +19,7 @@ export const outlookPollingTrigger: TriggerConfig = {
title: 'Credentials',
type: 'oauth-input',
description: 'This trigger requires outlook credentials to access your account.',
provider: 'outlook',
serviceId: 'outlook',
requiredScopes: [],
required: true,
mode: 'trigger',

View File

@@ -16,7 +16,7 @@ export const webflowCollectionItemChangedTrigger: TriggerConfig = {
title: 'Credentials',
type: 'oauth-input',
description: 'This trigger requires webflow credentials to access your account.',
provider: 'webflow',
serviceId: 'webflow',
requiredScopes: [],
required: true,
mode: 'trigger',

View File

@@ -29,7 +29,7 @@ export const webflowCollectionItemCreatedTrigger: TriggerConfig = {
title: 'Credentials',
type: 'oauth-input',
description: 'This trigger requires webflow credentials to access your account.',
provider: 'webflow',
serviceId: 'webflow',
requiredScopes: [],
required: true,
mode: 'trigger',

View File

@@ -16,7 +16,7 @@ export const webflowCollectionItemDeletedTrigger: TriggerConfig = {
title: 'Credentials',
type: 'oauth-input',
description: 'This trigger requires webflow credentials to access your account.',
provider: 'webflow',
serviceId: 'webflow',
requiredScopes: [],
required: true,
mode: 'trigger',

View File

@@ -16,7 +16,7 @@ export const webflowFormSubmissionTrigger: TriggerConfig = {
title: 'Credentials',
type: 'oauth-input',
description: 'This trigger requires webflow credentials to access your account.',
provider: 'webflow',
serviceId: 'webflow',
requiredScopes: [],
required: true,
mode: 'trigger',