@stablekernel/opencode-cursor 0.2.0 → 0.3.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 -3
- package/README.md +67 -16
- package/dist/{chunk-D4YQ7ZEM.js → chunk-BTI2NHEE.js} +73 -8
- package/dist/chunk-BTI2NHEE.js.map +1 -0
- package/dist/plugin/index.js +100 -13
- package/dist/plugin/index.js.map +1 -1
- package/dist/provider/index.d.ts +7 -5
- package/dist/provider/index.js +137 -17
- package/dist/provider/index.js.map +1 -1
- package/package.json +6 -1
- package/dist/chunk-D4YQ7ZEM.js.map +0 -1
package/dist/provider/index.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import {
|
|
2
2
|
acquireAgent,
|
|
3
|
+
getSessionRecord,
|
|
3
4
|
resolveControls,
|
|
4
5
|
resolveCursorApiKey,
|
|
5
6
|
streamAgentTurn
|
|
6
|
-
} from "../chunk-
|
|
7
|
+
} from "../chunk-BTI2NHEE.js";
|
|
7
8
|
|
|
8
9
|
// src/provider/index.ts
|
|
9
10
|
import { NoSuchModelError } from "@ai-sdk/provider";
|
|
@@ -12,6 +13,19 @@ import { NoSuchModelError } from "@ai-sdk/provider";
|
|
|
12
13
|
import { LoadAPIKeyError } from "@ai-sdk/provider";
|
|
13
14
|
|
|
14
15
|
// src/provider/message-map.ts
|
|
16
|
+
var TOOL_RESULT_CAP = 2e3;
|
|
17
|
+
var TOOL_ARGS_CAP = 500;
|
|
18
|
+
function stringify(value) {
|
|
19
|
+
if (typeof value === "string") return value;
|
|
20
|
+
try {
|
|
21
|
+
return JSON.stringify(value ?? null);
|
|
22
|
+
} catch {
|
|
23
|
+
return String(value);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
function truncate(text, cap) {
|
|
27
|
+
return text.length > cap ? `${text.slice(0, cap)}\u2026[+${text.length - cap} chars]` : text;
|
|
28
|
+
}
|
|
15
29
|
function promptToCursorMessage(prompt) {
|
|
16
30
|
const lines = [];
|
|
17
31
|
const images = [];
|
|
@@ -40,9 +54,16 @@ ${text.join("\n")}`);
|
|
|
40
54
|
const text = [];
|
|
41
55
|
for (const part of message.content) {
|
|
42
56
|
if (part.type === "text") text.push(part.text);
|
|
43
|
-
else if (part.type === "reasoning")
|
|
44
|
-
|
|
45
|
-
else if (part.type === "tool-
|
|
57
|
+
else if (part.type === "reasoning")
|
|
58
|
+
text.push(`(thinking) ${part.text}`);
|
|
59
|
+
else if (part.type === "tool-call")
|
|
60
|
+
text.push(
|
|
61
|
+
`[called ${part.toolName}(${truncate(stringify(part.input), TOOL_ARGS_CAP)})]`
|
|
62
|
+
);
|
|
63
|
+
else if (part.type === "tool-result")
|
|
64
|
+
text.push(
|
|
65
|
+
`[result of ${part.toolName}: ${truncate(stringify(part.output), TOOL_RESULT_CAP)}]`
|
|
66
|
+
);
|
|
46
67
|
}
|
|
47
68
|
lines.push(`# Assistant
|
|
48
69
|
${text.join("\n")}`);
|
|
@@ -51,8 +72,10 @@ ${text.join("\n")}`);
|
|
|
51
72
|
case "tool": {
|
|
52
73
|
for (const part of message.content) {
|
|
53
74
|
if (part.type === "tool-result") {
|
|
54
|
-
lines.push(
|
|
55
|
-
${
|
|
75
|
+
lines.push(
|
|
76
|
+
`# Tool result (${part.toolName})
|
|
77
|
+
${truncate(stringify(part.output), TOOL_RESULT_CAP)}`
|
|
78
|
+
);
|
|
56
79
|
}
|
|
57
80
|
}
|
|
58
81
|
break;
|
|
@@ -691,12 +714,17 @@ function cursorEventsToStream(events, toolDisplay = "blocks") {
|
|
|
691
714
|
break;
|
|
692
715
|
case "tool-call":
|
|
693
716
|
if (toolDisplay === "blocks") {
|
|
694
|
-
|
|
717
|
+
const parts = blockToolCallParts(
|
|
695
718
|
event.id,
|
|
696
719
|
event.name,
|
|
697
720
|
event.input,
|
|
698
721
|
toolState
|
|
699
|
-
)
|
|
722
|
+
);
|
|
723
|
+
if (parts.length > 0) {
|
|
724
|
+
closeText();
|
|
725
|
+
closeReasoning();
|
|
726
|
+
}
|
|
727
|
+
for (const part of parts) {
|
|
700
728
|
controller.enqueue(part);
|
|
701
729
|
}
|
|
702
730
|
} else {
|
|
@@ -707,13 +735,18 @@ ${formatToolCall(event.name, event.input)}
|
|
|
707
735
|
break;
|
|
708
736
|
case "tool-result":
|
|
709
737
|
if (toolDisplay === "blocks") {
|
|
710
|
-
|
|
738
|
+
const parts = blockToolResultParts(
|
|
711
739
|
event.id,
|
|
712
740
|
event.name,
|
|
713
741
|
event.result,
|
|
714
742
|
event.isError,
|
|
715
743
|
toolState
|
|
716
|
-
)
|
|
744
|
+
);
|
|
745
|
+
if (parts.length > 0) {
|
|
746
|
+
closeText();
|
|
747
|
+
closeReasoning();
|
|
748
|
+
}
|
|
749
|
+
for (const part of parts) {
|
|
717
750
|
controller.enqueue(part);
|
|
718
751
|
}
|
|
719
752
|
} else if (event.isError) {
|
|
@@ -828,6 +861,55 @@ ${formatToolCall(event.name, event.input)}
|
|
|
828
861
|
return { content, finishReason, usage };
|
|
829
862
|
}
|
|
830
863
|
|
|
864
|
+
// src/provider/transcript-fingerprint.ts
|
|
865
|
+
import { createHash } from "crypto";
|
|
866
|
+
function sha(input) {
|
|
867
|
+
return createHash("sha256").update(input).digest("hex");
|
|
868
|
+
}
|
|
869
|
+
function mcpServersFingerprint(servers) {
|
|
870
|
+
if (!servers) return "";
|
|
871
|
+
const keys = Object.keys(servers).sort();
|
|
872
|
+
if (keys.length === 0) return "";
|
|
873
|
+
return sha(JSON.stringify(keys.map((k) => [k, servers[k]])));
|
|
874
|
+
}
|
|
875
|
+
function userMessageKey(message) {
|
|
876
|
+
const parts = [];
|
|
877
|
+
for (const part of message.content) {
|
|
878
|
+
if (part.type === "text") parts.push(`t:${part.text}`);
|
|
879
|
+
else if (part.type === "file") parts.push(`f:${part.mediaType}`);
|
|
880
|
+
}
|
|
881
|
+
return parts.join("\n");
|
|
882
|
+
}
|
|
883
|
+
function fingerprint(prompt) {
|
|
884
|
+
const systemParts = [];
|
|
885
|
+
const userHashes = [];
|
|
886
|
+
for (const message of prompt) {
|
|
887
|
+
if (message.role === "system") systemParts.push(message.content);
|
|
888
|
+
else if (message.role === "user")
|
|
889
|
+
userHashes.push(sha(userMessageKey(message)));
|
|
890
|
+
}
|
|
891
|
+
return { systemHash: sha(systemParts.join("\n")), userHashes };
|
|
892
|
+
}
|
|
893
|
+
function isStrictPrefix(prefix, full) {
|
|
894
|
+
if (prefix.length >= full.length) return false;
|
|
895
|
+
for (let i = 0; i < prefix.length; i++) {
|
|
896
|
+
if (prefix[i] !== full[i]) return false;
|
|
897
|
+
}
|
|
898
|
+
return true;
|
|
899
|
+
}
|
|
900
|
+
function classifyTurn(prev, prompt) {
|
|
901
|
+
const fp = fingerprint(prompt);
|
|
902
|
+
if (!prev) return { kind: "new", fingerprint: fp };
|
|
903
|
+
if (prev.systemHash !== fp.systemHash)
|
|
904
|
+
return { kind: "side-call", fingerprint: fp };
|
|
905
|
+
const lastIsUser = prompt[prompt.length - 1]?.role === "user";
|
|
906
|
+
const exactlyOneNew = fp.userHashes.length === prev.userHashes.length + 1;
|
|
907
|
+
if (lastIsUser && exactlyOneNew && isStrictPrefix(prev.userHashes, fp.userHashes)) {
|
|
908
|
+
return { kind: "continuation", fingerprint: fp };
|
|
909
|
+
}
|
|
910
|
+
return { kind: "divergence", fingerprint: fp };
|
|
911
|
+
}
|
|
912
|
+
|
|
831
913
|
// src/provider/language-model.ts
|
|
832
914
|
var CursorLanguageModel = class {
|
|
833
915
|
constructor(modelId, config) {
|
|
@@ -858,8 +940,46 @@ var CursorLanguageModel = class {
|
|
|
858
940
|
providerOptions
|
|
859
941
|
);
|
|
860
942
|
const sessionID = typeof providerOptions?.["sessionID"] === "string" ? providerOptions["sessionID"] : void 0;
|
|
861
|
-
const
|
|
943
|
+
const dynamicMcp = providerOptions?.["mcpServers"];
|
|
944
|
+
const mcpServers = dynamicMcp ?? this.config.mcpServers;
|
|
945
|
+
const mcpHash = mcpServersFingerprint(mcpServers);
|
|
946
|
+
const sessionEnabled = (this.config.session ?? "auto") !== false;
|
|
862
947
|
const explicitAgentId = typeof providerOptions?.["agentId"] === "string" ? providerOptions["agentId"] : void 0;
|
|
948
|
+
const ephemeral = providerOptions?.["ephemeral"] === true;
|
|
949
|
+
const usePool = sessionEnabled && Boolean(sessionID) && !explicitAgentId;
|
|
950
|
+
let resumeAgentId = explicitAgentId;
|
|
951
|
+
let poolKey;
|
|
952
|
+
let record;
|
|
953
|
+
if (usePool) {
|
|
954
|
+
const classification = ephemeral ? {
|
|
955
|
+
kind: "side-call",
|
|
956
|
+
fingerprint: fingerprint(options.prompt)
|
|
957
|
+
} : classifyTurn(getSessionRecord(sessionID), options.prompt);
|
|
958
|
+
switch (classification.kind) {
|
|
959
|
+
case "continuation": {
|
|
960
|
+
const prev = getSessionRecord(sessionID);
|
|
961
|
+
if (prev?.mcpHash === mcpHash) {
|
|
962
|
+
resumeAgentId = prev?.agentId;
|
|
963
|
+
}
|
|
964
|
+
poolKey = sessionID;
|
|
965
|
+
record = { ...classification.fingerprint, mcpHash };
|
|
966
|
+
break;
|
|
967
|
+
}
|
|
968
|
+
case "new":
|
|
969
|
+
case "divergence":
|
|
970
|
+
poolKey = sessionID;
|
|
971
|
+
record = { ...classification.fingerprint, mcpHash };
|
|
972
|
+
break;
|
|
973
|
+
case "side-call":
|
|
974
|
+
break;
|
|
975
|
+
}
|
|
976
|
+
if (process.env["OPENCODE_CURSOR_DEBUG"] === "1") {
|
|
977
|
+
const label = classification.kind === "continuation" ? "resume" : `fresh:${classification.kind}`;
|
|
978
|
+
console.error(
|
|
979
|
+
`[cursor:debug] turn classification=${label} session=${sessionID}`
|
|
980
|
+
);
|
|
981
|
+
}
|
|
982
|
+
}
|
|
863
983
|
const acquired = await acquireAgent({
|
|
864
984
|
apiKey: this.requireApiKey(),
|
|
865
985
|
modelSelection,
|
|
@@ -867,12 +987,12 @@ var CursorLanguageModel = class {
|
|
|
867
987
|
cwd: this.config.cwd,
|
|
868
988
|
...this.config.settingSources ? { settingSources: this.config.settingSources } : {},
|
|
869
989
|
...this.config.sandbox !== void 0 ? { sandbox: this.config.sandbox } : {},
|
|
870
|
-
...
|
|
990
|
+
...mcpServers ? { mcpServers } : {},
|
|
871
991
|
...this.config.agents ? { agents: this.config.agents } : {},
|
|
872
|
-
...
|
|
873
|
-
...
|
|
874
|
-
|
|
875
|
-
|
|
992
|
+
...poolKey ? { name: `opencode/${sessionID.slice(-8)}` } : {},
|
|
993
|
+
...resumeAgentId ? { resumeAgentId } : {},
|
|
994
|
+
...poolKey ? { poolKey } : {},
|
|
995
|
+
...record ? { record } : {}
|
|
876
996
|
});
|
|
877
997
|
const message = acquired.resumed ? latestUserMessage(options.prompt) ?? promptToCursorMessage(options.prompt) : promptToCursorMessage(options.prompt);
|
|
878
998
|
try {
|
|
@@ -914,7 +1034,7 @@ function createCursor(options = {}) {
|
|
|
914
1034
|
...options.settingSources ? { settingSources: options.settingSources } : {},
|
|
915
1035
|
...options.sandbox !== void 0 ? { sandbox: options.sandbox } : {},
|
|
916
1036
|
...options.agents ? { agents: options.agents } : {},
|
|
917
|
-
|
|
1037
|
+
session: options.session ?? "auto",
|
|
918
1038
|
toolDisplay: options.toolDisplay ?? "blocks"
|
|
919
1039
|
};
|
|
920
1040
|
const notImplemented = (kind, modelId) => {
|