Version check of VS Code for utilizing GPT-5-Codex with GitHub Copilot authentication improperly reads installed VS Code version, blocking usage #2766

Closed
opened 2026-02-16 17:37:09 -05:00 by yindo · 4 comments
Owner

Originally created by @notactuallytreyanastasio on GitHub (Nov 10, 2025).

Description

Version: OpenCode 1.0.55

Visual Studio Code 1.105.1

I am authenticating with Github Copilot to use OpenCode. It required authentication via a third party IDP that my company uses to log in. This all went fine, and I authenticated with OpenCode. All other models except GPT-5-Codex work.

When I ctrl-x-m and choose the GPT-5-Codex model I get the following error:

model gpt-5-codex is not supported in this VS Code version. Please update it to version 1.104.1 or newer

It would seem that the version-checking here is not resolving correctly and is thinking that I am having an issue.

Note: before installing VS Code, I got the exact same error message

Note: I do not have the visual studio CLI (code) available in my $PATH or installed at all to my knowledge.

I am authenticated with the same account I am trying to use for Copilot in the VS Code application as well.

OpenCode version

1.0.55

Steps to reproduce

  1. Authenticate with GitHub copilot using opencode auth login using a 2FA enabled, 3rd party IDP for the authentication GitHub account
  2. Start up the CLI
  3. Choose a model, like GPT-5 regular or Sonnet 4.5 or whatever with ctrl-x-m
  4. Ask a basic question, note that response comes
  5. ctrl-x-m again, choose GPT-5-Codex
  6. Ask a question

This should result in a similar situation.

Other types of accounts may experience this, but due to the specific nature of it all I wanted to make sure I noted it.

I did some digging with GPT-5 trying to just get a better understanding. It suggested this diff in 2 commits:

commit 3f5acc3dffeb3172e0ac075e56fb8e8c8250e267
Author: Dax Raad <d@ironbay.co>
Date:   Mon Nov 10 16:39:40 2025 -0500

    add web and codesearch tools

diff --git a/.opencode/opencode.json b/.opencode/opencode.json
index ae036254..7da874d3 100644
--- a/.opencode/opencode.json
+++ b/.opencode/opencode.json
@@ -1,17 +1,4 @@
 {
   "$schema": "https://opencode.ai/config.json",
-  "plugin": ["opencode-openai-codex-auth"],
-  "mcp": {
-    "weather": {
-      "type": "local",
-      "command": ["bun", "x", "@h1deya/mcp-server-weather"]
-    },
-    "context7": {
-      "type": "remote",
-      "url": "https://mcp.context7.com/mcp",
-      "headers": {
-        "CONTEXT7_API_KEY": "{env:CONTEXT7_API_KEY}"
-      }
-    }
-  }
+  "plugin": ["opencode-openai-codex-auth"]
 }
