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
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ Visit [http://localhost:3000](http://localhost:3000) to get started.
- **Firecrawl**: Scrape URL, Search Web
- **GitHub**: Create Issue, List Issues, Get Issue, Update Issue
- **Linear**: Create Ticket, Find Issues
- **Olostep**: Scrape URL, Search Web, Map Website, AI Answer
- **Perplexity**: Search Web, Ask Question, Research Topic
- **Resend**: Send Email
- **Slack**: Send Slack Message
Expand Down
1 change: 1 addition & 0 deletions plugins/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import "./fal";
import "./firecrawl";
import "./github";
import "./linear";
import "./olostep";
import "./perplexity";
import "./resend";
import "./slack";
Expand Down
4 changes: 4 additions & 0 deletions plugins/olostep/credentials.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export type OlostepCredentials = {
OLOSTEP_API_KEY?: string;
};

24 changes: 24 additions & 0 deletions plugins/olostep/icon.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
export function OlostepIcon({ className }: { className?: string }) {
return (
<svg
aria-label="Olostep"
className={className}
viewBox="0 0 100 100"
xmlns="http://www.w3.org/2000/svg"
>
<title>Olostep</title>
<rect width="100" height="100" rx="20" fill="#6366F1" />
<text
x="50"
y="72"
textAnchor="middle"
fill="white"
fontSize="60"
fontWeight="bold"
fontFamily="system-ui, -apple-system, sans-serif"
>
O
</text>
</svg>
);
}
168 changes: 168 additions & 0 deletions plugins/olostep/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
import type { IntegrationPlugin } from "../registry";
import { registerIntegration } from "../registry";
import { OlostepIcon } from "./icon";

const olostepPlugin: IntegrationPlugin = {
type: "olostep",
label: "Olostep",
description: "Web Data API for AI - Search, extract, and structure web data",

icon: OlostepIcon,

formFields: [
{
id: "olostepApiKey",
label: "API Key",
type: "password",
placeholder: "ols_...",
configKey: "olostepApiKey",
envVar: "OLOSTEP_API_KEY",
helpText: "Get your API key from ",
helpLink: {
text: "olostep.com",
url: "https://olostep.com/dashboard",
},
},
],

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

actions: [
{
slug: "scrape",
label: "Scrape URL",
description: "Extract content from any URL with full JavaScript rendering",
category: "Olostep",
stepFunction: "olostepScrapeStep",
stepImportPath: "scrape",
outputFields: [
{ field: "markdown", description: "Scraped content as markdown" },
{ field: "html", description: "Raw HTML content" },
{ field: "metadata", description: "Page metadata object" },
],
configFields: [
{
key: "url",
label: "URL",
type: "template-input",
placeholder: "https://example.com or {{NodeName.url}}",
example: "https://example.com",
required: true,
},
{
key: "waitForSelector",
label: "Wait for Selector",
type: "text",
placeholder: "CSS selector to wait for (optional)",
example: ".main-content",
},
],
},
{
slug: "search",
label: "Search Web",
description: "Search the web using Google Search via Olostep",
category: "Olostep",
stepFunction: "olostepSearchStep",
stepImportPath: "search",
outputFields: [
{ field: "results", description: "Array of search results" },
{ field: "totalResults", description: "Total number of results" },
],
configFields: [
{
key: "query",
label: "Search Query",
type: "template-input",
placeholder: "Search query or {{NodeName.query}}",
example: "latest AI news",
required: true,
},
{
key: "limit",
label: "Result Limit",
type: "number",
placeholder: "10",
min: 1,
example: "10",
},
{
key: "country",
label: "Country",
type: "text",
placeholder: "Country code (e.g., us, uk)",
example: "us",
},
],
},
{
slug: "map",
label: "Map Website",
description: "Discover all URLs from a website (sitemap discovery)",
category: "Olostep",
stepFunction: "olostepMapStep",
stepImportPath: "map",
outputFields: [
{ field: "urls", description: "Array of discovered URLs" },
{ field: "count", description: "Number of URLs found" },
],
configFields: [
{
key: "url",
label: "Website URL",
type: "template-input",
placeholder: "https://example.com or {{NodeName.url}}",
example: "https://example.com",
required: true,
},
{
key: "limit",
label: "Max URLs",
type: "number",
placeholder: "100",
min: 1,
example: "100",
},
],
},
{
slug: "answer",
label: "AI Answer",
description: "Get AI-powered answers from web content",
category: "Olostep",
stepFunction: "olostepAnswerStep",
stepImportPath: "answer",
outputFields: [
{ field: "answer", description: "AI-generated answer" },
{ field: "sources", description: "Array of source URLs used" },
],
configFields: [
{
key: "question",
label: "Question",
type: "template-input",
placeholder: "What is the latest news about...?",
example: "What are the key features of this product?",
required: true,
},
{
key: "searchQuery",
label: "Search Query (optional)",
type: "template-input",
placeholder: "Search the web first for context",
example: "product features comparison 2024",
},
],
},
],
};

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

export default olostepPlugin;
95 changes: 95 additions & 0 deletions plugins/olostep/steps/answer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import "server-only";

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

type AnswerResult = {
answer: string;
sources: Array<{
url: string;
title?: string;
}>;
};

export type OlostepAnswerInput = StepInput & {
integrationId?: string;
question: string;
urls?: string[];
searchQuery?: string;
};

/**
* Answer logic using Olostep API
* Get AI-powered answers from web content
*/
async function getAnswer(input: OlostepAnswerInput): Promise<AnswerResult> {
const credentials = input.integrationId
? await fetchCredentials(input.integrationId)
: {};

const apiKey = credentials.OLOSTEP_API_KEY;

if (!apiKey) {
throw new Error("Olostep API Key is not configured.");
}

try {
const requestBody: Record<string, unknown> = {
question: input.question,
};

// If URLs are provided, use them as context
if (input.urls && input.urls.length > 0) {
requestBody.urls = input.urls;
}

// If search query is provided, search first
if (input.searchQuery) {
requestBody.search_query = input.searchQuery;
}

const response = await fetch("https://api.olostep.com/v1/answer", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${apiKey}`,
},
body: JSON.stringify(requestBody),
});

if (!response.ok) {
const errorText = await response.text();
throw new Error(`Olostep API error: ${errorText}`);
}

const result = await response.json();

return {
answer: result.answer || result.response || "",
sources: (result.sources || result.references || [])
.map((source: { url?: string; link?: string; title?: string }) => ({
url: source.url || source.link || "",
title: source.title,
}))
.filter((source: { url: string; title?: string }) => source.url),
};
} catch (error) {
throw new Error(`Failed to get answer: ${getErrorMessage(error)}`);
}
}

/**
* Olostep Answer Step
* Gets AI-powered answers from web content using Olostep
*/
export async function olostepAnswerStep(
input: OlostepAnswerInput
): Promise<AnswerResult> {
"use step";
return withStepLogging(input, () => getAnswer(input));
}
olostepAnswerStep.maxRetries = 0;

// Required for codegen auto-generation
export const _integrationType = "olostep";
80 changes: 80 additions & 0 deletions plugins/olostep/steps/map.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import "server-only";

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

type MapResult = {
urls: string[];
count: number;
};

export type OlostepMapInput = StepInput & {
integrationId?: string;
url: string;
limit?: number;
includeSubdomains?: boolean;
};

/**
* Map logic using Olostep API
* Gets all URLs from a website (sitemap discovery)
*/
async function mapUrls(input: OlostepMapInput): Promise<MapResult> {
const credentials = input.integrationId
? await fetchCredentials(input.integrationId)
: {};

const apiKey = credentials.OLOSTEP_API_KEY;

if (!apiKey) {
throw new Error("Olostep API Key is not configured.");
}

try {
const response = await fetch("https://api.olostep.com/v1/map", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${apiKey}`,
},
body: JSON.stringify({
url: input.url,
limit: input.limit || 100,
include_subdomains: input.includeSubdomains || false,
}),
});

if (!response.ok) {
const errorText = await response.text();
throw new Error(`Olostep API error: ${errorText}`);
}

const result = await response.json();

const urls = result.urls || result.links || [];
const limitedUrls = urls.slice(0, input.limit || 100);

return {
urls: limitedUrls,
count: limitedUrls.length,
};
} catch (error) {
throw new Error(`Failed to map URLs: ${getErrorMessage(error)}`);
}
}

/**
* Olostep Map Step
* Discovers all URLs from a website using Olostep
*/
export async function olostepMapStep(
input: OlostepMapInput
): Promise<MapResult> {
"use step";
return withStepLogging(input, () => mapUrls(input));
}
olostepMapStep.maxRetries = 0;

// Required for codegen auto-generation
export const _integrationType = "olostep";
Loading