feat(webflow): added collection, item, & site selectors for webflow (#2368)

* feat(webflow): added collection, item, & site selectors for webflow

* ack PR comments

* ack PR comments
This commit is contained in:
Waleed
2025-12-13 19:14:33 -08:00
committed by GitHub
parent d5b95cbd33
commit c962e3b398
17 changed files with 460 additions and 57 deletions

View File

@@ -42,6 +42,7 @@ List all items from a Webflow CMS collection
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `siteId` | string | Yes | ID of the Webflow site |
| `collectionId` | string | Yes | ID of the collection |
| `offset` | number | No | Offset for pagination \(optional\) |
| `limit` | number | No | Maximum number of items to return \(optional, default: 100\) |
@@ -61,6 +62,7 @@ Get a single item from a Webflow CMS collection
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `siteId` | string | Yes | ID of the Webflow site |
| `collectionId` | string | Yes | ID of the collection |
| `itemId` | string | Yes | ID of the item to retrieve |
@@ -79,6 +81,7 @@ Create a new item in a Webflow CMS collection
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `siteId` | string | Yes | ID of the Webflow site |
| `collectionId` | string | Yes | ID of the collection |
| `fieldData` | json | Yes | Field data for the new item as a JSON object. Keys should match collection field names. |
@@ -97,6 +100,7 @@ Update an existing item in a Webflow CMS collection
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `siteId` | string | Yes | ID of the Webflow site |
| `collectionId` | string | Yes | ID of the collection |
| `itemId` | string | Yes | ID of the item to update |
| `fieldData` | json | Yes | Field data to update as a JSON object. Only include fields you want to change. |
@@ -116,6 +120,7 @@ Delete an item from a Webflow CMS collection
| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `siteId` | string | Yes | ID of the Webflow site |
| `collectionId` | string | Yes | ID of the collection |
| `itemId` | string | Yes | ID of the item to delete |

View File

@@ -1,32 +1,53 @@
import { type NextRequest, NextResponse } from 'next/server'
import { getSession } from '@/lib/auth'
import { NextResponse } from 'next/server'
import { authorizeCredentialUse } from '@/lib/auth/credential-access'
import { generateRequestId } from '@/lib/core/utils/request'
import { createLogger } from '@/lib/logs/console/logger'
import { getOAuthToken } from '@/app/api/auth/oauth/utils'
import { refreshAccessTokenIfNeeded } from '@/app/api/auth/oauth/utils'
const logger = createLogger('WebflowCollectionsAPI')
export const dynamic = 'force-dynamic'
export async function GET(request: NextRequest) {
export async function POST(request: Request) {
try {
const session = await getSession()
if (!session?.user?.id) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
}
const requestId = generateRequestId()
const body = await request.json()
const { credential, workflowId, siteId } = body
const { searchParams } = new URL(request.url)
const siteId = searchParams.get('siteId')
if (!credential) {
logger.error('Missing credential in request')
return NextResponse.json({ error: 'Credential is required' }, { status: 400 })
}
if (!siteId) {
return NextResponse.json({ error: 'Missing siteId parameter' }, { status: 400 })
logger.error('Missing siteId in request')
return NextResponse.json({ error: 'Site ID is required' }, { status: 400 })
}
const accessToken = await getOAuthToken(session.user.id, 'webflow')
const authz = await authorizeCredentialUse(request as any, {
credentialId: credential,
workflowId,
})
if (!authz.ok || !authz.credentialOwnerUserId) {
return NextResponse.json({ error: authz.error || 'Unauthorized' }, { status: 403 })
}
const accessToken = await refreshAccessTokenIfNeeded(
credential,
authz.credentialOwnerUserId,
requestId
)
if (!accessToken) {
logger.error('Failed to get access token', {
credentialId: credential,
userId: authz.credentialOwnerUserId,
})
return NextResponse.json(
{ error: 'No Webflow access token found. Please connect your Webflow account.' },
{ status: 404 }
{
error: 'Could not retrieve access token',
authRequired: true,
},
{ status: 401 }
)
}
@@ -58,11 +79,11 @@ export async function GET(request: NextRequest) {
name: collection.displayName || collection.slug || collection.id,
}))
return NextResponse.json({ collections: formattedCollections }, { status: 200 })
} catch (error: any) {
logger.error('Error fetching Webflow collections', error)
return NextResponse.json({ collections: formattedCollections })
} catch (error) {
logger.error('Error processing Webflow collections request:', error)
return NextResponse.json(
{ error: 'Internal server error', details: error.message },
{ error: 'Failed to retrieve Webflow collections', details: (error as Error).message },
{ status: 500 }
)
}

