@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.
@@ -1,9 +1,10 @@
1
1
  import {
2
2
  acquireAgent,
3
+ getSessionRecord,
3
4
  resolveControls,
4
5
  resolveCursorApiKey,
5
6
  streamAgentTurn
6
- } from "../chunk-D4YQ7ZEM.js";
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") text.push(`(thinking) ${part.text}`);
44
- else if (part.type === "tool-call") text.push(`[called ${part.toolName}(${part.input})]`);
45
- else if (part.type === "tool-result") text.push(`[result of ${part.toolName}]`);
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(`# Tool result (${part.toolName})
55
- ${JSON.stringify(part.output)}`);
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
- for (const part of blockToolCallParts(
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
- for (const part of blockToolResultParts(
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 useSession = this.config.session === true && Boolean(sessionID);
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
- ...this.config.mcpServers ? { mcpServers: this.config.mcpServers } : {},
990
+ ...mcpServers ? { mcpServers } : {},
871
991
  ...this.config.agents ? { agents: this.config.agents } : {},
872
- ...useSession ? { name: `opencode/${sessionID.slice(-8)}` } : {},
873
- ...explicitAgentId ? { agentId: explicitAgentId } : {},
874
- sessionID,
875
- session: useSession
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
- ...options.session !== void 0 ? { session: options.session } : {},
1037
+ session: options.session ?? "auto",
918
1038
  toolDisplay: options.toolDisplay ?? "blocks"
919
1039
  };
920
1040
  const notImplemented = (kind, modelId) => {