feat(integrations): added deeper integrations for slack, supabase, firecrawl, exa, notion (#728)

* added deeper exa integrations

* added firecrawl crawl tool

* include (optional) indicator for fields that are not explicitly required to be filled in by the user

* use aliased imports, stronger typing, added additional notion tools

* added additional notion tools, tested

* added additional supabase tools, updated CSP

* added remaining supabase tools

* finished supabase tools

* fixed persistence of selector inputs on refresh, added supabase tools and slack tools

* fixed failing test

* remove unrelated file
This commit is contained in:
Waleed Latif
2025-07-19 21:29:14 -07:00
committed by GitHub
parent 5ee66252ed
commit cdfb2fcd4c
249 changed files with 2898 additions and 620 deletions
@@ -0,0 +1,39 @@
import { type NextRequest, NextResponse } from 'next/server'
export async function GET(
request: NextRequest,
{ params }: { params: Promise<{ jobId: string }> }
) {
const { jobId } = await params
const authHeader = request.headers.get('authorization')
if (!authHeader) {
return NextResponse.json({ error: 'Authorization header is required' }, { status: 401 })
}
try {
const response = await fetch(`https://api.firecrawl.dev/v1/crawl/${jobId}`, {
method: 'GET',
headers: {
Authorization: authHeader,
'Content-Type': 'application/json',
},
})
const data = await response.json()
if (!response.ok) {
return NextResponse.json(
{ error: data.error || data.message || 'Failed to get crawl status' },
{ status: response.status }
)
}
return NextResponse.json(data)
} catch (error: any) {
return NextResponse.json(
{ error: `Failed to fetch crawl status: ${error.message}` },
{ status: 500 }
)
}
}
@@ -4,6 +4,7 @@ import { useEffect, useState } from 'react'
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip'
import type { SubBlockConfig } from '@/blocks/types'
import { useSubBlockStore } from '@/stores/workflows/subblock/store'
import { useSubBlockValue } from '../../hooks/use-sub-block-value'
import { type SlackChannelInfo, SlackChannelSelector } from './components/slack-channel-selector'
interface ChannelSelectorInputProps {
@@ -25,7 +26,10 @@ export function ChannelSelectorInput({
isPreview = false,
previewValue,
}: ChannelSelectorInputProps) {
const { getValue, setValue } = useSubBlockStore()
const { getValue } = useSubBlockStore()
// Use the proper hook to get the current value and setter (same as file-selector)
const [storeValue, setStoreValue] = useSubBlockValue(blockId, subBlock.id)
const [selectedChannelId, setSelectedChannelId] = useState<string>('')
const [_channelInfo, setChannelInfo] = useState<SlackChannelInfo | null>(null)
@@ -47,9 +51,9 @@ export function ChannelSelectorInput({
}
// Use preview value when in preview mode, otherwise use store value
const value = isPreview ? previewValue : getValue(blockId, subBlock.id)
const value = isPreview ? previewValue : storeValue
// Get the current value from the store or prop value if in preview mode
// Get the current value from the store or prop value if in preview mode (same pattern as file-selector)
useEffect(() => {
if (isPreview && previewValue !== undefined) {
const value = previewValue
@@ -64,12 +68,12 @@ export function ChannelSelectorInput({
}
}, [blockId, subBlock.id, getValue, isPreview, previewValue])
// Handle channel selection
// Handle channel selection (same pattern as file-selector)
const handleChannelChange = (channelId: string, info?: SlackChannelInfo) => {
setSelectedChannelId(channelId)
setChannelInfo(info || null)
if (!isPreview) {
setValue(blockId, subBlock.id, channelId)
setStoreValue(channelId)
}
onChannelSelect?.(channelId)
}
@@ -107,15 +107,15 @@ const SCOPE_DESCRIPTIONS: Record<string, string> = {
'guilds.members.read': 'Read your Discord guild members',
read: 'Read access to your workspace',
write: 'Write access to your Linear workspace',
'channels:read': 'Read your Slack channels',
'groups:read': 'Read your Slack private channels',
'chat:write': 'Write to your invited Slack channels',
'chat:write.public': 'Write to your public Slack channels',
'users:read': 'Read your Slack users',
'search:read': 'Read your Slack search',
'files:read': 'Read your Slack files',
'links:read': 'Read your Slack links',
'links:write': 'Write to your Slack links',
'channels:read': 'View public channels',
'channels:history': 'Read channel messages',
'groups:read': 'View private channels',
'groups:history': 'Read private messages',
'chat:write': 'Send messages',
'chat:write.public': 'Post to public channels',
'users:read': 'View workspace users',
'files:write': 'Upload files',
'canvases:write': 'Create canvas documents',
}
// Convert OAuth scope to user-friendly description
@@ -1006,9 +1006,9 @@ export function ToolInput({
case 'channel-selector':
return (
<ChannelSelectorInput
blockId={uniqueBlockId}
blockId={blockId}
subBlock={{
id: param.id,
id: `tool-${toolIndex || 0}-${param.id}`,
type: 'channel-selector' as const,
title: param.id,
provider: uiComponent.provider || 'slack',
@@ -1023,9 +1023,9 @@ export function ToolInput({
case 'project-selector':
return (
<ProjectSelectorInput
blockId={uniqueBlockId}
blockId={blockId}
subBlock={{
id: param.id,
id: `tool-${toolIndex || 0}-${param.id}`,
type: 'project-selector' as const,
title: param.id,
provider: uiComponent.provider || 'jira',
@@ -1632,7 +1632,7 @@ export function ToolInput({
{param.required && param.visibility === 'user-only' && (
<span className='ml-1 text-red-500'>*</span>
)}
{!param.required && (
{(!param.required || param.visibility !== 'user-only') && (
<span className='ml-1 text-muted-foreground/60 text-xs'>
(Optional)
</span>
@@ -896,4 +896,4 @@ export function SearchModal({
</DialogPortal>
</Dialog>
)
}
}
+28 -2
View File
@@ -13,7 +13,6 @@ export const ExaBlock: BlockConfig<ExaResponse> = {
bgColor: '#1F40ED',
icon: ExaAIIcon,
subBlocks: [
// Operation selector
{
id: 'operation',
title: 'Operation',
@@ -24,6 +23,7 @@ export const ExaBlock: BlockConfig<ExaResponse> = {
{ label: 'Get Contents', id: 'exa_get_contents' },
{ label: 'Find Similar Links', id: 'exa_find_similar_links' },
{ label: 'Answer', id: 'exa_answer' },
{ label: 'Research', id: 'exa_research' },
],
value: () => 'exa_search',
},
@@ -129,6 +129,22 @@ export const ExaBlock: BlockConfig<ExaResponse> = {
layout: 'full',
condition: { field: 'operation', value: 'exa_answer' },
},
// Research operation inputs
{
id: 'query',
title: 'Research Query',
type: 'long-input',
layout: 'full',
placeholder: 'Enter your research topic or question...',
condition: { field: 'operation', value: 'exa_research' },
},
{
id: 'includeText',
title: 'Include Full Text',
type: 'switch',
layout: 'full',
condition: { field: 'operation', value: 'exa_research' },
},
// API Key (common)
{
id: 'apiKey',
@@ -140,7 +156,13 @@ export const ExaBlock: BlockConfig<ExaResponse> = {
},
],
tools: {
access: ['exa_search', 'exa_get_contents', 'exa_find_similar_links', 'exa_answer'],
access: [
'exa_search',
'exa_get_contents',
'exa_find_similar_links',
'exa_answer',
'exa_research',
],
config: {
tool: (params) => {
// Convert numResults to a number for operations that use it
@@ -157,6 +179,8 @@ export const ExaBlock: BlockConfig<ExaResponse> = {
return 'exa_find_similar_links'
case 'exa_answer':
return 'exa_answer'
case 'exa_research':
return 'exa_research'
default:
return 'exa_search'
}
@@ -186,5 +210,7 @@ export const ExaBlock: BlockConfig<ExaResponse> = {
// Answer output
answer: 'string',
citations: 'json',
// Research output
research: 'json',
},
}
+35 -3
View File
@@ -21,6 +21,7 @@ export const FirecrawlBlock: BlockConfig<FirecrawlResponse> = {
options: [
{ label: 'Scrape', id: 'scrape' },
{ label: 'Search', id: 'search' },
{ label: 'Crawl', id: 'crawl' },
],
value: () => 'scrape',
},
@@ -29,10 +30,10 @@ export const FirecrawlBlock: BlockConfig<FirecrawlResponse> = {
title: 'Website URL',
type: 'short-input',
layout: 'full',
placeholder: 'Enter the webpage URL to scrape',
placeholder: 'Enter the website URL',
condition: {
field: 'operation',
value: 'scrape',
value: ['scrape', 'crawl'],
},
},
{
@@ -45,6 +46,17 @@ export const FirecrawlBlock: BlockConfig<FirecrawlResponse> = {
value: 'scrape',
},
},
{
id: 'limit',
title: 'Page Limit',
type: 'short-input',
layout: 'half',
placeholder: '100',
condition: {
field: 'operation',
value: 'crawl',
},
},
{
id: 'query',
title: 'Search Query',
@@ -66,7 +78,7 @@ export const FirecrawlBlock: BlockConfig<FirecrawlResponse> = {
},
],
tools: {
access: ['firecrawl_scrape', 'firecrawl_search'],
access: ['firecrawl_scrape', 'firecrawl_search', 'firecrawl_crawl'],
config: {
tool: (params) => {
switch (params.operation) {
@@ -74,16 +86,32 @@ export const FirecrawlBlock: BlockConfig<FirecrawlResponse> = {
return 'firecrawl_scrape'
case 'search':
return 'firecrawl_search'
case 'crawl':
return 'firecrawl_crawl'
default:
return 'firecrawl_scrape'
}
},
params: (params) => {
const { operation, limit, ...rest } = params
switch (operation) {
case 'crawl':
return {
...rest,
limit: limit ? Number.parseInt(limit) : undefined,
}
default:
return rest
}
},
},
},
inputs: {
apiKey: { type: 'string', required: true },
operation: { type: 'string', required: true },
url: { type: 'string', required: false },
limit: { type: 'string', required: false },
query: { type: 'string', required: false },
scrapeOptions: { type: 'json', required: false },
},
@@ -95,5 +123,9 @@ export const FirecrawlBlock: BlockConfig<FirecrawlResponse> = {
// Search output
data: 'json',
warning: 'any',
// Crawl output
pages: 'json',
total: 'number',
creditsUsed: 'number',
},
}
+158 -32
View File
@@ -19,9 +19,17 @@ export const NotionBlock: BlockConfig<NotionResponse> = {
type: 'dropdown',
layout: 'full',
options: [
// Read Operations
{ label: 'Read Page', id: 'notion_read' },
{ label: 'Append Content', id: 'notion_write' },
{ label: 'Read Database', id: 'notion_read_database' },
// Create Operations
{ label: 'Create Page', id: 'notion_create_page' },
{ label: 'Create Database', id: 'notion_create_database' },
// Write Operations
{ label: 'Append Content', id: 'notion_write' },
// Query & Search Operations
{ label: 'Query Database', id: 'notion_query_database' },
{ label: 'Search Workspace', id: 'notion_search' },
],
},
{
@@ -46,6 +54,17 @@ export const NotionBlock: BlockConfig<NotionResponse> = {
value: 'notion_read',
},
},
{
id: 'databaseId',
title: 'Database ID',
type: 'short-input',
layout: 'full',
placeholder: 'Enter Notion database ID',
condition: {
field: 'operation',
value: 'notion_read_database',
},
},
{
id: 'pageId',
title: 'Page ID',
@@ -58,23 +77,12 @@ export const NotionBlock: BlockConfig<NotionResponse> = {
},
},
// Create operation fields
{
id: 'parentType',
title: 'Parent Type',
type: 'dropdown',
layout: 'full',
options: [
{ label: 'Page', id: 'page' },
{ label: 'Database', id: 'database' },
],
condition: { field: 'operation', value: 'notion_create_page' },
},
{
id: 'parentId',
title: 'Parent ID',
title: 'Parent Page ID',
type: 'short-input',
layout: 'full',
placeholder: 'ID of parent page or database',
placeholder: 'ID of parent page',
condition: { field: 'operation', value: 'notion_create_page' },
},
{
@@ -83,18 +91,6 @@ export const NotionBlock: BlockConfig<NotionResponse> = {
type: 'short-input',
layout: 'full',
placeholder: 'Title for the new page',
condition: {
field: 'operation',
value: 'notion_create_page',
and: { field: 'parentType', value: 'page' },
},
},
{
id: 'properties',
title: 'Page Properties (JSON)',
type: 'long-input',
layout: 'full',
placeholder: 'Enter page properties as JSON object',
condition: {
field: 'operation',
value: 'notion_create_page',
@@ -123,28 +119,126 @@ export const NotionBlock: BlockConfig<NotionResponse> = {
value: 'notion_create_page',
},
},
// Query Database Fields
{
id: 'databaseId',
title: 'Database ID',
type: 'short-input',
layout: 'full',
placeholder: 'Enter Notion database ID',
condition: { field: 'operation', value: 'notion_query_database' },
},
{
id: 'filter',
title: 'Filter (JSON)',
type: 'long-input',
layout: 'full',
placeholder: 'Enter filter conditions as JSON (optional)',
condition: { field: 'operation', value: 'notion_query_database' },
},
{
id: 'sorts',
title: 'Sort Criteria (JSON)',
type: 'long-input',
layout: 'full',
placeholder: 'Enter sort criteria as JSON array (optional)',
condition: { field: 'operation', value: 'notion_query_database' },
},
{
id: 'pageSize',
title: 'Page Size',
type: 'short-input',
layout: 'full',
placeholder: 'Number of results (default: 100, max: 100)',
condition: { field: 'operation', value: 'notion_query_database' },
},
// Search Fields
{
id: 'query',
title: 'Search Query',
type: 'short-input',
layout: 'full',
placeholder: 'Enter search terms (leave empty for all pages)',
condition: { field: 'operation', value: 'notion_search' },
},
{
id: 'filterType',
title: 'Filter Type',
type: 'dropdown',
layout: 'full',
options: [
{ label: 'All', id: 'all' },
{ label: 'Pages Only', id: 'page' },
{ label: 'Databases Only', id: 'database' },
],
condition: { field: 'operation', value: 'notion_search' },
},
// Create Database Fields
{
id: 'parentId',
title: 'Parent Page ID',
type: 'short-input',
layout: 'full',
placeholder: 'ID of parent page where database will be created',
condition: { field: 'operation', value: 'notion_create_database' },
},
{
id: 'title',
title: 'Database Title',
type: 'short-input',
layout: 'full',
placeholder: 'Title for the new database',
condition: { field: 'operation', value: 'notion_create_database' },
},
{
id: 'properties',
title: 'Database Properties (JSON)',
type: 'long-input',
layout: 'full',
placeholder: 'Enter database properties as JSON object',
condition: { field: 'operation', value: 'notion_create_database' },
},
],
tools: {
access: ['notion_read', 'notion_write', 'notion_create_page'],
access: [
'notion_read',
'notion_read_database',
'notion_write',
'notion_create_page',
'notion_query_database',
'notion_search',
'notion_create_database',
],
config: {
tool: (params) => {
switch (params.operation) {
case 'notion_read':
return 'notion_read'
case 'notion_read_database':
return 'notion_read_database'
case 'notion_write':
return 'notion_write'
case 'notion_create_page':
return 'notion_create_page'
case 'notion_query_database':
return 'notion_query_database'
case 'notion_search':
return 'notion_search'
case 'notion_create_database':
return 'notion_create_database'
default:
return 'notion_read'
}
},
params: (params) => {
const { credential, operation, properties, ...rest } = params
const { credential, operation, properties, filter, sorts, ...rest } = params
// Parse properties from JSON string for create operations
let parsedProperties
if (operation === 'notion_create_page' && properties) {
if (
(operation === 'notion_create_page' || operation === 'notion_create_database') &&
properties
) {
try {
parsedProperties = JSON.parse(properties)
} catch (error) {
@@ -154,10 +248,36 @@ export const NotionBlock: BlockConfig<NotionResponse> = {
}
}
// Parse filter for query database operations
let parsedFilter
if (operation === 'notion_query_database' && filter) {
try {
parsedFilter = JSON.parse(filter)
} catch (error) {
throw new Error(
`Invalid JSON for filter: ${error instanceof Error ? error.message : String(error)}`
)
}
}
// Parse sorts for query database operations
let parsedSorts
if (operation === 'notion_query_database' && sorts) {
try {
parsedSorts = JSON.parse(sorts)
} catch (error) {
throw new Error(
`Invalid JSON for sorts: ${error instanceof Error ? error.message : String(error)}`
)
}
}
return {
...rest,
accessToken: credential,
...(parsedProperties ? { properties: parsedProperties } : {}),
...(parsedFilter ? { filter: JSON.stringify(parsedFilter) } : {}),
...(parsedSorts ? { sorts: JSON.stringify(parsedSorts) } : {}),
}
},
},
@@ -168,10 +288,16 @@ export const NotionBlock: BlockConfig<NotionResponse> = {
pageId: { type: 'string', required: false },
content: { type: 'string', required: false },
// Create page inputs
parentType: { type: 'string', required: true },
parentId: { type: 'string', required: true },
parentId: { type: 'string', required: false },
title: { type: 'string', required: false },
properties: { type: 'string', required: false },
// Query database inputs
databaseId: { type: 'string', required: false },
filter: { type: 'string', required: false },
sorts: { type: 'string', required: false },
pageSize: { type: 'number', required: false },
// Search inputs
query: { type: 'string', required: false },
filterType: { type: 'string', required: false },
},
outputs: {
content: 'string',
+116 -10
View File
@@ -7,7 +7,7 @@ export const SlackBlock: BlockConfig<SlackResponse> = {
name: 'Slack',
description: 'Send messages to Slack',
longDescription:
"Comprehensive Slack integration with OAuth authentication. Send formatted messages using Slack's mrkdwn syntax or Block Kit.",
"Comprehensive Slack integration with OAuth authentication. Send formatted messages using Slack's mrkdwn syntax.",
docsLink: 'https://docs.simstudio.ai/tools/slack',
category: 'tools',
bgColor: '#611f69',
@@ -18,7 +18,11 @@ export const SlackBlock: BlockConfig<SlackResponse> = {
title: 'Operation',
type: 'dropdown',
layout: 'full',
options: [{ label: 'Send Message', id: 'send' }],
options: [
{ label: 'Send Message', id: 'send' },
{ label: 'Create Canvas', id: 'canvas' },
{ label: 'Read Messages', id: 'read' },
],
value: () => 'send',
},
{
@@ -41,13 +45,14 @@ export const SlackBlock: BlockConfig<SlackResponse> = {
serviceId: 'slack',
requiredScopes: [
'channels:read',
'channels:history',
'groups:read',
'groups:history',
'chat:write',
'chat:write.public',
'users:read',
'files:read',
'links:read',
'links:write',
'files:write',
'canvases:write',
],
placeholder: 'Select Slack workspace',
condition: {
@@ -91,22 +96,87 @@ export const SlackBlock: BlockConfig<SlackResponse> = {
type: 'long-input',
layout: 'full',
placeholder: 'Enter your message (supports Slack mrkdwn)',
condition: {
field: 'operation',
value: 'send',
},
},
// Canvas specific fields
{
id: 'title',
title: 'Canvas Title',
type: 'short-input',
layout: 'full',
placeholder: 'Enter canvas title',
condition: {
field: 'operation',
value: 'canvas',
},
},
{
id: 'content',
title: 'Canvas Content',
type: 'long-input',
layout: 'full',
placeholder: 'Enter canvas content (markdown supported)',
condition: {
field: 'operation',
value: 'canvas',
},
},
// Message Reader specific fields
{
id: 'limit',
title: 'Message Limit',
type: 'short-input',
layout: 'half',
placeholder: '50',
condition: {
field: 'operation',
value: 'read',
},
},
{
id: 'oldest',
title: 'Oldest Timestamp',
type: 'short-input',
layout: 'half',
placeholder: 'ISO 8601 timestamp',
condition: {
field: 'operation',
value: 'read',
},
},
],
tools: {
access: ['slack_message'],
access: ['slack_message', 'slack_canvas', 'slack_message_reader'],
config: {
tool: (params) => {
switch (params.operation) {
case 'send':
return 'slack_message'
case 'canvas':
return 'slack_canvas'
case 'read':
return 'slack_message_reader'
default:
throw new Error(`Invalid Slack operation: ${params.operation}`)
}
},
params: (params) => {
const { credential, authMethod, botToken, operation, channel, manualChannel, ...rest } =
params
const {
credential,
authMethod,
botToken,
operation,
channel,
manualChannel,
title,
content,
limit,
oldest,
...rest
} = params
// Handle channel input (selector or manual)
const effectiveChannel = (channel || manualChannel || '').trim()
@@ -118,7 +188,6 @@ export const SlackBlock: BlockConfig<SlackResponse> = {
}
const baseParams: Record<string, any> = {
...rest,
channel: effectiveChannel,
}
@@ -136,6 +205,36 @@ export const SlackBlock: BlockConfig<SlackResponse> = {
baseParams.credential = credential
}
// Handle operation-specific params
switch (operation) {
case 'send':
if (!rest.text) {
throw new Error('Message text is required for send operation')
}
baseParams.text = rest.text
break
case 'canvas':
if (!title || !content) {
throw new Error('Title and content are required for canvas operation')
}
baseParams.title = title
baseParams.content = content
break
case 'read':
if (limit) {
const parsedLimit = Number.parseInt(limit, 10)
baseParams.limit = !Number.isNaN(parsedLimit) ? parsedLimit : 10
} else {
baseParams.limit = 10
}
if (oldest) {
baseParams.oldest = oldest
}
break
}
return baseParams
},
},
@@ -147,10 +246,17 @@ export const SlackBlock: BlockConfig<SlackResponse> = {
botToken: { type: 'string', required: false },
channel: { type: 'string', required: false },
manualChannel: { type: 'string', required: false },
text: { type: 'string', required: true },
text: { type: 'string', required: false },
title: { type: 'string', required: false },
content: { type: 'string', required: false },
limit: { type: 'string', required: false },
oldest: { type: 'string', required: false },
},
outputs: {
ts: 'string',
channel: 'string',
canvas_id: 'string',
title: 'string',
messages: 'json',
},
}
+112 -13
View File
@@ -1,7 +1,10 @@
import { SupabaseIcon } from '@/components/icons'
import { createLogger } from '@/lib/logs/console-logger'
import type { BlockConfig } from '@/blocks/types'
import type { SupabaseResponse } from '@/tools/supabase/types'
const logger = createLogger('SupabaseBlock')
export const SupabaseBlock: BlockConfig<SupabaseResponse> = {
type: 'supabase',
name: 'Supabase',
@@ -19,8 +22,11 @@ export const SupabaseBlock: BlockConfig<SupabaseResponse> = {
type: 'dropdown',
layout: 'full',
options: [
{ label: 'Read All Rows', id: 'query' },
{ label: 'Insert Rows', id: 'insert' },
{ label: 'Get Many Rows', id: 'query' },
{ label: 'Get a Row', id: 'get_row' },
{ label: 'Create a Row', id: 'insert' },
{ label: 'Update a Row', id: 'update' },
{ label: 'Delete a Row', id: 'delete' },
],
},
{
@@ -28,6 +34,7 @@ export const SupabaseBlock: BlockConfig<SupabaseResponse> = {
title: 'Project ID',
type: 'short-input',
layout: 'full',
password: true,
placeholder: 'Your Supabase project ID (e.g., jdrkgepadsdopsntdlom)',
},
{
@@ -39,12 +46,13 @@ export const SupabaseBlock: BlockConfig<SupabaseResponse> = {
},
{
id: 'apiKey',
title: 'Client Anon Key',
title: 'Service Role Secret',
type: 'short-input',
layout: 'full',
placeholder: 'Your Supabase client anon key',
placeholder: 'Your Supabase service role secret key',
password: true,
},
// Data input for create/update operations
{
id: 'data',
title: 'Data',
@@ -53,9 +61,75 @@ export const SupabaseBlock: BlockConfig<SupabaseResponse> = {
placeholder: '{\n "column1": "value1",\n "column2": "value2"\n}',
condition: { field: 'operation', value: 'insert' },
},
{
id: 'data',
title: 'Data',
type: 'code',
layout: 'full',
placeholder: '{\n "column1": "value1",\n "column2": "value2"\n}',
condition: { field: 'operation', value: 'update' },
},
// Filter for get_row, update, delete operations (required)
{
id: 'filter',
title: 'Filter (PostgREST syntax)',
type: 'short-input',
layout: 'full',
placeholder: 'id=eq.123',
condition: { field: 'operation', value: 'get_row' },
},
{
id: 'filter',
title: 'Filter (PostgREST syntax)',
type: 'short-input',
layout: 'full',
placeholder: 'id=eq.123',
condition: { field: 'operation', value: 'update' },
},
{
id: 'filter',
title: 'Filter (PostgREST syntax)',
type: 'short-input',
layout: 'full',
placeholder: 'id=eq.123',
condition: { field: 'operation', value: 'delete' },
},
// Optional filter for query operation
{
id: 'filter',
title: 'Filter (PostgREST syntax)',
type: 'short-input',
layout: 'full',
placeholder: 'status=eq.active',
condition: { field: 'operation', value: 'query' },
},
// Optional order by for query operation
{
id: 'orderBy',
title: 'Order By',
type: 'short-input',
layout: 'full',
placeholder: 'column_name (add DESC for descending)',
condition: { field: 'operation', value: 'query' },
},
// Optional limit for query operation
{
id: 'limit',
title: 'Limit',
type: 'short-input',
layout: 'full',
placeholder: '100',
condition: { field: 'operation', value: 'query' },
},
],
tools: {
access: ['supabase_query', 'supabase_insert'],
access: [
'supabase_query',
'supabase_insert',
'supabase_get_row',
'supabase_update',
'supabase_delete',
],
config: {
tool: (params) => {
switch (params.operation) {
@@ -63,29 +137,49 @@ export const SupabaseBlock: BlockConfig<SupabaseResponse> = {
return 'supabase_query'
case 'insert':
return 'supabase_insert'
case 'get_row':
return 'supabase_get_row'
case 'update':
return 'supabase_update'
case 'delete':
return 'supabase_delete'
default:
throw new Error(`Invalid Supabase operation: ${params.operation}`)
}
},
params: (params) => {
const { data, ...rest } = params
const { operation, data, filter, ...rest } = params
// Parse JSON data if it's a string
let parsedData
if (data && typeof data === 'string') {
if (data && typeof data === 'string' && data.trim()) {
try {
parsedData = JSON.parse(data)
} catch (_e) {
throw new Error('Invalid JSON data format')
}
} else {
} else if (data && typeof data === 'object') {
parsedData = data
}
return {
...rest,
data: parsedData,
// Handle filter - just pass through PostgREST syntax
let parsedFilter
if (filter && typeof filter === 'string' && filter.trim()) {
parsedFilter = filter.trim()
}
// Build params object, only including defined values
const result = { ...rest }
if (parsedData !== undefined) {
result.data = parsedData
}
if (parsedFilter !== undefined && parsedFilter !== '') {
result.filter = parsedFilter
}
return result
},
},
},
@@ -94,8 +188,13 @@ export const SupabaseBlock: BlockConfig<SupabaseResponse> = {
projectId: { type: 'string', required: true },
table: { type: 'string', required: true },
apiKey: { type: 'string', required: true },
// Insert operation inputs
data: { type: 'string', required: false },
// Data for insert/update operations
data: { type: 'json', required: false },
// Filter for operations
filter: { type: 'string', required: false },
// Query operation inputs
orderBy: { type: 'string', required: false },
limit: { type: 'number', required: false },
},
outputs: {
message: 'string',
+1 -2
View File
@@ -2,7 +2,7 @@
* Blocks Registry
*
*/
// Import all blocks directly here
import { AgentBlock } from '@/blocks/blocks/agent'
import { AirtableBlock } from '@/blocks/blocks/airtable'
import { ApiBlock } from '@/blocks/blocks/api'
@@ -133,7 +133,6 @@ export const registry: Record<string, BlockConfig> = {
youtube: YouTubeBlock,
}
// Helper functions to access the registry
export const getBlock = (type: string): BlockConfig | undefined => registry[type]
export const getBlocksByCategory = (category: 'blocks' | 'tools' | 'triggers'): BlockConfig[] =>
+4 -3
View File
@@ -993,13 +993,14 @@ export const auth = betterAuth({
scopes: [
// Bot token scopes only - app acts as a bot user
'channels:read',
'channels:history',
'groups:read',
'groups:history',
'chat:write',
'chat:write.public',
'files:read',
'links:read',
'links:write',
'users:read',
'files:write',
'canvases:write',
],
responseType: 'code',
accessType: 'offline',
+1 -1
View File
@@ -140,7 +140,7 @@ const nextConfig: NextConfig = {
},
{
key: 'Content-Security-Policy',
value: `default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval' https://*.google.com https://apis.google.com https://*.vercel-scripts.com https://*.vercel-insights.com https://vercel.live https://*.vercel.live https://vercel.com https://*.vercel.app https://vitals.vercel-insights.com; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; img-src 'self' data: blob: https://*.googleusercontent.com https://*.google.com https://*.atlassian.com https://cdn.discordapp.com https://*.githubusercontent.com; media-src 'self' blob:; font-src 'self' https://fonts.gstatic.com; connect-src 'self' ${env.NEXT_PUBLIC_APP_URL || ''} ${env.OLLAMA_URL || 'http://localhost:11434'} ${env.NEXT_PUBLIC_SOCKET_URL || 'http://localhost:3002'} ${env.NEXT_PUBLIC_SOCKET_URL?.replace('http://', 'ws://').replace('https://', 'wss://') || 'ws://localhost:3002'} https://*.up.railway.app wss://*.up.railway.app https://api.browser-use.com https://*.googleapis.com https://*.amazonaws.com https://*.s3.amazonaws.com https://*.blob.core.windows.net https://*.vercel-insights.com https://vitals.vercel-insights.com https://*.atlassian.com https://vercel.live https://*.vercel.live https://vercel.com https://*.vercel.app wss://*.vercel.app; frame-src https://drive.google.com https://*.google.com; frame-ancestors 'self'; form-action 'self'; base-uri 'self'; object-src 'none'`,
value: `default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval' https://*.google.com https://apis.google.com https://*.vercel-scripts.com https://*.vercel-insights.com https://vercel.live https://*.vercel.live https://vercel.com https://*.vercel.app https://vitals.vercel-insights.com; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; img-src 'self' data: blob: https://*.googleusercontent.com https://*.google.com https://*.atlassian.com https://cdn.discordapp.com https://*.githubusercontent.com; media-src 'self' blob:; font-src 'self' https://fonts.gstatic.com; connect-src 'self' ${env.NEXT_PUBLIC_APP_URL || ''} ${env.OLLAMA_URL || 'http://localhost:11434'} ${env.NEXT_PUBLIC_SOCKET_URL || 'http://localhost:3002'} ${env.NEXT_PUBLIC_SOCKET_URL?.replace('http://', 'ws://').replace('https://', 'wss://') || 'ws://localhost:3002'} https://*.up.railway.app wss://*.up.railway.app https://api.browser-use.com https://api.exa.ai https://api.firecrawl.dev https://*.googleapis.com https://*.amazonaws.com https://*.s3.amazonaws.com https://*.blob.core.windows.net https://*.vercel-insights.com https://vitals.vercel-insights.com https://*.atlassian.com https://*.supabase.co https://vercel.live https://*.vercel.live https://vercel.com https://*.vercel.app wss://*.vercel.app; frame-src https://drive.google.com https://*.google.com; frame-ancestors 'self'; form-action 'self'; base-uri 'self'; object-src 'none'`,
},
],
},
+2 -2
View File
@@ -1,5 +1,5 @@
import type { ToolConfig } from '../types'
import type { AirtableCreateParams, AirtableCreateResponse } from './types'
import type { AirtableCreateParams, AirtableCreateResponse } from '@/tools/airtable/types'
import type { ToolConfig } from '@/tools/types'
export const airtableCreateRecordsTool: ToolConfig<AirtableCreateParams, AirtableCreateResponse> = {
id: 'airtable_create_records',
+2 -2
View File
@@ -1,5 +1,5 @@
import type { ToolConfig } from '../types'
import type { AirtableGetParams, AirtableGetResponse } from './types'
import type { AirtableGetParams, AirtableGetResponse } from '@/tools/airtable/types'
import type { ToolConfig } from '@/tools/types'
// import { logger } from '@/utils/logger' // Removed logger due to import issues
+5 -5
View File
@@ -1,8 +1,8 @@
import { airtableCreateRecordsTool } from './create_records'
import { airtableGetRecordTool } from './get_record'
import { airtableListRecordsTool } from './list_records'
import { airtableUpdateMultipleRecordsTool } from './update_multiple_records'
import { airtableUpdateRecordTool } from './update_record'
import { airtableCreateRecordsTool } from '@/tools/airtable/create_records'
import { airtableGetRecordTool } from '@/tools/airtable/get_record'
import { airtableListRecordsTool } from '@/tools/airtable/list_records'
import { airtableUpdateMultipleRecordsTool } from '@/tools/airtable/update_multiple_records'
import { airtableUpdateRecordTool } from '@/tools/airtable/update_record'
export {
airtableCreateRecordsTool,
+2 -2
View File
@@ -1,5 +1,5 @@
import type { ToolConfig } from '../types'
import type { AirtableListParams, AirtableListResponse } from './types'
import type { AirtableListParams, AirtableListResponse } from '@/tools/airtable/types'
import type { ToolConfig } from '@/tools/types'
export const airtableListRecordsTool: ToolConfig<AirtableListParams, AirtableListResponse> = {
id: 'airtable_list_records',
+1 -1
View File
@@ -1,4 +1,4 @@
import type { ToolResponse } from '../types'
import type { ToolResponse } from '@/tools/types'
// Common types
export interface AirtableRecord {
@@ -1,5 +1,8 @@
import type { ToolConfig } from '../types'
import type { AirtableUpdateMultipleParams, AirtableUpdateMultipleResponse } from './types'
import type {
AirtableUpdateMultipleParams,
AirtableUpdateMultipleResponse,
} from '@/tools/airtable/types'
import type { ToolConfig } from '@/tools/types'
// import { logger } from '@/utils/logger' // Removed logger due to import issues
+2 -2
View File
@@ -1,5 +1,5 @@
import type { ToolConfig } from '../types'
import type { AirtableUpdateParams, AirtableUpdateResponse } from './types'
import type { AirtableUpdateParams, AirtableUpdateResponse } from '@/tools/airtable/types'
import type { ToolConfig } from '@/tools/types'
// import { logger } from '@/utils/logger' // Removed logger due to import issues
+1 -1
View File
@@ -1,3 +1,3 @@
import { runTaskTool } from './run_task'
import { runTaskTool } from '@/tools/browser_use/run_task'
export const browserUseRunTaskTool = runTaskTool
+2 -2
View File
@@ -1,6 +1,6 @@
import { createLogger } from '@/lib/logs/console-logger'
import type { ToolConfig } from '../types'
import type { BrowserUseRunTaskParams, BrowserUseRunTaskResponse } from './types'
import type { BrowserUseRunTaskParams, BrowserUseRunTaskResponse } from '@/tools/browser_use/types'
import type { ToolConfig } from '@/tools/types'
const logger = createLogger('BrowserUseTool')
+1 -1
View File
@@ -1,4 +1,4 @@
import type { ToolResponse } from '../types'
import type { ToolResponse } from '@/tools/types'
export interface BrowserUseRunTaskParams {
task: string
+1 -1
View File
@@ -1,3 +1,3 @@
import { clayPopulateTool } from './populate'
import { clayPopulateTool } from '@/tools/clay/populate'
export { clayPopulateTool }
+2 -2
View File
@@ -1,5 +1,5 @@
import type { ToolConfig } from '../types'
import type { ClayPopulateParams, ClayPopulateResponse } from './types'
import type { ClayPopulateParams, ClayPopulateResponse } from '@/tools/clay/types'
import type { ToolConfig } from '@/tools/types'
export const clayPopulateTool: ToolConfig<ClayPopulateParams, ClayPopulateResponse> = {
id: 'clay_populate',
+1 -1
View File
@@ -1,4 +1,4 @@
import type { ToolResponse } from '../types'
import type { ToolResponse } from '@/tools/types'
export interface ClayPopulateParams {
webhookURL: string
+2 -2
View File
@@ -1,5 +1,5 @@
import { confluenceRetrieveTool } from './retrieve'
import { confluenceUpdateTool } from './update'
import { confluenceRetrieveTool } from '@/tools/confluence/retrieve'
import { confluenceUpdateTool } from '@/tools/confluence/update'
export { confluenceRetrieveTool }
export { confluenceUpdateTool }
+2 -2
View File
@@ -1,5 +1,5 @@
import type { ToolConfig } from '../types'
import type { ConfluenceRetrieveParams, ConfluenceRetrieveResponse } from './types'
import type { ConfluenceRetrieveParams, ConfluenceRetrieveResponse } from '@/tools/confluence/types'
import type { ToolConfig } from '@/tools/types'
export const confluenceRetrieveTool: ToolConfig<
ConfluenceRetrieveParams,
+1 -1
View File
@@ -1,4 +1,4 @@
import type { ToolResponse } from '../types'
import type { ToolResponse } from '@/tools/types'
export interface ConfluenceRetrieveParams {
accessToken: string
+2 -2
View File
@@ -1,5 +1,5 @@
import type { ToolConfig } from '../types'
import type { ConfluenceUpdateParams, ConfluenceUpdateResponse } from './types'
import type { ConfluenceUpdateParams, ConfluenceUpdateResponse } from '@/tools/confluence/types'
import type { ToolConfig } from '@/tools/types'
export const confluenceUpdateTool: ToolConfig<ConfluenceUpdateParams, ConfluenceUpdateResponse> = {
id: 'confluence_update',
+2 -2
View File
@@ -1,11 +1,11 @@
import { createLogger } from '@/lib/logs/console-logger'
import type { ToolConfig } from '../types'
import type {
DiscordAPIError,
DiscordGetMessagesParams,
DiscordGetMessagesResponse,
DiscordMessage,
} from './types'
} from '@/tools/discord/types'
import type { ToolConfig } from '@/tools/types'
const logger = createLogger('DiscordGetMessages')
+2 -2
View File
@@ -1,11 +1,11 @@
import { createLogger } from '@/lib/logs/console-logger'
import type { ToolConfig } from '../types'
import type {
DiscordAPIError,
DiscordGetServerParams,
DiscordGetServerResponse,
DiscordGuild,
} from './types'
} from '@/tools/discord/types'
import type { ToolConfig } from '@/tools/types'
const logger = createLogger('DiscordGetServer')
+2 -2
View File
@@ -1,11 +1,11 @@
import { createLogger } from '@/lib/logs/console-logger'
import type { ToolConfig } from '../types'
import type {
DiscordAPIError,
DiscordGetUserParams,
DiscordGetUserResponse,
DiscordUser,
} from './types'
} from '@/tools/discord/types'
import type { ToolConfig } from '@/tools/types'
const logger = createLogger('DiscordGetUser')
+4 -4
View File
@@ -1,6 +1,6 @@
import { discordGetMessagesTool } from './get_messages'
import { discordGetServerTool } from './get_server'
import { discordGetUserTool } from './get_user'
import { discordSendMessageTool } from './send_message'
import { discordGetMessagesTool } from '@/tools/discord/get_messages'
import { discordGetServerTool } from '@/tools/discord/get_server'
import { discordGetUserTool } from '@/tools/discord/get_user'
import { discordSendMessageTool } from '@/tools/discord/send_message'
export { discordSendMessageTool, discordGetMessagesTool, discordGetServerTool, discordGetUserTool }
+2 -2
View File
@@ -1,11 +1,11 @@
import { createLogger } from '@/lib/logs/console-logger'
import type { ToolConfig } from '../types'
import type {
DiscordAPIError,
DiscordMessage,
DiscordSendMessageParams,
DiscordSendMessageResponse,
} from './types'
} from '@/tools/discord/types'
import type { ToolConfig } from '@/tools/types'
const logger = createLogger('DiscordSendMessage')
+1 -1
View File
@@ -1,4 +1,4 @@
import type { ToolConfig, ToolResponse } from '../types'
import type { ToolConfig, ToolResponse } from '@/tools/types'
interface DocsSearchParams {
query: string
+1 -1
View File
@@ -1,3 +1,3 @@
import { elevenLabsTtsTool } from './tts'
import { elevenLabsTtsTool } from '@/tools/elevenlabs/tts'
export { elevenLabsTtsTool }
+2 -2
View File
@@ -1,5 +1,5 @@
import type { ToolConfig } from '../types'
import type { ExaAnswerParams, ExaAnswerResponse } from './types'
import type { ExaAnswerParams, ExaAnswerResponse } from '@/tools/exa/types'
import type { ToolConfig } from '@/tools/types'
export const answerTool: ToolConfig<ExaAnswerParams, ExaAnswerResponse> = {
id: 'exa_answer',
+2 -2
View File
@@ -1,5 +1,5 @@
import type { ToolConfig } from '../types'
import type { ExaFindSimilarLinksParams, ExaFindSimilarLinksResponse } from './types'
import type { ExaFindSimilarLinksParams, ExaFindSimilarLinksResponse } from '@/tools/exa/types'
import type { ToolConfig } from '@/tools/types'
export const findSimilarLinksTool: ToolConfig<
ExaFindSimilarLinksParams,
+2 -2
View File
@@ -1,5 +1,5 @@
import type { ToolConfig } from '../types'
import type { ExaGetContentsParams, ExaGetContentsResponse } from './types'
import type { ExaGetContentsParams, ExaGetContentsResponse } from '@/tools/exa/types'
import type { ToolConfig } from '@/tools/types'
export const getContentsTool: ToolConfig<ExaGetContentsParams, ExaGetContentsResponse> = {
id: 'exa_get_contents',
+6 -4
View File
@@ -1,9 +1,11 @@
import { answerTool } from './answer'
import { findSimilarLinksTool } from './find_similar_links'
import { getContentsTool } from './get_contents'
import { searchTool } from './search'
import { answerTool } from '@/tools/exa/answer'
import { findSimilarLinksTool } from '@/tools/exa/find_similar_links'
import { getContentsTool } from '@/tools/exa/get_contents'
import { researchTool } from '@/tools/exa/research'
import { searchTool } from '@/tools/exa/search'
export const exaAnswerTool = answerTool
export const exaFindSimilarLinksTool = findSimilarLinksTool
export const exaGetContentsTool = getContentsTool
export const exaSearchTool = searchTool
export const exaResearchTool = researchTool
+177
View File
@@ -0,0 +1,177 @@
import { createLogger } from '@/lib/logs/console-logger'
import type { ExaResearchParams, ExaResearchResponse } from '@/tools/exa/types'
import type { ToolConfig } from '@/tools/types'
const logger = createLogger('ExaResearchTool')
const POLL_INTERVAL_MS = 5000 // 5 seconds between polls
const MAX_POLL_TIME_MS = 300000 // 5 minutes maximum polling time
export const researchTool: ToolConfig<ExaResearchParams, ExaResearchResponse> = {
id: 'exa_research',
name: 'Exa Research',
description:
'Perform comprehensive research using AI to generate detailed reports with citations',
version: '1.0.0',
params: {
query: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Research query or topic',
},
includeText: {
type: 'boolean',
required: false,
visibility: 'user-only',
description: 'Include full text content in results',
},
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Exa AI API Key',
},
},
request: {
url: 'https://api.exa.ai/research/v0/tasks',
method: 'POST',
isInternalRoute: false,
headers: (params) => ({
'Content-Type': 'application/json',
'x-api-key': params.apiKey,
}),
body: (params) => {
const body: any = {
instructions: params.query,
model: 'exa-research',
output: {
schema: {
type: 'object',
properties: {
results: {
type: 'array',
items: {
type: 'object',
properties: {
title: { type: 'string' },
url: { type: 'string' },
summary: { type: 'string' },
text: { type: 'string' },
publishedDate: { type: 'string' },
author: { type: 'string' },
score: { type: 'number' },
},
},
},
},
required: ['results'],
},
},
}
return body
},
},
transformResponse: async (response: Response) => {
const data = await response.json()
if (!response.ok) {
throw new Error(data.message || data.error || 'Failed to create research task')
}
return {
success: true,
output: {
taskId: data.id,
research: [],
},
}
},
postProcess: async (result, params) => {
if (!result.success) {
return result
}
const taskId = result.output.taskId
logger.info(`Exa research task ${taskId} created, polling for completion...`)
let elapsedTime = 0
while (elapsedTime < MAX_POLL_TIME_MS) {
try {
const statusResponse = await fetch(`https://api.exa.ai/research/v0/tasks/${taskId}`, {
method: 'GET',
headers: {
'x-api-key': params.apiKey,
},
})
if (!statusResponse.ok) {
throw new Error(`Failed to get task status: ${statusResponse.statusText}`)
}
const taskData = await statusResponse.json()
logger.info(`Exa research task ${taskId} status: ${taskData.status}`)
if (taskData.status === 'completed') {
result.output = {
research: taskData.data?.results || [
{
title: 'Research Complete',
url: '',
summary: taskData.data || 'Research completed successfully',
text: undefined,
publishedDate: undefined,
author: undefined,
score: 1.0,
},
],
}
return result
}
if (taskData.status === 'failed') {
return {
...result,
success: false,
error: `Research task failed: ${taskData.error || 'Unknown error'}`,
}
}
await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL_MS))
elapsedTime += POLL_INTERVAL_MS
} catch (error: any) {
logger.error('Error polling for research task status:', {
message: error.message || 'Unknown error',
taskId,
})
return {
...result,
success: false,
error: `Error polling for research task status: ${error.message || 'Unknown error'}`,
}
}
}
logger.warn(
`Research task ${taskId} did not complete within the maximum polling time (${MAX_POLL_TIME_MS / 1000}s)`
)
return {
...result,
success: false,
error: `Research task did not complete within the maximum polling time (${MAX_POLL_TIME_MS / 1000}s)`,
}
},
transformError: (error) => {
const errorMessage = error?.message || ''
if (errorMessage.includes('401')) {
return new Error('Invalid API key. Please check your Exa AI API key.')
}
if (errorMessage.includes('429')) {
return new Error('Rate limit exceeded. Please try again later.')
}
return error
},
}
+2 -2
View File
@@ -1,5 +1,5 @@
import type { ToolConfig } from '../types'
import type { ExaSearchParams, ExaSearchResponse } from './types'
import type { ExaSearchParams, ExaSearchResponse } from '@/tools/exa/types'
import type { ToolConfig } from '@/tools/types'
export const searchTool: ToolConfig<ExaSearchParams, ExaSearchResponse> = {
id: 'exa_search',
+23 -1
View File
@@ -1,5 +1,5 @@
// Common types for Exa AI tools
import type { ToolResponse } from '../types'
import type { ToolResponse } from '@/tools/types'
// Common parameters for all Exa AI tools
export interface ExaBaseParams {
@@ -89,8 +89,30 @@ export interface ExaAnswerResponse extends ToolResponse {
}
}
// Research tool types
export interface ExaResearchParams extends ExaBaseParams {
query: string
includeText?: boolean
}
export interface ExaResearchResponse extends ToolResponse {
output: {
taskId?: string
research: {
title: string
url: string
summary: string
text?: string
publishedDate?: string
author?: string
score: number
}[]
}
}
export type ExaResponse =
| ExaSearchResponse
| ExaGetContentsResponse
| ExaFindSimilarLinksResponse
| ExaAnswerResponse
| ExaResearchResponse
+1 -1
View File
@@ -1,3 +1,3 @@
import { fileParserTool } from './parser'
import { fileParserTool } from '@/tools/file/parser'
export const fileParseTool = fileParserTool
+2 -2
View File
@@ -1,11 +1,11 @@
import { createLogger } from '@/lib/logs/console-logger'
import type { ToolConfig } from '../types'
import type {
FileParseResult,
FileParserInput,
FileParserOutput,
FileParserOutputData,
} from './types'
} from '@/tools/file/types'
import type { ToolConfig } from '@/tools/types'
const logger = createLogger('FileParserTool')
+1 -1
View File
@@ -1,4 +1,4 @@
import type { ToolResponse } from '../types'
import type { ToolResponse } from '@/tools/types'
export interface FileParserInput {
filePath: string | string[]
+156
View File
@@ -0,0 +1,156 @@
import { createLogger } from '@/lib/logs/console-logger'
import type { FirecrawlCrawlParams, FirecrawlCrawlResponse } from '@/tools/firecrawl/types'
import type { ToolConfig } from '@/tools/types'
const logger = createLogger('FirecrawlCrawlTool')
const POLL_INTERVAL_MS = 5000 // 5 seconds between polls
const MAX_POLL_TIME_MS = 300000 // 5 minutes maximum polling time
export const crawlTool: ToolConfig<FirecrawlCrawlParams, FirecrawlCrawlResponse> = {
id: 'firecrawl_crawl',
name: 'Firecrawl Crawl',
description: 'Crawl entire websites and extract structured content from all accessible pages',
version: '1.0.0',
params: {
url: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The website URL to crawl',
},
limit: {
type: 'number',
required: false,
visibility: 'user-only',
description: 'Maximum number of pages to crawl (default: 100)',
},
onlyMainContent: {
type: 'boolean',
required: false,
visibility: 'user-only',
description: 'Extract only main content from pages',
},
apiKey: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Firecrawl API Key',
},
},
request: {
url: 'https://api.firecrawl.dev/v1/crawl',
method: 'POST',
isInternalRoute: false,
headers: (params) => ({
'Content-Type': 'application/json',
Authorization: `Bearer ${params.apiKey}`,
}),
body: (params) => ({
url: params.url,
limit: Number(params.limit) || 100,
scrapeOptions: {
formats: ['markdown'],
onlyMainContent: params.onlyMainContent || false,
},
}),
},
transformResponse: async (response: Response) => {
const data = await response.json()
if (!response.ok) {
throw new Error(data.error || data.message || 'Failed to create crawl job')
}
return {
success: true,
output: {
jobId: data.jobId || data.id,
pages: [],
total: 0,
creditsUsed: 0,
},
}
},
postProcess: async (result, params) => {
if (!result.success) {
return result
}
const jobId = result.output.jobId
logger.info(`Firecrawl crawl job ${jobId} created, polling for completion...`)
let elapsedTime = 0
while (elapsedTime < MAX_POLL_TIME_MS) {
try {
const statusResponse = await fetch(`/api/tools/firecrawl/crawl/${jobId}`, {
method: 'GET',
headers: {
Authorization: `Bearer ${params.apiKey}`,
},
})
if (!statusResponse.ok) {
throw new Error(`Failed to get crawl status: ${statusResponse.statusText}`)
}
const crawlData = await statusResponse.json()
logger.info(`Firecrawl crawl job ${jobId} status: ${crawlData.status}`)
if (crawlData.status === 'completed') {
result.output = {
pages: crawlData.data || [],
total: crawlData.total || 0,
creditsUsed: crawlData.creditsUsed || 0,
}
return result
}
if (crawlData.status === 'failed') {
return {
...result,
success: false,
error: `Crawl job failed: ${crawlData.error || 'Unknown error'}`,
}
}
await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL_MS))
elapsedTime += POLL_INTERVAL_MS
} catch (error: any) {
logger.error('Error polling for crawl job status:', {
message: error.message || 'Unknown error',
jobId,
})
return {
...result,
success: false,
error: `Error polling for crawl job status: ${error.message || 'Unknown error'}`,
}
}
}
logger.warn(
`Crawl job ${jobId} did not complete within the maximum polling time (${MAX_POLL_TIME_MS / 1000}s)`
)
return {
...result,
success: false,
error: `Crawl job did not complete within the maximum polling time (${MAX_POLL_TIME_MS / 1000}s)`,
}
},
transformError: (error) => {
const errorMessage = error?.message || ''
if (errorMessage.includes('401')) {
return new Error('Invalid API key. Please check your Firecrawl API key.')
}
if (errorMessage.includes('429')) {
return new Error('Rate limit exceeded. Please try again later.')
}
if (errorMessage.includes('402')) {
return new Error('Insufficient credits. Please check your Firecrawl account.')
}
return error
},
}
+4 -3
View File
@@ -1,4 +1,5 @@
import { scrapeTool } from './scrape'
import { searchTool } from './search'
import { crawlTool } from '@/tools/firecrawl/crawl'
import { scrapeTool } from '@/tools/firecrawl/scrape'
import { searchTool } from '@/tools/firecrawl/search'
export { scrapeTool, searchTool }
export { scrapeTool, searchTool, crawlTool }
+2 -2
View File
@@ -1,5 +1,5 @@
import type { ToolConfig } from '../types'
import type { ScrapeParams, ScrapeResponse } from './types'
import type { ScrapeParams, ScrapeResponse } from '@/tools/firecrawl/types'
import type { ToolConfig } from '@/tools/types'
export const scrapeTool: ToolConfig<ScrapeParams, ScrapeResponse> = {
id: 'firecrawl_scrape',
+2 -2
View File
@@ -1,5 +1,5 @@
import type { ToolConfig } from '../types'
import type { SearchParams, SearchResponse } from './types'
import type { SearchParams, SearchResponse } from '@/tools/firecrawl/types'
import type { ToolConfig } from '@/tools/types'
export const searchTool: ToolConfig<SearchParams, SearchResponse> = {
id: 'firecrawl_search',
+28 -2
View File
@@ -1,4 +1,4 @@
import type { ToolResponse } from '../types'
import type { ToolResponse } from '@/tools/types'
export interface ScrapeParams {
apiKey: string
@@ -14,6 +14,13 @@ export interface SearchParams {
query: string
}
export interface FirecrawlCrawlParams {
apiKey: string
url: string
limit?: number
onlyMainContent?: boolean
}
export interface ScrapeResponse extends ToolResponse {
output: {
markdown: string
@@ -59,4 +66,23 @@ export interface SearchResponse extends ToolResponse {
}
}
export type FirecrawlResponse = ScrapeResponse | SearchResponse
export interface FirecrawlCrawlResponse extends ToolResponse {
output: {
jobId?: string
pages: Array<{
markdown: string
html?: string
metadata: {
title: string
description: string
language: string
sourceURL: string
statusCode: number
}
}>
total: number
creditsUsed: number
}
}
export type FirecrawlResponse = ScrapeResponse | SearchResponse | FirecrawlCrawlResponse
+2 -2
View File
@@ -7,8 +7,8 @@
* which runs JavaScript code in a secure sandbox.
*/
import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest'
import { ToolTester } from '../__test-utils__/test-tools'
import { functionExecuteTool } from './execute'
import { ToolTester } from '@/tools/__test-utils__/test-tools'
import { functionExecuteTool } from '@/tools/function/execute'
describe('Function Execute Tool', () => {
let tester: ToolTester
+2 -2
View File
@@ -1,5 +1,5 @@
import type { ToolConfig } from '../types'
import type { CodeExecutionInput, CodeExecutionOutput } from './types'
import type { CodeExecutionInput, CodeExecutionOutput } from '@/tools/function/types'
import type { ToolConfig } from '@/tools/types'
const DEFAULT_TIMEOUT = 10000 // 10 seconds
+1 -1
View File
@@ -1,3 +1,3 @@
import { functionExecuteTool } from './execute'
import { functionExecuteTool } from '@/tools/function/execute'
export { functionExecuteTool }
+1 -1
View File
@@ -1,4 +1,4 @@
import type { ToolResponse } from '../types'
import type { ToolResponse } from '@/tools/types'
export interface CodeExecutionInput {
code: Array<{ content: string; id: string }> | string
+2 -2
View File
@@ -1,5 +1,5 @@
import type { ToolConfig } from '../types'
import type { CreateCommentParams, CreateCommentResponse } from './types'
import type { CreateCommentParams, CreateCommentResponse } from '@/tools/github/types'
import type { ToolConfig } from '@/tools/types'
export const commentTool: ToolConfig<CreateCommentParams, CreateCommentResponse> = {
id: 'github_comment',
+4 -4
View File
@@ -1,7 +1,7 @@
import { commentTool } from './comment'
import { latestCommitTool } from './latest_commit'
import { prTool } from './pr'
import { repoInfoTool } from './repo_info'
import { commentTool } from '@/tools/github/comment'
import { latestCommitTool } from '@/tools/github/latest_commit'
import { prTool } from '@/tools/github/pr'
import { repoInfoTool } from '@/tools/github/repo_info'
export const githubCommentTool = commentTool
export const githubLatestCommitTool = latestCommitTool
+2 -2
View File
@@ -1,5 +1,5 @@
import type { ToolConfig } from '../types'
import type { LatestCommitParams, LatestCommitResponse } from './types'
import type { LatestCommitParams, LatestCommitResponse } from '@/tools/github/types'
import type { ToolConfig } from '@/tools/types'
export const latestCommitTool: ToolConfig<LatestCommitParams, LatestCommitResponse> = {
id: 'github_latest_commit',
+2 -2
View File
@@ -1,5 +1,5 @@
import type { ToolConfig } from '../types'
import type { PROperationParams, PullRequestResponse } from './types'
import type { PROperationParams, PullRequestResponse } from '@/tools/github/types'
import type { ToolConfig } from '@/tools/types'
export const prTool: ToolConfig<PROperationParams, PullRequestResponse> = {
id: 'github_pr',
+2 -2
View File
@@ -1,5 +1,5 @@
import type { ToolConfig } from '../types'
import type { BaseGitHubParams, RepoInfoResponse } from './types'
import type { BaseGitHubParams, RepoInfoResponse } from '@/tools/github/types'
import type { ToolConfig } from '@/tools/types'
export const repoInfoTool: ToolConfig<BaseGitHubParams, RepoInfoResponse> = {
id: 'github_repo_info',
+1 -1
View File
@@ -1,4 +1,4 @@
import type { ToolResponse } from '../types'
import type { ToolResponse } from '@/tools/types'
// Base parameters shared by all GitHub operations
export interface BaseGitHubParams {
+2 -2
View File
@@ -1,5 +1,5 @@
import type { ToolConfig } from '../types'
import type { GmailSendParams, GmailToolResponse } from './types'
import type { GmailSendParams, GmailToolResponse } from '@/tools/gmail/types'
import type { ToolConfig } from '@/tools/types'
const GMAIL_API_BASE = 'https://gmail.googleapis.com/gmail/v1/users/me'
+4 -4
View File
@@ -1,6 +1,6 @@
import { gmailDraftTool } from './draft'
import { gmailReadTool } from './read'
import { gmailSearchTool } from './search'
import { gmailSendTool } from './send'
import { gmailDraftTool } from '@/tools/gmail/draft'
import { gmailReadTool } from '@/tools/gmail/read'
import { gmailSearchTool } from '@/tools/gmail/search'
import { gmailSendTool } from '@/tools/gmail/send'
export { gmailSendTool, gmailReadTool, gmailSearchTool, gmailDraftTool }
+2 -2
View File
@@ -1,5 +1,5 @@
import type { ToolConfig } from '../types'
import type { GmailMessage, GmailReadParams, GmailToolResponse } from './types'
import type { GmailMessage, GmailReadParams, GmailToolResponse } from '@/tools/gmail/types'
import type { ToolConfig } from '@/tools/types'
const GMAIL_API_BASE = 'https://gmail.googleapis.com/gmail/v1/users/me'
+2 -2
View File
@@ -1,5 +1,5 @@
import type { ToolConfig } from '../types'
import type { GmailSearchParams, GmailToolResponse } from './types'
import type { GmailSearchParams, GmailToolResponse } from '@/tools/gmail/types'
import type { ToolConfig } from '@/tools/types'
const GMAIL_API_BASE = 'https://gmail.googleapis.com/gmail/v1/users/me'
+2 -2
View File
@@ -1,5 +1,5 @@
import type { ToolConfig } from '../types'
import type { GmailSendParams, GmailToolResponse } from './types'
import type { GmailSendParams, GmailToolResponse } from '@/tools/gmail/types'
import type { ToolConfig } from '@/tools/types'
const GMAIL_API_BASE = 'https://gmail.googleapis.com/gmail/v1/users/me'
+1 -1
View File
@@ -1,4 +1,4 @@
import type { ToolResponse } from '../types'
import type { ToolResponse } from '@/tools/types'
// Base parameters shared by all operations
interface BaseGmailParams {
+1 -1
View File
@@ -1,3 +1,3 @@
import { searchTool } from './search'
import { searchTool } from '@/tools/google/search'
export { searchTool }
+2 -2
View File
@@ -1,5 +1,5 @@
import type { ToolConfig } from '../types'
import type { GoogleSearchParams, GoogleSearchResponse } from './types'
import type { GoogleSearchParams, GoogleSearchResponse } from '@/tools/google/types'
import type { ToolConfig } from '@/tools/types'
export const searchTool: ToolConfig<GoogleSearchParams, GoogleSearchResponse> = {
id: 'google_search',
+1 -1
View File
@@ -1,4 +1,4 @@
import type { ToolResponse } from '../types'
import type { ToolResponse } from '@/tools/types'
export interface GoogleSearchParams {
query: string
+2 -2
View File
@@ -1,11 +1,11 @@
import type { ToolConfig } from '../types'
import {
CALENDAR_API_BASE,
type GoogleCalendarApiEventResponse,
type GoogleCalendarCreateParams,
type GoogleCalendarCreateResponse,
type GoogleCalendarEventRequestBody,
} from './types'
} from '@/tools/google_calendar/types'
import type { ToolConfig } from '@/tools/types'
export const createTool: ToolConfig<GoogleCalendarCreateParams, GoogleCalendarCreateResponse> = {
id: 'google_calendar_create',
+2 -2
View File
@@ -1,10 +1,10 @@
import type { ToolConfig } from '../types'
import {
CALENDAR_API_BASE,
type GoogleCalendarApiEventResponse,
type GoogleCalendarGetParams,
type GoogleCalendarGetResponse,
} from './types'
} from '@/tools/google_calendar/types'
import type { ToolConfig } from '@/tools/types'
export const getTool: ToolConfig<GoogleCalendarGetParams, GoogleCalendarGetResponse> = {
id: 'google_calendar_get',
+5 -5
View File
@@ -1,8 +1,8 @@
import { createTool } from './create'
import { getTool } from './get'
import { inviteTool } from './invite'
import { listTool } from './list'
import { quickAddTool } from './quick_add'
import { createTool } from '@/tools/google_calendar/create'
import { getTool } from '@/tools/google_calendar/get'
import { inviteTool } from '@/tools/google_calendar/invite'
import { listTool } from '@/tools/google_calendar/list'
import { quickAddTool } from '@/tools/google_calendar/quick_add'
export const googleCalendarCreateTool = createTool
export const googleCalendarGetTool = getTool
+2 -2
View File
@@ -1,9 +1,9 @@
import type { ToolConfig } from '../types'
import {
CALENDAR_API_BASE,
type GoogleCalendarInviteParams,
type GoogleCalendarInviteResponse,
} from './types'
} from '@/tools/google_calendar/types'
import type { ToolConfig } from '@/tools/types'
export const inviteTool: ToolConfig<GoogleCalendarInviteParams, GoogleCalendarInviteResponse> = {
id: 'google_calendar_invite',
+2 -2
View File
@@ -1,11 +1,11 @@
import type { ToolConfig } from '../types'
import {
CALENDAR_API_BASE,
type GoogleCalendarApiEventResponse,
type GoogleCalendarApiListResponse,
type GoogleCalendarListParams,
type GoogleCalendarListResponse,
} from './types'
} from '@/tools/google_calendar/types'
import type { ToolConfig } from '@/tools/types'
export const listTool: ToolConfig<GoogleCalendarListParams, GoogleCalendarListResponse> = {
id: 'google_calendar_list',
+2 -2
View File
@@ -1,9 +1,9 @@
import type { ToolConfig } from '../types'
import {
CALENDAR_API_BASE,
type GoogleCalendarQuickAddParams,
type GoogleCalendarQuickAddResponse,
} from './types'
} from '@/tools/google_calendar/types'
import type { ToolConfig } from '@/tools/types'
export const quickAddTool: ToolConfig<
GoogleCalendarQuickAddParams,
+1 -1
View File
@@ -1,4 +1,4 @@
import type { ToolResponse } from '../types'
import type { ToolResponse } from '@/tools/types'
export const CALENDAR_API_BASE = 'https://www.googleapis.com/calendar/v3'
+2 -2
View File
@@ -1,9 +1,9 @@
import type { ToolConfig } from '../types'
import {
CALENDAR_API_BASE,
type GoogleCalendarToolResponse,
type GoogleCalendarUpdateParams,
} from './types'
} from '@/tools/google_calendar/types'
import type { ToolConfig } from '@/tools/types'
export const updateTool: ToolConfig<GoogleCalendarUpdateParams, GoogleCalendarToolResponse> = {
id: 'google_calendar_update',
+2 -2
View File
@@ -1,6 +1,6 @@
import { createLogger } from '@/lib/logs/console-logger'
import type { ToolConfig } from '../types'
import type { GoogleDocsCreateResponse, GoogleDocsToolParams } from './types'
import type { GoogleDocsCreateResponse, GoogleDocsToolParams } from '@/tools/google_docs/types'
import type { ToolConfig } from '@/tools/types'
const logger = createLogger('GoogleDocsCreateTool')
+3 -3
View File
@@ -1,6 +1,6 @@
import { createTool } from './create'
import { readTool } from './read'
import { writeTool } from './write'
import { createTool } from '@/tools/google_docs/create'
import { readTool } from '@/tools/google_docs/read'
import { writeTool } from '@/tools/google_docs/write'
export const googleDocsReadTool = readTool
export const googleDocsWriteTool = writeTool
+2 -2
View File
@@ -1,5 +1,5 @@
import type { ToolConfig } from '../types'
import type { GoogleDocsReadResponse, GoogleDocsToolParams } from './types'
import type { GoogleDocsReadResponse, GoogleDocsToolParams } from '@/tools/google_docs/types'
import type { ToolConfig } from '@/tools/types'
export const readTool: ToolConfig<GoogleDocsToolParams, GoogleDocsReadResponse> = {
id: 'google_docs_read',
+1 -1
View File
@@ -1,4 +1,4 @@
import type { ToolResponse } from '../types'
import type { ToolResponse } from '@/tools/types'
export interface GoogleDocsMetadata {
documentId: string
+2 -2
View File
@@ -1,5 +1,5 @@
import type { ToolConfig } from '../types'
import type { GoogleDocsToolParams, GoogleDocsWriteResponse } from './types'
import type { GoogleDocsToolParams, GoogleDocsWriteResponse } from '@/tools/google_docs/types'
import type { ToolConfig } from '@/tools/types'
export const writeTool: ToolConfig<GoogleDocsToolParams, GoogleDocsWriteResponse> = {
id: 'google_docs_write',
+2 -2
View File
@@ -1,5 +1,5 @@
import type { ToolConfig } from '../types'
import type { GoogleDriveToolParams, GoogleDriveUploadResponse } from './types'
import type { GoogleDriveToolParams, GoogleDriveUploadResponse } from '@/tools/google_drive/types'
import type { ToolConfig } from '@/tools/types'
export const createFolderTool: ToolConfig<GoogleDriveToolParams, GoogleDriveUploadResponse> = {
id: 'google_drive_create_folder',
+6 -3
View File
@@ -1,7 +1,10 @@
import { createLogger } from '@/lib/logs/console-logger'
import type { ToolConfig } from '../types'
import type { GoogleDriveGetContentResponse, GoogleDriveToolParams } from './types'
import { DEFAULT_EXPORT_FORMATS, GOOGLE_WORKSPACE_MIME_TYPES } from './utils'
import type {
GoogleDriveGetContentResponse,
GoogleDriveToolParams,
} from '@/tools/google_drive/types'
import { DEFAULT_EXPORT_FORMATS, GOOGLE_WORKSPACE_MIME_TYPES } from '@/tools/google_drive/utils'
import type { ToolConfig } from '@/tools/types'
const logger = createLogger('GoogleDriveGetContentTool')
+4 -4
View File
@@ -1,7 +1,7 @@
import { createFolderTool } from './create_folder'
import { getContentTool } from './get_content'
import { listTool } from './list'
import { uploadTool } from './upload'
import { createFolderTool } from '@/tools/google_drive/create_folder'
import { getContentTool } from '@/tools/google_drive/get_content'
import { listTool } from '@/tools/google_drive/list'
import { uploadTool } from '@/tools/google_drive/upload'
export const googleDriveCreateFolderTool = createFolderTool
export const googleDriveGetContentTool = getContentTool
+2 -2
View File
@@ -1,5 +1,5 @@
import type { ToolConfig } from '../types'
import type { GoogleDriveListResponse, GoogleDriveToolParams } from './types'
import type { GoogleDriveListResponse, GoogleDriveToolParams } from '@/tools/google_drive/types'
import type { ToolConfig } from '@/tools/types'
export const listTool: ToolConfig<GoogleDriveToolParams, GoogleDriveListResponse> = {
id: 'google_drive_list',
+1 -1
View File
@@ -1,4 +1,4 @@
import type { ToolResponse } from '../types'
import type { ToolResponse } from '@/tools/types'
export interface GoogleDriveFile {
id: string
+3 -3
View File
@@ -1,7 +1,7 @@
import { createLogger } from '@/lib/logs/console-logger'
import type { ToolConfig } from '../types'
import type { GoogleDriveToolParams, GoogleDriveUploadResponse } from './types'
import { GOOGLE_WORKSPACE_MIME_TYPES, SOURCE_MIME_TYPES } from './utils'
import type { GoogleDriveToolParams, GoogleDriveUploadResponse } from '@/tools/google_drive/types'
import { GOOGLE_WORKSPACE_MIME_TYPES, SOURCE_MIME_TYPES } from '@/tools/google_drive/utils'
import type { ToolConfig } from '@/tools/types'
const logger = createLogger('GoogleDriveUploadTool')
+5 -2
View File
@@ -1,5 +1,8 @@
import type { ToolConfig } from '../types'
import type { GoogleSheetsAppendResponse, GoogleSheetsToolParams } from './types'
import type {
GoogleSheetsAppendResponse,
GoogleSheetsToolParams,
} from '@/tools/google_sheets/types'
import type { ToolConfig } from '@/tools/types'
export const appendTool: ToolConfig<GoogleSheetsToolParams, GoogleSheetsAppendResponse> = {
id: 'google_sheets_append',
+4 -4
View File
@@ -1,7 +1,7 @@
import { appendTool } from './append'
import { readTool } from './read'
import { updateTool } from './update'
import { writeTool } from './write'
import { appendTool } from '@/tools/google_sheets/append'
import { readTool } from '@/tools/google_sheets/read'
import { updateTool } from '@/tools/google_sheets/update'
import { writeTool } from '@/tools/google_sheets/write'
export const googleSheetsReadTool = readTool
export const googleSheetsWriteTool = writeTool
+2 -2
View File
@@ -1,5 +1,5 @@
import type { ToolConfig } from '../types'
import type { GoogleSheetsReadResponse, GoogleSheetsToolParams } from './types'
import type { GoogleSheetsReadResponse, GoogleSheetsToolParams } from '@/tools/google_sheets/types'
import type { ToolConfig } from '@/tools/types'
export const readTool: ToolConfig<GoogleSheetsToolParams, GoogleSheetsReadResponse> = {
id: 'google_sheets_read',
+1 -1
View File
@@ -1,4 +1,4 @@
import type { ToolResponse } from '../types'
import type { ToolResponse } from '@/tools/types'
export interface GoogleSheetsRange {
sheetId?: number
+5 -2
View File
@@ -1,5 +1,8 @@
import type { ToolConfig } from '../types'
import type { GoogleSheetsToolParams, GoogleSheetsUpdateResponse } from './types'
import type {
GoogleSheetsToolParams,
GoogleSheetsUpdateResponse,
} from '@/tools/google_sheets/types'
import type { ToolConfig } from '@/tools/types'
export const updateTool: ToolConfig<GoogleSheetsToolParams, GoogleSheetsUpdateResponse> = {
id: 'google_sheets_update',
+2 -2
View File
@@ -1,5 +1,5 @@
import type { ToolConfig } from '../types'
import type { GoogleSheetsToolParams, GoogleSheetsWriteResponse } from './types'
import type { GoogleSheetsToolParams, GoogleSheetsWriteResponse } from '@/tools/google_sheets/types'
import type { ToolConfig } from '@/tools/types'
export const writeTool: ToolConfig<GoogleSheetsToolParams, GoogleSheetsWriteResponse> = {
id: 'google_sheets_write',
+1 -1
View File
@@ -1,3 +1,3 @@
import { requestTool } from './request'
import { requestTool } from '@/tools/http/request'
export { requestTool }
+3 -3
View File
@@ -7,9 +7,9 @@
* to make HTTP requests to external APIs and services.
*/
import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest'
import { mockHttpResponses } from '../__test-utils__/mock-data'
import { ToolTester } from '../__test-utils__/test-tools'
import { requestTool } from './request'
import { mockHttpResponses } from '@/tools/__test-utils__/mock-data'
import { ToolTester } from '@/tools/__test-utils__/test-tools'
import { requestTool } from '@/tools/http/request'
process.env.VITEST = 'true'
+2 -2
View File
@@ -2,8 +2,8 @@ import { env } from '@/lib/env'
import { isTest } from '@/lib/environment'
import { createLogger } from '@/lib/logs/console-logger'
import { getBaseUrl } from '@/lib/urls/utils'
import type { HttpMethod, TableRow, ToolConfig } from '../types'
import type { RequestParams, RequestResponse } from './types'
import type { RequestParams, RequestResponse } from '@/tools/http/types'
import type { HttpMethod, TableRow, ToolConfig } from '@/tools/types'
const logger = createLogger('HTTPRequestTool')
+1 -1
View File
@@ -1,4 +1,4 @@
import type { HttpMethod, TableRow, ToolResponse } from '../types'
import type { HttpMethod, TableRow, ToolResponse } from '@/tools/types'
export interface RequestParams {
url: string

Some files were not shown because too many files have changed in this diff Show More