summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorKit Langton <[email protected]>2026-04-10 11:49:20 -0400
committerGitHub <[email protected]>2026-04-10 11:49:20 -0400
commitbf601628db3c187478ff853fe33b91cec652355e (patch)
tree413362702cdf04fbfdedf8190477de660a983b44
parent00e39d211482115245149b2b036625faa7f4d973 (diff)
downloadopencode-bf601628db3c187478ff853fe33b91cec652355e.tar.gz
opencode-bf601628db3c187478ff853fe33b91cec652355e.zip
refactor(tool): convert codesearch tool internals to Effect (#21811)
-rw-r--r--packages/opencode/src/tool/codesearch.ts179
-rw-r--r--packages/opencode/src/tool/registry.ts3
2 files changed, 58 insertions, 124 deletions
diff --git a/packages/opencode/src/tool/codesearch.ts b/packages/opencode/src/tool/codesearch.ts
index 28dd4eb49..7e167df55 100644
--- a/packages/opencode/src/tool/codesearch.ts
+++ b/packages/opencode/src/tool/codesearch.ts
@@ -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),
}
- },
-})
+ }),
+)
diff --git a/packages/opencode/src/tool/registry.ts b/packages/opencode/src/tool/registry.ts
index 389d3d6cd..7a70c7729 100644
--- a/packages/opencode/src/tool/registry.ts
+++ b/packages/opencode/src/tool/registry.ts
@@ -102,6 +102,7 @@ export namespace ToolRegistry {
const plan = yield* PlanExitTool
const webfetch = yield* WebFetchTool
const websearch = yield* WebSearchTool
+ const codesearch = yield* CodeSearchTool
const state = yield* InstanceState.make<State>(
Effect.fn("ToolRegistry.state")(function* (ctx) {
@@ -170,7 +171,7 @@ export namespace ToolRegistry {
fetch: Tool.init(webfetch),
todo: Tool.init(todo),
search: Tool.init(websearch),
- code: Tool.init(CodeSearchTool),
+ code: Tool.init(codesearch),
skill: Tool.init(SkillTool),
patch: Tool.init(ApplyPatchTool),
question: Tool.init(question),