@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.
@@ -93,8 +93,14 @@ function latestUserMessage(prompt) {
93
93
  }
94
94
 
95
95
  // src/provider/stream-map.ts
96
- var FINISH_STOP = { unified: "stop", raw: void 0 };
97
- var FINISH_ERROR = { unified: "error", raw: void 0 };
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 toolCallPart(id, name, input) {
114
+ function nativeToolCall(id, toolName, input) {
109
115
  return {
110
116
  type: "tool-call",
111
117
  toolCallId: id,
112
- toolName: blockToolName(name),
118
+ toolName,
113
119
  input: safeJsonString(input),
114
120
  providerExecuted: true,
115
121
  dynamic: true
116
122
  };
117
123
  }
118
- function toolResultPart(id, name, result, isError) {
124
+ function nativeToolResult(id, toolName, result, isError) {
119
125
  return {
120
126
  type: "tool-result",
121
127
  toolCallId: id,
122
- toolName: blockToolName(name),
128
+ toolName,
123
129
  result: result ?? null,
124
130
  isError,
125
131
  providerExecuted: true,
126
132
  dynamic: true
127
133
  };
128
134
  }
129
- function toolCallContent(id, name, input) {
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: blockToolName(name),
134
- input: safeJsonString(input),
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 toolResultContent(id, name, result, isError) {
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: blockToolName(name),
144
- result: result ?? null,
145
- isError,
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: { total: void 0, noCache: void 0, cacheRead: void 0, cacheWrite: void 0 },
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: { total: usage.outputTokens, text: void 0, reasoning: void 0 }
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 !== '""') arg = ` ${s.length > 120 ? `${s.slice(0, 120)}\u2026` : 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 = "reasoning") {
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 openToolCalls = /* @__PURE__ */ new Map();
637
+ const toolState = newBlockToolState();
189
638
  const closeDanglingToolCalls = () => {
190
- for (const [id, name] of openToolCalls) {
191
- controller.enqueue(toolResultPart(id, name, DANGLING_TOOL_RESULT, true));
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({ type: "reasoning-delta", id: ensureReasoning(), delta: text });
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({ type: "text-delta", id: ensureText(), delta: event.text });
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
- openToolCalls.set(event.id, event.name);
239
- controller.enqueue(toolCallPart(event.id, event.name, event.input));
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
- openToolCalls.delete(event.id);
249
- controller.enqueue(
250
- toolResultPart(event.id, event.name, event.result, event.isError)
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({ type: "text-delta", id: ensureText(), delta: event.text });
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({ type: "finish", usage: usage ?? EMPTY_USAGE, finishReason: FINISH_STOP });
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({ type: "finish", usage: usage ?? EMPTY_USAGE, finishReason: FINISH_ERROR });
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 = "reasoning") {
762
+ async function cursorEventsToContent(events, toolDisplay = "blocks") {
284
763
  const content = [];
285
764
  const toolParts = [];
286
- const openToolCalls = /* @__PURE__ */ new Map();
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
- openToolCalls.set(event.id, event.name);
303
- toolParts.push(toolCallContent(event.id, event.name, event.input));
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
- openToolCalls.delete(event.id);
313
- toolParts.push(toolResultContent(event.id, event.name, event.result, event.isError));
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 [id, name] of openToolCalls) {
331
- toolParts.push(toolResultContent(id, name, DANGLING_TOOL_RESULT, true));
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, { mode, abortSignal: options.abortSignal });
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 { stream: cursorEventsToStream(this.agentRun(options), this.config.toolDisplay) };
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(this.agentRun(options), this.config.toolDisplay);
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
- ...options.toolDisplay ? { toolDisplay: options.toolDisplay } : {}
918
+ toolDisplay: options.toolDisplay ?? "blocks"
417
919
  };
418
920
  const notImplemented = (kind, modelId) => {
419
921
  throw new NoSuchModelError({