@playwo/opencode-cursor-oauth 0.0.0-dev.4463bb589222 → 0.0.0-dev.628837adf8c9
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 +1 -3
- package/dist/proxy/bridge-streaming.d.ts +1 -1
- package/dist/proxy/bridge-streaming.js +95 -10
- package/dist/proxy/chat-completion.js +40 -1
- package/dist/proxy/cursor-request.js +28 -15
- package/dist/proxy/stream-dispatch.d.ts +2 -2
- package/dist/proxy/stream-dispatch.js +109 -52
- 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
|
}
|
|
@@ -38,14 +38,12 @@ 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) => {
|
|
46
44
|
try {
|
|
47
45
|
const serverMessage = fromBinary(AgentServerMessageSchema, messageBytes);
|
|
48
|
-
processServerMessage(serverMessage, payload.blobStore, payload.mcpTools, (data) => bridge.write(data), state, (text, isThinking) => {
|
|
46
|
+
processServerMessage(serverMessage, payload.blobStore, payload.rules, payload.mcpTools, (data) => bridge.write(data), state, (text, isThinking) => {
|
|
49
47
|
if (isThinking)
|
|
50
48
|
return;
|
|
51
49
|
const { content } = tagFilter.process(text);
|
|
@@ -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";
|
|
@@ -8,7 +8,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
10
|
const SSE_KEEPALIVE_INTERVAL_MS = 15_000;
|
|
11
|
-
function createBridgeStreamResponse(bridge, heartbeatTimer, blobStore, mcpTools, modelId, bridgeKey, convKey, metadata) {
|
|
11
|
+
function createBridgeStreamResponse(bridge, heartbeatTimer, blobStore, rules, mcpTools, modelId, bridgeKey, convKey, metadata) {
|
|
12
12
|
const completionId = `chatcmpl-${crypto.randomUUID().replace(/-/g, "").slice(0, 28)}`;
|
|
13
13
|
const created = Math.floor(Date.now() / 1000);
|
|
14
14
|
let keepaliveTimer;
|
|
@@ -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 ?? "";
|
|
@@ -90,7 +88,7 @@ function createBridgeStreamResponse(bridge, heartbeatTimer, blobStore, mcpTools,
|
|
|
90
88
|
const processChunk = createConnectFrameParser((messageBytes) => {
|
|
91
89
|
try {
|
|
92
90
|
const serverMessage = fromBinary(AgentServerMessageSchema, messageBytes);
|
|
93
|
-
processServerMessage(serverMessage, blobStore, mcpTools, (data) => bridge.write(data), state, (text, isThinking) => {
|
|
91
|
+
processServerMessage(serverMessage, blobStore, rules, mcpTools, (data) => bridge.write(data), state, (text, isThinking) => {
|
|
94
92
|
if (isThinking) {
|
|
95
93
|
sendSSE(makeChunk({ reasoning_content: text }));
|
|
96
94
|
return;
|
|
@@ -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)
|
|
@@ -142,6 +146,7 @@ function createBridgeStreamResponse(bridge, heartbeatTimer, blobStore, mcpTools,
|
|
|
142
146
|
bridge,
|
|
143
147
|
heartbeatTimer,
|
|
144
148
|
blobStore,
|
|
149
|
+
rules,
|
|
145
150
|
mcpTools,
|
|
146
151
|
pendingExecs: state.pendingExecs,
|
|
147
152
|
modelId,
|
|
@@ -206,8 +211,23 @@ function createBridgeStreamResponse(bridge, heartbeatTimer, blobStore, mcpTools,
|
|
|
206
211
|
stopKeepalive();
|
|
207
212
|
}
|
|
208
213
|
}, SSE_KEEPALIVE_INTERVAL_MS);
|
|
214
|
+
logPluginInfo("Opened Cursor streaming bridge", {
|
|
215
|
+
modelId,
|
|
216
|
+
bridgeKey,
|
|
217
|
+
convKey,
|
|
218
|
+
mcpToolCount: mcpTools.length,
|
|
219
|
+
ruleCount: rules.length,
|
|
220
|
+
});
|
|
209
221
|
bridge.onData(processChunk);
|
|
210
222
|
bridge.onClose((code) => {
|
|
223
|
+
logPluginInfo("Cursor streaming bridge closed", {
|
|
224
|
+
modelId,
|
|
225
|
+
bridgeKey,
|
|
226
|
+
convKey,
|
|
227
|
+
code,
|
|
228
|
+
mcpExecReceived,
|
|
229
|
+
hadEndStreamError: Boolean(endStreamError),
|
|
230
|
+
});
|
|
211
231
|
clearInterval(heartbeatTimer);
|
|
212
232
|
stopKeepalive();
|
|
213
233
|
syncStoredBlobStore(convKey, blobStore);
|
|
@@ -257,11 +277,36 @@ function createBridgeStreamResponse(bridge, heartbeatTimer, blobStore, mcpTools,
|
|
|
257
277
|
return new Response(stream, { headers: SSE_HEADERS });
|
|
258
278
|
}
|
|
259
279
|
export async function handleStreamingResponse(payload, accessToken, modelId, bridgeKey, convKey, metadata) {
|
|
280
|
+
logPluginInfo("Starting Cursor streaming response", {
|
|
281
|
+
modelId,
|
|
282
|
+
bridgeKey,
|
|
283
|
+
convKey,
|
|
284
|
+
mcpToolCount: payload.mcpTools.length,
|
|
285
|
+
});
|
|
260
286
|
const { bridge, heartbeatTimer } = await startBridge(accessToken, payload.requestBytes);
|
|
261
|
-
return createBridgeStreamResponse(bridge, heartbeatTimer, payload.blobStore, payload.mcpTools, modelId, bridgeKey, convKey, metadata);
|
|
287
|
+
return createBridgeStreamResponse(bridge, heartbeatTimer, payload.blobStore, payload.rules, payload.mcpTools, modelId, bridgeKey, convKey, metadata);
|
|
288
|
+
}
|
|
289
|
+
async function waitForResolvablePendingExecs(active, toolResults, timeoutMs = 2_000) {
|
|
290
|
+
const pendingToolCallIds = new Set(toolResults.map((result) => result.toolCallId));
|
|
291
|
+
const deadline = Date.now() + timeoutMs;
|
|
292
|
+
while (Date.now() < deadline) {
|
|
293
|
+
const unresolved = active.pendingExecs.filter((exec) => pendingToolCallIds.has(exec.toolCallId) && exec.execMsgId === 0);
|
|
294
|
+
if (unresolved.length === 0) {
|
|
295
|
+
return unresolved;
|
|
296
|
+
}
|
|
297
|
+
await new Promise((resolve) => setTimeout(resolve, 25));
|
|
298
|
+
}
|
|
299
|
+
const unresolved = active.pendingExecs.filter((exec) => pendingToolCallIds.has(exec.toolCallId) && exec.execMsgId === 0);
|
|
300
|
+
if (unresolved.length > 0) {
|
|
301
|
+
logPluginWarn("Cursor exec metadata did not arrive before tool-result resume", {
|
|
302
|
+
bridgeToolCallIds: unresolved.map((exec) => exec.toolCallId),
|
|
303
|
+
modelId: active.modelId,
|
|
304
|
+
});
|
|
305
|
+
}
|
|
306
|
+
return unresolved;
|
|
262
307
|
}
|
|
263
|
-
export function handleToolResultResume(active, toolResults, bridgeKey, convKey) {
|
|
264
|
-
const { bridge, heartbeatTimer, blobStore, mcpTools, pendingExecs, modelId, metadata, } = active;
|
|
308
|
+
export async function handleToolResultResume(active, toolResults, bridgeKey, convKey) {
|
|
309
|
+
const { bridge, heartbeatTimer, blobStore, rules, mcpTools, pendingExecs, modelId, metadata, } = active;
|
|
265
310
|
const resumeMetadata = {
|
|
266
311
|
...metadata,
|
|
267
312
|
assistantSeedText: [
|
|
@@ -271,6 +316,33 @@ export function handleToolResultResume(active, toolResults, bridgeKey, convKey)
|
|
|
271
316
|
.filter(Boolean)
|
|
272
317
|
.join("\n\n"),
|
|
273
318
|
};
|
|
319
|
+
logPluginInfo("Preparing Cursor tool-result resume", {
|
|
320
|
+
bridgeKey,
|
|
321
|
+
convKey,
|
|
322
|
+
modelId,
|
|
323
|
+
toolResults,
|
|
324
|
+
pendingExecs,
|
|
325
|
+
});
|
|
326
|
+
const unresolved = await waitForResolvablePendingExecs(active, toolResults);
|
|
327
|
+
logPluginInfo("Resolved pending exec state before Cursor tool-result resume", {
|
|
328
|
+
bridgeKey,
|
|
329
|
+
convKey,
|
|
330
|
+
modelId,
|
|
331
|
+
toolResults,
|
|
332
|
+
pendingExecs,
|
|
333
|
+
unresolvedPendingExecs: unresolved,
|
|
334
|
+
});
|
|
335
|
+
if (unresolved.length > 0) {
|
|
336
|
+
clearInterval(heartbeatTimer);
|
|
337
|
+
bridge.end();
|
|
338
|
+
return new Response(JSON.stringify({
|
|
339
|
+
error: {
|
|
340
|
+
message: "Cursor requested a tool call but never provided resumable exec metadata. Aborting instead of retrying with synthetic ids.",
|
|
341
|
+
type: "invalid_request_error",
|
|
342
|
+
code: "cursor_missing_exec_metadata",
|
|
343
|
+
},
|
|
344
|
+
}), { status: 400, headers: { "Content-Type": "application/json" } });
|
|
345
|
+
}
|
|
274
346
|
for (const exec of pendingExecs) {
|
|
275
347
|
const result = toolResults.find((toolResult) => toolResult.toolCallId === exec.toolCallId);
|
|
276
348
|
const mcpResult = result
|
|
@@ -311,7 +383,20 @@ export function handleToolResultResume(active, toolResults, bridgeKey, convKey)
|
|
|
311
383
|
const clientMessage = create(AgentClientMessageSchema, {
|
|
312
384
|
message: { case: "execClientMessage", value: execClientMessage },
|
|
313
385
|
});
|
|
386
|
+
logPluginInfo("Sending Cursor tool-result resume message", {
|
|
387
|
+
bridgeKey,
|
|
388
|
+
convKey,
|
|
389
|
+
modelId,
|
|
390
|
+
toolCallId: exec.toolCallId,
|
|
391
|
+
toolName: exec.toolName,
|
|
392
|
+
source: exec.source,
|
|
393
|
+
execId: exec.execId,
|
|
394
|
+
execMsgId: exec.execMsgId,
|
|
395
|
+
cursorCallId: exec.cursorCallId,
|
|
396
|
+
modelCallId: exec.modelCallId,
|
|
397
|
+
matchedToolResult: result,
|
|
398
|
+
});
|
|
314
399
|
bridge.write(toBinary(AgentClientMessageSchema, clientMessage));
|
|
315
400
|
}
|
|
316
|
-
return createBridgeStreamResponse(bridge, heartbeatTimer, blobStore, mcpTools, modelId, bridgeKey, convKey, resumeMetadata);
|
|
401
|
+
return createBridgeStreamResponse(bridge, heartbeatTimer, blobStore, rules, mcpTools, modelId, bridgeKey, convKey, resumeMetadata);
|
|
317
402
|
}
|
|
@@ -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, CursorRuleSchema, CursorRuleTypeAgentFetchedSchema, CursorRuleTypeSchema, 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 rules = buildCursorRules(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, rules);
|
|
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 rules = buildCursorRules(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, rules);
|
|
83
75
|
}
|
|
84
|
-
function buildRunRequest(modelId, conversationId, conversationState, action, blobStore) {
|
|
76
|
+
function buildRunRequest(modelId, conversationId, conversationState, action, blobStore, rules) {
|
|
85
77
|
const modelDetails = create(ModelDetailsSchema, {
|
|
86
78
|
modelId,
|
|
87
79
|
displayModelId: modelId,
|
|
@@ -99,6 +91,27 @@ function buildRunRequest(modelId, conversationId, conversationState, action, blo
|
|
|
99
91
|
return {
|
|
100
92
|
requestBytes: toBinary(AgentClientMessageSchema, clientMessage),
|
|
101
93
|
blobStore,
|
|
94
|
+
rules,
|
|
102
95
|
mcpTools: [],
|
|
103
96
|
};
|
|
104
97
|
}
|
|
98
|
+
function buildCursorRules(systemPrompt) {
|
|
99
|
+
const content = systemPrompt.trim();
|
|
100
|
+
if (!content)
|
|
101
|
+
return [];
|
|
102
|
+
return [
|
|
103
|
+
create(CursorRuleSchema, {
|
|
104
|
+
fullPath: "/opencode/system-prompt.md",
|
|
105
|
+
content,
|
|
106
|
+
type: create(CursorRuleTypeSchema, {
|
|
107
|
+
type: {
|
|
108
|
+
case: "agentFetched",
|
|
109
|
+
value: create(CursorRuleTypeAgentFetchedSchema, {
|
|
110
|
+
description: "OpenCode system prompt",
|
|
111
|
+
}),
|
|
112
|
+
},
|
|
113
|
+
}),
|
|
114
|
+
source: 0,
|
|
115
|
+
}),
|
|
116
|
+
];
|
|
117
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type AgentServerMessage, type McpToolDefinition } from "../proto/agent_pb";
|
|
1
|
+
import { type AgentServerMessage, type CursorRule, type McpToolDefinition } from "../proto/agent_pb";
|
|
2
2
|
import type { CursorSession } from "../cursor/bidi-session";
|
|
3
3
|
import type { StreamState } from "./stream-state";
|
|
4
4
|
import type { PendingExec } from "./types";
|
|
@@ -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>, rules: CursorRule[], 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, rules, 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, rules, 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,15 +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
|
-
state.interactionToolArgsText.set(partial.callId, partial.argsTextDelta);
|
|
186
|
-
}
|
|
194
|
+
return;
|
|
187
195
|
}
|
|
188
196
|
else if (updateCase === "toolCallCompleted") {
|
|
189
|
-
const
|
|
190
|
-
if (
|
|
191
|
-
|
|
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
|
+
}
|
|
192
207
|
}
|
|
193
208
|
else if (updateCase === "turnEnded") {
|
|
194
209
|
onTurnEnded?.();
|
|
@@ -211,43 +226,8 @@ function handleInteractionUpdate(update, state, onText, onMcpExec, onTurnEnded,
|
|
|
211
226
|
caseName: updateCase ?? "undefined",
|
|
212
227
|
});
|
|
213
228
|
}
|
|
214
|
-
//
|
|
215
|
-
//
|
|
216
|
-
// calls may still appear here on some models, so we surface those, but we
|
|
217
|
-
// do not abort the bridge for native Cursor tool-call progress events.
|
|
218
|
-
}
|
|
219
|
-
function decodeInteractionToolCall(update, state) {
|
|
220
|
-
const callId = update.callId ?? "";
|
|
221
|
-
const toolCase = update.toolCall?.tool?.case;
|
|
222
|
-
if (toolCase !== "mcpToolCall")
|
|
223
|
-
return null;
|
|
224
|
-
const mcpArgs = update.toolCall?.tool?.value?.args;
|
|
225
|
-
if (!mcpArgs)
|
|
226
|
-
return null;
|
|
227
|
-
const toolCallId = mcpArgs.toolCallId || callId || crypto.randomUUID();
|
|
228
|
-
if (state.emittedToolCallIds.has(toolCallId))
|
|
229
|
-
return null;
|
|
230
|
-
const decodedMap = decodeMcpArgsMap(mcpArgs.args ?? {});
|
|
231
|
-
const partialArgsText = callId
|
|
232
|
-
? state.interactionToolArgsText.get(callId)?.trim()
|
|
233
|
-
: undefined;
|
|
234
|
-
let decodedArgs = "{}";
|
|
235
|
-
if (Object.keys(decodedMap).length > 0) {
|
|
236
|
-
decodedArgs = JSON.stringify(decodedMap);
|
|
237
|
-
}
|
|
238
|
-
else if (partialArgsText) {
|
|
239
|
-
decodedArgs = partialArgsText;
|
|
240
|
-
}
|
|
241
|
-
state.emittedToolCallIds.add(toolCallId);
|
|
242
|
-
if (callId)
|
|
243
|
-
state.interactionToolArgsText.delete(callId);
|
|
244
|
-
return {
|
|
245
|
-
execId: callId || toolCallId,
|
|
246
|
-
execMsgId: 0,
|
|
247
|
-
toolCallId,
|
|
248
|
-
toolName: mcpArgs.toolName || mcpArgs.name || "unknown_mcp_tool",
|
|
249
|
-
decodedArgs,
|
|
250
|
-
};
|
|
229
|
+
// Interaction tool-call updates are informational only. Resumable MCP tool
|
|
230
|
+
// execution comes from execServerMessage.mcpArgs.
|
|
251
231
|
}
|
|
252
232
|
function handleInteractionQuery(query, sendFrame, onUnsupportedMessage) {
|
|
253
233
|
const queryCase = query.query.case;
|
|
@@ -374,11 +354,21 @@ function handleKvMessage(kvMsg, blobStore, sendFrame) {
|
|
|
374
354
|
sendKvResponse(kvMsg, "setBlobResult", create(SetBlobResultSchema, {}), sendFrame);
|
|
375
355
|
}
|
|
376
356
|
}
|
|
377
|
-
function handleExecMessage(execMsg, mcpTools, sendFrame, onMcpExec, onUnhandledExec) {
|
|
357
|
+
function handleExecMessage(execMsg, rules, mcpTools, sendFrame, state, onMcpExec, onUnhandledExec) {
|
|
378
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
|
+
});
|
|
379
364
|
if (execCase === "requestContextArgs") {
|
|
365
|
+
logPluginInfo("Responding to Cursor requestContextArgs", {
|
|
366
|
+
execId: execMsg.execId,
|
|
367
|
+
execMsgId: execMsg.id,
|
|
368
|
+
mcpToolCount: mcpTools.length,
|
|
369
|
+
});
|
|
380
370
|
const requestContext = create(RequestContextSchema, {
|
|
381
|
-
rules
|
|
371
|
+
rules,
|
|
382
372
|
repositoryInfo: [],
|
|
383
373
|
tools: mcpTools,
|
|
384
374
|
gitRepos: [],
|
|
@@ -399,13 +389,23 @@ function handleExecMessage(execMsg, mcpTools, sendFrame, onMcpExec, onUnhandledE
|
|
|
399
389
|
if (execCase === "mcpArgs") {
|
|
400
390
|
const mcpArgs = execMsg.message.value;
|
|
401
391
|
const decoded = decodeMcpArgsMap(mcpArgs.args ?? {});
|
|
402
|
-
|
|
392
|
+
const exec = {
|
|
403
393
|
execId: execMsg.execId,
|
|
404
394
|
execMsgId: execMsg.id,
|
|
405
395
|
toolCallId: mcpArgs.toolCallId || crypto.randomUUID(),
|
|
406
396
|
toolName: mcpArgs.toolName || mcpArgs.name,
|
|
407
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,
|
|
408
407
|
});
|
|
408
|
+
onMcpExec(exec);
|
|
409
409
|
return;
|
|
410
410
|
}
|
|
411
411
|
// --- Reject native Cursor tools ---
|
|
@@ -413,6 +413,11 @@ function handleExecMessage(execMsg, mcpTools, sendFrame, onMcpExec, onUnhandledE
|
|
|
413
413
|
// so it falls back to our MCP tools (registered via RequestContext).
|
|
414
414
|
const REJECT_REASON = "Tool not available in this environment. Use the MCP tools provided instead.";
|
|
415
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
|
+
});
|
|
416
421
|
const args = execMsg.message.value;
|
|
417
422
|
const result = create(ReadResultSchema, {
|
|
418
423
|
result: {
|
|
@@ -427,6 +432,11 @@ function handleExecMessage(execMsg, mcpTools, sendFrame, onMcpExec, onUnhandledE
|
|
|
427
432
|
return;
|
|
428
433
|
}
|
|
429
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
|
+
});
|
|
430
440
|
const args = execMsg.message.value;
|
|
431
441
|
const result = create(LsResultSchema, {
|
|
432
442
|
result: {
|
|
@@ -441,6 +451,10 @@ function handleExecMessage(execMsg, mcpTools, sendFrame, onMcpExec, onUnhandledE
|
|
|
441
451
|
return;
|
|
442
452
|
}
|
|
443
453
|
if (execCase === "grepArgs") {
|
|
454
|
+
logPluginInfo("Rejecting native Cursor grep tool in favor of MCP", {
|
|
455
|
+
execId: execMsg.execId,
|
|
456
|
+
execMsgId: execMsg.id,
|
|
457
|
+
});
|
|
444
458
|
const result = create(GrepResultSchema, {
|
|
445
459
|
result: {
|
|
446
460
|
case: "error",
|
|
@@ -451,6 +465,11 @@ function handleExecMessage(execMsg, mcpTools, sendFrame, onMcpExec, onUnhandledE
|
|
|
451
465
|
return;
|
|
452
466
|
}
|
|
453
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
|
+
});
|
|
454
473
|
const args = execMsg.message.value;
|
|
455
474
|
const result = create(WriteResultSchema, {
|
|
456
475
|
result: {
|
|
@@ -465,6 +484,11 @@ function handleExecMessage(execMsg, mcpTools, sendFrame, onMcpExec, onUnhandledE
|
|
|
465
484
|
return;
|
|
466
485
|
}
|
|
467
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
|
+
});
|
|
468
492
|
const args = execMsg.message.value;
|
|
469
493
|
const result = create(DeleteResultSchema, {
|
|
470
494
|
result: {
|
|
@@ -479,6 +503,13 @@ function handleExecMessage(execMsg, mcpTools, sendFrame, onMcpExec, onUnhandledE
|
|
|
479
503
|
return;
|
|
480
504
|
}
|
|
481
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
|
+
});
|
|
482
513
|
const args = execMsg.message.value;
|
|
483
514
|
const result = create(ShellResultSchema, {
|
|
484
515
|
result: {
|
|
@@ -495,6 +526,12 @@ function handleExecMessage(execMsg, mcpTools, sendFrame, onMcpExec, onUnhandledE
|
|
|
495
526
|
return;
|
|
496
527
|
}
|
|
497
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
|
+
});
|
|
498
535
|
const args = execMsg.message.value;
|
|
499
536
|
const result = create(BackgroundShellSpawnResultSchema, {
|
|
500
537
|
result: {
|
|
@@ -511,6 +548,10 @@ function handleExecMessage(execMsg, mcpTools, sendFrame, onMcpExec, onUnhandledE
|
|
|
511
548
|
return;
|
|
512
549
|
}
|
|
513
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
|
+
});
|
|
514
555
|
const result = create(WriteShellStdinResultSchema, {
|
|
515
556
|
result: {
|
|
516
557
|
case: "error",
|
|
@@ -521,6 +562,11 @@ function handleExecMessage(execMsg, mcpTools, sendFrame, onMcpExec, onUnhandledE
|
|
|
521
562
|
return;
|
|
522
563
|
}
|
|
523
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
|
+
});
|
|
524
570
|
const args = execMsg.message.value;
|
|
525
571
|
const result = create(FetchResultSchema, {
|
|
526
572
|
result: {
|
|
@@ -535,6 +581,11 @@ function handleExecMessage(execMsg, mcpTools, sendFrame, onMcpExec, onUnhandledE
|
|
|
535
581
|
return;
|
|
536
582
|
}
|
|
537
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
|
+
});
|
|
538
589
|
const result = create(DiagnosticsResultSchema, {});
|
|
539
590
|
sendExecResult(execMsg, "diagnosticsResult", result, sendFrame);
|
|
540
591
|
return;
|
|
@@ -548,6 +599,12 @@ function handleExecMessage(execMsg, mcpTools, sendFrame, onMcpExec, onUnhandledE
|
|
|
548
599
|
};
|
|
549
600
|
const resultCase = miscCaseMap[execCase];
|
|
550
601
|
if (resultCase) {
|
|
602
|
+
logPluginInfo("Responding to miscellaneous Cursor exec message", {
|
|
603
|
+
execCase,
|
|
604
|
+
execId: execMsg.execId,
|
|
605
|
+
execMsgId: execMsg.id,
|
|
606
|
+
resultCase,
|
|
607
|
+
});
|
|
551
608
|
sendExecResult(execMsg, resultCase, create(McpResultSchema, {}), sendFrame);
|
|
552
609
|
return;
|
|
553
610
|
}
|
package/dist/proxy/types.d.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import type { CursorSession } from "../cursor/bidi-session";
|
|
2
|
+
import type { CursorRule, McpToolDefinition } from "../proto/agent_pb";
|
|
2
3
|
import type { ConversationRequestMetadata } from "./conversation-meta";
|
|
3
|
-
import type { McpToolDefinition } from "../proto/agent_pb";
|
|
4
4
|
export interface CursorRequestPayload {
|
|
5
5
|
requestBytes: Uint8Array;
|
|
6
6
|
blobStore: Map<string, Uint8Array>;
|
|
7
|
+
rules: CursorRule[];
|
|
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
|
+
rules: CursorRule[];
|
|
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.628837adf8c9",
|
|
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": {
|