diff --git a/packages/core/src/plugin/websearch/firecrawl.ts b/packages/core/src/plugin/websearch/firecrawl.ts new file mode 100644 index 00000000000..77bb2980858 --- /dev/null +++ b/packages/core/src/plugin/websearch/firecrawl.ts @@ -0,0 +1,84 @@ +export * as WebSearchFirecrawl from "./firecrawl" + +import { define } from "@opencode-ai/plugin/effect/plugin" +import { Effect, Option, Schema, Scope } from "effect" +import { HttpClient } from "effect/unstable/http" +import { App } from "../../app" +import { WebSearchMcp } from "./mcp" + +export const endpoint = "https://mcp.firecrawl.dev/v2/mcp" + +const McpInput = Schema.Struct({ + query: Schema.String, + limit: Schema.Number.pipe(Schema.optional), +}) + +const McpOutput = Schema.Struct({ + content: Schema.Array(Schema.Struct({ type: Schema.Literal("text"), text: Schema.String })), +}) + +const SearchResponse = Schema.fromJsonString( + Schema.Struct({ + success: Schema.Boolean, + data: Schema.Struct({ + web: Schema.Array( + Schema.Struct({ + url: Schema.String, + title: Schema.NullOr(Schema.String).pipe(Schema.optional), + description: Schema.NullOr(Schema.String).pipe(Schema.optional), + }), + ), + }), + }), +) +const decodeSearchResponse = Schema.decodeUnknownOption(SearchResponse) + +export const Plugin = define({ + id: "opencode.websearch.firecrawl", + effect: Effect.fn("WebSearchFirecrawl.Plugin")(function* (ctx) { + const http = yield* HttpClient.HttpClient + yield* ctx.integration.transform((draft) => { + draft.update("firecrawl", (integration) => (integration.name = "Firecrawl")) + draft.method.update({ + integrationID: "firecrawl", + method: { type: "key", label: "API key (optional)" }, + }) + draft.method.update({ + integrationID: "firecrawl", + method: { type: "env", names: ["FIRECRAWL_API_KEY"] }, + }) + }) + yield* ctx.websearch.transform((draft) => { + draft.add({ + id: "firecrawl", + name: "Firecrawl", + execute: (input) => + Effect.gen(function* () { + const connection = yield* ctx.integration.connection.active("firecrawl") + const credential = connection ? yield* ctx.integration.connection.resolve(connection) : undefined + const result = yield* WebSearchMcp.call( + http, + endpoint, + "firecrawl_search", + { input: McpInput, output: McpOutput }, + { query: input.query, limit: 8 }, + { + "User-Agent": App.useragent(ctx.app), + ...(credential?.type === "key" ? { Authorization: `Bearer ${credential.key}` } : {}), + }, + ) + const content = result?.content.find((item) => item.text) + const response = content ? Option.getOrUndefined(decodeSearchResponse(content.text)) : undefined + return ( + response?.data.web.map((item) => ({ + url: item.url, + ...(item.title ? { title: item.title } : {}), + ...(item.description ? { content: item.description } : {}), + time: {}, + })) ?? [] + ) + }), + }) + }) + }), +}) diff --git a/packages/core/src/plugin/websearch/index.ts b/packages/core/src/plugin/websearch/index.ts index 8f8b5fec6de..c92529fc273 100644 --- a/packages/core/src/plugin/websearch/index.ts +++ b/packages/core/src/plugin/websearch/index.ts @@ -1,4 +1,5 @@ import { WebSearchExa } from "./exa" +import { WebSearchFirecrawl } from "./firecrawl" import { WebSearchParallel } from "./parallel" -export const WebSearchPlugins = [WebSearchExa.Plugin, WebSearchParallel.Plugin] as const +export const WebSearchPlugins = [WebSearchExa.Plugin, WebSearchFirecrawl.Plugin, WebSearchParallel.Plugin] as const diff --git a/packages/session-ui/src/components/message-part.tsx b/packages/session-ui/src/components/message-part.tsx index 76a713e970d..5694ca237a1 100644 --- a/packages/session-ui/src/components/message-part.tsx +++ b/packages/session-ui/src/components/message-part.tsx @@ -462,6 +462,7 @@ function newLayout() { function webSearchProviderLabel(provider: unknown) { if (provider === "parallel") return "Parallel Web Search" if (provider === "exa") return "Exa Web Search" + if (provider === "firecrawl") return "Firecrawl Web Search" return "Web Search" } diff --git a/packages/tui/src/util/tool-display.ts b/packages/tui/src/util/tool-display.ts index 25e333456b9..2c239b2ec41 100644 --- a/packages/tui/src/util/tool-display.ts +++ b/packages/tui/src/util/tool-display.ts @@ -22,6 +22,7 @@ export function primitiveInputSummary(input: Record, omit: read export function webSearchProviderLabel(provider: unknown) { if (provider === "parallel") return "Parallel Web Search" if (provider === "exa") return "Exa Web Search" + if (provider === "firecrawl") return "Firecrawl Web Search" return "Web Search" }