@playwo/opencode-cursor-oauth 0.0.0-dev.dfb269562f0c → 0.0.0-dev.e1637ce79fd6
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-non-streaming.js +0 -2
- package/dist/proxy/bridge-streaming.d.ts +1 -1
- package/dist/proxy/bridge-streaming.js +88 -5
- package/dist/proxy/chat-completion.js +40 -1
- package/dist/proxy/stream-dispatch.js +202 -54
- package/dist/proxy/stream-state.d.ts +0 -2
- package/dist/proxy/types.d.ts +3 -0
- 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
|
}
|
|
@@ -38,8 +38,6 @@ async function collectFullResponse(payload, accessToken, modelId, convKey, metad
|
|
|
38
38
|
pendingExecs: [],
|
|
39
39
|
outputTokens: 0,
|
|
40
40
|
totalTokens: 0,
|
|
41
|
-
interactionToolArgsText: new Map(),
|
|
42
|
-
emittedToolCallIds: new Set(),
|
|
43
41
|
};
|
|
44
42
|
const tagFilter = createThinkingTagFilter();
|
|
45
43
|
bridge.onData(createConnectFrameParser((messageBytes) => {
|
|
@@ -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,6 +1,6 @@
|
|
|
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";
|
|
@@ -27,8 +27,6 @@ function createBridgeStreamResponse(bridge, heartbeatTimer, blobStore, mcpTools,
|
|
|
27
27
|
pendingExecs: [],
|
|
28
28
|
outputTokens: 0,
|
|
29
29
|
totalTokens: 0,
|
|
30
|
-
interactionToolArgsText: new Map(),
|
|
31
|
-
emittedToolCallIds: new Set(),
|
|
32
30
|
};
|
|
33
31
|
const tagFilter = createThinkingTagFilter();
|
|
34
32
|
let assistantText = metadata.assistantSeedText ?? "";
|
|
@@ -103,7 +101,13 @@ function createBridgeStreamResponse(bridge, heartbeatTimer, blobStore, mcpTools,
|
|
|
103
101
|
sendSSE(makeChunk({ content }));
|
|
104
102
|
}
|
|
105
103
|
}, (exec) => {
|
|
106
|
-
state.pendingExecs.
|
|
104
|
+
const existingIndex = state.pendingExecs.findIndex((candidate) => candidate.toolCallId === exec.toolCallId);
|
|
105
|
+
if (existingIndex >= 0) {
|
|
106
|
+
state.pendingExecs[existingIndex] = exec;
|
|
107
|
+
}
|
|
108
|
+
else {
|
|
109
|
+
state.pendingExecs.push(exec);
|
|
110
|
+
}
|
|
107
111
|
mcpExecReceived = true;
|
|
108
112
|
const flushed = tagFilter.flush();
|
|
109
113
|
if (flushed.reasoning)
|
|
@@ -206,8 +210,22 @@ function createBridgeStreamResponse(bridge, heartbeatTimer, blobStore, mcpTools,
|
|
|
206
210
|
stopKeepalive();
|
|
207
211
|
}
|
|
208
212
|
}, SSE_KEEPALIVE_INTERVAL_MS);
|
|
213
|
+
logPluginInfo("Opened Cursor streaming bridge", {
|
|
214
|
+
modelId,
|
|
215
|
+
bridgeKey,
|
|
216
|
+
convKey,
|
|
217
|
+
mcpToolCount: mcpTools.length,
|
|
218
|
+
});
|
|
209
219
|
bridge.onData(processChunk);
|
|
210
220
|
bridge.onClose((code) => {
|
|
221
|
+
logPluginInfo("Cursor streaming bridge closed", {
|
|
222
|
+
modelId,
|
|
223
|
+
bridgeKey,
|
|
224
|
+
convKey,
|
|
225
|
+
code,
|
|
226
|
+
mcpExecReceived,
|
|
227
|
+
hadEndStreamError: Boolean(endStreamError),
|
|
228
|
+
});
|
|
211
229
|
clearInterval(heartbeatTimer);
|
|
212
230
|
stopKeepalive();
|
|
213
231
|
syncStoredBlobStore(convKey, blobStore);
|
|
@@ -257,10 +275,35 @@ function createBridgeStreamResponse(bridge, heartbeatTimer, blobStore, mcpTools,
|
|
|
257
275
|
return new Response(stream, { headers: SSE_HEADERS });
|
|
258
276
|
}
|
|
259
277
|
export async function handleStreamingResponse(payload, accessToken, modelId, bridgeKey, convKey, metadata) {
|
|
278
|
+
logPluginInfo("Starting Cursor streaming response", {
|
|
279
|
+
modelId,
|
|
280
|
+
bridgeKey,
|
|
281
|
+
convKey,
|
|
282
|
+
mcpToolCount: payload.mcpTools.length,
|
|
283
|
+
});
|
|
260
284
|
const { bridge, heartbeatTimer } = await startBridge(accessToken, payload.requestBytes);
|
|
261
285
|
return createBridgeStreamResponse(bridge, heartbeatTimer, payload.blobStore, payload.mcpTools, modelId, bridgeKey, convKey, metadata);
|
|
262
286
|
}
|
|
263
|
-
|
|
287
|
+
async function waitForResolvablePendingExecs(active, toolResults, timeoutMs = 2_000) {
|
|
288
|
+
const pendingToolCallIds = new Set(toolResults.map((result) => result.toolCallId));
|
|
289
|
+
const deadline = Date.now() + timeoutMs;
|
|
290
|
+
while (Date.now() < deadline) {
|
|
291
|
+
const unresolved = active.pendingExecs.filter((exec) => pendingToolCallIds.has(exec.toolCallId) && exec.execMsgId === 0);
|
|
292
|
+
if (unresolved.length === 0) {
|
|
293
|
+
return unresolved;
|
|
294
|
+
}
|
|
295
|
+
await new Promise((resolve) => setTimeout(resolve, 25));
|
|
296
|
+
}
|
|
297
|
+
const unresolved = active.pendingExecs.filter((exec) => pendingToolCallIds.has(exec.toolCallId) && exec.execMsgId === 0);
|
|
298
|
+
if (unresolved.length > 0) {
|
|
299
|
+
logPluginWarn("Cursor exec metadata did not arrive before tool-result resume", {
|
|
300
|
+
bridgeToolCallIds: unresolved.map((exec) => exec.toolCallId),
|
|
301
|
+
modelId: active.modelId,
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
return unresolved;
|
|
305
|
+
}
|
|
306
|
+
export async function handleToolResultResume(active, toolResults, bridgeKey, convKey) {
|
|
264
307
|
const { bridge, heartbeatTimer, blobStore, mcpTools, pendingExecs, modelId, metadata, } = active;
|
|
265
308
|
const resumeMetadata = {
|
|
266
309
|
...metadata,
|
|
@@ -271,6 +314,33 @@ export function handleToolResultResume(active, toolResults, bridgeKey, convKey)
|
|
|
271
314
|
.filter(Boolean)
|
|
272
315
|
.join("\n\n"),
|
|
273
316
|
};
|
|
317
|
+
logPluginInfo("Preparing Cursor tool-result resume", {
|
|
318
|
+
bridgeKey,
|
|
319
|
+
convKey,
|
|
320
|
+
modelId,
|
|
321
|
+
toolResults,
|
|
322
|
+
pendingExecs,
|
|
323
|
+
});
|
|
324
|
+
const unresolved = await waitForResolvablePendingExecs(active, toolResults);
|
|
325
|
+
logPluginInfo("Resolved pending exec state before Cursor tool-result resume", {
|
|
326
|
+
bridgeKey,
|
|
327
|
+
convKey,
|
|
328
|
+
modelId,
|
|
329
|
+
toolResults,
|
|
330
|
+
pendingExecs,
|
|
331
|
+
unresolvedPendingExecs: unresolved,
|
|
332
|
+
});
|
|
333
|
+
if (unresolved.length > 0) {
|
|
334
|
+
clearInterval(heartbeatTimer);
|
|
335
|
+
bridge.end();
|
|
336
|
+
return new Response(JSON.stringify({
|
|
337
|
+
error: {
|
|
338
|
+
message: "Cursor requested a tool call but never provided resumable exec metadata. Aborting instead of retrying with synthetic ids.",
|
|
339
|
+
type: "invalid_request_error",
|
|
340
|
+
code: "cursor_missing_exec_metadata",
|
|
341
|
+
},
|
|
342
|
+
}), { status: 400, headers: { "Content-Type": "application/json" } });
|
|
343
|
+
}
|
|
274
344
|
for (const exec of pendingExecs) {
|
|
275
345
|
const result = toolResults.find((toolResult) => toolResult.toolCallId === exec.toolCallId);
|
|
276
346
|
const mcpResult = result
|
|
@@ -311,6 +381,19 @@ export function handleToolResultResume(active, toolResults, bridgeKey, convKey)
|
|
|
311
381
|
const clientMessage = create(AgentClientMessageSchema, {
|
|
312
382
|
message: { case: "execClientMessage", value: execClientMessage },
|
|
313
383
|
});
|
|
384
|
+
logPluginInfo("Sending Cursor tool-result resume message", {
|
|
385
|
+
bridgeKey,
|
|
386
|
+
convKey,
|
|
387
|
+
modelId,
|
|
388
|
+
toolCallId: exec.toolCallId,
|
|
389
|
+
toolName: exec.toolName,
|
|
390
|
+
source: exec.source,
|
|
391
|
+
execId: exec.execId,
|
|
392
|
+
execMsgId: exec.execMsgId,
|
|
393
|
+
cursorCallId: exec.cursorCallId,
|
|
394
|
+
modelCallId: exec.modelCallId,
|
|
395
|
+
matchedToolResult: result,
|
|
396
|
+
});
|
|
314
397
|
bridge.write(toBinary(AgentClientMessageSchema, clientMessage));
|
|
315
398
|
}
|
|
316
399
|
return createBridgeStreamResponse(bridge, heartbeatTimer, blobStore, mcpTools, modelId, bridgeKey, convKey, resumeMetadata);
|
|
@@ -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,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 {
|
|
@@ -131,13 +131,13 @@ export function computeUsage(state) {
|
|
|
131
131
|
export function processServerMessage(msg, blobStore, 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, onMcpExec, onUnhandledExec);
|
|
140
|
+
handleExecMessage(msg.message.value, mcpTools, sendFrame, state, onMcpExec, onUnhandledExec);
|
|
141
141
|
}
|
|
142
142
|
else if (msgCase === "execServerControlMessage") {
|
|
143
143
|
onUnsupportedMessage?.({
|
|
@@ -146,10 +146,7 @@ export function processServerMessage(msg, blobStore, mcpTools, sendFrame, state,
|
|
|
146
146
|
});
|
|
147
147
|
}
|
|
148
148
|
else if (msgCase === "interactionQuery") {
|
|
149
|
-
onUnsupportedMessage
|
|
150
|
-
category: "interactionQuery",
|
|
151
|
-
caseName: msg.message.value.query.case ?? "undefined",
|
|
152
|
-
});
|
|
149
|
+
handleInteractionQuery(msg.message.value, sendFrame, onUnsupportedMessage);
|
|
153
150
|
}
|
|
154
151
|
else if (msgCase === "conversationCheckpointUpdate") {
|
|
155
152
|
const stateStructure = msg.message.value;
|
|
@@ -167,8 +164,19 @@ export function processServerMessage(msg, blobStore, mcpTools, sendFrame, state,
|
|
|
167
164
|
});
|
|
168
165
|
}
|
|
169
166
|
}
|
|
170
|
-
function handleInteractionUpdate(update, state, onText,
|
|
167
|
+
function handleInteractionUpdate(update, state, onText, onTurnEnded, onUnsupportedMessage) {
|
|
171
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
|
+
}
|
|
172
180
|
if (updateCase === "textDelta") {
|
|
173
181
|
const delta = update.message.value.text || "";
|
|
174
182
|
if (delta)
|
|
@@ -183,15 +191,19 @@ function handleInteractionUpdate(update, state, onText, onMcpExec, onTurnEnded,
|
|
|
183
191
|
state.outputTokens += update.message.value.tokens ?? 0;
|
|
184
192
|
}
|
|
185
193
|
else if (updateCase === "partialToolCall") {
|
|
186
|
-
|
|
187
|
-
if (partial.callId && partial.argsTextDelta) {
|
|
188
|
-
state.interactionToolArgsText.set(partial.callId, partial.argsTextDelta);
|
|
189
|
-
}
|
|
194
|
+
return;
|
|
190
195
|
}
|
|
191
196
|
else if (updateCase === "toolCallCompleted") {
|
|
192
|
-
const
|
|
193
|
-
if (
|
|
194
|
-
|
|
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
|
+
}
|
|
195
207
|
}
|
|
196
208
|
else if (updateCase === "turnEnded") {
|
|
197
209
|
onTurnEnded?.();
|
|
@@ -214,43 +226,92 @@ function handleInteractionUpdate(update, state, onText, onMcpExec, onTurnEnded,
|
|
|
214
226
|
caseName: updateCase ?? "undefined",
|
|
215
227
|
});
|
|
216
228
|
}
|
|
217
|
-
//
|
|
218
|
-
//
|
|
219
|
-
// calls may still appear here on some models, so we surface those, but we
|
|
220
|
-
// do not abort the bridge for native Cursor tool-call progress events.
|
|
229
|
+
// Interaction tool-call updates are informational only. Resumable MCP tool
|
|
230
|
+
// execution comes from execServerMessage.mcpArgs.
|
|
221
231
|
}
|
|
222
|
-
function
|
|
223
|
-
const
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
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
|
+
});
|
|
254
315
|
}
|
|
255
316
|
/** Send a KV client response back to Cursor. */
|
|
256
317
|
function sendKvResponse(kvMsg, messageCase, value, sendFrame) {
|
|
@@ -263,6 +324,16 @@ function sendKvResponse(kvMsg, messageCase, value, sendFrame) {
|
|
|
263
324
|
});
|
|
264
325
|
sendFrame(toBinary(AgentClientMessageSchema, clientMsg));
|
|
265
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
|
+
}
|
|
266
337
|
function handleKvMessage(kvMsg, blobStore, sendFrame) {
|
|
267
338
|
const kvCase = kvMsg.message.case;
|
|
268
339
|
if (kvCase === "getBlobArgs") {
|
|
@@ -283,9 +354,19 @@ function handleKvMessage(kvMsg, blobStore, sendFrame) {
|
|
|
283
354
|
sendKvResponse(kvMsg, "setBlobResult", create(SetBlobResultSchema, {}), sendFrame);
|
|
284
355
|
}
|
|
285
356
|
}
|
|
286
|
-
function handleExecMessage(execMsg, mcpTools, sendFrame, onMcpExec, onUnhandledExec) {
|
|
357
|
+
function handleExecMessage(execMsg, mcpTools, sendFrame, state, onMcpExec, onUnhandledExec) {
|
|
287
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
|
+
});
|
|
288
364
|
if (execCase === "requestContextArgs") {
|
|
365
|
+
logPluginInfo("Responding to Cursor requestContextArgs", {
|
|
366
|
+
execId: execMsg.execId,
|
|
367
|
+
execMsgId: execMsg.id,
|
|
368
|
+
mcpToolCount: mcpTools.length,
|
|
369
|
+
});
|
|
289
370
|
const requestContext = create(RequestContextSchema, {
|
|
290
371
|
rules: [],
|
|
291
372
|
repositoryInfo: [],
|
|
@@ -308,13 +389,23 @@ function handleExecMessage(execMsg, mcpTools, sendFrame, onMcpExec, onUnhandledE
|
|
|
308
389
|
if (execCase === "mcpArgs") {
|
|
309
390
|
const mcpArgs = execMsg.message.value;
|
|
310
391
|
const decoded = decodeMcpArgsMap(mcpArgs.args ?? {});
|
|
311
|
-
|
|
392
|
+
const exec = {
|
|
312
393
|
execId: execMsg.execId,
|
|
313
394
|
execMsgId: execMsg.id,
|
|
314
395
|
toolCallId: mcpArgs.toolCallId || crypto.randomUUID(),
|
|
315
396
|
toolName: mcpArgs.toolName || mcpArgs.name,
|
|
316
397
|
decodedArgs: JSON.stringify(decoded),
|
|
398
|
+
source: "exec",
|
|
399
|
+
};
|
|
400
|
+
logPluginInfo("Received Cursor exec MCP tool metadata", {
|
|
401
|
+
toolCallId: exec.toolCallId,
|
|
402
|
+
toolName: exec.toolName,
|
|
403
|
+
source: exec.source,
|
|
404
|
+
execId: exec.execId,
|
|
405
|
+
execMsgId: exec.execMsgId,
|
|
406
|
+
decodedArgs: exec.decodedArgs,
|
|
317
407
|
});
|
|
408
|
+
onMcpExec(exec);
|
|
318
409
|
return;
|
|
319
410
|
}
|
|
320
411
|
// --- Reject native Cursor tools ---
|
|
@@ -322,6 +413,11 @@ function handleExecMessage(execMsg, mcpTools, sendFrame, onMcpExec, onUnhandledE
|
|
|
322
413
|
// so it falls back to our MCP tools (registered via RequestContext).
|
|
323
414
|
const REJECT_REASON = "Tool not available in this environment. Use the MCP tools provided instead.";
|
|
324
415
|
if (execCase === "readArgs") {
|
|
416
|
+
logPluginInfo("Rejecting native Cursor read tool in favor of MCP", {
|
|
417
|
+
execId: execMsg.execId,
|
|
418
|
+
execMsgId: execMsg.id,
|
|
419
|
+
path: execMsg.message.value.path,
|
|
420
|
+
});
|
|
325
421
|
const args = execMsg.message.value;
|
|
326
422
|
const result = create(ReadResultSchema, {
|
|
327
423
|
result: {
|
|
@@ -336,6 +432,11 @@ function handleExecMessage(execMsg, mcpTools, sendFrame, onMcpExec, onUnhandledE
|
|
|
336
432
|
return;
|
|
337
433
|
}
|
|
338
434
|
if (execCase === "lsArgs") {
|
|
435
|
+
logPluginInfo("Rejecting native Cursor ls tool in favor of MCP", {
|
|
436
|
+
execId: execMsg.execId,
|
|
437
|
+
execMsgId: execMsg.id,
|
|
438
|
+
path: execMsg.message.value.path,
|
|
439
|
+
});
|
|
339
440
|
const args = execMsg.message.value;
|
|
340
441
|
const result = create(LsResultSchema, {
|
|
341
442
|
result: {
|
|
@@ -350,6 +451,10 @@ function handleExecMessage(execMsg, mcpTools, sendFrame, onMcpExec, onUnhandledE
|
|
|
350
451
|
return;
|
|
351
452
|
}
|
|
352
453
|
if (execCase === "grepArgs") {
|
|
454
|
+
logPluginInfo("Rejecting native Cursor grep tool in favor of MCP", {
|
|
455
|
+
execId: execMsg.execId,
|
|
456
|
+
execMsgId: execMsg.id,
|
|
457
|
+
});
|
|
353
458
|
const result = create(GrepResultSchema, {
|
|
354
459
|
result: {
|
|
355
460
|
case: "error",
|
|
@@ -360,6 +465,11 @@ function handleExecMessage(execMsg, mcpTools, sendFrame, onMcpExec, onUnhandledE
|
|
|
360
465
|
return;
|
|
361
466
|
}
|
|
362
467
|
if (execCase === "writeArgs") {
|
|
468
|
+
logPluginInfo("Rejecting native Cursor write tool in favor of MCP", {
|
|
469
|
+
execId: execMsg.execId,
|
|
470
|
+
execMsgId: execMsg.id,
|
|
471
|
+
path: execMsg.message.value.path,
|
|
472
|
+
});
|
|
363
473
|
const args = execMsg.message.value;
|
|
364
474
|
const result = create(WriteResultSchema, {
|
|
365
475
|
result: {
|
|
@@ -374,6 +484,11 @@ function handleExecMessage(execMsg, mcpTools, sendFrame, onMcpExec, onUnhandledE
|
|
|
374
484
|
return;
|
|
375
485
|
}
|
|
376
486
|
if (execCase === "deleteArgs") {
|
|
487
|
+
logPluginInfo("Rejecting native Cursor delete tool in favor of MCP", {
|
|
488
|
+
execId: execMsg.execId,
|
|
489
|
+
execMsgId: execMsg.id,
|
|
490
|
+
path: execMsg.message.value.path,
|
|
491
|
+
});
|
|
377
492
|
const args = execMsg.message.value;
|
|
378
493
|
const result = create(DeleteResultSchema, {
|
|
379
494
|
result: {
|
|
@@ -388,6 +503,13 @@ function handleExecMessage(execMsg, mcpTools, sendFrame, onMcpExec, onUnhandledE
|
|
|
388
503
|
return;
|
|
389
504
|
}
|
|
390
505
|
if (execCase === "shellArgs" || execCase === "shellStreamArgs") {
|
|
506
|
+
logPluginInfo("Rejecting native Cursor shell tool in favor of MCP", {
|
|
507
|
+
execId: execMsg.execId,
|
|
508
|
+
execMsgId: execMsg.id,
|
|
509
|
+
command: execMsg.message.value.command ?? "",
|
|
510
|
+
workingDirectory: execMsg.message.value.workingDirectory ?? "",
|
|
511
|
+
execCase,
|
|
512
|
+
});
|
|
391
513
|
const args = execMsg.message.value;
|
|
392
514
|
const result = create(ShellResultSchema, {
|
|
393
515
|
result: {
|
|
@@ -404,6 +526,12 @@ function handleExecMessage(execMsg, mcpTools, sendFrame, onMcpExec, onUnhandledE
|
|
|
404
526
|
return;
|
|
405
527
|
}
|
|
406
528
|
if (execCase === "backgroundShellSpawnArgs") {
|
|
529
|
+
logPluginInfo("Rejecting native Cursor background shell tool in favor of MCP", {
|
|
530
|
+
execId: execMsg.execId,
|
|
531
|
+
execMsgId: execMsg.id,
|
|
532
|
+
command: execMsg.message.value.command ?? "",
|
|
533
|
+
workingDirectory: execMsg.message.value.workingDirectory ?? "",
|
|
534
|
+
});
|
|
407
535
|
const args = execMsg.message.value;
|
|
408
536
|
const result = create(BackgroundShellSpawnResultSchema, {
|
|
409
537
|
result: {
|
|
@@ -420,6 +548,10 @@ function handleExecMessage(execMsg, mcpTools, sendFrame, onMcpExec, onUnhandledE
|
|
|
420
548
|
return;
|
|
421
549
|
}
|
|
422
550
|
if (execCase === "writeShellStdinArgs") {
|
|
551
|
+
logPluginInfo("Rejecting native Cursor shell stdin tool in favor of MCP", {
|
|
552
|
+
execId: execMsg.execId,
|
|
553
|
+
execMsgId: execMsg.id,
|
|
554
|
+
});
|
|
423
555
|
const result = create(WriteShellStdinResultSchema, {
|
|
424
556
|
result: {
|
|
425
557
|
case: "error",
|
|
@@ -430,6 +562,11 @@ function handleExecMessage(execMsg, mcpTools, sendFrame, onMcpExec, onUnhandledE
|
|
|
430
562
|
return;
|
|
431
563
|
}
|
|
432
564
|
if (execCase === "fetchArgs") {
|
|
565
|
+
logPluginInfo("Rejecting native Cursor fetch tool in favor of MCP", {
|
|
566
|
+
execId: execMsg.execId,
|
|
567
|
+
execMsgId: execMsg.id,
|
|
568
|
+
url: execMsg.message.value.url,
|
|
569
|
+
});
|
|
433
570
|
const args = execMsg.message.value;
|
|
434
571
|
const result = create(FetchResultSchema, {
|
|
435
572
|
result: {
|
|
@@ -444,6 +581,11 @@ function handleExecMessage(execMsg, mcpTools, sendFrame, onMcpExec, onUnhandledE
|
|
|
444
581
|
return;
|
|
445
582
|
}
|
|
446
583
|
if (execCase === "diagnosticsArgs") {
|
|
584
|
+
logPluginInfo("Rejecting native Cursor diagnostics tool in favor of MCP", {
|
|
585
|
+
execId: execMsg.execId,
|
|
586
|
+
execMsgId: execMsg.id,
|
|
587
|
+
path: execMsg.message.value.path,
|
|
588
|
+
});
|
|
447
589
|
const result = create(DiagnosticsResultSchema, {});
|
|
448
590
|
sendExecResult(execMsg, "diagnosticsResult", result, sendFrame);
|
|
449
591
|
return;
|
|
@@ -457,6 +599,12 @@ function handleExecMessage(execMsg, mcpTools, sendFrame, onMcpExec, onUnhandledE
|
|
|
457
599
|
};
|
|
458
600
|
const resultCase = miscCaseMap[execCase];
|
|
459
601
|
if (resultCase) {
|
|
602
|
+
logPluginInfo("Responding to miscellaneous Cursor exec message", {
|
|
603
|
+
execCase,
|
|
604
|
+
execId: execMsg.execId,
|
|
605
|
+
execMsgId: execMsg.id,
|
|
606
|
+
resultCase,
|
|
607
|
+
});
|
|
460
608
|
sendExecResult(execMsg, resultCase, create(McpResultSchema, {}), sendFrame);
|
|
461
609
|
return;
|
|
462
610
|
}
|
package/dist/proxy/types.d.ts
CHANGED
|
@@ -14,6 +14,9 @@ export interface PendingExec {
|
|
|
14
14
|
toolName: string;
|
|
15
15
|
/** Decoded arguments JSON string for SSE tool_calls emission. */
|
|
16
16
|
decodedArgs: string;
|
|
17
|
+
source?: "interaction" | "exec";
|
|
18
|
+
cursorCallId?: string;
|
|
19
|
+
modelCallId?: string;
|
|
17
20
|
}
|
|
18
21
|
/** A live Cursor session kept alive across requests for tool result continuation. */
|
|
19
22
|
export interface ActiveBridge {
|
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.e1637ce79fd6",
|
|
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": {
|