From 668a072bc419c02bdccaa3327da584b5feff2408 Mon Sep 17 00:00:00 2001 From: Zeeshan Adil Date: Wed, 3 Dec 2025 03:49:12 +0500 Subject: [PATCH 1/4] feat: Add Olostep integration with 4 web data actions Actions: - Scrape URL: Extract content with JS rendering and CSS selector wait - Search Web: Google Search via Olostep API with country targeting - Map Website: Discover all URLs from a website - AI Answer: Get AI-powered answers from web content Features: - No SDK dependency (uses fetch API directly) - Template variable support ({{NodeName.field}}) - Code generation for workflow export - Integration test endpoint Configure via Settings -> Integrations -> Olostep --- README.md | 1 + .../[integrationId]/test/route.ts | 46 +++++ lib/step-registry.ts | 30 +++- lib/types/integration.ts | 3 +- plugins/index.ts | 3 +- plugins/olostep/codegen/answer.ts | 50 ++++++ plugins/olostep/codegen/map.ts | 40 +++++ plugins/olostep/codegen/scrape.ts | 46 +++++ plugins/olostep/codegen/search.ts | 52 ++++++ plugins/olostep/icon.tsx | 24 +++ plugins/olostep/index.ts | 161 ++++++++++++++++++ plugins/olostep/steps/answer.ts | 94 ++++++++++ plugins/olostep/steps/map.ts | 78 +++++++++ plugins/olostep/steps/scrape.ts | 83 +++++++++ plugins/olostep/steps/search.ts | 110 ++++++++++++ plugins/olostep/test.ts | 31 ++++ 16 files changed, 845 insertions(+), 7 deletions(-) create mode 100644 plugins/olostep/codegen/answer.ts create mode 100644 plugins/olostep/codegen/map.ts create mode 100644 plugins/olostep/codegen/scrape.ts create mode 100644 plugins/olostep/codegen/search.ts create mode 100644 plugins/olostep/icon.tsx create mode 100644 plugins/olostep/index.ts create mode 100644 plugins/olostep/steps/answer.ts create mode 100644 plugins/olostep/steps/map.ts create mode 100644 plugins/olostep/steps/scrape.ts create mode 100644 plugins/olostep/steps/search.ts create mode 100644 plugins/olostep/test.ts diff --git a/README.md b/README.md index b08be906..887689a2 100644 --- a/README.md +++ b/README.md @@ -82,6 +82,7 @@ Visit [http://localhost:3000](http://localhost:3000) to get started. - **AI Gateway**: Generate Text, Generate Image - **Firecrawl**: Scrape URL, Search Web - **Linear**: Create Ticket, Find Issues +- **Olostep**: Scrape URL, Search Web, Map Website, AI Answer - **Resend**: Send Email - **Slack**: Send Slack Message - **v0**: Create Chat, Send Message diff --git a/app/api/integrations/[integrationId]/test/route.ts b/app/api/integrations/[integrationId]/test/route.ts index 0247e731..1cd0002d 100644 --- a/app/api/integrations/[integrationId]/test/route.ts +++ b/app/api/integrations/[integrationId]/test/route.ts @@ -68,6 +68,9 @@ export async function POST( integration.config.firecrawlApiKey ); break; + case "olostep": + result = await testOlostepConnection(integration.config.olostepApiKey); + break; default: return NextResponse.json( { error: "Invalid integration type" }, @@ -281,3 +284,46 @@ async function testFirecrawlConnection( }; } } + +async function testOlostepConnection( + apiKey?: string +): Promise { + try { + if (!apiKey) { + return { + status: "error", + message: "Olostep API Key is not configured", + }; + } + + const response = await fetch("https://api.olostep.com/v1/scrapes", { + method: "POST", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${apiKey}`, + }, + body: JSON.stringify({ + url_to_scrape: "https://example.com", + formats: ["markdown"], + }), + }); + + if (!response.ok) { + const errorText = await response.text(); + return { + status: "error", + message: `Connection failed: ${errorText}`, + }; + } + + return { + status: "success", + message: "Connected successfully", + }; + } catch (error) { + return { + status: "error", + message: error instanceof Error ? error.message : "Connection failed", + }; + } +} diff --git a/lib/step-registry.ts b/lib/step-registry.ts index 6a71649b..c04aa315 100644 --- a/lib/step-registry.ts +++ b/lib/step-registry.ts @@ -7,7 +7,7 @@ * This registry enables dynamic step imports that are statically analyzable * by the bundler. Each action type maps to its step importer function. * - * Generated entries: 10 + * Generated entries: 14 */ import "server-only"; @@ -45,7 +45,7 @@ export const PLUGIN_STEP_IMPORTERS: Record = { importer: () => import("@/plugins/firecrawl/steps/scrape"), stepFunction: "firecrawlScrapeStep", }, - Scrape: { + "Scrape": { importer: () => import("@/plugins/firecrawl/steps/scrape"), stepFunction: "firecrawlScrapeStep", }, @@ -53,7 +53,7 @@ export const PLUGIN_STEP_IMPORTERS: Record = { importer: () => import("@/plugins/firecrawl/steps/search"), stepFunction: "firecrawlSearchStep", }, - Search: { + "Search": { importer: () => import("@/plugins/firecrawl/steps/search"), stepFunction: "firecrawlSearchStep", }, @@ -73,6 +73,22 @@ export const PLUGIN_STEP_IMPORTERS: Record = { importer: () => import("@/plugins/linear/steps/find-issues"), stepFunction: "findIssuesStep", }, + "olostep/scrape": { + importer: () => import("@/plugins/olostep/steps/scrape"), + stepFunction: "olostepScrapeStep", + }, + "olostep/search": { + importer: () => import("@/plugins/olostep/steps/search"), + stepFunction: "olostepSearchStep", + }, + "olostep/map": { + importer: () => import("@/plugins/olostep/steps/map"), + stepFunction: "olostepMapStep", + }, + "olostep/answer": { + importer: () => import("@/plugins/olostep/steps/answer"), + stepFunction: "olostepAnswerStep", + }, "resend/send-email": { importer: () => import("@/plugins/resend/steps/send-email"), stepFunction: "sendEmailStep", @@ -118,12 +134,16 @@ export const ACTION_LABELS: Record = { "firecrawl/search": "Search Web", "linear/create-ticket": "Create Ticket", "linear/find-issues": "Find Issues", + "olostep/scrape": "Scrape URL", + "olostep/search": "Search Web", + "olostep/map": "Map Website", + "olostep/answer": "AI Answer", "resend/send-email": "Send Email", "slack/send-message": "Send Slack Message", "v0/create-chat": "Create Chat", "v0/send-message": "Send Message", - Scrape: "Scrape URL", - Search: "Search Web", + "Scrape": "Scrape URL", + "Search": "Search Web", "Generate Text": "Generate Text", "Generate Image": "Generate Image", "Send Email": "Send Email", diff --git a/lib/types/integration.ts b/lib/types/integration.ts index cd146253..6613837e 100644 --- a/lib/types/integration.ts +++ b/lib/types/integration.ts @@ -9,7 +9,7 @@ * 2. Add a system integration to SYSTEM_INTEGRATION_TYPES in discover-plugins.ts * 3. Run: pnpm discover-plugins * - * Generated types: ai-gateway, database, firecrawl, linear, resend, slack, v0 + * Generated types: ai-gateway, database, firecrawl, linear, olostep, resend, slack, v0 */ // Integration type union - plugins + system integrations @@ -18,6 +18,7 @@ export type IntegrationType = | "database" | "firecrawl" | "linear" + | "olostep" | "resend" | "slack" | "v0"; diff --git a/plugins/index.ts b/plugins/index.ts index 7a5965eb..86b33dbb 100644 --- a/plugins/index.ts +++ b/plugins/index.ts @@ -13,12 +13,13 @@ * 1. Delete the plugin directory * 2. Run: pnpm discover-plugins (or it runs automatically on build) * - * Discovered plugins: ai-gateway, firecrawl, linear, resend, slack, v0 + * Discovered plugins: ai-gateway, firecrawl, linear, olostep, resend, slack, v0 */ import "./ai-gateway"; import "./firecrawl"; import "./linear"; +import "./olostep"; import "./resend"; import "./slack"; import "./v0"; diff --git a/plugins/olostep/codegen/answer.ts b/plugins/olostep/codegen/answer.ts new file mode 100644 index 00000000..708c1311 --- /dev/null +++ b/plugins/olostep/codegen/answer.ts @@ -0,0 +1,50 @@ +/** + * Code generation template for Olostep Answer action + * This template is used when exporting workflows to standalone Next.js projects + * It uses environment variables instead of integrationId + */ +export const answerCodegenTemplate = `export async function olostepAnswerStep(input: { + question: string; + urls?: string[]; + searchQuery?: string; +}) { + "use step"; + + const requestBody: Record = { + question: input.question, + }; + + if (input.urls && input.urls.length > 0) { + requestBody.urls = input.urls; + } + + 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 \${process.env.OLOSTEP_API_KEY}\`, + }, + body: JSON.stringify(requestBody), + }); + + if (!response.ok) { + throw new Error(\`Olostep API error: \${await response.text()}\`); + } + + const result = await response.json(); + + return { + answer: result.answer || result.response || '', + sources: (result.sources || result.references || []).map((source: any) => ({ + url: source.url || source.link, + title: source.title, + })), + }; +}`; + + + diff --git a/plugins/olostep/codegen/map.ts b/plugins/olostep/codegen/map.ts new file mode 100644 index 00000000..75726419 --- /dev/null +++ b/plugins/olostep/codegen/map.ts @@ -0,0 +1,40 @@ +/** + * Code generation template for Olostep Map action + * This template is used when exporting workflows to standalone Next.js projects + * It uses environment variables instead of integrationId + */ +export const mapCodegenTemplate = `export async function olostepMapStep(input: { + url: string; + limit?: number; + includeSubdomains?: boolean; +}) { + "use step"; + + const response = await fetch('https://api.olostep.com/v1/map', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Authorization': \`Bearer \${process.env.OLOSTEP_API_KEY}\`, + }, + body: JSON.stringify({ + url: input.url, + limit: input.limit || 100, + include_subdomains: input.includeSubdomains || false, + }), + }); + + if (!response.ok) { + throw new Error(\`Olostep API error: \${await response.text()}\`); + } + + const result = await response.json(); + const urls = result.urls || result.links || []; + + return { + urls: urls.slice(0, input.limit || 100), + totalUrls: urls.length, + }; +}`; + + + diff --git a/plugins/olostep/codegen/scrape.ts b/plugins/olostep/codegen/scrape.ts new file mode 100644 index 00000000..8a33ffa1 --- /dev/null +++ b/plugins/olostep/codegen/scrape.ts @@ -0,0 +1,46 @@ +/** + * Code generation template for Olostep Scrape action + * This template is used when exporting workflows to standalone Next.js projects + * It uses environment variables instead of integrationId + */ +export const scrapeCodegenTemplate = `export async function olostepScrapeStep(input: { + url: string; + formats?: ('markdown' | 'html' | 'text')[]; + waitForSelector?: string; +}) { + "use step"; + + const response = await fetch('https://api.olostep.com/v1/scrapes', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Authorization': \`Bearer \${process.env.OLOSTEP_API_KEY}\`, + }, + body: JSON.stringify({ + url_to_scrape: input.url, + formats: input.formats || ['markdown'], + wait_for_selector: input.waitForSelector, + }), + }); + + if (!response.ok) { + throw new Error(\`Olostep API error: \${await response.text()}\`); + } + + const result = await response.json(); + + return { + markdown: result.markdown_content || result.markdown, + html: result.html_content || result.html, + metadata: { + title: result.title, + url: result.url, + statusCode: result.status_code, + }, + }; +}`; + + + + + diff --git a/plugins/olostep/codegen/search.ts b/plugins/olostep/codegen/search.ts new file mode 100644 index 00000000..875ce2f7 --- /dev/null +++ b/plugins/olostep/codegen/search.ts @@ -0,0 +1,52 @@ +/** + * Code generation template for Olostep Search action + * This template is used when exporting workflows to standalone Next.js projects + * It uses environment variables instead of integrationId + */ +export const searchCodegenTemplate = `export async function olostepSearchStep(input: { + query: string; + limit?: number; + country?: string; +}) { + "use step"; + + const params = new URLSearchParams({ + query: input.query, + limit: String(input.limit || 10), + }); + + if (input.country) { + params.append('country', input.country); + } + + const response = await fetch( + \`https://api.olostep.com/v1/google-search?\${params.toString()}\`, + { + method: 'GET', + headers: { + 'Authorization': \`Bearer \${process.env.OLOSTEP_API_KEY}\`, + }, + } + ); + + if (!response.ok) { + throw new Error(\`Olostep API error: \${await response.text()}\`); + } + + const result = await response.json(); + + return { + results: (result.results || result.items || []).slice(0, input.limit || 10).map((item: any) => ({ + url: item.url || item.link, + title: item.title, + description: item.description || item.snippet, + markdown: item.markdown, + })), + totalResults: result.total_results, + }; +}`; + + + + + diff --git a/plugins/olostep/icon.tsx b/plugins/olostep/icon.tsx new file mode 100644 index 00000000..b77bca01 --- /dev/null +++ b/plugins/olostep/icon.tsx @@ -0,0 +1,24 @@ +export function OlostepIcon({ className }: { className?: string }) { + return ( + + Olostep + + + O + + + ); +} diff --git a/plugins/olostep/index.ts b/plugins/olostep/index.ts new file mode 100644 index 00000000..2653403b --- /dev/null +++ b/plugins/olostep/index.ts @@ -0,0 +1,161 @@ +import type { IntegrationPlugin } from "../registry"; +import { registerIntegration } from "../registry"; +import { answerCodegenTemplate } from "./codegen/answer"; +import { mapCodegenTemplate } from "./codegen/map"; +import { scrapeCodegenTemplate } from "./codegen/scrape"; +import { searchCodegenTemplate } from "./codegen/search"; +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; + }, + }, + + dependencies: {}, + + actions: [ + { + slug: "scrape", + label: "Scrape URL", + description: "Extract content from any URL with full JavaScript rendering", + category: "Olostep", + stepFunction: "olostepScrapeStep", + stepImportPath: "scrape", + 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", + }, + ], + codegenTemplate: scrapeCodegenTemplate, + }, + { + slug: "search", + label: "Search Web", + description: "Search the web using Google Search via Olostep", + category: "Olostep", + stepFunction: "olostepSearchStep", + stepImportPath: "search", + 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", + }, + ], + codegenTemplate: searchCodegenTemplate, + }, + { + slug: "map", + label: "Map Website", + description: "Discover all URLs from a website (sitemap discovery)", + category: "Olostep", + stepFunction: "olostepMapStep", + stepImportPath: "map", + 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", + }, + ], + codegenTemplate: mapCodegenTemplate, + }, + { + slug: "answer", + label: "AI Answer", + description: "Get AI-powered answers from web content", + category: "Olostep", + stepFunction: "olostepAnswerStep", + stepImportPath: "answer", + 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", + }, + ], + codegenTemplate: answerCodegenTemplate, + }, + ], +}; + +// Auto-register on import +registerIntegration(olostepPlugin); + +export default olostepPlugin; diff --git a/plugins/olostep/steps/answer.ts b/plugins/olostep/steps/answer.ts new file mode 100644 index 00000000..c47b32c8 --- /dev/null +++ b/plugins/olostep/steps/answer.ts @@ -0,0 +1,94 @@ +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 { + 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 = { + 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, + }) + ), + }; + } 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 { + "use step"; + return withStepLogging(input, () => getAnswer(input)); +} + + + diff --git a/plugins/olostep/steps/map.ts b/plugins/olostep/steps/map.ts new file mode 100644 index 00000000..522d1092 --- /dev/null +++ b/plugins/olostep/steps/map.ts @@ -0,0 +1,78 @@ +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[]; + totalUrls: 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 { + 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 || []; + + return { + urls: urls.slice(0, input.limit || 100), + totalUrls: urls.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 { + "use step"; + return withStepLogging(input, () => mapUrls(input)); +} + + + diff --git a/plugins/olostep/steps/scrape.ts b/plugins/olostep/steps/scrape.ts new file mode 100644 index 00000000..ac8abb40 --- /dev/null +++ b/plugins/olostep/steps/scrape.ts @@ -0,0 +1,83 @@ +import "server-only"; + +import { fetchCredentials } from "@/lib/credential-fetcher"; +import { type StepInput, withStepLogging } from "@/lib/steps/step-handler"; +import { getErrorMessage } from "@/lib/utils"; + +type ScrapeResult = { + markdown?: string; + html?: string; + metadata?: Record; +}; + +export type OlostepScrapeInput = StepInput & { + integrationId?: string; + url: string; + formats?: ("markdown" | "html" | "text")[]; + waitForSelector?: string; +}; + +/** + * Scrape logic using Olostep API + */ +async function scrape(input: OlostepScrapeInput): Promise { + 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/scrapes", { + method: "POST", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${apiKey}`, + }, + body: JSON.stringify({ + url_to_scrape: input.url, + formats: input.formats || ["markdown"], + wait_for_selector: input.waitForSelector, + }), + }); + + if (!response.ok) { + const errorText = await response.text(); + throw new Error(`Olostep API error: ${errorText}`); + } + + const result = await response.json(); + + return { + markdown: result.markdown_content || result.markdown, + html: result.html_content || result.html, + metadata: { + title: result.title, + url: result.url, + statusCode: result.status_code, + }, + }; + } catch (error) { + throw new Error(`Failed to scrape: ${getErrorMessage(error)}`); + } +} + +/** + * Olostep Scrape Step + * Scrapes content from a URL using Olostep + */ +export async function olostepScrapeStep( + input: OlostepScrapeInput +): Promise { + "use step"; + return withStepLogging(input, () => scrape(input)); +} + + + + + diff --git a/plugins/olostep/steps/search.ts b/plugins/olostep/steps/search.ts new file mode 100644 index 00000000..4d1e2a12 --- /dev/null +++ b/plugins/olostep/steps/search.ts @@ -0,0 +1,110 @@ +import "server-only"; + +import { fetchCredentials } from "@/lib/credential-fetcher"; +import { type StepInput, withStepLogging } from "@/lib/steps/step-handler"; +import { getErrorMessage } from "@/lib/utils"; + +type SearchResultItem = { + url: string; + title?: string; + description?: string; + markdown?: string; +}; + +type SearchResult = { + results: SearchResultItem[]; + totalResults?: number; +}; + +export type OlostepSearchInput = StepInput & { + integrationId?: string; + query: string; + limit?: number; + country?: string; +}; + +/** + * Search logic using Olostep Google Search API + */ +async function search(input: OlostepSearchInput): Promise { + 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 { + // Use the map endpoint with google search for web search functionality + const params = new URLSearchParams({ + query: input.query, + limit: String(input.limit || 10), + }); + + if (input.country) { + params.append("country", input.country); + } + + const response = await fetch( + `https://api.olostep.com/v1/google-search?${params.toString()}`, + { + method: "GET", + headers: { + Authorization: `Bearer ${apiKey}`, + }, + } + ); + + if (!response.ok) { + const errorText = await response.text(); + throw new Error(`Olostep API error: ${errorText}`); + } + + const result = await response.json(); + + // Transform the response to a consistent format + const results: SearchResultItem[] = (result.results || result.items || []) + .slice(0, input.limit || 10) + .map( + (item: { + url?: string; + link?: string; + title?: string; + description?: string; + snippet?: string; + markdown?: string; + }) => ({ + url: item.url || item.link, + title: item.title, + description: item.description || item.snippet, + markdown: item.markdown, + }) + ); + + return { + results, + totalResults: result.total_results || results.length, + }; + } catch (error) { + throw new Error(`Failed to search: ${getErrorMessage(error)}`); + } +} + +/** + * Olostep Search Step + * Searches the web using Olostep + */ +export async function olostepSearchStep( + input: OlostepSearchInput +): Promise { + "use step"; + return withStepLogging(input, () => search(input)); +} + + + + + diff --git a/plugins/olostep/test.ts b/plugins/olostep/test.ts new file mode 100644 index 00000000..e81f14e1 --- /dev/null +++ b/plugins/olostep/test.ts @@ -0,0 +1,31 @@ +export async function testOlostep(credentials: Record) { + try { + const response = await fetch("https://api.olostep.com/v1/scrapes", { + method: "POST", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${credentials.OLOSTEP_API_KEY}`, + }, + body: JSON.stringify({ + url_to_scrape: "https://example.com", + formats: ["markdown"], + }), + }); + + if (response.ok) { + return { success: true }; + } + const error = await response.text(); + return { success: false, error }; + } catch (error) { + return { + success: false, + error: error instanceof Error ? error.message : String(error), + }; + } +} + + + + + From 743b3739df82452418c88eb44194d51abbab6cab Mon Sep 17 00:00:00 2001 From: Zeeshan Adil Date: Wed, 3 Dec 2025 04:23:43 +0500 Subject: [PATCH 2/4] fix: Add maxRetries = 0 to all Olostep step functions --- plugins/olostep/steps/answer.ts | 1 + plugins/olostep/steps/map.ts | 1 + plugins/olostep/steps/scrape.ts | 1 + plugins/olostep/steps/search.ts | 1 + 4 files changed, 4 insertions(+) diff --git a/plugins/olostep/steps/answer.ts b/plugins/olostep/steps/answer.ts index 40e4391f..b5b726c1 100644 --- a/plugins/olostep/steps/answer.ts +++ b/plugins/olostep/steps/answer.ts @@ -89,6 +89,7 @@ export async function olostepAnswerStep( "use step"; return withStepLogging(input, () => getAnswer(input)); } +olostepAnswerStep.maxRetries = 0; // Required for codegen auto-generation export const _integrationType = "olostep"; diff --git a/plugins/olostep/steps/map.ts b/plugins/olostep/steps/map.ts index 08a5e831..38253147 100644 --- a/plugins/olostep/steps/map.ts +++ b/plugins/olostep/steps/map.ts @@ -74,6 +74,7 @@ export async function olostepMapStep( "use step"; return withStepLogging(input, () => mapUrls(input)); } +olostepMapStep.maxRetries = 0; // Required for codegen auto-generation export const _integrationType = "olostep"; diff --git a/plugins/olostep/steps/scrape.ts b/plugins/olostep/steps/scrape.ts index dd61ba63..49dbc0fe 100644 --- a/plugins/olostep/steps/scrape.ts +++ b/plugins/olostep/steps/scrape.ts @@ -76,6 +76,7 @@ export async function olostepScrapeStep( "use step"; return withStepLogging(input, () => scrape(input)); } +olostepScrapeStep.maxRetries = 0; // Required for codegen auto-generation export const _integrationType = "olostep"; diff --git a/plugins/olostep/steps/search.ts b/plugins/olostep/steps/search.ts index d76e15cb..466a3555 100644 --- a/plugins/olostep/steps/search.ts +++ b/plugins/olostep/steps/search.ts @@ -104,6 +104,7 @@ export async function olostepSearchStep( "use step"; return withStepLogging(input, () => search(input)); } +olostepSearchStep.maxRetries = 0; // Required for codegen auto-generation export const _integrationType = "olostep"; From fe598c7b2f363ff88b6c074e02cccd0d5a27614f Mon Sep 17 00:00:00 2001 From: Zeeshan Adil Date: Wed, 3 Dec 2025 04:29:16 +0500 Subject: [PATCH 3/4] fix: Validate API key exists before testing Olostep connection --- plugins/olostep/test.ts | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/plugins/olostep/test.ts b/plugins/olostep/test.ts index e81f14e1..05497f89 100644 --- a/plugins/olostep/test.ts +++ b/plugins/olostep/test.ts @@ -1,10 +1,19 @@ export async function testOlostep(credentials: Record) { try { + const apiKey = credentials.OLOSTEP_API_KEY; + + if (!apiKey) { + return { + success: false, + error: "OLOSTEP_API_KEY is required", + }; + } + const response = await fetch("https://api.olostep.com/v1/scrapes", { method: "POST", headers: { "Content-Type": "application/json", - Authorization: `Bearer ${credentials.OLOSTEP_API_KEY}`, + Authorization: `Bearer ${apiKey}`, }, body: JSON.stringify({ url_to_scrape: "https://example.com", From 38e6fd02b5d4b646bd1ea257a680c68943857eaa Mon Sep 17 00:00:00 2001 From: Zeeshan Adil Date: Thu, 4 Dec 2025 05:00:46 +0500 Subject: [PATCH 4/4] fix: Filter search results before applying limit to return correct count --- plugins/olostep/steps/search.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/plugins/olostep/steps/search.ts b/plugins/olostep/steps/search.ts index 466a3555..b1746519 100644 --- a/plugins/olostep/steps/search.ts +++ b/plugins/olostep/steps/search.ts @@ -66,8 +66,8 @@ async function search(input: OlostepSearchInput): Promise { const result = await response.json(); // Transform the response to a consistent format + // Filter before slicing to ensure we return the requested number of valid results const results: SearchResultItem[] = (result.results || result.items || []) - .slice(0, input.limit || 10) .map( (item: { url?: string; @@ -83,7 +83,8 @@ async function search(input: OlostepSearchInput): Promise { markdown: item.markdown, }) ) - .filter((item: SearchResultItem) => item.url); + .filter((item: SearchResultItem) => item.url) + .slice(0, input.limit || 10); return { results,