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,355 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { Server as McpProtocolServer } from "@modelcontextprotocol/sdk/server/index.js";
3
+ import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
4
+ import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
5
+ import { bridgeToolExecutionAbortTracker } from "./cursor-pi-tool-bridge-abort.js";
6
+ import { MCP_ENDPOINT_ROOT, MCP_SERVER_NAME } from "./cursor-pi-tool-bridge-constants.js";
7
+ import { writeCursorPiToolBridgeDiagnostic, } from "./cursor-pi-tool-bridge-diagnostics.js";
8
+ import { resolveCursorPiToolBridgeCallTimeoutMs } from "./cursor-pi-tool-bridge-env.js";
9
+ import { asToolResultMessage, containsKnownMcpToolName, convertPiContentToMcpContent, normalizeMcpArgs, snapshotToolToMcpTool, waitForProtocolFlush, } from "./cursor-pi-tool-bridge-mcp.js";
10
+ import { asRecord, getFirstStringByKeys } from "./cursor-record-utils.js";
11
+ const MCP_SERVER_VERSION = "0.1.0";
12
+ export class CursorPiToolBridgeRunImpl {
13
+ id;
14
+ enabled;
15
+ snapshot;
16
+ mcpServers;
17
+ registry;
18
+ env;
19
+ endpointPath;
20
+ callTimeoutMs;
21
+ knownMcpToolNames;
22
+ knownCursorMcpCallIds = new Set();
23
+ queuedRequests = [];
24
+ pendingByPiToolCallId = new Map();
25
+ pendingByBridgeCallId = new Map();
26
+ pendingByCursorMcpCallId = new Map();
27
+ onToolRequest;
28
+ debugRecorder;
29
+ liveRunHandlerDetached = false;
30
+ mcpServer;
31
+ mcpTransport;
32
+ toolCallCounter = 0;
33
+ disposed = false;
34
+ constructor(registry, env, snapshot, enabled, options = {}) {
35
+ this.registry = registry;
36
+ this.env = env;
37
+ this.snapshot = snapshot;
38
+ this.enabled = enabled;
39
+ this.onToolRequest = options.onToolRequest;
40
+ this.debugRecorder = options.debugRecorder;
41
+ this.id = `cursor-pi-bridge-run-${randomUUID()}`;
42
+ this.endpointPath = `${MCP_ENDPOINT_ROOT}/${randomUUID()}/mcp`;
43
+ this.callTimeoutMs = resolveCursorPiToolBridgeCallTimeoutMs(env);
44
+ this.knownMcpToolNames = new Set(snapshot.tools.map((tool) => tool.mcpToolName));
45
+ }
46
+ async start() {
47
+ if (!this.enabled)
48
+ return;
49
+ await this.createMcpServer();
50
+ const endpointUrl = await this.registry.registerRun(this.endpointPath, this);
51
+ this.mcpServers = { [MCP_SERVER_NAME]: { type: "http", url: endpointUrl } };
52
+ }
53
+ emitStartDiagnostics(bridgeEnabled) {
54
+ const base = this.lifecycleDiagnosticFields();
55
+ this.emitDiagnostic({ event: "run_created", ...base });
56
+ if (!this.enabled) {
57
+ this.emitDiagnostic({
58
+ event: "run_skipped",
59
+ ...base,
60
+ reason: bridgeEnabled ? "no_exposed_tools" : "disabled",
61
+ });
62
+ return;
63
+ }
64
+ this.emitDiagnostic({
65
+ event: "tools_exposed",
66
+ ...base,
67
+ pairs: this.snapshot.tools.map((tool) => ({
68
+ piToolName: tool.piToolName,
69
+ mcpToolName: tool.mcpToolName,
70
+ })),
71
+ });
72
+ }
73
+ async handleHttpRequest(req, res) {
74
+ if (this.disposed || !this.mcpTransport) {
75
+ res.writeHead(410, { "content-type": "application/json" }).end(JSON.stringify({ error: "Cursor pi tool bridge run is disposed" }));
76
+ return;
77
+ }
78
+ await this.mcpTransport.handleRequest(req, res);
79
+ }
80
+ takeQueuedToolRequests() {
81
+ return this.queuedRequests.splice(0);
82
+ }
83
+ setOnToolRequest(handler) {
84
+ if (!handler) {
85
+ this.liveRunHandlerDetached = true;
86
+ this.rejectQueuedToolRequestsWithoutHandler("Cursor pi tool bridge has no active live run");
87
+ }
88
+ else {
89
+ this.liveRunHandlerDetached = false;
90
+ }
91
+ this.onToolRequest = handler;
92
+ if (handler) {
93
+ for (const request of this.queuedRequests.splice(0)) {
94
+ const pending = this.pendingByPiToolCallId.get(request.piToolCallId);
95
+ if (pending)
96
+ this.dispatchPendingToolRequest(pending, handler);
97
+ }
98
+ }
99
+ }
100
+ setDebugRecorder(recorder) {
101
+ this.debugRecorder = recorder;
102
+ }
103
+ recordBridgeRaw(payload) {
104
+ try {
105
+ this.debugRecorder?.recordBridgeRaw(payload);
106
+ }
107
+ catch {
108
+ // Debug capture must never block or strand a bridge call.
109
+ }
110
+ }
111
+ async resolveToolResults(toolResults) {
112
+ let resolvedCount = 0;
113
+ for (const toolResult of toolResults) {
114
+ const pending = this.pendingByPiToolCallId.get(toolResult.toolCallId);
115
+ if (!pending || pending.settled)
116
+ continue;
117
+ this.resolvePending(pending, {
118
+ content: convertPiContentToMcpContent(toolResult.content),
119
+ isError: toolResult.isError || undefined,
120
+ });
121
+ resolvedCount += 1;
122
+ }
123
+ if (resolvedCount > 0)
124
+ await waitForProtocolFlush();
125
+ }
126
+ async resolveToolResultsFromContext(context) {
127
+ await this.resolveToolResults(context.messages.map(asToolResultMessage).filter((message) => message !== undefined));
128
+ }
129
+ hasPendingPiToolCallId(piToolCallId) {
130
+ return this.pendingByPiToolCallId.has(piToolCallId);
131
+ }
132
+ cancelPendingPiToolCallId(piToolCallId, reason) {
133
+ const pending = this.pendingByPiToolCallId.get(piToolCallId);
134
+ if (!pending)
135
+ return false;
136
+ this.rejectPending(pending, new Error(reason), "cancelled");
137
+ return true;
138
+ }
139
+ isBridgeMcpToolCall(toolCall) {
140
+ const record = asRecord(toolCall);
141
+ if (!record)
142
+ return false;
143
+ const toolName = getFirstStringByKeys(record, ["name", "toolName", "mcpToolName"], { nonEmpty: true });
144
+ if (toolName && this.knownMcpToolNames.has(toolName))
145
+ return true;
146
+ const isMcpEnvelope = toolName === "mcp" || toolName === MCP_SERVER_NAME;
147
+ const cursorMcpCallId = getFirstStringByKeys(record, ["call_id", "callId", "id", "toolCallId", "requestId"], { nonEmpty: true });
148
+ if (cursorMcpCallId && this.knownCursorMcpCallIds.has(cursorMcpCallId) && isMcpEnvelope)
149
+ return true;
150
+ if (containsKnownMcpToolName(toolCall, this.knownMcpToolNames))
151
+ return true;
152
+ return false;
153
+ }
154
+ cancel(reason) {
155
+ const error = new Error(reason);
156
+ const pendingCount = this.pendingCount();
157
+ const queuedCount = this.queuedRequests.length;
158
+ if (pendingCount > 0 || queuedCount > 0) {
159
+ this.emitDiagnostic({
160
+ event: "run_cancelled",
161
+ ...this.lifecycleDiagnosticFields(pendingCount),
162
+ queuedCount,
163
+ cancelledRequestCount: pendingCount,
164
+ });
165
+ }
166
+ this.queuedRequests.splice(0);
167
+ for (const pending of [...this.pendingByBridgeCallId.values()]) {
168
+ this.rejectAndAbortPending(pending, error, "cancelled");
169
+ }
170
+ }
171
+ async dispose() {
172
+ if (this.disposed)
173
+ return;
174
+ this.disposed = true;
175
+ this.cancel("Cursor pi tool bridge run disposed");
176
+ await waitForProtocolFlush();
177
+ await Promise.allSettled([
178
+ this.mcpTransport?.close(),
179
+ this.mcpServer?.close(),
180
+ ]);
181
+ await this.registry.unregisterRun(this.endpointPath, this);
182
+ this.emitDiagnostic({
183
+ event: "run_disposed",
184
+ ...this.lifecycleDiagnosticFields(),
185
+ });
186
+ }
187
+ async createMcpServer() {
188
+ const server = new McpProtocolServer({ name: "pi-cursor-sdk-tool-bridge", version: MCP_SERVER_VERSION }, { capabilities: { tools: {} } });
189
+ const transport = new StreamableHTTPServerTransport({
190
+ sessionIdGenerator: randomUUID,
191
+ });
192
+ server.setRequestHandler(ListToolsRequestSchema, async () => ({
193
+ tools: this.snapshot.tools.map(snapshotToolToMcpTool),
194
+ }));
195
+ server.setRequestHandler(CallToolRequestSchema, async (request, extra) => {
196
+ return this.enqueueToolRequest(request.params.name, request.params.arguments, String(extra.requestId), extra.signal);
197
+ });
198
+ this.mcpServer = server;
199
+ this.mcpTransport = transport;
200
+ await server.connect(transport);
201
+ }
202
+ enqueueToolRequest(mcpToolName, argsValue, cursorMcpCallId, signal) {
203
+ const piToolName = this.snapshot.mcpToolNameToPiToolName.get(mcpToolName);
204
+ if (!piToolName) {
205
+ return Promise.resolve({
206
+ content: [{ type: "text", text: `Unknown pi bridge tool: ${mcpToolName}` }],
207
+ isError: true,
208
+ });
209
+ }
210
+ if (this.disposed)
211
+ return Promise.reject(new Error("Cursor pi tool bridge run is disposed"));
212
+ this.toolCallCounter += 1;
213
+ const bridgeCallId = `${this.id}-bridge-${this.toolCallCounter}`;
214
+ const request = {
215
+ runId: this.id,
216
+ bridgeCallId,
217
+ cursorMcpCallId,
218
+ piToolCallId: `${this.id}-tool-${this.toolCallCounter}`,
219
+ piToolName,
220
+ mcpToolName,
221
+ args: normalizeMcpArgs(argsValue),
222
+ };
223
+ return new Promise((resolve, reject) => {
224
+ const pending = {
225
+ request,
226
+ resolve,
227
+ reject,
228
+ signal,
229
+ settled: false,
230
+ };
231
+ pending.onAbort = () => {
232
+ this.rejectAndAbortPending(pending, new Error("Cursor MCP bridge tool request was aborted"), "cancelled");
233
+ };
234
+ if (signal?.aborted) {
235
+ pending.onAbort();
236
+ return;
237
+ }
238
+ signal?.addEventListener("abort", pending.onAbort, { once: true });
239
+ this.pendingByPiToolCallId.set(request.piToolCallId, pending);
240
+ this.pendingByBridgeCallId.set(request.bridgeCallId, pending);
241
+ this.pendingByCursorMcpCallId.set(cursorMcpCallId, pending);
242
+ this.knownCursorMcpCallIds.add(cursorMcpCallId);
243
+ pending.timeout = setTimeout(() => {
244
+ const reason = `Cursor pi bridge CallTool timed out after ${this.callTimeoutMs} ms`;
245
+ this.rejectAndAbortPending(pending, new Error(reason));
246
+ }, this.callTimeoutMs);
247
+ pending.timeout.unref?.();
248
+ if (!this.onToolRequest) {
249
+ if (this.liveRunHandlerDetached) {
250
+ this.rejectPending(pending, new Error("Cursor pi tool bridge has no active live run"), "cancelled");
251
+ return;
252
+ }
253
+ this.queuedRequests.push(request);
254
+ this.emitRequestQueuedDiagnostic(request);
255
+ this.recordBridgeRaw({ kind: "queued", request });
256
+ return;
257
+ }
258
+ this.emitRequestQueuedDiagnostic(request);
259
+ this.recordBridgeRaw({ kind: "queued", request });
260
+ this.dispatchPendingToolRequest(pending, this.onToolRequest);
261
+ });
262
+ }
263
+ dispatchPendingToolRequest(pending, handler) {
264
+ try {
265
+ handler(pending.request);
266
+ }
267
+ catch (error) {
268
+ this.rejectPending(pending, error instanceof Error ? error : new Error(String(error)), "error");
269
+ }
270
+ }
271
+ rejectQueuedToolRequestsWithoutHandler(reason) {
272
+ while (this.queuedRequests.length > 0) {
273
+ const request = this.queuedRequests.shift();
274
+ const pending = this.pendingByPiToolCallId.get(request.piToolCallId);
275
+ if (pending)
276
+ this.rejectPending(pending, new Error(reason), "cancelled");
277
+ }
278
+ }
279
+ resolvePending(pending, result) {
280
+ if (pending.settled)
281
+ return;
282
+ pending.settled = true;
283
+ this.removePending(pending);
284
+ this.emitRequestResolvedDiagnostic(pending.request, result.isError === true);
285
+ this.recordBridgeRaw({ kind: "resolved", request: pending.request, result });
286
+ pending.resolve(result);
287
+ }
288
+ rejectPending(pending, error, kind = "error") {
289
+ if (pending.settled)
290
+ return false;
291
+ pending.settled = true;
292
+ this.removePending(pending);
293
+ this.emitRequestRejectedDiagnostic(pending.request, kind);
294
+ this.recordBridgeRaw({
295
+ kind: "rejected",
296
+ request: pending.request,
297
+ error: error.message,
298
+ rejectionKind: kind,
299
+ });
300
+ pending.reject(error);
301
+ return true;
302
+ }
303
+ rejectAndAbortPending(pending, error, kind = "error") {
304
+ if (this.rejectPending(pending, error, kind)) {
305
+ bridgeToolExecutionAbortTracker.abort(pending.request.piToolCallId, error.message);
306
+ }
307
+ }
308
+ lifecycleDiagnosticFields(pendingCount = this.pendingCount()) {
309
+ return {
310
+ runId: this.id,
311
+ enabled: this.enabled,
312
+ exposedToolCount: this.snapshot.tools.length,
313
+ pendingCount,
314
+ };
315
+ }
316
+ requestDiagnosticFields(request) {
317
+ return {
318
+ runId: this.id,
319
+ bridgeCallId: request.bridgeCallId,
320
+ cursorMcpCallId: request.cursorMcpCallId,
321
+ piToolCallId: request.piToolCallId,
322
+ mcpToolName: request.mcpToolName,
323
+ piToolName: request.piToolName,
324
+ pendingCount: this.pendingCount(),
325
+ };
326
+ }
327
+ emitRequestQueuedDiagnostic(request) {
328
+ this.emitDiagnostic({ event: "request_queued", ...this.requestDiagnosticFields(request) });
329
+ }
330
+ emitRequestResolvedDiagnostic(request, isError) {
331
+ this.emitDiagnostic({ event: "request_resolved", ...this.requestDiagnosticFields(request), isError });
332
+ }
333
+ emitRequestRejectedDiagnostic(request, rejectionKind) {
334
+ this.emitDiagnostic({ event: "request_rejected", ...this.requestDiagnosticFields(request), rejectionKind });
335
+ }
336
+ emitDiagnostic(event) {
337
+ writeCursorPiToolBridgeDiagnostic(this.env, event, this.debugRecorder);
338
+ }
339
+ pendingCount() {
340
+ return this.pendingByBridgeCallId.size;
341
+ }
342
+ removePending(pending) {
343
+ if (pending.onAbort)
344
+ pending.signal?.removeEventListener("abort", pending.onAbort);
345
+ if (pending.timeout)
346
+ clearTimeout(pending.timeout);
347
+ this.pendingByPiToolCallId.delete(pending.request.piToolCallId);
348
+ this.pendingByBridgeCallId.delete(pending.request.bridgeCallId);
349
+ if (pending.request.cursorMcpCallId)
350
+ this.pendingByCursorMcpCallId.delete(pending.request.cursorMcpCallId);
351
+ const queuedIndex = this.queuedRequests.findIndex((request) => request.bridgeCallId === pending.request.bridgeCallId);
352
+ if (queuedIndex >= 0)
353
+ this.queuedRequests.splice(queuedIndex, 1);
354
+ }
355
+ }
@@ -0,0 +1,163 @@
1
+ import { createServer } from "node:http";
2
+ import { asRecord } from "./cursor-record-utils.js";
3
+ import { buildCursorPiToolBridgeSnapshot, buildCursorPiToolBridgeSurfaceSignature, createEmptySnapshot, resolveCursorPiToolBridgeBuiltinsEnabled, resolveCursorPiToolBridgeEnabled, } from "./cursor-pi-tool-bridge-snapshot.js";
4
+ export const LOOPBACK_HOST = "127.0.0.1";
5
+ const HTTP_SERVER_CLOSE_GRACE_MS = 250;
6
+ export class CursorPiToolBridgeRegistry {
7
+ pi;
8
+ env;
9
+ runs = new Set();
10
+ routes = new Map();
11
+ httpServer;
12
+ listenPromise;
13
+ constructor(pi, env = process.env) {
14
+ this.pi = pi;
15
+ this.env = env;
16
+ }
17
+ isEnabled() {
18
+ return resolveCursorPiToolBridgeEnabled(this.env);
19
+ }
20
+ getToolSurfaceSignature() {
21
+ if (!this.isEnabled())
22
+ return "bridge:off";
23
+ const snapshot = buildCursorPiToolBridgeSnapshot(this.pi, {
24
+ exposeOverlappingBuiltins: resolveCursorPiToolBridgeBuiltinsEnabled(this.env),
25
+ });
26
+ return buildCursorPiToolBridgeSurfaceSignature(snapshot);
27
+ }
28
+ async createRun(options = {}) {
29
+ const bridgeEnabled = this.isEnabled();
30
+ const snapshot = bridgeEnabled
31
+ ? buildCursorPiToolBridgeSnapshot(this.pi, {
32
+ exposeOverlappingBuiltins: resolveCursorPiToolBridgeBuiltinsEnabled(this.env),
33
+ })
34
+ : createEmptySnapshot();
35
+ const { CursorPiToolBridgeRunImpl } = await import("./cursor-pi-tool-bridge-run.js");
36
+ const run = new CursorPiToolBridgeRunImpl(this, this.env, snapshot, bridgeEnabled && snapshot.tools.length > 0, options);
37
+ this.runs.add(run);
38
+ await run.start();
39
+ run.emitStartDiagnostics(bridgeEnabled);
40
+ return run;
41
+ }
42
+ async disposeAll(reason = "Cursor pi tool bridge disposed") {
43
+ await Promise.all([...this.runs].map(async (run) => {
44
+ run.cancel(reason);
45
+ await run.dispose();
46
+ }));
47
+ }
48
+ async registerRun(pathname, run) {
49
+ await this.ensureHttpServer();
50
+ this.routes.set(pathname, run);
51
+ const address = this.getHttpServerAddress();
52
+ if (!address)
53
+ throw new Error("Cursor pi tool bridge HTTP server is not listening");
54
+ return `http://${LOOPBACK_HOST}:${address.port}${pathname}`;
55
+ }
56
+ async unregisterRun(pathname, run) {
57
+ if (this.routes.get(pathname) === run)
58
+ this.routes.delete(pathname);
59
+ this.runs.delete(run);
60
+ if (this.routes.size === 0)
61
+ await this.closeHttpServer();
62
+ }
63
+ getHttpServerAddress() {
64
+ const address = asRecord(this.httpServer?.address());
65
+ if (typeof address?.port !== "number")
66
+ return undefined;
67
+ return {
68
+ address: typeof address.address === "string" ? address.address : LOOPBACK_HOST,
69
+ family: typeof address.family === "string" ? address.family : "IPv4",
70
+ port: address.port,
71
+ };
72
+ }
73
+ getEndpointCount() {
74
+ return this.routes.size;
75
+ }
76
+ hasPendingPiToolCallId(piToolCallId) {
77
+ for (const run of this.runs) {
78
+ if (run.hasPendingPiToolCallId(piToolCallId))
79
+ return true;
80
+ }
81
+ return false;
82
+ }
83
+ cancelPendingPiToolCallId(piToolCallId, reason) {
84
+ for (const run of this.runs) {
85
+ if (run.cancelPendingPiToolCallId(piToolCallId, reason))
86
+ return true;
87
+ }
88
+ return false;
89
+ }
90
+ async ensureHttpServer() {
91
+ if (this.httpServer) {
92
+ await this.listenPromise;
93
+ return;
94
+ }
95
+ const server = createServer((req, res) => {
96
+ void this.handleHttpRequest(req, res);
97
+ });
98
+ this.httpServer = server;
99
+ this.listenPromise = new Promise((resolve, reject) => {
100
+ const onError = (error) => {
101
+ server.off("listening", onListening);
102
+ reject(error);
103
+ };
104
+ const onListening = () => {
105
+ server.off("error", onError);
106
+ resolve();
107
+ };
108
+ server.once("error", onError);
109
+ server.once("listening", onListening);
110
+ server.listen(0, LOOPBACK_HOST);
111
+ });
112
+ await this.listenPromise;
113
+ }
114
+ async closeHttpServer() {
115
+ const server = this.httpServer;
116
+ if (!server)
117
+ return;
118
+ this.httpServer = undefined;
119
+ this.listenPromise = undefined;
120
+ await new Promise((resolve, reject) => {
121
+ let settled = false;
122
+ let closeTimer;
123
+ const settle = (error) => {
124
+ if (settled)
125
+ return;
126
+ settled = true;
127
+ if (closeTimer)
128
+ clearTimeout(closeTimer);
129
+ if (error)
130
+ reject(error);
131
+ else
132
+ resolve();
133
+ };
134
+ closeTimer = setTimeout(() => settle(), HTTP_SERVER_CLOSE_GRACE_MS);
135
+ closeTimer.unref?.();
136
+ server.close((error) => {
137
+ settle(error ?? undefined);
138
+ });
139
+ server.closeIdleConnections();
140
+ server.closeAllConnections();
141
+ });
142
+ }
143
+ async handleHttpRequest(req, res) {
144
+ if (req.socket.localAddress !== LOOPBACK_HOST) {
145
+ res.writeHead(403, { "content-type": "application/json" }).end(JSON.stringify({ error: "Cursor pi tool bridge only accepts loopback requests" }));
146
+ return;
147
+ }
148
+ const url = new URL(req.url ?? "/", `http://${LOOPBACK_HOST}`);
149
+ const run = this.routes.get(url.pathname);
150
+ if (!run) {
151
+ res.writeHead(404, { "content-type": "application/json" }).end(JSON.stringify({ error: "Cursor pi tool bridge endpoint not found" }));
152
+ return;
153
+ }
154
+ try {
155
+ await run.handleHttpRequest(req, res);
156
+ }
157
+ catch (error) {
158
+ if (!res.headersSent) {
159
+ res.writeHead(500, { "content-type": "application/json" }).end(JSON.stringify({ error: error instanceof Error ? error.message : String(error) }));
160
+ }
161
+ }
162
+ }
163
+ }
@@ -0,0 +1,63 @@
1
+ import { createMcpToolName, normalizeMcpInputSchema, stableNameHash } from "./cursor-pi-tool-bridge-mcp.js";
2
+ export { CURSOR_PI_TOOL_BRIDGE_BUILTINS_ENV, CURSOR_PI_TOOL_BRIDGE_ENV, resolveCursorPiToolBridgeBuiltinsEnabled, resolveCursorPiToolBridgeEnabled, } from "./cursor-pi-tool-bridge-env.js";
3
+ import { isRegisteredCursorNativeToolName } from "./cursor-native-tool-display-state.js";
4
+ import { isExcludedFromCursorBridgeExposure } from "./cursor-tool-presentation-registry.js";
5
+ const OVERLAPPING_CURSOR_NATIVE_PI_BUILTIN_TOOL_NAMES = new Set(["read", "bash", "write", "edit", "grep", "find", "ls"]);
6
+ export function createEmptySnapshot() {
7
+ return {
8
+ tools: [],
9
+ mcpToolNameToPiToolName: new Map(),
10
+ piToolNameToMcpToolName: new Map(),
11
+ };
12
+ }
13
+ function isOverlappingCursorNativePiToolName(toolName) {
14
+ return OVERLAPPING_CURSOR_NATIVE_PI_BUILTIN_TOOL_NAMES.has(toolName);
15
+ }
16
+ export function buildCursorPiToolBridgeSurfaceSignature(snapshot) {
17
+ if (snapshot.tools.length === 0)
18
+ return "bridge:empty";
19
+ const serializedTools = snapshot.tools
20
+ .map((tool) => JSON.stringify({
21
+ piToolName: tool.piToolName,
22
+ mcpToolName: tool.mcpToolName,
23
+ description: tool.description,
24
+ promptGuidelines: tool.promptGuidelines,
25
+ inputSchema: tool.inputSchema,
26
+ source: tool.sourceInfo?.source,
27
+ path: tool.sourceInfo?.path,
28
+ scope: tool.sourceInfo?.scope,
29
+ }))
30
+ .sort()
31
+ .join("\0");
32
+ return `bridge:on:${stableNameHash(serializedTools)}`;
33
+ }
34
+ export function buildCursorPiToolBridgeSnapshot(pi, options = {}) {
35
+ const activeToolNames = new Set(pi.getActiveTools());
36
+ const allTools = pi.getAllTools();
37
+ const usedMcpToolNames = new Set();
38
+ const mcpToolNameToPiToolName = new Map();
39
+ const piToolNameToMcpToolName = new Map();
40
+ const tools = [];
41
+ const exposeOverlappingBuiltins = options.exposeOverlappingBuiltins === true;
42
+ for (const tool of allTools) {
43
+ if (!activeToolNames.has(tool.name))
44
+ continue;
45
+ if (isExcludedFromCursorBridgeExposure(tool.name) && isRegisteredCursorNativeToolName(tool.name))
46
+ continue;
47
+ if (!exposeOverlappingBuiltins && isOverlappingCursorNativePiToolName(tool.name))
48
+ continue;
49
+ const mcpToolName = createMcpToolName(tool.name, usedMcpToolNames);
50
+ const description = tool.description || `Run pi tool ${tool.name}`;
51
+ mcpToolNameToPiToolName.set(mcpToolName, tool.name);
52
+ piToolNameToMcpToolName.set(tool.name, mcpToolName);
53
+ tools.push({
54
+ piToolName: tool.name,
55
+ mcpToolName,
56
+ description,
57
+ promptGuidelines: tool.promptGuidelines,
58
+ inputSchema: normalizeMcpInputSchema(tool.parameters),
59
+ sourceInfo: tool.sourceInfo,
60
+ });
61
+ }
62
+ return { tools, mcpToolNameToPiToolName, piToolNameToMcpToolName };
63
+ }
@@ -0,0 +1 @@
1
+ export {};