pi-cursor-sdk 0.1.62 → 0.3.6

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.
Files changed (279) hide show
  1. package/CHANGELOG.md +85 -0
  2. package/README.md +57 -53
  3. package/dist/bundled-context-windows.js +39 -0
  4. package/dist/context-window-cache.js +92 -0
  5. package/dist/context.js +338 -0
  6. package/dist/cursor-active-tools.js +3 -0
  7. package/dist/cursor-agent-message-web-tools.js +165 -0
  8. package/dist/cursor-agents-context-registration.js +17 -0
  9. package/dist/cursor-agents-context.js +113 -0
  10. package/dist/cursor-api-key.js +34 -0
  11. package/dist/cursor-bridge-contract.js +27 -0
  12. package/dist/cursor-cloud-lifecycle.js +650 -0
  13. package/dist/cursor-cloud-local-state.js +460 -0
  14. package/dist/cursor-cloud-options.js +145 -0
  15. package/dist/cursor-cloud-reporting.js +222 -0
  16. package/dist/cursor-compact-tool-summary.js +75 -0
  17. package/dist/cursor-config.js +613 -0
  18. package/dist/cursor-context-tools.js +4 -0
  19. package/dist/cursor-display-only-trace.js +11 -0
  20. package/dist/cursor-display-text.js +18 -0
  21. package/dist/cursor-durable-fs.js +53 -0
  22. package/dist/cursor-edit-diff.js +13 -0
  23. package/dist/cursor-env-boolean.js +19 -0
  24. package/dist/cursor-fallback-models.generated.js +7760 -0
  25. package/dist/cursor-fallback-warning.js +15 -0
  26. package/dist/cursor-http1.js +45 -0
  27. package/dist/cursor-incomplete-tool-visibility.js +83 -0
  28. package/dist/cursor-live-run-accounting.js +55 -0
  29. package/dist/cursor-live-run-coordinator.js +406 -0
  30. package/dist/cursor-mcp-timeout-override.js +116 -0
  31. package/dist/cursor-model-lifecycle.js +32 -0
  32. package/dist/cursor-model.js +5 -0
  33. package/dist/cursor-native-replay-routing.js +35 -0
  34. package/dist/cursor-native-replay-trace.js +28 -0
  35. package/dist/cursor-native-tool-display-registration.js +102 -0
  36. package/dist/cursor-native-tool-display-replay.js +428 -0
  37. package/dist/cursor-native-tool-display-state.js +70 -0
  38. package/dist/cursor-native-tool-display-tools.js +123 -0
  39. package/dist/cursor-native-tool-names.js +11 -0
  40. package/dist/cursor-partial-content-emitter.js +118 -0
  41. package/dist/cursor-pi-tool-bridge-abort.js +114 -0
  42. package/dist/cursor-pi-tool-bridge-constants.js +6 -0
  43. package/dist/cursor-pi-tool-bridge-diagnostics.js +125 -0
  44. package/dist/cursor-pi-tool-bridge-env.js +18 -0
  45. package/dist/cursor-pi-tool-bridge-mcp.js +100 -0
  46. package/dist/cursor-pi-tool-bridge-run.js +355 -0
  47. package/dist/cursor-pi-tool-bridge-server.js +163 -0
  48. package/dist/cursor-pi-tool-bridge-snapshot.js +63 -0
  49. package/dist/cursor-pi-tool-bridge-types.js +1 -0
  50. package/dist/cursor-pi-tool-bridge.js +123 -0
  51. package/dist/cursor-provider-errors.js +311 -0
  52. package/dist/cursor-provider-lazy.js +39 -0
  53. package/dist/cursor-provider-live-run-drain.js +412 -0
  54. package/dist/cursor-provider-overflow.js +90 -0
  55. package/dist/cursor-provider-run-finalizer.js +176 -0
  56. package/dist/cursor-provider-run-outcome.js +76 -0
  57. package/dist/cursor-provider-turn-coordinator.js +275 -0
  58. package/dist/cursor-provider-turn-display-router.js +158 -0
  59. package/dist/cursor-provider-turn-emit.js +35 -0
  60. package/dist/cursor-provider-turn-finalize.js +154 -0
  61. package/dist/cursor-provider-turn-lifecycle-emitter.js +126 -0
  62. package/dist/cursor-provider-turn-prepare.js +374 -0
  63. package/dist/cursor-provider-turn-runner.js +146 -0
  64. package/dist/cursor-provider-turn-sdk-normalizer.js +74 -0
  65. package/dist/cursor-provider-turn-send.js +138 -0
  66. package/dist/cursor-provider-turn-shell-output.js +117 -0
  67. package/dist/cursor-provider-turn-tool-ledger.js +99 -0
  68. package/dist/cursor-provider-turn-types.js +1 -0
  69. package/dist/cursor-provider.js +72 -0
  70. package/dist/cursor-question-tool.js +194 -0
  71. package/dist/cursor-record-utils.js +61 -0
  72. package/dist/cursor-replay-activity-builders.js +114 -0
  73. package/dist/cursor-replay-source-names.js +27 -0
  74. package/dist/cursor-replay-summary-args.js +105 -0
  75. package/dist/cursor-replay-tool-details.js +167 -0
  76. package/dist/cursor-ripgrep-path.js +27 -0
  77. package/dist/cursor-run-final-text.js +63 -0
  78. package/dist/cursor-runtime-state.js +380 -0
  79. package/dist/cursor-sdk-billed-usage.js +86 -0
  80. package/dist/cursor-sdk-event-debug-constants.js +33 -0
  81. package/dist/cursor-sdk-event-debug-session.js +107 -0
  82. package/dist/cursor-sdk-event-debug.js +461 -0
  83. package/dist/cursor-sdk-output-filter.js +1 -0
  84. package/dist/cursor-sdk-process-error-guard.js +221 -0
  85. package/dist/cursor-sdk-runtime.js +3 -0
  86. package/dist/cursor-sensitive-text.js +23 -0
  87. package/dist/cursor-session-agent-cleanup.js +311 -0
  88. package/dist/cursor-session-agent-lifecycle.js +32 -0
  89. package/dist/cursor-session-agent-lineage.js +103 -0
  90. package/dist/cursor-session-agent-resume.js +417 -0
  91. package/dist/cursor-session-agent.js +507 -0
  92. package/dist/cursor-session-compaction-prep.js +21 -0
  93. package/dist/cursor-session-scope.js +113 -0
  94. package/dist/cursor-session-send-policy.js +18 -0
  95. package/dist/cursor-session-store.js +105 -0
  96. package/dist/cursor-session-turn-queue.js +50 -0
  97. package/dist/cursor-setting-sources.js +15 -0
  98. package/dist/cursor-skill-tool.js +230 -0
  99. package/dist/cursor-state.js +519 -0
  100. package/dist/cursor-task-presentation.js +59 -0
  101. package/dist/cursor-tool-lifecycle.js +94 -0
  102. package/dist/cursor-tool-manifest.js +34 -0
  103. package/dist/cursor-tool-presentation-registry.js +305 -0
  104. package/dist/cursor-tool-result-display-readers.js +158 -0
  105. package/dist/cursor-tool-transcript.js +32 -0
  106. package/dist/cursor-tool-visibility.js +28 -0
  107. package/dist/cursor-transcript-tool-formatters.js +618 -0
  108. package/dist/cursor-transcript-tool-specs.js +284 -0
  109. package/dist/cursor-transcript-utils.js +170 -0
  110. package/dist/cursor-usage-accounting.js +166 -0
  111. package/dist/cursor-web-tool-activity.js +23 -0
  112. package/dist/cursor-web-tool-args.js +12 -0
  113. package/dist/index.js +82 -0
  114. package/dist/model-discovery.js +305 -0
  115. package/dist/model-list-cache.js +156 -0
  116. package/docs/cursor-dogfood-checklist.md +2 -1
  117. package/docs/cursor-live-smoke-checklist.md +15 -14
  118. package/docs/cursor-model-ux-spec.md +29 -8
  119. package/docs/cursor-native-tool-replay.md +13 -13
  120. package/docs/cursor-native-tool-visual-audit.md +6 -3
  121. package/docs/cursor-testing-lessons.md +7 -7
  122. package/docs/cursor-tool-surfaces.md +5 -5
  123. package/docs/platform-smoke.md +6 -6
  124. package/node_modules/fast-uri/index.js +37 -1
  125. package/node_modules/fast-uri/package.json +1 -1
  126. package/node_modules/fast-uri/test/security.test.js +136 -0
  127. package/node_modules/hono/dist/cjs/context.js +28 -13
  128. package/node_modules/hono/dist/cjs/helper/proxy/index.js +4 -0
  129. package/node_modules/hono/dist/cjs/helper/streaming/sse.js +1 -1
  130. package/node_modules/hono/dist/cjs/hono-base.js +3 -2
  131. package/node_modules/hono/dist/cjs/jsx/base.js +27 -23
  132. package/node_modules/hono/dist/cjs/jsx/hooks/index.js +16 -13
  133. package/node_modules/hono/dist/cjs/middleware/cache/index.js +103 -8
  134. package/node_modules/hono/dist/cjs/middleware/compress/index.js +5 -0
  135. package/node_modules/hono/dist/cjs/middleware/cors/index.js +2 -2
  136. package/node_modules/hono/dist/cjs/middleware/etag/index.js +1 -1
  137. package/node_modules/hono/dist/cjs/middleware/jwk/jwk.js +9 -4
  138. package/node_modules/hono/dist/cjs/middleware/jwt/jwt.js +9 -4
  139. package/node_modules/hono/dist/cjs/middleware/language/language.js +10 -6
  140. package/node_modules/hono/dist/cjs/middleware/method-not-allowed/index.js +90 -0
  141. package/node_modules/hono/dist/cjs/middleware/secure-headers/secure-headers.js +10 -4
  142. package/node_modules/hono/dist/cjs/request.js +7 -9
  143. package/node_modules/hono/dist/cjs/router/reg-exp-router/node.js +55 -56
  144. package/node_modules/hono/dist/cjs/router/reg-exp-router/router.js +64 -85
  145. package/node_modules/hono/dist/cjs/router/reg-exp-router/trie.js +13 -5
  146. package/node_modules/hono/dist/cjs/router.js +1 -1
  147. package/node_modules/hono/dist/cjs/utils/accept.js +1 -1
  148. package/node_modules/hono/dist/cjs/utils/cookie.js +3 -2
  149. package/node_modules/hono/dist/cjs/utils/url.js +8 -8
  150. package/node_modules/hono/dist/context.js +28 -13
  151. package/node_modules/hono/dist/helper/proxy/index.js +4 -0
  152. package/node_modules/hono/dist/helper/streaming/sse.js +1 -1
  153. package/node_modules/hono/dist/hono-base.js +3 -2
  154. package/node_modules/hono/dist/jsx/base.js +27 -23
  155. package/node_modules/hono/dist/jsx/hooks/index.js +16 -13
  156. package/node_modules/hono/dist/middleware/cache/index.js +103 -8
  157. package/node_modules/hono/dist/middleware/compress/index.js +5 -0
  158. package/node_modules/hono/dist/middleware/cors/index.js +2 -2
  159. package/node_modules/hono/dist/middleware/etag/index.js +1 -1
  160. package/node_modules/hono/dist/middleware/jwk/jwk.js +9 -4
  161. package/node_modules/hono/dist/middleware/jwt/jwt.js +9 -4
  162. package/node_modules/hono/dist/middleware/language/language.js +10 -6
  163. package/node_modules/hono/dist/middleware/method-not-allowed/index.js +68 -0
  164. package/node_modules/hono/dist/middleware/secure-headers/secure-headers.js +10 -4
  165. package/node_modules/hono/dist/request.js +8 -10
  166. package/node_modules/hono/dist/router/reg-exp-router/node.js +55 -56
  167. package/node_modules/hono/dist/router/reg-exp-router/router.js +64 -85
  168. package/node_modules/hono/dist/router/reg-exp-router/trie.js +13 -5
  169. package/node_modules/hono/dist/router.js +1 -1
  170. package/node_modules/hono/dist/types/adapter/aws-lambda/types.d.ts +9 -0
  171. package/node_modules/hono/dist/types/client/types.d.ts +1 -1
  172. package/node_modules/hono/dist/types/hono-base.d.ts +4 -3
  173. package/node_modules/hono/dist/types/jsx/base.d.ts +4 -2
  174. package/node_modules/hono/dist/types/jsx/dom/index.d.ts +5 -5
  175. package/node_modules/hono/dist/types/jsx/dom/intrinsic-element/components.d.ts +2 -2
  176. package/node_modules/hono/dist/types/jsx/dom/server.d.ts +5 -5
  177. package/node_modules/hono/dist/types/jsx/hooks/index.d.ts +8 -6
  178. package/node_modules/hono/dist/types/jsx/index.d.ts +5 -5
  179. package/node_modules/hono/dist/types/middleware/cache/index.d.ts +6 -4
  180. package/node_modules/hono/dist/types/middleware/cors/index.d.ts +1 -1
  181. package/node_modules/hono/dist/types/middleware/jsx-renderer/index.d.ts +2 -2
  182. package/node_modules/hono/dist/types/middleware/jwk/jwk.d.ts +2 -0
  183. package/node_modules/hono/dist/types/middleware/jwt/jwt.d.ts +2 -0
  184. package/node_modules/hono/dist/types/middleware/method-not-allowed/index.d.ts +49 -0
  185. package/node_modules/hono/dist/types/router/reg-exp-router/node.d.ts +1 -1
  186. package/node_modules/hono/dist/types/router/reg-exp-router/trie.d.ts +2 -1
  187. package/node_modules/hono/dist/types/router.d.ts +1 -1
  188. package/node_modules/hono/dist/types/utils/headers.d.ts +2 -2
  189. package/node_modules/hono/dist/types/utils/url.d.ts +1 -0
  190. package/node_modules/hono/dist/utils/accept.js +1 -1
  191. package/node_modules/hono/dist/utils/cookie.js +4 -3
  192. package/node_modules/hono/dist/utils/url.js +6 -7
  193. package/node_modules/hono/package.json +10 -3
  194. package/node_modules/ip-address/README.md +146 -126
  195. package/node_modules/ip-address/dist/common.d.ts +29 -0
  196. package/node_modules/ip-address/dist/common.js +44 -5
  197. package/node_modules/ip-address/dist/common.js.map +1 -1
  198. package/node_modules/ip-address/dist/ipv4.d.ts +8 -1
  199. package/node_modules/ip-address/dist/ipv4.js +24 -20
  200. package/node_modules/ip-address/dist/ipv4.js.map +1 -1
  201. package/node_modules/ip-address/dist/ipv6.d.ts +68 -3
  202. package/node_modules/ip-address/dist/ipv6.js +180 -53
  203. package/node_modules/ip-address/dist/ipv6.js.map +1 -1
  204. package/node_modules/ip-address/dist/v4/constants.js +5 -1
  205. package/node_modules/ip-address/dist/v4/constants.js.map +1 -1
  206. package/node_modules/ip-address/dist/v6/constants.js +3 -2
  207. package/node_modules/ip-address/dist/v6/constants.js.map +1 -1
  208. package/node_modules/ip-address/package.json +6 -4
  209. package/package.json +23 -11
  210. package/platform-smoke.config.mjs +1 -1
  211. package/scripts/build.mjs +126 -0
  212. package/scripts/cloud-runtime-smoke.mjs +3 -1
  213. package/scripts/debug-provider-events.mjs +3 -1
  214. package/scripts/debug-sdk-events.mjs +1 -1
  215. package/scripts/isolated-cursor-smoke.sh +5 -4
  216. package/scripts/lib/cloud-smoke-cleanup-evidence.mjs +1 -1
  217. package/scripts/lib/cursor-visual-render.mjs +1 -1
  218. package/scripts/lib/ensure-built.mjs +16 -0
  219. package/scripts/lib/local-resume-smoke-harness.mjs +1 -1
  220. package/scripts/local-resume-smoke.mjs +3 -1
  221. package/scripts/platform-smoke/card-detect.mjs +1 -1
  222. package/scripts/platform-smoke/live-suite-runner.mjs +16 -40
  223. package/scripts/platform-smoke/platform-build-windows.ps1 +1 -1
  224. package/scripts/platform-smoke/render-ansi.mjs +1 -1
  225. package/scripts/platform-smoke/scenarios.mjs +1 -1
  226. package/scripts/platform-smoke/targets.mjs +2 -2
  227. package/scripts/platform-smoke.mjs +5 -5
  228. package/scripts/prepare.mjs +102 -0
  229. package/scripts/refresh-cursor-model-snapshots.mjs +16 -7
  230. package/scripts/steering-rpc-smoke.mjs +4 -2
  231. package/scripts/tmux-live-smoke.sh +11 -11
  232. package/scripts/visual-tui-smoke-self-test.mjs +4 -1
  233. package/scripts/visual-tui-smoke.mjs +51 -7
  234. package/shared/cursor-model-selection-identities.d.mts +21 -0
  235. package/shared/cursor-model-selection-identities.mjs +95 -0
  236. package/src/bundled-context-windows.ts +21 -12
  237. package/src/context.ts +1 -1
  238. package/src/cursor-agents-context-registration.ts +2 -2
  239. package/src/cursor-agents-context.ts +0 -2
  240. package/src/cursor-api-key.ts +5 -4
  241. package/src/cursor-cloud-reporting.ts +23 -2
  242. package/src/cursor-context-tools.ts +1 -1
  243. package/src/cursor-display-only-trace.ts +1 -1
  244. package/src/cursor-fallback-models.generated.ts +1726 -824
  245. package/src/cursor-live-run-accounting.ts +1 -1
  246. package/src/cursor-live-run-coordinator.ts +2 -1
  247. package/src/cursor-native-replay-routing.ts +1 -1
  248. package/src/cursor-native-tool-display-registration.ts +10 -11
  249. package/src/cursor-partial-content-emitter.ts +1 -1
  250. package/src/cursor-pi-tool-bridge-mcp.ts +1 -1
  251. package/src/cursor-pi-tool-bridge-run.ts +1 -1
  252. package/src/cursor-pi-tool-bridge-types.ts +1 -1
  253. package/src/cursor-provider-errors.ts +1 -1
  254. package/src/cursor-provider-lazy.ts +6 -5
  255. package/src/cursor-provider-live-run-drain.ts +2 -1
  256. package/src/cursor-provider-overflow.ts +1 -1
  257. package/src/cursor-provider-run-finalizer.ts +2 -1
  258. package/src/cursor-provider-turn-coordinator.ts +1 -1
  259. package/src/cursor-provider-turn-finalize.ts +12 -0
  260. package/src/cursor-provider-turn-lifecycle-emitter.ts +1 -1
  261. package/src/cursor-provider-turn-prepare.ts +1 -1
  262. package/src/cursor-provider-turn-types.ts +3 -1
  263. package/src/cursor-provider.ts +1 -1
  264. package/src/cursor-run-final-text.ts +1 -1
  265. package/src/cursor-sdk-billed-usage.ts +107 -0
  266. package/src/cursor-sdk-event-debug.ts +1 -1
  267. package/src/cursor-sdk-process-error-guard.ts +6 -4
  268. package/src/cursor-session-agent-lifecycle.ts +8 -9
  269. package/src/cursor-session-agent-resume.ts +23 -0
  270. package/src/cursor-session-agent.ts +1 -1
  271. package/src/cursor-session-compaction-prep.ts +4 -0
  272. package/src/cursor-session-send-policy.ts +1 -1
  273. package/src/cursor-tool-manifest.ts +1 -1
  274. package/src/cursor-usage-accounting.ts +71 -10
  275. package/src/index.ts +1 -1
  276. package/src/model-discovery.ts +32 -89
  277. package/tsconfig.build.json +8 -0
  278. package/tsconfig.json +16 -0
  279. package/node_modules/cross-spawn/node_modules/which/CHANGELOG.md +0 -166
