|
|
@@ -1,132 +1,65 @@
|
|
|
import z from "zod"
|
|
|
+import { Effect } from "effect"
|
|
|
+import { HttpClient } from "effect/unstable/http"
|
|
|
import { Tool } from "./tool"
|
|
|
+import * as McpExa from "./mcp-exa"
|
|
|
import DESCRIPTION from "./codesearch.txt"
|
|
|
-import { abortAfterAny } from "../util/abort"
|
|
|
|
|
|
-const API_CONFIG = {
|
|
|
- BASE_URL: "https://mcp.exa.ai",
|
|
|
- ENDPOINTS: {
|
|
|
- CONTEXT: "/mcp",
|
|
|
- },
|
|
|
-} as const
|
|
|
+export const CodeSearchTool = Tool.defineEffect(
|
|
|
+ "codesearch",
|
|
|
+ Effect.gen(function* () {
|
|
|
+ const http = yield* HttpClient.HttpClient
|
|
|
|
|
|
-interface McpCodeRequest {
|
|
|
- jsonrpc: string
|
|
|
- id: number
|
|
|
- method: string
|
|
|
- params: {
|
|
|
- name: string
|
|
|
- arguments: {
|
|
|
- query: string
|
|
|
- tokensNum: number
|
|
|
- }
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-interface McpCodeResponse {
|
|
|
- jsonrpc: string
|
|
|
- result: {
|
|
|
- content: Array<{
|
|
|
- type: string
|
|
|
- text: string
|
|
|
- }>
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-export const CodeSearchTool = Tool.define("codesearch", {
|
|
|
- description: DESCRIPTION,
|
|
|
- parameters: z.object({
|
|
|
- query: z
|
|
|
- .string()
|
|
|
- .describe(
|
|
|
- "Search query to find relevant context for APIs, Libraries, and SDKs. For example, 'React useState hook examples', 'Python pandas dataframe filtering', 'Express.js middleware', 'Next js partial prerendering configuration'",
|
|
|
- ),
|
|
|
- tokensNum: z
|
|
|
- .number()
|
|
|
- .min(1000)
|
|
|
- .max(50000)
|
|
|
- .default(5000)
|
|
|
- .describe(
|
|
|
- "Number of tokens to return (1000-50000). Default is 5000 tokens. Adjust this value based on how much context you need - use lower values for focused queries and higher values for comprehensive documentation.",
|
|
|
- ),
|
|
|
- }),
|
|
|
- async execute(params, ctx) {
|
|
|
- await ctx.ask({
|
|
|
- permission: "codesearch",
|
|
|
- patterns: [params.query],
|
|
|
- always: ["*"],
|
|
|
- metadata: {
|
|
|
- query: params.query,
|
|
|
- tokensNum: params.tokensNum,
|
|
|
- },
|
|
|
- })
|
|
|
-
|
|
|
- const codeRequest: McpCodeRequest = {
|
|
|
- jsonrpc: "2.0",
|
|
|
- id: 1,
|
|
|
- method: "tools/call",
|
|
|
- params: {
|
|
|
- name: "get_code_context_exa",
|
|
|
- arguments: {
|
|
|
- query: params.query,
|
|
|
- tokensNum: params.tokensNum || 5000,
|
|
|
- },
|
|
|
- },
|
|
|
- }
|
|
|
-
|
|
|
- const { signal, clearTimeout } = abortAfterAny(30000, ctx.abort)
|
|
|
-
|
|
|
- try {
|
|
|
- const headers: Record<string, string> = {
|
|
|
- accept: "application/json, text/event-stream",
|
|
|
- "content-type": "application/json",
|
|
|
- }
|
|
|
+ return {
|
|
|
+ description: DESCRIPTION,
|
|
|
+ parameters: z.object({
|
|
|
+ query: z
|
|
|
+ .string()
|
|
|
+ .describe(
|
|
|
+ "Search query to find relevant context for APIs, Libraries, and SDKs. For example, 'React useState hook examples', 'Python pandas dataframe filtering', 'Express.js middleware', 'Next js partial prerendering configuration'",
|
|
|
+ ),
|
|
|
+ tokensNum: z
|
|
|
+ .number()
|
|
|
+ .min(1000)
|
|
|
+ .max(50000)
|
|
|
+ .default(5000)
|
|
|
+ .describe(
|
|
|
+ "Number of tokens to return (1000-50000). Default is 5000 tokens. Adjust this value based on how much context you need - use lower values for focused queries and higher values for comprehensive documentation.",
|
|
|
+ ),
|
|
|
+ }),
|
|
|
+ execute: (params: { query: string; tokensNum: number }, ctx: Tool.Context) =>
|
|
|
+ Effect.gen(function* () {
|
|
|
+ yield* Effect.promise(() =>
|
|
|
+ ctx.ask({
|
|
|
+ permission: "codesearch",
|
|
|
+ patterns: [params.query],
|
|
|
+ always: ["*"],
|
|
|
+ metadata: {
|
|
|
+ query: params.query,
|
|
|
+ tokensNum: params.tokensNum,
|
|
|
+ },
|
|
|
+ }),
|
|
|
+ )
|
|
|
|
|
|
- const response = await fetch(`${API_CONFIG.BASE_URL}${API_CONFIG.ENDPOINTS.CONTEXT}`, {
|
|
|
- method: "POST",
|
|
|
- headers,
|
|
|
- body: JSON.stringify(codeRequest),
|
|
|
- signal,
|
|
|
- })
|
|
|
+ const result = yield* McpExa.call(
|
|
|
+ http,
|
|
|
+ "get_code_context_exa",
|
|
|
+ McpExa.CodeArgs,
|
|
|
+ {
|
|
|
+ query: params.query,
|
|
|
+ tokensNum: params.tokensNum || 5000,
|
|
|
+ },
|
|
|
+ "30 seconds",
|
|
|
+ )
|
|
|
|
|
|
- clearTimeout()
|
|
|
-
|
|
|
- if (!response.ok) {
|
|
|
- const errorText = await response.text()
|
|
|
- throw new Error(`Code search error (${response.status}): ${errorText}`)
|
|
|
- }
|
|
|
-
|
|
|
- const responseText = await response.text()
|
|
|
-
|
|
|
- // Parse SSE response
|
|
|
- const lines = responseText.split("\n")
|
|
|
- for (const line of lines) {
|
|
|
- if (line.startsWith("data: ")) {
|
|
|
- const data: McpCodeResponse = JSON.parse(line.substring(6))
|
|
|
- if (data.result && data.result.content && data.result.content.length > 0) {
|
|
|
- return {
|
|
|
- output: data.result.content[0].text,
|
|
|
- title: `Code search: ${params.query}`,
|
|
|
- metadata: {},
|
|
|
- }
|
|
|
+ return {
|
|
|
+ output:
|
|
|
+ result ??
|
|
|
+ "No code snippets or documentation found. Please try a different query, be more specific about the library or programming concept, or check the spelling of framework names.",
|
|
|
+ title: `Code search: ${params.query}`,
|
|
|
+ metadata: {},
|
|
|
}
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- return {
|
|
|
- output:
|
|
|
- "No code snippets or documentation found. Please try a different query, be more specific about the library or programming concept, or check the spelling of framework names.",
|
|
|
- title: `Code search: ${params.query}`,
|
|
|
- metadata: {},
|
|
|
- }
|
|
|
- } catch (error) {
|
|
|
- clearTimeout()
|
|
|
-
|
|
|
- if (error instanceof Error && error.name === "AbortError") {
|
|
|
- throw new Error("Code search request timed out")
|
|
|
- }
|
|
|
-
|
|
|
- throw error
|
|
|
+ }).pipe(Effect.runPromise),
|
|
|
}
|
|
|
- },
|
|
|
-})
|
|
|
+ }),
|
|
|
+)
|