diff --git a/packages/opencode/src/flag/flag.ts b/packages/opencode/src/flag/flag.ts
index 86ca0765..77a88843 100644
--- a/packages/opencode/src/flag/flag.ts
+++ b/packages/opencode/src/flag/flag.ts
@@ -13,9 +13,11 @@ export namespace Flag {
   export const OPENCODE_FAKE_VCS = process.env["OPENCODE_FAKE_VCS"]
 
   // Experimental
+  export const OPENCODE_EXPERIMENTAL = truthy("OPENCODE_EXPERIMENTAL")
   export const OPENCODE_EXPERIMENTAL_WATCHER = truthy("OPENCODE_EXPERIMENTAL_WATCHER")
   export const OPENCODE_EXPERIMENTAL_TURN_SUMMARY = truthy("OPENCODE_EXPERIMENTAL_TURN_SUMMARY")
   export const OPENCODE_EXPERIMENTAL_NO_BOOTSTRAP = truthy("OPENCODE_EXPERIMENTAL_NO_BOOTSTRAP")
+  export const OPENCODE_EXPERIMENTAL_EXA = OPENCODE_EXPERIMENTAL || truthy("OPENCODE_EXPERIMENTAL_EXA")
 
   function truthy(key: string) {
     const value = process.env[key]?.toLowerCase()
diff --git a/packages/opencode/src/tool/codesearch.ts b/packages/opencode/src/tool/codesearch.ts
new file mode 100644
index 00000000..0227c06f
--- /dev/null
+++ b/packages/opencode/src/tool/codesearch.ts
@@ -0,0 +1,138 @@
+import z from "zod"
+import { Tool } from "./tool"
+import DESCRIPTION from "./codesearch.txt"
+import { Config } from "../config/config"
+import { Permission } from "../permission"
+
+const API_CONFIG = {
+  BASE_URL: "https://mcp.exa.ai",
+  ENDPOINTS: {
+    CONTEXT: "/mcp",
+  },
+} as const
+
+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) {
+    const cfg = await Config.get()
+    if (cfg.permission?.webfetch === "ask")
+      await Permission.ask({
+        type: "codesearch",
+        sessionID: ctx.sessionID,
+        messageID: ctx.messageID,
+        callID: ctx.callID,
+        title: "Search code for: " + params.query,
+        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 controller = new AbortController()
+    const timeoutId = setTimeout(() => controller.abort(), 30000)
+
+    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: AbortSignal.any([controller.signal, ctx.abort]),
+      })
+
+      clearTimeout(timeoutId)
+
+      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:
+          "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(timeoutId)
+
+      if (error instanceof Error && error.name === "AbortError") {
+        throw new Error("Code search request timed out")
+      }
+
+      throw error
+    }
+  },
+})
diff --git a/packages/opencode/src/tool/codesearch.txt b/packages/opencode/src/tool/codesearch.txt
new file mode 100644
index 00000000..4187f08d
--- /dev/null
+++ b/packages/opencode/src/tool/codesearch.txt
@@ -0,0 +1,12 @@
+- Search and get relevant context for any programming task using Exa Code API
+- Provides the highest quality and freshest context for libraries, SDKs, and APIs
+- Use this tool for ANY question or task related to programming
+- Returns comprehensive code examples, documentation, and API references
+- Optimized for finding specific programming patterns and solutions
+
+Usage notes:
+  - Adjustable token count (1000-50000) for focused or comprehensive results
+  - Default 5000 tokens provides balanced context for most queries
+  - Use lower values for specific questions, higher values for comprehensive documentation
+  - Supports queries about frameworks, libraries, APIs, and programming concepts
+  - Examples: 'React useState hook examples', 'Python pandas dataframe filtering', 'Express.js middleware'
diff --git a/packages/opencode/src/tool/registry.ts b/packages/opencode/src/tool/registry.ts
index 6234a4e6..f7888761 100644
--- a/packages/opencode/src/tool/registry.ts
+++ b/packages/opencode/src/tool/registry.ts
@@ -17,6 +17,9 @@ import path from "path"
 import { type ToolDefinition } from "@opencode-ai/plugin"
 import z from "zod"
 import { Plugin } from "../plugin"
