@stablekernel/opencode-cursor 0.1.0-rc.2 → 0.2.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.
- package/CHANGELOG.md +43 -11
- package/README.md +72 -17
- package/dist/provider/index.d.ts +8 -7
- package/dist/provider/index.js +547 -45
- package/dist/provider/index.js.map +1 -1
- package/package.json +1 -1
package/dist/provider/index.js
CHANGED
|
@@ -93,8 +93,14 @@ function latestUserMessage(prompt) {
|
|
|
93
93
|
}
|
|
94
94
|
|
|
95
95
|
// src/provider/stream-map.ts
|
|
96
|
-
var FINISH_STOP = {
|
|
97
|
-
|
|
96
|
+
var FINISH_STOP = {
|
|
97
|
+
unified: "stop",
|
|
98
|
+
raw: void 0
|
|
99
|
+
};
|
|
100
|
+
var FINISH_ERROR = {
|
|
101
|
+
unified: "error",
|
|
102
|
+
raw: void 0
|
|
103
|
+
};
|
|
98
104
|
function safeJsonString(input) {
|
|
99
105
|
try {
|
|
100
106
|
return typeof input === "string" ? input : JSON.stringify(input ?? {});
|
|
@@ -105,50 +111,488 @@ function safeJsonString(input) {
|
|
|
105
111
|
function blockToolName(name) {
|
|
106
112
|
return `cursor_${name.replace(/[^A-Za-z0-9_-]/g, "_")}`;
|
|
107
113
|
}
|
|
108
|
-
function
|
|
114
|
+
function nativeToolCall(id, toolName, input) {
|
|
109
115
|
return {
|
|
110
116
|
type: "tool-call",
|
|
111
117
|
toolCallId: id,
|
|
112
|
-
toolName
|
|
118
|
+
toolName,
|
|
113
119
|
input: safeJsonString(input),
|
|
114
120
|
providerExecuted: true,
|
|
115
121
|
dynamic: true
|
|
116
122
|
};
|
|
117
123
|
}
|
|
118
|
-
function
|
|
124
|
+
function nativeToolResult(id, toolName, result, isError) {
|
|
119
125
|
return {
|
|
120
126
|
type: "tool-result",
|
|
121
127
|
toolCallId: id,
|
|
122
|
-
toolName
|
|
128
|
+
toolName,
|
|
123
129
|
result: result ?? null,
|
|
124
130
|
isError,
|
|
125
131
|
providerExecuted: true,
|
|
126
132
|
dynamic: true
|
|
127
133
|
};
|
|
128
134
|
}
|
|
129
|
-
function
|
|
135
|
+
function toolCallObj(id, name, input) {
|
|
136
|
+
return nativeToolCall(id, blockToolName(name), input);
|
|
137
|
+
}
|
|
138
|
+
function toolResultObj(id, name, result, isError) {
|
|
139
|
+
return nativeToolResult(id, blockToolName(name), result, isError);
|
|
140
|
+
}
|
|
141
|
+
var EDIT_TOOL_NAME = "edit";
|
|
142
|
+
function isRecord(v) {
|
|
143
|
+
return typeof v === "object" && v !== null;
|
|
144
|
+
}
|
|
145
|
+
function strField(v, key) {
|
|
146
|
+
return isRecord(v) && typeof v[key] === "string" ? v[key] : void 0;
|
|
147
|
+
}
|
|
148
|
+
function numField(v, key) {
|
|
149
|
+
return isRecord(v) && typeof v[key] === "number" ? v[key] : void 0;
|
|
150
|
+
}
|
|
151
|
+
function successValue(result) {
|
|
152
|
+
return isRecord(result) && result["status"] === "success" ? result["value"] : void 0;
|
|
153
|
+
}
|
|
154
|
+
function flattenMcpContent(value) {
|
|
155
|
+
if (!isRecord(value) || !Array.isArray(value["content"])) return null;
|
|
156
|
+
const parts = value["content"].flatMap((item) => {
|
|
157
|
+
const text = isRecord(item) ? strField(item["text"], "text") : void 0;
|
|
158
|
+
if (text !== void 0) return [text];
|
|
159
|
+
if (isRecord(item) && isRecord(item["image"])) return ["[image]"];
|
|
160
|
+
return [];
|
|
161
|
+
});
|
|
162
|
+
return parts.join("\n");
|
|
163
|
+
}
|
|
164
|
+
function mcpFold(result) {
|
|
165
|
+
const text = flattenMcpContent(successValue(result));
|
|
166
|
+
if (text === null) return null;
|
|
167
|
+
return { title: "", metadata: {}, output: text };
|
|
168
|
+
}
|
|
169
|
+
function mcpInputArgs(args) {
|
|
170
|
+
return isRecord(args) ? args["args"] : void 0;
|
|
171
|
+
}
|
|
172
|
+
function webSearchProvider(args) {
|
|
173
|
+
const id = (strField(args, "providerIdentifier") ?? "").toLowerCase();
|
|
174
|
+
if (id.includes("exa")) return "exa";
|
|
175
|
+
if (id.includes("parallel")) return "parallel";
|
|
176
|
+
return void 0;
|
|
177
|
+
}
|
|
178
|
+
function isWebSearchName(name) {
|
|
179
|
+
return /web[_-]?search/i.test(name);
|
|
180
|
+
}
|
|
181
|
+
var WEBSEARCH_ADAPTER = {
|
|
182
|
+
tool: "websearch",
|
|
183
|
+
input: (args) => {
|
|
184
|
+
const query = strField(mcpInputArgs(args), "query");
|
|
185
|
+
return query !== void 0 ? { query } : {};
|
|
186
|
+
},
|
|
187
|
+
result: (value, args) => {
|
|
188
|
+
const provider = webSearchProvider(args);
|
|
189
|
+
return {
|
|
190
|
+
title: "",
|
|
191
|
+
metadata: provider ? { provider } : {},
|
|
192
|
+
output: flattenMcpContent(value) ?? ""
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
};
|
|
196
|
+
function resolveAdapter(name, _input) {
|
|
197
|
+
const exact = NATIVE_ADAPTERS[name];
|
|
198
|
+
if (exact) return exact;
|
|
199
|
+
if (isWebSearchName(name)) return WEBSEARCH_ADAPTER;
|
|
200
|
+
return void 0;
|
|
201
|
+
}
|
|
202
|
+
function mapTodoStatus(status) {
|
|
203
|
+
if (status === "inProgress") return "in_progress";
|
|
204
|
+
return typeof status === "string" ? status : "pending";
|
|
205
|
+
}
|
|
206
|
+
function mapTodos(args) {
|
|
207
|
+
const todos = isRecord(args) && Array.isArray(args["todos"]) ? args["todos"] : [];
|
|
208
|
+
return todos.flatMap(
|
|
209
|
+
(t) => isRecord(t) && typeof t["content"] === "string" ? [
|
|
210
|
+
{
|
|
211
|
+
content: t["content"],
|
|
212
|
+
status: mapTodoStatus(t["status"])
|
|
213
|
+
}
|
|
214
|
+
] : []
|
|
215
|
+
);
|
|
216
|
+
}
|
|
217
|
+
var NATIVE_ADAPTERS = {
|
|
218
|
+
// Cursor `shell` → opencode `bash` (console renderer).
|
|
219
|
+
shell: {
|
|
220
|
+
tool: "bash",
|
|
221
|
+
input: (args) => ({ command: strField(args, "command") ?? "" }),
|
|
222
|
+
result: (value, args) => {
|
|
223
|
+
if (!isRecord(value)) return null;
|
|
224
|
+
const command = strField(args, "command") ?? "";
|
|
225
|
+
const stdout = strField(value, "stdout") ?? "";
|
|
226
|
+
const stderr = strField(value, "stderr") ?? "";
|
|
227
|
+
const exit = numField(value, "exitCode");
|
|
228
|
+
const body = [stdout, stderr].filter((s) => s.length > 0).join("\n");
|
|
229
|
+
const output = exit !== void 0 && exit !== 0 ? `${body}${body ? "\n" : ""}(exit ${exit})` : body;
|
|
230
|
+
return {
|
|
231
|
+
title: command,
|
|
232
|
+
metadata: { command, output, exit: exit ?? 0 },
|
|
233
|
+
output
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
},
|
|
237
|
+
// Cursor `read` → opencode `read`.
|
|
238
|
+
read: {
|
|
239
|
+
tool: "read",
|
|
240
|
+
input: (args) => ({ filePath: strField(args, "path") ?? "" }),
|
|
241
|
+
result: (value, args) => {
|
|
242
|
+
const content = strField(value, "content");
|
|
243
|
+
if (content === void 0) return null;
|
|
244
|
+
const filePath = strField(args, "path") ?? "";
|
|
245
|
+
const totalLines = numField(value, "totalLines");
|
|
246
|
+
return {
|
|
247
|
+
title: filePath,
|
|
248
|
+
metadata: {
|
|
249
|
+
preview: content.split("\n").slice(0, 20).join("\n"),
|
|
250
|
+
loaded: [],
|
|
251
|
+
...totalLines !== void 0 ? { totalLines } : {}
|
|
252
|
+
},
|
|
253
|
+
output: content
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
},
|
|
257
|
+
// Cursor `write` → opencode `write` (renders input.content as the new file).
|
|
258
|
+
write: {
|
|
259
|
+
tool: "write",
|
|
260
|
+
input: (args) => ({
|
|
261
|
+
filePath: strField(args, "path") ?? "",
|
|
262
|
+
content: strField(args, "fileText") ?? ""
|
|
263
|
+
}),
|
|
264
|
+
result: (value, args) => {
|
|
265
|
+
const filePath = strField(args, "path") ?? "";
|
|
266
|
+
const lines = numField(value, "linesCreated");
|
|
267
|
+
const output = lines !== void 0 ? `Wrote ${lines} line${lines === 1 ? "" : "s"}.` : "Wrote file successfully.";
|
|
268
|
+
return {
|
|
269
|
+
title: filePath,
|
|
270
|
+
metadata: { diagnostics: {}, filepath: filePath, exists: false },
|
|
271
|
+
output
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
},
|
|
275
|
+
// Cursor `glob` → opencode `glob`.
|
|
276
|
+
glob: {
|
|
277
|
+
tool: "glob",
|
|
278
|
+
input: (args) => {
|
|
279
|
+
const pattern = strField(args, "globPattern") ?? "";
|
|
280
|
+
const dir = strField(args, "targetDirectory");
|
|
281
|
+
return dir ? { pattern, path: dir } : { pattern };
|
|
282
|
+
},
|
|
283
|
+
result: (value) => {
|
|
284
|
+
if (!isRecord(value) || !Array.isArray(value["files"])) return null;
|
|
285
|
+
const files = value["files"].filter(
|
|
286
|
+
(f) => typeof f === "string"
|
|
287
|
+
);
|
|
288
|
+
const truncated = value["clientTruncated"] === true || value["ripgrepTruncated"] === true;
|
|
289
|
+
return {
|
|
290
|
+
title: "",
|
|
291
|
+
metadata: { count: files.length, truncated },
|
|
292
|
+
output: files.length > 0 ? files.join("\n") : "No files found"
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
},
|
|
296
|
+
// Cursor `grep` → opencode `grep` (flatten matches into ripgrep-style text).
|
|
297
|
+
grep: {
|
|
298
|
+
tool: "grep",
|
|
299
|
+
input: (args) => {
|
|
300
|
+
const out = {
|
|
301
|
+
pattern: strField(args, "pattern") ?? ""
|
|
302
|
+
};
|
|
303
|
+
const p = strField(args, "path");
|
|
304
|
+
if (p) out["path"] = p;
|
|
305
|
+
const g = strField(args, "glob");
|
|
306
|
+
if (g) out["include"] = g;
|
|
307
|
+
return out;
|
|
308
|
+
},
|
|
309
|
+
result: (value) => {
|
|
310
|
+
if (!isRecord(value)) return null;
|
|
311
|
+
const unions = [];
|
|
312
|
+
const ws = value["workspaceResults"];
|
|
313
|
+
if (isRecord(ws)) unions.push(...Object.values(ws));
|
|
314
|
+
if (value["activeEditorResult"] !== void 0)
|
|
315
|
+
unions.push(value["activeEditorResult"]);
|
|
316
|
+
const lines = [];
|
|
317
|
+
let total = 0;
|
|
318
|
+
let current = "";
|
|
319
|
+
for (const u of unions) {
|
|
320
|
+
if (!isRecord(u)) continue;
|
|
321
|
+
const output = u["output"];
|
|
322
|
+
if (u["type"] === "content" && isRecord(output) && Array.isArray(output["matches"])) {
|
|
323
|
+
for (const m of output["matches"]) {
|
|
324
|
+
if (!isRecord(m)) continue;
|
|
325
|
+
const file = strField(m, "file") ?? "";
|
|
326
|
+
const line = numField(m, "lineNumber");
|
|
327
|
+
const text = strField(m, "line") ?? "";
|
|
328
|
+
if (current !== file) {
|
|
329
|
+
if (current) lines.push("");
|
|
330
|
+
current = file;
|
|
331
|
+
lines.push(`${file}:`);
|
|
332
|
+
}
|
|
333
|
+
lines.push(
|
|
334
|
+
line !== void 0 ? ` Line ${line}: ${text}` : ` ${text}`
|
|
335
|
+
);
|
|
336
|
+
total++;
|
|
337
|
+
}
|
|
338
|
+
} else if (u["type"] === "files" && isRecord(output) && Array.isArray(output["files"])) {
|
|
339
|
+
for (const f of output["files"]) {
|
|
340
|
+
if (typeof f === "string") {
|
|
341
|
+
lines.push(f);
|
|
342
|
+
total++;
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
return {
|
|
348
|
+
title: "",
|
|
349
|
+
metadata: { matches: total, truncated: false },
|
|
350
|
+
output: total > 0 ? [
|
|
351
|
+
`Found ${total} match${total === 1 ? "" : "es"}`,
|
|
352
|
+
"",
|
|
353
|
+
...lines
|
|
354
|
+
].join("\n") : "No matches found"
|
|
355
|
+
};
|
|
356
|
+
}
|
|
357
|
+
},
|
|
358
|
+
// Cursor `ls` → opencode `list` (flatten the directory tree into paths).
|
|
359
|
+
ls: {
|
|
360
|
+
tool: "list",
|
|
361
|
+
input: (args) => ({ path: strField(args, "path") ?? "" }),
|
|
362
|
+
result: (value) => {
|
|
363
|
+
if (!isRecord(value)) return null;
|
|
364
|
+
const root = value["directoryTreeRoot"];
|
|
365
|
+
if (!isRecord(root)) return null;
|
|
366
|
+
const out = [];
|
|
367
|
+
const walk = (node) => {
|
|
368
|
+
const base = strField(node, "absPath") ?? "";
|
|
369
|
+
const files = Array.isArray(node["childrenFiles"]) ? node["childrenFiles"] : [];
|
|
370
|
+
for (const f of files) {
|
|
371
|
+
const name = strField(f, "name");
|
|
372
|
+
if (name) out.push(`${base}/${name}`);
|
|
373
|
+
}
|
|
374
|
+
const dirs = Array.isArray(node["childrenDirs"]) ? node["childrenDirs"] : [];
|
|
375
|
+
for (const d of dirs) {
|
|
376
|
+
if (!isRecord(d)) continue;
|
|
377
|
+
out.push(`${strField(d, "absPath") ?? ""}/`);
|
|
378
|
+
walk(d);
|
|
379
|
+
}
|
|
380
|
+
};
|
|
381
|
+
walk(root);
|
|
382
|
+
return {
|
|
383
|
+
title: strField(root, "absPath") ?? "",
|
|
384
|
+
metadata: {},
|
|
385
|
+
output: out.length > 0 ? out.join("\n") : "(empty)"
|
|
386
|
+
};
|
|
387
|
+
}
|
|
388
|
+
},
|
|
389
|
+
// Cursor `updateTodos` → opencode `todowrite` (todo checklist renderer).
|
|
390
|
+
updateTodos: {
|
|
391
|
+
tool: "todowrite",
|
|
392
|
+
input: (args) => ({ todos: mapTodos(args) }),
|
|
393
|
+
result: (_value, args) => {
|
|
394
|
+
const todos = mapTodos(args);
|
|
395
|
+
const done = todos.filter((t) => t.status === "completed").length;
|
|
396
|
+
return {
|
|
397
|
+
title: `${done}/${todos.length}`,
|
|
398
|
+
metadata: { todos },
|
|
399
|
+
output: `Updated ${todos.length} todo${todos.length === 1 ? "" : "s"}.`
|
|
400
|
+
};
|
|
401
|
+
}
|
|
402
|
+
},
|
|
403
|
+
// Cursor `task` (subagent) → opencode `task` (agent card: name + description).
|
|
404
|
+
// Non-clickable here — the subagent ran inside Cursor, not as an opencode
|
|
405
|
+
// child session — but the native card reads far better than raw JSON.
|
|
406
|
+
task: {
|
|
407
|
+
tool: "task",
|
|
408
|
+
input: (args) => {
|
|
409
|
+
const description = strField(args, "description") ?? "";
|
|
410
|
+
const sub = isRecord(args) ? args["subagentType"] : void 0;
|
|
411
|
+
const subagent = strField(sub, "name") ?? strField(sub, "kind") ?? void 0;
|
|
412
|
+
return subagent ? { description, subagent_type: subagent } : { description };
|
|
413
|
+
},
|
|
414
|
+
result: (value, args) => {
|
|
415
|
+
const description = strField(args, "description") ?? "";
|
|
416
|
+
const suffix = strField(value, "resultSuffix");
|
|
417
|
+
const background = isRecord(value) && value["isBackground"] === true;
|
|
418
|
+
return {
|
|
419
|
+
title: description,
|
|
420
|
+
metadata: background ? { background: true } : {},
|
|
421
|
+
output: suffix ?? "Subagent task completed."
|
|
422
|
+
};
|
|
423
|
+
}
|
|
424
|
+
},
|
|
425
|
+
// Cursor `readLints` has no opencode counterpart — format-only: render the
|
|
426
|
+
// diagnostics as a readable list instead of dumping the nested JSON.
|
|
427
|
+
readLints: {
|
|
428
|
+
input: (args) => {
|
|
429
|
+
const paths = isRecord(args) && Array.isArray(args["paths"]) ? args["paths"] : [];
|
|
430
|
+
return { paths };
|
|
431
|
+
},
|
|
432
|
+
result: (value) => {
|
|
433
|
+
const files = isRecord(value) && Array.isArray(value["fileDiagnostics"]) ? value["fileDiagnostics"] : [];
|
|
434
|
+
const lines = [];
|
|
435
|
+
let total = 0;
|
|
436
|
+
for (const file of files) {
|
|
437
|
+
if (!isRecord(file)) continue;
|
|
438
|
+
const diags = Array.isArray(file["diagnostics"]) ? file["diagnostics"] : [];
|
|
439
|
+
if (diags.length === 0) continue;
|
|
440
|
+
lines.push(`${strField(file, "path") ?? ""}`);
|
|
441
|
+
for (const d of diags) {
|
|
442
|
+
if (!isRecord(d)) continue;
|
|
443
|
+
const severity = strField(d, "severity") ?? "info";
|
|
444
|
+
const start = isRecord(d["range"]) ? d["range"]["start"] : void 0;
|
|
445
|
+
const line = numField(start, "line");
|
|
446
|
+
const char = numField(start, "character");
|
|
447
|
+
const loc = line !== void 0 ? ` L${line + 1}${char !== void 0 ? `:${char + 1}` : ""}` : "";
|
|
448
|
+
lines.push(` ${severity}${loc}: ${strField(d, "message") ?? ""}`);
|
|
449
|
+
total++;
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
return {
|
|
453
|
+
title: total > 0 ? `${total} problem${total === 1 ? "" : "s"}` : "No problems",
|
|
454
|
+
metadata: { count: total },
|
|
455
|
+
output: total > 0 ? lines.join("\n") : "No problems found."
|
|
456
|
+
};
|
|
457
|
+
}
|
|
458
|
+
},
|
|
459
|
+
// Cursor `delete` has no opencode counterpart — format-only: a one-line
|
|
460
|
+
// confirmation instead of `{"fileSize":N}`.
|
|
461
|
+
delete: {
|
|
462
|
+
input: (args) => ({ path: strField(args, "path") ?? "" }),
|
|
463
|
+
result: (value, args) => {
|
|
464
|
+
const path = strField(args, "path") ?? "";
|
|
465
|
+
const size = numField(value, "fileSize");
|
|
466
|
+
return {
|
|
467
|
+
title: path,
|
|
468
|
+
metadata: {},
|
|
469
|
+
output: size !== void 0 ? `Deleted ${path} (${size} bytes).` : `Deleted ${path}.`
|
|
470
|
+
};
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
};
|
|
474
|
+
function editFilePath(input) {
|
|
475
|
+
return isRecord(input) && typeof input["path"] === "string" ? input["path"] : "";
|
|
476
|
+
}
|
|
477
|
+
function editDiffString(result) {
|
|
478
|
+
if (!isRecord(result) || result["status"] !== "success") return null;
|
|
479
|
+
const value = result["value"];
|
|
480
|
+
if (!isRecord(value)) return null;
|
|
481
|
+
const diff = value["diffString"];
|
|
482
|
+
return typeof diff === "string" && diff.length > 0 ? diff : null;
|
|
483
|
+
}
|
|
484
|
+
function reconstructEditStrings(diff) {
|
|
485
|
+
const oldLines = [];
|
|
486
|
+
const newLines = [];
|
|
487
|
+
for (const line of diff.split("\n")) {
|
|
488
|
+
if (line.startsWith("---") || line.startsWith("+++") || line.startsWith("@@") || line.startsWith("Index:") || line.startsWith("===")) {
|
|
489
|
+
continue;
|
|
490
|
+
}
|
|
491
|
+
if (line.startsWith("-")) oldLines.push(line.slice(1));
|
|
492
|
+
else if (line.startsWith("+")) newLines.push(line.slice(1));
|
|
493
|
+
}
|
|
494
|
+
return { oldString: oldLines.join("\n"), newString: newLines.join("\n") };
|
|
495
|
+
}
|
|
496
|
+
function editCallFields(id, filePath, diff) {
|
|
497
|
+
const { oldString, newString } = reconstructEditStrings(diff);
|
|
130
498
|
return {
|
|
131
499
|
type: "tool-call",
|
|
132
500
|
toolCallId: id,
|
|
133
|
-
toolName:
|
|
134
|
-
input: safeJsonString(
|
|
501
|
+
toolName: EDIT_TOOL_NAME,
|
|
502
|
+
input: safeJsonString({ filePath, oldString, newString }),
|
|
135
503
|
providerExecuted: true,
|
|
136
504
|
dynamic: true
|
|
137
505
|
};
|
|
138
506
|
}
|
|
139
|
-
function
|
|
507
|
+
function editResultFields(id, filePath, diff, result) {
|
|
508
|
+
const value = isRecord(result) ? result["value"] : void 0;
|
|
509
|
+
const added = isRecord(value) && typeof value["linesAdded"] === "number" ? value["linesAdded"] : void 0;
|
|
510
|
+
const removed = isRecord(value) && typeof value["linesRemoved"] === "number" ? value["linesRemoved"] : void 0;
|
|
511
|
+
const counts = added !== void 0 || removed !== void 0 ? ` (+${added ?? 0}/-${removed ?? 0})` : "";
|
|
140
512
|
return {
|
|
141
513
|
type: "tool-result",
|
|
142
514
|
toolCallId: id,
|
|
143
|
-
toolName:
|
|
144
|
-
result:
|
|
145
|
-
|
|
515
|
+
toolName: EDIT_TOOL_NAME,
|
|
516
|
+
result: {
|
|
517
|
+
title: filePath,
|
|
518
|
+
metadata: { diff, diagnostics: {} },
|
|
519
|
+
output: `Edit applied${counts}.`
|
|
520
|
+
},
|
|
521
|
+
isError: false,
|
|
146
522
|
providerExecuted: true,
|
|
147
523
|
dynamic: true
|
|
148
524
|
};
|
|
149
525
|
}
|
|
526
|
+
function newBlockToolState() {
|
|
527
|
+
return { open: /* @__PURE__ */ new Map(), pendingEdits: /* @__PURE__ */ new Map() };
|
|
528
|
+
}
|
|
529
|
+
function blockToolCallParts(id, name, input, state) {
|
|
530
|
+
if (name === EDIT_TOOL_NAME) {
|
|
531
|
+
state.pendingEdits.set(id, editFilePath(input));
|
|
532
|
+
return [];
|
|
533
|
+
}
|
|
534
|
+
const adapter = resolveAdapter(name, input);
|
|
535
|
+
if (adapter) {
|
|
536
|
+
const toolName2 = adapter.tool ?? blockToolName(name);
|
|
537
|
+
state.open.set(id, { toolName: toolName2, adapter, args: input });
|
|
538
|
+
return [nativeToolCall(id, toolName2, adapter.input(input))];
|
|
539
|
+
}
|
|
540
|
+
const toolName = blockToolName(name);
|
|
541
|
+
state.open.set(id, { toolName, args: input });
|
|
542
|
+
return [nativeToolCall(id, toolName, input)];
|
|
543
|
+
}
|
|
544
|
+
function blockToolResultParts(id, name, result, isError, state) {
|
|
545
|
+
if (state.pendingEdits.has(id)) {
|
|
546
|
+
const filePath = state.pendingEdits.get(id);
|
|
547
|
+
state.pendingEdits.delete(id);
|
|
548
|
+
const diff = isError ? null : editDiffString(result);
|
|
549
|
+
if (diff && filePath) {
|
|
550
|
+
return [
|
|
551
|
+
editCallFields(id, filePath, diff),
|
|
552
|
+
editResultFields(id, filePath, diff, result)
|
|
553
|
+
];
|
|
554
|
+
}
|
|
555
|
+
return [
|
|
556
|
+
toolCallObj(id, EDIT_TOOL_NAME, { path: filePath }),
|
|
557
|
+
toolResultObj(id, EDIT_TOOL_NAME, result, isError)
|
|
558
|
+
];
|
|
559
|
+
}
|
|
560
|
+
const open = state.open.get(id);
|
|
561
|
+
state.open.delete(id);
|
|
562
|
+
const toolName = open?.toolName ?? blockToolName(name);
|
|
563
|
+
if (open?.adapter && !isError) {
|
|
564
|
+
const value = successValue(result);
|
|
565
|
+
if (value !== void 0) {
|
|
566
|
+
const folded = open.adapter.result(value, open.args);
|
|
567
|
+
if (folded) return [nativeToolResult(id, toolName, folded, false)];
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
if (!isError) {
|
|
571
|
+
const folded = mcpFold(result);
|
|
572
|
+
if (folded) return [nativeToolResult(id, toolName, folded, false)];
|
|
573
|
+
}
|
|
574
|
+
return [nativeToolResult(id, toolName, result, isError)];
|
|
575
|
+
}
|
|
576
|
+
function blockDanglingParts(state) {
|
|
577
|
+
const parts = [];
|
|
578
|
+
for (const [id, open] of state.open) {
|
|
579
|
+
parts.push(nativeToolResult(id, open.toolName, DANGLING_TOOL_RESULT, true));
|
|
580
|
+
}
|
|
581
|
+
state.open.clear();
|
|
582
|
+
for (const [id, filePath] of state.pendingEdits) {
|
|
583
|
+
parts.push(toolCallObj(id, EDIT_TOOL_NAME, { path: filePath }));
|
|
584
|
+
parts.push(toolResultObj(id, EDIT_TOOL_NAME, DANGLING_TOOL_RESULT, true));
|
|
585
|
+
}
|
|
586
|
+
state.pendingEdits.clear();
|
|
587
|
+
return parts;
|
|
588
|
+
}
|
|
150
589
|
var EMPTY_USAGE = {
|
|
151
|
-
inputTokens: {
|
|
590
|
+
inputTokens: {
|
|
591
|
+
total: void 0,
|
|
592
|
+
noCache: void 0,
|
|
593
|
+
cacheRead: void 0,
|
|
594
|
+
cacheWrite: void 0
|
|
595
|
+
},
|
|
152
596
|
outputTokens: { total: void 0, text: void 0, reasoning: void 0 }
|
|
153
597
|
};
|
|
154
598
|
function mapUsage(usage) {
|
|
@@ -159,14 +603,19 @@ function mapUsage(usage) {
|
|
|
159
603
|
cacheRead: usage.cacheReadTokens,
|
|
160
604
|
cacheWrite: usage.cacheWriteTokens
|
|
161
605
|
},
|
|
162
|
-
outputTokens: {
|
|
606
|
+
outputTokens: {
|
|
607
|
+
total: usage.outputTokens,
|
|
608
|
+
text: void 0,
|
|
609
|
+
reasoning: void 0
|
|
610
|
+
}
|
|
163
611
|
};
|
|
164
612
|
}
|
|
165
613
|
function formatToolCall(name, input) {
|
|
166
614
|
let arg = "";
|
|
167
615
|
try {
|
|
168
616
|
const s = typeof input === "string" ? input : JSON.stringify(input);
|
|
169
|
-
if (s && s !== "{}" && s !== '""')
|
|
617
|
+
if (s && s !== "{}" && s !== '""')
|
|
618
|
+
arg = ` ${s.length > 120 ? `${s.slice(0, 120)}\u2026` : s}`;
|
|
170
619
|
} catch {
|
|
171
620
|
}
|
|
172
621
|
return `[tool] ${name}${arg}`;
|
|
@@ -175,7 +624,7 @@ var DANGLING_TOOL_RESULT = {
|
|
|
175
624
|
status: "error",
|
|
176
625
|
error: "Cursor run ended before this tool call completed."
|
|
177
626
|
};
|
|
178
|
-
function cursorEventsToStream(events, toolDisplay = "
|
|
627
|
+
function cursorEventsToStream(events, toolDisplay = "blocks") {
|
|
179
628
|
return new ReadableStream({
|
|
180
629
|
async start(controller) {
|
|
181
630
|
controller.enqueue({ type: "stream-start", warnings: [] });
|
|
@@ -185,12 +634,11 @@ function cursorEventsToStream(events, toolDisplay = "reasoning") {
|
|
|
185
634
|
let reasoningCount = 0;
|
|
186
635
|
let usage;
|
|
187
636
|
let streamedText = false;
|
|
188
|
-
const
|
|
637
|
+
const toolState = newBlockToolState();
|
|
189
638
|
const closeDanglingToolCalls = () => {
|
|
190
|
-
for (const
|
|
191
|
-
controller.enqueue(
|
|
639
|
+
for (const part of blockDanglingParts(toolState)) {
|
|
640
|
+
controller.enqueue(part);
|
|
192
641
|
}
|
|
193
|
-
openToolCalls.clear();
|
|
194
642
|
};
|
|
195
643
|
const closeReasoning = () => {
|
|
196
644
|
if (reasoningId) {
|
|
@@ -221,22 +669,36 @@ function cursorEventsToStream(events, toolDisplay = "reasoning") {
|
|
|
221
669
|
return reasoningId;
|
|
222
670
|
};
|
|
223
671
|
const reasoningLine = (text) => {
|
|
224
|
-
controller.enqueue({
|
|
672
|
+
controller.enqueue({
|
|
673
|
+
type: "reasoning-delta",
|
|
674
|
+
id: ensureReasoning(),
|
|
675
|
+
delta: text
|
|
676
|
+
});
|
|
225
677
|
};
|
|
226
678
|
try {
|
|
227
679
|
for await (const event of events) {
|
|
228
680
|
switch (event.type) {
|
|
229
681
|
case "text-delta":
|
|
230
682
|
streamedText = true;
|
|
231
|
-
controller.enqueue({
|
|
683
|
+
controller.enqueue({
|
|
684
|
+
type: "text-delta",
|
|
685
|
+
id: ensureText(),
|
|
686
|
+
delta: event.text
|
|
687
|
+
});
|
|
232
688
|
break;
|
|
233
689
|
case "reasoning-delta":
|
|
234
690
|
reasoningLine(event.text);
|
|
235
691
|
break;
|
|
236
692
|
case "tool-call":
|
|
237
693
|
if (toolDisplay === "blocks") {
|
|
238
|
-
|
|
239
|
-
|
|
694
|
+
for (const part of blockToolCallParts(
|
|
695
|
+
event.id,
|
|
696
|
+
event.name,
|
|
697
|
+
event.input,
|
|
698
|
+
toolState
|
|
699
|
+
)) {
|
|
700
|
+
controller.enqueue(part);
|
|
701
|
+
}
|
|
240
702
|
} else {
|
|
241
703
|
reasoningLine(`
|
|
242
704
|
${formatToolCall(event.name, event.input)}
|
|
@@ -245,10 +707,15 @@ ${formatToolCall(event.name, event.input)}
|
|
|
245
707
|
break;
|
|
246
708
|
case "tool-result":
|
|
247
709
|
if (toolDisplay === "blocks") {
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
710
|
+
for (const part of blockToolResultParts(
|
|
711
|
+
event.id,
|
|
712
|
+
event.name,
|
|
713
|
+
event.result,
|
|
714
|
+
event.isError,
|
|
715
|
+
toolState
|
|
716
|
+
)) {
|
|
717
|
+
controller.enqueue(part);
|
|
718
|
+
}
|
|
252
719
|
} else if (event.isError) {
|
|
253
720
|
reasoningLine(`[tool] ${event.name} failed
|
|
254
721
|
`);
|
|
@@ -259,7 +726,11 @@ ${formatToolCall(event.name, event.input)}
|
|
|
259
726
|
break;
|
|
260
727
|
case "finish":
|
|
261
728
|
if (!streamedText && event.text) {
|
|
262
|
-
controller.enqueue({
|
|
729
|
+
controller.enqueue({
|
|
730
|
+
type: "text-delta",
|
|
731
|
+
id: ensureText(),
|
|
732
|
+
delta: event.text
|
|
733
|
+
});
|
|
263
734
|
}
|
|
264
735
|
break;
|
|
265
736
|
}
|
|
@@ -267,23 +738,31 @@ ${formatToolCall(event.name, event.input)}
|
|
|
267
738
|
closeDanglingToolCalls();
|
|
268
739
|
closeReasoning();
|
|
269
740
|
closeText();
|
|
270
|
-
controller.enqueue({
|
|
741
|
+
controller.enqueue({
|
|
742
|
+
type: "finish",
|
|
743
|
+
usage: usage ?? EMPTY_USAGE,
|
|
744
|
+
finishReason: FINISH_STOP
|
|
745
|
+
});
|
|
271
746
|
controller.close();
|
|
272
747
|
} catch (err) {
|
|
273
748
|
controller.enqueue({ type: "error", error: err });
|
|
274
749
|
closeDanglingToolCalls();
|
|
275
750
|
closeReasoning();
|
|
276
751
|
closeText();
|
|
277
|
-
controller.enqueue({
|
|
752
|
+
controller.enqueue({
|
|
753
|
+
type: "finish",
|
|
754
|
+
usage: usage ?? EMPTY_USAGE,
|
|
755
|
+
finishReason: FINISH_ERROR
|
|
756
|
+
});
|
|
278
757
|
controller.close();
|
|
279
758
|
}
|
|
280
759
|
}
|
|
281
760
|
});
|
|
282
761
|
}
|
|
283
|
-
async function cursorEventsToContent(events, toolDisplay = "
|
|
762
|
+
async function cursorEventsToContent(events, toolDisplay = "blocks") {
|
|
284
763
|
const content = [];
|
|
285
764
|
const toolParts = [];
|
|
286
|
-
const
|
|
765
|
+
const toolState = newBlockToolState();
|
|
287
766
|
let text = "";
|
|
288
767
|
let reasoning = "";
|
|
289
768
|
let usage = EMPTY_USAGE;
|
|
@@ -299,8 +778,14 @@ async function cursorEventsToContent(events, toolDisplay = "reasoning") {
|
|
|
299
778
|
break;
|
|
300
779
|
case "tool-call":
|
|
301
780
|
if (toolDisplay === "blocks") {
|
|
302
|
-
|
|
303
|
-
|
|
781
|
+
for (const part of blockToolCallParts(
|
|
782
|
+
event.id,
|
|
783
|
+
event.name,
|
|
784
|
+
event.input,
|
|
785
|
+
toolState
|
|
786
|
+
)) {
|
|
787
|
+
toolParts.push(part);
|
|
788
|
+
}
|
|
304
789
|
} else {
|
|
305
790
|
reasoning += `
|
|
306
791
|
${formatToolCall(event.name, event.input)}
|
|
@@ -309,8 +794,15 @@ ${formatToolCall(event.name, event.input)}
|
|
|
309
794
|
break;
|
|
310
795
|
case "tool-result":
|
|
311
796
|
if (toolDisplay === "blocks") {
|
|
312
|
-
|
|
313
|
-
|
|
797
|
+
for (const part of blockToolResultParts(
|
|
798
|
+
event.id,
|
|
799
|
+
event.name,
|
|
800
|
+
event.result,
|
|
801
|
+
event.isError,
|
|
802
|
+
toolState
|
|
803
|
+
)) {
|
|
804
|
+
toolParts.push(part);
|
|
805
|
+
}
|
|
314
806
|
} else if (event.isError) {
|
|
315
807
|
reasoning += `[tool] ${event.name} failed
|
|
316
808
|
`;
|
|
@@ -327,10 +819,9 @@ ${formatToolCall(event.name, event.input)}
|
|
|
327
819
|
} catch {
|
|
328
820
|
finishReason = FINISH_ERROR;
|
|
329
821
|
}
|
|
330
|
-
for (const
|
|
331
|
-
toolParts.push(
|
|
822
|
+
for (const part of blockDanglingParts(toolState)) {
|
|
823
|
+
toolParts.push(part);
|
|
332
824
|
}
|
|
333
|
-
openToolCalls.clear();
|
|
334
825
|
if (reasoning) content.push({ type: "reasoning", text: reasoning });
|
|
335
826
|
content.push(...toolParts);
|
|
336
827
|
if (text) content.push({ type: "text", text });
|
|
@@ -385,16 +876,27 @@ var CursorLanguageModel = class {
|
|
|
385
876
|
});
|
|
386
877
|
const message = acquired.resumed ? latestUserMessage(options.prompt) ?? promptToCursorMessage(options.prompt) : promptToCursorMessage(options.prompt);
|
|
387
878
|
try {
|
|
388
|
-
yield* streamAgentTurn(acquired.agent, message, {
|
|
879
|
+
yield* streamAgentTurn(acquired.agent, message, {
|
|
880
|
+
mode,
|
|
881
|
+
abortSignal: options.abortSignal
|
|
882
|
+
});
|
|
389
883
|
} finally {
|
|
390
884
|
acquired.release();
|
|
391
885
|
}
|
|
392
886
|
}
|
|
393
887
|
async doStream(options) {
|
|
394
|
-
return {
|
|
888
|
+
return {
|
|
889
|
+
stream: cursorEventsToStream(
|
|
890
|
+
this.agentRun(options),
|
|
891
|
+
this.config.toolDisplay
|
|
892
|
+
)
|
|
893
|
+
};
|
|
395
894
|
}
|
|
396
895
|
async doGenerate(options) {
|
|
397
|
-
const result = await cursorEventsToContent(
|
|
896
|
+
const result = await cursorEventsToContent(
|
|
897
|
+
this.agentRun(options),
|
|
898
|
+
this.config.toolDisplay
|
|
899
|
+
);
|
|
398
900
|
return { ...result, warnings: [] };
|
|
399
901
|
}
|
|
400
902
|
};
|
|
@@ -413,7 +915,7 @@ function createCursor(options = {}) {
|
|
|
413
915
|
...options.sandbox !== void 0 ? { sandbox: options.sandbox } : {},
|
|
414
916
|
...options.agents ? { agents: options.agents } : {},
|
|
415
917
|
...options.session !== void 0 ? { session: options.session } : {},
|
|
416
|
-
|
|
918
|
+
toolDisplay: options.toolDisplay ?? "blocks"
|
|
417
919
|
};
|
|
418
920
|
const notImplemented = (kind, modelId) => {
|
|
419
921
|
throw new NoSuchModelError({
|