cursor-opencode-provider 0.2.2 → 0.2.3
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.
- package/README.md +1 -1
- package/dist/language-model.js +21 -1
- package/dist/protocol/messages.js +35 -2
- package/dist/protocol/tools.d.ts +6 -0
- package/dist/protocol/tools.js +59 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -240,7 +240,7 @@ OpenCode
|
|
|
240
240
|
|
|
241
241
|
- **Personal use / ToS** — this provider speaks Cursor’s private agent protocol (CLI-shaped client identity). Use only with an account you own; Cursor may change or restrict the API without notice.
|
|
242
242
|
- **`request_context` from OpenCode** — each Run sends Cursor `RequestContext` built from OpenCode project context (workspace env, `AGENTS.md` / `instructions`, `.opencode` agents/skills/plugins, git, layout, plus `.claude`/`.agents` skill fallbacks). Same discovery as OpenCode — including `.cursor/` paths only when listed in `instructions`. Cursor-only cloud/sandbox marketplace surfaces are omitted.
|
|
243
|
-
- **Configured MCP tools keep their upstream server id** — OpenCode builtins and plugin/custom tools are advertised under a synthetic `opencode` MCP server. Tools whose flattened name matches an MCP server in merged `opencode.json` configuration (`github_create_pull_request`, …) are grouped into that server's `mcp_descriptors` / `provider_identifier` (`github`, …). Unknown underscore-containing names stay under `opencode` rather than being guessed incorrectly.
|
|
243
|
+
- **Configured MCP tools keep their upstream server id** — OpenCode builtins and plugin/custom tools are advertised under a synthetic `opencode` MCP server. Tools whose flattened name matches an MCP server in merged `opencode.json` configuration (`github_create_pull_request`, …) are grouped into that server's `mcp_descriptors` / `provider_identifier` (`github`, …). Unknown underscore-containing names stay under `opencode` rather than being guessed incorrectly. Cursor's MCP-state exec probe is answered from the same advertised descriptors before the actual tool request, and exec still reconstructs the full OpenCode tool id.
|
|
244
244
|
- **Display completions are notifications, not execution requests** — Cursor `tool_call_*` frames use a typed `ToolCall` oneof. The provider decodes them for diagnostics but only mirrors finalized todo/plan state (`update_todos_tool_call` / `create_plan_tool_call`) into advertised OpenCode `todowrite`; the completed payload already contains the authoritative final list. Interactive, data-returning, and side-effecting completions are never replayed as new tools because their result could not be returned to Cursor. Exec-backed Pi read/bash/edit/write/grep/find/ls calls use their typed request/result fields instead. Unknown display variants are logged; unknown exec variants fail the turn explicitly rather than receiving a guessed response that could deadlock the Run.
|
|
245
245
|
- **Cursor-native interaction queries remain headless** — Cursor UI/approval *queries* (as distinct from display tool calls) still cannot be surfaced through the AI SDK provider interface. The normal system prompt redirects questions, planning, plan-mode transitions, and known-URL fetching to equivalent OpenCode tools only when they are advertised (`question`, `todowrite`, `plan_enter` / `plan_exit`, `webfetch`); native web/PR/MCP/image/SCM requests are declined so they remain behind OpenCode's tools and permissions. Compaction prompts are unchanged. Unknown future interaction variants fail the turn explicitly instead of hanging the Run stream.
|
|
246
246
|
- **Compaction resets Cursor conversation state** — the classic plugin marks OpenCode's `compaction` agent explicitly. On those turns the provider mints an isolated Cursor `conversation_id`, drops the prior checkpoint + KV blobs, preserves real tool-result text in the seed history, and re-advertises the session's last tool catalog while refusing execution during the summary itself. The first normal turn then rebases once more onto a fresh conversation seeded with OpenCode's compacted prompt and normal system instructions, so the summary-agent checkpoint cannot suppress later tool calls. Ordinary no-tool / `toolChoice:none` calls do not reset conversation state.
|
package/dist/language-model.js
CHANGED
|
@@ -5,7 +5,7 @@ import { trace } from "./debug.js";
|
|
|
5
5
|
import { buildRunRequest, buildHeartbeat } from "./protocol/request.js";
|
|
6
6
|
import { decodeFramePayload } from "./protocol/framing.js";
|
|
7
7
|
import { decodeMessage } from "./protocol/messages.js";
|
|
8
|
-
import { parseExecServerMessage, buildToolCallPart, buildExecClientMessages, parseExecIdFromToolCallId, detectExecVariantField, buildRequestContextResult, } from "./protocol/tools.js";
|
|
8
|
+
import { parseExecServerMessage, buildToolCallPart, buildExecClientMessages, parseExecIdFromToolCallId, detectExecVariantField, buildRequestContextResult, buildMcpStateResult, } from "./protocol/tools.js";
|
|
9
9
|
import { advertisedToolNamesFromDescriptors, extractExecDisplayCallId, extractProtobufSubmessage, listProtobufFieldNumbers, parseDisplayToolCall, resolveBridgedOpenCodeToolCall, } from "./protocol/tool-call-bridge.js";
|
|
10
10
|
import { handleKvServerMessage } from "./protocol/kv.js";
|
|
11
11
|
import { handleInteractionQuery, inspectInteractionQueryWire } from "./protocol/interactions.js";
|
|
@@ -751,6 +751,26 @@ export async function pump(session, controller, ids, abortSignal) {
|
|
|
751
751
|
trace(`exec request_context: write FAILED ${e.message}`);
|
|
752
752
|
}
|
|
753
753
|
}
|
|
754
|
+
else if (esm.mcp_state_exec_args) {
|
|
755
|
+
// MCP-backed writes/reads can be preceded by this control-plane probe.
|
|
756
|
+
// Confirm the virtual servers from the already-advertised context, then
|
|
757
|
+
// keep pumping until Cursor emits the actual mcp_args tool request.
|
|
758
|
+
const stateArgs = esm.mcp_state_exec_args;
|
|
759
|
+
const requested = Array.isArray(stateArgs.server_identifiers)
|
|
760
|
+
? stateArgs.server_identifiers.join(",")
|
|
761
|
+
: "";
|
|
762
|
+
try {
|
|
763
|
+
session.stream.write(buildMcpStateResult(esmId, stateArgs, session.requestContext));
|
|
764
|
+
trace(`exec mcp_state: replied id=${esmId} requested=[${requested}]`);
|
|
765
|
+
}
|
|
766
|
+
catch (e) {
|
|
767
|
+
const error = new Error(`Failed to answer Cursor MCP state probe: ${e.message}`);
|
|
768
|
+
trace(`exec mcp_state: write FAILED ${error.message}`);
|
|
769
|
+
safeError(error);
|
|
770
|
+
sessionManager.close(session);
|
|
771
|
+
return;
|
|
772
|
+
}
|
|
773
|
+
}
|
|
754
774
|
else {
|
|
755
775
|
const parsed = parseExecServerMessage(esm);
|
|
756
776
|
const displayCallId = extractExecDisplayCallId(esm);
|
|
@@ -657,6 +657,37 @@ export function createMessageTypes() {
|
|
|
657
657
|
addType(root, "RequestContextResult", [
|
|
658
658
|
{ id: 1, name: "success", type: "RequestContextSuccess" },
|
|
659
659
|
]);
|
|
660
|
+
// Cursor probes MCP server availability before emitting an MCP-backed tool
|
|
661
|
+
// call. OpenCode owns those servers, so answer from the descriptors already
|
|
662
|
+
// advertised in RequestContext rather than surfacing this as a user tool.
|
|
663
|
+
addType(root, "McpStateExecArgs", [
|
|
664
|
+
{ id: 1, name: "server_identifiers", type: "string", repeated: true },
|
|
665
|
+
{ id: 2, name: "kick_only", type: "bool" },
|
|
666
|
+
]);
|
|
667
|
+
addType(root, "McpInstructions", [
|
|
668
|
+
{ id: 1, name: "server_name", type: "string" },
|
|
669
|
+
{ id: 2, name: "instructions", type: "string" },
|
|
670
|
+
{ id: 3, name: "server_identifier", type: "string" },
|
|
671
|
+
]);
|
|
672
|
+
addType(root, "McpStateServer", [
|
|
673
|
+
{ id: 1, name: "server_name", type: "string" },
|
|
674
|
+
{ id: 2, name: "server_identifier", type: "string" },
|
|
675
|
+
{ id: 3, name: "plugin", type: "string" },
|
|
676
|
+
{ id: 4, name: "marketplace", type: "string" },
|
|
677
|
+
{ id: 5, name: "tools", type: "McpFsToolDescriptor", repeated: true },
|
|
678
|
+
{ id: 6, name: "instructions", type: "McpInstructions", repeated: true },
|
|
679
|
+
{ id: 7, name: "status", type: "string" },
|
|
680
|
+
]);
|
|
681
|
+
addType(root, "McpStateSuccess", [
|
|
682
|
+
{ id: 1, name: "servers", type: "McpStateServer", repeated: true },
|
|
683
|
+
]);
|
|
684
|
+
addType(root, "McpStateError", [{ id: 1, name: "error", type: "string" }]);
|
|
685
|
+
addType(root, "McpStateRejected", [{ id: 1, name: "reason", type: "string" }]);
|
|
686
|
+
addType(root, "McpStateExecResult", [
|
|
687
|
+
{ id: 1, name: "success", type: "McpStateSuccess" },
|
|
688
|
+
{ id: 2, name: "error", type: "McpStateError" },
|
|
689
|
+
{ id: 3, name: "rejected", type: "McpStateRejected" },
|
|
690
|
+
], [{ name: "result", fields: ["success", "error", "rejected"] }]);
|
|
660
691
|
// ExecServerMessage — server asks us to execute a tool
|
|
661
692
|
addType(root, "ExecServerMessage", [
|
|
662
693
|
{ id: 1, name: "id", type: "uint32" },
|
|
@@ -670,6 +701,7 @@ export function createMessageTypes() {
|
|
|
670
701
|
{ id: 10, name: "request_context_args", type: "RequestContextArgs" },
|
|
671
702
|
{ id: 11, name: "mcp_args", type: "McpArgs" },
|
|
672
703
|
{ id: 14, name: "shell_stream_args", type: "ShellArgs" },
|
|
704
|
+
{ id: 36, name: "mcp_state_exec_args", type: "McpStateExecArgs" },
|
|
673
705
|
{ id: 45, name: "pi_read_args", type: "PiReadToolArgs" },
|
|
674
706
|
{ id: 46, name: "pi_bash_args", type: "PiBashToolArgs" },
|
|
675
707
|
{ id: 47, name: "pi_edit_args", type: "PiEditToolArgs" },
|
|
@@ -679,7 +711,7 @@ export function createMessageTypes() {
|
|
|
679
711
|
{ id: 51, name: "pi_ls_args", type: "PiLsToolArgs" },
|
|
680
712
|
], [{ name: "args", fields: [
|
|
681
713
|
"write_args", "delete_args", "grep_args", "read_args", "ls_args",
|
|
682
|
-
"request_context_args", "mcp_args", "shell_stream_args",
|
|
714
|
+
"request_context_args", "mcp_args", "shell_stream_args", "mcp_state_exec_args",
|
|
683
715
|
"pi_read_args", "pi_bash_args", "pi_edit_args", "pi_write_args",
|
|
684
716
|
"pi_grep_args", "pi_find_args", "pi_ls_args",
|
|
685
717
|
] }]);
|
|
@@ -696,6 +728,7 @@ export function createMessageTypes() {
|
|
|
696
728
|
{ id: 10, name: "request_context_result", type: "RequestContextResult" },
|
|
697
729
|
{ id: 11, name: "mcp_result", type: "McpResult" },
|
|
698
730
|
{ id: 14, name: "shell_stream", type: "ShellStream" },
|
|
731
|
+
{ id: 36, name: "mcp_state_exec_result", type: "McpStateExecResult" },
|
|
699
732
|
{ id: 46, name: "pi_read_result", type: "PiReadExecResult" },
|
|
700
733
|
{ id: 47, name: "pi_bash_result", type: "PiBashExecResult" },
|
|
701
734
|
{ id: 48, name: "pi_edit_result", type: "PiEditExecResult" },
|
|
@@ -705,7 +738,7 @@ export function createMessageTypes() {
|
|
|
705
738
|
{ id: 52, name: "pi_ls_result", type: "PiLsExecResult" },
|
|
706
739
|
], [{ name: "result", fields: [
|
|
707
740
|
"write_result", "delete_result", "grep_result", "read_result", "ls_result",
|
|
708
|
-
"request_context_result", "mcp_result", "shell_stream",
|
|
741
|
+
"request_context_result", "mcp_result", "shell_stream", "mcp_state_exec_result",
|
|
709
742
|
"pi_read_result", "pi_bash_result", "pi_edit_result", "pi_write_result",
|
|
710
743
|
"pi_grep_result", "pi_find_result", "pi_ls_result",
|
|
711
744
|
] }]);
|
package/dist/protocol/tools.d.ts
CHANGED
|
@@ -142,3 +142,9 @@ export declare function detectExecVariantField(agentServerPayload: Uint8Array):
|
|
|
142
142
|
* Encode exec #10 request_context_result from a prebuilt RequestContext payload.
|
|
143
143
|
*/
|
|
144
144
|
export declare function buildRequestContextResult(execId: number, requestContext: Record<string, unknown>): Uint8Array;
|
|
145
|
+
/**
|
|
146
|
+
* Answer Cursor's exec #36 MCP-state probe from the same descriptors advertised
|
|
147
|
+
* in RequestContext. OpenCode remains the executor; this only confirms that the
|
|
148
|
+
* provider's virtual MCP servers and their tools are available.
|
|
149
|
+
*/
|
|
150
|
+
export declare function buildMcpStateResult(execId: number, args: Record<string, unknown>, requestContext: Record<string, unknown>): Uint8Array;
|
package/dist/protocol/tools.js
CHANGED
|
@@ -810,3 +810,62 @@ export function buildRequestContextResult(execId, requestContext) {
|
|
|
810
810
|
},
|
|
811
811
|
});
|
|
812
812
|
}
|
|
813
|
+
/**
|
|
814
|
+
* Answer Cursor's exec #36 MCP-state probe from the same descriptors advertised
|
|
815
|
+
* in RequestContext. OpenCode remains the executor; this only confirms that the
|
|
816
|
+
* provider's virtual MCP servers and their tools are available.
|
|
817
|
+
*/
|
|
818
|
+
export function buildMcpStateResult(execId, args, requestContext) {
|
|
819
|
+
const requested = new Set(Array.isArray(args.server_identifiers)
|
|
820
|
+
? args.server_identifiers.filter((id) => typeof id === "string" && id.length > 0)
|
|
821
|
+
: []);
|
|
822
|
+
const fsOptions = recordValue(requestContext.mcp_file_system_options);
|
|
823
|
+
const nested = Array.isArray(fsOptions?.mcp_descriptors)
|
|
824
|
+
? fsOptions.mcp_descriptors.map(recordValue).filter((d) => !!d)
|
|
825
|
+
: [];
|
|
826
|
+
const descriptors = nested.length > 0 ? nested : descriptorsFromFlatTools(requestContext.tools);
|
|
827
|
+
const servers = descriptors
|
|
828
|
+
.filter((descriptor) => {
|
|
829
|
+
const id = stringValue(descriptor.server_identifier);
|
|
830
|
+
return requested.size === 0 || (id !== undefined && requested.has(id));
|
|
831
|
+
})
|
|
832
|
+
.map((descriptor) => ({
|
|
833
|
+
server_name: stringValue(descriptor.server_name) ?? stringValue(descriptor.server_identifier) ?? "",
|
|
834
|
+
server_identifier: stringValue(descriptor.server_identifier) ?? stringValue(descriptor.server_name) ?? "",
|
|
835
|
+
tools: Array.isArray(descriptor.tools) ? descriptor.tools : [],
|
|
836
|
+
}));
|
|
837
|
+
return encodeMessage("AgentClientMessage", {
|
|
838
|
+
exec_client_message: {
|
|
839
|
+
id: execId,
|
|
840
|
+
mcp_state_exec_result: { success: { servers } },
|
|
841
|
+
},
|
|
842
|
+
});
|
|
843
|
+
}
|
|
844
|
+
function recordValue(value) {
|
|
845
|
+
return value && typeof value === "object" && !Array.isArray(value)
|
|
846
|
+
? value
|
|
847
|
+
: undefined;
|
|
848
|
+
}
|
|
849
|
+
function descriptorsFromFlatTools(value) {
|
|
850
|
+
if (!Array.isArray(value))
|
|
851
|
+
return [];
|
|
852
|
+
const byServer = new Map();
|
|
853
|
+
for (const raw of value) {
|
|
854
|
+
const tool = recordValue(raw);
|
|
855
|
+
if (!tool)
|
|
856
|
+
continue;
|
|
857
|
+
const server = stringValue(tool.provider_identifier) ?? "opencode";
|
|
858
|
+
const tools = byServer.get(server) ?? [];
|
|
859
|
+
tools.push({
|
|
860
|
+
tool_name: stringValue(tool.tool_name) ?? stringValue(tool.name) ?? "",
|
|
861
|
+
description: stringValue(tool.description) ?? "",
|
|
862
|
+
input_schema: tool.input_schema,
|
|
863
|
+
});
|
|
864
|
+
byServer.set(server, tools);
|
|
865
|
+
}
|
|
866
|
+
return [...byServer].map(([server, tools]) => ({
|
|
867
|
+
server_name: server,
|
|
868
|
+
server_identifier: server,
|
|
869
|
+
tools,
|
|
870
|
+
}));
|
|
871
|
+
}
|