cursor-opencode-provider 0.2.1 → 0.2.2

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,44 @@
1
+ /**
2
+ * Cursor's interaction_update.tool_call_* carries a typed ToolCall oneof
3
+ * (shell_tool_call, update_todos_tool_call, mcp_tool_call, …). Some of those
4
+ * never become ExecServerMessage requests — Cursor completes them display-only
5
+ * — so OpenCode would never see a tool-call unless we bridge them here.
6
+ */
7
+ export type DisplayToolCall = {
8
+ callId: string;
9
+ variant: string;
10
+ /** Preferred OpenCode tool id before advertisement checks. */
11
+ preferredToolName: string;
12
+ args: Record<string, unknown>;
13
+ /** False when the Cursor call cannot be represented safely in OpenCode. */
14
+ bridgeable?: boolean;
15
+ };
16
+ export type BridgedOpenCodeToolCall = {
17
+ toolName: string;
18
+ args: Record<string, unknown>;
19
+ callId: string;
20
+ variant: string;
21
+ };
22
+ /**
23
+ * Decode a Cursor ToolCall oneof into a display tool call we can bridge.
24
+ */
25
+ export declare function parseDisplayToolCall(callId: string, toolCall: Record<string, unknown> | undefined): DisplayToolCall | undefined;
26
+ /**
27
+ * Pick an advertised OpenCode tool for a display call, remapping args as needed.
28
+ * Returns undefined when nothing compatible is advertised this turn.
29
+ */
30
+ export declare function resolveBridgedOpenCodeToolCall(display: DisplayToolCall, advertised: Iterable<string>): BridgedOpenCodeToolCall | undefined;
31
+ /** Advertised OpenCode tool ids from Cursor McpToolDefinition descriptors. */
32
+ export declare function advertisedToolNamesFromDescriptors(descriptors: Array<Record<string, unknown>>): string[];
33
+ /** Walk a protobuf message and return top-level field numbers present. */
34
+ export declare function listProtobufFieldNumbers(buf: Uint8Array): number[];
35
+ /**
36
+ * Extract nested bytes for path of field numbers (each must be wire type 2).
37
+ * Returns undefined if any segment is missing.
38
+ */
39
+ export declare function extractProtobufSubmessage(buf: Uint8Array, path: number[]): Uint8Array | undefined;
40
+ /**
41
+ * Pull Cursor's tool_call_id out of a raw ExecServerMessage args variant so we
42
+ * can correlate display tool_call_started with the authoritative exec path.
43
+ */
44
+ export declare function extractExecDisplayCallId(execMsg: Record<string, unknown>): string | undefined;
@@ -0,0 +1,480 @@
1
+ import { mapCursorArgsToOpencode, mcpRealToolName } from "./tools.js";
2
+ import { decodeStructEntriesToJson } from "./struct.js";
3
+ const TODO_STATUS = {
4
+ 0: "pending",
5
+ 1: "pending",
6
+ 2: "in_progress",
7
+ 3: "completed",
8
+ 4: "cancelled",
9
+ };
10
+ /** Cursor ToolCall oneof field → default OpenCode tool id. */
11
+ const VARIANT_TO_OPENCODE = {
12
+ shell_tool_call: "bash",
13
+ delete_tool_call: "bash",
14
+ glob_tool_call: "glob",
15
+ grep_tool_call: "grep",
16
+ read_tool_call: "read",
17
+ update_todos_tool_call: "todowrite",
18
+ read_todos_tool_call: "todoread",
19
+ edit_tool_call: "write",
20
+ ls_tool_call: "read",
21
+ mcp_tool_call: "mcp",
22
+ create_plan_tool_call: "todowrite",
23
+ web_search_tool_call: "websearch",
24
+ task_tool_call: "task",
25
+ ask_question_tool_call: "question",
26
+ fetch_tool_call: "webfetch",
27
+ web_fetch_tool_call: "webfetch",
28
+ switch_mode_tool_call: "plan_enter",
29
+ generate_image_tool_call: "generateimage",
30
+ await_tool_call: "await",
31
+ get_mcp_tools_tool_call: "get_mcp_tools",
32
+ pi_read_tool_call: "read",
33
+ pi_bash_tool_call: "bash",
34
+ pi_edit_tool_call: "edit",
35
+ pi_write_tool_call: "write",
36
+ pi_grep_tool_call: "grep",
37
+ pi_find_tool_call: "glob",
38
+ pi_ls_tool_call: "read",
39
+ };
40
+ const TOOL_CALL_VARIANTS = Object.keys(VARIANT_TO_OPENCODE);
41
+ // ToolCallCompleted is a notification, not an execution request. Only mirror
42
+ // client-visible state whose authoritative final value is already present in
43
+ // the completed payload. Data-returning, interactive, and side-effecting calls
44
+ // must use an exec/interaction request channel where Cursor can receive their
45
+ // actual result.
46
+ const DISPLAY_STATE_MIRROR_VARIANTS = new Set([
47
+ "update_todos_tool_call",
48
+ "create_plan_tool_call",
49
+ ]);
50
+ function asRecord(v) {
51
+ return v && typeof v === "object" && !Array.isArray(v) ? v : undefined;
52
+ }
53
+ function findToolVariant(toolCall) {
54
+ for (const key of TOOL_CALL_VARIANTS) {
55
+ if (toolCall[key] != null)
56
+ return key;
57
+ }
58
+ for (const [key, value] of Object.entries(toolCall)) {
59
+ if (!key.endsWith("_tool_call"))
60
+ continue;
61
+ if (value && typeof value === "object")
62
+ return key;
63
+ }
64
+ return undefined;
65
+ }
66
+ function mapTodoStatus(status) {
67
+ if (typeof status === "string" && status.length > 0) {
68
+ const s = status.toLowerCase().replace(/^todo_status_/, "");
69
+ if (s === "unspecified")
70
+ return "pending";
71
+ return s;
72
+ }
73
+ if (typeof status === "number" && TODO_STATUS[status])
74
+ return TODO_STATUS[status];
75
+ return "pending";
76
+ }
77
+ function mapTodos(raw) {
78
+ if (!Array.isArray(raw))
79
+ return [];
80
+ return raw.map((item, index) => {
81
+ const t = asRecord(item) ?? {};
82
+ const content = typeof t.content === "string" ? t.content : "";
83
+ const id = typeof t.id === "string" && t.id.length > 0
84
+ ? t.id
85
+ : `todo_${index + 1}`;
86
+ return {
87
+ id,
88
+ content,
89
+ status: mapTodoStatus(t.status),
90
+ priority: typeof t.priority === "string" && t.priority ? t.priority : "medium",
91
+ };
92
+ });
93
+ }
94
+ function unwrapArgs(variantPayload) {
95
+ const nested = asRecord(variantPayload.args);
96
+ return nested ?? variantPayload;
97
+ }
98
+ /**
99
+ * Decode a Cursor ToolCall oneof into a display tool call we can bridge.
100
+ */
101
+ export function parseDisplayToolCall(callId, toolCall) {
102
+ if (!toolCall || !callId)
103
+ return undefined;
104
+ const variant = findToolVariant(toolCall);
105
+ if (!variant)
106
+ return undefined;
107
+ const payload = asRecord(toolCall[variant]);
108
+ if (!payload)
109
+ return undefined;
110
+ const args = unwrapArgs(payload);
111
+ if (variant === "mcp_tool_call") {
112
+ const name = mcpRealToolName(args);
113
+ let mcpArgs = asRecord(args.args) ?? {};
114
+ if (Array.isArray(args.args)) {
115
+ mcpArgs = decodeStructEntriesToJson(args.args);
116
+ }
117
+ return {
118
+ callId,
119
+ variant,
120
+ preferredToolName: name,
121
+ args: mcpArgs,
122
+ };
123
+ }
124
+ if (variant === "update_todos_tool_call" || variant === "create_plan_tool_call") {
125
+ const result = asRecord(payload.result);
126
+ const success = asRecord(result?.success);
127
+ const completedTodos = Array.isArray(success?.todos) ? success.todos : undefined;
128
+ const isMerge = variant === "update_todos_tool_call" && args.merge === true;
129
+ // OpenCode todowrite replaces the whole list. For Cursor merge updates,
130
+ // only bridge when ToolCallCompleted includes the final merged list.
131
+ const sourceTodos = completedTodos ?? (!isMerge ? args.todos : undefined);
132
+ const todos = mapTodos(sourceTodos);
133
+ if (variant === "create_plan_tool_call") {
134
+ const overview = typeof args.overview === "string" ? args.overview.trim() : "";
135
+ const plan = typeof args.plan === "string" ? args.plan.trim() : "";
136
+ const name = typeof args.name === "string" ? args.name.trim() : "";
137
+ if (overview || plan || name) {
138
+ todos.unshift({
139
+ id: "plan",
140
+ content: [name && `Plan: ${name}`, overview, plan].filter(Boolean).join("\n").slice(0, 2000),
141
+ status: "pending",
142
+ priority: "high",
143
+ });
144
+ }
145
+ }
146
+ return {
147
+ callId,
148
+ variant,
149
+ preferredToolName: "todowrite",
150
+ args: { todos },
151
+ bridgeable: variant === "create_plan_tool_call" ? todos.length > 0 : Array.isArray(sourceTodos),
152
+ };
153
+ }
154
+ if (variant === "edit_tool_call") {
155
+ const path = typeof args.path === "string" ? args.path : "";
156
+ const content = typeof args.stream_content === "string" ? args.stream_content : undefined;
157
+ return {
158
+ callId,
159
+ variant,
160
+ preferredToolName: "write",
161
+ args: { path, content },
162
+ bridgeable: path.length > 0 && content !== undefined,
163
+ };
164
+ }
165
+ if (variant === "pi_edit_tool_call") {
166
+ const path = typeof args.path === "string" ? args.path : "";
167
+ const edits = Array.isArray(args.edits) ? args.edits : [];
168
+ const replacement = edits.length === 1 ? asRecord(edits[0]) : undefined;
169
+ const oldText = replacement?.old_text;
170
+ const newText = replacement?.new_text;
171
+ return {
172
+ callId,
173
+ variant,
174
+ preferredToolName: "edit",
175
+ args: { path, old_string: oldText, new_string: newText },
176
+ bridgeable: path.length > 0 &&
177
+ typeof oldText === "string" && oldText.length > 0 &&
178
+ typeof newText === "string",
179
+ };
180
+ }
181
+ if (variant === "task_tool_call") {
182
+ const subagentType = openCodeSubagentType(args.subagent_type);
183
+ const description = typeof args.description === "string" ? args.description : "";
184
+ const prompt = typeof args.prompt === "string" ? args.prompt : "";
185
+ const taskArgs = {
186
+ description,
187
+ prompt,
188
+ subagent_type: subagentType,
189
+ };
190
+ if (typeof args.resume === "string" && args.resume)
191
+ taskArgs.task_id = args.resume;
192
+ return {
193
+ callId,
194
+ variant,
195
+ preferredToolName: "task",
196
+ args: taskArgs,
197
+ bridgeable: description.length > 0 && prompt.length > 0 && !!subagentType,
198
+ };
199
+ }
200
+ if (variant === "ask_question_tool_call") {
201
+ const title = typeof args.title === "string" ? args.title : "";
202
+ const questions = Array.isArray(args.questions)
203
+ ? args.questions.map((q) => {
204
+ const qq = asRecord(q) ?? {};
205
+ const prompt = typeof qq.prompt === "string" ? qq.prompt : "";
206
+ const options = Array.isArray(qq.options)
207
+ ? qq.options.map((o) => {
208
+ const oo = asRecord(o) ?? {};
209
+ return {
210
+ label: typeof oo.label === "string" ? oo.label : String(oo.id ?? ""),
211
+ description: typeof oo.description === "string" ? oo.description : "",
212
+ };
213
+ })
214
+ : [];
215
+ return {
216
+ question: prompt,
217
+ header: (typeof qq.id === "string" && qq.id) || title || "Question",
218
+ options,
219
+ multiple: qq.allow_multiple === true,
220
+ };
221
+ })
222
+ : [];
223
+ return {
224
+ callId,
225
+ variant,
226
+ preferredToolName: "question",
227
+ args: { questions },
228
+ };
229
+ }
230
+ if (variant === "switch_mode_tool_call") {
231
+ const target = typeof args.target_mode_id === "string" ? args.target_mode_id.toLowerCase() : "";
232
+ const preferred = target.includes("plan") || target === "plan" ? "plan_enter" : "plan_exit";
233
+ return {
234
+ callId,
235
+ variant,
236
+ preferredToolName: preferred,
237
+ // OpenCode plan_enter / plan_exit both advertise an empty input schema.
238
+ args: {},
239
+ };
240
+ }
241
+ if (variant === "delete_tool_call") {
242
+ const path = typeof args.path === "string" ? args.path : "";
243
+ return {
244
+ callId,
245
+ variant,
246
+ preferredToolName: "bash",
247
+ args: path ? { command: `rm -f -- ${shellQuote(path)}` } : { command: "true" },
248
+ };
249
+ }
250
+ const preferred = VARIANT_TO_OPENCODE[variant] ?? variant.replace(/_tool_call$/, "");
251
+ return {
252
+ callId,
253
+ variant,
254
+ preferredToolName: preferred,
255
+ args,
256
+ };
257
+ }
258
+ function shellQuote(s) {
259
+ return `'${s.replace(/'/g, `'\\''`)}'`;
260
+ }
261
+ /**
262
+ * Pick an advertised OpenCode tool for a display call, remapping args as needed.
263
+ * Returns undefined when nothing compatible is advertised this turn.
264
+ */
265
+ export function resolveBridgedOpenCodeToolCall(display, advertised) {
266
+ if (!DISPLAY_STATE_MIRROR_VARIANTS.has(display.variant))
267
+ return undefined;
268
+ if (display.bridgeable === false)
269
+ return undefined;
270
+ const names = new Set([...advertised].filter(Boolean));
271
+ if (names.size === 0)
272
+ return undefined;
273
+ const candidates = candidateToolNames(display, names);
274
+ for (const toolName of candidates) {
275
+ if (!names.has(toolName))
276
+ continue;
277
+ const mapped = mapCursorArgsToOpencode(toolName, display.args);
278
+ if (toolName === "todowrite") {
279
+ mapped.args = {
280
+ todos: mapTodos(display.args.todos ?? mapped.args.todos),
281
+ };
282
+ }
283
+ return {
284
+ toolName: mapped.toolName,
285
+ args: mapped.args,
286
+ callId: display.callId,
287
+ variant: display.variant,
288
+ };
289
+ }
290
+ return undefined;
291
+ }
292
+ function candidateToolNames(display, advertised) {
293
+ const out = [];
294
+ const add = (n) => {
295
+ if (n && !out.includes(n))
296
+ out.push(n);
297
+ };
298
+ add(display.preferredToolName);
299
+ if (display.variant === "create_plan_tool_call")
300
+ add("todowrite");
301
+ if (display.variant === "update_todos_tool_call")
302
+ add("todowrite");
303
+ if (display.variant === "read_todos_tool_call")
304
+ add("todoread");
305
+ if (display.variant === "ask_question_tool_call")
306
+ add("question");
307
+ if (display.variant === "web_search_tool_call")
308
+ add("websearch");
309
+ if (display.variant === "fetch_tool_call" || display.variant === "web_fetch_tool_call") {
310
+ add("webfetch");
311
+ }
312
+ for (const name of advertised) {
313
+ if (name.toLowerCase() === display.preferredToolName.toLowerCase())
314
+ add(name);
315
+ }
316
+ return out;
317
+ }
318
+ /** Map only Cursor subagent variants with a safe OpenCode identity. */
319
+ function openCodeSubagentType(raw) {
320
+ const subtype = asRecord(raw);
321
+ if (!subtype)
322
+ return undefined;
323
+ const custom = asRecord(subtype.custom);
324
+ if (typeof custom?.name === "string" && custom.name.length > 0)
325
+ return custom.name;
326
+ if (subtype.explore != null)
327
+ return "explore";
328
+ return undefined;
329
+ }
330
+ /** Advertised OpenCode tool ids from Cursor McpToolDefinition descriptors. */
331
+ export function advertisedToolNamesFromDescriptors(descriptors) {
332
+ const out = [];
333
+ for (const d of descriptors) {
334
+ const toolName = typeof d.tool_name === "string" ? d.tool_name : undefined;
335
+ const provider = typeof d.provider_identifier === "string" ? d.provider_identifier : "opencode";
336
+ if (!toolName)
337
+ continue;
338
+ if (provider && provider !== "opencode")
339
+ out.push(`${provider}_${toolName}`);
340
+ else
341
+ out.push(toolName);
342
+ }
343
+ return out;
344
+ }
345
+ /** Walk a protobuf message and return top-level field numbers present. */
346
+ export function listProtobufFieldNumbers(buf) {
347
+ const fields = [];
348
+ let i = 0;
349
+ while (i < buf.length) {
350
+ let key = 0;
351
+ let shift = 0;
352
+ while (i < buf.length) {
353
+ const byte = buf[i++];
354
+ key |= (byte & 0x7f) << shift;
355
+ if ((byte & 0x80) === 0)
356
+ break;
357
+ shift += 7;
358
+ if (shift > 35)
359
+ return fields;
360
+ }
361
+ const field = key >>> 3;
362
+ const wire = key & 7;
363
+ fields.push(field);
364
+ if (wire === 0) {
365
+ // varint
366
+ while (i < buf.length && (buf[i++] & 0x80) !== 0) { /* consume */ }
367
+ }
368
+ else if (wire === 1) {
369
+ i += 8;
370
+ }
371
+ else if (wire === 2) {
372
+ let len = 0;
373
+ shift = 0;
374
+ while (i < buf.length) {
375
+ const byte = buf[i++];
376
+ len |= (byte & 0x7f) << shift;
377
+ if ((byte & 0x80) === 0)
378
+ break;
379
+ shift += 7;
380
+ if (shift > 35)
381
+ return fields;
382
+ }
383
+ i += len;
384
+ }
385
+ else if (wire === 5) {
386
+ i += 4;
387
+ }
388
+ else {
389
+ break;
390
+ }
391
+ if (i > buf.length)
392
+ break;
393
+ }
394
+ return fields;
395
+ }
396
+ /**
397
+ * Extract nested bytes for path of field numbers (each must be wire type 2).
398
+ * Returns undefined if any segment is missing.
399
+ */
400
+ export function extractProtobufSubmessage(buf, path) {
401
+ let cur = buf;
402
+ for (const want of path) {
403
+ if (!cur)
404
+ return undefined;
405
+ let i = 0;
406
+ let found;
407
+ while (i < cur.length) {
408
+ let key = 0;
409
+ let shift = 0;
410
+ while (i < cur.length) {
411
+ const byte = cur[i++];
412
+ key |= (byte & 0x7f) << shift;
413
+ if ((byte & 0x80) === 0)
414
+ break;
415
+ shift += 7;
416
+ if (shift > 35)
417
+ return undefined;
418
+ }
419
+ const field = key >>> 3;
420
+ const wire = key & 7;
421
+ if (wire === 0) {
422
+ while (i < cur.length && (cur[i++] & 0x80) !== 0) { /* consume */ }
423
+ }
424
+ else if (wire === 1) {
425
+ i += 8;
426
+ }
427
+ else if (wire === 2) {
428
+ let len = 0;
429
+ shift = 0;
430
+ while (i < cur.length) {
431
+ const byte = cur[i++];
432
+ len |= (byte & 0x7f) << shift;
433
+ if ((byte & 0x80) === 0)
434
+ break;
435
+ shift += 7;
436
+ if (shift > 35)
437
+ return undefined;
438
+ }
439
+ const slice = cur.subarray(i, i + len);
440
+ i += len;
441
+ if (field === want)
442
+ found = slice;
443
+ }
444
+ else if (wire === 5) {
445
+ i += 4;
446
+ }
447
+ else {
448
+ return undefined;
449
+ }
450
+ if (i > cur.length)
451
+ return undefined;
452
+ }
453
+ cur = found;
454
+ }
455
+ return cur;
456
+ }
457
+ /**
458
+ * Pull Cursor's tool_call_id out of a raw ExecServerMessage args variant so we
459
+ * can correlate display tool_call_started with the authoritative exec path.
460
+ */
461
+ export function extractExecDisplayCallId(execMsg) {
462
+ for (const key of [
463
+ "read_args",
464
+ "write_args",
465
+ "pi_write_args",
466
+ "grep_args",
467
+ "ls_args",
468
+ "delete_args",
469
+ "shell_stream_args",
470
+ "mcp_args",
471
+ ]) {
472
+ const args = asRecord(execMsg[key]);
473
+ if (!args)
474
+ continue;
475
+ if (typeof args.tool_call_id === "string" && args.tool_call_id.length > 0) {
476
+ return args.tool_call_id;
477
+ }
478
+ }
479
+ return undefined;
480
+ }
@@ -4,28 +4,45 @@ export type OpencodeToolDef = {
4
4
  description?: string;
5
5
  inputSchema?: unknown;
6
6
  };
7
+ export type ToolServerIdentity = {
8
+ /** Cursor MCP server id / provider_identifier (e.g. opencode, github). */
9
+ server: string;
10
+ /** Bare tool name inside that server (Cursor McpArgs.tool_name). */
11
+ toolName: string;
12
+ /** Full OpenCode tool id used for local execution (e.g. github_create_pull_request). */
13
+ opencodeName: string;
14
+ };
15
+ /** Match OpenCode's McpCatalog.sanitize for config server ids. */
16
+ export declare function sanitizeMcpServerId(value: string): string;
17
+ /**
18
+ * Resolve an OpenCode tool id using known MCP server ids from merged config.
19
+ * Unknown names remain under the synthetic default server: a flattened tool
20
+ * name alone cannot distinguish plugin/custom tools from `<server>_<tool>`.
21
+ */
22
+ export declare function resolveToolServerIdentity(opencodeName: string, defaultServer?: string, knownMcpServers?: Iterable<string>): ToolServerIdentity;
7
23
  /**
8
24
  * Convert opencode's per-turn tool list into Cursor `McpToolDefinition`
9
25
  * entries for `request_context.tools` (#7) and `AgentRunRequest.mcp_tools`.
10
26
  *
11
- * opencode tools are already namespaced (e.g. `read`, `grep`, or
12
- * `<server>_<tool>` for MCP). We advertise them all under a synthetic
13
- * `opencode` provider so Cursor routes their calls back to us as
14
- * `mcp_args` on the exec channel. The composite `name` is what the model
15
- * calls and what comes back as `McpArgs.name`.
27
+ * Builtins and unknown plugin/custom tools are advertised under the synthetic
28
+ * default server (`opencode`). Tools whose prefixes match configured MCP
29
+ * servers keep those server ids (`github`, …). Composite `name` is
30
+ * `<server>-<bareTool>`; local execution still uses the full OpenCode id
31
+ * reconstructed in `mcpRealToolName`.
16
32
  */
17
- export declare function toolsToDescriptors(tools: OpencodeToolDef[], providerIdentifier?: string): Array<Record<string, unknown>>;
33
+ export declare function toolsToDescriptors(tools: OpencodeToolDef[], providerIdentifier?: string, knownMcpServers?: Iterable<string>): Array<Record<string, unknown>>;
18
34
  /**
19
35
  * Build the nested McpFileSystemOptions / McpMetaToolOptions shape used by
20
- * requestContext.#23 / #34. Groups every tool under one synthetic server so
21
- * Cursor's IDE-style decoder also sees the list.
36
+ * requestContext.#23 / #34. One `McpDescriptor` per resolved server (builtins
37
+ * and unknown tools under the synthetic default; configured MCP tools under
38
+ * their upstream server id).
22
39
  */
23
- export declare function toolsToMcpDescriptors(tools: OpencodeToolDef[], providerIdentifier?: string): Array<Record<string, unknown>>;
40
+ export declare function toolsToMcpDescriptors(tools: OpencodeToolDef[], providerIdentifier?: string, knownMcpServers?: Iterable<string>): Array<Record<string, unknown>>;
24
41
  /**
25
42
  * @deprecated Prefer `buildRequestContext` from `../context/build.js`.
26
43
  * Kept as a sync tools-only fallback for unit tests that don't need collectors.
27
44
  */
28
- export declare function buildLiveRequestContext(tools: OpencodeToolDef[], providerIdentifier?: string): Record<string, unknown>;
45
+ export declare function buildLiveRequestContext(tools: OpencodeToolDef[], providerIdentifier?: string, knownMcpServers?: Iterable<string>): Record<string, unknown>;
29
46
  export declare function mapExecServerToToolName(execField: string): string | undefined;
30
47
  export declare function mapToolNameToExecField(toolName: string): string | undefined;
31
48
  export type ParsedExecRequest = {
@@ -35,6 +52,8 @@ export type ParsedExecRequest = {
35
52
  args: Record<string, unknown>;
36
53
  /** ExecClientMessage result field to reply with (matches the request variant). */
37
54
  resultField: string;
55
+ /** Typed error to return without asking OpenCode to execute invalid args. */
56
+ localError?: string;
38
57
  };
39
58
  export declare function parseExecServerMessage(msg: Record<string, unknown>): ParsedExecRequest | undefined;
40
59
  /**
@@ -46,6 +65,12 @@ export declare function mapCursorArgsToOpencode(toolName: string, raw: Record<st
46
65
  toolName: string;
47
66
  args: Record<string, unknown>;
48
67
  };
68
+ /**
69
+ * Map Cursor McpArgs back to the OpenCode tool id.
70
+ * Prefers provider_identifier + bare tool_name (github + create_pull_request
71
+ * → github_create_pull_request). Builtins under the default server stay bare.
72
+ */
73
+ export declare function mcpRealToolName(mcpArgs: Record<string, unknown>, defaultServer?: string): string;
49
74
  export type ToolResultInput = {
50
75
  execId: number;
51
76
  /** ExecClientMessage result field (from ParsedExecRequest.resultField). */
@@ -113,8 +138,6 @@ export declare function parseExecIdFromToolCallId(toolCallId: string): {
113
138
  * Returns undefined if there is no exec_server_message or no variant set.
114
139
  */
115
140
  export declare function detectExecVariantField(agentServerPayload: Uint8Array): number | undefined;
116
- /** Build ExecClientMessage{1:id, <resultField>: empty} as raw bytes. */
117
- export declare function buildRawEmptyExecReply(execId: number, resultField: number): Uint8Array;
118
141
  /**
119
142
  * Encode exec #10 request_context_result from a prebuilt RequestContext payload.
120
143
  */