cursor-opencode-provider 0.2.7 → 0.2.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,127 @@
1
+ import { tool } from "@opencode-ai/plugin";
2
+ const EXA_MCP_URL = "https://mcp.exa.ai/mcp";
3
+ const WEB_SEARCH_TIMEOUT_MS = 25_000;
4
+ function exaMcpUrl() {
5
+ const apiKey = process.env.EXA_API_KEY;
6
+ if (!apiKey)
7
+ return EXA_MCP_URL;
8
+ const url = new URL(EXA_MCP_URL);
9
+ url.searchParams.set("exaApiKey", apiKey);
10
+ return url.href;
11
+ }
12
+ function mcpText(value) {
13
+ if (!value || typeof value !== "object")
14
+ return undefined;
15
+ const result = value.result;
16
+ if (!result || typeof result !== "object")
17
+ return undefined;
18
+ const content = result.content;
19
+ if (!Array.isArray(content))
20
+ return undefined;
21
+ for (const item of content) {
22
+ if (item &&
23
+ typeof item === "object" &&
24
+ item.type === "text" &&
25
+ typeof item.text === "string") {
26
+ return item.text;
27
+ }
28
+ }
29
+ return undefined;
30
+ }
31
+ export function parseOpenCodeWebSearchResponse(raw) {
32
+ const trimmed = raw.trim();
33
+ if (!trimmed)
34
+ return undefined;
35
+ try {
36
+ const text = mcpText(JSON.parse(trimmed));
37
+ if (text)
38
+ return text;
39
+ }
40
+ catch {
41
+ // MCP may respond as an SSE stream instead of one JSON object.
42
+ }
43
+ for (const line of raw.split("\n")) {
44
+ if (!line.startsWith("data: "))
45
+ continue;
46
+ try {
47
+ const text = mcpText(JSON.parse(line.slice(6)));
48
+ if (text)
49
+ return text;
50
+ }
51
+ catch {
52
+ // Ignore non-JSON SSE events.
53
+ }
54
+ }
55
+ return undefined;
56
+ }
57
+ export async function executeOpenCodeWebSearch(args, context, fetchImpl = fetch) {
58
+ await context.ask({
59
+ permission: "websearch",
60
+ patterns: [args.query],
61
+ always: ["*"],
62
+ metadata: {
63
+ query: args.query,
64
+ numResults: args.numResults,
65
+ livecrawl: args.livecrawl,
66
+ type: args.type,
67
+ contextMaxCharacters: args.contextMaxCharacters,
68
+ provider: "exa",
69
+ },
70
+ });
71
+ const controller = new AbortController();
72
+ const abort = () => controller.abort(context.abort.reason);
73
+ if (context.abort.aborted)
74
+ abort();
75
+ else
76
+ context.abort.addEventListener("abort", abort, { once: true });
77
+ const timeout = setTimeout(() => controller.abort(new Error("Web search timed out")), WEB_SEARCH_TIMEOUT_MS);
78
+ try {
79
+ const response = await fetchImpl(exaMcpUrl(), {
80
+ method: "POST",
81
+ headers: {
82
+ accept: "application/json, text/event-stream",
83
+ "content-type": "application/json",
84
+ },
85
+ body: JSON.stringify({
86
+ jsonrpc: "2.0",
87
+ id: 1,
88
+ method: "tools/call",
89
+ params: {
90
+ name: "web_search_exa",
91
+ arguments: {
92
+ query: args.query,
93
+ type: args.type ?? "auto",
94
+ numResults: args.numResults ?? 8,
95
+ livecrawl: args.livecrawl ?? "fallback",
96
+ contextMaxCharacters: args.contextMaxCharacters,
97
+ },
98
+ },
99
+ }),
100
+ signal: controller.signal,
101
+ });
102
+ const raw = await response.text();
103
+ if (!response.ok)
104
+ throw new Error(`Web search failed (${response.status}): ${raw.slice(0, 500)}`);
105
+ const output = parseOpenCodeWebSearchResponse(raw) ?? "No search results found. Please try a different query.";
106
+ return {
107
+ title: `Exa Web Search: ${args.query}`,
108
+ output,
109
+ metadata: { provider: "exa" },
110
+ };
111
+ }
112
+ finally {
113
+ clearTimeout(timeout);
114
+ context.abort.removeEventListener("abort", abort);
115
+ }
116
+ }
117
+ export const openCodeWebSearchTool = tool({
118
+ description: "Search the web for current information using OpenCode's web search backend.",
119
+ args: {
120
+ query: tool.schema.string().describe("Web search query"),
121
+ numResults: tool.schema.number().int().min(1).max(20).optional(),
122
+ livecrawl: tool.schema.enum(["fallback", "preferred"]).optional(),
123
+ type: tool.schema.enum(["auto", "fast", "deep"]).optional(),
124
+ contextMaxCharacters: tool.schema.number().int().positive().optional(),
125
+ },
126
+ execute: executeOpenCodeWebSearch,
127
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cursor-opencode-provider",
3
- "version": "0.2.7",
3
+ "version": "0.2.8",
4
4
  "description": "Use Cursor subscription models from OpenCode via Cursor's Connect-RPC agent protocol",
5
5
  "type": "module",
6
6
  "license": "MIT",