+import { WebSearchTool } from "./websearch"
+import { CodeSearchTool } from "./codesearch"
+import { Flag } from "@/flag/flag"
 
 export namespace ToolRegistry {
   export const state = Instance.state(async () => {
@@ -91,6 +94,7 @@ export namespace ToolRegistry {
       TodoWriteTool,
       TodoReadTool,
       TaskTool,
+      ...(Flag.OPENCODE_EXPERIMENTAL_EXA ? [WebSearchTool, CodeSearchTool] : []),
       ...custom,
     ]
   }
diff --git a/packages/opencode/src/tool/websearch.ts b/packages/opencode/src/tool/websearch.ts
new file mode 100644
index 00000000..4064d12f
--- /dev/null
+++ b/packages/opencode/src/tool/websearch.ts
@@ -0,0 +1,150 @@
+import z from "zod"
+import { Tool } from "./tool"
+import DESCRIPTION from "./websearch.txt"
+import { Config } from "../config/config"
+import { Permission } from "../permission"
+
+const API_CONFIG = {
+  BASE_URL: "https://mcp.exa.ai",
+  ENDPOINTS: {
+    SEARCH: "/mcp",
+  },
+  DEFAULT_NUM_RESULTS: 8,
+} as const
+
+interface McpSearchRequest {
+  jsonrpc: string
+  id: number
+  method: string
+  params: {
+    name: string
+    arguments: {
+      query: string
+      numResults?: number
+      livecrawl?: "fallback" | "preferred"
+      type?: "auto" | "fast" | "deep"
+      contextMaxCharacters?: number
+    }
+  }
+}
+
+interface McpSearchResponse {
+  jsonrpc: string
+  result: {
+    content: Array<{
+      type: string
+      text: string
+    }>
+  }
+}
+
+export const WebSearchTool = Tool.define("websearch", {
+  description: DESCRIPTION,
+  parameters: z.object({
+    query: z.string().describe("Websearch query"),
+    numResults: z.number().optional().describe("Number of search results to return (default: 8)"),
+    livecrawl: z
+      .enum(["fallback", "preferred"])
+      .optional()
+      .describe(
+        "Live crawl mode - 'fallback': use live crawling as backup if cached content unavailable, 'preferred': prioritize live crawling (default: 'fallback')",
+      ),
+    type: z
+      .enum(["auto", "fast", "deep"])
+      .optional()
+      .describe("Search type - 'auto': balanced search (default), 'fast': quick results, 'deep': comprehensive search"),
+    contextMaxCharacters: z
+      .number()
+      .optional()
+      .describe("Maximum characters for context string optimized for LLMs (default: 10000)"),
+  }),
+  async execute(params, ctx) {
+    const cfg = await Config.get()
+    if (cfg.permission?.webfetch === "ask")
+      await Permission.ask({
+        type: "websearch",
+        sessionID: ctx.sessionID,
+        messageID: ctx.messageID,
+        callID: ctx.callID,
+        title: "Search web for: " + params.query,
+        metadata: {
+          query: params.query,
+          numResults: params.numResults,
+          livecrawl: params.livecrawl,
+          type: params.type,
+          contextMaxCharacters: params.contextMaxCharacters,
+        },
+      })
+
+    const searchRequest: McpSearchRequest = {
+      jsonrpc: "2.0",
+      id: 1,
+      method: "tools/call",
+      params: {
+        name: "web_search_exa",
+        arguments: {
+          query: params.query,
+          type: params.type || "auto",
+          numResults: params.numResults || API_CONFIG.DEFAULT_NUM_RESULTS,
+          livecrawl: params.livecrawl || "fallback",
+          contextMaxCharacters: params.contextMaxCharacters,
+        },
+      },
+    }
+
+    const controller = new AbortController()
+    const timeoutId = setTimeout(() => controller.abort(), 25000)
+
+    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.SEARCH}`, {
+        method: "POST",
+        headers,
+        body: JSON.stringify(searchRequest),
+        signal: AbortSignal.any([controller.signal, ctx.abort]),
+      })
+
+      clearTimeout(timeoutId)
+
+      if (!response.ok) {
+        const errorText = await response.text()
+        throw new Error(`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: McpSearchResponse = JSON.parse(line.substring(6))
+          if (data.result && data.result.content && data.result.content.length > 0) {
+            return {
+              output: data.result.content[0].text,
+              title: `Web search: ${params.query}`,
+              metadata: {},
+            }
+          }
+        }
+      }
+
+      return {
+        output: "No search results found. Please try a different query.",
+        title: `Web search: ${params.query}`,
+        metadata: {},
+      }
+    } catch (error) {
+      clearTimeout(timeoutId)
+
+      if (error instanceof Error && error.name === "AbortError") {
+        throw new Error("Search request timed out")
+      }
+
+      throw error
+    }
+  },
+})
diff --git a/packages/opencode/src/tool/websearch.txt b/packages/opencode/src/tool/websearch.txt
index 09d2eaa2..22427e24 100644
--- a/packages/opencode/src/tool/websearch.txt
+++ b/packages/opencode/src/tool/websearch.txt
@@ -1,11 +1,11 @@
-
-- Allows opencode to search the web and use the results to inform responses
+- Search the web using Exa AI - performs real-time web searches and can scrape content from specific URLs
 - Provides up-to-date information for current events and recent data