View File

@@ -0,0 +1,104 @@
import { NextResponse } from 'next/server'
import { authorizeCredentialUse } from '@/lib/auth/credential-access'
import { generateRequestId } from '@/lib/core/utils/request'
import { createLogger } from '@/lib/logs/console/logger'
import { refreshAccessTokenIfNeeded } from '@/app/api/auth/oauth/utils'
const logger = createLogger('WebflowItemsAPI')
export const dynamic = 'force-dynamic'
export async function POST(request: Request) {
try {
const requestId = generateRequestId()
const body = await request.json()
const { credential, workflowId, collectionId, search } = body
if (!credential) {
logger.error('Missing credential in request')
return NextResponse.json({ error: 'Credential is required' }, { status: 400 })
}
if (!collectionId) {
logger.error('Missing collectionId in request')
return NextResponse.json({ error: 'Collection ID is required' }, { status: 400 })
}
const authz = await authorizeCredentialUse(request as any, {
credentialId: credential,
workflowId,
})
if (!authz.ok || !authz.credentialOwnerUserId) {
return NextResponse.json({ error: authz.error || 'Unauthorized' }, { status: 403 })
}
const accessToken = await refreshAccessTokenIfNeeded(
credential,
authz.credentialOwnerUserId,
requestId
)
if (!accessToken) {
logger.error('Failed to get access token', {
credentialId: credential,
userId: authz.credentialOwnerUserId,
})
return NextResponse.json(
{
error: 'Could not retrieve access token',
authRequired: true,
},
{ status: 401 }
)
}
const response = await fetch(
`https://api.webflow.com/v2/collections/${collectionId}/items?limit=100`,
{
headers: {
Authorization: `Bearer ${accessToken}`,
accept: 'application/json',
},
}
)
if (!response.ok) {
const errorData = await response.json().catch(() => ({}))
logger.error('Failed to fetch Webflow items', {
status: response.status,
error: errorData,
collectionId,
})
return NextResponse.json(
{ error: 'Failed to fetch Webflow items', details: errorData },
{ status: response.status }
)
}
const data = await response.json()
const items = data.items || []
let formattedItems = items.map((item: any) => {
const fieldData = item.fieldData || {}
const name = fieldData.name || fieldData.title || fieldData.slug || item.id
return {
id: item.id,
name,
}
})
if (search) {
const searchLower = search.toLowerCase()
formattedItems = formattedItems.filter((item: { id: string; name: string }) =>
item.name.toLowerCase().includes(searchLower)
)
}
return NextResponse.json({ items: formattedItems })
} catch (error) {
logger.error('Error processing Webflow items request:', error)
return NextResponse.json(
{ error: 'Failed to retrieve Webflow items', details: (error as Error).message },
{ status: 500 }
)
}
}

View File

