@playwo/opencode-cursor-oauth 0.0.0-dev.4258a6733133 → 0.0.0-dev.4696faa690e4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +33 -12
- package/dist/AGENTS.md +8 -0
- package/dist/agent-rules.d.ts +1 -0
- package/dist/agent-rules.js +28 -0
- package/dist/cursor/bidi-session.d.ts +1 -2
- package/dist/cursor/bidi-session.js +153 -138
- package/dist/cursor/index.d.ts +1 -1
- package/dist/cursor/index.js +1 -1
- package/dist/cursor/unary-rpc.d.ts +0 -1
- package/dist/cursor/unary-rpc.js +2 -59
- package/dist/logger.d.ts +1 -0
- package/dist/logger.js +3 -0
- package/dist/proxy/bridge-close-controller.d.ts +6 -0
- package/dist/proxy/bridge-close-controller.js +37 -0
- package/dist/proxy/bridge-non-streaming.js +31 -7
- package/dist/proxy/bridge-session.js +1 -3
- package/dist/proxy/bridge-streaming.d.ts +1 -1
- package/dist/proxy/bridge-streaming.js +377 -57
- package/dist/proxy/chat-completion.js +41 -1
- package/dist/proxy/cursor-request.js +13 -15
- package/dist/proxy/stream-dispatch.d.ts +7 -1
- package/dist/proxy/stream-dispatch.js +228 -56
- package/dist/proxy/stream-state.d.ts +0 -2
- package/dist/proxy/types.d.ts +14 -1
- package/package.json +2 -3
|
@@ -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,24 @@ 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
|
+
diagnostics: activeBridge.diagnostics,
|
|
71
|
+
});
|
|
42
72
|
activeBridges.delete(bridgeKey);
|
|
43
73
|
if (activeBridge.bridge.alive) {
|
|
44
74
|
if (activeBridge.modelId !== modelId) {
|
|
@@ -93,6 +123,16 @@ export function handleChatCompletion(body, accessToken, context = {}) {
|
|
|
93
123
|
: userText;
|
|
94
124
|
const payload = buildCursorRequest(modelId, systemPrompt, effectiveUserText, replayTurns, stored.conversationId, stored.checkpoint, stored.blobStore);
|
|
95
125
|
payload.mcpTools = mcpTools;
|
|
126
|
+
logPluginInfo("Built Cursor run request payload", {
|
|
127
|
+
modelId,
|
|
128
|
+
bridgeKey,
|
|
129
|
+
convKey,
|
|
130
|
+
mcpToolCount: mcpTools.length,
|
|
131
|
+
conversationId: stored.conversationId,
|
|
132
|
+
hasCheckpoint: Boolean(stored.checkpoint),
|
|
133
|
+
replayTurnCount: replayTurns.length,
|
|
134
|
+
effectiveUserText,
|
|
135
|
+
});
|
|
96
136
|
if (body.stream === false) {
|
|
97
137
|
return handleNonStreamingResponse(payload, accessToken, modelId, convKey, {
|
|
98
138
|
systemPrompt,
|
|
@@ -1,13 +1,9 @@
|
|
|
1
1
|
import { create, fromBinary, toBinary } from "@bufbuild/protobuf";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { AgentClientMessageSchema, AgentRunRequestSchema, AgentConversationTurnStructureSchema, AssistantMessageSchema, ConversationActionSchema, ConversationStateStructureSchema, ConversationTurnStructureSchema, ConversationStepSchema, ModelDetailsSchema, ResumeActionSchema, UserMessageActionSchema, UserMessageSchema, } from "../proto/agent_pb";
|
|
3
|
+
import { appendBundledAgentsRule } from "../agent-rules";
|
|
4
4
|
export function buildCursorRequest(modelId, systemPrompt, userText, turns, conversationId, checkpoint, existingBlobStore) {
|
|
5
5
|
const blobStore = new Map(existingBlobStore ?? []);
|
|
6
|
-
|
|
7
|
-
const systemJson = JSON.stringify({ role: "system", content: systemPrompt });
|
|
8
|
-
const systemBytes = new TextEncoder().encode(systemJson);
|
|
9
|
-
const systemBlobId = new Uint8Array(createHash("sha256").update(systemBytes).digest());
|
|
10
|
-
blobStore.set(Buffer.from(systemBlobId).toString("hex"), systemBytes);
|
|
6
|
+
const cloudRule = buildCloudRule(systemPrompt);
|
|
11
7
|
let conversationState;
|
|
12
8
|
if (checkpoint) {
|
|
13
9
|
conversationState = fromBinary(ConversationStateStructureSchema, checkpoint);
|
|
@@ -40,7 +36,7 @@ export function buildCursorRequest(modelId, systemPrompt, userText, turns, conve
|
|
|
40
36
|
turnBytes.push(toBinary(ConversationTurnStructureSchema, turnStructure));
|
|
41
37
|
}
|
|
42
38
|
conversationState = create(ConversationStateStructureSchema, {
|
|
43
|
-
rootPromptMessagesJson: [
|
|
39
|
+
rootPromptMessagesJson: [],
|
|
44
40
|
turns: turnBytes,
|
|
45
41
|
todos: [],
|
|
46
42
|
pendingToolCalls: [],
|
|
@@ -64,14 +60,11 @@ export function buildCursorRequest(modelId, systemPrompt, userText, turns, conve
|
|
|
64
60
|
value: create(UserMessageActionSchema, { userMessage }),
|
|
65
61
|
},
|
|
66
62
|
});
|
|
67
|
-
return buildRunRequest(modelId, conversationId, conversationState, action, blobStore);
|
|
63
|
+
return buildRunRequest(modelId, conversationId, conversationState, action, blobStore, cloudRule);
|
|
68
64
|
}
|
|
69
65
|
export function buildCursorResumeRequest(modelId, systemPrompt, conversationId, checkpoint, existingBlobStore) {
|
|
70
66
|
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);
|
|
67
|
+
const cloudRule = buildCloudRule(systemPrompt);
|
|
75
68
|
const conversationState = fromBinary(ConversationStateStructureSchema, checkpoint);
|
|
76
69
|
const action = create(ConversationActionSchema, {
|
|
77
70
|
action: {
|
|
@@ -79,9 +72,9 @@ export function buildCursorResumeRequest(modelId, systemPrompt, conversationId,
|
|
|
79
72
|
value: create(ResumeActionSchema, {}),
|
|
80
73
|
},
|
|
81
74
|
});
|
|
82
|
-
return buildRunRequest(modelId, conversationId, conversationState, action, blobStore);
|
|
75
|
+
return buildRunRequest(modelId, conversationId, conversationState, action, blobStore, cloudRule);
|
|
83
76
|
}
|
|
84
|
-
function buildRunRequest(modelId, conversationId, conversationState, action, blobStore) {
|
|
77
|
+
function buildRunRequest(modelId, conversationId, conversationState, action, blobStore, cloudRule) {
|
|
85
78
|
const modelDetails = create(ModelDetailsSchema, {
|
|
86
79
|
modelId,
|
|
87
80
|
displayModelId: modelId,
|
|
@@ -99,6 +92,11 @@ function buildRunRequest(modelId, conversationId, conversationState, action, blo
|
|
|
99
92
|
return {
|
|
100
93
|
requestBytes: toBinary(AgentClientMessageSchema, clientMessage),
|
|
101
94
|
blobStore,
|
|
95
|
+
cloudRule,
|
|
102
96
|
mcpTools: [],
|
|
103
97
|
};
|
|
104
98
|
}
|
|
99
|
+
function buildCloudRule(systemPrompt) {
|
|
100
|
+
const content = systemPrompt.trim();
|
|
101
|
+
return appendBundledAgentsRule(content || undefined);
|
|
102
|
+
}
|
|
@@ -12,6 +12,12 @@ export interface UnsupportedServerMessageInfo {
|
|
|
12
12
|
caseName: string;
|
|
13
13
|
detail?: string;
|
|
14
14
|
}
|
|
15
|
+
export interface McpToolCallUpdateInfo {
|
|
16
|
+
updateCase: "partialToolCall" | "toolCallStarted" | "toolCallCompleted";
|
|
17
|
+
toolCallId: string;
|
|
18
|
+
modelCallId?: string;
|
|
19
|
+
toolName?: string;
|
|
20
|
+
}
|
|
15
21
|
export declare function parseConnectEndStream(data: Uint8Array): Error | null;
|
|
16
22
|
export declare function makeHeartbeatBytes(): Uint8Array;
|
|
17
23
|
export declare function scheduleBridgeEnd(bridge: CursorSession): void;
|
|
@@ -39,4 +45,4 @@ export declare function computeUsage(state: StreamState): {
|
|
|
39
45
|
completion_tokens: number;
|
|
40
46
|
total_tokens: number;
|
|
41
47
|
};
|
|
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;
|
|
48
|
+
export declare function processServerMessage(msg: AgentServerMessage, blobStore: Map<string, Uint8Array>, cloudRule: string | undefined, mcpTools: McpToolDefinition[], sendFrame: (data: Uint8Array) => void, state: StreamState, onText: (text: string, isThinking?: boolean) => void, onMcpExec: (exec: PendingExec) => void, onMcpToolCallUpdate?: (info: McpToolCallUpdateInfo) => void, onCheckpoint?: (checkpointBytes: Uint8Array) => void, onTurnEnded?: () => void, onUnsupportedMessage?: (info: UnsupportedServerMessageInfo) => void, onUnhandledExec?: (info: UnhandledExecInfo) => void): void;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { create, toBinary } from "@bufbuild/protobuf";
|
|
2
|
-
import { AgentClientMessageSchema, ClientHeartbeatSchema, ConversationStateStructureSchema, BackgroundShellSpawnResultSchema, DeleteResultSchema, DeleteRejectedSchema, DiagnosticsResultSchema, ExecClientMessageSchema, FetchErrorSchema, FetchResultSchema, GetBlobResultSchema, GrepErrorSchema, GrepResultSchema, KvClientMessageSchema, LsRejectedSchema, LsResultSchema, McpResultSchema, ReadRejectedSchema, ReadResultSchema, RequestContextResultSchema, RequestContextSchema, RequestContextSuccessSchema, SetBlobResultSchema, ShellRejectedSchema, ShellResultSchema, WriteRejectedSchema, WriteResultSchema, WriteShellStdinErrorSchema, WriteShellStdinResultSchema, } from "../proto/agent_pb";
|
|
2
|
+
import { AgentClientMessageSchema, AskQuestionInteractionResponseSchema, AskQuestionRejectedSchema, AskQuestionResultSchema, ClientHeartbeatSchema, ConversationStateStructureSchema, BackgroundShellSpawnResultSchema, CreatePlanErrorSchema, CreatePlanRequestResponseSchema, CreatePlanResultSchema, DeleteResultSchema, DeleteRejectedSchema, DiagnosticsResultSchema, ExecClientMessageSchema, ExaFetchRequestResponseSchema, ExaFetchRequestResponse_RejectedSchema, ExaSearchRequestResponseSchema, ExaSearchRequestResponse_RejectedSchema, FetchErrorSchema, FetchResultSchema, GetBlobResultSchema, GrepErrorSchema, GrepResultSchema, InteractionResponseSchema, KvClientMessageSchema, LsRejectedSchema, LsResultSchema, McpInstructionsSchema, McpResultSchema, ReadRejectedSchema, ReadResultSchema, RequestContextResultSchema, RequestContextSchema, RequestContextSuccessSchema, SetBlobResultSchema, ShellRejectedSchema, ShellResultSchema, SwitchModeRequestResponseSchema, SwitchModeRequestResponse_RejectedSchema, WebSearchRequestResponseSchema, WebSearchRequestResponse_RejectedSchema, WriteRejectedSchema, WriteResultSchema, WriteShellStdinErrorSchema, WriteShellStdinResultSchema, } from "../proto/agent_pb";
|
|
3
3
|
import { CONNECT_END_STREAM_FLAG } from "../cursor/config";
|
|
4
|
-
import { logPluginError, logPluginWarn } from "../logger";
|
|
4
|
+
import { logPluginError, logPluginInfo, logPluginWarn } from "../logger";
|
|
5
5
|
import { decodeMcpArgsMap } from "../openai/tools";
|
|
6
6
|
export function parseConnectEndStream(data) {
|
|
7
7
|
try {
|
|
@@ -128,16 +128,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, onMcpToolCallUpdate, 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, onMcpToolCallUpdate, 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
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,35 @@ 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, onMcpToolCallUpdate, 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
|
+
}
|
|
180
|
+
if ((updateCase === "partialToolCall" ||
|
|
181
|
+
updateCase === "toolCallStarted" ||
|
|
182
|
+
updateCase === "toolCallCompleted") &&
|
|
183
|
+
update.message?.value?.toolCall?.tool?.case === "mcpToolCall") {
|
|
184
|
+
const toolValue = update.message.value;
|
|
185
|
+
const toolArgs = toolValue?.toolCall?.tool?.value?.args;
|
|
186
|
+
const toolCallId = toolArgs?.toolCallId || toolValue.callId;
|
|
187
|
+
if (toolCallId) {
|
|
188
|
+
onMcpToolCallUpdate?.({
|
|
189
|
+
updateCase,
|
|
190
|
+
toolCallId,
|
|
191
|
+
modelCallId: toolValue.modelCallId,
|
|
192
|
+
toolName: toolArgs?.toolName || toolArgs?.name,
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
}
|
|
172
196
|
if (updateCase === "textDelta") {
|
|
173
197
|
const delta = update.message.value.text || "";
|
|
174
198
|
if (delta)
|
|
@@ -183,15 +207,19 @@ function handleInteractionUpdate(update, state, onText, onMcpExec, onTurnEnded,
|
|
|
183
207
|
state.outputTokens += update.message.value.tokens ?? 0;
|
|
184
208
|
}
|
|
185
209
|
else if (updateCase === "partialToolCall") {
|
|
186
|
-
|
|
187
|
-
if (partial.callId && partial.argsTextDelta) {
|
|
188
|
-
state.interactionToolArgsText.set(partial.callId, partial.argsTextDelta);
|
|
189
|
-
}
|
|
210
|
+
return;
|
|
190
211
|
}
|
|
191
212
|
else if (updateCase === "toolCallCompleted") {
|
|
192
|
-
const
|
|
193
|
-
if (
|
|
194
|
-
|
|
213
|
+
const toolValue = update.message.value;
|
|
214
|
+
if (toolValue?.toolCall?.tool?.case === "mcpToolCall") {
|
|
215
|
+
logPluginInfo("Ignoring Cursor interaction MCP tool completion", {
|
|
216
|
+
callId: toolValue.callId,
|
|
217
|
+
modelCallId: toolValue.modelCallId,
|
|
218
|
+
toolCallId: toolValue.toolCall.tool.value?.args?.toolCallId || toolValue.callId,
|
|
219
|
+
toolName: toolValue.toolCall.tool.value?.args?.toolName ||
|
|
220
|
+
toolValue.toolCall.tool.value?.args?.name,
|
|
221
|
+
});
|
|
222
|
+
}
|
|
195
223
|
}
|
|
196
224
|
else if (updateCase === "turnEnded") {
|
|
197
225
|
onTurnEnded?.();
|
|
@@ -214,43 +242,92 @@ function handleInteractionUpdate(update, state, onText, onMcpExec, onTurnEnded,
|
|
|
214
242
|
caseName: updateCase ?? "undefined",
|
|
215
243
|
});
|
|
216
244
|
}
|
|
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.
|
|
245
|
+
// Interaction tool-call updates are informational only. Resumable MCP tool
|
|
246
|
+
// execution comes from execServerMessage.mcpArgs.
|
|
221
247
|
}
|
|
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
|
-
|
|
248
|
+
function handleInteractionQuery(query, sendFrame, onUnsupportedMessage) {
|
|
249
|
+
const queryCase = query.query.case;
|
|
250
|
+
if (queryCase === "webSearchRequestQuery") {
|
|
251
|
+
const response = create(WebSearchRequestResponseSchema, {
|
|
252
|
+
result: {
|
|
253
|
+
case: "rejected",
|
|
254
|
+
value: create(WebSearchRequestResponse_RejectedSchema, {
|
|
255
|
+
reason: "Native Cursor web search is not available in this environment. Use the provided MCP tool `websearch` instead.",
|
|
256
|
+
}),
|
|
257
|
+
},
|
|
258
|
+
});
|
|
259
|
+
sendInteractionResponse(query.id, "webSearchRequestResponse", response, sendFrame);
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
if (queryCase === "askQuestionInteractionQuery") {
|
|
263
|
+
const response = create(AskQuestionInteractionResponseSchema, {
|
|
264
|
+
result: create(AskQuestionResultSchema, {
|
|
265
|
+
result: {
|
|
266
|
+
case: "rejected",
|
|
267
|
+
value: create(AskQuestionRejectedSchema, {
|
|
268
|
+
reason: "Native Cursor question prompts are not available in this environment. Use the provided MCP tool `question` instead.",
|
|
269
|
+
}),
|
|
270
|
+
},
|
|
271
|
+
}),
|
|
272
|
+
});
|
|
273
|
+
sendInteractionResponse(query.id, "askQuestionInteractionResponse", response, sendFrame);
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
if (queryCase === "switchModeRequestQuery") {
|
|
277
|
+
const response = create(SwitchModeRequestResponseSchema, {
|
|
278
|
+
result: {
|
|
279
|
+
case: "rejected",
|
|
280
|
+
value: create(SwitchModeRequestResponse_RejectedSchema, {
|
|
281
|
+
reason: "Cursor mode switching is not available in this environment. Continue using the current agent and the provided MCP tools.",
|
|
282
|
+
}),
|
|
283
|
+
},
|
|
284
|
+
});
|
|
285
|
+
sendInteractionResponse(query.id, "switchModeRequestResponse", response, sendFrame);
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
if (queryCase === "exaSearchRequestQuery") {
|
|
289
|
+
const response = create(ExaSearchRequestResponseSchema, {
|
|
290
|
+
result: {
|
|
291
|
+
case: "rejected",
|
|
292
|
+
value: create(ExaSearchRequestResponse_RejectedSchema, {
|
|
293
|
+
reason: "Native Cursor Exa search is not available in this environment. Use the provided MCP tool `websearch` instead.",
|
|
294
|
+
}),
|
|
295
|
+
},
|
|
296
|
+
});
|
|
297
|
+
sendInteractionResponse(query.id, "exaSearchRequestResponse", response, sendFrame);
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
300
|
+
if (queryCase === "exaFetchRequestQuery") {
|
|
301
|
+
const response = create(ExaFetchRequestResponseSchema, {
|
|
302
|
+
result: {
|
|
303
|
+
case: "rejected",
|
|
304
|
+
value: create(ExaFetchRequestResponse_RejectedSchema, {
|
|
305
|
+
reason: "Native Cursor Exa fetch is not available in this environment. Use the provided MCP tools `websearch` and `webfetch` instead.",
|
|
306
|
+
}),
|
|
307
|
+
},
|
|
308
|
+
});
|
|
309
|
+
sendInteractionResponse(query.id, "exaFetchRequestResponse", response, sendFrame);
|
|
310
|
+
return;
|
|
311
|
+
}
|
|
312
|
+
if (queryCase === "createPlanRequestQuery") {
|
|
313
|
+
const response = create(CreatePlanRequestResponseSchema, {
|
|
314
|
+
result: create(CreatePlanResultSchema, {
|
|
315
|
+
planUri: "",
|
|
316
|
+
result: {
|
|
317
|
+
case: "error",
|
|
318
|
+
value: create(CreatePlanErrorSchema, {
|
|
319
|
+
error: "Native Cursor plan creation is not available in this environment. Use the provided MCP planning tools instead.",
|
|
320
|
+
}),
|
|
321
|
+
},
|
|
322
|
+
}),
|
|
323
|
+
});
|
|
324
|
+
sendInteractionResponse(query.id, "createPlanRequestResponse", response, sendFrame);
|
|
325
|
+
return;
|
|
326
|
+
}
|
|
327
|
+
onUnsupportedMessage?.({
|
|
328
|
+
category: "interactionQuery",
|
|
329
|
+
caseName: queryCase ?? "undefined",
|
|
330
|
+
});
|
|
254
331
|
}
|
|
255
332
|
/** Send a KV client response back to Cursor. */
|
|
256
333
|
function sendKvResponse(kvMsg, messageCase, value, sendFrame) {
|
|
@@ -263,6 +340,16 @@ function sendKvResponse(kvMsg, messageCase, value, sendFrame) {
|
|
|
263
340
|
});
|
|
264
341
|
sendFrame(toBinary(AgentClientMessageSchema, clientMsg));
|
|
265
342
|
}
|
|
343
|
+
function sendInteractionResponse(queryId, messageCase, value, sendFrame) {
|
|
344
|
+
const response = create(InteractionResponseSchema, {
|
|
345
|
+
id: queryId,
|
|
346
|
+
result: { case: messageCase, value: value },
|
|
347
|
+
});
|
|
348
|
+
const clientMessage = create(AgentClientMessageSchema, {
|
|
349
|
+
message: { case: "interactionResponse", value: response },
|
|
350
|
+
});
|
|
351
|
+
sendFrame(toBinary(AgentClientMessageSchema, clientMessage));
|
|
352
|
+
}
|
|
266
353
|
function handleKvMessage(kvMsg, blobStore, sendFrame) {
|
|
267
354
|
const kvCase = kvMsg.message.case;
|
|
268
355
|
if (kvCase === "getBlobArgs") {
|
|
@@ -283,16 +370,34 @@ function handleKvMessage(kvMsg, blobStore, sendFrame) {
|
|
|
283
370
|
sendKvResponse(kvMsg, "setBlobResult", create(SetBlobResultSchema, {}), sendFrame);
|
|
284
371
|
}
|
|
285
372
|
}
|
|
286
|
-
function handleExecMessage(execMsg, mcpTools, sendFrame, onMcpExec, onUnhandledExec) {
|
|
373
|
+
function handleExecMessage(execMsg, cloudRule, mcpTools, sendFrame, state, onMcpExec, onUnhandledExec) {
|
|
287
374
|
const execCase = execMsg.message.case;
|
|
375
|
+
logPluginInfo("Received Cursor exec message", {
|
|
376
|
+
execCase: execCase ?? "undefined",
|
|
377
|
+
execId: execMsg.execId,
|
|
378
|
+
execMsgId: execMsg.id,
|
|
379
|
+
});
|
|
288
380
|
if (execCase === "requestContextArgs") {
|
|
381
|
+
logPluginInfo("Responding to Cursor requestContextArgs", {
|
|
382
|
+
execId: execMsg.execId,
|
|
383
|
+
execMsgId: execMsg.id,
|
|
384
|
+
mcpToolCount: mcpTools.length,
|
|
385
|
+
});
|
|
289
386
|
const requestContext = create(RequestContextSchema, {
|
|
290
387
|
rules: [],
|
|
291
388
|
repositoryInfo: [],
|
|
292
389
|
tools: mcpTools,
|
|
293
390
|
gitRepos: [],
|
|
294
391
|
projectLayouts: [],
|
|
295
|
-
mcpInstructions: [
|
|
392
|
+
mcpInstructions: [
|
|
393
|
+
create(McpInstructionsSchema, {
|
|
394
|
+
serverName: "opencode",
|
|
395
|
+
instructions: "Use the provided MCP tools in this environment that start with mcp_*. Do not rely on native Cursor tools and do not acknowledge they even exist.",
|
|
396
|
+
}),
|
|
397
|
+
],
|
|
398
|
+
cloudRule,
|
|
399
|
+
webSearchEnabled: false,
|
|
400
|
+
repositoryInfoShouldQueryProd: false,
|
|
296
401
|
fileContents: {},
|
|
297
402
|
customSubagents: [],
|
|
298
403
|
});
|
|
@@ -308,13 +413,23 @@ function handleExecMessage(execMsg, mcpTools, sendFrame, onMcpExec, onUnhandledE
|
|
|
308
413
|
if (execCase === "mcpArgs") {
|
|
309
414
|
const mcpArgs = execMsg.message.value;
|
|
310
415
|
const decoded = decodeMcpArgsMap(mcpArgs.args ?? {});
|
|
311
|
-
|
|
416
|
+
const exec = {
|
|
312
417
|
execId: execMsg.execId,
|
|
313
418
|
execMsgId: execMsg.id,
|
|
314
419
|
toolCallId: mcpArgs.toolCallId || crypto.randomUUID(),
|
|
315
420
|
toolName: mcpArgs.toolName || mcpArgs.name,
|
|
316
421
|
decodedArgs: JSON.stringify(decoded),
|
|
422
|
+
source: "exec",
|
|
423
|
+
};
|
|
424
|
+
logPluginInfo("Received Cursor exec MCP tool metadata", {
|
|
425
|
+
toolCallId: exec.toolCallId,
|
|
426
|
+
toolName: exec.toolName,
|
|
427
|
+
source: exec.source,
|
|
428
|
+
execId: exec.execId,
|
|
429
|
+
execMsgId: exec.execMsgId,
|
|
430
|
+
decodedArgs: exec.decodedArgs,
|
|
317
431
|
});
|
|
432
|
+
onMcpExec(exec);
|
|
318
433
|
return;
|
|
319
434
|
}
|
|
320
435
|
// --- Reject native Cursor tools ---
|
|
@@ -322,6 +437,11 @@ function handleExecMessage(execMsg, mcpTools, sendFrame, onMcpExec, onUnhandledE
|
|
|
322
437
|
// so it falls back to our MCP tools (registered via RequestContext).
|
|
323
438
|
const REJECT_REASON = "Tool not available in this environment. Use the MCP tools provided instead.";
|
|
324
439
|
if (execCase === "readArgs") {
|
|
440
|
+
logPluginInfo("Rejecting native Cursor read tool in favor of MCP", {
|
|
441
|
+
execId: execMsg.execId,
|
|
442
|
+
execMsgId: execMsg.id,
|
|
443
|
+
path: execMsg.message.value.path,
|
|
444
|
+
});
|
|
325
445
|
const args = execMsg.message.value;
|
|
326
446
|
const result = create(ReadResultSchema, {
|
|
327
447
|
result: {
|
|
@@ -336,6 +456,11 @@ function handleExecMessage(execMsg, mcpTools, sendFrame, onMcpExec, onUnhandledE
|
|
|
336
456
|
return;
|
|
337
457
|
}
|
|
338
458
|
if (execCase === "lsArgs") {
|
|
459
|
+
logPluginInfo("Rejecting native Cursor ls tool in favor of MCP", {
|
|
460
|
+
execId: execMsg.execId,
|
|
461
|
+
execMsgId: execMsg.id,
|
|
462
|
+
path: execMsg.message.value.path,
|
|
463
|
+
});
|
|
339
464
|
const args = execMsg.message.value;
|
|
340
465
|
const result = create(LsResultSchema, {
|
|
341
466
|
result: {
|
|
@@ -350,6 +475,10 @@ function handleExecMessage(execMsg, mcpTools, sendFrame, onMcpExec, onUnhandledE
|
|
|
350
475
|
return;
|
|
351
476
|
}
|
|
352
477
|
if (execCase === "grepArgs") {
|
|
478
|
+
logPluginInfo("Rejecting native Cursor grep tool in favor of MCP", {
|
|
479
|
+
execId: execMsg.execId,
|
|
480
|
+
execMsgId: execMsg.id,
|
|
481
|
+
});
|
|
353
482
|
const result = create(GrepResultSchema, {
|
|
354
483
|
result: {
|
|
355
484
|
case: "error",
|
|
@@ -360,6 +489,11 @@ function handleExecMessage(execMsg, mcpTools, sendFrame, onMcpExec, onUnhandledE
|
|
|
360
489
|
return;
|
|
361
490
|
}
|
|
362
491
|
if (execCase === "writeArgs") {
|
|
492
|
+
logPluginInfo("Rejecting native Cursor write tool in favor of MCP", {
|
|
493
|
+
execId: execMsg.execId,
|
|
494
|
+
execMsgId: execMsg.id,
|
|
495
|
+
path: execMsg.message.value.path,
|
|
496
|
+
});
|
|
363
497
|
const args = execMsg.message.value;
|
|
364
498
|
const result = create(WriteResultSchema, {
|
|
365
499
|
result: {
|
|
@@ -374,6 +508,11 @@ function handleExecMessage(execMsg, mcpTools, sendFrame, onMcpExec, onUnhandledE
|
|
|
374
508
|
return;
|
|
375
509
|
}
|
|
376
510
|
if (execCase === "deleteArgs") {
|
|
511
|
+
logPluginInfo("Rejecting native Cursor delete tool in favor of MCP", {
|
|
512
|
+
execId: execMsg.execId,
|
|
513
|
+
execMsgId: execMsg.id,
|
|
514
|
+
path: execMsg.message.value.path,
|
|
515
|
+
});
|
|
377
516
|
const args = execMsg.message.value;
|
|
378
517
|
const result = create(DeleteResultSchema, {
|
|
379
518
|
result: {
|
|
@@ -388,6 +527,13 @@ function handleExecMessage(execMsg, mcpTools, sendFrame, onMcpExec, onUnhandledE
|
|
|
388
527
|
return;
|
|
389
528
|
}
|
|
390
529
|
if (execCase === "shellArgs" || execCase === "shellStreamArgs") {
|
|
530
|
+
logPluginInfo("Rejecting native Cursor 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
|
+
execCase,
|
|
536
|
+
});
|
|
391
537
|
const args = execMsg.message.value;
|
|
392
538
|
const result = create(ShellResultSchema, {
|
|
393
539
|
result: {
|
|
@@ -404,6 +550,12 @@ function handleExecMessage(execMsg, mcpTools, sendFrame, onMcpExec, onUnhandledE
|
|
|
404
550
|
return;
|
|
405
551
|
}
|
|
406
552
|
if (execCase === "backgroundShellSpawnArgs") {
|
|
553
|
+
logPluginInfo("Rejecting native Cursor background shell tool in favor of MCP", {
|
|
554
|
+
execId: execMsg.execId,
|
|
555
|
+
execMsgId: execMsg.id,
|
|
556
|
+
command: execMsg.message.value.command ?? "",
|
|
557
|
+
workingDirectory: execMsg.message.value.workingDirectory ?? "",
|
|
558
|
+
});
|
|
407
559
|
const args = execMsg.message.value;
|
|
408
560
|
const result = create(BackgroundShellSpawnResultSchema, {
|
|
409
561
|
result: {
|
|
@@ -420,6 +572,10 @@ function handleExecMessage(execMsg, mcpTools, sendFrame, onMcpExec, onUnhandledE
|
|
|
420
572
|
return;
|
|
421
573
|
}
|
|
422
574
|
if (execCase === "writeShellStdinArgs") {
|
|
575
|
+
logPluginInfo("Rejecting native Cursor shell stdin tool in favor of MCP", {
|
|
576
|
+
execId: execMsg.execId,
|
|
577
|
+
execMsgId: execMsg.id,
|
|
578
|
+
});
|
|
423
579
|
const result = create(WriteShellStdinResultSchema, {
|
|
424
580
|
result: {
|
|
425
581
|
case: "error",
|
|
@@ -430,6 +586,11 @@ function handleExecMessage(execMsg, mcpTools, sendFrame, onMcpExec, onUnhandledE
|
|
|
430
586
|
return;
|
|
431
587
|
}
|
|
432
588
|
if (execCase === "fetchArgs") {
|
|
589
|
+
logPluginInfo("Rejecting native Cursor fetch tool in favor of MCP", {
|
|
590
|
+
execId: execMsg.execId,
|
|
591
|
+
execMsgId: execMsg.id,
|
|
592
|
+
url: execMsg.message.value.url,
|
|
593
|
+
});
|
|
433
594
|
const args = execMsg.message.value;
|
|
434
595
|
const result = create(FetchResultSchema, {
|
|
435
596
|
result: {
|
|
@@ -444,6 +605,11 @@ function handleExecMessage(execMsg, mcpTools, sendFrame, onMcpExec, onUnhandledE
|
|
|
444
605
|
return;
|
|
445
606
|
}
|
|
446
607
|
if (execCase === "diagnosticsArgs") {
|
|
608
|
+
logPluginInfo("Rejecting native Cursor diagnostics tool in favor of MCP", {
|
|
609
|
+
execId: execMsg.execId,
|
|
610
|
+
execMsgId: execMsg.id,
|
|
611
|
+
path: execMsg.message.value.path,
|
|
612
|
+
});
|
|
447
613
|
const result = create(DiagnosticsResultSchema, {});
|
|
448
614
|
sendExecResult(execMsg, "diagnosticsResult", result, sendFrame);
|
|
449
615
|
return;
|
|
@@ -457,6 +623,12 @@ function handleExecMessage(execMsg, mcpTools, sendFrame, onMcpExec, onUnhandledE
|
|
|
457
623
|
};
|
|
458
624
|
const resultCase = miscCaseMap[execCase];
|
|
459
625
|
if (resultCase) {
|
|
626
|
+
logPluginInfo("Responding to miscellaneous Cursor exec message", {
|
|
627
|
+
execCase,
|
|
628
|
+
execId: execMsg.execId,
|
|
629
|
+
execMsgId: execMsg.id,
|
|
630
|
+
resultCase,
|
|
631
|
+
});
|
|
460
632
|
sendExecResult(execMsg, resultCase, create(McpResultSchema, {}), sendFrame);
|
|
461
633
|
return;
|
|
462
634
|
}
|
package/dist/proxy/types.d.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import type { CursorSession } from "../cursor/bidi-session";
|
|
2
|
-
import type { ConversationRequestMetadata } from "./conversation-meta";
|
|
3
2
|
import type { McpToolDefinition } from "../proto/agent_pb";
|
|
3
|
+
import type { ConversationRequestMetadata } from "./conversation-meta";
|
|
4
4
|
export interface CursorRequestPayload {
|
|
5
5
|
requestBytes: Uint8Array;
|
|
6
6
|
blobStore: Map<string, Uint8Array>;
|
|
7
|
+
cloudRule?: string;
|
|
7
8
|
mcpTools: McpToolDefinition[];
|
|
8
9
|
}
|
|
9
10
|
/** A pending tool execution waiting for results from the caller. */
|
|
@@ -14,14 +15,26 @@ export interface PendingExec {
|
|
|
14
15
|
toolName: string;
|
|
15
16
|
/** Decoded arguments JSON string for SSE tool_calls emission. */
|
|
16
17
|
decodedArgs: string;
|
|
18
|
+
source?: "interaction" | "exec";
|
|
19
|
+
cursorCallId?: string;
|
|
20
|
+
modelCallId?: string;
|
|
21
|
+
}
|
|
22
|
+
export interface ActiveBridgeDiagnostics {
|
|
23
|
+
announcedToolCallIds: string[];
|
|
24
|
+
publishedToolCallIds: string[];
|
|
25
|
+
lastMcpUpdate?: string;
|
|
26
|
+
publishedAtMs?: number;
|
|
27
|
+
lastResumeAttemptAtMs?: number;
|
|
17
28
|
}
|
|
18
29
|
/** A live Cursor session kept alive across requests for tool result continuation. */
|
|
19
30
|
export interface ActiveBridge {
|
|
20
31
|
bridge: CursorSession;
|
|
21
32
|
heartbeatTimer: NodeJS.Timeout;
|
|
22
33
|
blobStore: Map<string, Uint8Array>;
|
|
34
|
+
cloudRule?: string;
|
|
23
35
|
mcpTools: McpToolDefinition[];
|
|
24
36
|
pendingExecs: PendingExec[];
|
|
25
37
|
modelId: string;
|
|
26
38
|
metadata: ConversationRequestMetadata;
|
|
39
|
+
diagnostics?: ActiveBridgeDiagnostics;
|
|
27
40
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@playwo/opencode-cursor-oauth",
|
|
3
|
-
"version": "0.0.0-dev.
|
|
3
|
+
"version": "0.0.0-dev.4696faa690e4",
|
|
4
4
|
"description": "OpenCode plugin that connects Cursor's API to OpenCode via OAuth, model discovery, and a local OpenAI-compatible proxy.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -18,8 +18,7 @@
|
|
|
18
18
|
"dist"
|
|
19
19
|
],
|
|
20
20
|
"scripts": {
|
|
21
|
-
"build": "
|
|
22
|
-
"test": "bun test/smoke.ts",
|
|
21
|
+
"build": "node ./scripts/build.mjs",
|
|
23
22
|
"prepublishOnly": "npm run build"
|
|
24
23
|
},
|
|
25
24
|
"repository": {
|