-- Returns search result information formatted as search result blocks
-- Use this tool for accessing information beyond Claude's knowledge cutoff
+- Supports configurable result counts and returns the content from the most relevant websites
+- Use this tool for accessing information beyond knowledge cutoff
 - Searches are performed automatically within a single API call
 
 Usage notes:
-  - Domain filtering is supported to include or block specific websites
-  - Web search is only available in the US
-
+  - Supports live crawling modes: 'fallback' (backup if cached unavailable) or 'preferred' (prioritize live crawling)
+  - Search types: 'auto' (balanced), 'fast' (quick results), 'deep' (comprehensive search)
+  - Configurable context length for optimal LLM integration
+  - Domain filtering and advanced search options available

I am not sure if that's super helpful, and feels like a lot of extension, but am sharing in case it might spark any other further ideas/provide more info and context.

Screenshot and/or share link

Image Image

Operating System

MacOS 15.5 (24F74)

Terminal

iTerm2

Originally created by @notactuallytreyanastasio on GitHub (Nov 10, 2025). ### Description Version: OpenCode 1.0.55 Visual Studio Code 1.105.1 I am authenticating with Github Copilot to use OpenCode. It required authentication via a third party IDP that my company uses to log in. This all went fine, and I authenticated with OpenCode. All other models except GPT-5-Codex work. When I `ctrl-x-m` and choose the GPT-5-Codex model I get the following error: ``` model gpt-5-codex is not supported in this VS Code version. Please update it to version 1.104.1 or newer ``` It would seem that the version-checking here is not resolving correctly and is thinking that I am having an issue. Note: before installing VS Code, I got the exact same error message Note: I do not have the visual studio CLI (`code`) available in my $PATH or installed at all to my knowledge. I am authenticated with the same account I am trying to use for Copilot in the VS Code application as well. ### OpenCode version 1.0.55 ### Steps to reproduce 1. Authenticate with GitHub copilot using `opencode auth login` using a 2FA enabled, 3rd party IDP for the authentication GitHub account 2. Start up the CLI 3. Choose a model, like GPT-5 regular or Sonnet 4.5 or whatever with `ctrl-x-m` 4. Ask a basic question, note that response comes 5. `ctrl-x-m` again, choose `GPT-5-Codex` 6. Ask a question This should result in a similar situation. Other types of accounts may experience this, but due to the specific nature of it all I wanted to make sure I noted it. I did some digging with GPT-5 trying to just get a better understanding. It suggested this diff in 2 commits: ```diff commit 3f5acc3dffeb3172e0ac075e56fb8e8c8250e267 Author: Dax Raad <d@ironbay.co> Date: Mon Nov 10 16:39:40 2025 -0500 add web and codesearch tools diff --git a/.opencode/opencode.json b/.opencode/opencode.json index ae036254..7da874d3 100644 --- a/.opencode/opencode.json +++ b/.opencode/opencode.json @@ -1,17 +1,4 @@ { "$schema": "https://opencode.ai/config.json", - "plugin": ["opencode-openai-codex-auth"], - "mcp": { - "weather": { - "type": "local", - "command": ["bun", "x", "@h1deya/mcp-server-weather"] - }, - "context7": { - "type": "remote", - "url": "https://mcp.context7.com/mcp", - "headers": { - "CONTEXT7_API_KEY": "{env:CONTEXT7_API_KEY}" - } - } - } + "plugin": ["opencode-openai-codex-auth"] } diff --git a/packages/opencode/src/flag/flag.ts b/packages/opencode/src/flag/flag.ts index 86ca0765..77a88843 100644 --- a/packages/opencode/src/flag/flag.ts +++ b/packages/opencode/src/flag/flag.ts @@ -13,9 +13,11 @@ export namespace Flag { export const OPENCODE_FAKE_VCS = process.env["OPENCODE_FAKE_VCS"] // Experimental + export const OPENCODE_EXPERIMENTAL = truthy("OPENCODE_EXPERIMENTAL") export const OPENCODE_EXPERIMENTAL_WATCHER = truthy("OPENCODE_EXPERIMENTAL_WATCHER") export const OPENCODE_EXPERIMENTAL_TURN_SUMMARY = truthy("OPENCODE_EXPERIMENTAL_TURN_SUMMARY") export const OPENCODE_EXPERIMENTAL_NO_BOOTSTRAP = truthy("OPENCODE_EXPERIMENTAL_NO_BOOTSTRAP") + export const OPENCODE_EXPERIMENTAL_EXA = OPENCODE_EXPERIMENTAL || truthy("OPENCODE_EXPERIMENTAL_EXA") function truthy(key: string) { const value = process.env[key]?.toLowerCase() diff --git a/packages/opencode/src/tool/codesearch.ts b/packages/opencode/src/tool/codesearch.ts new file mode 100644 index 00000000..0227c06f --- /dev/null +++ b/packages/opencode/src/tool/codesearch.ts @@ -0,0 +1,138 @@ +import z from "zod" +import { Tool } from "./tool" +import DESCRIPTION from "./codesearch.txt" +import { Config } from "../config/config" +import { Permission } from "../permission" + +const API_CONFIG = { + BASE_URL: "https://mcp.exa.ai", + ENDPOINTS: { + CONTEXT: "/mcp", + }, +} as const + +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) { + const cfg = await Config.get() + if (cfg.permission?.webfetch === "ask") + await Permission.ask({ + type: "codesearch", + sessionID: ctx.sessionID, + messageID: ctx.messageID, + callID: ctx.callID, + title: "Search code for: " + params.query, + 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 controller = new AbortController() + const timeoutId = setTimeout(() => controller.abort(), 30000) + + 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: AbortSignal.any([controller.signal, ctx.abort]), + }) + + clearTimeout(timeoutId) + + 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: + "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(timeoutId) + + if (error instanceof Error && error.name === "AbortError") { + throw new Error("Code search request timed out") + } + + throw error + } + }, +}) diff --git a/packages/opencode/src/tool/codesearch.txt b/packages/opencode/src/tool/codesearch.txt new file mode 100644 index 00000000..4187f08d --- /dev/null +++ b/packages/opencode/src/tool/codesearch.txt @@ -0,0 +1,12 @@ +- Search and get relevant context for any programming task using Exa Code API +- Provides the highest quality and freshest context for libraries, SDKs, and APIs +- Use this tool for ANY question or task related to programming +- Returns comprehensive code examples, documentation, and API references +- Optimized for finding specific programming patterns and solutions + +Usage notes: + - Adjustable token count (1000-50000) for focused or comprehensive results + - Default 5000 tokens provides balanced context for most queries + - Use lower values for specific questions, higher values for comprehensive documentation + - Supports queries about frameworks, libraries, APIs, and programming concepts + - Examples: 'React useState hook examples', 'Python pandas dataframe filtering', 'Express.js middleware' diff --git a/packages/opencode/src/tool/registry.ts b/packages/opencode/src/tool/registry.ts index 6234a4e6..f7888761 100644 --- a/packages/opencode/src/tool/registry.ts +++ b/packages/opencode/src/tool/registry.ts @@ -17,6 +17,9 @@ import path from "path" import { type ToolDefinition } from "@opencode-ai/plugin" import z from "zod" import { Plugin } from "../plugin" +import { WebSearchTool } from "./websearch" +import { CodeSearchTool } from "./codesearch" +import { Flag } from "@/flag/flag" export namespace ToolRegistry { export const state = Instance.state(async () => { @@ -91,6 +94,7 @@ export namespace ToolRegistry { TodoWriteTool, TodoReadTool, TaskTool, + ...(Flag.OPENCODE_EXPERIMENTAL_EXA ? [WebSearchTool, CodeSearchTool] : []), ...custom, ] } diff --git a/packages/opencode/src/tool/websearch.ts b/packages/opencode/src/tool/websearch.ts new file mode 100644 index 00000000..4064d12f --- /dev/null +++ b/packages/opencode/src/tool/websearch.ts @@ -0,0 +1,150 @@ +import z from "zod" +import { Tool } from "./tool" +import DESCRIPTION from "./websearch.txt" +import { Config } from "../config/config" +import { Permission } from "../permission" + +const API_CONFIG = { + BASE_URL: "https://mcp.exa.ai", + ENDPOINTS: { + SEARCH: "/mcp", + }, + DEFAULT_NUM_RESULTS: 8, +} as const + +interface McpSearchRequest { + jsonrpc: string + id: number + method: string + params: { + name: string + arguments: { + query: string + numResults?: number + livecrawl?: "fallback" | "preferred" + type?: "auto" | "fast" | "deep" + contextMaxCharacters?: number + } + } +} + +interface McpSearchResponse { + jsonrpc: string + result: { + content: Array<{ + type: string + text: string + }> + } +} + +export const WebSearchTool = Tool.define("websearch", { + description: DESCRIPTION, + parameters: z.object({ + query: z.string().describe("Websearch query"), + numResults: z.number().optional().describe("Number of search results to return (default: 8)"), + livecrawl: z + .enum(["fallback", "preferred"]) + .optional() + .describe( + "Live crawl mode - 'fallback': use live crawling as backup if cached content unavailable, 'preferred': prioritize live crawling (default: 'fallback')", + ), + type: z + .enum(["auto", "fast", "deep"]) + .optional() + .describe("Search type - 'auto': balanced search (default), 'fast': quick results, 'deep': comprehensive search"), + contextMaxCharacters: z + .number() + .optional() + .describe("Maximum characters for context string optimized for LLMs (default: 10000)"), + }), + async execute(params, ctx) { + const cfg = await Config.get() + if (cfg.permission?.webfetch === "ask") + await Permission.ask({ + type: "websearch", + sessionID: ctx.sessionID, + messageID: ctx.messageID, + callID: ctx.callID, + title: "Search web for: " + params.query, + metadata: { + query: params.query, + numResults: params.numResults, + livecrawl: params.livecrawl, + type: params.type, + contextMaxCharacters: params.contextMaxCharacters, + }, + }) + + const searchRequest: McpSearchRequest = { + jsonrpc: "2.0", + id: 1, + method: "tools/call", + params: { + name: "web_search_exa", + arguments: { + query: params.query, + type: params.type || "auto", + numResults: params.numResults || API_CONFIG.DEFAULT_NUM_RESULTS, + livecrawl: params.livecrawl || "fallback", + contextMaxCharacters: params.contextMaxCharacters, + }, + }, + } + + const controller = new AbortController() + const timeoutId = setTimeout(() => controller.abort(), 25000) + + 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.SEARCH}`, { + method: "POST", + headers, + body: JSON.stringify(searchRequest), + signal: AbortSignal.any([controller.signal, ctx.abort]), + }) + + clearTimeout(timeoutId) + + if (!response.ok) { + const errorText = await response.text() + throw new Error(`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: McpSearchResponse = JSON.parse(line.substring(6)) + if (data.result && data.result.content && data.result.content.length > 0) { + return { + output: data.result.content[0].text, + title: `Web search: ${params.query}`, + metadata: {}, + } + } + } + } + + return { + output: "No search results found. Please try a different query.", + title: `Web search: ${params.query}`, + metadata: {}, + } + } catch (error) { + clearTimeout(timeoutId) + + if (error instanceof Error && error.name === "AbortError") { + throw new Error("Search request timed out") + } + + throw error + } + }, +}) diff --git a/packages/opencode/src/tool/websearch.txt b/packages/opencode/src/tool/websearch.txt index 09d2eaa2..22427e24 100644 --- a/packages/opencode/src/tool/websearch.txt +++ b/packages/opencode/src/tool/websearch.txt @@ -1,11 +1,11 @@ - -- Allows opencode to search the web and use the results to inform responses +- Search the web using Exa AI - performs real-time web searches and can scrape content from specific URLs - Provides up-to-date information for current events and recent data -- Returns search result information formatted as search result blocks -- Use this tool for accessing information beyond Claude's knowledge cutoff +- Supports configurable result counts and returns the content from the most relevant websites +- Use this tool for accessing information beyond knowledge cutoff - Searches are performed automatically within a single API call Usage notes: - - Domain filtering is supported to include or block specific websites - - Web search is only available in the US - + - Supports live crawling modes: 'fallback' (backup if cached unavailable) or 'preferred' (prioritize live crawling) + - Search types: 'auto' (balanced), 'fast' (quick results), 'deep' (comprehensive search) + - Configurable context length for optimal LLM integration + - Domain filtering and advanced search options available ``` I am not sure if that's super helpful, and feels like a lot of extension, but am sharing in case it might spark any other further ideas/provide more info and context. ### Screenshot and/or share link <img width="237" height="298" alt="Image" src="https://github.com/user-attachments/assets/67126e5b-c5c2-4114-9a61-9142a07595ed" /> <img width="660" height="250" alt="Image" src="https://github.com/user-attachments/assets/f42f25da-7a02-4fbf-a333-dfffc49c80f2" /> ### Operating System MacOS 15.5 (24F74) ### Terminal iTerm2
yindo added the opentuibug labels 2026-02-16 17:37:09 -05:00
yindo closed this issue 2026-02-16 17:37:09 -05:00
Author
Owner