@@ -1,25 +1,48 @@
import { type NextRequest, NextResponse } from 'next/server'
import { getSession } from '@/lib/auth'
import { NextResponse } from 'next/server'
import { authorizeCredentialUse } from '@/lib/auth/credential-access'
import { generateRequestId } from '@/lib/core/utils/request'
import { createLogger } from '@/lib/logs/console/logger'
import { getOAuthToken } from '@/app/api/auth/oauth/utils'
import { refreshAccessTokenIfNeeded } from '@/app/api/auth/oauth/utils'
const logger = createLogger('WebflowSitesAPI')
export const dynamic = 'force-dynamic'
export async function GET(request: NextRequest) {
export async function POST(request: Request) {
try {
const session = await getSession()
if (!session?.user?.id) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
const requestId = generateRequestId()
const body = await request.json()
const { credential, workflowId } = body
if (!credential) {
logger.error('Missing credential in request')
return NextResponse.json({ error: 'Credential is required' }, { status: 400 })
}
const accessToken = await getOAuthToken(session.user.id, 'webflow')
const authz = await authorizeCredentialUse(request as any, {
credentialId: credential,
workflowId,
})
if (!authz.ok || !authz.credentialOwnerUserId) {
return NextResponse.json({ error: authz.error || 'Unauthorized' }, { status: 403 })
}
const accessToken = await refreshAccessTokenIfNeeded(
credential,
authz.credentialOwnerUserId,
requestId
)
if (!accessToken) {
logger.error('Failed to get access token', {
credentialId: credential,
userId: authz.credentialOwnerUserId,
})
return NextResponse.json(
{ error: 'No Webflow access token found. Please connect your Webflow account.' },
{ status: 404 }
{
error: 'Could not retrieve access token',
authRequired: true,
},
{ status: 401 }
)
}
@@ -50,11 +73,11 @@ export async function GET(request: NextRequest) {
name: site.displayName || site.shortName || site.id,
}))
return NextResponse.json({ sites: formattedSites }, { status: 200 })
} catch (error: any) {
logger.error('Error fetching Webflow sites', error)
return NextResponse.json({ sites: formattedSites })
} catch (error) {
logger.error('Error processing Webflow sites request:', error)
return NextResponse.json(
{ error: 'Internal server error', details: error.message },
{ error: 'Failed to retrieve Webflow sites', details: (error as Error).message },
{ status: 500 }
)
}

View File

