@playwo/opencode-cursor-oauth 0.0.0-dev.0e8f5d6c8379 → 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.
- package/dist/logger.d.ts +1 -0
- package/dist/logger.js +3 -0
- 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 +8 -4
- package/dist/proxy/bridge-streaming.d.ts +1 -1
- package/dist/proxy/bridge-streaming.js +96 -10
- package/dist/proxy/chat-completion.js +40 -1
- package/dist/proxy/cursor-request.js +12 -15
- package/dist/proxy/stream-dispatch.d.ts +1 -1
- package/dist/proxy/stream-dispatch.js +110 -25
- package/dist/proxy/stream-state.d.ts +0 -2
- package/dist/proxy/types.d.ts +6 -1
- package/package.json +1 -2
package/dist/logger.d.ts
CHANGED
|
@@ -2,5 +2,6 @@ import type { PluginInput } from "@opencode-ai/plugin";
|
|
|
2
2
|
export declare function configurePluginLogger(input: PluginInput): void;
|
|
3
3
|
export declare function errorDetails(error: unknown): Record<string, unknown>;
|
|
4
4
|
export declare function logPluginWarn(message: string, extra?: Record<string, unknown>): void;
|
|
5
|
+
export declare function logPluginInfo(message: string, extra?: Record<string, unknown>): void;
|
|
5
6
|
export declare function logPluginError(message: string, extra?: Record<string, unknown>): void;
|
|
6
7
|
export declare function flushPluginLogs(): Promise<void>;
|
package/dist/logger.js
CHANGED
|
@@ -27,6 +27,9 @@ export function errorDetails(error) {
|
|
|
27
27
|
export function logPluginWarn(message, extra = {}) {
|
|
28
28
|
logPlugin("warn", message, extra);
|
|
29
29
|
}
|
|
30
|
+
export function logPluginInfo(message, extra = {}) {
|
|
31
|
+
logPlugin("info", message, extra);
|
|
32
|
+
}
|
|
30
33
|
export function logPluginError(message, extra = {}) {
|
|
31
34
|
logPlugin("error", message, extra);
|
|
32
35
|
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { scheduleBridgeEnd } from "./stream-dispatch";
|
|
2
|
+
const TURN_END_GRACE_MS = 750;
|
|
3
|
+
export function createBridgeCloseController(bridge) {
|
|
4
|
+
let turnEnded = false;
|
|
5
|
+
let checkpointSeen = false;
|
|
6
|
+
let closeTimer;
|
|
7
|
+
const clearCloseTimer = () => {
|
|
8
|
+
if (!closeTimer)
|
|
9
|
+
return;
|
|
10
|
+
clearTimeout(closeTimer);
|
|
11
|
+
closeTimer = undefined;
|
|
12
|
+
};
|
|
13
|
+
const closeBridge = () => {
|
|
14
|
+
clearCloseTimer();
|
|
15
|
+
scheduleBridgeEnd(bridge);
|
|
16
|
+
};
|
|
17
|
+
return {
|
|
18
|
+
noteTurnEnded() {
|
|
19
|
+
turnEnded = true;
|
|
20
|
+
if (checkpointSeen) {
|
|
21
|
+
closeBridge();
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
clearCloseTimer();
|
|
25
|
+
closeTimer = setTimeout(closeBridge, TURN_END_GRACE_MS);
|
|
26
|
+
},
|
|
27
|
+
noteCheckpoint() {
|
|
28
|
+
checkpointSeen = true;
|
|
29
|
+
if (turnEnded) {
|
|
30
|
+
closeBridge();
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
dispose() {
|
|
34
|
+
clearCloseTimer();
|
|
35
|
+
},
|
|
36
|
+
};
|
|
37
|
+
}
|
|
@@ -5,6 +5,7 @@ import { updateStoredConversationAfterCompletion } from "./conversation-state";
|
|
|
5
5
|
import { startBridge } from "./bridge-session";
|
|
6
6
|
import { updateConversationCheckpoint, syncStoredBlobStore, } from "./state-sync";
|
|
7
7
|
import { computeUsage, createConnectFrameParser, createThinkingTagFilter, parseConnectEndStream, processServerMessage, scheduleBridgeEnd, } from "./stream-dispatch";
|
|
8
|
+
import { createBridgeCloseController } from "./bridge-close-controller";
|
|
8
9
|
export async function handleNonStreamingResponse(payload, accessToken, modelId, convKey, metadata) {
|
|
9
10
|
const completionId = `chatcmpl-${crypto.randomUUID().replace(/-/g, "").slice(0, 28)}`;
|
|
10
11
|
const created = Math.floor(Date.now() / 1000);
|
|
@@ -33,19 +34,18 @@ async function collectFullResponse(payload, accessToken, modelId, convKey, metad
|
|
|
33
34
|
let endStreamError = null;
|
|
34
35
|
const pendingToolCalls = [];
|
|
35
36
|
const { bridge, heartbeatTimer } = await startBridge(accessToken, payload.requestBytes);
|
|
37
|
+
const bridgeCloseController = createBridgeCloseController(bridge);
|
|
36
38
|
const state = {
|
|
37
39
|
toolCallIndex: 0,
|
|
38
40
|
pendingExecs: [],
|
|
39
41
|
outputTokens: 0,
|
|
40
42
|
totalTokens: 0,
|
|
41
|
-
interactionToolArgsText: new Map(),
|
|
42
|
-
emittedToolCallIds: new Set(),
|
|
43
43
|
};
|
|
44
44
|
const tagFilter = createThinkingTagFilter();
|
|
45
45
|
bridge.onData(createConnectFrameParser((messageBytes) => {
|
|
46
46
|
try {
|
|
47
47
|
const serverMessage = fromBinary(AgentServerMessageSchema, messageBytes);
|
|
48
|
-
processServerMessage(serverMessage, payload.blobStore, payload.mcpTools, (data) => bridge.write(data), state, (text, isThinking) => {
|
|
48
|
+
processServerMessage(serverMessage, payload.blobStore, payload.cloudRule, payload.mcpTools, (data) => bridge.write(data), state, (text, isThinking) => {
|
|
49
49
|
if (isThinking)
|
|
50
50
|
return;
|
|
51
51
|
const { content } = tagFilter.process(text);
|
|
@@ -60,7 +60,10 @@ async function collectFullResponse(payload, accessToken, modelId, convKey, metad
|
|
|
60
60
|
},
|
|
61
61
|
});
|
|
62
62
|
scheduleBridgeEnd(bridge);
|
|
63
|
-
}, (checkpointBytes) =>
|
|
63
|
+
}, (checkpointBytes) => {
|
|
64
|
+
updateConversationCheckpoint(convKey, checkpointBytes);
|
|
65
|
+
bridgeCloseController.noteCheckpoint();
|
|
66
|
+
}, () => bridgeCloseController.noteTurnEnded(), (info) => {
|
|
64
67
|
endStreamError = new Error(`Cursor returned unsupported ${info.category}: ${info.caseName}${info.detail ? ` (${info.detail})` : ""}`);
|
|
65
68
|
logPluginError("Closing non-streaming Cursor bridge after unsupported message", {
|
|
66
69
|
modelId,
|
|
@@ -97,6 +100,7 @@ async function collectFullResponse(payload, accessToken, modelId, convKey, metad
|
|
|
97
100
|
scheduleBridgeEnd(bridge);
|
|
98
101
|
}));
|
|
99
102
|
bridge.onClose(() => {
|
|
103
|
+
bridgeCloseController.dispose();
|
|
100
104
|
clearInterval(heartbeatTimer);
|
|
101
105
|
syncStoredBlobStore(convKey, payload.blobStore);
|
|
102
106
|
const flushed = tagFilter.flush();
|
|
@@ -2,4 +2,4 @@ import { type ToolResultInfo } from "../openai/messages";
|
|
|
2
2
|
import type { ConversationRequestMetadata } from "./conversation-meta";
|
|
3
3
|
import type { ActiveBridge, CursorRequestPayload } from "./types";
|
|
4
4
|
export declare function handleStreamingResponse(payload: CursorRequestPayload, accessToken: string, modelId: string, bridgeKey: string, convKey: string, metadata: ConversationRequestMetadata): Promise<Response>;
|
|
5
|
-
export declare function handleToolResultResume(active: ActiveBridge, toolResults: ToolResultInfo[], bridgeKey: string, convKey: string): Response
|
|
5
|
+
export declare function handleToolResultResume(active: ActiveBridge, toolResults: ToolResultInfo[], bridgeKey: string, convKey: string): Promise<Response>;
|
|
@@ -1,17 +1,19 @@
|
|
|
1
1
|
import { create, fromBinary, toBinary } from "@bufbuild/protobuf";
|
|
2
2
|
import { AgentClientMessageSchema, AgentServerMessageSchema, ExecClientMessageSchema, McpErrorSchema, McpResultSchema, McpSuccessSchema, McpTextContentSchema, McpToolResultContentItemSchema, } from "../proto/agent_pb";
|
|
3
|
-
import { errorDetails, logPluginError, logPluginWarn } from "../logger";
|
|
3
|
+
import { errorDetails, logPluginError, logPluginInfo, logPluginWarn, } from "../logger";
|
|
4
4
|
import { formatToolCallSummary, formatToolResultSummary, } from "../openai/messages";
|
|
5
5
|
import { activeBridges, updateStoredConversationAfterCompletion, } from "./conversation-state";
|
|
6
6
|
import { startBridge } from "./bridge-session";
|
|
7
7
|
import { updateConversationCheckpoint, syncStoredBlobStore, } from "./state-sync";
|
|
8
8
|
import { SSE_HEADERS } from "./sse";
|
|
9
9
|
import { computeUsage, createConnectFrameParser, createThinkingTagFilter, parseConnectEndStream, processServerMessage, scheduleBridgeEnd, } from "./stream-dispatch";
|
|
10
|
+
import { createBridgeCloseController } from "./bridge-close-controller";
|
|
10
11
|
const SSE_KEEPALIVE_INTERVAL_MS = 15_000;
|
|
11
|
-
function createBridgeStreamResponse(bridge, heartbeatTimer, blobStore, mcpTools, modelId, bridgeKey, convKey, metadata) {
|
|
12
|
+
function createBridgeStreamResponse(bridge, heartbeatTimer, blobStore, cloudRule, mcpTools, modelId, bridgeKey, convKey, metadata) {
|
|
12
13
|
const completionId = `chatcmpl-${crypto.randomUUID().replace(/-/g, "").slice(0, 28)}`;
|
|
13
14
|
const created = Math.floor(Date.now() / 1000);
|
|
14
15
|
let keepaliveTimer;
|
|
16
|
+
const bridgeCloseController = createBridgeCloseController(bridge);
|
|
15
17
|
const stopKeepalive = () => {
|
|
16
18
|
if (!keepaliveTimer)
|
|
17
19
|
return;
|
|
@@ -27,8 +29,6 @@ function createBridgeStreamResponse(bridge, heartbeatTimer, blobStore, mcpTools,
|
|
|
27
29
|
pendingExecs: [],
|
|
28
30
|
outputTokens: 0,
|
|
29
31
|
totalTokens: 0,
|
|
30
|
-
interactionToolArgsText: new Map(),
|
|
31
|
-
emittedToolCallIds: new Set(),
|
|
32
32
|
};
|
|
33
33
|
const tagFilter = createThinkingTagFilter();
|
|
34
34
|
let assistantText = metadata.assistantSeedText ?? "";
|
|
@@ -90,7 +90,7 @@ function createBridgeStreamResponse(bridge, heartbeatTimer, blobStore, mcpTools,
|
|
|
90
90
|
const processChunk = createConnectFrameParser((messageBytes) => {
|
|
91
91
|
try {
|
|
92
92
|
const serverMessage = fromBinary(AgentServerMessageSchema, messageBytes);
|
|
93
|
-
processServerMessage(serverMessage, blobStore, mcpTools, (data) => bridge.write(data), state, (text, isThinking) => {
|
|
93
|
+
processServerMessage(serverMessage, blobStore, cloudRule, mcpTools, (data) => bridge.write(data), state, (text, isThinking) => {
|
|
94
94
|
if (isThinking) {
|
|
95
95
|
sendSSE(makeChunk({ reasoning_content: text }));
|
|
96
96
|
return;
|
|
@@ -148,6 +148,7 @@ function createBridgeStreamResponse(bridge, heartbeatTimer, blobStore, mcpTools,
|
|
|
148
148
|
bridge,
|
|
149
149
|
heartbeatTimer,
|
|
150
150
|
blobStore,
|
|
151
|
+
cloudRule,
|
|
151
152
|
mcpTools,
|
|
152
153
|
pendingExecs: state.pendingExecs,
|
|
153
154
|
modelId,
|
|
@@ -159,7 +160,10 @@ function createBridgeStreamResponse(bridge, heartbeatTimer, blobStore, mcpTools,
|
|
|
159
160
|
sendSSE(makeChunk({}, "tool_calls"));
|
|
160
161
|
sendDone();
|
|
161
162
|
closeController();
|
|
162
|
-
}, (checkpointBytes) =>
|
|
163
|
+
}, (checkpointBytes) => {
|
|
164
|
+
updateConversationCheckpoint(convKey, checkpointBytes);
|
|
165
|
+
bridgeCloseController.noteCheckpoint();
|
|
166
|
+
}, () => bridgeCloseController.noteTurnEnded(), (info) => {
|
|
163
167
|
endStreamError = new Error(`Cursor returned unsupported ${info.category}: ${info.caseName}${info.detail ? ` (${info.detail})` : ""}`);
|
|
164
168
|
logPluginError("Closing Cursor bridge after unsupported message", {
|
|
165
169
|
modelId,
|
|
@@ -212,8 +216,24 @@ function createBridgeStreamResponse(bridge, heartbeatTimer, blobStore, mcpTools,
|
|
|
212
216
|
stopKeepalive();
|
|
213
217
|
}
|
|
214
218
|
}, SSE_KEEPALIVE_INTERVAL_MS);
|
|
219
|
+
logPluginInfo("Opened Cursor streaming bridge", {
|
|
220
|
+
modelId,
|
|
221
|
+
bridgeKey,
|
|
222
|
+
convKey,
|
|
223
|
+
mcpToolCount: mcpTools.length,
|
|
224
|
+
hasCloudRule: Boolean(cloudRule),
|
|
225
|
+
});
|
|
215
226
|
bridge.onData(processChunk);
|
|
216
227
|
bridge.onClose((code) => {
|
|
228
|
+
logPluginInfo("Cursor streaming bridge closed", {
|
|
229
|
+
modelId,
|
|
230
|
+
bridgeKey,
|
|
231
|
+
convKey,
|
|
232
|
+
code,
|
|
233
|
+
mcpExecReceived,
|
|
234
|
+
hadEndStreamError: Boolean(endStreamError),
|
|
235
|
+
});
|
|
236
|
+
bridgeCloseController.dispose();
|
|
217
237
|
clearInterval(heartbeatTimer);
|
|
218
238
|
stopKeepalive();
|
|
219
239
|
syncStoredBlobStore(convKey, blobStore);
|
|
@@ -244,6 +264,7 @@ function createBridgeStreamResponse(bridge, heartbeatTimer, blobStore, mcpTools,
|
|
|
244
264
|
});
|
|
245
265
|
},
|
|
246
266
|
cancel(reason) {
|
|
267
|
+
bridgeCloseController.dispose();
|
|
247
268
|
stopKeepalive();
|
|
248
269
|
clearInterval(heartbeatTimer);
|
|
249
270
|
syncStoredBlobStore(convKey, blobStore);
|
|
@@ -263,11 +284,36 @@ function createBridgeStreamResponse(bridge, heartbeatTimer, blobStore, mcpTools,
|
|
|
263
284
|
return new Response(stream, { headers: SSE_HEADERS });
|
|
264
285
|
}
|
|
265
286
|
export async function handleStreamingResponse(payload, accessToken, modelId, bridgeKey, convKey, metadata) {
|
|
287
|
+
logPluginInfo("Starting Cursor streaming response", {
|
|
288
|
+
modelId,
|
|
289
|
+
bridgeKey,
|
|
290
|
+
convKey,
|
|
291
|
+
mcpToolCount: payload.mcpTools.length,
|
|
292
|
+
});
|
|
266
293
|
const { bridge, heartbeatTimer } = await startBridge(accessToken, payload.requestBytes);
|
|
267
|
-
return createBridgeStreamResponse(bridge, heartbeatTimer, payload.blobStore, payload.mcpTools, modelId, bridgeKey, convKey, metadata);
|
|
294
|
+
return createBridgeStreamResponse(bridge, heartbeatTimer, payload.blobStore, payload.cloudRule, payload.mcpTools, modelId, bridgeKey, convKey, metadata);
|
|
295
|
+
}
|
|
296
|
+
async function waitForResolvablePendingExecs(active, toolResults, timeoutMs = 2_000) {
|
|
297
|
+
const pendingToolCallIds = new Set(toolResults.map((result) => result.toolCallId));
|
|
298
|
+
const deadline = Date.now() + timeoutMs;
|
|
299
|
+
while (Date.now() < deadline) {
|
|
300
|
+
const unresolved = active.pendingExecs.filter((exec) => pendingToolCallIds.has(exec.toolCallId) && exec.execMsgId === 0);
|
|
301
|
+
if (unresolved.length === 0) {
|
|
302
|
+
return unresolved;
|
|
303
|
+
}
|
|
304
|
+
await new Promise((resolve) => setTimeout(resolve, 25));
|
|
305
|
+
}
|
|
306
|
+
const unresolved = active.pendingExecs.filter((exec) => pendingToolCallIds.has(exec.toolCallId) && exec.execMsgId === 0);
|
|
307
|
+
if (unresolved.length > 0) {
|
|
308
|
+
logPluginWarn("Cursor exec metadata did not arrive before tool-result resume", {
|
|
309
|
+
bridgeToolCallIds: unresolved.map((exec) => exec.toolCallId),
|
|
310
|
+
modelId: active.modelId,
|
|
311
|
+
});
|
|
312
|
+
}
|
|
313
|
+
return unresolved;
|
|
268
314
|
}
|
|
269
|
-
export function handleToolResultResume(active, toolResults, bridgeKey, convKey) {
|
|
270
|
-
const { bridge, heartbeatTimer, blobStore, mcpTools, pendingExecs, modelId, metadata, } = active;
|
|
315
|
+
export async function handleToolResultResume(active, toolResults, bridgeKey, convKey) {
|
|
316
|
+
const { bridge, heartbeatTimer, blobStore, cloudRule, mcpTools, pendingExecs, modelId, metadata, } = active;
|
|
271
317
|
const resumeMetadata = {
|
|
272
318
|
...metadata,
|
|
273
319
|
assistantSeedText: [
|
|
@@ -277,6 +323,33 @@ export function handleToolResultResume(active, toolResults, bridgeKey, convKey)
|
|
|
277
323
|
.filter(Boolean)
|
|
278
324
|
.join("\n\n"),
|
|
279
325
|
};
|
|
326
|
+
logPluginInfo("Preparing Cursor tool-result resume", {
|
|
327
|
+
bridgeKey,
|
|
328
|
+
convKey,
|
|
329
|
+
modelId,
|
|
330
|
+
toolResults,
|
|
331
|
+
pendingExecs,
|
|
332
|
+
});
|
|
333
|
+
const unresolved = await waitForResolvablePendingExecs(active, toolResults);
|
|
334
|
+
logPluginInfo("Resolved pending exec state before Cursor tool-result resume", {
|
|
335
|
+
bridgeKey,
|
|
336
|
+
convKey,
|
|
337
|
+
modelId,
|
|
338
|
+
toolResults,
|
|
339
|
+
pendingExecs,
|
|
340
|
+
unresolvedPendingExecs: unresolved,
|
|
341
|
+
});
|
|
342
|
+
if (unresolved.length > 0) {
|
|
343
|
+
clearInterval(heartbeatTimer);
|
|
344
|
+
bridge.end();
|
|
345
|
+
return new Response(JSON.stringify({
|
|
346
|
+
error: {
|
|
347
|
+
message: "Cursor requested a tool call but never provided resumable exec metadata. Aborting instead of retrying with synthetic ids.",
|
|
348
|
+
type: "invalid_request_error",
|
|
349
|
+
code: "cursor_missing_exec_metadata",
|
|
350
|
+
},
|
|
351
|
+
}), { status: 400, headers: { "Content-Type": "application/json" } });
|
|
352
|
+
}
|
|
280
353
|
for (const exec of pendingExecs) {
|
|
281
354
|
const result = toolResults.find((toolResult) => toolResult.toolCallId === exec.toolCallId);
|
|
282
355
|
const mcpResult = result
|
|
@@ -317,7 +390,20 @@ export function handleToolResultResume(active, toolResults, bridgeKey, convKey)
|
|
|
317
390
|
const clientMessage = create(AgentClientMessageSchema, {
|
|
318
391
|
message: { case: "execClientMessage", value: execClientMessage },
|
|
319
392
|
});
|
|
393
|
+
logPluginInfo("Sending Cursor tool-result resume message", {
|
|
394
|
+
bridgeKey,
|
|
395
|
+
convKey,
|
|
396
|
+
modelId,
|
|
397
|
+
toolCallId: exec.toolCallId,
|
|
398
|
+
toolName: exec.toolName,
|
|
399
|
+
source: exec.source,
|
|
400
|
+
execId: exec.execId,
|
|
401
|
+
execMsgId: exec.execMsgId,
|
|
402
|
+
cursorCallId: exec.cursorCallId,
|
|
403
|
+
modelCallId: exec.modelCallId,
|
|
404
|
+
matchedToolResult: result,
|
|
405
|
+
});
|
|
320
406
|
bridge.write(toBinary(AgentClientMessageSchema, clientMessage));
|
|
321
407
|
}
|
|
322
|
-
return createBridgeStreamResponse(bridge, heartbeatTimer, blobStore, mcpTools, modelId, bridgeKey, convKey, resumeMetadata);
|
|
408
|
+
return createBridgeStreamResponse(bridge, heartbeatTimer, blobStore, cloudRule, mcpTools, modelId, bridgeKey, convKey, resumeMetadata);
|
|
323
409
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { logPluginWarn } from "../logger";
|
|
1
|
+
import { logPluginInfo, logPluginWarn } from "../logger";
|
|
2
2
|
import { buildInitialHandoffPrompt, buildTitleSourceText, buildToolResumePrompt, detectTitleRequest, parseMessages, } from "../openai/messages";
|
|
3
3
|
import { buildMcpToolDefinitions, selectToolsForChoice } from "../openai/tools";
|
|
4
4
|
import { activeBridges, conversationStates, createStoredConversation, deriveBridgeKey, deriveConversationKey, evictStaleConversations, hashString, normalizeAgentKey, resetStoredConversation, } from "./conversation-state";
|
|
@@ -10,6 +10,19 @@ export function handleChatCompletion(body, accessToken, context = {}) {
|
|
|
10
10
|
const { systemPrompt, userText, turns, toolResults, pendingAssistantSummary, completedTurnsFingerprint, } = parsed;
|
|
11
11
|
const modelId = body.model;
|
|
12
12
|
const normalizedAgentKey = normalizeAgentKey(context.agentKey);
|
|
13
|
+
logPluginInfo("Handling Cursor chat completion request", {
|
|
14
|
+
modelId,
|
|
15
|
+
stream: body.stream !== false,
|
|
16
|
+
messageCount: body.messages.length,
|
|
17
|
+
toolCount: body.tools?.length ?? 0,
|
|
18
|
+
toolChoice: body.tool_choice,
|
|
19
|
+
sessionId: context.sessionId,
|
|
20
|
+
agentKey: normalizedAgentKey,
|
|
21
|
+
parsedUserText: userText,
|
|
22
|
+
parsedToolResults: toolResults,
|
|
23
|
+
hasPendingAssistantSummary: pendingAssistantSummary.trim().length > 0,
|
|
24
|
+
turnCount: turns.length,
|
|
25
|
+
});
|
|
13
26
|
const titleDetection = detectTitleRequest(body);
|
|
14
27
|
const isTitleAgent = titleDetection.matched;
|
|
15
28
|
if (isTitleAgent) {
|
|
@@ -38,7 +51,23 @@ export function handleChatCompletion(body, accessToken, context = {}) {
|
|
|
38
51
|
const bridgeKey = deriveBridgeKey(modelId, body.messages, context.sessionId, context.agentKey);
|
|
39
52
|
const convKey = deriveConversationKey(body.messages, context.sessionId, context.agentKey);
|
|
40
53
|
const activeBridge = activeBridges.get(bridgeKey);
|
|
54
|
+
logPluginInfo("Resolved Cursor conversation keys", {
|
|
55
|
+
modelId,
|
|
56
|
+
bridgeKey,
|
|
57
|
+
convKey,
|
|
58
|
+
hasActiveBridge: Boolean(activeBridge),
|
|
59
|
+
sessionId: context.sessionId,
|
|
60
|
+
agentKey: normalizedAgentKey,
|
|
61
|
+
});
|
|
41
62
|
if (activeBridge && toolResults.length > 0) {
|
|
63
|
+
logPluginInfo("Matched OpenAI tool results to active Cursor bridge", {
|
|
64
|
+
bridgeKey,
|
|
65
|
+
convKey,
|
|
66
|
+
requestedModelId: modelId,
|
|
67
|
+
activeBridgeModelId: activeBridge.modelId,
|
|
68
|
+
toolResults,
|
|
69
|
+
pendingExecs: activeBridge.pendingExecs,
|
|
70
|
+
});
|
|
42
71
|
activeBridges.delete(bridgeKey);
|
|
43
72
|
if (activeBridge.bridge.alive) {
|
|
44
73
|
if (activeBridge.modelId !== modelId) {
|
|
@@ -93,6 +122,16 @@ export function handleChatCompletion(body, accessToken, context = {}) {
|
|
|
93
122
|
: userText;
|
|
94
123
|
const payload = buildCursorRequest(modelId, systemPrompt, effectiveUserText, replayTurns, stored.conversationId, stored.checkpoint, stored.blobStore);
|
|
95
124
|
payload.mcpTools = mcpTools;
|
|
125
|
+
logPluginInfo("Built Cursor run request payload", {
|
|
126
|
+
modelId,
|
|
127
|
+
bridgeKey,
|
|
128
|
+
convKey,
|
|
129
|
+
mcpToolCount: mcpTools.length,
|
|
130
|
+
conversationId: stored.conversationId,
|
|
131
|
+
hasCheckpoint: Boolean(stored.checkpoint),
|
|
132
|
+
replayTurnCount: replayTurns.length,
|
|
133
|
+
effectiveUserText,
|
|
134
|
+
});
|
|
96
135
|
if (body.stream === false) {
|
|
97
136
|
return handleNonStreamingResponse(payload, accessToken, modelId, convKey, {
|
|
98
137
|
systemPrompt,
|
|
@@ -1,13 +1,8 @@
|
|
|
1
1
|
import { create, fromBinary, toBinary } from "@bufbuild/protobuf";
|
|
2
|
-
import {
|
|
3
|
-
import { AgentClientMessageSchema, AgentRunRequestSchema, ConversationActionSchema, ConversationStateStructureSchema, ConversationStepSchema, AgentConversationTurnStructureSchema, ConversationTurnStructureSchema, AssistantMessageSchema, ModelDetailsSchema, ResumeActionSchema, UserMessageActionSchema, UserMessageSchema, } from "../proto/agent_pb";
|
|
2
|
+
import { AgentClientMessageSchema, AgentRunRequestSchema, AgentConversationTurnStructureSchema, AssistantMessageSchema, ConversationActionSchema, ConversationStateStructureSchema, ConversationTurnStructureSchema, ConversationStepSchema, ModelDetailsSchema, ResumeActionSchema, UserMessageActionSchema, UserMessageSchema, } from "../proto/agent_pb";
|
|
4
3
|
export function buildCursorRequest(modelId, systemPrompt, userText, turns, conversationId, checkpoint, existingBlobStore) {
|
|
5
4
|
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);
|
|
5
|
+
const cloudRule = buildCloudRule(systemPrompt);
|
|
11
6
|
let conversationState;
|
|
12
7
|
if (checkpoint) {
|
|
13
8
|
conversationState = fromBinary(ConversationStateStructureSchema, checkpoint);
|
|
@@ -40,7 +35,7 @@ export function buildCursorRequest(modelId, systemPrompt, userText, turns, conve
|
|
|
40
35
|
turnBytes.push(toBinary(ConversationTurnStructureSchema, turnStructure));
|
|
41
36
|
}
|
|
42
37
|
conversationState = create(ConversationStateStructureSchema, {
|
|
43
|
-
rootPromptMessagesJson: [
|
|
38
|
+
rootPromptMessagesJson: [],
|
|
44
39
|
turns: turnBytes,
|
|
45
40
|
todos: [],
|
|
46
41
|
pendingToolCalls: [],
|
|
@@ -64,14 +59,11 @@ export function buildCursorRequest(modelId, systemPrompt, userText, turns, conve
|
|
|
64
59
|
value: create(UserMessageActionSchema, { userMessage }),
|
|
65
60
|
},
|
|
66
61
|
});
|
|
67
|
-
return buildRunRequest(modelId, conversationId, conversationState, action, blobStore);
|
|
62
|
+
return buildRunRequest(modelId, conversationId, conversationState, action, blobStore, cloudRule);
|
|
68
63
|
}
|
|
69
64
|
export function buildCursorResumeRequest(modelId, systemPrompt, conversationId, checkpoint, existingBlobStore) {
|
|
70
65
|
const blobStore = new Map(existingBlobStore ?? []);
|
|
71
|
-
const
|
|
72
|
-
const systemBytes = new TextEncoder().encode(systemJson);
|
|
73
|
-
const systemBlobId = new Uint8Array(createHash("sha256").update(systemBytes).digest());
|
|
74
|
-
blobStore.set(Buffer.from(systemBlobId).toString("hex"), systemBytes);
|
|
66
|
+
const cloudRule = buildCloudRule(systemPrompt);
|
|
75
67
|
const conversationState = fromBinary(ConversationStateStructureSchema, checkpoint);
|
|
76
68
|
const action = create(ConversationActionSchema, {
|
|
77
69
|
action: {
|
|
@@ -79,9 +71,9 @@ export function buildCursorResumeRequest(modelId, systemPrompt, conversationId,
|
|
|
79
71
|
value: create(ResumeActionSchema, {}),
|
|
80
72
|
},
|
|
81
73
|
});
|
|
82
|
-
return buildRunRequest(modelId, conversationId, conversationState, action, blobStore);
|
|
74
|
+
return buildRunRequest(modelId, conversationId, conversationState, action, blobStore, cloudRule);
|
|
83
75
|
}
|
|
84
|
-
function buildRunRequest(modelId, conversationId, conversationState, action, blobStore) {
|
|
76
|
+
function buildRunRequest(modelId, conversationId, conversationState, action, blobStore, cloudRule) {
|
|
85
77
|
const modelDetails = create(ModelDetailsSchema, {
|
|
86
78
|
modelId,
|
|
87
79
|
displayModelId: modelId,
|
|
@@ -99,6 +91,11 @@ function buildRunRequest(modelId, conversationId, conversationState, action, blo
|
|
|
99
91
|
return {
|
|
100
92
|
requestBytes: toBinary(AgentClientMessageSchema, clientMessage),
|
|
101
93
|
blobStore,
|
|
94
|
+
cloudRule,
|
|
102
95
|
mcpTools: [],
|
|
103
96
|
};
|
|
104
97
|
}
|
|
98
|
+
function buildCloudRule(systemPrompt) {
|
|
99
|
+
const content = systemPrompt.trim();
|
|
100
|
+
return content || undefined;
|
|
101
|
+
}
|
|
@@ -39,4 +39,4 @@ export declare function computeUsage(state: StreamState): {
|
|
|
39
39
|
completion_tokens: number;
|
|
40
40
|
total_tokens: number;
|
|
41
41
|
};
|
|
42
|
-
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, onTurnEnded?: () => void, onUnsupportedMessage?: (info: UnsupportedServerMessageInfo) => void, onUnhandledExec?: (info: UnhandledExecInfo) => void): void;
|
|
42
|
+
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, 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
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,16 @@ 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, onUnsupportedMessage, 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,
|
|
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, state, onMcpExec, onUnhandledExec);
|
|
140
|
+
handleExecMessage(msg.message.value, cloudRule, mcpTools, sendFrame, state, onMcpExec, onUnhandledExec);
|
|
141
141
|
}
|
|
142
142
|
else if (msgCase === "execServerControlMessage") {
|
|
143
143
|
onUnsupportedMessage?.({
|
|
@@ -164,8 +164,19 @@ export function processServerMessage(msg, blobStore, mcpTools, sendFrame, state,
|
|
|
164
164
|
});
|
|
165
165
|
}
|
|
166
166
|
}
|
|
167
|
-
function handleInteractionUpdate(update, state, onText,
|
|
167
|
+
function handleInteractionUpdate(update, state, onText, onTurnEnded, onUnsupportedMessage) {
|
|
168
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
|
+
}
|
|
169
180
|
if (updateCase === "textDelta") {
|
|
170
181
|
const delta = update.message.value.text || "";
|
|
171
182
|
if (delta)
|
|
@@ -180,16 +191,19 @@ function handleInteractionUpdate(update, state, onText, onMcpExec, onTurnEnded,
|
|
|
180
191
|
state.outputTokens += update.message.value.tokens ?? 0;
|
|
181
192
|
}
|
|
182
193
|
else if (updateCase === "partialToolCall") {
|
|
183
|
-
|
|
184
|
-
if (partial.callId && partial.argsTextDelta) {
|
|
185
|
-
const existing = state.interactionToolArgsText.get(partial.callId) ?? "";
|
|
186
|
-
state.interactionToolArgsText.set(partial.callId, `${existing}${partial.argsTextDelta}`);
|
|
187
|
-
}
|
|
194
|
+
return;
|
|
188
195
|
}
|
|
189
196
|
else if (updateCase === "toolCallCompleted") {
|
|
190
|
-
const
|
|
191
|
-
if (
|
|
192
|
-
|
|
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
|
+
}
|
|
193
207
|
}
|
|
194
208
|
else if (updateCase === "turnEnded") {
|
|
195
209
|
onTurnEnded?.();
|
|
@@ -212,10 +226,8 @@ function handleInteractionUpdate(update, state, onText, onMcpExec, onTurnEnded,
|
|
|
212
226
|
caseName: updateCase ?? "undefined",
|
|
213
227
|
});
|
|
214
228
|
}
|
|
215
|
-
//
|
|
216
|
-
//
|
|
217
|
-
// execServerMessage.mcpArgs so tool results can be resumed with the correct
|
|
218
|
-
// exec envelope.
|
|
229
|
+
// Interaction tool-call updates are informational only. Resumable MCP tool
|
|
230
|
+
// execution comes from execServerMessage.mcpArgs.
|
|
219
231
|
}
|
|
220
232
|
function handleInteractionQuery(query, sendFrame, onUnsupportedMessage) {
|
|
221
233
|
const queryCase = query.query.case;
|
|
@@ -342,9 +354,19 @@ function handleKvMessage(kvMsg, blobStore, sendFrame) {
|
|
|
342
354
|
sendKvResponse(kvMsg, "setBlobResult", create(SetBlobResultSchema, {}), sendFrame);
|
|
343
355
|
}
|
|
344
356
|
}
|
|
345
|
-
function handleExecMessage(execMsg, mcpTools, sendFrame, state, onMcpExec, onUnhandledExec) {
|
|
357
|
+
function handleExecMessage(execMsg, cloudRule, mcpTools, sendFrame, state, onMcpExec, onUnhandledExec) {
|
|
346
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
|
+
});
|
|
347
364
|
if (execCase === "requestContextArgs") {
|
|
365
|
+
logPluginInfo("Responding to Cursor requestContextArgs", {
|
|
366
|
+
execId: execMsg.execId,
|
|
367
|
+
execMsgId: execMsg.id,
|
|
368
|
+
mcpToolCount: mcpTools.length,
|
|
369
|
+
});
|
|
348
370
|
const requestContext = create(RequestContextSchema, {
|
|
349
371
|
rules: [],
|
|
350
372
|
repositoryInfo: [],
|
|
@@ -352,6 +374,7 @@ function handleExecMessage(execMsg, mcpTools, sendFrame, state, onMcpExec, onUnh
|
|
|
352
374
|
gitRepos: [],
|
|
353
375
|
projectLayouts: [],
|
|
354
376
|
mcpInstructions: [],
|
|
377
|
+
cloudRule,
|
|
355
378
|
fileContents: {},
|
|
356
379
|
customSubagents: [],
|
|
357
380
|
});
|
|
@@ -367,18 +390,23 @@ function handleExecMessage(execMsg, mcpTools, sendFrame, state, onMcpExec, onUnh
|
|
|
367
390
|
if (execCase === "mcpArgs") {
|
|
368
391
|
const mcpArgs = execMsg.message.value;
|
|
369
392
|
const decoded = decodeMcpArgsMap(mcpArgs.args ?? {});
|
|
370
|
-
const
|
|
371
|
-
if (state.emittedToolCallIds.has(toolCallId)) {
|
|
372
|
-
return;
|
|
373
|
-
}
|
|
374
|
-
state.emittedToolCallIds.add(toolCallId);
|
|
375
|
-
onMcpExec({
|
|
393
|
+
const exec = {
|
|
376
394
|
execId: execMsg.execId,
|
|
377
395
|
execMsgId: execMsg.id,
|
|
378
|
-
toolCallId,
|
|
396
|
+
toolCallId: mcpArgs.toolCallId || crypto.randomUUID(),
|
|
379
397
|
toolName: mcpArgs.toolName || mcpArgs.name,
|
|
380
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,
|
|
381
408
|
});
|
|
409
|
+
onMcpExec(exec);
|
|
382
410
|
return;
|
|
383
411
|
}
|
|
384
412
|
// --- Reject native Cursor tools ---
|
|
@@ -386,6 +414,11 @@ function handleExecMessage(execMsg, mcpTools, sendFrame, state, onMcpExec, onUnh
|
|
|
386
414
|
// so it falls back to our MCP tools (registered via RequestContext).
|
|
387
415
|
const REJECT_REASON = "Tool not available in this environment. Use the MCP tools provided instead.";
|
|
388
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
|
+
});
|
|
389
422
|
const args = execMsg.message.value;
|
|
390
423
|
const result = create(ReadResultSchema, {
|
|
391
424
|
result: {
|
|
@@ -400,6 +433,11 @@ function handleExecMessage(execMsg, mcpTools, sendFrame, state, onMcpExec, onUnh
|
|
|
400
433
|
return;
|
|
401
434
|
}
|
|
402
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
|
+
});
|
|
403
441
|
const args = execMsg.message.value;
|
|
404
442
|
const result = create(LsResultSchema, {
|
|
405
443
|
result: {
|
|
@@ -414,6 +452,10 @@ function handleExecMessage(execMsg, mcpTools, sendFrame, state, onMcpExec, onUnh
|
|
|
414
452
|
return;
|
|
415
453
|
}
|
|
416
454
|
if (execCase === "grepArgs") {
|
|
455
|
+
logPluginInfo("Rejecting native Cursor grep tool in favor of MCP", {
|
|
456
|
+
execId: execMsg.execId,
|
|
457
|
+
execMsgId: execMsg.id,
|
|
458
|
+
});
|
|
417
459
|
const result = create(GrepResultSchema, {
|
|
418
460
|
result: {
|
|
419
461
|
case: "error",
|
|
@@ -424,6 +466,11 @@ function handleExecMessage(execMsg, mcpTools, sendFrame, state, onMcpExec, onUnh
|
|
|
424
466
|
return;
|
|
425
467
|
}
|
|
426
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
|
+
});
|
|
427
474
|
const args = execMsg.message.value;
|
|
428
475
|
const result = create(WriteResultSchema, {
|
|
429
476
|
result: {
|
|
@@ -438,6 +485,11 @@ function handleExecMessage(execMsg, mcpTools, sendFrame, state, onMcpExec, onUnh
|
|
|
438
485
|
return;
|
|
439
486
|
}
|
|
440
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
|
+
});
|
|
441
493
|
const args = execMsg.message.value;
|
|
442
494
|
const result = create(DeleteResultSchema, {
|
|
443
495
|
result: {
|
|
@@ -452,6 +504,13 @@ function handleExecMessage(execMsg, mcpTools, sendFrame, state, onMcpExec, onUnh
|
|
|
452
504
|
return;
|
|
453
505
|
}
|
|
454
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
|
+
});
|
|
455
514
|
const args = execMsg.message.value;
|
|
456
515
|
const result = create(ShellResultSchema, {
|
|
457
516
|
result: {
|
|
@@ -468,6 +527,12 @@ function handleExecMessage(execMsg, mcpTools, sendFrame, state, onMcpExec, onUnh
|
|
|
468
527
|
return;
|
|
469
528
|
}
|
|
470
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
|
+
});
|
|
471
536
|
const args = execMsg.message.value;
|
|
472
537
|
const result = create(BackgroundShellSpawnResultSchema, {
|
|
473
538
|
result: {
|
|
@@ -484,6 +549,10 @@ function handleExecMessage(execMsg, mcpTools, sendFrame, state, onMcpExec, onUnh
|
|
|
484
549
|
return;
|
|
485
550
|
}
|
|
486
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
|
+
});
|
|
487
556
|
const result = create(WriteShellStdinResultSchema, {
|
|
488
557
|
result: {
|
|
489
558
|
case: "error",
|
|
@@ -494,6 +563,11 @@ function handleExecMessage(execMsg, mcpTools, sendFrame, state, onMcpExec, onUnh
|
|
|
494
563
|
return;
|
|
495
564
|
}
|
|
496
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
|
+
});
|
|
497
571
|
const args = execMsg.message.value;
|
|
498
572
|
const result = create(FetchResultSchema, {
|
|
499
573
|
result: {
|
|
@@ -508,6 +582,11 @@ function handleExecMessage(execMsg, mcpTools, sendFrame, state, onMcpExec, onUnh
|
|
|
508
582
|
return;
|
|
509
583
|
}
|
|
510
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
|
+
});
|
|
511
590
|
const result = create(DiagnosticsResultSchema, {});
|
|
512
591
|
sendExecResult(execMsg, "diagnosticsResult", result, sendFrame);
|
|
513
592
|
return;
|
|
@@ -521,6 +600,12 @@ function handleExecMessage(execMsg, mcpTools, sendFrame, state, onMcpExec, onUnh
|
|
|
521
600
|
};
|
|
522
601
|
const resultCase = miscCaseMap[execCase];
|
|
523
602
|
if (resultCase) {
|
|
603
|
+
logPluginInfo("Responding to miscellaneous Cursor exec message", {
|
|
604
|
+
execCase,
|
|
605
|
+
execId: execMsg.execId,
|
|
606
|
+
execMsgId: execMsg.id,
|
|
607
|
+
resultCase,
|
|
608
|
+
});
|
|
524
609
|
sendExecResult(execMsg, resultCase, create(McpResultSchema, {}), sendFrame);
|
|
525
610
|
return;
|
|
526
611
|
}
|
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,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.
|
|
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": {
|