@@ -0,0 +1,284 @@
1
+ import { CURSOR_KNOWN_NORMALIZED_TOOL_NAMES, CURSOR_REPLAY_ACTIVITY_TOOL_NAME, getCursorReplayCallSummary, getCursorToolActivityTitle, getCursorToolActivityReplaySpec, getCursorToolGenerateImageReplaySpec, } from "./cursor-tool-presentation-registry.js";
2
+ import { resolveCursorEditDiff } from "./cursor-edit-diff.js";
3
+ import { assembleCursorReplayActivityDetails, assembleCursorReplayGenerateImageDetails, CURSOR_REPLAY_UNREGISTERED_ACTIVITY_TOOL_NAME, } from "./cursor-replay-tool-details.js";
4
+ import { asRecord, getNumber, getString } from "./cursor-record-utils.js";
5
+ import { firstNonEmptyLine, formatDisplayPath, formatDiffString, formatError, limitText, truncateArg, } from "./cursor-transcript-utils.js";
6
+ import { buildCursorEditActivityDisplayArgs, buildFindDisplayArgs, buildGrepDisplayArgs, buildNativeEditDisplayArgs, buildReadDisplayArgs, buildShellDisplayArgs, buildWriteDisplayArgs, formatDelete, formatEdit, formatFallback, formatGenerateImage, formatGlob, formatGrep, formatLs, formatMcp, formatWebFetch, formatWebSearch, formatPlan, formatRecordScreen, formatSemSearch, formatRead, formatReadLints, formatShell, formatTask, formatTodos, formatWrite, formatNativeReadDisplayContent, getCursorWriteArgContent, getGlobBody, getGrepBody, getLsBody, getShellOutput, usesLocalReadPreview, } from "./cursor-transcript-tool-formatters.js";
7
+ function textToolResult(text, details) {
8
+ return { content: [{ type: "text", text }], details };
9
+ }
10
+ function buildCursorActivityDisplayArgs(args, activityTitle, activitySummary) {
11
+ const trimmedSummary = activitySummary?.trim();
12
+ return {
13
+ ...args,
14
+ activityTitle,
15
+ ...(trimmedSummary ? { activitySummary: trimmedSummary } : {}),
16
+ };
17
+ }
18
+ function buildReplaySummaryDisplay(toolName, args, result, contentText, details) {
19
+ const isError = result.status === "error";
20
+ const expandedText = details.expandedText ?? contentText;
21
+ const summary = details.summary;
22
+ return {
23
+ toolName,
24
+ args,
25
+ result: textToolResult(contentText, {
26
+ ...details,
27
+ summary,
28
+ expandedText,
29
+ }),
30
+ isError,
31
+ };
32
+ }
33
+ function buildActivityReplayDisplay(sourceToolName, context) {
34
+ const spec = TOOL_DISPLAY_IMPLEMENTATIONS[sourceToolName];
35
+ const activity = getCursorToolActivityReplaySpec(sourceToolName);
36
+ if (!activity)
37
+ throw new Error(`Missing activity replay spec for ${sourceToolName}`);
38
+ const activityTitle = getCursorToolActivityTitle(sourceToolName);
39
+ const replayArgs = activity.buildActivityArgs(context);
40
+ const activitySummary = getCursorReplayCallSummary(sourceToolName, replayArgs);
41
+ const activityArgs = buildCursorActivityDisplayArgs({ ...replayArgs }, activityTitle, activitySummary);
42
+ const contentText = spec.formatTranscript(context).trimEnd();
43
+ const activityFields = activity.buildDetails(context, contentText);
44
+ const details = assembleCursorReplayActivityDetails(sourceToolName, activityTitle, activityFields, contentText, context.result.status === "error", activitySummary);
45
+ return buildReplaySummaryDisplay(CURSOR_REPLAY_ACTIVITY_TOOL_NAME, activityArgs, context.result, contentText, details);
46
+ }
47
+ function buildGenerateImageReplayDisplay(context) {
48
+ const spec = TOOL_DISPLAY_IMPLEMENTATIONS.generateImage;
49
+ const replay = getCursorToolGenerateImageReplaySpec("generateImage");
50
+ if (!replay)
51
+ throw new Error("Missing generate image replay spec");
52
+ const activityTitle = getCursorToolActivityTitle("generateImage");
53
+ const replayArgs = replay.buildActivityArgs(context);
54
+ const activitySummary = getCursorReplayCallSummary("generateImage", replayArgs);
55
+ const activityArgs = buildCursorActivityDisplayArgs({ ...replayArgs }, activityTitle, activitySummary);
56
+ const contentText = spec.formatTranscript(context).trimEnd();
57
+ const details = assembleCursorReplayGenerateImageDetails(replay.buildDetails(context, contentText), contentText, context.result.status === "error", activitySummary);
58
+ return buildReplaySummaryDisplay(CURSOR_REPLAY_ACTIVITY_TOOL_NAME, activityArgs, context.result, contentText, details);
59
+ }
60
+ function buildGenericPiToolDisplay(context) {
61
+ const { rawName, name, args, result, options } = context;
62
+ const displayName = rawName.trim() || name;
63
+ const activityTitle = getCursorToolActivityTitle(displayName);
64
+ const contentText = formatFallback(name, args, result, options);
65
+ const fallbackBody = contentText.includes("\n\n") ? contentText.slice(contentText.indexOf("\n\n") + 2) : "";
66
+ const activitySummary = result.status === "error" ? undefined : firstNonEmptyLine(fallbackBody);
67
+ const activityArgs = buildCursorActivityDisplayArgs({}, activityTitle, activitySummary);
68
+ const summary = result.status === "error"
69
+ ? undefined
70
+ : activitySummary ?? truncateArg(displayName === "unknown" ? "tool" : displayName);
71
+ return buildReplaySummaryDisplay(CURSOR_REPLAY_ACTIVITY_TOOL_NAME, activityArgs, result, contentText, assembleCursorReplayActivityDetails(CURSOR_REPLAY_UNREGISTERED_ACTIVITY_TOOL_NAME, activityTitle, { summary, expandedText: contentText }, contentText, result.status === "error", activitySummary));
72
+ }
73
+ function buildEditActivitySummary(displayPath, value) {
74
+ const path = displayPath ?? "replayed";
75
+ const linesAdded = getNumber(value, "linesAdded");
76
+ const linesRemoved = getNumber(value, "linesRemoved");
77
+ const parts = [
78
+ linesAdded ? `added ${linesAdded} line${linesAdded === 1 ? "" : "s"}` : undefined,
79
+ linesRemoved ? `removed ${linesRemoved} line${linesRemoved === 1 ? "" : "s"}` : undefined,
80
+ ].filter((part) => Boolean(part));
81
+ if (parts.length > 0)
82
+ return `${path} ${parts.join(", ")}`;
83
+ return path;
84
+ }
85
+ function buildEditPiToolDisplay(context) {
86
+ const { rawName, args, result, options } = context;
87
+ const value = asRecord(result.value);
88
+ const rawDiff = resolveCursorEditDiff(value);
89
+ const normalizedDiff = formatDiffString(rawDiff, options);
90
+ const nativeEditArgs = buildNativeEditDisplayArgs(rawName, args, options);
91
+ const baseActivityArgs = buildCursorEditActivityDisplayArgs(args, options);
92
+ const displayPath = typeof baseActivityArgs.path === "string" ? baseActivityArgs.path : undefined;
93
+ const activityTitle = getCursorToolActivityTitle("edit");
94
+ const activityArgs = buildCursorActivityDisplayArgs(baseActivityArgs, activityTitle, displayPath);
95
+ const contentText = formatEdit(activityArgs, result, options);
96
+ const details = {
97
+ variant: "nativeEdit",
98
+ path: displayPath,
99
+ linesAdded: getNumber(value, "linesAdded"),
100
+ linesRemoved: getNumber(value, "linesRemoved"),
101
+ diffString: normalizedDiff,
102
+ diff: normalizedDiff,
103
+ firstChangedLine: getNumber(value, "firstChangedLine"),
104
+ };
105
+ if (nativeEditArgs) {
106
+ return {
107
+ toolName: "edit",
108
+ args: nativeEditArgs,
109
+ result: textToolResult(contentText, details),
110
+ isError: result.status === "error",
111
+ };
112
+ }
113
+ return buildReplaySummaryDisplay(CURSOR_REPLAY_ACTIVITY_TOOL_NAME, activityArgs, result, contentText.trimEnd(), assembleCursorReplayActivityDetails("edit", activityTitle, {
114
+ path: displayPath,
115
+ summary: result.status === "error" ? undefined : buildEditActivitySummary(displayPath, value ?? {}),
116
+ expandedText: contentText.trimEnd(),
117
+ diffString: normalizedDiff,
118
+ diff: normalizedDiff,
119
+ linesAdded: getNumber(value, "linesAdded"),
120
+ linesRemoved: getNumber(value, "linesRemoved"),
121
+ }, contentText.trimEnd(), result.status === "error", displayPath));
122
+ }
123
+ function buildWritePiToolDisplay(context) {
124
+ const { args, result, options } = context;
125
+ const value = asRecord(result.value);
126
+ const content = getCursorWriteArgContent(args);
127
+ const displayArgs = buildWriteDisplayArgs(args, options);
128
+ const displayPath = typeof args.path === "string" ? formatDisplayPath(args.path, options.cwd) : undefined;
129
+ const contentText = formatWrite(args, result, options).trimEnd();
130
+ const details = {
131
+ variant: "nativeWrite",
132
+ path: displayPath,
133
+ linesCreated: getNumber(value, "linesCreated"),
134
+ fileSize: getNumber(value, "fileSize"),
135
+ fileContentAfterWrite: getString(value, "fileContentAfterWrite"),
136
+ expandedText: contentText,
137
+ };
138
+ if (content === undefined) {
139
+ const activityTitle = getCursorToolActivityTitle("write");
140
+ return buildReplaySummaryDisplay(CURSOR_REPLAY_ACTIVITY_TOOL_NAME, buildCursorActivityDisplayArgs(displayArgs, activityTitle, displayPath ?? "file"), result, contentText, assembleCursorReplayActivityDetails("write", activityTitle, {
141
+ path: displayPath,
142
+ summary: result.status === "error" ? undefined : displayPath ?? "wrote file",
143
+ expandedText: contentText,
144
+ fileContentAfterWrite: getString(value, "fileContentAfterWrite"),
145
+ }, contentText, result.status === "error", displayPath ?? "file"));
146
+ }
147
+ return {
148
+ toolName: "write",
149
+ args: displayArgs,
150
+ result: textToolResult(contentText, details),
151
+ isError: result.status === "error",
152
+ };
153
+ }
154
+ const TOOL_DISPLAY_IMPLEMENTATIONS = {
155
+ read: {
156
+ formatTranscript: ({ args, result, options }) => formatRead(args, result, options),
157
+ buildPiToolDisplay: ({ args, result, options }) => {
158
+ const isError = result.status === "error";
159
+ const usesLocalPreview = !isError && usesLocalReadPreview(args, result, options);
160
+ return {
161
+ toolName: "read",
162
+ args: buildReadDisplayArgs(args, options, result),
163
+ result: textToolResult(isError ? formatError(result.error) : formatNativeReadDisplayContent(args, result, options), usesLocalPreview ? { localReadPreview: true } : undefined),
164
+ isError,
165
+ };
166
+ },
167
+ },
168
+ shell: {
169
+ formatTranscript: ({ args, result, options }) => formatShell(args, result, options),
170
+ buildPiToolDisplay: ({ args, result, options }) => {
171
+ const shellOutput = getShellOutput(result, args);
172
+ const isError = result.status === "error" || shellOutput.timedOut || (shellOutput.exitCode !== undefined && shellOutput.exitCode !== 0);
173
+ return {
174
+ toolName: "bash",
175
+ args: buildShellDisplayArgs(args),
176
+ result: textToolResult(result.status === "error" ? formatError(result.error) : limitText(shellOutput.text, options)),
177
+ isError,
178
+ };
179
+ },
180
+ },
181
+ grep: {
182
+ formatTranscript: ({ args, result, options }) => formatGrep(args, result, options),
183
+ buildPiToolDisplay: ({ args, result, options }) => {
184
+ const isError = result.status === "error";
185
+ return {
186
+ toolName: "grep",
187
+ args: buildGrepDisplayArgs(args, options),
188
+ result: textToolResult(isError ? formatError(result.error) : getGrepBody(result, options)),
189
+ isError,
190
+ };
191
+ },
192
+ },
193
+ glob: {
194
+ formatTranscript: ({ args, result, options }) => formatGlob(args, result, options),
195
+ buildPiToolDisplay: ({ args, result, options }) => {
196
+ const isError = result.status === "error";
197
+ return {
198
+ toolName: "find",
199
+ args: buildFindDisplayArgs(args, options),
200
+ result: textToolResult(isError ? formatError(result.error) : getGlobBody(result, options)),
201
+ isError,
202
+ };
203
+ },
204
+ },
205
+ ls: {
206
+ formatTranscript: ({ args, result, options }) => formatLs(args, result, options),
207
+ buildPiToolDisplay: ({ args, result, options }) => ({
208
+ toolName: "ls",
209
+ args,
210
+ result: textToolResult(result.status === "error" ? formatError(result.error) : getLsBody(result, options).trim()),
211
+ isError: result.status === "error",
212
+ }),
213
+ },
214
+ edit: {
215
+ formatTranscript: ({ args, result, options }) => formatEdit(args, result, options),
216
+ buildPiToolDisplay: buildEditPiToolDisplay,
217
+ },
218
+ write: {
219
+ formatTranscript: ({ args, result, options }) => formatWrite(args, result, options),
220
+ buildPiToolDisplay: buildWritePiToolDisplay,
221
+ },
222
+ delete: {
223
+ formatTranscript: ({ args, result, options }) => formatDelete(args, result, options),
224
+ buildPiToolDisplay: (context) => buildActivityReplayDisplay("delete", context),
225
+ },
226
+ readLints: {
227
+ formatTranscript: ({ args, result, options }) => formatReadLints(args, result, options),
228
+ buildPiToolDisplay: (context) => buildActivityReplayDisplay("readLints", context),
229
+ },
230
+ updateTodos: {
231
+ formatTranscript: ({ args, result, options }) => formatTodos(args, result, options, "updateTodos"),
232
+ buildPiToolDisplay: (context) => buildActivityReplayDisplay("updateTodos", context),
233
+ },
234
+ createPlan: {
235
+ formatTranscript: ({ args, result, options }) => formatPlan(args, result, options),
236
+ buildPiToolDisplay: (context) => buildActivityReplayDisplay("createPlan", context),
237
+ },
238
+ task: {
239
+ formatTranscript: ({ args, result, options }) => formatTask(args, result, options),
240
+ buildPiToolDisplay: (context) => buildActivityReplayDisplay("task", context),
241
+ },
242
+ generateImage: {
243
+ formatTranscript: ({ args, result, options }) => formatGenerateImage(args, result, options),
244
+ buildPiToolDisplay: (context) => buildGenerateImageReplayDisplay(context),
245
+ },
246
+ mcp: {
247
+ formatTranscript: ({ args, result, options }) => formatMcp(args, result, options),
248
+ buildPiToolDisplay: (context) => buildActivityReplayDisplay("mcp", context),
249
+ },
250
+ semSearch: {
251
+ formatTranscript: ({ args, result, options }) => formatSemSearch(args, result, options),
252
+ buildPiToolDisplay: (context) => buildActivityReplayDisplay("semSearch", context),
253
+ },
254
+ recordScreen: {
255
+ formatTranscript: ({ args, result, options }) => formatRecordScreen(args, result, options),
256
+ buildPiToolDisplay: (context) => buildActivityReplayDisplay("recordScreen", context),
257
+ },
258
+ webSearch: {
259
+ formatTranscript: ({ args, result, options }) => formatWebSearch(args, result, options),
260
+ buildPiToolDisplay: (context) => buildActivityReplayDisplay("webSearch", context),
261
+ },
262
+ webFetch: {
263
+ formatTranscript: ({ args, result, options }) => formatWebFetch(args, result, options),
264
+ buildPiToolDisplay: (context) => buildActivityReplayDisplay("webFetch", context),
265
+ },
266
+ };
267
+ export const CURSOR_TOOL_DISPLAY_SPEC_KEYS = CURSOR_KNOWN_NORMALIZED_TOOL_NAMES;
268
+ function getToolDisplaySpec(name) {
269
+ if (Object.hasOwn(TOOL_DISPLAY_IMPLEMENTATIONS, name))
270
+ return TOOL_DISPLAY_IMPLEMENTATIONS[name];
271
+ return undefined;
272
+ }
273
+ export function formatCursorToolTranscriptFromSpec(context) {
274
+ const spec = getToolDisplaySpec(context.name);
275
+ if (spec)
276
+ return spec.formatTranscript(context);
277
+ return formatFallback(context.name, context.args, context.result, context.options);
278
+ }
279
+ export function buildCursorPiToolDisplayFromSpec(context) {
280
+ const spec = getToolDisplaySpec(context.name);
281
+ if (spec)
282
+ return spec.buildPiToolDisplay(context);
283
+ return buildGenericPiToolDisplay(context);
284
+ }
@@ -0,0 +1,170 @@
1
+ import { closeSync, openSync, readSync, realpathSync, statSync } from "node:fs";
2
+ import { isAbsolute, relative, resolve } from "node:path";
3
+ import { asRecord, getRecord, getString, stringifyUnknown as stringifyUnknownValue } from "./cursor-record-utils.js";
4
+ export const DEFAULT_MAX_TRANSCRIPT_CHARS = 24000;
5
+ export const DEFAULT_MAX_TRANSCRIPT_LINES = 800;
6
+ export const DEFAULT_MAX_LIST_ITEMS = 200;
7
+ export const DEFAULT_READ_TRANSCRIPT_CHARS = 4000;
8
+ export const DEFAULT_READ_TRANSCRIPT_LINES = 12;
9
+ export const DEFAULT_NATIVE_READ_DISPLAY_LINES = 20;
10
+ export const LOCAL_READ_PREVIEW_NOTICE = "[local file preview at transcript time; Cursor read result content was unavailable]";
11
+ export function isLocalReadPreviewContent(text) {
12
+ return text.startsWith(LOCAL_READ_PREVIEW_NOTICE);
13
+ }
14
+ export function getToolName(toolCall) {
15
+ const record = asRecord(toolCall);
16
+ return getString(record, "name") ?? getString(record, "type") ?? getString(record, "toolName") ?? "unknown";
17
+ }
18
+ export function getToolArgs(toolCall) {
19
+ const record = asRecord(toolCall);
20
+ return getRecord(record, "args") ?? getRecord(record, "input") ?? {};
21
+ }
22
+ export function getToolResult(toolCall) {
23
+ const record = asRecord(toolCall);
24
+ return record?.result;
25
+ }
26
+ export function normalizeResult(result) {
27
+ const record = asRecord(result);
28
+ const status = getString(record, "status");
29
+ if (status === "success" || status === "error") {
30
+ return { status, value: record?.value, error: record?.error };
31
+ }
32
+ return { status, value: result, error: undefined };
33
+ }
34
+ export function stringifyUnknown(value) {
35
+ return stringifyUnknownValue(value, { pretty: true });
36
+ }
37
+ export function limitText(text, options = {}, knownTotalLines) {
38
+ const maxChars = options.maxChars ?? DEFAULT_MAX_TRANSCRIPT_CHARS;
39
+ const maxLines = options.maxLines ?? DEFAULT_MAX_TRANSCRIPT_LINES;
40
+ const lines = text.split("\n");
41
+ let limitedLines = lines.slice(0, maxLines);
42
+ let limited = limitedLines.join("\n");
43
+ let truncatedLines = Math.max((knownTotalLines ?? lines.length) - limitedLines.length, 0);
44
+ let truncatedChars = 0;
45
+ if (limited.length > maxChars) {
46
+ truncatedChars += limited.length - maxChars;
47
+ limited = limited.slice(0, maxChars);
48
+ limitedLines = limited.split("\n");
49
+ truncatedLines = Math.max(truncatedLines, Math.max((knownTotalLines ?? lines.length) - limitedLines.length, 0));
50
+ }
51
+ if (text.length > limited.length) {
52
+ truncatedChars += Math.max(text.length - limited.length - truncatedChars, 0);
53
+ }
54
+ const suffixParts = [];
55
+ if (truncatedLines > 0)
56
+ suffixParts.push(`${truncatedLines} more lines`);
57
+ if (truncatedChars > 0 && truncatedLines === 0)
58
+ suffixParts.push(`${truncatedChars} more chars`);
59
+ return suffixParts.length > 0 ? `${limited}\n... (${suffixParts.join(", ")} truncated)` : limited;
60
+ }
61
+ export function limitItems(items, options = {}) {
62
+ const maxListItems = options.maxListItems ?? DEFAULT_MAX_LIST_ITEMS;
63
+ return { items: items.slice(0, maxListItems), omitted: Math.max(items.length - maxListItems, 0) };
64
+ }
65
+ export function joinSections(header, body) {
66
+ const trimmedBody = body?.trimEnd();
67
+ return trimmedBody ? `${header}\n\n${trimmedBody}\n` : `${header}\n`;
68
+ }
69
+ export function formatError(error) {
70
+ const text = stringifyUnknown(error).trim();
71
+ return text ? `Error: ${text}` : "Error";
72
+ }
73
+ function normalizeDisplaySeparators(path) {
74
+ return path.replace(/\\/g, "/");
75
+ }
76
+ export function formatDisplayPath(path, cwd = process.cwd()) {
77
+ const trimmed = path.trim();
78
+ if (!trimmed)
79
+ return trimmed;
80
+ if (!isAbsolute(trimmed))
81
+ return normalizeDisplaySeparators(trimmed);
82
+ const relativePath = relative(cwd, trimmed);
83
+ if (!relativePath || relativePath === "")
84
+ return ".";
85
+ if (relativePath.startsWith("..") || isAbsolute(relativePath))
86
+ return normalizeDisplaySeparators(trimmed);
87
+ return normalizeDisplaySeparators(relativePath);
88
+ }
89
+ export function formatDiffPath(path, cwd = process.cwd()) {
90
+ if (path === "/dev/null")
91
+ return path;
92
+ return formatDisplayPath(path, cwd);
93
+ }
94
+ export function formatDiffHeaderLine(line, options) {
95
+ const match = /^(---|\+\+\+)\s+((?:[ab]\/)?)(.+)$/.exec(line);
96
+ if (!match)
97
+ return line;
98
+ const [, marker, prefix, rawPath] = match;
99
+ if (!prefix && rawPath !== "/dev/null")
100
+ return line;
101
+ const displayPath = formatDiffPath(rawPath, options.cwd);
102
+ return `${marker} ${prefix}${displayPath}`;
103
+ }
104
+ export function formatDiffString(diff, options) {
105
+ return diff
106
+ ?.split("\n")
107
+ .map((line) => formatDiffHeaderLine(line, options))
108
+ .join("\n");
109
+ }
110
+ export function resolveFilePath(path, cwd = process.cwd()) {
111
+ return isAbsolute(path) ? path : resolve(cwd, path);
112
+ }
113
+ export function isPathWithinCwd(filePath, cwd = process.cwd()) {
114
+ const relativePath = relative(cwd, filePath);
115
+ return relativePath === "" || (!relativePath.startsWith("..") && !isAbsolute(relativePath));
116
+ }
117
+ export function isSensitivePreviewPath(filePath) {
118
+ const segments = filePath.split(/[\\/]+/).map((segment) => segment.toLowerCase());
119
+ const basename = segments.at(-1) ?? "";
120
+ return (segments.includes(".ssh") ||
121
+ segments.includes("secrets") ||
122
+ basename === ".env" ||
123
+ basename.startsWith(".env.") ||
124
+ basename === ".npmrc" ||
125
+ basename === ".netrc" ||
126
+ basename === "credentials" ||
127
+ basename === "id_rsa" ||
128
+ basename === "id_ed25519" ||
129
+ /\.(?:pem|key|p12|pfx)$/i.test(basename));
130
+ }
131
+ export function readFilePreview(path, options) {
132
+ const cwd = options.cwd ?? process.cwd();
133
+ const filePath = resolveFilePath(path, cwd);
134
+ const maxChars = options.maxChars ?? DEFAULT_READ_TRANSCRIPT_CHARS;
135
+ const maxBytes = Math.max(8192, maxChars * 4);
136
+ let fd;
137
+ try {
138
+ const realCwd = realpathSync(cwd);
139
+ const realFilePath = realpathSync(filePath);
140
+ if (!isPathWithinCwd(realFilePath, realCwd) || isSensitivePreviewPath(filePath) || isSensitivePreviewPath(realFilePath))
141
+ return undefined;
142
+ const stat = statSync(realFilePath);
143
+ if (!stat.isFile())
144
+ return undefined;
145
+ fd = openSync(realFilePath, "r");
146
+ const buffer = Buffer.alloc(Math.min(stat.size, maxBytes));
147
+ const bytesRead = readSync(fd, buffer, 0, buffer.length, 0);
148
+ const text = buffer.toString("utf8", 0, bytesRead);
149
+ if (text.includes("\0"))
150
+ return undefined;
151
+ return text;
152
+ }
153
+ catch {
154
+ return undefined;
155
+ }
156
+ finally {
157
+ if (fd !== undefined)
158
+ closeSync(fd);
159
+ }
160
+ }
161
+ export function formatPathArg(args, options, key = "path") {
162
+ const path = args[key];
163
+ return typeof path === "string" && path.trim() ? formatDisplayPath(path, options.cwd) : undefined;
164
+ }
165
+ export function firstNonEmptyLine(text) {
166
+ return text.split("\n").find((line) => line.trim())?.trim();
167
+ }
168
+ export function truncateArg(value, maxLength = 120) {
169
+ return value.length > maxLength ? `${value.slice(0, maxLength - 1)}…` : value;
170
+ }
@@ -0,0 +1,166 @@
1
+ import { CURSOR_APPROX_CHARS_PER_TOKEN, CURSOR_IMAGE_TOKEN_ESTIMATE, estimateCursorContextTokens, estimateCursorTextTokens, } from "./context.js";
2
+ import { asRecord, getNumber } from "./cursor-record-utils.js";
3
+ function getPromptInputTokenBudget(model) {
4
+ const outputReserveTokens = Math.min(model.maxTokens, Math.max(1, Math.floor(model.contextWindow * 0.2)));
5
+ return Math.max(1, model.contextWindow - outputReserveTokens);
6
+ }
7
+ export function getCursorPromptOptions(model) {
8
+ return {
9
+ maxInputTokens: getPromptInputTokenBudget(model),
10
+ charsPerToken: CURSOR_APPROX_CHARS_PER_TOKEN,
11
+ imageTokenEstimate: CURSOR_IMAGE_TOKEN_ESTIMATE,
12
+ };
13
+ }
14
+ function getNonNegativeTokenCount(record, key) {
15
+ const value = getNumber(record, key);
16
+ return value === undefined || value < 0 ? undefined : Math.floor(value);
17
+ }
18
+ export function readCursorSdkTurnUsage(value) {
19
+ const record = asRecord(value);
20
+ const inputTokens = getNonNegativeTokenCount(record, "inputTokens");
21
+ const outputTokens = getNonNegativeTokenCount(record, "outputTokens");
22
+ const cacheReadTokens = getNonNegativeTokenCount(record, "cacheReadTokens");
23
+ const cacheWriteTokens = getNonNegativeTokenCount(record, "cacheWriteTokens");
24
+ if (inputTokens === undefined || outputTokens === undefined || cacheReadTokens === undefined || cacheWriteTokens === undefined)
25
+ return undefined;
26
+ return { inputTokens, outputTokens, cacheReadTokens, cacheWriteTokens };
27
+ }
28
+ export function readCursorSdkTurnUsageFromUpdate(update) {
29
+ const record = asRecord(update);
30
+ return record?.type === "turn-ended" ? readCursorSdkTurnUsage(record.usage) : undefined;
31
+ }
32
+ function stringifyUsageValue(value) {
33
+ try {
34
+ return JSON.stringify(value) ?? "";
35
+ }
36
+ catch {
37
+ return String(value);
38
+ }
39
+ }
40
+ export function estimateCursorAssistantSessionOutputTokens(message) {
41
+ const parts = message.content
42
+ .map((block) => {
43
+ if (block.type === "text")
44
+ return block.text;
45
+ if (block.type === "thinking")
46
+ return block.thinking;
47
+ if (block.type === "toolCall") {
48
+ return `Tool call (${block.name}, call ${block.id}): ${stringifyUsageValue(block.arguments)}`;
49
+ }
50
+ return "";
51
+ })
52
+ .filter(Boolean);
53
+ return estimateCursorTextTokens(parts.join("\n"), { charsPerToken: CURSOR_APPROX_CHARS_PER_TOKEN });
54
+ }
55
+ function withAssistantMessage(context, partial) {
56
+ return { ...context, messages: [...context.messages, partial] };
57
+ }
58
+ export function estimateCursorContextTotalTokens(partial, model, context) {
59
+ return estimateCursorContextTokens(withAssistantMessage(context, partial), getCursorPromptOptions(model));
60
+ }
61
+ function getCursorSdkUncachedInputTokens(turnUsage) {
62
+ // Observed raw local turn-ended.usage: inputTokens is the full prompt; cache fields partition it.
63
+ // Published SDK toTokenUsage instead sums all four into totalTokens — do not use that transform here.
64
+ return turnUsage.inputTokens - turnUsage.cacheReadTokens - turnUsage.cacheWriteTokens;
65
+ }
66
+ export function isCursorSdkUsagePartitionSafe(turnUsage, model) {
67
+ const counts = [turnUsage.inputTokens, turnUsage.outputTokens, turnUsage.cacheReadTokens, turnUsage.cacheWriteTokens];
68
+ const uncachedInput = getCursorSdkUncachedInputTokens(turnUsage);
69
+ return (counts.every((count) => Number.isFinite(count) && count >= 0) &&
70
+ Number.isFinite(uncachedInput) &&
71
+ uncachedInput >= 0 &&
72
+ turnUsage.outputTokens <= model.maxTokens);
73
+ }
74
+ export function isCursorSdkUsageSafeForPiMessage(turnUsage, model) {
75
+ return (isCursorSdkUsagePartitionSafe(turnUsage, model) &&
76
+ turnUsage.inputTokens + turnUsage.outputTokens <= model.contextWindow);
77
+ }
78
+ export function applyCursorSdkUsage(partial, turnUsage) {
79
+ // Pi treats input/cacheRead/cacheWrite as disjoint additive prompt components.
80
+ partial.usage.input = getCursorSdkUncachedInputTokens(turnUsage);
81
+ partial.usage.output = turnUsage.outputTokens;
82
+ partial.usage.cacheRead = turnUsage.cacheReadTokens;
83
+ partial.usage.cacheWrite = turnUsage.cacheWriteTokens;
84
+ // totalTokens is context occupancy (full prompt + output), not the sum of spend components alone.
85
+ partial.usage.totalTokens = turnUsage.inputTokens + turnUsage.outputTokens;
86
+ }
87
+ function isCompatibleCursorAssistantMeasurement(assistant, model) {
88
+ return assistant.api === model.api && assistant.provider === model.provider && assistant.model === model.id;
89
+ }
90
+ function getLatestCompactionBoundary(context) {
91
+ for (let index = context.messages.length - 1; index >= 0; index -= 1) {
92
+ const message = context.messages[index];
93
+ if (message.role !== "compactionSummary")
94
+ continue;
95
+ const tokensBefore = message.tokensBefore;
96
+ return {
97
+ index,
98
+ tokensBefore: Number.isFinite(tokensBefore) && tokensBefore !== undefined && tokensBefore > 0 ? Math.floor(tokensBefore) : undefined,
99
+ };
100
+ }
101
+ return undefined;
102
+ }
103
+ function getLastAcceptedContextOccupancy(context, model) {
104
+ const boundary = getLatestCompactionBoundary(context);
105
+ for (let index = context.messages.length - 1; index >= 0; index -= 1) {
106
+ if (boundary && index < boundary.index)
107
+ break;
108
+ const message = context.messages[index];
109
+ if (message.role !== "assistant" || !("usage" in message))
110
+ continue;
111
+ const assistant = message;
112
+ if (assistant.stopReason === "aborted" || assistant.stopReason === "error" || !assistant.usage)
113
+ continue;
114
+ if (!isCompatibleCursorAssistantMeasurement(assistant, model))
115
+ continue;
116
+ const { usage } = assistant;
117
+ const total = usage.totalTokens || usage.input + usage.output + usage.cacheRead + usage.cacheWrite;
118
+ if (!Number.isFinite(total) || total <= 0 || total > model.contextWindow)
119
+ continue;
120
+ if (boundary?.tokensBefore !== undefined && total >= boundary.tokensBefore)
121
+ continue;
122
+ return total;
123
+ }
124
+ return 0;
125
+ }
126
+ export function applyCursorApproximateUsage(partial, model, context, sessionInputTokens) {
127
+ const outputTokens = estimateCursorAssistantSessionOutputTokens(partial);
128
+ partial.usage.input = Math.max(0, sessionInputTokens);
129
+ partial.usage.output = outputTokens;
130
+ partial.usage.cacheRead = 0;
131
+ partial.usage.cacheWrite = 0;
132
+ // Never report less occupancy than the last compatible same-model in-window assistant measurement.
133
+ partial.usage.totalTokens = Math.max(partial.usage.input + partial.usage.output, estimateCursorContextTotalTokens(partial, model, context), getLastAcceptedContextOccupancy(context, model));
134
+ }
135
+ function applyCursorOccupancyEstimate(partial, model, context) {
136
+ partial.usage.totalTokens = Math.max(estimateCursorContextTotalTokens(partial, model, context), getLastAcceptedContextOccupancy(context, model));
137
+ }
138
+ function isCurrentLocalOccupancy(turn, model, context) {
139
+ if (!isCursorSdkUsageSafeForPiMessage(turn, model))
140
+ return false;
141
+ const tokensBefore = getLatestCompactionBoundary(context)?.tokensBefore;
142
+ return tokensBefore === undefined || turn.inputTokens + turn.outputTokens < tokensBefore;
143
+ }
144
+ function applyResolvedCursorOccupancy(partial, model, context, localTurn) {
145
+ if (localTurn && isCurrentLocalOccupancy(localTurn, model, context)) {
146
+ partial.usage.totalTokens = localTurn.inputTokens + localTurn.outputTokens;
147
+ return;
148
+ }
149
+ applyCursorOccupancyEstimate(partial, model, context);
150
+ }
151
+ export function applyCursorUsage(partial, model, context, sessionInputTokens, sdkUsage) {
152
+ const billed = sdkUsage?.billed;
153
+ const localTurn = sdkUsage?.runtime === "local" ? sdkUsage.turn : undefined;
154
+ if (billed && isCursorSdkUsagePartitionSafe(billed, model)) {
155
+ applyCursorSdkUsage(partial, billed);
156
+ applyResolvedCursorOccupancy(partial, model, context, localTurn);
157
+ return;
158
+ }
159
+ // Only local raw turn-ended usage has a captured full-prompt/cache-partition occupancy contract.
160
+ if (localTurn && isCursorSdkUsageSafeForPiMessage(localTurn, model)) {
161
+ applyCursorSdkUsage(partial, localTurn);
162
+ applyResolvedCursorOccupancy(partial, model, context, localTurn);
163
+ return;
164
+ }
165
+ applyCursorApproximateUsage(partial, model, context, sessionInputTokens);
166
+ }
@@ -0,0 +1,23 @@
1
+ import { getFirstStringByKeys } from "./cursor-record-utils.js";
2
+ import { classifyCursorWebToolKind, normalizeCursorToolName as normalizeToolName, } from "./cursor-tool-presentation-registry.js";
3
+ function getMcpToolName(args) {
4
+ const toolName = getFirstStringByKeys(args, ["toolName", "tool_name"]);
5
+ const trimmed = toolName?.trim();
6
+ return trimmed || undefined;
7
+ }
8
+ /**
9
+ * Maps SDK/host/MCP tool names to transcript display keys.
10
+ * Web search/fetch often arrives as MCP `toolName` values, not dedicated SDK ToolTypes.
11
+ */
12
+ export function resolveTranscriptToolName(rawName, args) {
13
+ const normalized = normalizeToolName(rawName);
14
+ const directWebKind = classifyCursorWebToolKind(rawName) ?? classifyCursorWebToolKind(normalized);
15
+ if (directWebKind)
16
+ return directWebKind;
17
+ if (normalized === "mcp") {
18
+ const mcpWebKind = classifyCursorWebToolKind(getMcpToolName(args));
19
+ if (mcpWebKind)
20
+ return mcpWebKind;
21
+ }
22
+ return normalized;
23
+ }
@@ -0,0 +1,12 @@
1
+ import { asRecord, firstNonEmptyString } from "./cursor-record-utils.js";
2
+ function getNestedMcpArgs(args) {
3
+ return asRecord(args.args) ?? {};
4
+ }
5
+ export function extractWebSearchQuery(args) {
6
+ const nested = getNestedMcpArgs(args);
7
+ return firstNonEmptyString(typeof args.search_term === "string" ? args.search_term : undefined, typeof args.searchTerm === "string" ? args.searchTerm : undefined, typeof args.query === "string" ? args.query : undefined, typeof args.q === "string" ? args.q : undefined, typeof nested.search_term === "string" ? nested.search_term : undefined, typeof nested.searchTerm === "string" ? nested.searchTerm : undefined, typeof nested.query === "string" ? nested.query : undefined, typeof nested.q === "string" ? nested.q : undefined);
8
+ }
9
+ export function extractWebFetchTarget(args) {
10
+ const nested = getNestedMcpArgs(args);
11
+ return firstNonEmptyString(typeof args.url === "string" ? args.url : undefined, typeof args.uri === "string" ? args.uri : undefined, typeof args.href === "string" ? args.href : undefined, typeof nested.url === "string" ? nested.url : undefined, typeof nested.uri === "string" ? nested.uri : undefined, typeof nested.href === "string" ? nested.href : undefined);
12
+ }