Skip to content
Open
Show file tree
Hide file tree
Changes from 4 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