@playwo/opencode-cursor-oauth 0.0.0-dev.12f4642b8f6b → 0.0.0-dev.14c6316643ec

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.
@@ -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, 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,25 @@ 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, onTurnEnded, onUnhandledExec) {
131
+ export function processServerMessage(msg, blobStore, cloudRule, mcpTools, sendFrame, state, onText, onMcpExec, onCheckpoint, onTurnEnded, onUnsupportedMessage, onUnhandledExec) {
132
132
  const msgCase = msg.message.case;
133
133
  if (msgCase === "interactionUpdate") {
134
- handleInteractionUpdate(msg.message.value, state, onText, onTurnEnded);
134
+ handleInteractionUpdate(msg.message.value, state, onText, onTurnEnded, onUnsupportedMessage);
135
135
  }
136
136
  else if (msgCase === "kvServerMessage") {
137
137
  handleKvMessage(msg.message.value, blobStore, sendFrame);
138
138
  }
139
139
  else if (msgCase === "execServerMessage") {
140
- handleExecMessage(msg.message.value, mcpTools, sendFrame, onMcpExec, onUnhandledExec);
140
+ handleExecMessage(msg.message.value, cloudRule, mcpTools, sendFrame, state, onMcpExec, onUnhandledExec);
141
+ }
142
+ else if (msgCase === "execServerControlMessage") {
143
+ onUnsupportedMessage?.({
144
+ category: "execServerControl",
145
+ caseName: msg.message.value.message.case ?? "undefined",
146
+ });
147
+ }
148
+ else if (msgCase === "interactionQuery") {
149
+ handleInteractionQuery(msg.message.value, sendFrame, onUnsupportedMessage);
141
150
  }
142
151
  else if (msgCase === "conversationCheckpointUpdate") {
143
152
  const stateStructure = msg.message.value;
@@ -148,9 +157,26 @@ export function processServerMessage(msg, blobStore, mcpTools, sendFrame, state,
148
157
  onCheckpoint(toBinary(ConversationStateStructureSchema, stateStructure));
149
158
  }
150
159
  }
160
+ else {
161
+ onUnsupportedMessage?.({
162
+ category: "agentMessage",
163
+ caseName: msgCase ?? "undefined",
164
+ });
165
+ }
151
166
  }
152
- function handleInteractionUpdate(update, state, onText, onTurnEnded) {
167
+ function handleInteractionUpdate(update, state, onText, onTurnEnded, onUnsupportedMessage) {
153
168
  const updateCase = update.message?.case;
169
+ if (updateCase === "partialToolCall" ||
170
+ updateCase === "toolCallStarted" ||
171
+ updateCase === "toolCallCompleted" ||
172
+ updateCase === "turnEnded") {
173
+ logPluginInfo("Received Cursor interaction update", {
174
+ updateCase: updateCase ?? "undefined",
175
+ callId: update.message?.value?.callId,
176
+ modelCallId: update.message?.value?.modelCallId,
177
+ toolCase: update.message?.value?.toolCall?.tool?.case,
178
+ });
179
+ }
154
180
  if (updateCase === "textDelta") {
155
181
  const delta = update.message.value.text || "";
156
182
  if (delta)
@@ -164,12 +190,128 @@ function handleInteractionUpdate(update, state, onText, onTurnEnded) {
164
190
  else if (updateCase === "tokenDelta") {
165
191
  state.outputTokens += update.message.value.tokens ?? 0;
166
192
  }
193
+ else if (updateCase === "partialToolCall") {
194
+ return;
195
+ }
196
+ else if (updateCase === "toolCallCompleted") {
197
+ const toolValue = update.message.value;
198
+ if (toolValue?.toolCall?.tool?.case === "mcpToolCall") {
199
+ logPluginInfo("Ignoring Cursor interaction MCP tool completion", {
200
+ callId: toolValue.callId,
201
+ modelCallId: toolValue.modelCallId,
202
+ toolCallId: toolValue.toolCall.tool.value?.args?.toolCallId || toolValue.callId,
203
+ toolName: toolValue.toolCall.tool.value?.args?.toolName ||
204
+ toolValue.toolCall.tool.value?.args?.name,
205
+ });
206
+ }
207
+ }
167
208
  else if (updateCase === "turnEnded") {
168
209
  onTurnEnded?.();
169
210
  }
170
- // toolCallStarted, partialToolCall, toolCallDelta, toolCallCompleted
171
- // are intentionally ignored. MCP tool calls flow through the exec
172
- // message path (mcpArgs → mcpResult), not interaction updates.
211
+ else if (updateCase === "toolCallStarted" ||
212
+ updateCase === "toolCallDelta" ||
213
+ updateCase === "thinkingCompleted" ||
214
+ updateCase === "userMessageAppended" ||
215
+ updateCase === "summary" ||
216
+ updateCase === "summaryStarted" ||
217
+ updateCase === "summaryCompleted" ||
218
+ updateCase === "heartbeat" ||
219
+ updateCase === "stepStarted" ||
220
+ updateCase === "stepCompleted") {
221
+ return;
222
+ }
223
+ else {
224
+ onUnsupportedMessage?.({
225
+ category: "interactionUpdate",
226
+ caseName: updateCase ?? "undefined",
227
+ });
228
+ }
229
+ // Interaction tool-call updates are informational only. Resumable MCP tool
230
+ // execution comes from execServerMessage.mcpArgs.
231
+ }
232
+ function handleInteractionQuery(query, sendFrame, onUnsupportedMessage) {
233
+ const queryCase = query.query.case;
234
+ if (queryCase === "webSearchRequestQuery") {
235
+ const response = create(WebSearchRequestResponseSchema, {
236
+ result: {
237
+ case: "rejected",
238
+ value: create(WebSearchRequestResponse_RejectedSchema, {
239
+ reason: "Native Cursor web search is not available in this environment. Use the provided MCP tool `websearch` instead.",
240
+ }),
241
+ },
242
+ });
243
+ sendInteractionResponse(query.id, "webSearchRequestResponse", response, sendFrame);
244
+ return;
245
+ }
246
+ if (queryCase === "askQuestionInteractionQuery") {
247
+ const response = create(AskQuestionInteractionResponseSchema, {
248
+ result: create(AskQuestionResultSchema, {
249
+ result: {
250
+ case: "rejected",
251
+ value: create(AskQuestionRejectedSchema, {
252
+ reason: "Native Cursor question prompts are not available in this environment. Use the provided MCP tool `question` instead.",
253
+ }),
254
+ },
255
+ }),
256
+ });
257
+ sendInteractionResponse(query.id, "askQuestionInteractionResponse", response, sendFrame);
258
+ return;
259
+ }
260
+ if (queryCase === "switchModeRequestQuery") {
261
+ const response = create(SwitchModeRequestResponseSchema, {
262
+ result: {
263
+ case: "rejected",
264
+ value: create(SwitchModeRequestResponse_RejectedSchema, {
265
+ reason: "Cursor mode switching is not available in this environment. Continue using the current agent and the provided MCP tools.",
266
+ }),
267
+ },
268
+ });
269
+ sendInteractionResponse(query.id, "switchModeRequestResponse", response, sendFrame);
270
+ return;
271
+ }
272
+ if (queryCase === "exaSearchRequestQuery") {
273
+ const response = create(ExaSearchRequestResponseSchema, {
274
+ result: {
275
+ case: "rejected",
276
+ value: create(ExaSearchRequestResponse_RejectedSchema, {
277
+ reason: "Native Cursor Exa search is not available in this environment. Use the provided MCP tool `websearch` instead.",
278
+ }),
279
+ },
280
+ });
281
+ sendInteractionResponse(query.id, "exaSearchRequestResponse", response, sendFrame);
282
+ return;
283
+ }
284
+ if (queryCase === "exaFetchRequestQuery") {
285
+ const response = create(ExaFetchRequestResponseSchema, {
286
+ result: {
287
+ case: "rejected",
288
+ value: create(ExaFetchRequestResponse_RejectedSchema, {
289
+ reason: "Native Cursor Exa fetch is not available in this environment. Use the provided MCP tools `websearch` and `webfetch` instead.",
290
+ }),
291
+ },
292
+ });
293
+ sendInteractionResponse(query.id, "exaFetchRequestResponse", response, sendFrame);
294
+ return;
295
+ }
296
+ if (queryCase === "createPlanRequestQuery") {
297
+ const response = create(CreatePlanRequestResponseSchema, {
298
+ result: create(CreatePlanResultSchema, {
299
+ planUri: "",
300
+ result: {
301
+ case: "error",
302
+ value: create(CreatePlanErrorSchema, {
303
+ error: "Native Cursor plan creation is not available in this environment. Use the provided MCP planning tools instead.",
304
+ }),
305
+ },
306
+ }),
307
+ });
308
+ sendInteractionResponse(query.id, "createPlanRequestResponse", response, sendFrame);
309
+ return;
310
+ }
311
+ onUnsupportedMessage?.({
312
+ category: "interactionQuery",
313
+ caseName: queryCase ?? "undefined",
314
+ });
173
315
  }
174
316
  /** Send a KV client response back to Cursor. */
175
317
  function sendKvResponse(kvMsg, messageCase, value, sendFrame) {
@@ -182,6 +324,16 @@ function sendKvResponse(kvMsg, messageCase, value, sendFrame) {
182
324
  });
183
325
  sendFrame(toBinary(AgentClientMessageSchema, clientMsg));
184
326
  }
327
+ function sendInteractionResponse(queryId, messageCase, value, sendFrame) {
328
+ const response = create(InteractionResponseSchema, {
329
+ id: queryId,
330
+ result: { case: messageCase, value: value },
331
+ });
332
+ const clientMessage = create(AgentClientMessageSchema, {
333
+ message: { case: "interactionResponse", value: response },
334
+ });
335
+ sendFrame(toBinary(AgentClientMessageSchema, clientMessage));
336
+ }
185
337
  function handleKvMessage(kvMsg, blobStore, sendFrame) {
186
338
  const kvCase = kvMsg.message.case;
187
339
  if (kvCase === "getBlobArgs") {
@@ -202,9 +354,19 @@ function handleKvMessage(kvMsg, blobStore, sendFrame) {
202
354
  sendKvResponse(kvMsg, "setBlobResult", create(SetBlobResultSchema, {}), sendFrame);
203
355
  }
204
356
  }
205
- function handleExecMessage(execMsg, mcpTools, sendFrame, onMcpExec, onUnhandledExec) {
357
+ function handleExecMessage(execMsg, cloudRule, mcpTools, sendFrame, state, onMcpExec, onUnhandledExec) {
206
358
  const execCase = execMsg.message.case;
359
+ logPluginInfo("Received Cursor exec message", {
360
+ execCase: execCase ?? "undefined",
361
+ execId: execMsg.execId,
362
+ execMsgId: execMsg.id,
363
+ });
207
364
  if (execCase === "requestContextArgs") {
365
+ logPluginInfo("Responding to Cursor requestContextArgs", {
366
+ execId: execMsg.execId,
367
+ execMsgId: execMsg.id,
368
+ mcpToolCount: mcpTools.length,
369
+ });
208
370
  const requestContext = create(RequestContextSchema, {
209
371
  rules: [],
210
372
  repositoryInfo: [],
@@ -212,6 +374,7 @@ function handleExecMessage(execMsg, mcpTools, sendFrame, onMcpExec, onUnhandledE
212
374
  gitRepos: [],
213
375
  projectLayouts: [],
214
376
  mcpInstructions: [],
377
+ cloudRule,
215
378
  fileContents: {},
216
379
  customSubagents: [],
217
380
  });
@@ -227,13 +390,23 @@ function handleExecMessage(execMsg, mcpTools, sendFrame, onMcpExec, onUnhandledE
227
390
  if (execCase === "mcpArgs") {
228
391
  const mcpArgs = execMsg.message.value;
229
392
  const decoded = decodeMcpArgsMap(mcpArgs.args ?? {});
230
- onMcpExec({
393
+ const exec = {
231
394
  execId: execMsg.execId,
232
395
  execMsgId: execMsg.id,
233
396
  toolCallId: mcpArgs.toolCallId || crypto.randomUUID(),
234
397
  toolName: mcpArgs.toolName || mcpArgs.name,
235
398
  decodedArgs: JSON.stringify(decoded),
399
+ source: "exec",
400
+ };
401
+ logPluginInfo("Received Cursor exec MCP tool metadata", {
402
+ toolCallId: exec.toolCallId,
403
+ toolName: exec.toolName,
404
+ source: exec.source,
405
+ execId: exec.execId,
406
+ execMsgId: exec.execMsgId,
407
+ decodedArgs: exec.decodedArgs,
236
408
  });
409
+ onMcpExec(exec);
237
410
  return;
238
411
  }
239
412
  // --- Reject native Cursor tools ---
@@ -241,6 +414,11 @@ function handleExecMessage(execMsg, mcpTools, sendFrame, onMcpExec, onUnhandledE
241
414
  // so it falls back to our MCP tools (registered via RequestContext).
242
415
  const REJECT_REASON = "Tool not available in this environment. Use the MCP tools provided instead.";
243
416
  if (execCase === "readArgs") {
417
+ logPluginInfo("Rejecting native Cursor read tool in favor of MCP", {
418
+ execId: execMsg.execId,
419
+ execMsgId: execMsg.id,
420
+ path: execMsg.message.value.path,
421
+ });
244
422
  const args = execMsg.message.value;
245
423
  const result = create(ReadResultSchema, {
246
424
  result: {
@@ -255,6 +433,11 @@ function handleExecMessage(execMsg, mcpTools, sendFrame, onMcpExec, onUnhandledE
255
433
  return;
256
434
  }
257
435
  if (execCase === "lsArgs") {
436
+ logPluginInfo("Rejecting native Cursor ls tool in favor of MCP", {
437
+ execId: execMsg.execId,
438
+ execMsgId: execMsg.id,
439
+ path: execMsg.message.value.path,
440
+ });
258
441
  const args = execMsg.message.value;
259
442
  const result = create(LsResultSchema, {
260
443
  result: {
@@ -269,6 +452,10 @@ function handleExecMessage(execMsg, mcpTools, sendFrame, onMcpExec, onUnhandledE
269
452
  return;
270
453
  }
271
454
  if (execCase === "grepArgs") {
455
+ logPluginInfo("Rejecting native Cursor grep tool in favor of MCP", {
456
+ execId: execMsg.execId,
457
+ execMsgId: execMsg.id,
458
+ });
272
459
  const result = create(GrepResultSchema, {
273
460
  result: {
274
461
  case: "error",
@@ -279,6 +466,11 @@ function handleExecMessage(execMsg, mcpTools, sendFrame, onMcpExec, onUnhandledE
279
466
  return;
280
467
  }
281
468
  if (execCase === "writeArgs") {
469
+ logPluginInfo("Rejecting native Cursor write tool in favor of MCP", {
470
+ execId: execMsg.execId,
471
+ execMsgId: execMsg.id,
472
+ path: execMsg.message.value.path,
473
+ });
282
474
  const args = execMsg.message.value;
283
475
  const result = create(WriteResultSchema, {
284
476
  result: {
@@ -293,6 +485,11 @@ function handleExecMessage(execMsg, mcpTools, sendFrame, onMcpExec, onUnhandledE
293
485
  return;
294
486
  }
295
487
  if (execCase === "deleteArgs") {
488
+ logPluginInfo("Rejecting native Cursor delete tool in favor of MCP", {
489
+ execId: execMsg.execId,
490
+ execMsgId: execMsg.id,
491
+ path: execMsg.message.value.path,
492
+ });
296
493
  const args = execMsg.message.value;
297
494
  const result = create(DeleteResultSchema, {
298
495
  result: {
@@ -307,6 +504,13 @@ function handleExecMessage(execMsg, mcpTools, sendFrame, onMcpExec, onUnhandledE
307
504
  return;
308
505
  }
309
506
  if (execCase === "shellArgs" || execCase === "shellStreamArgs") {
507
+ logPluginInfo("Rejecting native Cursor shell tool in favor of MCP", {
508
+ execId: execMsg.execId,
509
+ execMsgId: execMsg.id,
510
+ command: execMsg.message.value.command ?? "",
511
+ workingDirectory: execMsg.message.value.workingDirectory ?? "",
512
+ execCase,
513
+ });
310
514
  const args = execMsg.message.value;
311
515
  const result = create(ShellResultSchema, {
312
516
  result: {
@@ -323,6 +527,12 @@ function handleExecMessage(execMsg, mcpTools, sendFrame, onMcpExec, onUnhandledE
323
527
  return;
324
528
  }
325
529
  if (execCase === "backgroundShellSpawnArgs") {
530
+ logPluginInfo("Rejecting native Cursor background shell tool in favor of MCP", {
531
+ execId: execMsg.execId,
532
+ execMsgId: execMsg.id,
533
+ command: execMsg.message.value.command ?? "",
534
+ workingDirectory: execMsg.message.value.workingDirectory ?? "",
535
+ });
326
536
  const args = execMsg.message.value;
327
537
  const result = create(BackgroundShellSpawnResultSchema, {
328
538
  result: {
@@ -339,6 +549,10 @@ function handleExecMessage(execMsg, mcpTools, sendFrame, onMcpExec, onUnhandledE
339
549
  return;
340
550
  }
341
551
  if (execCase === "writeShellStdinArgs") {
552
+ logPluginInfo("Rejecting native Cursor shell stdin tool in favor of MCP", {
553
+ execId: execMsg.execId,
554
+ execMsgId: execMsg.id,
555
+ });
342
556
  const result = create(WriteShellStdinResultSchema, {
343
557
  result: {
344
558
  case: "error",
@@ -349,6 +563,11 @@ function handleExecMessage(execMsg, mcpTools, sendFrame, onMcpExec, onUnhandledE
349
563
  return;
350
564
  }
351
565
  if (execCase === "fetchArgs") {
566
+ logPluginInfo("Rejecting native Cursor fetch tool in favor of MCP", {
567
+ execId: execMsg.execId,
568
+ execMsgId: execMsg.id,
569
+ url: execMsg.message.value.url,
570
+ });
352
571
  const args = execMsg.message.value;
353
572
  const result = create(FetchResultSchema, {
354
573
  result: {
@@ -363,6 +582,11 @@ function handleExecMessage(execMsg, mcpTools, sendFrame, onMcpExec, onUnhandledE
363
582
  return;
364
583
  }
365
584
  if (execCase === "diagnosticsArgs") {
585
+ logPluginInfo("Rejecting native Cursor diagnostics tool in favor of MCP", {
586
+ execId: execMsg.execId,
587
+ execMsgId: execMsg.id,
588
+ path: execMsg.message.value.path,
589
+ });
366
590
  const result = create(DiagnosticsResultSchema, {});
367
591
  sendExecResult(execMsg, "diagnosticsResult", result, sendFrame);
368
592
  return;
@@ -376,6 +600,12 @@ function handleExecMessage(execMsg, mcpTools, sendFrame, onMcpExec, onUnhandledE
376
600
  };
377
601
  const resultCase = miscCaseMap[execCase];
378
602
  if (resultCase) {
603
+ logPluginInfo("Responding to miscellaneous Cursor exec message", {
604
+ execCase,
605
+ execId: execMsg.execId,
606
+ execMsgId: execMsg.id,
607
+ resultCase,
608
+ });
379
609
  sendExecResult(execMsg, resultCase, create(McpResultSchema, {}), sendFrame);
380
610
  return;
381
611
  }
@@ -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,12 +15,16 @@ 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;
17
21
  }
18
22
  /** A live Cursor session kept alive across requests for tool result continuation. */
19
23
  export interface ActiveBridge {
20
24
  bridge: CursorSession;
21
25
  heartbeatTimer: NodeJS.Timeout;
22
26
  blobStore: Map<string, Uint8Array>;
27
+ cloudRule?: string;
23
28
  mcpTools: McpToolDefinition[];
24
29
  pendingExecs: PendingExec[];
25
30
  modelId: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@playwo/opencode-cursor-oauth",
3
- "version": "0.0.0-dev.12f4642b8f6b",
3
+ "version": "0.0.0-dev.14c6316643ec",
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",
@@ -19,7 +19,6 @@
19
19
  ],
20
20
  "scripts": {
21
21
  "build": "tsc -p tsconfig.json",
22
- "test": "bun test/smoke.ts",
23
22
  "prepublishOnly": "npm run build"
24
23
  },
25
24
  "repository": {