@stablekernel/opencode-cursor 0.4.4-next.1 → 0.4.5-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 +96 -0
- package/README.md +51 -5
- package/dist/{chunk-KGDYXGZL.js → chunk-734L3SKU.js} +152 -22
- package/dist/chunk-734L3SKU.js.map +1 -0
- package/dist/plugin/index.js +124 -1
- package/dist/plugin/index.js.map +1 -1
- package/dist/provider/index.d.ts +21 -0
- package/dist/provider/index.js +176 -50
- package/dist/provider/index.js.map +1 -1
- package/package.json +4 -2
- package/dist/chunk-KGDYXGZL.js.map +0 -1
package/dist/provider/index.js
CHANGED
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
import {
|
|
2
2
|
acquireAgent,
|
|
3
|
+
dropSessionRecord,
|
|
4
|
+
extractSystemText,
|
|
3
5
|
getSessionRecord,
|
|
4
6
|
resolveControls,
|
|
5
7
|
resolveCursorApiKey,
|
|
8
|
+
resolveSystemDelivery,
|
|
9
|
+
sendAgentTurnSilently,
|
|
6
10
|
streamAgentTurn
|
|
7
|
-
} from "../chunk-
|
|
11
|
+
} from "../chunk-734L3SKU.js";
|
|
8
12
|
|
|
9
13
|
// src/provider/index.ts
|
|
10
14
|
import { NoSuchModelError } from "@ai-sdk/provider";
|
|
@@ -13,6 +17,7 @@ import { NoSuchModelError } from "@ai-sdk/provider";
|
|
|
13
17
|
import { LoadAPIKeyError } from "@ai-sdk/provider";
|
|
14
18
|
|
|
15
19
|
// src/provider/message-map.ts
|
|
20
|
+
import { fileURLToPath } from "url";
|
|
16
21
|
var TOOL_RESULT_CAP = 2e3;
|
|
17
22
|
var TOOL_ARGS_CAP = 500;
|
|
18
23
|
function stringify(value) {
|
|
@@ -26,25 +31,21 @@ function stringify(value) {
|
|
|
26
31
|
function truncate(text, cap) {
|
|
27
32
|
return text.length > cap ? `${text.slice(0, cap)}\u2026[+${text.length - cap} chars]` : text;
|
|
28
33
|
}
|
|
29
|
-
function promptToCursorMessage(prompt) {
|
|
34
|
+
function promptToCursorMessage(prompt, systemPrompt = "rules") {
|
|
30
35
|
const lines = [];
|
|
31
|
-
|
|
32
|
-
prompt.forEach((message, index) => {
|
|
33
|
-
const isLast = index === prompt.length - 1;
|
|
36
|
+
prompt.forEach((message) => {
|
|
34
37
|
switch (message.role) {
|
|
35
38
|
case "system":
|
|
36
|
-
|
|
39
|
+
if (systemPrompt === "message") {
|
|
40
|
+
lines.push(`# System
|
|
37
41
|
${message.content}`);
|
|
42
|
+
}
|
|
38
43
|
break;
|
|
39
44
|
case "user": {
|
|
40
45
|
const text = [];
|
|
41
46
|
for (const part of message.content) {
|
|
42
47
|
if (part.type === "text") text.push(part.text);
|
|
43
|
-
else if (part.type === "file"
|
|
44
|
-
const image = fileToImage(part.data, part.mediaType);
|
|
45
|
-
if (isLast && image) images.push(image);
|
|
46
|
-
text.push("[image attached]");
|
|
47
|
-
}
|
|
48
|
+
else if (part.type === "file") text.push(fileNote(part));
|
|
48
49
|
}
|
|
49
50
|
lines.push(`# User
|
|
50
51
|
${text.join("\n")}`);
|
|
@@ -82,37 +83,49 @@ ${truncate(stringify(part.output), TOOL_RESULT_CAP)}`
|
|
|
82
83
|
}
|
|
83
84
|
}
|
|
84
85
|
});
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
86
|
+
return { text: lines.join("\n\n") };
|
|
87
|
+
}
|
|
88
|
+
function fileNote(part) {
|
|
89
|
+
const name = part.filename ?? describeSource(part.data) ?? "file";
|
|
90
|
+
return `[attached file: ${name} (${part.mediaType}) \u2014 not forwarded to Cursor]`;
|
|
88
91
|
}
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
if (
|
|
92
|
-
|
|
93
|
-
|
|
92
|
+
var URL_SCHEME = /^[a-z][a-z0-9+.-]*:\/\//i;
|
|
93
|
+
function describeSource(data) {
|
|
94
|
+
if (data instanceof URL)
|
|
95
|
+
return data.protocol === "file:" ? fileUrlToPath(data) : data.href;
|
|
96
|
+
if (typeof data === "string" && URL_SCHEME.test(data))
|
|
97
|
+
return data.startsWith("file://") ? fileUrlToPath(data) : data;
|
|
98
|
+
return void 0;
|
|
99
|
+
}
|
|
100
|
+
function fileUrlToPath(url) {
|
|
101
|
+
try {
|
|
102
|
+
return fileURLToPath(url);
|
|
103
|
+
} catch {
|
|
104
|
+
return typeof url === "string" ? url : url.href;
|
|
94
105
|
}
|
|
95
|
-
|
|
96
|
-
|
|
106
|
+
}
|
|
107
|
+
function userTurnToCursorMessage(message) {
|
|
108
|
+
const text = [];
|
|
109
|
+
for (const part of message.content) {
|
|
110
|
+
if (part.type === "text") text.push(part.text);
|
|
111
|
+
else if (part.type === "file") text.push(fileNote(part));
|
|
97
112
|
}
|
|
98
|
-
return
|
|
113
|
+
return { text: text.join("\n") };
|
|
99
114
|
}
|
|
100
115
|
function latestUserMessage(prompt) {
|
|
101
116
|
const last = prompt[prompt.length - 1];
|
|
102
117
|
if (!last || last.role !== "user") return void 0;
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
118
|
+
return userTurnToCursorMessage(last);
|
|
119
|
+
}
|
|
120
|
+
function trailingUserMessages(prompt, count) {
|
|
121
|
+
if (count <= 0) return [];
|
|
122
|
+
const collected = [];
|
|
123
|
+
for (let i = prompt.length - 1; i >= 0 && collected.length < count; i--) {
|
|
124
|
+
const message = prompt[i];
|
|
125
|
+
if (!message || message.role !== "user") break;
|
|
126
|
+
collected.push(userTurnToCursorMessage(message));
|
|
112
127
|
}
|
|
113
|
-
|
|
114
|
-
if (images.length > 0) out.images = images;
|
|
115
|
-
return out;
|
|
128
|
+
return collected.reverse();
|
|
116
129
|
}
|
|
117
130
|
|
|
118
131
|
// src/provider/stream-map.ts
|
|
@@ -954,9 +967,14 @@ function classifyTurn(prev, prompt) {
|
|
|
954
967
|
if (prev.systemHash !== fp.systemHash)
|
|
955
968
|
return { kind: "side-call", fingerprint: fp };
|
|
956
969
|
const lastIsUser = prompt[prompt.length - 1]?.role === "user";
|
|
957
|
-
const
|
|
958
|
-
|
|
959
|
-
|
|
970
|
+
const newUserCount = fp.userHashes.length - prev.userHashes.length;
|
|
971
|
+
const isPrefix = isStrictPrefix(prev.userHashes, fp.userHashes);
|
|
972
|
+
if (lastIsUser && isPrefix && newUserCount === 1) {
|
|
973
|
+
return { kind: "continuation", fingerprint: fp, newUserCount: 1 };
|
|
974
|
+
}
|
|
975
|
+
const tailContiguous = newUserCount >= 2 && prompt.length >= newUserCount && prompt.slice(prompt.length - newUserCount).every((m) => m.role === "user");
|
|
976
|
+
if (lastIsUser && isPrefix && tailContiguous) {
|
|
977
|
+
return { kind: "continuation-multi", fingerprint: fp, newUserCount };
|
|
960
978
|
}
|
|
961
979
|
return { kind: "divergence", fingerprint: fp };
|
|
962
980
|
}
|
|
@@ -972,8 +990,17 @@ var CursorLanguageModel = class {
|
|
|
972
990
|
specificationVersion = "v3";
|
|
973
991
|
modelId;
|
|
974
992
|
provider;
|
|
975
|
-
//
|
|
993
|
+
// The local Cursor agent has no attachment channel, so file parts (images,
|
|
994
|
+
// directories, other media) are noted as text in message-map rather than
|
|
995
|
+
// fetched or attached; no URLs are resolved natively.
|
|
976
996
|
supportedUrls = {};
|
|
997
|
+
/** Messages already emitted, so degradation warnings fire once, not per turn. */
|
|
998
|
+
warned = /* @__PURE__ */ new Set();
|
|
999
|
+
warnOnce(message) {
|
|
1000
|
+
if (this.warned.has(message)) return;
|
|
1001
|
+
this.warned.add(message);
|
|
1002
|
+
console.warn(`[${this.provider}] ${message}`);
|
|
1003
|
+
}
|
|
977
1004
|
requireApiKey() {
|
|
978
1005
|
const apiKey = resolveCursorApiKey(this.config.apiKey);
|
|
979
1006
|
if (!apiKey) {
|
|
@@ -1001,19 +1028,24 @@ var CursorLanguageModel = class {
|
|
|
1001
1028
|
let resumeAgentId = explicitAgentId;
|
|
1002
1029
|
let poolKey;
|
|
1003
1030
|
let record;
|
|
1031
|
+
let multiNewUserCount = 0;
|
|
1004
1032
|
if (usePool) {
|
|
1005
1033
|
const classification = ephemeral ? {
|
|
1006
1034
|
kind: "side-call",
|
|
1007
1035
|
fingerprint: fingerprint(options.prompt)
|
|
1008
1036
|
} : classifyTurn(getSessionRecord(sessionID), options.prompt);
|
|
1009
1037
|
switch (classification.kind) {
|
|
1010
|
-
case "continuation":
|
|
1038
|
+
case "continuation":
|
|
1039
|
+
case "continuation-multi": {
|
|
1011
1040
|
const prev = getSessionRecord(sessionID);
|
|
1012
1041
|
if (prev?.mcpHash === mcpHash) {
|
|
1013
1042
|
resumeAgentId = prev?.agentId;
|
|
1014
1043
|
}
|
|
1015
1044
|
poolKey = sessionID;
|
|
1016
1045
|
record = { ...classification.fingerprint, mcpHash };
|
|
1046
|
+
if (classification.kind === "continuation-multi") {
|
|
1047
|
+
multiNewUserCount = classification.newUserCount ?? 0;
|
|
1048
|
+
}
|
|
1017
1049
|
break;
|
|
1018
1050
|
}
|
|
1019
1051
|
case "new":
|
|
@@ -1025,34 +1057,127 @@ var CursorLanguageModel = class {
|
|
|
1025
1057
|
break;
|
|
1026
1058
|
}
|
|
1027
1059
|
if (process.env["OPENCODE_CURSOR_DEBUG"] === "1") {
|
|
1028
|
-
const label = classification.kind === "continuation" ? "resume" : `fresh:${classification.kind}`;
|
|
1060
|
+
const label = classification.kind === "continuation" ? "resume" : classification.kind === "continuation-multi" ? `resume-multi:${multiNewUserCount}` : `fresh:${classification.kind}`;
|
|
1029
1061
|
console.error(
|
|
1030
1062
|
`[cursor:debug] turn classification=${label} session=${sessionID}`
|
|
1031
1063
|
);
|
|
1032
1064
|
}
|
|
1033
1065
|
}
|
|
1034
|
-
|
|
1066
|
+
let multiTurns;
|
|
1067
|
+
if (multiNewUserCount >= 2) {
|
|
1068
|
+
const turns = trailingUserMessages(options.prompt, multiNewUserCount);
|
|
1069
|
+
if (turns.length === multiNewUserCount) {
|
|
1070
|
+
multiTurns = turns;
|
|
1071
|
+
} else {
|
|
1072
|
+
resumeAgentId = void 0;
|
|
1073
|
+
}
|
|
1074
|
+
}
|
|
1075
|
+
const delivery = resolveSystemDelivery({
|
|
1076
|
+
mode: this.config.systemPrompt ?? "rules",
|
|
1077
|
+
settingSources: this.config.settingSources,
|
|
1078
|
+
cwd: this.config.cwd,
|
|
1079
|
+
systemText: extractSystemText(options.prompt),
|
|
1080
|
+
warn: (message) => this.warnOnce(message)
|
|
1081
|
+
});
|
|
1082
|
+
const systemMode = delivery.mode;
|
|
1083
|
+
const settingSources = delivery.settingSources;
|
|
1084
|
+
const baseAcquire = {
|
|
1035
1085
|
apiKey: this.requireApiKey(),
|
|
1036
1086
|
modelSelection,
|
|
1037
1087
|
mode,
|
|
1038
1088
|
cwd: this.config.cwd,
|
|
1039
|
-
...
|
|
1089
|
+
...settingSources ? { settingSources } : {},
|
|
1040
1090
|
...this.config.sandbox !== void 0 ? { sandbox: this.config.sandbox } : {},
|
|
1041
1091
|
...mcpServers ? { mcpServers } : {},
|
|
1042
1092
|
...this.config.agents ? { agents: this.config.agents } : {},
|
|
1043
1093
|
...poolKey ? { name: `opencode/${sessionID.slice(-8)}` } : {},
|
|
1044
|
-
...resumeAgentId ? { resumeAgentId } : {},
|
|
1045
1094
|
...poolKey ? { poolKey } : {},
|
|
1046
1095
|
...record ? { record } : {}
|
|
1096
|
+
};
|
|
1097
|
+
const acquired = await acquireAgent({
|
|
1098
|
+
...baseAcquire,
|
|
1099
|
+
...resumeAgentId ? { resumeAgentId } : {}
|
|
1047
1100
|
});
|
|
1048
|
-
|
|
1101
|
+
let yielded = false;
|
|
1102
|
+
let releasedOriginal = false;
|
|
1049
1103
|
try {
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1104
|
+
if (acquired.resumed && multiTurns) {
|
|
1105
|
+
let delivered = false;
|
|
1106
|
+
try {
|
|
1107
|
+
let aborted = false;
|
|
1108
|
+
for (let i = 0; i < multiTurns.length - 1; i++) {
|
|
1109
|
+
if (options.abortSignal?.aborted) {
|
|
1110
|
+
aborted = true;
|
|
1111
|
+
break;
|
|
1112
|
+
}
|
|
1113
|
+
await sendAgentTurnSilently(acquired.agent, multiTurns[i], {
|
|
1114
|
+
mode,
|
|
1115
|
+
abortSignal: options.abortSignal
|
|
1116
|
+
});
|
|
1117
|
+
}
|
|
1118
|
+
if (!aborted && !options.abortSignal?.aborted) {
|
|
1119
|
+
for await (const event of streamAgentTurn(
|
|
1120
|
+
acquired.agent,
|
|
1121
|
+
multiTurns[multiTurns.length - 1],
|
|
1122
|
+
{ mode, abortSignal: options.abortSignal }
|
|
1123
|
+
)) {
|
|
1124
|
+
yielded = true;
|
|
1125
|
+
yield event;
|
|
1126
|
+
}
|
|
1127
|
+
delivered = true;
|
|
1128
|
+
}
|
|
1129
|
+
} finally {
|
|
1130
|
+
if (!delivered && sessionID) dropSessionRecord(sessionID);
|
|
1131
|
+
}
|
|
1132
|
+
} else {
|
|
1133
|
+
const message = acquired.resumed ? latestUserMessage(options.prompt) ?? promptToCursorMessage(options.prompt, systemMode) : promptToCursorMessage(options.prompt, systemMode);
|
|
1134
|
+
try {
|
|
1135
|
+
for await (const event of streamAgentTurn(acquired.agent, message, {
|
|
1136
|
+
mode,
|
|
1137
|
+
abortSignal: options.abortSignal
|
|
1138
|
+
})) {
|
|
1139
|
+
yielded = true;
|
|
1140
|
+
yield event;
|
|
1141
|
+
}
|
|
1142
|
+
} catch (err) {
|
|
1143
|
+
if (acquired.resumed && !yielded && !options.abortSignal?.aborted) {
|
|
1144
|
+
if (process.env["OPENCODE_CURSOR_DEBUG"] === "1") {
|
|
1145
|
+
console.error(
|
|
1146
|
+
"[cursor:debug] resumed turn failed before emitting; retrying with a fresh agent"
|
|
1147
|
+
);
|
|
1148
|
+
}
|
|
1149
|
+
acquired.release();
|
|
1150
|
+
releasedOriginal = true;
|
|
1151
|
+
let retry;
|
|
1152
|
+
try {
|
|
1153
|
+
retry = await acquireAgent({ ...baseAcquire });
|
|
1154
|
+
} catch (retryErr) {
|
|
1155
|
+
if (retryErr instanceof Error && retryErr.cause === void 0) {
|
|
1156
|
+
retryErr.cause = err;
|
|
1157
|
+
} else if (process.env["OPENCODE_CURSOR_DEBUG"] === "1") {
|
|
1158
|
+
console.error(
|
|
1159
|
+
"[cursor:debug] original resume failure (not attachable as cause):",
|
|
1160
|
+
err
|
|
1161
|
+
);
|
|
1162
|
+
}
|
|
1163
|
+
throw retryErr;
|
|
1164
|
+
}
|
|
1165
|
+
try {
|
|
1166
|
+
const replay = promptToCursorMessage(options.prompt, systemMode);
|
|
1167
|
+
yield* streamAgentTurn(retry.agent, replay, {
|
|
1168
|
+
mode,
|
|
1169
|
+
abortSignal: options.abortSignal
|
|
1170
|
+
});
|
|
1171
|
+
} finally {
|
|
1172
|
+
retry.release();
|
|
1173
|
+
}
|
|
1174
|
+
} else {
|
|
1175
|
+
throw err;
|
|
1176
|
+
}
|
|
1177
|
+
}
|
|
1178
|
+
}
|
|
1054
1179
|
} finally {
|
|
1055
|
-
acquired.release();
|
|
1180
|
+
if (!releasedOriginal) acquired.release();
|
|
1056
1181
|
}
|
|
1057
1182
|
}
|
|
1058
1183
|
async doStream(options) {
|
|
@@ -1086,7 +1211,8 @@ function createCursor(options = {}) {
|
|
|
1086
1211
|
...options.sandbox !== void 0 ? { sandbox: options.sandbox } : {},
|
|
1087
1212
|
...options.agents ? { agents: options.agents } : {},
|
|
1088
1213
|
session: options.session ?? "auto",
|
|
1089
|
-
toolDisplay: options.toolDisplay ?? "blocks"
|
|
1214
|
+
toolDisplay: options.toolDisplay ?? "blocks",
|
|
1215
|
+
systemPrompt: options.systemPrompt ?? "rules"
|
|
1090
1216
|
};
|
|
1091
1217
|
const notImplemented = (kind, modelId) => {
|
|
1092
1218
|
throw new NoSuchModelError({
|