Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,13 +43,16 @@ Create a `.env.local` file with the following:
```env
# Database
DATABASE_URL=postgresql://user:password@localhost:5432/workflow_builder
Key should be a 32-byte hex string (64 characters)
INTEGRATION_ENCRYPTION_KEY=your-encryption-key

# Better Auth
BETTER_AUTH_SECRET=your-secret-key
BETTER_AUTH_URL=http://localhost:3000

# AI Gateway (for AI workflow generation)
AI_GATEWAY_API_KEY=your-openai-api-key
# AI Gateway (for AI workflow generation, see more https://vercel.com/ai-gateway)
AI_GATEWAY_API_KEY=your-ai-gateway-api-key

Comment on lines +46 to +55
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please remove unrelated changes

```

### Installation
Expand Down Expand Up @@ -81,6 +84,7 @@ Visit [http://localhost:3000](http://localhost:3000) to get started.
<!-- PLUGINS:START - Do not remove. Auto-generated by discover-plugins -->
- **AI Gateway**: Generate Text, Generate Image
- **Blob**: Put Blob, List Blobs
- **Exa**: Search Web
- **fal.ai**: Generate Image, Generate Video, Upscale Image, Remove Background, Image to Image
- **Firecrawl**: Scrape URL, Search Web
- **GitHub**: Create Issue, List Issues, Get Issue, Update Issue
Expand Down
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@
"db:push": "drizzle-kit push",
"db:studio": "drizzle-kit studio",
"discover-plugins": "tsx scripts/discover-plugins.ts",
"create-plugin": "tsx scripts/create-plugin.ts"
"create-plugin": "tsx scripts/create-plugin.ts",
"workflow:runs:web": "npx workflow inspect runs --web"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please remove unrelated change

},
"dependencies": {
"@ai-sdk/provider": "^2.0.0",
Expand All @@ -37,6 +38,7 @@
"clsx": "^2.1.1",
"dotenv": "^17.2.3",
"drizzle-orm": "^0.44.7",
"exa-js": "^2.0.11",
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unused dependency, please remove

"jotai": "^2.15.1",
"jszip": "^3.10.1",
"lucide-react": "^0.552.0",
Expand Down
4 changes: 4 additions & 0 deletions plugins/exa/credentials.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export type ExaCredentials = {
EXA_API_KEY?: string;
};

31 changes: 31 additions & 0 deletions plugins/exa/icon.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
export function ExaIcon({ className }: { className?: string }) {
return (
<svg
aria-label="Exa logo"
className={className}
fill="currentColor"
viewBox="0 0 277 100"
xmlns="http://www.w3.org/2000/svg"
>
<title>Exa</title>
<path
d="M161.632 53.2837H115.472C115.918 66.4186 125.061 72.7596 133.981 72.7596C142.9 72.7596 147.806 68.6833 150.371 62.682H160.851C158.064 73.2126 148.587 81.8182 133.981 81.8182C115.026 81.8182 104.545 68.0039 104.545 50C104.545 30.7506 117.256 18.4083 133.646 18.4083C151.931 18.4083 162.97 34.0343 161.632 53.2837ZM133.646 27.2404C124.615 27.2404 116.476 32.2226 115.584 44.4516H150.928C150.705 35.846 144.35 27.2404 133.646 27.2404Z"
fill="currentColor"
/>
<path
d="M219.201 19.4274L198.797 48.528L221.208 80.3462H209.055L192.777 57.1336L176.61 80.3462H165.014L187.09 48.9809L166.352 19.4274H178.505L193.111 40.3753L207.829 19.4274H219.201Z"
fill="currentColor"
/>
<path
d="M266.458 54.869V51.0191C248.061 52.944 236.354 55.6616 236.354 64.0408C236.354 69.8156 240.702 73.6655 247.949 73.6655C257.426 73.6655 266.458 69.2494 266.458 54.869ZM245.719 81.8182C234.458 81.8182 225.092 75.4772 225.092 64.2672C225.092 49.8868 241.036 45.6972 265.677 42.8664V41.3944C265.677 30.2976 259.545 26.561 252.075 26.561C243.712 26.561 238.806 31.2035 238.36 38.6768H227.88C228.883 25.5419 240.256 18.1818 251.963 18.1818C268.465 18.1818 275.935 26.2213 275.823 43.3193L275.712 57.3601C275.6 67.551 276.158 74.5713 277.273 80.3462H267.015C266.681 78.0815 266.346 75.5904 266.235 71.967C262.555 78.1948 256.311 81.8182 245.719 81.8182Z"
fill="currentColor"
/>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M0 0H78.1818V7.46269L44.8165 50L78.1818 92.5373V100H0V0ZM39.5825 43.1172L66.6956 7.46269H12.4695L39.5825 43.1172ZM8.79612 16.3977V46.2687H31.5111L8.79612 16.3977ZM31.5111 53.7313H8.79612V83.6023L31.5111 53.7313ZM12.4695 92.5373L39.5825 56.8828L66.6956 92.5373H12.4695Z"
fill="#1F40ED"
/>
</svg>
);
}
84 changes: 84 additions & 0 deletions plugins/exa/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import type { IntegrationPlugin } from "../registry";
import { registerIntegration } from "../registry";
import { ExaIcon } from "./icon";

const exaPlugin: IntegrationPlugin = {
type: "exa",
label: "Exa",
description:
"Semantic web search API giving AI apps fast, relevant, up-to-date results",

icon: ExaIcon,

formFields: [
{
id: "exaApiKey",
label: "API Key",
type: "password",
placeholder: "Your Exa API key",
configKey: "exaApiKey",
envVar: "EXA_API_KEY",
helpText: "Get your API key from ",
helpLink: {
text: "Exa Dashboard",
url: "https://dashboard.exa.ai/api-keys/",
},
},
],

testConfig: {
getTestFunction: async () => {
const { testExa } = await import("./test");
return testExa;
},
},

actions: [
{
slug: "search",
label: "Search Web",
description:
"Perform semantic web search and retrieve relevant results with content",
category: "Exa",
stepFunction: "exaSearchStep",
stepImportPath: "search",
outputFields: [
{ field: "results", description: "Array of search results" },
],
configFields: [
{
key: "query",
label: "Search Query",
type: "template-input",
placeholder: "Search query or {{NodeName.query}}",
example: "latest AI research papers",
required: true,
},
{
key: "numResults",
label: "Number of Results",
type: "number",
placeholder: "10",
min: 1,
example: "10",
},
{
key: "type",
label: "Search Type",
type: "select",
options: [
{ value: "auto", label: "Auto" },
{ value: "neural", label: "Neural" },
{ value: "keyword", label: "Keyword" },
],
defaultValue: "auto",
},
],
},
],
};

// Auto-register on import
registerIntegration(exaPlugin);

export default exaPlugin;
129 changes: 129 additions & 0 deletions plugins/exa/steps/search.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
import "server-only";

import { fetchCredentials } from "@/lib/credential-fetcher";
import { type StepInput, withStepLogging } from "@/lib/steps/step-handler";
import type { ExaCredentials } from "../credentials";

const EXA_API_URL = "https://api.exa.ai";

type ExaSearchResponse = {
results: Array<{
url: string;
id: string;
title: string | null;
publishedDate?: string;
author?: string;
text?: string;
}>;
autopromptString?: string;
};

type ExaErrorResponse = {
error?: string;
message?: string;
};

type SearchResult =
| {
success: true;
results: Array<{
url: string;
title: string | null;
publishedDate?: string;
author?: string;
text?: string;
}>;
}
| { success: false; error: string };

export type SearchCoreInput = {
query: string;
numResults?: number;
type?: "auto" | "neural" | "keyword";
};

export type ExaSearchInput = StepInput &
SearchCoreInput & {
integrationId?: string;
};

/**
* Core logic - portable between app and export
*/
async function stepHandler(
input: SearchCoreInput,
credentials: ExaCredentials
): Promise<SearchResult> {
const apiKey = credentials.EXA_API_KEY;

if (!apiKey) {
return {
success: false,
error:
"EXA_API_KEY is not configured. Please add it in Project Integrations.",
};
}

try {
const response = await fetch(`${EXA_API_URL}/search`, {
method: "POST",
headers: {
"Content-Type": "application/json",
"x-api-key": apiKey,
},
body: JSON.stringify({
query: input.query,
numResults: input.numResults ? Number(input.numResults) : 10,
type: input.type || "auto",
}),
});

if (!response.ok) {
const errorData = (await response.json()) as ExaErrorResponse;
return {
success: false,
error:
errorData.error ||
errorData.message ||
`HTTP ${response.status}: Search failed`,
};
}

const data = (await response.json()) as ExaSearchResponse;

return {
success: true,
results: data.results.map((r) => ({
url: r.url,
title: r.title,
publishedDate: r.publishedDate,
author: r.author,
text: r.text,
})),
};
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
return {
success: false,
error: `Failed to search: ${message}`,
};
}
}

/**
* App entry point - fetches credentials and wraps with logging
*/
export async function exaSearchStep(
input: ExaSearchInput
): Promise<SearchResult> {
"use step";

const credentials = input.integrationId
? await fetchCredentials(input.integrationId)
: {};

return withStepLogging(input, () => stepHandler(input, credentials));
}

// Export marker for codegen auto-generation
export const _integrationType = "exa";
42 changes: 42 additions & 0 deletions plugins/exa/test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
export async function testExa(credentials: Record<string, string>) {
try {
const apiKey = credentials.EXA_API_KEY;

if (!apiKey) {
return {
success: false,
error: "EXA_API_KEY is required",
};
}

// Use a minimal search request to validate the API key
const response = await fetch("https://api.exa.ai/search", {
method: "POST",
headers: {
"Content-Type": "application/json",
"x-api-key": apiKey,
},
body: JSON.stringify({
query: "test",
numResults: 1,
type: "keyword",
}),
});

if (response.ok) {
return { success: true };
}

if (response.status === 401) {
return { success: false, error: "Invalid API key" };
}

const error = await response.text();
return { success: false, error: error || `API error: HTTP ${response.status}` };
} catch (error) {
return {
success: false,
error: error instanceof Error ? error.message : String(error),
};
}
}
1 change: 1 addition & 0 deletions plugins/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

import "./ai-gateway";
import "./blob";
import "./exa";
import "./fal";
import "./firecrawl";
import "./github";
Expand Down
Loading