@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.
@@ -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 { createHash } from "node:crypto";
3
- import { AgentClientMessageSchema, AgentRunRequestSchema, ConversationActionSchema, ConversationStateStructureSchema, ConversationStepSchema, AgentConversationTurnStructureSchema, ConversationTurnStructureSchema, AssistantMessageSchema, ModelDetailsSchema, UserMessageActionSchema, UserMessageSchema, } from "../proto/agent_pb";
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
- // System prompt → blob store (Cursor requests it back via KV handshake)
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: [systemBlobId],
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
+ }
@@ -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-opencode-session-id") ??
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
- return handleChatCompletion(body, accessToken, {
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
- // toolCallStarted, partialToolCall, toolCallDelta, toolCallCompleted
168
- // are intentionally ignored. MCP tool calls flow through the exec
169
- // message path (mcpArgs → mcpResult), not interaction updates.
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
- onMcpExec({
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
  }
@@ -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.2c48be2f48c9",
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": "tsc -p tsconfig.json",
22
- "test": "bun test/smoke.ts",
21
+ "build": "node ./scripts/build.mjs",
23
22
  "prepublishOnly": "npm run build"
24
23
  },
25
24
  "repository": {