opencodev2-cursor 0.0.0-stage → 0.1.0

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.
Files changed (69) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +161 -3
  3. package/dist/backends/agent-argv.mjs +12 -0
  4. package/dist/backends/agent-cli.d.ts +10 -0
  5. package/dist/backends/agent-cli.js +117 -0
  6. package/dist/backends/agent-cli.js.map +1 -0
  7. package/dist/backends/auto-models.d.ts +26 -0
  8. package/dist/backends/auto-models.js +43 -0
  9. package/dist/backends/auto-models.js.map +1 -0
  10. package/dist/backends/checkpoint.d.ts +8 -0
  11. package/dist/backends/checkpoint.js +28 -0
  12. package/dist/backends/checkpoint.js.map +1 -0
  13. package/dist/backends/native/auth.d.ts +20 -0
  14. package/dist/backends/native/auth.js +145 -0
  15. package/dist/backends/native/auth.js.map +1 -0
  16. package/dist/backends/native/backend.d.ts +12 -0
  17. package/dist/backends/native/backend.js +105 -0
  18. package/dist/backends/native/backend.js.map +1 -0
  19. package/dist/backends/native/h2-bridge.mjs +55 -0
  20. package/dist/backends/native/proto.d.ts +31 -0
  21. package/dist/backends/native/proto.js +130 -0
  22. package/dist/backends/native/proto.js.map +1 -0
  23. package/dist/backends/sdk-runner.mjs +317 -0
  24. package/dist/backends/sdk.d.ts +30 -0
  25. package/dist/backends/sdk.js +304 -0
  26. package/dist/backends/sdk.js.map +1 -0
  27. package/dist/backends/select.d.ts +4 -0
  28. package/dist/backends/select.js +15 -0
  29. package/dist/backends/select.js.map +1 -0
  30. package/dist/backends/spawn.d.ts +15 -0
  31. package/dist/backends/spawn.js +64 -0
  32. package/dist/backends/spawn.js.map +1 -0
  33. package/dist/backends/stream-json.d.ts +7 -0
  34. package/dist/backends/stream-json.js +106 -0
  35. package/dist/backends/stream-json.js.map +1 -0
  36. package/dist/backends/types.d.ts +72 -0
  37. package/dist/backends/types.js +15 -0
  38. package/dist/backends/types.js.map +1 -0
  39. package/dist/index.d.ts +4 -0
  40. package/dist/index.js +118 -0
  41. package/dist/index.js.map +1 -0
  42. package/dist/plugin/auth.d.ts +9 -0
  43. package/dist/plugin/auth.js +126 -0
  44. package/dist/plugin/auth.js.map +1 -0
  45. package/dist/plugin/catalog.d.ts +41 -0
  46. package/dist/plugin/catalog.js +123 -0
  47. package/dist/plugin/catalog.js.map +1 -0
  48. package/dist/proxy/server.d.ts +17 -0
  49. package/dist/proxy/server.js +265 -0
  50. package/dist/proxy/server.js.map +1 -0
  51. package/dist/proxy/title.d.ts +4 -0
  52. package/dist/proxy/title.js +24 -0
  53. package/dist/proxy/title.js.map +1 -0
  54. package/dist/proxy/tools.d.ts +4 -0
  55. package/dist/proxy/tools.js +26 -0
  56. package/dist/proxy/tools.js.map +1 -0
  57. package/dist/proxy/translate.d.ts +42 -0
  58. package/dist/proxy/translate.js +77 -0
  59. package/dist/proxy/translate.js.map +1 -0
  60. package/dist/util/log.d.ts +1 -0
  61. package/dist/util/log.js +27 -0
  62. package/dist/util/log.js.map +1 -0
  63. package/dist/util/paths.d.ts +6 -0
  64. package/dist/util/paths.js +30 -0
  65. package/dist/util/paths.js.map +1 -0
  66. package/dist/util/windows.d.ts +28 -0
  67. package/dist/util/windows.js +123 -0
  68. package/dist/util/windows.js.map +1 -0
  69. 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
+ })