@playwo/opencode-cursor-oauth 0.0.0-dev.2c48be2f48c9 → 0.0.0-dev.36e1216df6cf
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 +33 -12
- package/dist/AGENTS.md +8 -0
- package/dist/agent-rules.d.ts +1 -0
- package/dist/agent-rules.js +28 -0
- package/dist/cursor/bidi-session.d.ts +1 -2
- package/dist/cursor/bidi-session.js +153 -138
- package/dist/cursor/index.d.ts +1 -1
- package/dist/cursor/index.js +1 -1
- package/dist/cursor/unary-rpc.d.ts +0 -1
- package/dist/cursor/unary-rpc.js +2 -59
- package/dist/logger.d.ts +1 -0
- package/dist/logger.js +3 -0
- package/dist/openai/messages.js +5 -0
- package/dist/plugin/cursor-auth-plugin.js +0 -1
- package/dist/proxy/bridge-close-controller.d.ts +6 -0
- package/dist/proxy/bridge-close-controller.js +37 -0
- package/dist/proxy/bridge-non-streaming.js +33 -3
- package/dist/proxy/bridge-session.js +1 -3
- package/dist/proxy/bridge-streaming.d.ts +1 -1
- package/dist/proxy/bridge-streaming.js +430 -64
- package/dist/proxy/chat-completion.js +44 -3
- package/dist/proxy/cursor-request.d.ts +1 -0
- package/dist/proxy/cursor-request.js +24 -8
- package/dist/proxy/server.js +23 -5
- package/dist/proxy/stream-dispatch.d.ts +17 -1
- package/dist/proxy/stream-dispatch.js +280 -12
- package/dist/proxy/types.d.ts +14 -1
- package/package.json +2 -3
|
@@ -3,3 +3,4 @@ export declare function buildCursorRequest(modelId: string, systemPrompt: string
|
|
|
3
3
|
userText: string;
|
|
4
4
|
assistantText: string;
|
|
5
5
|
}>, conversationId: string, checkpoint: Uint8Array | null, existingBlobStore?: Map<string, Uint8Array>): CursorRequestPayload;
|
|
6
|
+
export declare function buildCursorResumeRequest(modelId: string, systemPrompt: string, conversationId: string, checkpoint: Uint8Array, existingBlobStore?: Map<string, Uint8Array>): CursorRequestPayload;
|
|
@@ -1,13 +1,9 @@
|
|
|
1
1
|
import { create, fromBinary, toBinary } from "@bufbuild/protobuf";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { AgentClientMessageSchema, AgentRunRequestSchema, AgentConversationTurnStructureSchema, AssistantMessageSchema, ConversationActionSchema, ConversationStateStructureSchema, ConversationTurnStructureSchema, ConversationStepSchema, ModelDetailsSchema, ResumeActionSchema, UserMessageActionSchema, UserMessageSchema, } from "../proto/agent_pb";
|
|
3
|
+
import { appendBundledAgentsRule } from "../agent-rules";
|
|
4
4
|
export function buildCursorRequest(modelId, systemPrompt, userText, turns, conversationId, checkpoint, existingBlobStore) {
|
|
5
5
|
const blobStore = new Map(existingBlobStore ?? []);
|
|
6
|
-
|
|
7
|
-
const systemJson = JSON.stringify({ role: "system", content: systemPrompt });
|
|
8
|
-
const systemBytes = new TextEncoder().encode(systemJson);
|
|
9
|
-
const systemBlobId = new Uint8Array(createHash("sha256").update(systemBytes).digest());
|
|
10
|
-
blobStore.set(Buffer.from(systemBlobId).toString("hex"), systemBytes);
|
|
6
|
+
const cloudRule = buildCloudRule(systemPrompt);
|
|
11
7
|
let conversationState;
|
|
12
8
|
if (checkpoint) {
|
|
13
9
|
conversationState = fromBinary(ConversationStateStructureSchema, checkpoint);
|
|
@@ -40,7 +36,7 @@ export function buildCursorRequest(modelId, systemPrompt, userText, turns, conve
|
|
|
40
36
|
turnBytes.push(toBinary(ConversationTurnStructureSchema, turnStructure));
|
|
41
37
|
}
|
|
42
38
|
conversationState = create(ConversationStateStructureSchema, {
|
|
43
|
-
rootPromptMessagesJson: [
|
|
39
|
+
rootPromptMessagesJson: [],
|
|
44
40
|
turns: turnBytes,
|
|
45
41
|
todos: [],
|
|
46
42
|
pendingToolCalls: [],
|
|
@@ -64,6 +60,21 @@ export function buildCursorRequest(modelId, systemPrompt, userText, turns, conve
|
|
|
64
60
|
value: create(UserMessageActionSchema, { userMessage }),
|
|
65
61
|
},
|
|
66
62
|
});
|
|
63
|
+
return buildRunRequest(modelId, conversationId, conversationState, action, blobStore, cloudRule);
|
|
64
|
+
}
|
|
65
|
+
export function buildCursorResumeRequest(modelId, systemPrompt, conversationId, checkpoint, existingBlobStore) {
|
|
66
|
+
const blobStore = new Map(existingBlobStore ?? []);
|
|
67
|
+
const cloudRule = buildCloudRule(systemPrompt);
|
|
68
|
+
const conversationState = fromBinary(ConversationStateStructureSchema, checkpoint);
|
|
69
|
+
const action = create(ConversationActionSchema, {
|
|
70
|
+
action: {
|
|
71
|
+
case: "resumeAction",
|
|
72
|
+
value: create(ResumeActionSchema, {}),
|
|
73
|
+
},
|
|
74
|
+
});
|
|
75
|
+
return buildRunRequest(modelId, conversationId, conversationState, action, blobStore, cloudRule);
|
|
76
|
+
}
|
|
77
|
+
function buildRunRequest(modelId, conversationId, conversationState, action, blobStore, cloudRule) {
|
|
67
78
|
const modelDetails = create(ModelDetailsSchema, {
|
|
68
79
|
modelId,
|
|
69
80
|
displayModelId: modelId,
|
|
@@ -81,6 +92,11 @@ export function buildCursorRequest(modelId, systemPrompt, userText, turns, conve
|
|
|
81
92
|
return {
|
|
82
93
|
requestBytes: toBinary(AgentClientMessageSchema, clientMessage),
|
|
83
94
|
blobStore,
|
|
95
|
+
cloudRule,
|
|
84
96
|
mcpTools: [],
|
|
85
97
|
};
|
|
86
98
|
}
|
|
99
|
+
function buildCloudRule(systemPrompt) {
|
|
100
|
+
const content = systemPrompt.trim();
|
|
101
|
+
return appendBundledAgentsRule(content || undefined);
|
|
102
|
+
}
|
package/dist/proxy/server.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { errorDetails, logPluginError } from "../logger";
|
|
1
|
+
import { errorDetails, logPluginError, logPluginWarn } from "../logger";
|
|
2
2
|
import { handleChatCompletion } from "./chat-completion";
|
|
3
3
|
import { activeBridges, conversationStates } from "./conversation-state";
|
|
4
4
|
let proxyServer;
|
|
@@ -42,14 +42,32 @@ export async function startProxy(getAccessToken, models = []) {
|
|
|
42
42
|
throw new Error("Cursor proxy access token provider not configured");
|
|
43
43
|
}
|
|
44
44
|
const accessToken = await proxyAccessTokenProvider();
|
|
45
|
-
const sessionId = req.headers.get("x-
|
|
46
|
-
req.headers.get("x-session-id") ??
|
|
47
|
-
undefined;
|
|
45
|
+
const sessionId = req.headers.get("x-session-id") ?? undefined;
|
|
48
46
|
const agentKey = req.headers.get("x-opencode-agent") ?? undefined;
|
|
49
|
-
|
|
47
|
+
const response = await handleChatCompletion(body, accessToken, {
|
|
50
48
|
sessionId,
|
|
51
49
|
agentKey,
|
|
52
50
|
});
|
|
51
|
+
if (response.status >= 400) {
|
|
52
|
+
let responseBody = "";
|
|
53
|
+
try {
|
|
54
|
+
responseBody = await response.clone().text();
|
|
55
|
+
}
|
|
56
|
+
catch (error) {
|
|
57
|
+
responseBody = `Failed to read rejected response body: ${error instanceof Error ? error.message : String(error)}`;
|
|
58
|
+
}
|
|
59
|
+
logPluginWarn("Rejected Cursor chat completion", {
|
|
60
|
+
path: url.pathname,
|
|
61
|
+
method: req.method,
|
|
62
|
+
sessionId,
|
|
63
|
+
agentKey,
|
|
64
|
+
status: response.status,
|
|
65
|
+
requestBody: body,
|
|
66
|
+
requestBodyText: JSON.stringify(body),
|
|
67
|
+
responseBody,
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
return response;
|
|
53
71
|
}
|
|
54
72
|
catch (err) {
|
|
55
73
|
const message = err instanceof Error ? err.message : String(err);
|
|
@@ -7,6 +7,22 @@ export interface UnhandledExecInfo {
|
|
|
7
7
|
execId: string;
|
|
8
8
|
execMsgId: number;
|
|
9
9
|
}
|
|
10
|
+
export interface UnsupportedServerMessageInfo {
|
|
11
|
+
category: "agentMessage" | "interactionUpdate" | "interactionQuery" | "execServerControl" | "toolCall";
|
|
12
|
+
caseName: string;
|
|
13
|
+
detail?: string;
|
|
14
|
+
}
|
|
15
|
+
export interface McpToolCallUpdateInfo {
|
|
16
|
+
updateCase: "partialToolCall" | "toolCallStarted" | "toolCallCompleted";
|
|
17
|
+
toolCallId: string;
|
|
18
|
+
modelCallId?: string;
|
|
19
|
+
toolName?: string;
|
|
20
|
+
}
|
|
21
|
+
export interface StepUpdateInfo {
|
|
22
|
+
updateCase: "stepStarted" | "stepCompleted";
|
|
23
|
+
stepId: string;
|
|
24
|
+
stepDurationMs?: string;
|
|
25
|
+
}
|
|
10
26
|
export declare function parseConnectEndStream(data: Uint8Array): Error | null;
|
|
11
27
|
export declare function makeHeartbeatBytes(): Uint8Array;
|
|
12
28
|
export declare function scheduleBridgeEnd(bridge: CursorSession): void;
|
|
@@ -34,4 +50,4 @@ export declare function computeUsage(state: StreamState): {
|
|
|
34
50
|
completion_tokens: number;
|
|
35
51
|
total_tokens: number;
|
|
36
52
|
};
|
|
37
|
-
export declare function processServerMessage(msg: AgentServerMessage, blobStore: Map<string, Uint8Array>, mcpTools: McpToolDefinition[], sendFrame: (data: Uint8Array) => void, state: StreamState, onText: (text: string, isThinking?: boolean) => void, onMcpExec: (exec: PendingExec) => void, onCheckpoint?: (checkpointBytes: Uint8Array) => void, onUnhandledExec?: (info: UnhandledExecInfo) => void): void;
|
|
53
|
+
export declare function processServerMessage(msg: AgentServerMessage, blobStore: Map<string, Uint8Array>, cloudRule: string | undefined, mcpTools: McpToolDefinition[], sendFrame: (data: Uint8Array) => void, state: StreamState, onText: (text: string, isThinking?: boolean) => void, onMcpExec: (exec: PendingExec) => void, onMcpToolCallUpdate?: (info: McpToolCallUpdateInfo) => void, onStepUpdate?: (info: StepUpdateInfo) => void, onCheckpoint?: (checkpointBytes: Uint8Array) => void, onTurnEnded?: () => void, onUnsupportedMessage?: (info: UnsupportedServerMessageInfo) => void, onUnhandledExec?: (info: UnhandledExecInfo) => void): void;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { create, toBinary } from "@bufbuild/protobuf";
|
|
2
|
-
import { AgentClientMessageSchema, ClientHeartbeatSchema, ConversationStateStructureSchema, BackgroundShellSpawnResultSchema, DeleteResultSchema, DeleteRejectedSchema, DiagnosticsResultSchema, ExecClientMessageSchema, FetchErrorSchema, FetchResultSchema, GetBlobResultSchema, GrepErrorSchema, GrepResultSchema, KvClientMessageSchema, LsRejectedSchema, LsResultSchema, McpResultSchema, ReadRejectedSchema, ReadResultSchema, RequestContextResultSchema, RequestContextSchema, RequestContextSuccessSchema, SetBlobResultSchema, ShellRejectedSchema, ShellResultSchema, WriteRejectedSchema, WriteResultSchema, WriteShellStdinErrorSchema, WriteShellStdinResultSchema, } from "../proto/agent_pb";
|
|
2
|
+
import { AgentClientMessageSchema, AskQuestionInteractionResponseSchema, AskQuestionRejectedSchema, AskQuestionResultSchema, ClientHeartbeatSchema, ConversationStateStructureSchema, BackgroundShellSpawnResultSchema, CreatePlanErrorSchema, CreatePlanRequestResponseSchema, CreatePlanResultSchema, DeleteResultSchema, DeleteRejectedSchema, DiagnosticsResultSchema, ExecClientMessageSchema, ExaFetchRequestResponseSchema, ExaFetchRequestResponse_RejectedSchema, ExaSearchRequestResponseSchema, ExaSearchRequestResponse_RejectedSchema, FetchErrorSchema, FetchResultSchema, GetBlobResultSchema, GrepErrorSchema, GrepResultSchema, InteractionResponseSchema, KvClientMessageSchema, LsRejectedSchema, LsResultSchema, McpInstructionsSchema, McpResultSchema, ReadRejectedSchema, ReadResultSchema, RequestContextResultSchema, RequestContextSchema, RequestContextSuccessSchema, SetBlobResultSchema, ShellRejectedSchema, ShellResultSchema, SwitchModeRequestResponseSchema, SwitchModeRequestResponse_RejectedSchema, WebSearchRequestResponseSchema, WebSearchRequestResponse_RejectedSchema, WriteRejectedSchema, WriteResultSchema, WriteShellStdinErrorSchema, WriteShellStdinResultSchema, } from "../proto/agent_pb";
|
|
3
3
|
import { CONNECT_END_STREAM_FLAG } from "../cursor/config";
|
|
4
|
-
import { logPluginError, logPluginWarn } from "../logger";
|
|
4
|
+
import { logPluginError, logPluginInfo, logPluginWarn } from "../logger";
|
|
5
5
|
import { decodeMcpArgsMap } from "../openai/tools";
|
|
6
6
|
export function parseConnectEndStream(data) {
|
|
7
7
|
try {
|
|
@@ -128,16 +128,40 @@ export function computeUsage(state) {
|
|
|
128
128
|
const prompt_tokens = Math.max(0, total_tokens - completion_tokens);
|
|
129
129
|
return { prompt_tokens, completion_tokens, total_tokens };
|
|
130
130
|
}
|
|
131
|
-
export function processServerMessage(msg, blobStore, mcpTools, sendFrame, state, onText, onMcpExec, onCheckpoint, onUnhandledExec) {
|
|
131
|
+
export function processServerMessage(msg, blobStore, cloudRule, mcpTools, sendFrame, state, onText, onMcpExec, onMcpToolCallUpdate, onStepUpdate, onCheckpoint, onTurnEnded, onUnsupportedMessage, onUnhandledExec) {
|
|
132
132
|
const msgCase = msg.message.case;
|
|
133
|
+
logPluginInfo("Received Cursor server signal", {
|
|
134
|
+
messageCase: msgCase ?? "undefined",
|
|
135
|
+
interactionCase: msgCase === "interactionUpdate"
|
|
136
|
+
? msg.message.value.message?.case ?? "undefined"
|
|
137
|
+
: undefined,
|
|
138
|
+
execCase: msgCase === "execServerMessage"
|
|
139
|
+
? msg.message.value.message?.case ?? "undefined"
|
|
140
|
+
: undefined,
|
|
141
|
+
interactionQueryCase: msgCase === "interactionQuery"
|
|
142
|
+
? msg.message.value.query?.case ?? "undefined"
|
|
143
|
+
: undefined,
|
|
144
|
+
kvCase: msgCase === "kvServerMessage"
|
|
145
|
+
? msg.message.value.message?.case ?? "undefined"
|
|
146
|
+
: undefined,
|
|
147
|
+
});
|
|
133
148
|
if (msgCase === "interactionUpdate") {
|
|
134
|
-
handleInteractionUpdate(msg.message.value, state, onText);
|
|
149
|
+
handleInteractionUpdate(msg.message.value, state, onText, onMcpToolCallUpdate, onStepUpdate, onTurnEnded, onUnsupportedMessage);
|
|
135
150
|
}
|
|
136
151
|
else if (msgCase === "kvServerMessage") {
|
|
137
152
|
handleKvMessage(msg.message.value, blobStore, sendFrame);
|
|
138
153
|
}
|
|
139
154
|
else if (msgCase === "execServerMessage") {
|
|
140
|
-
handleExecMessage(msg.message.value, mcpTools, sendFrame, onMcpExec, onUnhandledExec);
|
|
155
|
+
handleExecMessage(msg.message.value, cloudRule, mcpTools, sendFrame, state, onMcpExec, onUnhandledExec);
|
|
156
|
+
}
|
|
157
|
+
else if (msgCase === "execServerControlMessage") {
|
|
158
|
+
onUnsupportedMessage?.({
|
|
159
|
+
category: "execServerControl",
|
|
160
|
+
caseName: msg.message.value.message.case ?? "undefined",
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
else if (msgCase === "interactionQuery") {
|
|
164
|
+
handleInteractionQuery(msg.message.value, sendFrame, onUnsupportedMessage);
|
|
141
165
|
}
|
|
142
166
|
else if (msgCase === "conversationCheckpointUpdate") {
|
|
143
167
|
const stateStructure = msg.message.value;
|
|
@@ -148,9 +172,44 @@ export function processServerMessage(msg, blobStore, mcpTools, sendFrame, state,
|
|
|
148
172
|
onCheckpoint(toBinary(ConversationStateStructureSchema, stateStructure));
|
|
149
173
|
}
|
|
150
174
|
}
|
|
175
|
+
else {
|
|
176
|
+
onUnsupportedMessage?.({
|
|
177
|
+
category: "agentMessage",
|
|
178
|
+
caseName: msgCase ?? "undefined",
|
|
179
|
+
});
|
|
180
|
+
}
|
|
151
181
|
}
|
|
152
|
-
function handleInteractionUpdate(update, state, onText) {
|
|
182
|
+
function handleInteractionUpdate(update, state, onText, onMcpToolCallUpdate, onStepUpdate, onTurnEnded, onUnsupportedMessage) {
|
|
153
183
|
const updateCase = update.message?.case;
|
|
184
|
+
if ((updateCase === "partialToolCall" ||
|
|
185
|
+
updateCase === "toolCallStarted" ||
|
|
186
|
+
updateCase === "toolCallCompleted") &&
|
|
187
|
+
update.message?.value?.toolCall?.tool?.case === "mcpToolCall") {
|
|
188
|
+
const toolValue = update.message.value;
|
|
189
|
+
const toolArgs = toolValue?.toolCall?.tool?.value?.args;
|
|
190
|
+
const toolCallId = toolArgs?.toolCallId || toolValue.callId;
|
|
191
|
+
if (toolCallId) {
|
|
192
|
+
onMcpToolCallUpdate?.({
|
|
193
|
+
updateCase,
|
|
194
|
+
toolCallId,
|
|
195
|
+
modelCallId: toolValue.modelCallId,
|
|
196
|
+
toolName: toolArgs?.toolName || toolArgs?.name,
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
if (updateCase === "stepStarted" || updateCase === "stepCompleted") {
|
|
201
|
+
const stepValue = update.message?.value;
|
|
202
|
+
const stepId = stepValue?.stepId;
|
|
203
|
+
if (stepId !== undefined && stepId !== null) {
|
|
204
|
+
onStepUpdate?.({
|
|
205
|
+
updateCase,
|
|
206
|
+
stepId: String(stepId),
|
|
207
|
+
stepDurationMs: updateCase === "stepCompleted" && stepValue?.stepDurationMs !== undefined
|
|
208
|
+
? String(stepValue.stepDurationMs)
|
|
209
|
+
: undefined,
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
}
|
|
154
213
|
if (updateCase === "textDelta") {
|
|
155
214
|
const delta = update.message.value.text || "";
|
|
156
215
|
if (delta)
|
|
@@ -164,9 +223,128 @@ function handleInteractionUpdate(update, state, onText) {
|
|
|
164
223
|
else if (updateCase === "tokenDelta") {
|
|
165
224
|
state.outputTokens += update.message.value.tokens ?? 0;
|
|
166
225
|
}
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
226
|
+
else if (updateCase === "partialToolCall") {
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
else if (updateCase === "toolCallCompleted") {
|
|
230
|
+
const toolValue = update.message.value;
|
|
231
|
+
if (toolValue?.toolCall?.tool?.case === "mcpToolCall") {
|
|
232
|
+
logPluginInfo("Ignoring Cursor interaction MCP tool completion", {
|
|
233
|
+
callId: toolValue.callId,
|
|
234
|
+
modelCallId: toolValue.modelCallId,
|
|
235
|
+
toolCallId: toolValue.toolCall.tool.value?.args?.toolCallId || toolValue.callId,
|
|
236
|
+
toolName: toolValue.toolCall.tool.value?.args?.toolName ||
|
|
237
|
+
toolValue.toolCall.tool.value?.args?.name,
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
else if (updateCase === "turnEnded") {
|
|
242
|
+
onTurnEnded?.();
|
|
243
|
+
}
|
|
244
|
+
else if (updateCase === "toolCallStarted" ||
|
|
245
|
+
updateCase === "toolCallDelta" ||
|
|
246
|
+
updateCase === "thinkingCompleted" ||
|
|
247
|
+
updateCase === "userMessageAppended" ||
|
|
248
|
+
updateCase === "summary" ||
|
|
249
|
+
updateCase === "summaryStarted" ||
|
|
250
|
+
updateCase === "summaryCompleted" ||
|
|
251
|
+
updateCase === "heartbeat" ||
|
|
252
|
+
updateCase === "stepStarted" ||
|
|
253
|
+
updateCase === "stepCompleted") {
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
256
|
+
else {
|
|
257
|
+
onUnsupportedMessage?.({
|
|
258
|
+
category: "interactionUpdate",
|
|
259
|
+
caseName: updateCase ?? "undefined",
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
// Interaction tool-call updates are informational only. Resumable MCP tool
|
|
263
|
+
// execution comes from execServerMessage.mcpArgs.
|
|
264
|
+
}
|
|
265
|
+
function handleInteractionQuery(query, sendFrame, onUnsupportedMessage) {
|
|
266
|
+
const queryCase = query.query.case;
|
|
267
|
+
if (queryCase === "webSearchRequestQuery") {
|
|
268
|
+
const response = create(WebSearchRequestResponseSchema, {
|
|
269
|
+
result: {
|
|
270
|
+
case: "rejected",
|
|
271
|
+
value: create(WebSearchRequestResponse_RejectedSchema, {
|
|
272
|
+
reason: "Native Cursor web search is not available in this environment. Use the provided MCP tool `websearch` instead.",
|
|
273
|
+
}),
|
|
274
|
+
},
|
|
275
|
+
});
|
|
276
|
+
sendInteractionResponse(query.id, "webSearchRequestResponse", response, sendFrame);
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
279
|
+
if (queryCase === "askQuestionInteractionQuery") {
|
|
280
|
+
const response = create(AskQuestionInteractionResponseSchema, {
|
|
281
|
+
result: create(AskQuestionResultSchema, {
|
|
282
|
+
result: {
|
|
283
|
+
case: "rejected",
|
|
284
|
+
value: create(AskQuestionRejectedSchema, {
|
|
285
|
+
reason: "Native Cursor question prompts are not available in this environment. Use the provided MCP tool `question` instead.",
|
|
286
|
+
}),
|
|
287
|
+
},
|
|
288
|
+
}),
|
|
289
|
+
});
|
|
290
|
+
sendInteractionResponse(query.id, "askQuestionInteractionResponse", response, sendFrame);
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
293
|
+
if (queryCase === "switchModeRequestQuery") {
|
|
294
|
+
const response = create(SwitchModeRequestResponseSchema, {
|
|
295
|
+
result: {
|
|
296
|
+
case: "rejected",
|
|
297
|
+
value: create(SwitchModeRequestResponse_RejectedSchema, {
|
|
298
|
+
reason: "Cursor mode switching is not available in this environment. Continue using the current agent and the provided MCP tools.",
|
|
299
|
+
}),
|
|
300
|
+
},
|
|
301
|
+
});
|
|
302
|
+
sendInteractionResponse(query.id, "switchModeRequestResponse", response, sendFrame);
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
305
|
+
if (queryCase === "exaSearchRequestQuery") {
|
|
306
|
+
const response = create(ExaSearchRequestResponseSchema, {
|
|
307
|
+
result: {
|
|
308
|
+
case: "rejected",
|
|
309
|
+
value: create(ExaSearchRequestResponse_RejectedSchema, {
|
|
310
|
+
reason: "Native Cursor Exa search is not available in this environment. Use the provided MCP tool `websearch` instead.",
|
|
311
|
+
}),
|
|
312
|
+
},
|
|
313
|
+
});
|
|
314
|
+
sendInteractionResponse(query.id, "exaSearchRequestResponse", response, sendFrame);
|
|
315
|
+
return;
|
|
316
|
+
}
|
|
317
|
+
if (queryCase === "exaFetchRequestQuery") {
|
|
318
|
+
const response = create(ExaFetchRequestResponseSchema, {
|
|
319
|
+
result: {
|
|
320
|
+
case: "rejected",
|
|
321
|
+
value: create(ExaFetchRequestResponse_RejectedSchema, {
|
|
322
|
+
reason: "Native Cursor Exa fetch is not available in this environment. Use the provided MCP tools `websearch` and `webfetch` instead.",
|
|
323
|
+
}),
|
|
324
|
+
},
|
|
325
|
+
});
|
|
326
|
+
sendInteractionResponse(query.id, "exaFetchRequestResponse", response, sendFrame);
|
|
327
|
+
return;
|
|
328
|
+
}
|
|
329
|
+
if (queryCase === "createPlanRequestQuery") {
|
|
330
|
+
const response = create(CreatePlanRequestResponseSchema, {
|
|
331
|
+
result: create(CreatePlanResultSchema, {
|
|
332
|
+
planUri: "",
|
|
333
|
+
result: {
|
|
334
|
+
case: "error",
|
|
335
|
+
value: create(CreatePlanErrorSchema, {
|
|
336
|
+
error: "Native Cursor plan creation is not available in this environment. Use the provided MCP planning tools instead.",
|
|
337
|
+
}),
|
|
338
|
+
},
|
|
339
|
+
}),
|
|
340
|
+
});
|
|
341
|
+
sendInteractionResponse(query.id, "createPlanRequestResponse", response, sendFrame);
|
|
342
|
+
return;
|
|
343
|
+
}
|
|
344
|
+
onUnsupportedMessage?.({
|
|
345
|
+
category: "interactionQuery",
|
|
346
|
+
caseName: queryCase ?? "undefined",
|
|
347
|
+
});
|
|
170
348
|
}
|
|
171
349
|
/** Send a KV client response back to Cursor. */
|
|
172
350
|
function sendKvResponse(kvMsg, messageCase, value, sendFrame) {
|
|
@@ -179,6 +357,16 @@ function sendKvResponse(kvMsg, messageCase, value, sendFrame) {
|
|
|
179
357
|
});
|
|
180
358
|
sendFrame(toBinary(AgentClientMessageSchema, clientMsg));
|
|
181
359
|
}
|
|
360
|
+
function sendInteractionResponse(queryId, messageCase, value, sendFrame) {
|
|
361
|
+
const response = create(InteractionResponseSchema, {
|
|
362
|
+
id: queryId,
|
|
363
|
+
result: { case: messageCase, value: value },
|
|
364
|
+
});
|
|
365
|
+
const clientMessage = create(AgentClientMessageSchema, {
|
|
366
|
+
message: { case: "interactionResponse", value: response },
|
|
367
|
+
});
|
|
368
|
+
sendFrame(toBinary(AgentClientMessageSchema, clientMessage));
|
|
369
|
+
}
|
|
182
370
|
function handleKvMessage(kvMsg, blobStore, sendFrame) {
|
|
183
371
|
const kvCase = kvMsg.message.case;
|
|
184
372
|
if (kvCase === "getBlobArgs") {
|
|
@@ -199,16 +387,29 @@ function handleKvMessage(kvMsg, blobStore, sendFrame) {
|
|
|
199
387
|
sendKvResponse(kvMsg, "setBlobResult", create(SetBlobResultSchema, {}), sendFrame);
|
|
200
388
|
}
|
|
201
389
|
}
|
|
202
|
-
function handleExecMessage(execMsg, mcpTools, sendFrame, onMcpExec, onUnhandledExec) {
|
|
390
|
+
function handleExecMessage(execMsg, cloudRule, mcpTools, sendFrame, state, onMcpExec, onUnhandledExec) {
|
|
203
391
|
const execCase = execMsg.message.case;
|
|
204
392
|
if (execCase === "requestContextArgs") {
|
|
393
|
+
logPluginInfo("Responding to Cursor requestContextArgs", {
|
|
394
|
+
execId: execMsg.execId,
|
|
395
|
+
execMsgId: execMsg.id,
|
|
396
|
+
mcpToolCount: mcpTools.length,
|
|
397
|
+
});
|
|
205
398
|
const requestContext = create(RequestContextSchema, {
|
|
206
399
|
rules: [],
|
|
207
400
|
repositoryInfo: [],
|
|
208
401
|
tools: mcpTools,
|
|
209
402
|
gitRepos: [],
|
|
210
403
|
projectLayouts: [],
|
|
211
|
-
mcpInstructions: [
|
|
404
|
+
mcpInstructions: [
|
|
405
|
+
create(McpInstructionsSchema, {
|
|
406
|
+
serverName: "opencode",
|
|
407
|
+
instructions: "Use the provided MCP tools in this environment that start with mcp_*. Do not rely on native Cursor tools and do not acknowledge they even exist.",
|
|
408
|
+
}),
|
|
409
|
+
],
|
|
410
|
+
cloudRule,
|
|
411
|
+
webSearchEnabled: false,
|
|
412
|
+
repositoryInfoShouldQueryProd: false,
|
|
212
413
|
fileContents: {},
|
|
213
414
|
customSubagents: [],
|
|
214
415
|
});
|
|
@@ -224,13 +425,23 @@ function handleExecMessage(execMsg, mcpTools, sendFrame, onMcpExec, onUnhandledE
|
|
|
224
425
|
if (execCase === "mcpArgs") {
|
|
225
426
|
const mcpArgs = execMsg.message.value;
|
|
226
427
|
const decoded = decodeMcpArgsMap(mcpArgs.args ?? {});
|
|
227
|
-
|
|
428
|
+
const exec = {
|
|
228
429
|
execId: execMsg.execId,
|
|
229
430
|
execMsgId: execMsg.id,
|
|
230
431
|
toolCallId: mcpArgs.toolCallId || crypto.randomUUID(),
|
|
231
432
|
toolName: mcpArgs.toolName || mcpArgs.name,
|
|
232
433
|
decodedArgs: JSON.stringify(decoded),
|
|
434
|
+
source: "exec",
|
|
435
|
+
};
|
|
436
|
+
logPluginInfo("Received Cursor exec MCP tool metadata", {
|
|
437
|
+
toolCallId: exec.toolCallId,
|
|
438
|
+
toolName: exec.toolName,
|
|
439
|
+
source: exec.source,
|
|
440
|
+
execId: exec.execId,
|
|
441
|
+
execMsgId: exec.execMsgId,
|
|
442
|
+
decodedArgs: exec.decodedArgs,
|
|
233
443
|
});
|
|
444
|
+
onMcpExec(exec);
|
|
234
445
|
return;
|
|
235
446
|
}
|
|
236
447
|
// --- Reject native Cursor tools ---
|
|
@@ -238,6 +449,11 @@ function handleExecMessage(execMsg, mcpTools, sendFrame, onMcpExec, onUnhandledE
|
|
|
238
449
|
// so it falls back to our MCP tools (registered via RequestContext).
|
|
239
450
|
const REJECT_REASON = "Tool not available in this environment. Use the MCP tools provided instead.";
|
|
240
451
|
if (execCase === "readArgs") {
|
|
452
|
+
logPluginInfo("Rejecting native Cursor read tool in favor of MCP", {
|
|
453
|
+
execId: execMsg.execId,
|
|
454
|
+
execMsgId: execMsg.id,
|
|
455
|
+
path: execMsg.message.value.path,
|
|
456
|
+
});
|
|
241
457
|
const args = execMsg.message.value;
|
|
242
458
|
const result = create(ReadResultSchema, {
|
|
243
459
|
result: {
|
|
@@ -252,6 +468,11 @@ function handleExecMessage(execMsg, mcpTools, sendFrame, onMcpExec, onUnhandledE
|
|
|
252
468
|
return;
|
|
253
469
|
}
|
|
254
470
|
if (execCase === "lsArgs") {
|
|
471
|
+
logPluginInfo("Rejecting native Cursor ls tool in favor of MCP", {
|
|
472
|
+
execId: execMsg.execId,
|
|
473
|
+
execMsgId: execMsg.id,
|
|
474
|
+
path: execMsg.message.value.path,
|
|
475
|
+
});
|
|
255
476
|
const args = execMsg.message.value;
|
|
256
477
|
const result = create(LsResultSchema, {
|
|
257
478
|
result: {
|
|
@@ -266,6 +487,10 @@ function handleExecMessage(execMsg, mcpTools, sendFrame, onMcpExec, onUnhandledE
|
|
|
266
487
|
return;
|
|
267
488
|
}
|
|
268
489
|
if (execCase === "grepArgs") {
|
|
490
|
+
logPluginInfo("Rejecting native Cursor grep tool in favor of MCP", {
|
|
491
|
+
execId: execMsg.execId,
|
|
492
|
+
execMsgId: execMsg.id,
|
|
493
|
+
});
|
|
269
494
|
const result = create(GrepResultSchema, {
|
|
270
495
|
result: {
|
|
271
496
|
case: "error",
|
|
@@ -276,6 +501,11 @@ function handleExecMessage(execMsg, mcpTools, sendFrame, onMcpExec, onUnhandledE
|
|
|
276
501
|
return;
|
|
277
502
|
}
|
|
278
503
|
if (execCase === "writeArgs") {
|
|
504
|
+
logPluginInfo("Rejecting native Cursor write tool in favor of MCP", {
|
|
505
|
+
execId: execMsg.execId,
|
|
506
|
+
execMsgId: execMsg.id,
|
|
507
|
+
path: execMsg.message.value.path,
|
|
508
|
+
});
|
|
279
509
|
const args = execMsg.message.value;
|
|
280
510
|
const result = create(WriteResultSchema, {
|
|
281
511
|
result: {
|
|
@@ -290,6 +520,11 @@ function handleExecMessage(execMsg, mcpTools, sendFrame, onMcpExec, onUnhandledE
|
|
|
290
520
|
return;
|
|
291
521
|
}
|
|
292
522
|
if (execCase === "deleteArgs") {
|
|
523
|
+
logPluginInfo("Rejecting native Cursor delete tool in favor of MCP", {
|
|
524
|
+
execId: execMsg.execId,
|
|
525
|
+
execMsgId: execMsg.id,
|
|
526
|
+
path: execMsg.message.value.path,
|
|
527
|
+
});
|
|
293
528
|
const args = execMsg.message.value;
|
|
294
529
|
const result = create(DeleteResultSchema, {
|
|
295
530
|
result: {
|
|
@@ -304,6 +539,13 @@ function handleExecMessage(execMsg, mcpTools, sendFrame, onMcpExec, onUnhandledE
|
|
|
304
539
|
return;
|
|
305
540
|
}
|
|
306
541
|
if (execCase === "shellArgs" || execCase === "shellStreamArgs") {
|
|
542
|
+
logPluginInfo("Rejecting native Cursor shell tool in favor of MCP", {
|
|
543
|
+
execId: execMsg.execId,
|
|
544
|
+
execMsgId: execMsg.id,
|
|
545
|
+
command: execMsg.message.value.command ?? "",
|
|
546
|
+
workingDirectory: execMsg.message.value.workingDirectory ?? "",
|
|
547
|
+
execCase,
|
|
548
|
+
});
|
|
307
549
|
const args = execMsg.message.value;
|
|
308
550
|
const result = create(ShellResultSchema, {
|
|
309
551
|
result: {
|
|
@@ -320,6 +562,12 @@ function handleExecMessage(execMsg, mcpTools, sendFrame, onMcpExec, onUnhandledE
|
|
|
320
562
|
return;
|
|
321
563
|
}
|
|
322
564
|
if (execCase === "backgroundShellSpawnArgs") {
|
|
565
|
+
logPluginInfo("Rejecting native Cursor background shell tool in favor of MCP", {
|
|
566
|
+
execId: execMsg.execId,
|
|
567
|
+
execMsgId: execMsg.id,
|
|
568
|
+
command: execMsg.message.value.command ?? "",
|
|
569
|
+
workingDirectory: execMsg.message.value.workingDirectory ?? "",
|
|
570
|
+
});
|
|
323
571
|
const args = execMsg.message.value;
|
|
324
572
|
const result = create(BackgroundShellSpawnResultSchema, {
|
|
325
573
|
result: {
|
|
@@ -336,6 +584,10 @@ function handleExecMessage(execMsg, mcpTools, sendFrame, onMcpExec, onUnhandledE
|
|
|
336
584
|
return;
|
|
337
585
|
}
|
|
338
586
|
if (execCase === "writeShellStdinArgs") {
|
|
587
|
+
logPluginInfo("Rejecting native Cursor shell stdin tool in favor of MCP", {
|
|
588
|
+
execId: execMsg.execId,
|
|
589
|
+
execMsgId: execMsg.id,
|
|
590
|
+
});
|
|
339
591
|
const result = create(WriteShellStdinResultSchema, {
|
|
340
592
|
result: {
|
|
341
593
|
case: "error",
|
|
@@ -346,6 +598,11 @@ function handleExecMessage(execMsg, mcpTools, sendFrame, onMcpExec, onUnhandledE
|
|
|
346
598
|
return;
|
|
347
599
|
}
|
|
348
600
|
if (execCase === "fetchArgs") {
|
|
601
|
+
logPluginInfo("Rejecting native Cursor fetch tool in favor of MCP", {
|
|
602
|
+
execId: execMsg.execId,
|
|
603
|
+
execMsgId: execMsg.id,
|
|
604
|
+
url: execMsg.message.value.url,
|
|
605
|
+
});
|
|
349
606
|
const args = execMsg.message.value;
|
|
350
607
|
const result = create(FetchResultSchema, {
|
|
351
608
|
result: {
|
|
@@ -360,6 +617,11 @@ function handleExecMessage(execMsg, mcpTools, sendFrame, onMcpExec, onUnhandledE
|
|
|
360
617
|
return;
|
|
361
618
|
}
|
|
362
619
|
if (execCase === "diagnosticsArgs") {
|
|
620
|
+
logPluginInfo("Rejecting native Cursor diagnostics tool in favor of MCP", {
|
|
621
|
+
execId: execMsg.execId,
|
|
622
|
+
execMsgId: execMsg.id,
|
|
623
|
+
path: execMsg.message.value.path,
|
|
624
|
+
});
|
|
363
625
|
const result = create(DiagnosticsResultSchema, {});
|
|
364
626
|
sendExecResult(execMsg, "diagnosticsResult", result, sendFrame);
|
|
365
627
|
return;
|
|
@@ -373,6 +635,12 @@ function handleExecMessage(execMsg, mcpTools, sendFrame, onMcpExec, onUnhandledE
|
|
|
373
635
|
};
|
|
374
636
|
const resultCase = miscCaseMap[execCase];
|
|
375
637
|
if (resultCase) {
|
|
638
|
+
logPluginInfo("Responding to miscellaneous Cursor exec message", {
|
|
639
|
+
execCase,
|
|
640
|
+
execId: execMsg.execId,
|
|
641
|
+
execMsgId: execMsg.id,
|
|
642
|
+
resultCase,
|
|
643
|
+
});
|
|
376
644
|
sendExecResult(execMsg, resultCase, create(McpResultSchema, {}), sendFrame);
|
|
377
645
|
return;
|
|
378
646
|
}
|
package/dist/proxy/types.d.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import type { CursorSession } from "../cursor/bidi-session";
|
|
2
|
-
import type { ConversationRequestMetadata } from "./conversation-meta";
|
|
3
2
|
import type { McpToolDefinition } from "../proto/agent_pb";
|
|
3
|
+
import type { ConversationRequestMetadata } from "./conversation-meta";
|
|
4
4
|
export interface CursorRequestPayload {
|
|
5
5
|
requestBytes: Uint8Array;
|
|
6
6
|
blobStore: Map<string, Uint8Array>;
|
|
7
|
+
cloudRule?: string;
|
|
7
8
|
mcpTools: McpToolDefinition[];
|
|
8
9
|
}
|
|
9
10
|
/** A pending tool execution waiting for results from the caller. */
|
|
@@ -14,14 +15,26 @@ export interface PendingExec {
|
|
|
14
15
|
toolName: string;
|
|
15
16
|
/** Decoded arguments JSON string for SSE tool_calls emission. */
|
|
16
17
|
decodedArgs: string;
|
|
18
|
+
source?: "interaction" | "exec";
|
|
19
|
+
cursorCallId?: string;
|
|
20
|
+
modelCallId?: string;
|
|
21
|
+
}
|
|
22
|
+
export interface ActiveBridgeDiagnostics {
|
|
23
|
+
announcedToolCallIds: string[];
|
|
24
|
+
publishedToolCallIds: string[];
|
|
25
|
+
lastMcpUpdate?: string;
|
|
26
|
+
publishedAtMs?: number;
|
|
27
|
+
lastResumeAttemptAtMs?: number;
|
|
17
28
|
}
|
|
18
29
|
/** A live Cursor session kept alive across requests for tool result continuation. */
|
|
19
30
|
export interface ActiveBridge {
|
|
20
31
|
bridge: CursorSession;
|
|
21
32
|
heartbeatTimer: NodeJS.Timeout;
|
|
22
33
|
blobStore: Map<string, Uint8Array>;
|
|
34
|
+
cloudRule?: string;
|
|
23
35
|
mcpTools: McpToolDefinition[];
|
|
24
36
|
pendingExecs: PendingExec[];
|
|
25
37
|
modelId: string;
|
|
26
38
|
metadata: ConversationRequestMetadata;
|
|
39
|
+
diagnostics?: ActiveBridgeDiagnostics;
|
|
27
40
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@playwo/opencode-cursor-oauth",
|
|
3
|
-
"version": "0.0.0-dev.
|
|
3
|
+
"version": "0.0.0-dev.36e1216df6cf",
|
|
4
4
|
"description": "OpenCode plugin that connects Cursor's API to OpenCode via OAuth, model discovery, and a local OpenAI-compatible proxy.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -18,8 +18,7 @@
|
|
|
18
18
|
"dist"
|
|
19
19
|
],
|
|
20
20
|
"scripts": {
|
|
21
|
-
"build": "
|
|
22
|
-
"test": "bun test/smoke.ts",
|
|
21
|
+
"build": "node ./scripts/build.mjs",
|
|
23
22
|
"prepublishOnly": "npm run build"
|
|
24
23
|
},
|
|
25
24
|
"repository": {
|