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.
@@ -10,6 +10,8 @@ import { BACKGROUND_SHELL_MARKER, buildBackgroundShellCommand, } from "../shell-
10
10
  // probe (ExecServerMessage #10 → ExecClientMessage #10). request/result share a
11
11
  // field number for every exec variant, so this is also the result field.
12
12
  export const REQUEST_CONTEXT_RESULT_FIELD = 10;
13
+ export const CUSTOM_WEBSEARCH_TOOL = "custom_websearch";
14
+ export const CUSTOM_WEBFETCH_TOOL = "custom_webfetch";
13
15
  /** Match OpenCode's McpCatalog.sanitize for config server ids. */
14
16
  export function sanitizeMcpServerId(value) {
15
17
  return value.replace(/[^a-zA-Z0-9_-]/g, "_");
@@ -51,13 +53,17 @@ export function resolveToolServerIdentity(opencodeName, defaultServer = "opencod
51
53
  */
52
54
  export function toolsToDescriptors(tools, providerIdentifier = "opencode", knownMcpServers = []) {
53
55
  return tools.map((t) => {
54
- const id = resolveToolServerIdentity(t.name, providerIdentifier, knownMcpServers);
56
+ const id = resolveToolServerIdentity(t.sourceName ?? t.name, providerIdentifier, knownMcpServers);
57
+ const collisionSafeWebAlias = t.name === CUSTOM_WEBSEARCH_TOOL || t.name === CUSTOM_WEBFETCH_TOOL;
55
58
  return {
56
- name: `${id.server}-${id.toolName}`,
59
+ // Keep the collision-safe public name exact. Prefixing it with the
60
+ // synthetic default server weakens the distinction from Cursor-native
61
+ // web tools in the model-visible catalog.
62
+ name: collisionSafeWebAlias ? t.name : `${id.server}-${id.toolName}`,
57
63
  description: t.description ?? "",
58
64
  input_schema: encodeJsonAsValue(normalizeInputSchema(t.inputSchema)),
59
65
  provider_identifier: id.server,
60
- tool_name: id.toolName,
66
+ tool_name: t.sourceName ? t.name : id.toolName,
61
67
  };
62
68
  });
63
69
  }
@@ -67,6 +73,67 @@ function normalizeInputSchema(schema) {
67
73
  }
68
74
  return { type: "object", properties: {} };
69
75
  }
76
+ const WEB_ALIAS_RULES = [
77
+ {
78
+ alias: CUSTOM_WEBSEARCH_TOOL,
79
+ exact: ["websearch", "web_search"],
80
+ suffixes: ["_web_search", "-web_search", "_websearch", "-websearch"],
81
+ },
82
+ {
83
+ alias: CUSTOM_WEBFETCH_TOOL,
84
+ exact: ["webfetch", "web_fetch"],
85
+ suffixes: ["_web_fetch", "-web_fetch", "_webfetch", "-webfetch"],
86
+ },
87
+ ];
88
+ /**
89
+ * Give collision-prone web capabilities names Cursor will not confuse with its
90
+ * native UI-bound WebSearch/WebFetch interactions. Exact host tools win; a
91
+ * flattened MCP suffix is accepted only when it identifies one unique tool.
92
+ */
93
+ export function buildCustomWebToolAliases(tools) {
94
+ const aliases = new Map();
95
+ const ambiguous = new Map();
96
+ const replacements = new Map();
97
+ for (const rule of WEB_ALIAS_RULES) {
98
+ // A host/plugin may already expose the collision-safe public name. Keep it
99
+ // authoritative instead of hiding it behind a second mapping.
100
+ if (tools.some((tool) => tool.name === rule.alias))
101
+ continue;
102
+ const exact = tools.filter((tool) => rule.exact.includes(tool.name.toLowerCase()));
103
+ const candidates = exact.length > 0
104
+ ? exact
105
+ : tools.filter((tool) => {
106
+ const name = tool.name.toLowerCase();
107
+ return rule.suffixes.some((suffix) => name.endsWith(suffix));
108
+ });
109
+ if (candidates.length !== 1) {
110
+ if (candidates.length > 1)
111
+ ambiguous.set(rule.alias, candidates.map((tool) => tool.name));
112
+ continue;
113
+ }
114
+ const original = candidates[0].name;
115
+ aliases.set(rule.alias, original);
116
+ replacements.set(original, rule.alias);
117
+ }
118
+ return {
119
+ advertisedTools: tools.map((tool) => {
120
+ const alias = replacements.get(tool.name);
121
+ return alias ? { ...tool, name: alias, sourceName: tool.name } : { ...tool };
122
+ }),
123
+ aliases,
124
+ ambiguous,
125
+ };
126
+ }
127
+ export function resolveCustomWebToolAlias(toolName, aliases) {
128
+ const direct = aliases?.get(toolName);
129
+ if (direct)
130
+ return direct;
131
+ for (const [alias, original] of aliases ?? []) {
132
+ if (toolName.endsWith(`_${alias}`))
133
+ return original;
134
+ }
135
+ return toolName;
136
+ }
70
137
  /**
71
138
  * Build the nested McpFileSystemOptions / McpMetaToolOptions shape used by
72
139
  * requestContext.#23 / #34. One `McpDescriptor` per resolved server (builtins
@@ -79,7 +146,7 @@ export function toolsToMcpDescriptors(tools, providerIdentifier = "opencode", kn
79
146
  const order = [];
80
147
  const byServer = new Map();
81
148
  for (const t of tools) {
82
- const id = resolveToolServerIdentity(t.name, providerIdentifier, knownMcpServers);
149
+ const id = resolveToolServerIdentity(t.sourceName ?? t.name, providerIdentifier, knownMcpServers);
83
150
  let list = byServer.get(id.server);
84
151
  if (!list) {
85
152
  list = [];
@@ -87,7 +154,7 @@ export function toolsToMcpDescriptors(tools, providerIdentifier = "opencode", kn
87
154
  order.push(id.server);
88
155
  }
89
156
  list.push({
90
- tool_name: id.toolName,
157
+ tool_name: t.sourceName ? t.name : id.toolName,
91
158
  description: t.description ?? "",
92
159
  input_schema: encodeJsonAsValue(normalizeInputSchema(t.inputSchema)),
93
160
  });
@@ -118,6 +185,8 @@ export function buildLiveRequestContext(tools, providerIdentifier = "opencode",
118
185
  enabled: true,
119
186
  mcp_descriptors: nested,
120
187
  },
188
+ web_search_enabled: false,
189
+ web_fetch_enabled: false,
121
190
  rules_info_complete: true,
122
191
  env_info_complete: true,
123
192
  repository_info_complete: true,
@@ -0,0 +1,23 @@
1
+ import type { CursorProviderError } from "./errors.js";
2
+ export type ReplayBarrierReason = "visible-text" | "visible-reasoning" | "display-tool-lifecycle" | "non-control-exec" | "stateful-interaction" | "unknown-or-malformed-frame";
3
+ export declare class AttemptReplaySafety {
4
+ private readonly sessionId;
5
+ private barrierReason;
6
+ constructor(sessionId: string);
7
+ markBarrier(reason: ReplayBarrierReason): void;
8
+ applyTo(failure: CursorProviderError): CursorProviderError;
9
+ }
10
+ export type DecodedReplayFrame = {
11
+ interactionUpdate?: Record<string, unknown>;
12
+ exec?: Record<string, unknown>;
13
+ kv?: Record<string, unknown>;
14
+ execControl?: Record<string, unknown>;
15
+ interactionQuery?: Record<string, unknown>;
16
+ checkpointBytes?: Uint8Array;
17
+ };
18
+ export type ReplayFrameAnalysis = {
19
+ semanticProgress: boolean;
20
+ barrier?: ReplayBarrierReason;
21
+ };
22
+ /** Classify one decoded server frame without performing any protocol side effects. */
23
+ export declare function analyzeReplayFrame(payload: Uint8Array, decoded: DecodedReplayFrame): ReplayFrameAnalysis;
@@ -0,0 +1,126 @@
1
+ import { trace } from "./debug.js";
2
+ import { readAllFieldsStrict } from "./protocol/struct.js";
3
+ export class AttemptReplaySafety {
4
+ sessionId;
5
+ barrierReason;
6
+ constructor(sessionId) {
7
+ this.sessionId = sessionId;
8
+ }
9
+ markBarrier(reason) {
10
+ if (this.barrierReason)
11
+ return;
12
+ this.barrierReason = reason;
13
+ trace(`replay barrier: reason=${reason} sessionId=${this.sessionId}`);
14
+ }
15
+ applyTo(failure) {
16
+ failure.replaySafe = this.barrierReason === undefined && failure.replaySafe;
17
+ if (this.barrierReason) {
18
+ trace(`replay suppressed: reason=${this.barrierReason} sessionId=${this.sessionId}`);
19
+ }
20
+ return failure;
21
+ }
22
+ }
23
+ const INTERACTION_UPDATE_FIELDS = new Set([1, 2, 3, 4, 7, 13, 14, 16, 17]);
24
+ const INTERACTION_QUERY_FIELDS = new Set([2, 3, 4, 7, 8, 9, 10, 11, 12, 13, 14]);
25
+ const TOP_LEVEL_FIELDS = new Set([1, 2, 3, 4, 5, 7]);
26
+ function nestedFields(topLevel, field) {
27
+ if (topLevel?.fn !== field || topLevel.wt !== 2 || !topLevel.bytes)
28
+ return [];
29
+ return readAllFieldsStrict(topLevel.bytes) ?? [];
30
+ }
31
+ function analyzeExecWire(topLevel) {
32
+ const variants = nestedFields(topLevel, 2)
33
+ .filter((field) => ![1, 15, 19].includes(field.fn));
34
+ const exactVariant = (field) => variants.length === 1 && variants[0].fn === field && variants[0].wt === 2;
35
+ return {
36
+ exactRequestContext: exactVariant(10),
37
+ exactMcpState: exactVariant(36),
38
+ };
39
+ }
40
+ function validKvWire(topLevel) {
41
+ const variants = nestedFields(topLevel, 4).filter((field) => field.fn !== 1);
42
+ const variant = variants.length === 1 ? variants[0] : undefined;
43
+ const args = variant?.wt === 2 && variant.bytes
44
+ ? (readAllFieldsStrict(variant.bytes) ?? [])
45
+ : [];
46
+ const blobIds = args.filter((field) => field.fn === 1 && field.wt === 2 && (field.bytes?.length ?? 0) > 0);
47
+ return !!variant
48
+ && [2, 3].includes(variant.fn)
49
+ && variant.wt === 2
50
+ && blobIds.length === 1
51
+ && args.every((field) => field.wt === 2 && (field.fn === 1 || (variant.fn === 3 && field.fn === 2)));
52
+ }
53
+ function validInteractionUpdateWire(topLevel, decoded) {
54
+ if (!decoded)
55
+ return true;
56
+ const fields = nestedFields(topLevel, 1);
57
+ const update = fields.length === 1 ? fields[0] : undefined;
58
+ if (!update || update.wt !== 2 || !INTERACTION_UPDATE_FIELDS.has(update.fn))
59
+ return false;
60
+ if (![1, 4].includes(update.fn))
61
+ return true;
62
+ const delta = update.bytes ? (readAllFieldsStrict(update.bytes) ?? []) : [];
63
+ return delta.length === 1 && delta[0].fn === 1 && delta[0].wt === 2;
64
+ }
65
+ function validInteractionQueryWire(topLevel, decoded) {
66
+ if (!decoded)
67
+ return true;
68
+ const fields = nestedFields(topLevel, 7);
69
+ const ids = fields.filter((field) => field.fn === 1);
70
+ const variants = fields.filter((field) => field.fn !== 1);
71
+ return ids.length <= 1
72
+ && ids.every((field) => field.wt === 0)
73
+ && variants.length === 1
74
+ && variants[0].wt === 2
75
+ && INTERACTION_QUERY_FIELDS.has(variants[0].fn);
76
+ }
77
+ function decodedMatchesWire(topLevel, decoded) {
78
+ return !((topLevel?.fn === 1 && !decoded.interactionUpdate)
79
+ || (topLevel?.fn === 2 && !decoded.exec)
80
+ || (topLevel?.fn === 4 && !decoded.kv)
81
+ || (topLevel?.fn === 5 && !decoded.execControl)
82
+ || (topLevel?.fn === 7 && !decoded.interactionQuery));
83
+ }
84
+ function hasSemanticProgress(decoded) {
85
+ const update = decoded.interactionUpdate;
86
+ const text = update?.text_delta?.text;
87
+ const thinking = update?.thinking_delta?.text;
88
+ return (typeof text === "string" && text.length > 0)
89
+ || (typeof thinking === "string" && thinking.length > 0)
90
+ || !!update?.turn_ended
91
+ || !!update?.tool_call_started
92
+ || !!update?.tool_call_completed
93
+ || !!decoded.exec
94
+ || !!decoded.kv
95
+ || !!decoded.execControl
96
+ || !!decoded.interactionQuery
97
+ || !!decoded.checkpointBytes?.length;
98
+ }
99
+ /** Classify one decoded server frame without performing any protocol side effects. */
100
+ export function analyzeReplayFrame(payload, decoded) {
101
+ const topLevelFields = readAllFieldsStrict(payload) ?? [];
102
+ const topLevel = topLevelFields.length === 1 ? topLevelFields[0] : undefined;
103
+ const exec = analyzeExecWire(topLevel);
104
+ const validKv = validKvWire(topLevel);
105
+ const malformed = !topLevel
106
+ || topLevel.wt !== 2
107
+ || !TOP_LEVEL_FIELDS.has(topLevel.fn)
108
+ || !validInteractionUpdateWire(topLevel, decoded.interactionUpdate)
109
+ || !validInteractionQueryWire(topLevel, decoded.interactionQuery)
110
+ || !decodedMatchesWire(topLevel, decoded)
111
+ || !!decoded.execControl;
112
+ let barrier;
113
+ if (decoded.interactionUpdate?.tool_call_started || decoded.interactionUpdate?.tool_call_completed) {
114
+ barrier = "display-tool-lifecycle";
115
+ }
116
+ else if (malformed || (topLevel.fn === 4 && !validKv)) {
117
+ barrier = "unknown-or-malformed-frame";
118
+ }
119
+ else if (topLevel.fn === 2 && !exec.exactRequestContext && !exec.exactMcpState) {
120
+ barrier = "non-control-exec";
121
+ }
122
+ return {
123
+ semanticProgress: hasSemanticProgress(decoded),
124
+ barrier,
125
+ };
126
+ }
package/dist/session.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import type { BidiStream } from "./transport/connect.js";
2
2
  import { type CursorProviderError } from "./errors.js";
3
3
  import { type SessionActivitySource } from "./activity.js";
4
+ import type { ToolAliasRegistry } from "./protocol/tools.js";
4
5
  export type Frame = {
5
6
  flags: number;
6
7
  payload: Uint8Array;
@@ -117,6 +118,8 @@ export type CursorSession = {
117
118
  blobs: Map<string, Uint8Array>;
118
119
  /** McpToolDefinition list advertised this turn — echoed into the request_context reply. */
119
120
  toolDescriptors: Array<Record<string, unknown>>;
121
+ /** Cursor-facing web alias → exact executable host tool for this Run. */
122
+ toolAliases?: ToolAliasRegistry;
120
123
  /** Full RequestContext for exec #10 replies. */
121
124
  requestContext: Record<string, unknown>;
122
125
  /**
@@ -5,6 +5,7 @@ export declare function unaryAvailableModels(token: string, options?: {
5
5
  apiBaseURL?: string;
6
6
  baseURL?: string;
7
7
  headers?: Record<string, string>;
8
+ timeoutMs?: number;
8
9
  }): Promise<Record<string, unknown>>;
9
10
  /**
10
11
  * Cursor Run stream hosts from GetServerConfig / agentBaseURL.
@@ -30,6 +31,7 @@ export declare function fetchAgentUrl(token: string, options?: {
30
31
  apiBaseURL?: string;
31
32
  baseURL?: string;
32
33
  telemetryEnabled?: boolean;
34
+ timeoutMs?: number;
33
35
  }): Promise<string>;
34
36
  export type BidiStream = {
35
37
  write(msg: Uint8Array): boolean | void;
@@ -5,8 +5,25 @@ import { getDeviceIds } from "../protocol/device-id.js";
5
5
  import { resolveClientVersion } from "../protocol/client-version.js";
6
6
  import { trace } from "../debug.js";
7
7
  import { CursorLocalCancellationError, CursorProtocolError, CursorTransportError, cursorGrpcError, cursorHttpError, errorCode, CursorProviderError, } from "../errors.js";
8
+ import { withAbortDeadline } from "../deadline.js";
8
9
  import http2 from "node:http2";
9
10
  const API_BASE = `https://${CURSOR_API_HOST}`;
11
+ const DEFAULT_UNARY_TIMEOUT_MS = 5_000;
12
+ const MAX_TIMER_MS = 2_147_483_647;
13
+ function unaryTimeoutMs(operation, value) {
14
+ const timeoutMs = value ?? DEFAULT_UNARY_TIMEOUT_MS;
15
+ if (!Number.isSafeInteger(timeoutMs) || timeoutMs <= 0 || timeoutMs > MAX_TIMER_MS) {
16
+ throw new CursorProtocolError(`${operation} timeout must be a positive integer no greater than ${MAX_TIMER_MS}`, { code: "CURSOR_INVALID_TIMEOUT" });
17
+ }
18
+ return timeoutMs;
19
+ }
20
+ async function withUnaryDeadline(operation, timeoutMs, timeoutCode, run) {
21
+ return withAbortDeadline(timeoutMs, () => new CursorTransportError(`${operation} timed out after ${timeoutMs}ms`, {
22
+ transient: true,
23
+ replaySafe: true,
24
+ code: timeoutCode,
25
+ }), run);
26
+ }
10
27
  function resolveApiBaseURL(options) {
11
28
  return new URL(options.apiBaseURL ?? options.baseURL ?? API_BASE).origin;
12
29
  }
@@ -33,42 +50,46 @@ export function buildBaseHeaders(token, clientVersion, extra) {
33
50
  export async function unaryAvailableModels(token, options = {}) {
34
51
  const base = resolveApiBaseURL(options);
35
52
  const url = `${base}/aiserver.v1.AiService/AvailableModels`;
36
- const clientVersion = await resolveClientVersion();
37
- const headers = buildBaseHeaders(token, clientVersion, options.headers);
38
- let res;
39
- try {
40
- res = await fetch(url, {
41
- method: "POST",
42
- headers: {
43
- ...headers,
44
- "content-type": "application/json",
45
- accept: "application/json",
46
- },
47
- // Request parameterized effort/context/fast variants like Cursor IDE.
48
- body: JSON.stringify({
49
- includeLongContextModels: true,
50
- useModelParameters: true,
51
- useCloudAgentEffortModes: true,
52
- }),
53
- });
54
- }
55
- catch (cause) {
56
- throw new CursorTransportError("AvailableModels network request failed", {
57
- transient: true,
58
- replaySafe: true,
59
- code: errorCode(cause),
60
- cause,
61
- });
62
- }
63
- if (!res.ok) {
64
- throw await cursorHttpResponseError("AvailableModels failed:", res);
65
- }
66
- try {
67
- return (await res.json());
68
- }
69
- catch (cause) {
70
- throw new CursorProtocolError("AvailableModels returned malformed JSON", { cause });
71
- }
53
+ const timeoutMs = unaryTimeoutMs("AvailableModels", options.timeoutMs);
54
+ return withUnaryDeadline("AvailableModels", timeoutMs, "CURSOR_AVAILABLE_MODELS_TIMEOUT", async (signal) => {
55
+ const clientVersion = await resolveClientVersion();
56
+ const headers = buildBaseHeaders(token, clientVersion, options.headers);
57
+ let res;
58
+ try {
59
+ res = await fetch(url, {
60
+ method: "POST",
61
+ headers: {
62
+ ...headers,
63
+ "content-type": "application/json",
64
+ accept: "application/json",
65
+ },
66
+ // Request parameterized effort/context/fast variants like Cursor IDE.
67
+ body: JSON.stringify({
68
+ includeLongContextModels: true,
69
+ useModelParameters: true,
70
+ useCloudAgentEffortModes: true,
71
+ }),
72
+ signal,
73
+ });
74
+ }
75
+ catch (cause) {
76
+ throw new CursorTransportError("AvailableModels network request failed", {
77
+ transient: true,
78
+ replaySafe: true,
79
+ code: errorCode(cause),
80
+ cause,
81
+ });
82
+ }
83
+ if (!res.ok) {
84
+ throw await cursorHttpResponseError("AvailableModels failed:", res);
85
+ }
86
+ try {
87
+ return (await res.json());
88
+ }
89
+ catch (cause) {
90
+ throw new CursorProtocolError("AvailableModels returned malformed JSON", { cause });
91
+ }
92
+ });
72
93
  }
73
94
  // ── Unary (GetServerConfig) ──
74
95
  /**
@@ -116,51 +137,55 @@ export function normalizeAgentRunOrigin(raw) {
116
137
  export async function fetchAgentUrl(token, options = {}) {
117
138
  const base = resolveApiBaseURL(options);
118
139
  const url = `${base}${SERVER_CONFIG_PATH}`;
119
- const clientVersion = await resolveClientVersion();
120
- // Match Cursor CLI: GetServerConfig uses base headers only session headers belong on Run.
121
- const headers = buildBaseHeaders(token, clientVersion);
122
- trace(`GetServerConfig POST ${url}`);
123
- let res;
124
- try {
125
- res = await fetch(url, {
126
- method: "POST",
127
- headers: {
128
- ...headers,
129
- "content-type": "application/json",
130
- accept: "application/json",
131
- },
132
- body: JSON.stringify({ telem_enabled: options.telemetryEnabled ?? false }),
133
- });
134
- }
135
- catch (cause) {
136
- throw new CursorTransportError("GetServerConfig network request failed", {
137
- transient: true,
138
- replaySafe: true,
139
- code: errorCode(cause),
140
- cause,
141
- });
142
- }
143
- if (!res.ok) {
144
- throw await cursorHttpResponseError("GetServerConfig failed:", res);
145
- }
146
- let body;
147
- try {
148
- body = (await res.json());
149
- }
150
- catch (cause) {
151
- throw new CursorProtocolError("GetServerConfig returned malformed JSON", { cause });
152
- }
153
- const cfg = body.agentUrlConfig;
154
- const raw = cfg?.agentnUrl || cfg?.agentUrl;
155
- const normalized = normalizeAgentRunOrigin(raw);
156
- trace(`GetServerConfig reply: agentnUrl=${cfg?.agentnUrl ?? "<missing>"} ` +
157
- `agentUrl=${cfg?.agentUrl ?? "<missing>"} ${normalized ?? "<invalid>"}`);
158
- if (!normalized) {
159
- throw new CursorProtocolError(raw
160
- ? "GetServerConfig returned an invalid Cursor agent URL"
161
- : "GetServerConfig response missing agentUrlConfig.agentnUrl");
162
- }
163
- return normalized;
140
+ const timeoutMs = unaryTimeoutMs("GetServerConfig", options.timeoutMs);
141
+ return withUnaryDeadline("GetServerConfig", timeoutMs, "CURSOR_AGENT_URL_TIMEOUT", async (signal) => {
142
+ const clientVersion = await resolveClientVersion();
143
+ // Match Cursor CLI: GetServerConfig uses base headers only — session headers belong on Run.
144
+ const headers = buildBaseHeaders(token, clientVersion);
145
+ trace(`GetServerConfig POST ${url}`);
146
+ let res;
147
+ try {
148
+ res = await fetch(url, {
149
+ method: "POST",
150
+ headers: {
151
+ ...headers,
152
+ "content-type": "application/json",
153
+ accept: "application/json",
154
+ },
155
+ body: JSON.stringify({ telem_enabled: options.telemetryEnabled ?? false }),
156
+ signal,
157
+ });
158
+ }
159
+ catch (cause) {
160
+ throw new CursorTransportError("GetServerConfig network request failed", {
161
+ transient: true,
162
+ replaySafe: true,
163
+ code: errorCode(cause),
164
+ cause,
165
+ });
166
+ }
167
+ if (!res.ok) {
168
+ throw await cursorHttpResponseError("GetServerConfig failed:", res);
169
+ }
170
+ let body;
171
+ try {
172
+ body = (await res.json());
173
+ }
174
+ catch (cause) {
175
+ throw new CursorProtocolError("GetServerConfig returned malformed JSON", { cause });
176
+ }
177
+ const cfg = body.agentUrlConfig;
178
+ const raw = cfg?.agentnUrl || cfg?.agentUrl;
179
+ const normalized = normalizeAgentRunOrigin(raw);
180
+ trace(`GetServerConfig reply: agentnUrl=${cfg?.agentnUrl ?? "<missing>"} ` +
181
+ `agentUrl=${cfg?.agentUrl ?? "<missing>"} ${normalized ?? "<invalid>"}`);
182
+ if (!normalized) {
183
+ throw new CursorProtocolError(raw
184
+ ? "GetServerConfig returned an invalid Cursor agent URL"
185
+ : "GetServerConfig response missing agentUrlConfig.agentnUrl");
186
+ }
187
+ return normalized;
188
+ });
164
189
  }
165
190
  export class CursorRunInterruptedError extends CursorTransportError {
166
191
  constructor(message = "Cursor Run ended before turn_ended", options) {
@@ -217,7 +242,6 @@ const CONNECT_TIMEOUT_MS = 15_000;
217
242
  const DEFAULT_SESSION_PING_TIMEOUT_MS = 5_000;
218
243
  const DEFAULT_READ_IDLE_MS = 120_000;
219
244
  const WRITE_DRAIN_TIMEOUT_CODE = "CURSOR_WRITE_DRAIN_TIMEOUT";
220
- const MAX_TIMER_MS = 2_147_483_647;
221
245
  export const HTTP2_SESSION_MAX_AGE_MS = 15 * 60_000;
222
246
  export function shouldReuseHttp2Session(state, createdAt, now = Date.now()) {
223
247
  return !state.destroyed && !state.closed && now - createdAt < HTTP2_SESSION_MAX_AGE_MS;
@@ -0,0 +1,34 @@
1
+ import { type ToolContext, type ToolResult } from "@opencode-ai/plugin";
2
+ export type OpenCodeWebSearchArgs = {
3
+ query: string;
4
+ numResults?: number;
5
+ livecrawl?: "fallback" | "preferred";
6
+ type?: "auto" | "fast" | "deep";
7
+ contextMaxCharacters?: number;
8
+ };
9
+ export declare function parseOpenCodeWebSearchResponse(raw: string): string | undefined;
10
+ export declare function executeOpenCodeWebSearch(args: OpenCodeWebSearchArgs, context: ToolContext, fetchImpl?: typeof fetch): Promise<ToolResult>;
11
+ export declare const openCodeWebSearchTool: {
12
+ description: string;
13
+ args: {
14
+ query: import("zod").ZodString;
15
+ numResults: import("zod").ZodOptional<import("zod").ZodNumber>;
16
+ livecrawl: import("zod").ZodOptional<import("zod").ZodEnum<{
17
+ fallback: "fallback";
18
+ preferred: "preferred";
19
+ }>>;
20
+ type: import("zod").ZodOptional<import("zod").ZodEnum<{
21
+ fast: "fast";
22
+ auto: "auto";
23
+ deep: "deep";
24
+ }>>;
25
+ contextMaxCharacters: import("zod").ZodOptional<import("zod").ZodNumber>;
26
+ };
27
+ execute(args: {
28
+ query: string;
29
+ numResults?: number | undefined;
30
+ livecrawl?: "fallback" | "preferred" | undefined;
31
+ type?: "fast" | "auto" | "deep" | undefined;
32
+ contextMaxCharacters?: number | undefined;
33
+ }, context: ToolContext): Promise<ToolResult>;
34
+ };