@stablekernel/opencode-cursor 0.4.7-next.0 → 0.5.0-next.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 +50 -0
- package/README.md +55 -16
- package/dist/{chunk-SVIXYMHP.js → chunk-LAOFD3JB.js} +487 -292
- package/dist/chunk-LAOFD3JB.js.map +1 -0
- package/dist/plugin/index.js +28 -4
- package/dist/plugin/index.js.map +1 -1
- package/dist/provider/index.d.ts +12 -0
- package/dist/provider/index.js +205 -23
- package/dist/provider/index.js.map +1 -1
- package/dist/sidecar/agent-host.d.ts +9 -2
- package/dist/sidecar/agent-host.js +7 -1
- package/dist/sidecar/agent-host.js.map +1 -1
- package/package.json +6 -5
- package/dist/chunk-SVIXYMHP.js.map +0 -1
package/dist/provider/index.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
acquireAgent,
|
|
3
|
+
addUsage,
|
|
4
|
+
classifyError,
|
|
3
5
|
dropSessionRecord,
|
|
4
6
|
extractSystemText,
|
|
5
7
|
getSessionRecord,
|
|
@@ -7,8 +9,9 @@ import {
|
|
|
7
9
|
resolveCursorApiKey,
|
|
8
10
|
resolveSystemDelivery,
|
|
9
11
|
sendAgentTurnSilently,
|
|
12
|
+
setPreferredTransport,
|
|
10
13
|
streamAgentTurn
|
|
11
|
-
} from "../chunk-
|
|
14
|
+
} from "../chunk-LAOFD3JB.js";
|
|
12
15
|
|
|
13
16
|
// src/provider/index.ts
|
|
14
17
|
import { NoSuchModelError } from "@ai-sdk/provider";
|
|
@@ -420,6 +423,26 @@ var NATIVE_ADAPTERS = {
|
|
|
420
423
|
};
|
|
421
424
|
}
|
|
422
425
|
},
|
|
426
|
+
// Cursor `semSearch` has no opencode counterpart — format-only: query title
|
|
427
|
+
// + results body instead of the raw `{results}` JSON.
|
|
428
|
+
semSearch: {
|
|
429
|
+
input: (args) => {
|
|
430
|
+
const out = { query: strField(args, "query") ?? "" };
|
|
431
|
+
const dirs = isRecord(args) && Array.isArray(args["targetDirectories"]) ? args["targetDirectories"] : void 0;
|
|
432
|
+
if (dirs && dirs.length > 0) out["targetDirectories"] = dirs;
|
|
433
|
+
return out;
|
|
434
|
+
},
|
|
435
|
+
result: (value, args) => {
|
|
436
|
+
const query = strField(args, "query") ?? "";
|
|
437
|
+
const results = strField(value, "results") ?? "";
|
|
438
|
+
const count = results ? results.split("\n").filter((l) => l.trim().length > 0).length : 0;
|
|
439
|
+
return {
|
|
440
|
+
title: query,
|
|
441
|
+
metadata: { matches: count, truncated: false },
|
|
442
|
+
output: results || "No results"
|
|
443
|
+
};
|
|
444
|
+
}
|
|
445
|
+
},
|
|
423
446
|
// Cursor `ls` → opencode `list` (flatten the directory tree into paths).
|
|
424
447
|
ls: {
|
|
425
448
|
tool: "list",
|
|
@@ -589,7 +612,39 @@ function editResultFields(id, filePath, diff, result) {
|
|
|
589
612
|
};
|
|
590
613
|
}
|
|
591
614
|
function newBlockToolState() {
|
|
592
|
-
return {
|
|
615
|
+
return {
|
|
616
|
+
open: /* @__PURE__ */ new Map(),
|
|
617
|
+
pendingEdits: /* @__PURE__ */ new Map(),
|
|
618
|
+
dropped: /* @__PURE__ */ new Set(),
|
|
619
|
+
partials: /* @__PURE__ */ new Map(),
|
|
620
|
+
planText: /* @__PURE__ */ new Map()
|
|
621
|
+
};
|
|
622
|
+
}
|
|
623
|
+
function resolveToolName(name, input) {
|
|
624
|
+
const adapter = resolveAdapter(name, input);
|
|
625
|
+
return {
|
|
626
|
+
toolName: adapter?.tool ?? blockToolName(name),
|
|
627
|
+
...adapter ? { adapter } : {}
|
|
628
|
+
};
|
|
629
|
+
}
|
|
630
|
+
function blockToolInputPartialParts(id, name, input, state) {
|
|
631
|
+
if (process.env["OPENCODE_CURSOR_TOOL_INPUT_STREAM"] === "0") return [];
|
|
632
|
+
const serialized = safeJsonString(input);
|
|
633
|
+
const prev = state.partials.get(id);
|
|
634
|
+
const toolName = prev?.toolName ?? resolveToolName(name, input).toolName;
|
|
635
|
+
state.partials.set(id, { toolName, serialized });
|
|
636
|
+
const parts = [];
|
|
637
|
+
if (!prev)
|
|
638
|
+
parts.push({
|
|
639
|
+
type: "tool-input-start",
|
|
640
|
+
id,
|
|
641
|
+
toolName,
|
|
642
|
+
providerExecuted: true,
|
|
643
|
+
dynamic: true
|
|
644
|
+
});
|
|
645
|
+
const delta = prev && serialized.startsWith(prev.serialized) ? serialized.slice(prev.serialized.length) : serialized;
|
|
646
|
+
if (delta) parts.push({ type: "tool-input-delta", id, delta });
|
|
647
|
+
return parts;
|
|
593
648
|
}
|
|
594
649
|
function blockToolCallParts(id, name, input, state) {
|
|
595
650
|
if (name === EDIT_TOOL_NAME) {
|
|
@@ -649,6 +704,20 @@ function blockDanglingParts(state) {
|
|
|
649
704
|
parts.push(toolResultObj(id, EDIT_TOOL_NAME, DANGLING_TOOL_RESULT, true));
|
|
650
705
|
}
|
|
651
706
|
state.pendingEdits.clear();
|
|
707
|
+
for (const [id, partial] of state.partials) {
|
|
708
|
+
parts.push({ type: "tool-input-end", id });
|
|
709
|
+
let args = {};
|
|
710
|
+
try {
|
|
711
|
+
args = JSON.parse(partial.serialized);
|
|
712
|
+
} catch {
|
|
713
|
+
args = {};
|
|
714
|
+
}
|
|
715
|
+
parts.push(nativeToolCall(id, partial.toolName, args));
|
|
716
|
+
parts.push(
|
|
717
|
+
nativeToolResult(id, partial.toolName, DANGLING_TOOL_RESULT, true)
|
|
718
|
+
);
|
|
719
|
+
}
|
|
720
|
+
state.partials.clear();
|
|
652
721
|
return parts;
|
|
653
722
|
}
|
|
654
723
|
var EMPTY_USAGE = {
|
|
@@ -699,6 +768,8 @@ function cursorEventsToStream(events, toolDisplay = "blocks") {
|
|
|
699
768
|
let reasoningCount = 0;
|
|
700
769
|
let usage;
|
|
701
770
|
let streamedText = false;
|
|
771
|
+
let thinkingMs = 0;
|
|
772
|
+
let compactions = 0;
|
|
702
773
|
const toolState = newBlockToolState();
|
|
703
774
|
const closeDanglingToolCalls = () => {
|
|
704
775
|
for (const part of blockDanglingParts(toolState)) {
|
|
@@ -754,22 +825,61 @@ function cursorEventsToStream(events, toolDisplay = "blocks") {
|
|
|
754
825
|
case "reasoning-delta":
|
|
755
826
|
reasoningLine(event.text);
|
|
756
827
|
break;
|
|
828
|
+
case "tool-input-partial":
|
|
829
|
+
if (isCreatePlanTool(event.name)) {
|
|
830
|
+
const plan = createPlanContent(event.input) ?? "";
|
|
831
|
+
const prevPlan = toolState.planText.get(event.id) ?? "";
|
|
832
|
+
if (plan.length > prevPlan.length) {
|
|
833
|
+
closeReasoning();
|
|
834
|
+
streamedText = true;
|
|
835
|
+
controller.enqueue({
|
|
836
|
+
type: "text-delta",
|
|
837
|
+
id: ensureText(),
|
|
838
|
+
delta: plan.slice(prevPlan.length)
|
|
839
|
+
});
|
|
840
|
+
}
|
|
841
|
+
toolState.planText.set(event.id, plan);
|
|
842
|
+
toolState.dropped.add(event.id);
|
|
843
|
+
break;
|
|
844
|
+
}
|
|
845
|
+
if (toolDisplay === "blocks" && event.name !== EDIT_TOOL_NAME) {
|
|
846
|
+
const parts = blockToolInputPartialParts(
|
|
847
|
+
event.id,
|
|
848
|
+
event.name,
|
|
849
|
+
event.input,
|
|
850
|
+
toolState
|
|
851
|
+
);
|
|
852
|
+
if (parts.length > 0) {
|
|
853
|
+
closeText();
|
|
854
|
+
closeReasoning();
|
|
855
|
+
}
|
|
856
|
+
for (const part of parts) controller.enqueue(part);
|
|
857
|
+
}
|
|
858
|
+
break;
|
|
757
859
|
case "tool-call":
|
|
758
860
|
if (isCreatePlanTool(event.name)) {
|
|
759
|
-
const plan = createPlanContent(event.input);
|
|
760
|
-
|
|
861
|
+
const plan = createPlanContent(event.input) ?? "";
|
|
862
|
+
const prevPlan = toolState.planText.get(event.id) ?? "";
|
|
863
|
+
if (plan.length > prevPlan.length) {
|
|
761
864
|
closeReasoning();
|
|
762
865
|
streamedText = true;
|
|
763
866
|
controller.enqueue({
|
|
764
867
|
type: "text-delta",
|
|
765
868
|
id: ensureText(),
|
|
766
|
-
delta: plan
|
|
869
|
+
delta: plan.slice(prevPlan.length)
|
|
767
870
|
});
|
|
768
871
|
}
|
|
872
|
+
toolState.planText.set(event.id, plan);
|
|
769
873
|
toolState.dropped.add(event.id);
|
|
770
874
|
break;
|
|
771
875
|
}
|
|
772
876
|
if (toolDisplay === "blocks") {
|
|
877
|
+
if (toolState.partials.delete(event.id)) {
|
|
878
|
+
controller.enqueue({
|
|
879
|
+
type: "tool-input-end",
|
|
880
|
+
id: event.id
|
|
881
|
+
});
|
|
882
|
+
}
|
|
773
883
|
const parts = blockToolCallParts(
|
|
774
884
|
event.id,
|
|
775
885
|
event.name,
|
|
@@ -814,6 +924,14 @@ ${formatToolCall(event.name, event.input)}
|
|
|
814
924
|
case "usage":
|
|
815
925
|
usage = mapUsage(event.usage);
|
|
816
926
|
break;
|
|
927
|
+
case "reasoning-complete":
|
|
928
|
+
closeReasoning();
|
|
929
|
+
if (typeof event.durationMs === "number")
|
|
930
|
+
thinkingMs += event.durationMs;
|
|
931
|
+
break;
|
|
932
|
+
case "compaction":
|
|
933
|
+
compactions++;
|
|
934
|
+
break;
|
|
817
935
|
case "finish":
|
|
818
936
|
if (!streamedText && event.text) {
|
|
819
937
|
controller.enqueue({
|
|
@@ -828,22 +946,34 @@ ${formatToolCall(event.name, event.input)}
|
|
|
828
946
|
closeDanglingToolCalls();
|
|
829
947
|
closeReasoning();
|
|
830
948
|
closeText();
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
949
|
+
{
|
|
950
|
+
const cursorMeta = {};
|
|
951
|
+
if (thinkingMs > 0) cursorMeta["thinkingDurationMs"] = thinkingMs;
|
|
952
|
+
if (compactions > 0) cursorMeta["compactions"] = compactions;
|
|
953
|
+
controller.enqueue({
|
|
954
|
+
type: "finish",
|
|
955
|
+
usage: usage ?? EMPTY_USAGE,
|
|
956
|
+
finishReason: FINISH_STOP,
|
|
957
|
+
...Object.keys(cursorMeta).length > 0 ? { providerMetadata: { cursor: cursorMeta } } : {}
|
|
958
|
+
});
|
|
959
|
+
}
|
|
836
960
|
controller.close();
|
|
837
961
|
} catch (err) {
|
|
838
962
|
controller.enqueue({ type: "error", error: err });
|
|
839
963
|
closeDanglingToolCalls();
|
|
840
964
|
closeReasoning();
|
|
841
965
|
closeText();
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
966
|
+
{
|
|
967
|
+
const cursorMeta = {};
|
|
968
|
+
if (thinkingMs > 0) cursorMeta["thinkingDurationMs"] = thinkingMs;
|
|
969
|
+
if (compactions > 0) cursorMeta["compactions"] = compactions;
|
|
970
|
+
controller.enqueue({
|
|
971
|
+
type: "finish",
|
|
972
|
+
usage: usage ?? EMPTY_USAGE,
|
|
973
|
+
finishReason: FINISH_ERROR,
|
|
974
|
+
...Object.keys(cursorMeta).length > 0 ? { providerMetadata: { cursor: cursorMeta } } : {}
|
|
975
|
+
});
|
|
976
|
+
}
|
|
847
977
|
controller.close();
|
|
848
978
|
}
|
|
849
979
|
}
|
|
@@ -866,6 +996,8 @@ async function cursorEventsToContent(events, toolDisplay = "blocks") {
|
|
|
866
996
|
case "reasoning-delta":
|
|
867
997
|
reasoning += event.text;
|
|
868
998
|
break;
|
|
999
|
+
case "tool-input-partial":
|
|
1000
|
+
break;
|
|
869
1001
|
case "tool-call":
|
|
870
1002
|
if (isCreatePlanTool(event.name)) {
|
|
871
1003
|
const plan = createPlanContent(event.input);
|
|
@@ -908,6 +1040,10 @@ ${formatToolCall(event.name, event.input)}
|
|
|
908
1040
|
case "usage":
|
|
909
1041
|
usage = mapUsage(event.usage);
|
|
910
1042
|
break;
|
|
1043
|
+
case "reasoning-complete":
|
|
1044
|
+
break;
|
|
1045
|
+
case "compaction":
|
|
1046
|
+
break;
|
|
911
1047
|
case "finish":
|
|
912
1048
|
if (!text && event.text) text = event.text;
|
|
913
1049
|
break;
|
|
@@ -978,6 +1114,9 @@ function classifyTurn(prev, prompt) {
|
|
|
978
1114
|
}
|
|
979
1115
|
return { kind: "divergence", fingerprint: fp };
|
|
980
1116
|
}
|
|
1117
|
+
function sendIdempotencyKey(sessionID, record, messageText) {
|
|
1118
|
+
return createHash("sha256").update(`${sessionID ?? "ephemeral"}|${record?.userHashes.join(",") ?? ""}|${messageText}`).digest("hex").slice(0, 32);
|
|
1119
|
+
}
|
|
981
1120
|
|
|
982
1121
|
// src/provider/language-model.ts
|
|
983
1122
|
var CursorLanguageModel = class {
|
|
@@ -1081,6 +1220,12 @@ var CursorLanguageModel = class {
|
|
|
1081
1220
|
resumeAgentId = void 0;
|
|
1082
1221
|
}
|
|
1083
1222
|
}
|
|
1223
|
+
const latestUser = latestUserMessage(options.prompt);
|
|
1224
|
+
const idempotencyKey = sendIdempotencyKey(
|
|
1225
|
+
sessionID,
|
|
1226
|
+
record,
|
|
1227
|
+
latestUser?.text ?? JSON.stringify(options.prompt)
|
|
1228
|
+
);
|
|
1084
1229
|
const delivery = resolveSystemDelivery({
|
|
1085
1230
|
mode: this.config.systemPrompt ?? "rules",
|
|
1086
1231
|
settingSources: this.config.settingSources,
|
|
@@ -1097,6 +1242,7 @@ var CursorLanguageModel = class {
|
|
|
1097
1242
|
cwd: this.config.cwd,
|
|
1098
1243
|
...settingSources ? { settingSources } : {},
|
|
1099
1244
|
...this.config.sandbox !== void 0 ? { sandbox: this.config.sandbox } : {},
|
|
1245
|
+
...this.config.autoReview !== void 0 ? { autoReview: this.config.autoReview } : {},
|
|
1100
1246
|
...mcpServers ? { mcpServers } : {},
|
|
1101
1247
|
...this.config.agents ? { agents: this.config.agents } : {},
|
|
1102
1248
|
...poolKey ? { name: `opencode/${sessionID.slice(-8)}` } : {},
|
|
@@ -1114,21 +1260,45 @@ var CursorLanguageModel = class {
|
|
|
1114
1260
|
let delivered = false;
|
|
1115
1261
|
try {
|
|
1116
1262
|
let aborted = false;
|
|
1263
|
+
let replayUsage;
|
|
1117
1264
|
for (let i = 0; i < multiTurns.length - 1; i++) {
|
|
1118
1265
|
if (options.abortSignal?.aborted) {
|
|
1119
1266
|
aborted = true;
|
|
1120
1267
|
break;
|
|
1121
1268
|
}
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1269
|
+
replayUsage = addUsage(
|
|
1270
|
+
replayUsage,
|
|
1271
|
+
await sendAgentTurnSilently(acquired.agent, multiTurns[i], {
|
|
1272
|
+
mode,
|
|
1273
|
+
abortSignal: options.abortSignal,
|
|
1274
|
+
idempotencyKey: sendIdempotencyKey(
|
|
1275
|
+
sessionID,
|
|
1276
|
+
{ userHashes: [...record?.userHashes ?? [], String(i)] },
|
|
1277
|
+
multiTurns[i].text
|
|
1278
|
+
)
|
|
1279
|
+
})
|
|
1280
|
+
);
|
|
1126
1281
|
}
|
|
1127
1282
|
if (!aborted && !options.abortSignal?.aborted) {
|
|
1128
1283
|
for await (const event of streamAgentTurn(
|
|
1129
1284
|
acquired.agent,
|
|
1130
1285
|
multiTurns[multiTurns.length - 1],
|
|
1131
|
-
{
|
|
1286
|
+
{
|
|
1287
|
+
mode,
|
|
1288
|
+
abortSignal: options.abortSignal,
|
|
1289
|
+
// Key intentionally diverges from the single-turn key: the multi-replay and single-turn branches are mutually exclusive.
|
|
1290
|
+
idempotencyKey: sendIdempotencyKey(
|
|
1291
|
+
sessionID,
|
|
1292
|
+
{
|
|
1293
|
+
userHashes: [
|
|
1294
|
+
...record?.userHashes ?? [],
|
|
1295
|
+
String(multiTurns.length - 1)
|
|
1296
|
+
]
|
|
1297
|
+
},
|
|
1298
|
+
multiTurns[multiTurns.length - 1].text
|
|
1299
|
+
),
|
|
1300
|
+
...replayUsage ? { usageBase: replayUsage } : {}
|
|
1301
|
+
}
|
|
1132
1302
|
)) {
|
|
1133
1303
|
yielded = true;
|
|
1134
1304
|
yield event;
|
|
@@ -1143,13 +1313,22 @@ var CursorLanguageModel = class {
|
|
|
1143
1313
|
try {
|
|
1144
1314
|
for await (const event of streamAgentTurn(acquired.agent, message, {
|
|
1145
1315
|
mode,
|
|
1146
|
-
abortSignal: options.abortSignal
|
|
1316
|
+
abortSignal: options.abortSignal,
|
|
1317
|
+
idempotencyKey
|
|
1147
1318
|
})) {
|
|
1148
1319
|
yielded = true;
|
|
1149
1320
|
yield event;
|
|
1150
1321
|
}
|
|
1151
1322
|
} catch (err) {
|
|
1152
|
-
|
|
1323
|
+
const classified = classifyError(err);
|
|
1324
|
+
if (classified.kind === "auth" || classified.kind === "config") {
|
|
1325
|
+
throw classified.helpUrl ? new Error(
|
|
1326
|
+
`${classified.message} (see ${classified.helpUrl})`,
|
|
1327
|
+
{ cause: err }
|
|
1328
|
+
) : err;
|
|
1329
|
+
}
|
|
1330
|
+
const replayable = classified.kind === "agent-not-found" || classified.kind === "rate-limit" || classified.kind === "network" || classified.kind === "unknown";
|
|
1331
|
+
if (replayable && acquired.resumed && !yielded && !options.abortSignal?.aborted) {
|
|
1153
1332
|
if (process.env["OPENCODE_CURSOR_DEBUG"] === "1") {
|
|
1154
1333
|
console.error(
|
|
1155
1334
|
"[cursor:debug] resumed turn failed before emitting; retrying with a fresh agent"
|
|
@@ -1175,7 +1354,8 @@ var CursorLanguageModel = class {
|
|
|
1175
1354
|
const replay = promptToCursorMessage(options.prompt, systemMode);
|
|
1176
1355
|
yield* streamAgentTurn(retry.agent, replay, {
|
|
1177
1356
|
mode,
|
|
1178
|
-
abortSignal: options.abortSignal
|
|
1357
|
+
abortSignal: options.abortSignal,
|
|
1358
|
+
idempotencyKey
|
|
1179
1359
|
});
|
|
1180
1360
|
} finally {
|
|
1181
1361
|
retry.release();
|
|
@@ -1208,6 +1388,7 @@ var CursorLanguageModel = class {
|
|
|
1208
1388
|
|
|
1209
1389
|
// src/provider/index.ts
|
|
1210
1390
|
function createCursor(options = {}) {
|
|
1391
|
+
if (options.transport) setPreferredTransport(options.transport);
|
|
1211
1392
|
const mcpServers = options.mcpServers && Object.keys(options.mcpServers).length > 0 ? options.mcpServers : void 0;
|
|
1212
1393
|
const config = {
|
|
1213
1394
|
providerName: options.name ?? "cursor",
|
|
@@ -1219,6 +1400,7 @@ function createCursor(options = {}) {
|
|
|
1219
1400
|
...mcpServers ? { mcpServers } : {},
|
|
1220
1401
|
...options.settingSources ? { settingSources: options.settingSources } : {},
|
|
1221
1402
|
...options.sandbox !== void 0 ? { sandbox: options.sandbox } : {},
|
|
1403
|
+
...options.autoReview !== void 0 ? { autoReview: options.autoReview } : {},
|
|
1222
1404
|
...options.agents ? { agents: options.agents } : {},
|
|
1223
1405
|
session: options.session ?? "auto",
|
|
1224
1406
|
toolDisplay: options.toolDisplay ?? "blocks",
|