refactor(tool): convert codesearch tool internals to Effect (#21811)

This commit is contained in:
Kit Langton
2026-04-10 11:49:20 -04:00
committed by GitHub
parent 00e39d2114
commit bf601628db
2 changed files with 58 additions and 124 deletions
+25 -92
View File
@@ -1,39 +1,16 @@
import z from "zod" import z from "zod"
import { Effect } from "effect"
import { HttpClient } from "effect/unstable/http"
import { Tool } from "./tool" import { Tool } from "./tool"
import * as McpExa from "./mcp-exa"
import DESCRIPTION from "./codesearch.txt" import DESCRIPTION from "./codesearch.txt"
import { abortAfterAny } from "../util/abort"
const API_CONFIG = { export const CodeSearchTool = Tool.defineEffect(
BASE_URL: "https://mcp.exa.ai", "codesearch",
ENDPOINTS: { Effect.gen(function* () {
CONTEXT: "/mcp", const http = yield* HttpClient.HttpClient
},
} as const
interface McpCodeRequest { return {
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, description: DESCRIPTION,
parameters: z.object({ parameters: z.object({
query: z query: z
@@ -50,8 +27,10 @@ export const CodeSearchTool = Tool.define("codesearch", {
"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.", "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) { execute: (params: { query: string; tokensNum: number }, ctx: Tool.Context) =>
await ctx.ask({ Effect.gen(function* () {
yield* Effect.promise(() =>
ctx.ask({
permission: "codesearch", permission: "codesearch",
patterns: [params.query], patterns: [params.query],
always: ["*"], always: ["*"],
@@ -59,74 +38,28 @@ export const CodeSearchTool = Tool.define("codesearch", {
query: params.query, query: params.query,
tokensNum: params.tokensNum, tokensNum: params.tokensNum,
}, },
}) }),
)
const codeRequest: McpCodeRequest = { const result = yield* McpExa.call(
jsonrpc: "2.0", http,
id: 1, "get_code_context_exa",
method: "tools/call", McpExa.CodeArgs,
params: { {
name: "get_code_context_exa",
arguments: {
query: params.query, query: params.query,
tokensNum: params.tokensNum || 5000, tokensNum: params.tokensNum || 5000,
}, },
}, "30 seconds",
} )
const { signal, clearTimeout } = abortAfterAny(30000, ctx.abort)
try {
const headers: Record<string, string> = {
accept: "application/json, text/event-stream",
"content-type": "application/json",
}
const response = await fetch(`${API_CONFIG.BASE_URL}${API_CONFIG.ENDPOINTS.CONTEXT}`, {
method: "POST",
headers,
body: JSON.stringify(codeRequest),
signal,
})
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 { return {
output: 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.", "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}`, title: `Code search: ${params.query}`,
metadata: {}, metadata: {},
} }
} catch (error) { }).pipe(Effect.runPromise),
clearTimeout()
if (error instanceof Error && error.name === "AbortError") {
throw new Error("Code search request timed out")
} }
}),
throw error )
}
},
})
+2 -1
View File
@@ -102,6 +102,7 @@ export namespace ToolRegistry {
const plan = yield* PlanExitTool const plan = yield* PlanExitTool
const webfetch = yield* WebFetchTool const webfetch = yield* WebFetchTool
const websearch = yield* WebSearchTool const websearch = yield* WebSearchTool
const codesearch = yield* CodeSearchTool
const state = yield* InstanceState.make<State>( const state = yield* InstanceState.make<State>(
Effect.fn("ToolRegistry.state")(function* (ctx) { Effect.fn("ToolRegistry.state")(function* (ctx) {
@@ -170,7 +171,7 @@ export namespace ToolRegistry {
fetch: Tool.init(webfetch), fetch: Tool.init(webfetch),
todo: Tool.init(todo), todo: Tool.init(todo),
search: Tool.init(websearch), search: Tool.init(websearch),
code: Tool.init(CodeSearchTool), code: Tool.init(codesearch),
skill: Tool.init(SkillTool), skill: Tool.init(SkillTool),
patch: Tool.init(ApplyPatchTool), patch: Tool.init(ApplyPatchTool),
question: Tool.init(question), question: Tool.init(question),