@github-actions[bot] commented on GitHub (Nov 10, 2025):

This issue might be a duplicate of existing issues. Please check:

  • #4172: Same exact error message with GPT-5-Codex and GitHub Copilot authentication - the issue mentions that the endpoint needs to change to support this model and was closed (likely resolved)

Feel free to ignore if none of these address your specific case.

@github-actions[bot] commented on GitHub (Nov 10, 2025): This issue might be a duplicate of existing issues. Please check: - #4172: Same exact error message with GPT-5-Codex and GitHub Copilot authentication - the issue mentions that the endpoint needs to change to support this model and was closed (likely resolved) Feel free to ignore if none of these address your specific case.
Author
Owner

@OpeOginni commented on GitHub (Nov 10, 2025):

Short quick fix, use this in your opencode.json

{
  "$schema": "https://opencode.ai/config.json",
    "provider": {
      "github-copilot": {
        "npm": "@opeoginni/github-copilot-openai-compatible",
        "id": "github-copilot"
      },
  }
}

Note that this solution might make your billed requests to be a lot larger per prompt, but its the fasest way to get it working. The team are def working on supporting Codex 5 from Github normally.

@OpeOginni commented on GitHub (Nov 10, 2025): Short quick fix, use this in your `opencode.json` ```json { "$schema": "https://opencode.ai/config.json", "provider": { "github-copilot": { "npm": "@opeoginni/github-copilot-openai-compatible", "id": "github-copilot" }, } } ``` Note that this solution might make your billed requests to be a lot larger per prompt, but its the fasest way to get it working. The team are def working on supporting Codex 5 from Github normally.
Author
Owner

@rekram1-node commented on GitHub (Nov 10, 2025):

this is 2 fold:

1 needed to update headers (fixed in next release)
2 need to change endpoints, tracking here: https://github.com/sst/opencode/issues/2758

@rekram1-node commented on GitHub (Nov 10, 2025): this is 2 fold: 1 needed to update headers (fixed in next release) 2 need to change endpoints, tracking here: https://github.com/sst/opencode/issues/2758
Author
Owner

@notactuallytreyanastasio commented on GitHub (Nov 10, 2025):

Thanks a ton y'all! Up and running.

@notactuallytreyanastasio commented on GitHub (Nov 10, 2025): Thanks a ton y'all! Up and running.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: anomalyco/opencode#2766