@@ -47,12 +47,16 @@ export function FileSelectorInput({
const [projectIdValueFromStore] = useSubBlockValue(blockId, 'projectId')
const [planIdValueFromStore] = useSubBlockValue(blockId, 'planId')
const [teamIdValueFromStore] = useSubBlockValue(blockId, 'teamId')
const [siteIdValueFromStore] = useSubBlockValue(blockId, 'siteId')
const [collectionIdValueFromStore] = useSubBlockValue(blockId, 'collectionId')
const connectedCredential = previewContextValues?.credential ?? connectedCredentialFromStore
const domainValue = previewContextValues?.domain ?? domainValueFromStore
const projectIdValue = previewContextValues?.projectId ?? projectIdValueFromStore
const planIdValue = previewContextValues?.planId ?? planIdValueFromStore
const teamIdValue = previewContextValues?.teamId ?? teamIdValueFromStore
const siteIdValue = previewContextValues?.siteId ?? siteIdValueFromStore
const collectionIdValue = previewContextValues?.collectionId ?? collectionIdValueFromStore
const normalizedCredentialId =
typeof connectedCredential === 'string'
@@ -75,6 +79,8 @@ export function FileSelectorInput({
projectId: (projectIdValue as string) || undefined,
planId: (planIdValue as string) || undefined,
teamId: (teamIdValue as string) || undefined,
siteId: (siteIdValue as string) || undefined,
collectionId: (collectionIdValue as string) || undefined,
})
}, [
subBlock,
@@ -84,6 +90,8 @@ export function FileSelectorInput({
projectIdValue,
planIdValue,
teamIdValue,
siteIdValue,
collectionIdValue,
])
const missingCredential = !normalizedCredentialId
@@ -97,6 +105,10 @@ export function FileSelectorInput({
!selectorResolution.context.projectId
const missingPlan =
selectorResolution?.key === 'microsoft.planner' && !selectorResolution.context.planId
const missingSite =
selectorResolution?.key === 'webflow.collections' && !selectorResolution.context.siteId
const missingCollection =
selectorResolution?.key === 'webflow.items' && !selectorResolution.context.collectionId
const disabledReason =
finalDisabled ||
@@ -105,6 +117,8 @@ export function FileSelectorInput({
missingDomain ||
missingProject ||
missingPlan ||
missingSite ||
missingCollection ||
!selectorResolution?.key
if (!selectorResolution?.key) {

View File

@@ -43,14 +43,12 @@ export function ProjectSelectorInput({
// Use previewContextValues if provided (for tools inside agent blocks), otherwise use store values
const connectedCredential = previewContextValues?.credential ?? connectedCredentialFromStore
const linearCredential = previewContextValues?.credential ?? connectedCredentialFromStore
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(
effectiveProviderId,
@@ -65,7 +63,6 @@ export function ProjectSelectorInput({
})
// Jira/Discord upstream fields - use values from previewContextValues or store
const jiraCredential = connectedCredential
const domain = (jiraDomain as string) || ''
// Verify Jira credential belongs to current user; if not, treat as absent
@@ -84,19 +81,11 @@ export function ProjectSelectorInput({
const selectorResolution = useMemo(() => {
return resolveSelectorForSubBlock(subBlock, {
workflowId: workflowIdFromUrl || undefined,
credentialId: (isLinear ? linearCredential : jiraCredential) as string | undefined,
credentialId: (connectedCredential as string) || undefined,
domain,
teamId: (linearTeamId as string) || undefined,
})
}, [
subBlock,
workflowIdFromUrl,
isLinear,
linearCredential,
jiraCredential,
domain,
linearTeamId,
])
}, [subBlock, workflowIdFromUrl, connectedCredential, domain, linearTeamId])
const missingCredential = !selectorResolution?.context.credentialId

View File

@@ -47,12 +47,16 @@ export function FileSelectorInput({
const [projectIdValueFromStore] = useSubBlockValue(blockId, 'projectId')
const [planIdValueFromStore] = useSubBlockValue(blockId, 'planId')
const [teamIdValueFromStore] = useSubBlockValue(blockId, 'teamId')
const [siteIdValueFromStore] = useSubBlockValue(blockId, 'siteId')
const [collectionIdValueFromStore] = useSubBlockValue(blockId, 'collectionId')
const connectedCredential = previewContextValues?.credential ?? connectedCredentialFromStore
const domainValue = previewContextValues?.domain ?? domainValueFromStore
const projectIdValue = previewContextValues?.projectId ?? projectIdValueFromStore
const planIdValue = previewContextValues?.planId ?? planIdValueFromStore
const teamIdValue = previewContextValues?.teamId ?? teamIdValueFromStore
const siteIdValue = previewContextValues?.siteId ?? siteIdValueFromStore
const collectionIdValue = previewContextValues?.collectionId ?? collectionIdValueFromStore
const normalizedCredentialId =
typeof connectedCredential === 'string'
@@ -75,6 +79,8 @@ export function FileSelectorInput({
projectId: (projectIdValue as string) || undefined,
planId: (planIdValue as string) || undefined,
teamId: (teamIdValue as string) || undefined,
siteId: (siteIdValue as string) || undefined,
collectionId: (collectionIdValue as string) || undefined,
})
}, [
subBlock,
@@ -84,6 +90,8 @@ export function FileSelectorInput({
projectIdValue,
planIdValue,
teamIdValue,
siteIdValue,
collectionIdValue,
])
const missingCredential = !normalizedCredentialId
@@ -97,6 +105,10 @@ export function FileSelectorInput({
!selectorResolution?.context.projectId
const missingPlan =
selectorResolution?.key === 'microsoft.planner' && !selectorResolution?.context.planId
const missingSite =
selectorResolution?.key === 'webflow.collections' && !selectorResolution?.context.siteId
const missingCollection =
selectorResolution?.key === 'webflow.items' && !selectorResolution?.context.collectionId
const disabledReason =
finalDisabled ||
@@ -105,6 +117,8 @@ export function FileSelectorInput({
missingDomain ||
missingProject ||
missingPlan ||
missingSite ||
missingCollection ||
!selectorResolution?.key
if (!selectorResolution?.key) {

View File

@@ -39,19 +39,65 @@ export const WebflowBlock: BlockConfig<WebflowResponse> = {
placeholder: 'Select Webflow account',
required: true,
},
{
id: 'siteId',
title: 'Site',
type: 'project-selector',
canonicalParamId: 'siteId',
serviceId: 'webflow',
placeholder: 'Select Webflow site',
dependsOn: ['credential'],
mode: 'basic',
required: true,
},
{
id: 'manualSiteId',
title: 'Site ID',
type: 'short-input',
canonicalParamId: 'siteId',
placeholder: 'Enter site ID',
mode: 'advanced',
required: true,
},
{
id: 'collectionId',
title: 'Collection',
type: 'file-selector',
canonicalParamId: 'collectionId',
serviceId: 'webflow',
placeholder: 'Select collection',
dependsOn: ['credential', 'siteId'],
mode: 'basic',
required: true,
},
{
id: 'manualCollectionId',
title: 'Collection ID',
type: 'short-input',
canonicalParamId: 'collectionId',
placeholder: 'Enter collection ID',
dependsOn: ['credential'],
mode: 'advanced',
required: true,
},
{
id: 'itemId',
title: 'Item',
type: 'file-selector',
canonicalParamId: 'itemId',
serviceId: 'webflow',
placeholder: 'Select item',
dependsOn: ['credential', 'collectionId'],
mode: 'basic',
condition: { field: 'operation', value: ['get', 'update', 'delete'] },
required: true,
},
{
id: 'manualItemId',
title: 'Item ID',
type: 'short-input',
placeholder: 'ID of the item',
canonicalParamId: 'itemId',
placeholder: 'Enter item ID',
mode: 'advanced',
condition: { field: 'operation', value: ['get', 'update', 'delete'] },
required: true,
},
@@ -108,7 +154,17 @@ export const WebflowBlock: BlockConfig<WebflowResponse> = {
}
},
params: (params) => {
const { credential, fieldData, ...rest } = params
const {
credential,
fieldData,
siteId,
manualSiteId,
collectionId,
manualCollectionId,
itemId,
manualItemId,
...rest
} = params
let parsedFieldData: any | undefined
try {
@@ -119,15 +175,46 @@ export const WebflowBlock: BlockConfig<WebflowResponse> = {
throw new Error(`Invalid JSON input for ${params.operation} operation: ${error.message}`)
}
const effectiveSiteId = ((siteId as string) || (manualSiteId as string) || '').trim()
const effectiveCollectionId = (
(collectionId as string) ||
(manualCollectionId as string) ||
''
).trim()
const effectiveItemId = ((itemId as string) || (manualItemId as string) || '').trim()
if (!effectiveSiteId) {
throw new Error('Site ID is required')
}
if (!effectiveCollectionId) {
throw new Error('Collection ID is required')
}
const baseParams = {
credential,
siteId: effectiveSiteId,
collectionId: effectiveCollectionId,
...rest,
}
switch (params.operation) {
case 'create':
case 'update':
return { ...baseParams, fieldData: parsedFieldData }
if (params.operation === 'update' && !effectiveItemId) {
throw new Error('Item ID is required for update operation')
}
return {
...baseParams,
itemId: effectiveItemId || undefined,
fieldData: parsedFieldData,
}
case 'get':
case 'delete':
if (!effectiveItemId) {
throw new Error(`Item ID is required for ${params.operation} operation`)
}
return { ...baseParams, itemId: effectiveItemId }
default:
return baseParams
}
@@ -137,12 +224,15 @@ export const WebflowBlock: BlockConfig<WebflowResponse> = {
inputs: {
operation: { type: 'string', description: 'Operation to perform' },
credential: { type: 'string', description: 'Webflow OAuth access token' },
siteId: { type: 'string', description: 'Webflow site identifier' },
manualSiteId: { type: 'string', description: 'Manual site identifier' },
collectionId: { type: 'string', description: 'Webflow collection identifier' },
// Conditional inputs
itemId: { type: 'string', description: 'Item identifier' }, // Required for get/update/delete
offset: { type: 'number', description: 'Pagination offset' }, // Optional for list
limit: { type: 'number', description: 'Maximum items to return' }, // Optional for list
fieldData: { type: 'json', description: 'Item field data' }, // Required for create/update
manualCollectionId: { type: 'string', description: 'Manual collection identifier' },
itemId: { type: 'string', description: 'Item identifier' },
manualItemId: { type: 'string', description: 'Manual item identifier' },
offset: { type: 'number', description: 'Pagination offset' },
limit: { type: 'number', description: 'Maximum items to return' },
fieldData: { type: 'json', description: 'Item field data' },
},
outputs: {
items: { type: 'json', description: 'Array of items (list operation)' },

View File

@@ -673,6 +673,99 @@ const registry: Record<SelectorKey, SelectorDefinition> = {
return { id: doc.id, label: doc.filename }
},
},
'webflow.sites': {
key: 'webflow.sites',
staleTime: SELECTOR_STALE,
getQueryKey: ({ context }: SelectorQueryArgs) => [
'selectors',
'webflow.sites',
context.credentialId ?? 'none',
],
enabled: ({ context }) => Boolean(context.credentialId),
fetchList: async ({ context }: SelectorQueryArgs) => {
const credentialId = ensureCredential(context, 'webflow.sites')
const body = JSON.stringify({ credential: credentialId, workflowId: context.workflowId })
const data = await fetchJson<{ sites: { id: string; name: string }[] }>(
'/api/tools/webflow/sites',
{
method: 'POST',
body,
}
)
return (data.sites || []).map((site) => ({
id: site.id,
label: site.name,
}))
},
},
'webflow.collections': {
key: 'webflow.collections',
staleTime: SELECTOR_STALE,
getQueryKey: ({ context }: SelectorQueryArgs) => [
'selectors',
'webflow.collections',
context.credentialId ?? 'none',
context.siteId ?? 'none',
],
enabled: ({ context }) => Boolean(context.credentialId && context.siteId),
fetchList: async ({ context }: SelectorQueryArgs) => {
const credentialId = ensureCredential(context, 'webflow.collections')
if (!context.siteId) {
throw new Error('Missing site ID for webflow.collections selector')
}
const body = JSON.stringify({
credential: credentialId,
workflowId: context.workflowId,
siteId: context.siteId,
})
const data = await fetchJson<{ collections: { id: string; name: string }[] }>(
'/api/tools/webflow/collections',
{
method: 'POST',
body,
}
)
return (data.collections || []).map((collection) => ({
id: collection.id,
label: collection.name,
}))
},
},
'webflow.items': {
key: 'webflow.items',
staleTime: 15 * 1000,
getQueryKey: ({ context, search }: SelectorQueryArgs) => [
'selectors',
'webflow.items',
context.credentialId ?? 'none',
context.collectionId ?? 'none',
search ?? '',
],
enabled: ({ context }) => Boolean(context.credentialId && context.collectionId),
fetchList: async ({ context, search }: SelectorQueryArgs) => {
const credentialId = ensureCredential(context, 'webflow.items')
if (!context.collectionId) {
throw new Error('Missing collection ID for webflow.items selector')
}
const body = JSON.stringify({
credential: credentialId,
workflowId: context.workflowId,
collectionId: context.collectionId,
search,
})
const data = await fetchJson<{ items: { id: string; name: string }[] }>(
'/api/tools/webflow/items',
{
method: 'POST',
body,
}
)
return (data.items || []).map((item) => ({
id: item.id,
label: item.name,
}))
},
},
}
export function getSelectorDefinition(key: SelectorKey): SelectorDefinition {

View File

@@ -15,6 +15,8 @@ export interface SelectorResolutionArgs {
planId?: string
teamId?: string
knowledgeBaseId?: string
siteId?: string
collectionId?: string
}
const defaultContext: SelectorContext = {}
@@ -52,6 +54,8 @@ function buildBaseContext(
planId: args.planId,
teamId: args.teamId,
knowledgeBaseId: args.knowledgeBaseId,
siteId: args.siteId,
collectionId: args.collectionId,
...extra,
}
}
@@ -106,6 +110,14 @@ function resolveFileSelector(
}
case 'sharepoint':
return { key: 'sharepoint.sites', context, allowSearch: true }
case 'webflow':
if (subBlock.id === 'collectionId') {
return { key: 'webflow.collections', context, allowSearch: false }
}
if (subBlock.id === 'itemId') {
return { key: 'webflow.items', context, allowSearch: true }
}
return { key: null, context, allowSearch: true }
default:
return { key: null, context, allowSearch: true }
}
@@ -159,6 +171,8 @@ function resolveProjectSelector(
}
case 'jira':
return { key: 'jira.projects', context, allowSearch: true }
case 'webflow':
return { key: 'webflow.sites', context, allowSearch: false }
default:
return { key: null, context, allowSearch: true }
}

View File

@@ -23,6 +23,9 @@ export type SelectorKey =
| 'microsoft.planner'
| 'google.drive'
| 'knowledge.documents'
| 'webflow.sites'
| 'webflow.collections'
| 'webflow.items'
export interface SelectorOption {
id: string
@@ -43,6 +46,8 @@ export interface SelectorContext {
planId?: string
mimeType?: string
fileId?: string
siteId?: string
collectionId?: string
}
export interface SelectorQueryArgs {

View File

@@ -20,6 +20,12 @@ export const webflowCreateItemTool: ToolConfig<WebflowCreateItemParams, WebflowC
visibility: 'hidden',
description: 'OAuth access token',
},
siteId: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'ID of the Webflow site',
},
collectionId: {
type: 'string',
required: true,

View File

@@ -20,6 +20,12 @@ export const webflowDeleteItemTool: ToolConfig<WebflowDeleteItemParams, WebflowD
visibility: 'hidden',
description: 'OAuth access token',
},
siteId: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'ID of the Webflow site',
},
collectionId: {
type: 'string',
required: true,
@@ -29,7 +35,7 @@ export const webflowDeleteItemTool: ToolConfig<WebflowDeleteItemParams, WebflowD
itemId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
visibility: 'user-only',
description: 'ID of the item to delete',
},
},

View File

@@ -19,6 +19,12 @@ export const webflowGetItemTool: ToolConfig<WebflowGetItemParams, WebflowGetItem
visibility: 'hidden',
description: 'OAuth access token',
},
siteId: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'ID of the Webflow site',
},
collectionId: {
type: 'string',
required: true,
@@ -28,7 +34,7 @@ export const webflowGetItemTool: ToolConfig<WebflowGetItemParams, WebflowGetItem
itemId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
visibility: 'user-only',
description: 'ID of the item to retrieve',
},
},

View File

@@ -19,6 +19,12 @@ export const webflowListItemsTool: ToolConfig<WebflowListItemsParams, WebflowLis
visibility: 'hidden',
description: 'OAuth access token',
},
siteId: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'ID of the Webflow site',
},
collectionId: {
type: 'string',
required: true,

View File

@@ -1,5 +1,6 @@
export interface WebflowBaseParams {
accessToken: string
siteId: string
collectionId: string
}

View File

@@ -20,6 +20,12 @@ export const webflowUpdateItemTool: ToolConfig<WebflowUpdateItemParams, WebflowU
visibility: 'hidden',
description: 'OAuth access token',
},
siteId: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'ID of the Webflow site',
},
collectionId: {
type: 'string',
required: true,
@@ -29,7 +35,7 @@ export const webflowUpdateItemTool: ToolConfig<WebflowUpdateItemParams, WebflowU
itemId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
visibility: 'user-only',
description: 'ID of the item to update',
},
fieldData: {