opencodev2-cursor 0.0.0-stage → 0.1.1
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/LICENSE +21 -0
- package/README.md +161 -3
- package/dist/backends/agent-argv.mjs +12 -0
- package/dist/backends/agent-cli.d.ts +10 -0
- package/dist/backends/agent-cli.js +117 -0
- package/dist/backends/agent-cli.js.map +1 -0
- package/dist/backends/auto-models.d.ts +26 -0
- package/dist/backends/auto-models.js +43 -0
- package/dist/backends/auto-models.js.map +1 -0
- package/dist/backends/checkpoint.d.ts +8 -0
- package/dist/backends/checkpoint.js +28 -0
- package/dist/backends/checkpoint.js.map +1 -0
- package/dist/backends/native/auth.d.ts +20 -0
- package/dist/backends/native/auth.js +145 -0
- package/dist/backends/native/auth.js.map +1 -0
- package/dist/backends/native/backend.d.ts +12 -0
- package/dist/backends/native/backend.js +105 -0
- package/dist/backends/native/backend.js.map +1 -0
- package/dist/backends/native/h2-bridge.mjs +55 -0
- package/dist/backends/native/proto.d.ts +31 -0
- package/dist/backends/native/proto.js +130 -0
- package/dist/backends/native/proto.js.map +1 -0
- package/dist/backends/sdk-runner.mjs +317 -0
- package/dist/backends/sdk.d.ts +30 -0
- package/dist/backends/sdk.js +304 -0
- package/dist/backends/sdk.js.map +1 -0
- package/dist/backends/select.d.ts +4 -0
- package/dist/backends/select.js +15 -0
- package/dist/backends/select.js.map +1 -0
- package/dist/backends/spawn.d.ts +15 -0
- package/dist/backends/spawn.js +64 -0
- package/dist/backends/spawn.js.map +1 -0
- package/dist/backends/stream-json.d.ts +7 -0
- package/dist/backends/stream-json.js +106 -0
- package/dist/backends/stream-json.js.map +1 -0
- package/dist/backends/types.d.ts +72 -0
- package/dist/backends/types.js +15 -0
- package/dist/backends/types.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +118 -0
- package/dist/index.js.map +1 -0
- package/dist/plugin/auth.d.ts +9 -0
- package/dist/plugin/auth.js +126 -0
- package/dist/plugin/auth.js.map +1 -0
- package/dist/plugin/catalog.d.ts +41 -0
- package/dist/plugin/catalog.js +120 -0
- package/dist/plugin/catalog.js.map +1 -0
- package/dist/proxy/server.d.ts +17 -0
- package/dist/proxy/server.js +265 -0
- package/dist/proxy/server.js.map +1 -0
- package/dist/proxy/title.d.ts +4 -0
- package/dist/proxy/title.js +24 -0
- package/dist/proxy/title.js.map +1 -0
- package/dist/proxy/tools.d.ts +4 -0
- package/dist/proxy/tools.js +26 -0
- package/dist/proxy/tools.js.map +1 -0
- package/dist/proxy/translate.d.ts +42 -0
- package/dist/proxy/translate.js +77 -0
- package/dist/proxy/translate.js.map +1 -0
- package/dist/util/log.d.ts +1 -0
- package/dist/util/log.js +27 -0
- package/dist/util/log.js.map +1 -0
- package/dist/util/paths.d.ts +6 -0
- package/dist/util/paths.js +30 -0
- package/dist/util/paths.js.map +1 -0
- package/dist/util/windows.d.ts +28 -0
- package/dist/util/windows.js +123 -0
- package/dist/util/windows.js.map +1 -0
- package/package.json +53 -6
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { BackendEvent, BackendRunRequest, CursorBackend } from "../types.js";
|
|
2
|
+
/** OAuth access-token backend. Uses the SDK unless CURSOR_NATIVE_TRANSPORT=connect. */
|
|
3
|
+
export declare class NativeBackend implements CursorBackend {
|
|
4
|
+
private readonly accessToken;
|
|
5
|
+
readonly id: "native";
|
|
6
|
+
private readonly sdk;
|
|
7
|
+
constructor(accessToken: () => Promise<string>);
|
|
8
|
+
listModels(): Promise<import("../types.js").CursorModel[]>;
|
|
9
|
+
warmUp(cwd: string): Promise<import("../types.js").CursorModel[]>;
|
|
10
|
+
run(request: BackendRunRequest): AsyncIterable<BackendEvent>;
|
|
11
|
+
dispose(): Promise<void>;
|
|
12
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { createInterface } from "node:readline";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import { promptWithToolResults } from "../types.js";
|
|
5
|
+
import { SdkBackend } from "../sdk.js";
|
|
6
|
+
import { encodeConnectFrames, encodeRunRequest, decodeConnectData, stringsInProtobuf, toolCallFromText } from "./proto.js";
|
|
7
|
+
import { nodeBinary } from "../spawn.js";
|
|
8
|
+
import { mapCursorTool } from "../../proxy/tools.js";
|
|
9
|
+
import { log } from "../../util/log.js";
|
|
10
|
+
function bridgeScript() {
|
|
11
|
+
return fileURLToPath(new URL("./h2-bridge.mjs", import.meta.url));
|
|
12
|
+
}
|
|
13
|
+
async function* connectRun(request, accessToken) {
|
|
14
|
+
const child = spawn(nodeBinary(), [bridgeScript()], {
|
|
15
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
16
|
+
windowsHide: true,
|
|
17
|
+
env: process.env,
|
|
18
|
+
});
|
|
19
|
+
const lines = createInterface({ input: child.stdout });
|
|
20
|
+
const prompt = promptWithToolResults(request.prompt, request.toolResults);
|
|
21
|
+
const body = encodeConnectFrames(encodeRunRequest(request.model, prompt, {
|
|
22
|
+
modelParams: request.modelParams,
|
|
23
|
+
tools: request.tools,
|
|
24
|
+
toolResults: request.toolResults,
|
|
25
|
+
}));
|
|
26
|
+
child.stdin.write(JSON.stringify({
|
|
27
|
+
type: "request",
|
|
28
|
+
id: "run",
|
|
29
|
+
path: "/agent.v1.AgentService/Run",
|
|
30
|
+
authorization: `Bearer ${accessToken}`,
|
|
31
|
+
body: body.toString("base64"),
|
|
32
|
+
}) + "\n");
|
|
33
|
+
const onAbort = () => child.kill();
|
|
34
|
+
request.signal.addEventListener("abort", onAbort, { once: true });
|
|
35
|
+
try {
|
|
36
|
+
for await (const line of lines) {
|
|
37
|
+
const event = JSON.parse(line);
|
|
38
|
+
if (event.type === "error") {
|
|
39
|
+
yield { type: "error", message: event.message || "Cursor Connect request failed" };
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
if (event.type === "data" && event.data) {
|
|
43
|
+
const payload = Buffer.from(event.data, "base64");
|
|
44
|
+
const frames = decodeConnectData(payload);
|
|
45
|
+
const texts = frames.flatMap((frame) => stringsInProtobuf(frame));
|
|
46
|
+
if (texts.length === 0 && event.status && event.status >= 400) {
|
|
47
|
+
yield { type: "error", message: `Cursor Connect returned HTTP ${event.status}` };
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
const advertised = new Set(request.tools.map((tool) => tool.name));
|
|
51
|
+
for (const text of texts) {
|
|
52
|
+
const call = toolCallFromText(text);
|
|
53
|
+
if (!call) {
|
|
54
|
+
yield { type: "text", text };
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
yield {
|
|
58
|
+
type: "tool_call",
|
|
59
|
+
id: call.id,
|
|
60
|
+
name: mapCursorTool(call.name, advertised) || call.name,
|
|
61
|
+
arguments: call.arguments,
|
|
62
|
+
};
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
if (event.type === "end") {
|
|
67
|
+
yield { type: "done" };
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
yield { type: "error", message: "Cursor Connect bridge closed" };
|
|
72
|
+
}
|
|
73
|
+
finally {
|
|
74
|
+
request.signal.removeEventListener("abort", onAbort);
|
|
75
|
+
child.kill();
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
/** OAuth access-token backend. Uses the SDK unless CURSOR_NATIVE_TRANSPORT=connect. */
|
|
79
|
+
export class NativeBackend {
|
|
80
|
+
accessToken;
|
|
81
|
+
id = "native";
|
|
82
|
+
sdk;
|
|
83
|
+
constructor(accessToken) {
|
|
84
|
+
this.accessToken = accessToken;
|
|
85
|
+
this.sdk = new SdkBackend(accessToken);
|
|
86
|
+
}
|
|
87
|
+
listModels() {
|
|
88
|
+
return this.sdk.listModels();
|
|
89
|
+
}
|
|
90
|
+
warmUp(cwd) {
|
|
91
|
+
return this.sdk.warmUp(cwd);
|
|
92
|
+
}
|
|
93
|
+
async *run(request) {
|
|
94
|
+
if (process.env.CURSOR_NATIVE_TRANSPORT === "connect") {
|
|
95
|
+
log("info", "native connect run", { model: request.model });
|
|
96
|
+
yield* connectRun(request, await this.accessToken());
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
yield* this.sdk.run(request);
|
|
100
|
+
}
|
|
101
|
+
async dispose() {
|
|
102
|
+
await this.sdk.dispose();
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
//# sourceMappingURL=backend.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"backend.js","sourceRoot":"","sources":["../../../src/backends/native/backend.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import http2 from "node:http2"
|
|
2
|
+
import readline from "node:readline"
|
|
3
|
+
|
|
4
|
+
const client = http2.connect(process.env.CURSOR_BACKEND_URL || "https://api2.cursor.sh")
|
|
5
|
+
client.on("error", (error) => {
|
|
6
|
+
process.stdout.write(JSON.stringify({ type: "error", message: error.message }) + "\n")
|
|
7
|
+
})
|
|
8
|
+
|
|
9
|
+
function emit(message) {
|
|
10
|
+
process.stdout.write(JSON.stringify(message) + "\n")
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const lines = readline.createInterface({ input: process.stdin })
|
|
14
|
+
lines.on("line", (line) => {
|
|
15
|
+
let cmd
|
|
16
|
+
try {
|
|
17
|
+
cmd = JSON.parse(line)
|
|
18
|
+
} catch {
|
|
19
|
+
return
|
|
20
|
+
}
|
|
21
|
+
if (cmd.type !== "request") return
|
|
22
|
+
const headers = {
|
|
23
|
+
":method": "POST",
|
|
24
|
+
":path": cmd.path,
|
|
25
|
+
"content-type": cmd.contentType || "application/connect+proto",
|
|
26
|
+
"connect-protocol-version": "1",
|
|
27
|
+
authorization: cmd.authorization,
|
|
28
|
+
}
|
|
29
|
+
const request = client.request(headers)
|
|
30
|
+
let pending = Buffer.alloc(0)
|
|
31
|
+
let status
|
|
32
|
+
const emitFrame = (frame) => {
|
|
33
|
+
emit({ id: cmd.id, type: "data", status, data: frame.toString("base64") })
|
|
34
|
+
}
|
|
35
|
+
request.on("response", (responseHeaders) => {
|
|
36
|
+
status = responseHeaders[":status"]
|
|
37
|
+
emit({ id: cmd.id, type: "headers", status })
|
|
38
|
+
})
|
|
39
|
+
request.on("data", (chunk) => {
|
|
40
|
+
pending = Buffer.concat([pending, chunk])
|
|
41
|
+
while (pending.length >= 5) {
|
|
42
|
+
const length = pending.readUInt32BE(1)
|
|
43
|
+
const frameLength = 5 + length
|
|
44
|
+
if (pending.length < frameLength) break
|
|
45
|
+
emitFrame(pending.subarray(0, frameLength))
|
|
46
|
+
pending = pending.subarray(frameLength)
|
|
47
|
+
}
|
|
48
|
+
})
|
|
49
|
+
request.on("end", () => {
|
|
50
|
+
if (pending.length) emitFrame(pending)
|
|
51
|
+
emit({ id: cmd.id, type: "end" })
|
|
52
|
+
})
|
|
53
|
+
request.on("error", (error) => emit({ id: cmd.id, type: "error", message: error.message }))
|
|
54
|
+
request.end(Buffer.from(cmd.body, "base64"))
|
|
55
|
+
})
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Protobuf body for the native Connect fallback.
|
|
3
|
+
* Field numbers belong to this plugin, not Cursor's AgentService schema:
|
|
4
|
+
* 1 model, 2 prompt, 3 model params JSON, 4 tools JSON, 5 tool results JSON.
|
|
5
|
+
*/
|
|
6
|
+
export interface ConnectRunExtra {
|
|
7
|
+
modelParams?: Array<{
|
|
8
|
+
id: string;
|
|
9
|
+
value: string;
|
|
10
|
+
}>;
|
|
11
|
+
tools?: Array<{
|
|
12
|
+
name: string;
|
|
13
|
+
description?: string;
|
|
14
|
+
parameters?: unknown;
|
|
15
|
+
}>;
|
|
16
|
+
toolResults?: Array<{
|
|
17
|
+
id: string;
|
|
18
|
+
content: string;
|
|
19
|
+
}>;
|
|
20
|
+
}
|
|
21
|
+
export declare function encodeRunRequest(model: string, prompt: string, extra?: ConnectRunExtra): Buffer;
|
|
22
|
+
/** Connect streaming envelope: one data frame plus an end-of-stream frame. */
|
|
23
|
+
export declare function encodeConnectFrames(payload: Buffer): Buffer;
|
|
24
|
+
export declare function decodeConnectData(frame: Buffer): Buffer[];
|
|
25
|
+
export declare function stringsInProtobuf(payload: Buffer): string[];
|
|
26
|
+
/** A Connect string that is itself a tool call. Plain assistant text is left alone. */
|
|
27
|
+
export declare function toolCallFromText(text: string): {
|
|
28
|
+
id: string;
|
|
29
|
+
name: string;
|
|
30
|
+
arguments: string;
|
|
31
|
+
} | undefined;
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Protobuf body for the native Connect fallback.
|
|
3
|
+
* Field numbers belong to this plugin, not Cursor's AgentService schema:
|
|
4
|
+
* 1 model, 2 prompt, 3 model params JSON, 4 tools JSON, 5 tool results JSON.
|
|
5
|
+
*/
|
|
6
|
+
function encodeVarint(value) {
|
|
7
|
+
const bytes = [];
|
|
8
|
+
let rest = value >>> 0;
|
|
9
|
+
while (rest > 127) {
|
|
10
|
+
bytes.push((rest & 0x7f) | 0x80);
|
|
11
|
+
rest >>>= 7;
|
|
12
|
+
}
|
|
13
|
+
bytes.push(rest);
|
|
14
|
+
return bytes;
|
|
15
|
+
}
|
|
16
|
+
function encodeString(field, value) {
|
|
17
|
+
const data = Buffer.from(value);
|
|
18
|
+
return [(field << 3) | 2, ...encodeVarint(data.length), ...data];
|
|
19
|
+
}
|
|
20
|
+
export function encodeRunRequest(model, prompt, extra) {
|
|
21
|
+
const bytes = [...encodeString(1, model), ...encodeString(2, prompt)];
|
|
22
|
+
if (extra?.modelParams?.length)
|
|
23
|
+
bytes.push(...encodeString(3, JSON.stringify(extra.modelParams)));
|
|
24
|
+
if (extra?.tools?.length)
|
|
25
|
+
bytes.push(...encodeString(4, JSON.stringify(extra.tools)));
|
|
26
|
+
if (extra?.toolResults?.length)
|
|
27
|
+
bytes.push(...encodeString(5, JSON.stringify(extra.toolResults)));
|
|
28
|
+
return Buffer.from(bytes);
|
|
29
|
+
}
|
|
30
|
+
/** Connect streaming envelope: one data frame plus an end-of-stream frame. */
|
|
31
|
+
export function encodeConnectFrames(payload) {
|
|
32
|
+
const header = Buffer.alloc(5);
|
|
33
|
+
header[0] = 0;
|
|
34
|
+
header.writeUInt32BE(payload.length, 1);
|
|
35
|
+
const end = Buffer.alloc(5);
|
|
36
|
+
end[0] = 0x02;
|
|
37
|
+
end.writeUInt32BE(2, 1);
|
|
38
|
+
return Buffer.concat([header, payload, end, Buffer.from("{}")]);
|
|
39
|
+
}
|
|
40
|
+
export function decodeConnectData(frame) {
|
|
41
|
+
const parts = [];
|
|
42
|
+
let offset = 0;
|
|
43
|
+
while (offset + 5 <= frame.length) {
|
|
44
|
+
const flags = frame[offset];
|
|
45
|
+
const length = frame.readUInt32BE(offset + 1);
|
|
46
|
+
offset += 5;
|
|
47
|
+
const body = frame.subarray(offset, offset + length);
|
|
48
|
+
offset += length;
|
|
49
|
+
if ((flags & 0x02) === 0)
|
|
50
|
+
parts.push(Buffer.from(body));
|
|
51
|
+
}
|
|
52
|
+
return parts;
|
|
53
|
+
}
|
|
54
|
+
export function stringsInProtobuf(payload) {
|
|
55
|
+
const found = [];
|
|
56
|
+
let offset = 0;
|
|
57
|
+
while (offset < payload.length) {
|
|
58
|
+
const key = payload[offset];
|
|
59
|
+
if (key === undefined)
|
|
60
|
+
break;
|
|
61
|
+
offset += 1;
|
|
62
|
+
const wire = key & 7;
|
|
63
|
+
if (wire === 2) {
|
|
64
|
+
let length = 0;
|
|
65
|
+
let shift = 0;
|
|
66
|
+
while (offset < payload.length) {
|
|
67
|
+
const byte = payload[offset];
|
|
68
|
+
offset += 1;
|
|
69
|
+
if (byte === undefined)
|
|
70
|
+
break;
|
|
71
|
+
length |= (byte & 0x7f) << shift;
|
|
72
|
+
if ((byte & 0x80) === 0)
|
|
73
|
+
break;
|
|
74
|
+
shift += 7;
|
|
75
|
+
}
|
|
76
|
+
const slice = payload.subarray(offset, offset + length);
|
|
77
|
+
offset += length;
|
|
78
|
+
const text = readableText(slice);
|
|
79
|
+
if (text)
|
|
80
|
+
found.push(text);
|
|
81
|
+
}
|
|
82
|
+
else if (wire === 0) {
|
|
83
|
+
while (offset < payload.length && (payload[offset] & 0x80) !== 0)
|
|
84
|
+
offset += 1;
|
|
85
|
+
offset += 1;
|
|
86
|
+
}
|
|
87
|
+
else
|
|
88
|
+
break;
|
|
89
|
+
}
|
|
90
|
+
return found;
|
|
91
|
+
}
|
|
92
|
+
function readableText(slice) {
|
|
93
|
+
if (slice.length === 0 || slice.includes(0))
|
|
94
|
+
return undefined;
|
|
95
|
+
const text = slice.toString("utf8");
|
|
96
|
+
if (text.includes("\uFFFD"))
|
|
97
|
+
return undefined;
|
|
98
|
+
const chars = [...text];
|
|
99
|
+
let printable = 0;
|
|
100
|
+
for (const char of chars) {
|
|
101
|
+
const code = char.codePointAt(0) ?? 0;
|
|
102
|
+
if (code === 9 || code === 10 || code === 13 || code >= 32)
|
|
103
|
+
printable += 1;
|
|
104
|
+
}
|
|
105
|
+
if (printable !== chars.length)
|
|
106
|
+
return undefined;
|
|
107
|
+
return text;
|
|
108
|
+
}
|
|
109
|
+
/** A Connect string that is itself a tool call. Plain assistant text is left alone. */
|
|
110
|
+
export function toolCallFromText(text) {
|
|
111
|
+
if (!text.startsWith("{"))
|
|
112
|
+
return undefined;
|
|
113
|
+
try {
|
|
114
|
+
const value = JSON.parse(text);
|
|
115
|
+
if (value.type !== "tool_call" || typeof value.name !== "string")
|
|
116
|
+
return undefined;
|
|
117
|
+
const args = typeof value.arguments === "string"
|
|
118
|
+
? value.arguments
|
|
119
|
+
: value.arguments && typeof value.arguments === "object"
|
|
120
|
+
? JSON.stringify(value.arguments)
|
|
121
|
+
: undefined;
|
|
122
|
+
if (!args)
|
|
123
|
+
return undefined;
|
|
124
|
+
return { id: typeof value.id === "string" ? value.id : "tool", name: value.name, arguments: args };
|
|
125
|
+
}
|
|
126
|
+
catch {
|
|
127
|
+
return undefined;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
//# sourceMappingURL=proto.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"proto.js","sourceRoot":"","sources":["../../../src/backends/native/proto.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
import path from "node:path"
|
|
2
|
+
import readline from "node:readline"
|
|
3
|
+
import { randomUUID } from "node:crypto"
|
|
4
|
+
import { Agent, Cursor, JsonlLocalAgentStore, configureCursorSdk, createAgentPlatform } from "@cursor/sdk"
|
|
5
|
+
|
|
6
|
+
const major = Number(process.versions.node.split(".")[0] ?? 0)
|
|
7
|
+
const minor = Number(process.versions.node.split(".")[1] ?? 0)
|
|
8
|
+
if (major < 22 || (major === 22 && minor < 13)) {
|
|
9
|
+
process.stdout.write(JSON.stringify({ type: "error", message: `Cursor SDK needs Node.js 22.13 or newer (this runner is ${process.version}). Set CURSOR_ACP_NODE_BIN to a newer node.` }) + "\n")
|
|
10
|
+
process.exit(1)
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/** Long-lived hosts: avoid re-walking the tree every 20s (SDK default). Override with CURSOR_RIPWALK_CACHE_TTL_MS. */
|
|
14
|
+
const WORKSPACE_SCAN_TTL_MS = Number(process.env.CURSOR_RIPWALK_CACHE_TTL_MS || 30 * 60 * 1000)
|
|
15
|
+
|
|
16
|
+
const waiters = new Map()
|
|
17
|
+
let agent = null
|
|
18
|
+
let activeModel = null
|
|
19
|
+
let activeSession = null
|
|
20
|
+
let runHandle = null
|
|
21
|
+
let busy = false
|
|
22
|
+
/** Bumped when a run is force-cleared so a superseded turn's finally does not clear the new turn's busy flag. */
|
|
23
|
+
let turnGeneration = 0
|
|
24
|
+
let store = null
|
|
25
|
+
let storeRoot = null
|
|
26
|
+
let platform = null
|
|
27
|
+
let prewarmRelease = null
|
|
28
|
+
let prewarmedCwd = null
|
|
29
|
+
let prewarmedApiKey = null
|
|
30
|
+
|
|
31
|
+
function emit(message) {
|
|
32
|
+
process.stdout.write(JSON.stringify(message) + "\n")
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function customTools(tools) {
|
|
36
|
+
const defined = {}
|
|
37
|
+
for (const tool of tools ?? []) {
|
|
38
|
+
defined[tool.name] = {
|
|
39
|
+
description: tool.description || `Host tool ${tool.name}. OpenCode runs it and returns the result.`,
|
|
40
|
+
inputSchema: tool.parameters && typeof tool.parameters === "object" ? tool.parameters : { type: "object", additionalProperties: true },
|
|
41
|
+
execute(_args, context) {
|
|
42
|
+
const id = context?.toolCallId || randomUUID()
|
|
43
|
+
emit({ type: "tool", id, name: tool.name, arguments: JSON.stringify(_args ?? {}) })
|
|
44
|
+
return new Promise((resolve) => {
|
|
45
|
+
waiters.set(id, resolve)
|
|
46
|
+
})
|
|
47
|
+
},
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return defined
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** Same shape as modelSelectionKey() in auto-models.ts. The runner cannot import that TypeScript module. */
|
|
54
|
+
function modelKey(cmd) {
|
|
55
|
+
const params = Array.isArray(cmd.modelParams)
|
|
56
|
+
? cmd.modelParams.map((param) => ({ id: String(param.id), value: String(param.value) })).sort((left, right) => left.id.localeCompare(right.id))
|
|
57
|
+
: []
|
|
58
|
+
return JSON.stringify({ id: cmd.model || "auto-smart", params })
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
async function clearStuckRun() {
|
|
62
|
+
turnGeneration += 1
|
|
63
|
+
const pending = runHandle
|
|
64
|
+
runHandle = null
|
|
65
|
+
for (const [, resolve] of waiters) resolve("(cancelled)")
|
|
66
|
+
waiters.clear()
|
|
67
|
+
if (pending) {
|
|
68
|
+
try {
|
|
69
|
+
await pending.cancel()
|
|
70
|
+
} catch {
|
|
71
|
+
// Already terminal or store already marked cancelled.
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
busy = false
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function ensureStore(stateRoot) {
|
|
78
|
+
if (store && storeRoot === stateRoot) return
|
|
79
|
+
storeRoot = stateRoot
|
|
80
|
+
store = new JsonlLocalAgentStore(stateRoot)
|
|
81
|
+
configureCursorSdk({
|
|
82
|
+
local: {
|
|
83
|
+
store,
|
|
84
|
+
useHttp1ForAgent: process.env.CURSOR_SDK_HTTP1 === "1",
|
|
85
|
+
workspaceScanCacheTtlMs: Number.isFinite(WORKSPACE_SCAN_TTL_MS) ? WORKSPACE_SCAN_TTL_MS : 30 * 60 * 1000,
|
|
86
|
+
},
|
|
87
|
+
})
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function agentLocalOptions(cmd, tools) {
|
|
91
|
+
return {
|
|
92
|
+
cwd: cmd.cwd,
|
|
93
|
+
store,
|
|
94
|
+
settingSources: [],
|
|
95
|
+
customTools: tools && Object.keys(tools).length ? tools : undefined,
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
async function ensurePrewarm(cmd) {
|
|
100
|
+
if (!cmd.cwd || !cmd.apiKey) return
|
|
101
|
+
ensureStore(cmd.stateRoot || path.join(cmd.cwd, ".opencodev2-cursor-sdk"))
|
|
102
|
+
if (prewarmRelease && prewarmedCwd === cmd.cwd && prewarmedApiKey === cmd.apiKey) return
|
|
103
|
+
if (prewarmRelease) {
|
|
104
|
+
try {
|
|
105
|
+
await prewarmRelease()
|
|
106
|
+
} catch {
|
|
107
|
+
// Best effort; a later send() rebuilds.
|
|
108
|
+
}
|
|
109
|
+
prewarmRelease = null
|
|
110
|
+
prewarmedCwd = null
|
|
111
|
+
prewarmedApiKey = null
|
|
112
|
+
}
|
|
113
|
+
if (!platform) platform = await createAgentPlatform()
|
|
114
|
+
try {
|
|
115
|
+
prewarmRelease = await platform.prewarmLocalWorkspace({
|
|
116
|
+
apiKey: cmd.apiKey,
|
|
117
|
+
model: { id: "auto-smart" },
|
|
118
|
+
name: "opencodev2-cursor",
|
|
119
|
+
local: {
|
|
120
|
+
cwd: cmd.cwd,
|
|
121
|
+
store,
|
|
122
|
+
settingSources: [],
|
|
123
|
+
},
|
|
124
|
+
})
|
|
125
|
+
prewarmedCwd = cmd.cwd
|
|
126
|
+
prewarmedApiKey = cmd.apiKey
|
|
127
|
+
} catch (error) {
|
|
128
|
+
emit({
|
|
129
|
+
type: "warn",
|
|
130
|
+
message: error instanceof Error ? error.message : String(error),
|
|
131
|
+
})
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
async function ensureAgent(cmd) {
|
|
136
|
+
const requestedModel = modelKey(cmd)
|
|
137
|
+
const sessionKey = cmd.sessionKey || "default"
|
|
138
|
+
if (
|
|
139
|
+
agent
|
|
140
|
+
&& activeModel === requestedModel
|
|
141
|
+
&& activeSession === sessionKey
|
|
142
|
+
&& (!cmd.agentId || agent.agentId === cmd.agentId)
|
|
143
|
+
) {
|
|
144
|
+
return agent
|
|
145
|
+
}
|
|
146
|
+
ensureStore(cmd.stateRoot)
|
|
147
|
+
await ensurePrewarm(cmd)
|
|
148
|
+
const tools = customTools(cmd.tools)
|
|
149
|
+
const model = { id: cmd.model || "auto-smart" }
|
|
150
|
+
if (Array.isArray(cmd.modelParams) && cmd.modelParams.length) model.params = cmd.modelParams
|
|
151
|
+
const options = {
|
|
152
|
+
apiKey: cmd.apiKey,
|
|
153
|
+
model,
|
|
154
|
+
name: "opencodev2-cursor",
|
|
155
|
+
local: agentLocalOptions(cmd, tools),
|
|
156
|
+
}
|
|
157
|
+
if (cmd.tools?.length) {
|
|
158
|
+
options.tools = ["mcp"]
|
|
159
|
+
options.disallowedTools = ["shell", "read", "edit", "grep", "glob", "delete", "task"]
|
|
160
|
+
}
|
|
161
|
+
// Resume only when the parent sends an agentId. A model change omits it, so this creates a new agent.
|
|
162
|
+
agent = cmd.agentId ? await Agent.resume(cmd.agentId, options) : await Agent.create(options)
|
|
163
|
+
activeModel = requestedModel
|
|
164
|
+
activeSession = sessionKey
|
|
165
|
+
emit({ type: "session", agentId: agent.agentId })
|
|
166
|
+
return agent
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
async function startRun(current, prompt) {
|
|
170
|
+
// local.force expires orphaned activeRunId left by abort/crash/retry (Cursor SDK docs).
|
|
171
|
+
try {
|
|
172
|
+
return await current.send(prompt, { local: { force: true } })
|
|
173
|
+
} catch (error) {
|
|
174
|
+
const message = error instanceof Error ? error.message : String(error)
|
|
175
|
+
if (!/already has active run/i.test(message)) throw error
|
|
176
|
+
const pending = runHandle
|
|
177
|
+
runHandle = null
|
|
178
|
+
for (const [, resolve] of waiters) resolve("(cancelled)")
|
|
179
|
+
waiters.clear()
|
|
180
|
+
if (pending) {
|
|
181
|
+
try {
|
|
182
|
+
await pending.cancel()
|
|
183
|
+
} catch {
|
|
184
|
+
// Ignore.
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
return current.send(prompt, { local: { force: true } })
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
async function runTurn(cmd) {
|
|
192
|
+
const toolContinue = (cmd.toolResults ?? []).length > 0
|
|
193
|
+
if (toolContinue) {
|
|
194
|
+
// Tool results are normally delivered as type=tool_result; keep this path from clearing busy.
|
|
195
|
+
for (const result of cmd.toolResults ?? []) {
|
|
196
|
+
const resolve = waiters.get(result.id)
|
|
197
|
+
if (resolve) {
|
|
198
|
+
waiters.delete(result.id)
|
|
199
|
+
resolve(result.content ?? "")
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
return
|
|
203
|
+
}
|
|
204
|
+
if (busy) {
|
|
205
|
+
// OpenCode retried or sent a new user message while a previous run was wedged.
|
|
206
|
+
await clearStuckRun()
|
|
207
|
+
}
|
|
208
|
+
const myGeneration = turnGeneration + 1
|
|
209
|
+
turnGeneration = myGeneration
|
|
210
|
+
busy = true
|
|
211
|
+
try {
|
|
212
|
+
const current = await ensureAgent(cmd)
|
|
213
|
+
const run = await startRun(current, cmd.prompt || "")
|
|
214
|
+
if (myGeneration !== turnGeneration) return
|
|
215
|
+
runHandle = run
|
|
216
|
+
let sent = ""
|
|
217
|
+
for await (const event of run.stream()) {
|
|
218
|
+
if (myGeneration !== turnGeneration) return
|
|
219
|
+
if (event.type === "assistant") {
|
|
220
|
+
const blocks = event.message?.content ?? []
|
|
221
|
+
for (const block of blocks) {
|
|
222
|
+
if (block.type !== "text" || typeof block.text !== "string") continue
|
|
223
|
+
const text = block.text.startsWith(sent) ? block.text.slice(sent.length) : block.text
|
|
224
|
+
sent = block.text.startsWith(sent) ? block.text : sent + block.text
|
|
225
|
+
if (text) emit({ type: "text", text })
|
|
226
|
+
}
|
|
227
|
+
} else if (event.type === "thinking" && event.text) {
|
|
228
|
+
emit({ type: "reasoning", text: event.text })
|
|
229
|
+
} else if (event.type === "status" && (event.status === "ERROR" || event.status === "CANCELLED")) {
|
|
230
|
+
emit({ type: "error", message: event.message || event.status })
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
const result = await run.wait()
|
|
234
|
+
if (myGeneration !== turnGeneration) return
|
|
235
|
+
runHandle = null
|
|
236
|
+
if (result.status === "error") emit({ type: "error", message: result.error?.message || "Cursor run failed" })
|
|
237
|
+
else emit({ type: "done", text: result.result })
|
|
238
|
+
} catch (error) {
|
|
239
|
+
if (myGeneration === turnGeneration) {
|
|
240
|
+
emit({ type: "error", message: error instanceof Error ? error.message : String(error) })
|
|
241
|
+
}
|
|
242
|
+
} finally {
|
|
243
|
+
if (myGeneration === turnGeneration) {
|
|
244
|
+
busy = false
|
|
245
|
+
emit({ type: "idle" })
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
async function listModels(cmd) {
|
|
251
|
+
try {
|
|
252
|
+
const models = await Cursor.models.list({ apiKey: cmd.apiKey })
|
|
253
|
+
emit({
|
|
254
|
+
type: "models",
|
|
255
|
+
id: cmd.id,
|
|
256
|
+
models: models.map((model) => ({ id: model.id, name: model.displayName || model.id })),
|
|
257
|
+
})
|
|
258
|
+
} catch (error) {
|
|
259
|
+
emit({ type: "error", id: cmd.id, message: error instanceof Error ? error.message : String(error) })
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
async function warm(cmd) {
|
|
264
|
+
const started = Date.now()
|
|
265
|
+
try {
|
|
266
|
+
const modelsPromise = Cursor.models.list({ apiKey: cmd.apiKey }).catch((error) => {
|
|
267
|
+
throw error
|
|
268
|
+
})
|
|
269
|
+
await ensurePrewarm(cmd)
|
|
270
|
+
const models = await modelsPromise
|
|
271
|
+
emit({
|
|
272
|
+
type: "warm",
|
|
273
|
+
id: cmd.id,
|
|
274
|
+
ms: Date.now() - started,
|
|
275
|
+
cwd: cmd.cwd,
|
|
276
|
+
prewarmed: Boolean(prewarmRelease),
|
|
277
|
+
models: models.map((model) => ({ id: model.id, name: model.displayName || model.id })),
|
|
278
|
+
})
|
|
279
|
+
} catch (error) {
|
|
280
|
+
emit({ type: "error", id: cmd.id, message: error instanceof Error ? error.message : String(error) })
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
const lines = readline.createInterface({ input: process.stdin })
|
|
285
|
+
lines.on("line", (line) => {
|
|
286
|
+
let cmd
|
|
287
|
+
try {
|
|
288
|
+
cmd = JSON.parse(line)
|
|
289
|
+
} catch {
|
|
290
|
+
return
|
|
291
|
+
}
|
|
292
|
+
if (cmd.type === "tool_result") {
|
|
293
|
+
const resolve = waiters.get(cmd.id)
|
|
294
|
+
if (resolve) {
|
|
295
|
+
waiters.delete(cmd.id)
|
|
296
|
+
resolve(cmd.content ?? "")
|
|
297
|
+
}
|
|
298
|
+
return
|
|
299
|
+
}
|
|
300
|
+
if (cmd.type === "cancel") {
|
|
301
|
+
void clearStuckRun()
|
|
302
|
+
return
|
|
303
|
+
}
|
|
304
|
+
if (cmd.type === "ping") {
|
|
305
|
+
emit({ type: "pong" })
|
|
306
|
+
return
|
|
307
|
+
}
|
|
308
|
+
if (cmd.type === "models") {
|
|
309
|
+
void listModels(cmd)
|
|
310
|
+
return
|
|
311
|
+
}
|
|
312
|
+
if (cmd.type === "warm") {
|
|
313
|
+
void warm(cmd)
|
|
314
|
+
return
|
|
315
|
+
}
|
|
316
|
+
if (cmd.type === "turn") void runTurn(cmd)
|
|
317
|
+
})
|