cursor-opencode-provider 0.6.4 → 0.6.7

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.
@@ -0,0 +1,33 @@
1
+ /** aiserver.v1.GetDiffRequest.OutputFormat */
2
+ export declare const GIT_DIFF_FORMAT_UNSPECIFIED = 0;
3
+ export declare const GIT_DIFF_FORMAT_NAME_STATUS = 1;
4
+ export declare const GIT_DIFF_FORMAT_NAME_STATUS_AND_NUMSTAT = 2;
5
+ export declare const GIT_DIFF_FORMAT_FILE_DIFFS = 3;
6
+ export declare const GIT_DIFF_FORMAT_DIFFS_WITH_BEFORE_AND_AFTER = 4;
7
+ export type GitDiffChunk = {
8
+ content: string;
9
+ lines: string[];
10
+ old_start: number;
11
+ old_lines: number;
12
+ new_start: number;
13
+ new_lines: number;
14
+ };
15
+ export type GitFileDiff = {
16
+ from: string;
17
+ to: string;
18
+ chunks: GitDiffChunk[];
19
+ added: number;
20
+ removed: number;
21
+ };
22
+ /** Parse `git diff --no-color` into aiserver.v1.FileDiff rows (CLI `Oc` / `FR`). */
23
+ export declare function parseUnifiedDiff(text: string): GitFileDiff[];
24
+ export declare function executeGitDiff(request: Record<string, unknown>, workspaceRoot: string): Promise<Record<string, unknown>>;
25
+ /**
26
+ * Answer exec #44. GetDiffResponse has no error oneof — failures use
27
+ * exec_client_control_message.throw, matching Cursor CLI `class Dc`.
28
+ */
29
+ export declare function buildGitDiffExecMessages(input: {
30
+ execId: number;
31
+ request: Record<string, unknown>;
32
+ workspaceRoot: string;
33
+ }): Promise<Uint8Array[]>;
@@ -0,0 +1,372 @@
1
+ import { execFile } from "node:child_process";
2
+ import fs from "node:fs";
3
+ import path from "node:path";
4
+ import { promisify } from "node:util";
5
+ import { encodeMessage } from "./messages.js";
6
+ const execFileAsync = promisify(execFile);
7
+ /** aiserver.v1.GetDiffRequest.OutputFormat */
8
+ export const GIT_DIFF_FORMAT_UNSPECIFIED = 0;
9
+ export const GIT_DIFF_FORMAT_NAME_STATUS = 1;
10
+ export const GIT_DIFF_FORMAT_NAME_STATUS_AND_NUMSTAT = 2;
11
+ export const GIT_DIFF_FORMAT_FILE_DIFFS = 3;
12
+ export const GIT_DIFF_FORMAT_DIFFS_WITH_BEFORE_AND_AFTER = 4;
13
+ const DEFAULT_REMOTE_BRANCHES = ["main", "master", "develop"];
14
+ const GIT_TIMEOUT_MS = 30_000;
15
+ const GIT_MAX_BUFFER = 32 * 1024 * 1024;
16
+ const GIT_ENV = {
17
+ ...process.env,
18
+ GIT_TERMINAL_PROMPT: "0",
19
+ GIT_OPTIONAL_LOCKS: "0",
20
+ };
21
+ function gitErrorMessage(error, fallback) {
22
+ if (!error || typeof error !== "object")
23
+ return fallback;
24
+ const err = error;
25
+ const stderr = typeof err.stderr === "string" ? err.stderr.trim() : "";
26
+ if (stderr)
27
+ return stderr.split("\n")[0] ?? stderr;
28
+ if (typeof err.message === "string" && err.message)
29
+ return err.message;
30
+ return fallback;
31
+ }
32
+ async function git(cwd, args, options) {
33
+ try {
34
+ const { stdout, stderr } = await execFileAsync("git", args, {
35
+ cwd,
36
+ encoding: "utf8",
37
+ timeout: GIT_TIMEOUT_MS,
38
+ maxBuffer: GIT_MAX_BUFFER,
39
+ env: GIT_ENV,
40
+ });
41
+ return {
42
+ stdout: options?.trim === false ? stdout : stdout.trimEnd(),
43
+ stderr: stderr ?? "",
44
+ code: 0,
45
+ };
46
+ }
47
+ catch (error) {
48
+ const err = error;
49
+ const code = typeof err.code === "number" ? err.code : 1;
50
+ if (options?.ignoreExit) {
51
+ return {
52
+ stdout: options.trim === false ? (err.stdout ?? "") : (err.stdout ?? "").trimEnd(),
53
+ stderr: err.stderr ?? "",
54
+ code,
55
+ };
56
+ }
57
+ throw new Error(gitErrorMessage(error, `git ${args.join(" ")} failed`));
58
+ }
59
+ }
60
+ function str(value) {
61
+ return typeof value === "string" ? value : "";
62
+ }
63
+ function num(value) {
64
+ if (typeof value === "number" && Number.isFinite(value))
65
+ return value;
66
+ if (typeof value === "string" && value.trim() !== "" && Number.isFinite(Number(value))) {
67
+ return Number(value);
68
+ }
69
+ return undefined;
70
+ }
71
+ function resolveGitCwd(requestCwd, workspaceRoot) {
72
+ const root = path.resolve(workspaceRoot || process.cwd());
73
+ const requested = requestCwd.trim();
74
+ if (!requested)
75
+ return root;
76
+ return path.isAbsolute(requested) ? path.resolve(requested) : path.resolve(root, requested);
77
+ }
78
+ async function resolveDefaultBase(cwd) {
79
+ try {
80
+ const head = (await git(cwd, ["symbolic-ref", "--short", "refs/remotes/origin/HEAD"])).stdout.trim();
81
+ if (head)
82
+ return head;
83
+ }
84
+ catch {
85
+ /* try origin/<main|master|develop> */
86
+ }
87
+ for (const name of DEFAULT_REMOTE_BRANCHES) {
88
+ const ref = `origin/${name}`;
89
+ try {
90
+ await git(cwd, ["rev-parse", "--verify", ref]);
91
+ return ref;
92
+ }
93
+ catch {
94
+ /* next candidate */
95
+ }
96
+ }
97
+ try {
98
+ const remotes = (await git(cwd, ["branch", "-r"])).stdout.split("\n")
99
+ .map((line) => line.trim())
100
+ .find((line) => line.startsWith("origin/"));
101
+ if (remotes)
102
+ return remotes;
103
+ }
104
+ catch {
105
+ /* try init.defaultBranch */
106
+ }
107
+ try {
108
+ const configured = (await git(cwd, ["config", "--get", "init.defaultBranch"])).stdout.trim();
109
+ if (configured)
110
+ return configured;
111
+ }
112
+ catch {
113
+ /* no default */
114
+ }
115
+ throw new Error("Could not determine default branch");
116
+ }
117
+ function stripDiffPrefix(value) {
118
+ return value.replace(/^[abiwco12]\//, "");
119
+ }
120
+ function parseDiffGitPaths(line) {
121
+ if (!line.startsWith("diff --git "))
122
+ return undefined;
123
+ const rest = line.slice("diff --git ".length).trim();
124
+ const match = rest.match(/^(?:[abiwco12]\/)?(.+?) (?:[abiwco12]\/)?(.+)$/);
125
+ if (!match)
126
+ return undefined;
127
+ return { from: stripDiffPrefix(match[1]), to: stripDiffPrefix(match[2]) };
128
+ }
129
+ function parsePathHeader(line) {
130
+ const body = line.replace(/^(---|\+\+\+)\s+/, "");
131
+ const tab = body.indexOf("\t");
132
+ const raw = (tab >= 0 ? body.slice(0, tab) : body).trim().replace(/^["']|["']$/g, "");
133
+ if (raw === "/dev/null")
134
+ return "/dev/null";
135
+ return stripDiffPrefix(raw);
136
+ }
137
+ /** Parse `git diff --no-color` into aiserver.v1.FileDiff rows (CLI `Oc` / `FR`). */
138
+ export function parseUnifiedDiff(text) {
139
+ if (!text || /^\s+$/.test(text))
140
+ return [];
141
+ const diffs = [];
142
+ let current;
143
+ let chunk;
144
+ let remainingOld = 0;
145
+ let remainingNew = 0;
146
+ let oldLine = 0;
147
+ let newLine = 0;
148
+ let inHunk = false;
149
+ const startFile = (from = "", to = "") => {
150
+ current = { from, to, chunks: [], added: 0, removed: 0 };
151
+ diffs.push(current);
152
+ chunk = undefined;
153
+ inHunk = false;
154
+ };
155
+ const ensureFile = () => {
156
+ if (!current)
157
+ startFile();
158
+ };
159
+ const appendNoNewline = (line) => {
160
+ if (!chunk || chunk.lines.length === 0)
161
+ return;
162
+ chunk.lines.push(line);
163
+ };
164
+ for (const line of text.split("\n")) {
165
+ if (inHunk && chunk && current) {
166
+ if (line === "\") {
167
+ appendNoNewline(line);
168
+ continue;
169
+ }
170
+ if (line.startsWith("-")) {
171
+ chunk.lines.push(line);
172
+ current.removed++;
173
+ oldLine++;
174
+ remainingOld--;
175
+ }
176
+ else if (line.startsWith("+")) {
177
+ chunk.lines.push(line);
178
+ current.added++;
179
+ newLine++;
180
+ remainingNew--;
181
+ }
182
+ else if (line.startsWith(" ") || line === "") {
183
+ chunk.lines.push(line.length === 0 ? " " : line);
184
+ oldLine++;
185
+ newLine++;
186
+ remainingOld--;
187
+ remainingNew--;
188
+ }
189
+ else {
190
+ inHunk = false;
191
+ }
192
+ if (inHunk && remainingOld <= 0 && remainingNew <= 0)
193
+ inHunk = false;
194
+ if (inHunk)
195
+ continue;
196
+ }
197
+ if (line.startsWith("diff --git ")) {
198
+ const paths = parseDiffGitPaths(line);
199
+ startFile(paths?.from ?? "", paths?.to ?? "");
200
+ continue;
201
+ }
202
+ if (line.startsWith("new file mode ")) {
203
+ ensureFile();
204
+ current.from = "/dev/null";
205
+ continue;
206
+ }
207
+ if (line.startsWith("deleted file mode ")) {
208
+ ensureFile();
209
+ current.to = "/dev/null";
210
+ continue;
211
+ }
212
+ if (line.startsWith("--- ")) {
213
+ ensureFile();
214
+ current.from = parsePathHeader(line);
215
+ continue;
216
+ }
217
+ if (line.startsWith("+++ ")) {
218
+ ensureFile();
219
+ current.to = parsePathHeader(line);
220
+ continue;
221
+ }
222
+ const hunk = /^@@\s+-(\d+),?(\d+)?\s+\+(\d+),?(\d+)?\s@@/.exec(line);
223
+ if (hunk) {
224
+ ensureFile();
225
+ oldLine = Number(hunk[1]);
226
+ newLine = Number(hunk[3]);
227
+ remainingOld = hunk[2] !== undefined ? Number(hunk[2]) : 1;
228
+ remainingNew = hunk[4] !== undefined ? Number(hunk[4]) : 1;
229
+ chunk = {
230
+ content: line,
231
+ lines: [],
232
+ old_start: Number(hunk[1]),
233
+ old_lines: remainingOld,
234
+ new_start: Number(hunk[3]),
235
+ new_lines: remainingNew,
236
+ };
237
+ current.chunks.push(chunk);
238
+ inHunk = true;
239
+ }
240
+ }
241
+ return diffs;
242
+ }
243
+ function effectiveFormat(raw) {
244
+ switch (raw) {
245
+ case GIT_DIFF_FORMAT_NAME_STATUS:
246
+ case GIT_DIFF_FORMAT_NAME_STATUS_AND_NUMSTAT:
247
+ case GIT_DIFF_FORMAT_FILE_DIFFS:
248
+ case GIT_DIFF_FORMAT_DIFFS_WITH_BEFORE_AND_AFTER:
249
+ return raw;
250
+ default:
251
+ return GIT_DIFF_FORMAT_FILE_DIFFS;
252
+ }
253
+ }
254
+ async function collectUntrackedDiffs(cwd, maxFiles) {
255
+ if (maxFiles <= 0)
256
+ return [];
257
+ const listed = (await git(cwd, ["ls-files", "--others", "--exclude-standard"])).stdout
258
+ .split("\n")
259
+ .map((line) => line.trim())
260
+ .filter(Boolean)
261
+ .slice(0, maxFiles);
262
+ const extra = [];
263
+ for (const file of listed) {
264
+ const raw = await git(cwd, ["diff", "--no-color", "--no-index", "/dev/null", file], {
265
+ ignoreExit: true,
266
+ trim: false,
267
+ });
268
+ extra.push(...parseUnifiedDiff(raw.stdout));
269
+ }
270
+ return extra;
271
+ }
272
+ export async function executeGitDiff(request, workspaceRoot) {
273
+ const cwd = resolveGitCwd(str(request.cwd), workspaceRoot);
274
+ if (!fs.existsSync(cwd)) {
275
+ throw new Error(`git_diff cwd does not exist: ${cwd}`);
276
+ }
277
+ const inside = await git(cwd, ["rev-parse", "--is-inside-work-tree"]).catch((error) => {
278
+ throw new Error(gitErrorMessage(error, "not a git repository"));
279
+ });
280
+ if (inside.stdout.trim() !== "true") {
281
+ throw new Error("not a git repository");
282
+ }
283
+ const ref = str(request.ref);
284
+ let base = str(request.base_ref);
285
+ if (!base)
286
+ base = await resolveDefaultBase(cwd);
287
+ if (request.merge_base === true) {
288
+ base = (await git(cwd, ["merge-base", base, ref || "HEAD"])).stdout.trim();
289
+ }
290
+ const args = ["diff", "--no-color"];
291
+ if (request.include_space_changes !== true)
292
+ args.push("--ignore-space-change");
293
+ const contextLines = num(request.unified_context_lines);
294
+ if (contextLines !== undefined && contextLines > 0)
295
+ args.push(`-U${contextLines}`);
296
+ args.push(base);
297
+ if (ref)
298
+ args.push(ref);
299
+ const targetPaths = Array.isArray(request.target_paths)
300
+ ? request.target_paths.filter((item) => typeof item === "string" && item.length > 0)
301
+ : [];
302
+ if (targetPaths.length > 0) {
303
+ args.push("--");
304
+ args.push(...targetPaths);
305
+ }
306
+ const diffText = (await git(cwd, args, { trim: false })).stdout;
307
+ const diffs = parseUnifiedDiff(diffText);
308
+ diffs.push(...await collectUntrackedDiffs(cwd, num(request.max_untracked_files) ?? 0));
309
+ const format = effectiveFormat(num(request.output_format));
310
+ if (format !== GIT_DIFF_FORMAT_FILE_DIFFS && format !== GIT_DIFF_FORMAT_DIFFS_WITH_BEFORE_AND_AFTER) {
311
+ for (const diff of diffs)
312
+ diff.chunks = [];
313
+ }
314
+ const response = {
315
+ diff: {
316
+ diffs,
317
+ diff_type: 0,
318
+ },
319
+ };
320
+ if (request.return_head_sha === true) {
321
+ response.head_sha = (await git(cwd, ["rev-parse", "HEAD"])).stdout.trim();
322
+ const status = (await git(cwd, ["status", "--porcelain=v1", "--untracked-files=all"])).stdout.trim();
323
+ response.has_uncommitted_changes = status.length > 0;
324
+ }
325
+ return response;
326
+ }
327
+ function throwAndClose(execId, error) {
328
+ return [
329
+ encodeMessage("AgentClientMessage", {
330
+ exec_client_control_message: {
331
+ throw: { id: execId, error },
332
+ },
333
+ }),
334
+ encodeMessage("AgentClientMessage", {
335
+ exec_client_control_message: {
336
+ stream_close: { id: execId },
337
+ },
338
+ }),
339
+ ];
340
+ }
341
+ /**
342
+ * Answer exec #44. GetDiffResponse has no error oneof — failures use
343
+ * exec_client_control_message.throw, matching Cursor CLI `class Dc`.
344
+ */
345
+ export async function buildGitDiffExecMessages(input) {
346
+ let response;
347
+ try {
348
+ response = await executeGitDiff(input.request, input.workspaceRoot);
349
+ }
350
+ catch (error) {
351
+ return throwAndClose(input.execId, error.message || "git_diff failed");
352
+ }
353
+ const responseBytes = encodeMessage("GetDiffResponse", response);
354
+ const maxBytes = num(input.request.max_response_bytes) ?? 0;
355
+ if (maxBytes > 0 && responseBytes.byteLength > maxBytes) {
356
+ return throwAndClose(input.execId, `GetDiffResponseTooLarge: ${responseBytes.byteLength} bytes exceeds the ${maxBytes} byte limit`);
357
+ }
358
+ return [
359
+ encodeMessage("AgentClientMessage", {
360
+ exec_client_message: {
361
+ id: input.execId,
362
+ local_execution_time_ms: 0,
363
+ git_diff_response: response,
364
+ },
365
+ }),
366
+ encodeMessage("AgentClientMessage", {
367
+ exec_client_control_message: {
368
+ stream_close: { id: input.execId },
369
+ },
370
+ }),
371
+ ];
372
+ }