@playwo/opencode-cursor-oauth 0.0.0-dev.1c231591c1ee → 0.0.0-dev.240de9fcc758
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/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/proxy/bridge-non-streaming.js +4 -1
- package/dist/proxy/bridge-session.js +1 -3
- package/dist/proxy/bridge-streaming.js +23 -10
- package/dist/proxy/stream-dispatch.d.ts +1 -0
- package/dist/proxy/stream-dispatch.js +163 -22
- package/dist/proxy/stream-state.d.ts +2 -0
- package/package.json +1 -1
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { type CursorBaseRequestOptions } from "./headers";
|
|
2
|
-
export declare function encodeBidiAppendRequest(dataHex: string, requestId: string, appendSeqno: number): Uint8Array;
|
|
3
2
|
export interface CursorSession {
|
|
4
3
|
write: (data: Uint8Array) => void;
|
|
5
4
|
end: () => void;
|
|
@@ -8,6 +7,6 @@ export interface CursorSession {
|
|
|
8
7
|
readonly alive: boolean;
|
|
9
8
|
}
|
|
10
9
|
export interface CreateCursorSessionOptions extends CursorBaseRequestOptions {
|
|
11
|
-
|
|
10
|
+
initialRequestBytes: Uint8Array;
|
|
12
11
|
}
|
|
13
12
|
export declare function createCursorSession(options: CreateCursorSessionOptions): Promise<CursorSession>;
|
|
@@ -1,149 +1,164 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
export function encodeBidiAppendRequest(dataHex, requestId, appendSeqno) {
|
|
8
|
-
const requestIdBytes = toBinary(BidiRequestIdSchema, create(BidiRequestIdSchema, { requestId }));
|
|
9
|
-
return concatBytes([
|
|
10
|
-
encodeProtoStringField(1, dataHex),
|
|
11
|
-
encodeProtoMessageField(2, requestIdBytes),
|
|
12
|
-
encodeProtoVarintField(3, appendSeqno),
|
|
13
|
-
]);
|
|
14
|
-
}
|
|
1
|
+
import { connect as connectHttp2, } from "node:http2";
|
|
2
|
+
import { CURSOR_API_URL, CURSOR_CONNECT_PROTOCOL_VERSION } from "./config";
|
|
3
|
+
import { frameConnectMessage } from "./connect-framing";
|
|
4
|
+
import { buildCursorHeaderValues, } from "./headers";
|
|
5
|
+
import { errorDetails, logPluginError } from "../logger";
|
|
6
|
+
const CURSOR_BIDI_RUN_PATH = "/agent.v1.AgentService/Run";
|
|
15
7
|
export async function createCursorSession(options) {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
headers: buildCursorHeaders(options, "application/connect+proto", {
|
|
19
|
-
accept: "text/event-stream",
|
|
20
|
-
"connect-protocol-version": "1",
|
|
21
|
-
}),
|
|
22
|
-
body: toFetchBody(frameConnectMessage(toBinary(BidiRequestIdSchema, create(BidiRequestIdSchema, { requestId: options.requestId })))),
|
|
23
|
-
});
|
|
24
|
-
if (!response.ok || !response.body) {
|
|
25
|
-
const errorBody = await response.text().catch(() => "");
|
|
26
|
-
logPluginError("Cursor RunSSE request failed", {
|
|
27
|
-
requestId: options.requestId,
|
|
28
|
-
status: response.status,
|
|
29
|
-
responseBody: errorBody,
|
|
30
|
-
});
|
|
31
|
-
throw new Error(`RunSSE failed: ${response.status}${errorBody ? ` ${errorBody}` : ""}`);
|
|
8
|
+
if (options.initialRequestBytes.length === 0) {
|
|
9
|
+
throw new Error("Cursor sessions require an initial request message");
|
|
32
10
|
}
|
|
33
|
-
const
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
const appendResponse = await fetch(new URL("/aiserver.v1.BidiService/BidiAppend", options.url ?? CURSOR_API_URL), {
|
|
54
|
-
method: "POST",
|
|
55
|
-
headers: buildCursorHeaders(options, "application/proto"),
|
|
56
|
-
body: toFetchBody(requestBody),
|
|
57
|
-
signal: abortController.signal,
|
|
58
|
-
});
|
|
59
|
-
if (!appendResponse.ok) {
|
|
60
|
-
const errorBody = await appendResponse.text().catch(() => "");
|
|
61
|
-
logPluginError("Cursor BidiAppend request failed", {
|
|
62
|
-
requestId: options.requestId,
|
|
63
|
-
appendSeqno: appendSeqno - 1,
|
|
64
|
-
status: appendResponse.status,
|
|
65
|
-
responseBody: errorBody,
|
|
66
|
-
});
|
|
67
|
-
throw new Error(`BidiAppend failed: ${appendResponse.status}${errorBody ? ` ${errorBody}` : ""}`);
|
|
68
|
-
}
|
|
69
|
-
await appendResponse.arrayBuffer().catch(() => undefined);
|
|
70
|
-
};
|
|
71
|
-
(async () => {
|
|
72
|
-
try {
|
|
73
|
-
while (true) {
|
|
74
|
-
const { done, value } = await reader.read();
|
|
75
|
-
if (done) {
|
|
76
|
-
finish(0);
|
|
77
|
-
break;
|
|
78
|
-
}
|
|
79
|
-
if (value && value.length > 0) {
|
|
80
|
-
const chunk = Buffer.from(value);
|
|
81
|
-
if (cbs.data) {
|
|
82
|
-
cbs.data(chunk);
|
|
83
|
-
}
|
|
84
|
-
else {
|
|
85
|
-
pendingChunks.push(chunk);
|
|
86
|
-
}
|
|
87
|
-
}
|
|
11
|
+
const target = new URL(CURSOR_BIDI_RUN_PATH, options.url ?? CURSOR_API_URL);
|
|
12
|
+
const authority = `${target.protocol}//${target.host}`;
|
|
13
|
+
const requestId = crypto.randomUUID();
|
|
14
|
+
return new Promise((resolve, reject) => {
|
|
15
|
+
const cbs = {
|
|
16
|
+
data: null,
|
|
17
|
+
close: null,
|
|
18
|
+
};
|
|
19
|
+
let session;
|
|
20
|
+
let stream;
|
|
21
|
+
let alive = true;
|
|
22
|
+
let closeCode = 0;
|
|
23
|
+
let opened = false;
|
|
24
|
+
let settled = false;
|
|
25
|
+
let statusCode = 0;
|
|
26
|
+
const pendingChunks = [];
|
|
27
|
+
const errorChunks = [];
|
|
28
|
+
const closeTransport = () => {
|
|
29
|
+
try {
|
|
30
|
+
stream?.close();
|
|
88
31
|
}
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
32
|
+
catch { }
|
|
33
|
+
try {
|
|
34
|
+
session?.close();
|
|
35
|
+
}
|
|
36
|
+
catch { }
|
|
37
|
+
};
|
|
38
|
+
const finish = (code) => {
|
|
39
|
+
if (!alive)
|
|
40
|
+
return;
|
|
41
|
+
alive = false;
|
|
42
|
+
closeCode = code;
|
|
43
|
+
cbs.close?.(code);
|
|
44
|
+
closeTransport();
|
|
45
|
+
};
|
|
46
|
+
const rejectOpen = (error) => {
|
|
47
|
+
if (settled)
|
|
48
|
+
return;
|
|
49
|
+
settled = true;
|
|
50
|
+
alive = false;
|
|
51
|
+
closeTransport();
|
|
52
|
+
reject(error);
|
|
53
|
+
};
|
|
54
|
+
const resolveOpen = (sessionHandle) => {
|
|
55
|
+
if (settled)
|
|
56
|
+
return;
|
|
57
|
+
settled = true;
|
|
58
|
+
opened = true;
|
|
59
|
+
resolve(sessionHandle);
|
|
60
|
+
};
|
|
61
|
+
const handleTransportError = (message, error) => {
|
|
62
|
+
logPluginError(message, {
|
|
63
|
+
requestId,
|
|
64
|
+
url: target.toString(),
|
|
93
65
|
...errorDetails(error),
|
|
94
66
|
});
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
})();
|
|
98
|
-
return {
|
|
99
|
-
get alive() {
|
|
100
|
-
return alive;
|
|
101
|
-
},
|
|
102
|
-
write(data) {
|
|
103
|
-
if (!alive)
|
|
67
|
+
if (!opened) {
|
|
68
|
+
rejectOpen(new Error(error instanceof Error ? error.message : String(error ?? message)));
|
|
104
69
|
return;
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
70
|
+
}
|
|
71
|
+
finish(1);
|
|
72
|
+
};
|
|
73
|
+
const sessionHandle = {
|
|
74
|
+
get alive() {
|
|
75
|
+
return alive;
|
|
76
|
+
},
|
|
77
|
+
write(data) {
|
|
78
|
+
if (!alive || !stream)
|
|
79
|
+
return;
|
|
112
80
|
try {
|
|
113
|
-
|
|
81
|
+
stream.write(frameConnectMessage(data));
|
|
114
82
|
}
|
|
115
|
-
catch {
|
|
116
|
-
|
|
117
|
-
|
|
83
|
+
catch (error) {
|
|
84
|
+
handleTransportError("Cursor HTTP/2 write failed", error);
|
|
85
|
+
}
|
|
86
|
+
},
|
|
87
|
+
end() {
|
|
88
|
+
finish(0);
|
|
89
|
+
},
|
|
90
|
+
onData(cb) {
|
|
91
|
+
cbs.data = cb;
|
|
92
|
+
while (pendingChunks.length > 0) {
|
|
93
|
+
cb(pendingChunks.shift());
|
|
94
|
+
}
|
|
95
|
+
},
|
|
96
|
+
onClose(cb) {
|
|
97
|
+
if (!alive) {
|
|
98
|
+
queueMicrotask(() => cb(closeCode));
|
|
118
99
|
}
|
|
119
|
-
|
|
120
|
-
|
|
100
|
+
else {
|
|
101
|
+
cbs.close = cb;
|
|
102
|
+
}
|
|
103
|
+
},
|
|
104
|
+
};
|
|
105
|
+
try {
|
|
106
|
+
session = connectHttp2(authority);
|
|
107
|
+
session.once("error", (error) => {
|
|
108
|
+
handleTransportError("Cursor HTTP/2 session failed", error);
|
|
121
109
|
});
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
110
|
+
const headers = {
|
|
111
|
+
":method": "POST",
|
|
112
|
+
":path": `${target.pathname}${target.search}`,
|
|
113
|
+
...buildCursorHeaderValues(options, "application/connect+proto", {
|
|
114
|
+
accept: "application/connect+proto",
|
|
115
|
+
"connect-protocol-version": CURSOR_CONNECT_PROTOCOL_VERSION,
|
|
116
|
+
}),
|
|
117
|
+
};
|
|
118
|
+
stream = session.request(headers);
|
|
119
|
+
stream.once("response", (responseHeaders) => {
|
|
120
|
+
const statusHeader = responseHeaders[":status"];
|
|
121
|
+
statusCode =
|
|
122
|
+
typeof statusHeader === "number"
|
|
123
|
+
? statusHeader
|
|
124
|
+
: Number(statusHeader ?? 0);
|
|
125
|
+
if (statusCode >= 200 && statusCode < 300) {
|
|
126
|
+
resolveOpen(sessionHandle);
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
stream.on("data", (chunk) => {
|
|
130
|
+
const buffer = Buffer.from(chunk);
|
|
131
|
+
if (!opened && statusCode >= 400) {
|
|
132
|
+
errorChunks.push(buffer);
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
if (cbs.data) {
|
|
136
|
+
cbs.data(buffer);
|
|
137
|
+
}
|
|
138
|
+
else {
|
|
139
|
+
pendingChunks.push(buffer);
|
|
140
|
+
}
|
|
141
|
+
});
|
|
142
|
+
stream.once("end", () => {
|
|
143
|
+
if (!opened) {
|
|
144
|
+
const errorBody = Buffer.concat(errorChunks).toString("utf8").trim();
|
|
145
|
+
logPluginError("Cursor HTTP/2 Run request failed", {
|
|
146
|
+
requestId,
|
|
147
|
+
status: statusCode,
|
|
148
|
+
responseBody: errorBody,
|
|
149
|
+
});
|
|
150
|
+
rejectOpen(new Error(`Run failed: ${statusCode || 1}${errorBody ? ` ${errorBody}` : ""}`));
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
finish(statusCode >= 200 && statusCode < 300 ? 0 : statusCode || 1);
|
|
154
|
+
});
|
|
155
|
+
stream.once("error", (error) => {
|
|
156
|
+
handleTransportError("Cursor HTTP/2 stream failed", error);
|
|
157
|
+
});
|
|
158
|
+
stream.write(frameConnectMessage(options.initialRequestBytes));
|
|
159
|
+
}
|
|
160
|
+
catch (error) {
|
|
161
|
+
handleTransportError("Cursor HTTP/2 transport setup failed", error);
|
|
162
|
+
}
|
|
163
|
+
});
|
|
149
164
|
}
|
package/dist/cursor/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { CURSOR_API_URL, CURSOR_CLIENT_VERSION, CURSOR_CONNECT_PROTOCOL_VERSION, CONNECT_END_STREAM_FLAG, } from "./config";
|
|
2
2
|
export { concatBytes, decodeConnectUnaryBody, encodeProtoMessageField, encodeProtoStringField, encodeProtoVarintField, encodeVarint, frameConnectMessage, toFetchBody, } from "./connect-framing";
|
|
3
3
|
export { buildCursorHeaders, buildCursorHeaderValues, type CursorBaseRequestOptions, } from "./headers";
|
|
4
|
-
export { createCursorSession,
|
|
4
|
+
export { createCursorSession, type CreateCursorSessionOptions, type CursorSession, } from "./bidi-session";
|
|
5
5
|
export { callCursorUnaryRpc, type CursorUnaryRpcOptions } from "./unary-rpc";
|
package/dist/cursor/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { CURSOR_API_URL, CURSOR_CLIENT_VERSION, CURSOR_CONNECT_PROTOCOL_VERSION, CONNECT_END_STREAM_FLAG, } from "./config";
|
|
2
2
|
export { concatBytes, decodeConnectUnaryBody, encodeProtoMessageField, encodeProtoStringField, encodeProtoVarintField, encodeVarint, frameConnectMessage, toFetchBody, } from "./connect-framing";
|
|
3
3
|
export { buildCursorHeaders, buildCursorHeaderValues, } from "./headers";
|
|
4
|
-
export { createCursorSession,
|
|
4
|
+
export { createCursorSession, } from "./bidi-session";
|
|
5
5
|
export { callCursorUnaryRpc } from "./unary-rpc";
|
package/dist/cursor/unary-rpc.js
CHANGED
|
@@ -1,67 +1,10 @@
|
|
|
1
1
|
import { connect as connectHttp2, } from "node:http2";
|
|
2
2
|
import { CURSOR_API_URL, CURSOR_CONNECT_PROTOCOL_VERSION } from "./config";
|
|
3
|
-
import {
|
|
4
|
-
import { buildCursorHeaders, buildCursorHeaderValues } from "./headers";
|
|
3
|
+
import { buildCursorHeaderValues } from "./headers";
|
|
5
4
|
import { errorDetails, logPluginError } from "../logger";
|
|
6
5
|
export async function callCursorUnaryRpc(options) {
|
|
7
6
|
const target = new URL(options.rpcPath, options.url ?? CURSOR_API_URL);
|
|
8
|
-
|
|
9
|
-
if (transport === "http2" ||
|
|
10
|
-
(transport === "auto" && target.protocol === "https:")) {
|
|
11
|
-
const http2Result = await callCursorUnaryRpcOverHttp2(options, target);
|
|
12
|
-
if (transport === "http2" ||
|
|
13
|
-
http2Result.timedOut ||
|
|
14
|
-
http2Result.exitCode !== 1) {
|
|
15
|
-
return http2Result;
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
return callCursorUnaryRpcOverFetch(options, target);
|
|
19
|
-
}
|
|
20
|
-
async function callCursorUnaryRpcOverFetch(options, target) {
|
|
21
|
-
let timedOut = false;
|
|
22
|
-
const timeoutMs = options.timeoutMs ?? 5_000;
|
|
23
|
-
const controller = new AbortController();
|
|
24
|
-
const timeout = timeoutMs > 0
|
|
25
|
-
? setTimeout(() => {
|
|
26
|
-
timedOut = true;
|
|
27
|
-
controller.abort();
|
|
28
|
-
}, timeoutMs)
|
|
29
|
-
: undefined;
|
|
30
|
-
try {
|
|
31
|
-
const response = await fetch(target, {
|
|
32
|
-
method: "POST",
|
|
33
|
-
headers: buildCursorHeaders(options, "application/proto", {
|
|
34
|
-
accept: "application/proto, application/json",
|
|
35
|
-
"connect-protocol-version": CURSOR_CONNECT_PROTOCOL_VERSION,
|
|
36
|
-
"connect-timeout-ms": String(timeoutMs),
|
|
37
|
-
}),
|
|
38
|
-
body: toFetchBody(options.requestBody),
|
|
39
|
-
signal: controller.signal,
|
|
40
|
-
});
|
|
41
|
-
const body = new Uint8Array(await response.arrayBuffer());
|
|
42
|
-
return {
|
|
43
|
-
body,
|
|
44
|
-
exitCode: response.ok ? 0 : response.status,
|
|
45
|
-
timedOut,
|
|
46
|
-
};
|
|
47
|
-
}
|
|
48
|
-
catch {
|
|
49
|
-
logPluginError("Cursor unary fetch transport failed", {
|
|
50
|
-
rpcPath: options.rpcPath,
|
|
51
|
-
url: target.toString(),
|
|
52
|
-
timeoutMs,
|
|
53
|
-
timedOut,
|
|
54
|
-
});
|
|
55
|
-
return {
|
|
56
|
-
body: new Uint8Array(),
|
|
57
|
-
exitCode: timedOut ? 124 : 1,
|
|
58
|
-
timedOut,
|
|
59
|
-
};
|
|
60
|
-
}
|
|
61
|
-
finally {
|
|
62
|
-
if (timeout)
|
|
63
|
-
clearTimeout(timeout);
|
|
64
|
-
}
|
|
7
|
+
return callCursorUnaryRpcOverHttp2(options, target);
|
|
65
8
|
}
|
|
66
9
|
async function callCursorUnaryRpcOverHttp2(options, target) {
|
|
67
10
|
const timeoutMs = options.timeoutMs ?? 5_000;
|
|
@@ -4,7 +4,7 @@ import { errorDetails, logPluginError } from "../logger";
|
|
|
4
4
|
import { updateStoredConversationAfterCompletion } from "./conversation-state";
|
|
5
5
|
import { startBridge } from "./bridge-session";
|
|
6
6
|
import { updateConversationCheckpoint, syncStoredBlobStore, } from "./state-sync";
|
|
7
|
-
import { computeUsage, createConnectFrameParser, createThinkingTagFilter, parseConnectEndStream, processServerMessage, scheduleBridgeEnd, } from "./stream-dispatch";
|
|
7
|
+
import { clearDeferredInteractionExecs, computeUsage, createConnectFrameParser, createThinkingTagFilter, parseConnectEndStream, processServerMessage, scheduleBridgeEnd, } from "./stream-dispatch";
|
|
8
8
|
export async function handleNonStreamingResponse(payload, accessToken, modelId, convKey, metadata) {
|
|
9
9
|
const completionId = `chatcmpl-${crypto.randomUUID().replace(/-/g, "").slice(0, 28)}`;
|
|
10
10
|
const created = Math.floor(Date.now() / 1000);
|
|
@@ -40,6 +40,8 @@ async function collectFullResponse(payload, accessToken, modelId, convKey, metad
|
|
|
40
40
|
totalTokens: 0,
|
|
41
41
|
interactionToolArgsText: new Map(),
|
|
42
42
|
emittedToolCallIds: new Set(),
|
|
43
|
+
deferredInteractionExecs: new Map(),
|
|
44
|
+
deferredInteractionExecTimers: new Map(),
|
|
43
45
|
};
|
|
44
46
|
const tagFilter = createThinkingTagFilter();
|
|
45
47
|
bridge.onData(createConnectFrameParser((messageBytes) => {
|
|
@@ -98,6 +100,7 @@ async function collectFullResponse(payload, accessToken, modelId, convKey, metad
|
|
|
98
100
|
}));
|
|
99
101
|
bridge.onClose(() => {
|
|
100
102
|
clearInterval(heartbeatTimer);
|
|
103
|
+
clearDeferredInteractionExecs(state);
|
|
101
104
|
syncStoredBlobStore(convKey, payload.blobStore);
|
|
102
105
|
const flushed = tagFilter.flush();
|
|
103
106
|
fullText += flushed.content;
|
|
@@ -2,12 +2,10 @@ import { createCursorSession } from "../cursor/bidi-session";
|
|
|
2
2
|
import { makeHeartbeatBytes } from "./stream-dispatch";
|
|
3
3
|
const HEARTBEAT_INTERVAL_MS = 5_000;
|
|
4
4
|
export async function startBridge(accessToken, requestBytes) {
|
|
5
|
-
const requestId = crypto.randomUUID();
|
|
6
5
|
const bridge = await createCursorSession({
|
|
7
6
|
accessToken,
|
|
8
|
-
|
|
7
|
+
initialRequestBytes: requestBytes,
|
|
9
8
|
});
|
|
10
|
-
bridge.write(requestBytes);
|
|
11
9
|
const heartbeatTimer = setInterval(() => bridge.write(makeHeartbeatBytes()), HEARTBEAT_INTERVAL_MS);
|
|
12
10
|
return { bridge, heartbeatTimer };
|
|
13
11
|
}
|
|
@@ -6,12 +6,13 @@ import { activeBridges, updateStoredConversationAfterCompletion, } from "./conve
|
|
|
6
6
|
import { startBridge } from "./bridge-session";
|
|
7
7
|
import { updateConversationCheckpoint, syncStoredBlobStore, } from "./state-sync";
|
|
8
8
|
import { SSE_HEADERS } from "./sse";
|
|
9
|
-
import { computeUsage, createConnectFrameParser, createThinkingTagFilter, parseConnectEndStream, processServerMessage, scheduleBridgeEnd, } from "./stream-dispatch";
|
|
9
|
+
import { clearDeferredInteractionExecs, computeUsage, createConnectFrameParser, createThinkingTagFilter, parseConnectEndStream, processServerMessage, scheduleBridgeEnd, } from "./stream-dispatch";
|
|
10
10
|
const SSE_KEEPALIVE_INTERVAL_MS = 15_000;
|
|
11
11
|
function createBridgeStreamResponse(bridge, heartbeatTimer, blobStore, 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;
|
|
15
|
+
let activeState;
|
|
15
16
|
const stopKeepalive = () => {
|
|
16
17
|
if (!keepaliveTimer)
|
|
17
18
|
return;
|
|
@@ -29,7 +30,10 @@ function createBridgeStreamResponse(bridge, heartbeatTimer, blobStore, mcpTools,
|
|
|
29
30
|
totalTokens: 0,
|
|
30
31
|
interactionToolArgsText: new Map(),
|
|
31
32
|
emittedToolCallIds: new Set(),
|
|
33
|
+
deferredInteractionExecs: new Map(),
|
|
34
|
+
deferredInteractionExecTimers: new Map(),
|
|
32
35
|
};
|
|
36
|
+
activeState = state;
|
|
33
37
|
const tagFilter = createThinkingTagFilter();
|
|
34
38
|
let assistantText = metadata.assistantSeedText ?? "";
|
|
35
39
|
let mcpExecReceived = false;
|
|
@@ -49,6 +53,19 @@ function createBridgeStreamResponse(bridge, heartbeatTimer, blobStore, mcpTools,
|
|
|
49
53
|
return;
|
|
50
54
|
controller.enqueue(encoder.encode("data: [DONE]\n\n"));
|
|
51
55
|
};
|
|
56
|
+
const failStream = (message, code) => {
|
|
57
|
+
if (closed)
|
|
58
|
+
return;
|
|
59
|
+
sendSSE({
|
|
60
|
+
error: {
|
|
61
|
+
message,
|
|
62
|
+
type: "server_error",
|
|
63
|
+
...(code ? { code } : {}),
|
|
64
|
+
},
|
|
65
|
+
});
|
|
66
|
+
sendDone();
|
|
67
|
+
closeController();
|
|
68
|
+
};
|
|
52
69
|
const closeController = () => {
|
|
53
70
|
if (closed)
|
|
54
71
|
return;
|
|
@@ -197,13 +214,11 @@ function createBridgeStreamResponse(bridge, heartbeatTimer, blobStore, mcpTools,
|
|
|
197
214
|
bridge.onClose((code) => {
|
|
198
215
|
clearInterval(heartbeatTimer);
|
|
199
216
|
stopKeepalive();
|
|
217
|
+
clearDeferredInteractionExecs(state);
|
|
200
218
|
syncStoredBlobStore(convKey, blobStore);
|
|
201
219
|
if (endStreamError) {
|
|
202
220
|
activeBridges.delete(bridgeKey);
|
|
203
|
-
|
|
204
|
-
closed = true;
|
|
205
|
-
controller.error(endStreamError);
|
|
206
|
-
}
|
|
221
|
+
failStream(endStreamError.message, "cursor_bridge_closed");
|
|
207
222
|
return;
|
|
208
223
|
}
|
|
209
224
|
if (!mcpExecReceived) {
|
|
@@ -223,17 +238,15 @@ function createBridgeStreamResponse(bridge, heartbeatTimer, blobStore, mcpTools,
|
|
|
223
238
|
}
|
|
224
239
|
activeBridges.delete(bridgeKey);
|
|
225
240
|
if (code !== 0 && !closed) {
|
|
226
|
-
|
|
227
|
-
sendSSE(makeChunk({}, "stop"));
|
|
228
|
-
sendSSE(makeUsageChunk());
|
|
229
|
-
sendDone();
|
|
230
|
-
closeController();
|
|
241
|
+
failStream("Cursor bridge connection lost", "cursor_bridge_closed");
|
|
231
242
|
}
|
|
232
243
|
});
|
|
233
244
|
},
|
|
234
245
|
cancel(reason) {
|
|
235
246
|
stopKeepalive();
|
|
236
247
|
clearInterval(heartbeatTimer);
|
|
248
|
+
if (activeState)
|
|
249
|
+
clearDeferredInteractionExecs(activeState);
|
|
237
250
|
syncStoredBlobStore(convKey, blobStore);
|
|
238
251
|
const active = activeBridges.get(bridgeKey);
|
|
239
252
|
if (active?.bridge === bridge) {
|
|
@@ -39,4 +39,5 @@ export declare function computeUsage(state: StreamState): {
|
|
|
39
39
|
completion_tokens: number;
|
|
40
40
|
total_tokens: number;
|
|
41
41
|
};
|
|
42
|
+
export declare function clearDeferredInteractionExecs(state: StreamState): void;
|
|
42
43
|
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;
|
|
@@ -1,8 +1,9 @@
|
|
|
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
4
|
import { logPluginError, logPluginWarn } from "../logger";
|
|
5
5
|
import { decodeMcpArgsMap } from "../openai/tools";
|
|
6
|
+
const INTERACTION_TOOL_CALL_DEFER_MS = 75;
|
|
6
7
|
export function parseConnectEndStream(data) {
|
|
7
8
|
try {
|
|
8
9
|
const payload = JSON.parse(new TextDecoder().decode(data));
|
|
@@ -128,6 +129,59 @@ export function computeUsage(state) {
|
|
|
128
129
|
const prompt_tokens = Math.max(0, total_tokens - completion_tokens);
|
|
129
130
|
return { prompt_tokens, completion_tokens, total_tokens };
|
|
130
131
|
}
|
|
132
|
+
function getPendingExecKey(exec) {
|
|
133
|
+
return exec.toolCallId || `${exec.toolName}:${exec.decodedArgs}`;
|
|
134
|
+
}
|
|
135
|
+
function replacePendingExec(state, exec) {
|
|
136
|
+
const execKey = getPendingExecKey(exec);
|
|
137
|
+
const existingIndex = state.pendingExecs.findIndex((candidate) => getPendingExecKey(candidate) === execKey);
|
|
138
|
+
if (existingIndex >= 0) {
|
|
139
|
+
state.pendingExecs[existingIndex] = exec;
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
state.pendingExecs.push(exec);
|
|
143
|
+
}
|
|
144
|
+
function emitPendingExec(exec, state, onMcpExec) {
|
|
145
|
+
const execKey = getPendingExecKey(exec);
|
|
146
|
+
if (state.emittedToolCallIds.has(execKey)) {
|
|
147
|
+
replacePendingExec(state, exec);
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
state.emittedToolCallIds.add(execKey);
|
|
151
|
+
replacePendingExec(state, exec);
|
|
152
|
+
onMcpExec(exec);
|
|
153
|
+
}
|
|
154
|
+
function clearDeferredInteractionExec(state, execKey) {
|
|
155
|
+
const timer = state.deferredInteractionExecTimers.get(execKey);
|
|
156
|
+
if (timer)
|
|
157
|
+
clearTimeout(timer);
|
|
158
|
+
state.deferredInteractionExecTimers.delete(execKey);
|
|
159
|
+
state.deferredInteractionExecs.delete(execKey);
|
|
160
|
+
}
|
|
161
|
+
function queueInteractionPendingExec(exec, state, onMcpExec) {
|
|
162
|
+
const execKey = getPendingExecKey(exec);
|
|
163
|
+
if (state.emittedToolCallIds.has(execKey) ||
|
|
164
|
+
state.deferredInteractionExecs.has(execKey)) {
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
state.deferredInteractionExecs.set(execKey, exec);
|
|
168
|
+
const timer = setTimeout(() => {
|
|
169
|
+
state.deferredInteractionExecTimers.delete(execKey);
|
|
170
|
+
const pendingExec = state.deferredInteractionExecs.get(execKey);
|
|
171
|
+
if (!pendingExec)
|
|
172
|
+
return;
|
|
173
|
+
state.deferredInteractionExecs.delete(execKey);
|
|
174
|
+
emitPendingExec(pendingExec, state, onMcpExec);
|
|
175
|
+
}, INTERACTION_TOOL_CALL_DEFER_MS);
|
|
176
|
+
state.deferredInteractionExecTimers.set(execKey, timer);
|
|
177
|
+
}
|
|
178
|
+
export function clearDeferredInteractionExecs(state) {
|
|
179
|
+
for (const timer of state.deferredInteractionExecTimers.values()) {
|
|
180
|
+
clearTimeout(timer);
|
|
181
|
+
}
|
|
182
|
+
state.deferredInteractionExecTimers.clear();
|
|
183
|
+
state.deferredInteractionExecs.clear();
|
|
184
|
+
}
|
|
131
185
|
export function processServerMessage(msg, blobStore, mcpTools, sendFrame, state, onText, onMcpExec, onCheckpoint, onTurnEnded, onUnsupportedMessage, onUnhandledExec) {
|
|
132
186
|
const msgCase = msg.message.case;
|
|
133
187
|
if (msgCase === "interactionUpdate") {
|
|
@@ -137,7 +191,7 @@ export function processServerMessage(msg, blobStore, mcpTools, sendFrame, state,
|
|
|
137
191
|
handleKvMessage(msg.message.value, blobStore, sendFrame);
|
|
138
192
|
}
|
|
139
193
|
else if (msgCase === "execServerMessage") {
|
|
140
|
-
handleExecMessage(msg.message.value, mcpTools, sendFrame, onMcpExec, onUnhandledExec);
|
|
194
|
+
handleExecMessage(msg.message.value, mcpTools, sendFrame, state, onMcpExec, onUnhandledExec);
|
|
141
195
|
}
|
|
142
196
|
else if (msgCase === "execServerControlMessage") {
|
|
143
197
|
onUnsupportedMessage?.({
|
|
@@ -146,10 +200,7 @@ export function processServerMessage(msg, blobStore, mcpTools, sendFrame, state,
|
|
|
146
200
|
});
|
|
147
201
|
}
|
|
148
202
|
else if (msgCase === "interactionQuery") {
|
|
149
|
-
onUnsupportedMessage
|
|
150
|
-
category: "interactionQuery",
|
|
151
|
-
caseName: msg.message.value.query.case ?? "undefined",
|
|
152
|
-
});
|
|
203
|
+
handleInteractionQuery(msg.message.value, sendFrame, onUnsupportedMessage);
|
|
153
204
|
}
|
|
154
205
|
else if (msgCase === "conversationCheckpointUpdate") {
|
|
155
206
|
const stateStructure = msg.message.value;
|
|
@@ -185,20 +236,14 @@ function handleInteractionUpdate(update, state, onText, onMcpExec, onTurnEnded,
|
|
|
185
236
|
else if (updateCase === "partialToolCall") {
|
|
186
237
|
const partial = update.message.value;
|
|
187
238
|
if (partial.callId && partial.argsTextDelta) {
|
|
188
|
-
state.interactionToolArgsText.
|
|
239
|
+
const existing = state.interactionToolArgsText.get(partial.callId) ?? "";
|
|
240
|
+
state.interactionToolArgsText.set(partial.callId, `${existing}${partial.argsTextDelta}`);
|
|
189
241
|
}
|
|
190
242
|
}
|
|
191
243
|
else if (updateCase === "toolCallCompleted") {
|
|
192
244
|
const exec = decodeInteractionToolCall(update.message.value, state);
|
|
193
245
|
if (exec)
|
|
194
|
-
|
|
195
|
-
else {
|
|
196
|
-
onUnsupportedMessage?.({
|
|
197
|
-
category: "toolCall",
|
|
198
|
-
caseName: update.message.value?.toolCall?.tool?.case ?? "undefined",
|
|
199
|
-
detail: "toolCallCompleted",
|
|
200
|
-
});
|
|
201
|
-
}
|
|
246
|
+
queueInteractionPendingExec(exec, state, onMcpExec);
|
|
202
247
|
}
|
|
203
248
|
else if (updateCase === "turnEnded") {
|
|
204
249
|
onTurnEnded?.();
|
|
@@ -221,9 +266,10 @@ function handleInteractionUpdate(update, state, onText, onMcpExec, onTurnEnded,
|
|
|
221
266
|
caseName: updateCase ?? "undefined",
|
|
222
267
|
});
|
|
223
268
|
}
|
|
224
|
-
// toolCallStarted, partialToolCall, toolCallDelta,
|
|
225
|
-
// are
|
|
226
|
-
//
|
|
269
|
+
// toolCallStarted, partialToolCall, toolCallDelta, and non-MCP
|
|
270
|
+
// toolCallCompleted updates are informational only. Actionable MCP tool
|
|
271
|
+
// calls may still appear here on some models, so we surface those, but we
|
|
272
|
+
// do not abort the bridge for native Cursor tool-call progress events.
|
|
227
273
|
}
|
|
228
274
|
function decodeInteractionToolCall(update, state) {
|
|
229
275
|
const callId = update.callId ?? "";
|
|
@@ -247,7 +293,6 @@ function decodeInteractionToolCall(update, state) {
|
|
|
247
293
|
else if (partialArgsText) {
|
|
248
294
|
decodedArgs = partialArgsText;
|
|
249
295
|
}
|
|
250
|
-
state.emittedToolCallIds.add(toolCallId);
|
|
251
296
|
if (callId)
|
|
252
297
|
state.interactionToolArgsText.delete(callId);
|
|
253
298
|
return {
|
|
@@ -258,6 +303,90 @@ function decodeInteractionToolCall(update, state) {
|
|
|
258
303
|
decodedArgs,
|
|
259
304
|
};
|
|
260
305
|
}
|
|
306
|
+
function handleInteractionQuery(query, sendFrame, onUnsupportedMessage) {
|
|
307
|
+
const queryCase = query.query.case;
|
|
308
|
+
if (queryCase === "webSearchRequestQuery") {
|
|
309
|
+
const response = create(WebSearchRequestResponseSchema, {
|
|
310
|
+
result: {
|
|
311
|
+
case: "rejected",
|
|
312
|
+
value: create(WebSearchRequestResponse_RejectedSchema, {
|
|
313
|
+
reason: "Native Cursor web search is not available in this environment. Use the provided MCP tool `websearch` instead.",
|
|
314
|
+
}),
|
|
315
|
+
},
|
|
316
|
+
});
|
|
317
|
+
sendInteractionResponse(query.id, "webSearchRequestResponse", response, sendFrame);
|
|
318
|
+
return;
|
|
319
|
+
}
|
|
320
|
+
if (queryCase === "askQuestionInteractionQuery") {
|
|
321
|
+
const response = create(AskQuestionInteractionResponseSchema, {
|
|
322
|
+
result: create(AskQuestionResultSchema, {
|
|
323
|
+
result: {
|
|
324
|
+
case: "rejected",
|
|
325
|
+
value: create(AskQuestionRejectedSchema, {
|
|
326
|
+
reason: "Native Cursor question prompts are not available in this environment. Use the provided MCP tool `question` instead.",
|
|
327
|
+
}),
|
|
328
|
+
},
|
|
329
|
+
}),
|
|
330
|
+
});
|
|
331
|
+
sendInteractionResponse(query.id, "askQuestionInteractionResponse", response, sendFrame);
|
|
332
|
+
return;
|
|
333
|
+
}
|
|
334
|
+
if (queryCase === "switchModeRequestQuery") {
|
|
335
|
+
const response = create(SwitchModeRequestResponseSchema, {
|
|
336
|
+
result: {
|
|
337
|
+
case: "rejected",
|
|
338
|
+
value: create(SwitchModeRequestResponse_RejectedSchema, {
|
|
339
|
+
reason: "Cursor mode switching is not available in this environment. Continue using the current agent and the provided MCP tools.",
|
|
340
|
+
}),
|
|
341
|
+
},
|
|
342
|
+
});
|
|
343
|
+
sendInteractionResponse(query.id, "switchModeRequestResponse", response, sendFrame);
|
|
344
|
+
return;
|
|
345
|
+
}
|
|
346
|
+
if (queryCase === "exaSearchRequestQuery") {
|
|
347
|
+
const response = create(ExaSearchRequestResponseSchema, {
|
|
348
|
+
result: {
|
|
349
|
+
case: "rejected",
|
|
350
|
+
value: create(ExaSearchRequestResponse_RejectedSchema, {
|
|
351
|
+
reason: "Native Cursor Exa search is not available in this environment. Use the provided MCP tool `websearch` instead.",
|
|
352
|
+
}),
|
|
353
|
+
},
|
|
354
|
+
});
|
|
355
|
+
sendInteractionResponse(query.id, "exaSearchRequestResponse", response, sendFrame);
|
|
356
|
+
return;
|
|
357
|
+
}
|
|
358
|
+
if (queryCase === "exaFetchRequestQuery") {
|
|
359
|
+
const response = create(ExaFetchRequestResponseSchema, {
|
|
360
|
+
result: {
|
|
361
|
+
case: "rejected",
|
|
362
|
+
value: create(ExaFetchRequestResponse_RejectedSchema, {
|
|
363
|
+
reason: "Native Cursor Exa fetch is not available in this environment. Use the provided MCP tools `websearch` and `webfetch` instead.",
|
|
364
|
+
}),
|
|
365
|
+
},
|
|
366
|
+
});
|
|
367
|
+
sendInteractionResponse(query.id, "exaFetchRequestResponse", response, sendFrame);
|
|
368
|
+
return;
|
|
369
|
+
}
|
|
370
|
+
if (queryCase === "createPlanRequestQuery") {
|
|
371
|
+
const response = create(CreatePlanRequestResponseSchema, {
|
|
372
|
+
result: create(CreatePlanResultSchema, {
|
|
373
|
+
planUri: "",
|
|
374
|
+
result: {
|
|
375
|
+
case: "error",
|
|
376
|
+
value: create(CreatePlanErrorSchema, {
|
|
377
|
+
error: "Native Cursor plan creation is not available in this environment. Use the provided MCP planning tools instead.",
|
|
378
|
+
}),
|
|
379
|
+
},
|
|
380
|
+
}),
|
|
381
|
+
});
|
|
382
|
+
sendInteractionResponse(query.id, "createPlanRequestResponse", response, sendFrame);
|
|
383
|
+
return;
|
|
384
|
+
}
|
|
385
|
+
onUnsupportedMessage?.({
|
|
386
|
+
category: "interactionQuery",
|
|
387
|
+
caseName: queryCase ?? "undefined",
|
|
388
|
+
});
|
|
389
|
+
}
|
|
261
390
|
/** Send a KV client response back to Cursor. */
|
|
262
391
|
function sendKvResponse(kvMsg, messageCase, value, sendFrame) {
|
|
263
392
|
const response = create(KvClientMessageSchema, {
|
|
@@ -269,6 +398,16 @@ function sendKvResponse(kvMsg, messageCase, value, sendFrame) {
|
|
|
269
398
|
});
|
|
270
399
|
sendFrame(toBinary(AgentClientMessageSchema, clientMsg));
|
|
271
400
|
}
|
|
401
|
+
function sendInteractionResponse(queryId, messageCase, value, sendFrame) {
|
|
402
|
+
const response = create(InteractionResponseSchema, {
|
|
403
|
+
id: queryId,
|
|
404
|
+
result: { case: messageCase, value: value },
|
|
405
|
+
});
|
|
406
|
+
const clientMessage = create(AgentClientMessageSchema, {
|
|
407
|
+
message: { case: "interactionResponse", value: response },
|
|
408
|
+
});
|
|
409
|
+
sendFrame(toBinary(AgentClientMessageSchema, clientMessage));
|
|
410
|
+
}
|
|
272
411
|
function handleKvMessage(kvMsg, blobStore, sendFrame) {
|
|
273
412
|
const kvCase = kvMsg.message.case;
|
|
274
413
|
if (kvCase === "getBlobArgs") {
|
|
@@ -289,7 +428,7 @@ function handleKvMessage(kvMsg, blobStore, sendFrame) {
|
|
|
289
428
|
sendKvResponse(kvMsg, "setBlobResult", create(SetBlobResultSchema, {}), sendFrame);
|
|
290
429
|
}
|
|
291
430
|
}
|
|
292
|
-
function handleExecMessage(execMsg, mcpTools, sendFrame, onMcpExec, onUnhandledExec) {
|
|
431
|
+
function handleExecMessage(execMsg, mcpTools, sendFrame, state, onMcpExec, onUnhandledExec) {
|
|
293
432
|
const execCase = execMsg.message.case;
|
|
294
433
|
if (execCase === "requestContextArgs") {
|
|
295
434
|
const requestContext = create(RequestContextSchema, {
|
|
@@ -314,13 +453,15 @@ function handleExecMessage(execMsg, mcpTools, sendFrame, onMcpExec, onUnhandledE
|
|
|
314
453
|
if (execCase === "mcpArgs") {
|
|
315
454
|
const mcpArgs = execMsg.message.value;
|
|
316
455
|
const decoded = decodeMcpArgsMap(mcpArgs.args ?? {});
|
|
317
|
-
|
|
456
|
+
const exec = {
|
|
318
457
|
execId: execMsg.execId,
|
|
319
458
|
execMsgId: execMsg.id,
|
|
320
459
|
toolCallId: mcpArgs.toolCallId || crypto.randomUUID(),
|
|
321
460
|
toolName: mcpArgs.toolName || mcpArgs.name,
|
|
322
461
|
decodedArgs: JSON.stringify(decoded),
|
|
323
|
-
}
|
|
462
|
+
};
|
|
463
|
+
clearDeferredInteractionExec(state, getPendingExecKey(exec));
|
|
464
|
+
emitPendingExec(exec, state, onMcpExec);
|
|
324
465
|
return;
|
|
325
466
|
}
|
|
326
467
|
// --- Reject native Cursor tools ---
|
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.240de9fcc758",
|
|
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",
|