@herbertgao/pi-extensions 2026.9.10 → 2026.9.12

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 (271) hide show
  1. package/README.md +1 -0
  2. package/THIRD_PARTY_NOTICES.md +26 -0
  3. package/node_modules/@herbertgao/pi-bark/package.json +2 -2
  4. package/node_modules/@herbertgao/pi-cc-extensions/README.en.md +2 -2
  5. package/node_modules/@herbertgao/pi-cc-extensions/README.md +2 -2
  6. package/node_modules/@herbertgao/pi-cc-extensions/package.json +3 -2
  7. package/node_modules/@herbertgao/pi-subagents/CHANGELOG.md +6 -0
  8. package/node_modules/@herbertgao/pi-subagents/package.json +2 -2
  9. package/node_modules/@herbertgao/resume-from/package.json +1 -1
  10. package/node_modules/@herbertgao/sol-pi/README.md +3 -3
  11. package/node_modules/@herbertgao/sol-pi/THIRD_PARTY_NOTICES.md +4 -4
  12. package/node_modules/@herbertgao/sol-pi/agents-install.md +4 -4
  13. package/node_modules/@herbertgao/sol-pi/docs/compatibility.md +6 -6
  14. package/node_modules/@herbertgao/sol-pi/package.json +2 -2
  15. package/node_modules/@juicesharp/rpiv-ask-user-question/package.json +2 -2
  16. package/node_modules/@narumitw/pi-btw/dist/index.ts +39 -89
  17. package/node_modules/@narumitw/pi-btw/dist/index.ts.map +3 -3
  18. package/node_modules/@narumitw/pi-btw/package.json +4 -4
  19. package/node_modules/@narumitw/pi-btw/src/btw.ts +28 -87
  20. package/node_modules/@narumitw/pi-btw/src/main-tree-picker.ts +8 -0
  21. package/node_modules/@narumitw/pi-btw/src/side-thread.ts +40 -37
  22. package/node_modules/@narumitw/pi-caffeinate/README.md +21 -66
  23. package/node_modules/@narumitw/pi-caffeinate/dist/index.ts +10 -41
  24. package/node_modules/@narumitw/pi-caffeinate/dist/index.ts.map +2 -2
  25. package/node_modules/@narumitw/pi-caffeinate/package.json +50 -51
  26. package/node_modules/@narumitw/pi-caffeinate/src/caffeinate.ts +637 -663
  27. package/node_modules/@narumitw/pi-caffeinate/src/dbus-inhibit.ts +114 -120
  28. package/node_modules/@narumitw/pi-caffeinate/src/inhibitor-process.ts +29 -29
  29. package/node_modules/@narumitw/pi-caffeinate/src/inhibitors.ts +108 -126
  30. package/node_modules/@narumitw/pi-caffeinate/src/settings.ts +124 -128
  31. package/node_modules/pi-mcp-adapter/CHANGELOG.md +62 -0
  32. package/node_modules/pi-mcp-adapter/README.md +75 -7
  33. package/node_modules/pi-mcp-adapter/bearer-command-resolver.ts +193 -0
  34. package/node_modules/pi-mcp-adapter/cli.js +56 -5
  35. package/node_modules/pi-mcp-adapter/commands.ts +96 -5
  36. package/node_modules/pi-mcp-adapter/config.ts +73 -6
  37. package/node_modules/pi-mcp-adapter/direct-tool-surface.ts +22 -28
  38. package/node_modules/pi-mcp-adapter/direct-tools.ts +13 -5
  39. package/node_modules/pi-mcp-adapter/dist/abort.d.ts +2 -0
  40. package/node_modules/pi-mcp-adapter/dist/abort.js +36 -0
  41. package/node_modules/pi-mcp-adapter/dist/abort.js.map +1 -0
  42. package/node_modules/pi-mcp-adapter/dist/bearer-command-resolver.d.ts +6 -0
  43. package/node_modules/pi-mcp-adapter/dist/bearer-command-resolver.js +194 -0
  44. package/node_modules/pi-mcp-adapter/dist/bearer-command-resolver.js.map +1 -0
  45. package/node_modules/pi-mcp-adapter/dist/config.d.ts +5 -0
  46. package/node_modules/pi-mcp-adapter/dist/config.js +75 -6
  47. package/node_modules/pi-mcp-adapter/dist/config.js.map +1 -1
  48. package/node_modules/pi-mcp-adapter/dist/consent-manager.d.ts +18 -0
  49. package/node_modules/pi-mcp-adapter/dist/consent-manager.js +88 -0
  50. package/node_modules/pi-mcp-adapter/dist/consent-manager.js.map +1 -0
  51. package/node_modules/pi-mcp-adapter/dist/elicitation-handler.d.ts +17 -0
  52. package/node_modules/pi-mcp-adapter/dist/elicitation-handler.js +316 -0
  53. package/node_modules/pi-mcp-adapter/dist/elicitation-handler.js.map +1 -0
  54. package/node_modules/pi-mcp-adapter/dist/errors.d.ts +131 -0
  55. package/node_modules/pi-mcp-adapter/dist/errors.js +278 -0
  56. package/node_modules/pi-mcp-adapter/dist/errors.js.map +1 -0
  57. package/node_modules/pi-mcp-adapter/dist/http-ca.d.ts +8 -0
  58. package/node_modules/pi-mcp-adapter/dist/http-ca.js +88 -0
  59. package/node_modules/pi-mcp-adapter/dist/http-ca.js.map +1 -0
  60. package/node_modules/pi-mcp-adapter/dist/jev-client.d.ts +15 -0
  61. package/node_modules/pi-mcp-adapter/dist/jev-client.js +340 -0
  62. package/node_modules/pi-mcp-adapter/dist/jev-client.js.map +1 -0
  63. package/node_modules/pi-mcp-adapter/dist/jev-contracts.d.ts +77 -0
  64. package/node_modules/pi-mcp-adapter/dist/jev-contracts.js +2 -0
  65. package/node_modules/pi-mcp-adapter/dist/jev-contracts.js.map +1 -0
  66. package/node_modules/pi-mcp-adapter/dist/jev-key-store.d.ts +22 -0
  67. package/node_modules/pi-mcp-adapter/dist/jev-key-store.js +85 -0
  68. package/node_modules/pi-mcp-adapter/dist/jev-key-store.js.map +1 -0
  69. package/node_modules/pi-mcp-adapter/dist/json-schema-validator.d.ts +2 -0
  70. package/node_modules/pi-mcp-adapter/dist/json-schema-validator.js +55 -0
  71. package/node_modules/pi-mcp-adapter/dist/json-schema-validator.js.map +1 -0
  72. package/node_modules/pi-mcp-adapter/dist/lifecycle.d.ts +56 -0
  73. package/node_modules/pi-mcp-adapter/dist/lifecycle.js +410 -0
  74. package/node_modules/pi-mcp-adapter/dist/lifecycle.js.map +1 -0
  75. package/node_modules/pi-mcp-adapter/dist/logger.d.ts +51 -0
  76. package/node_modules/pi-mcp-adapter/dist/logger.js +131 -0
  77. package/node_modules/pi-mcp-adapter/dist/logger.js.map +1 -0
  78. package/node_modules/pi-mcp-adapter/dist/mcp-auth-fetch.d.ts +26 -0
  79. package/node_modules/pi-mcp-adapter/dist/mcp-auth-fetch.js +103 -0
  80. package/node_modules/pi-mcp-adapter/dist/mcp-auth-fetch.js.map +1 -0
  81. package/node_modules/pi-mcp-adapter/dist/mcp-auth-flow.d.ts +118 -0
  82. package/node_modules/pi-mcp-adapter/dist/mcp-auth-flow.js +1098 -0
  83. package/node_modules/pi-mcp-adapter/dist/mcp-auth-flow.js.map +1 -0
  84. package/node_modules/pi-mcp-adapter/dist/mcp-auth.d.ts +168 -0
  85. package/node_modules/pi-mcp-adapter/dist/mcp-auth.js +1117 -0
  86. package/node_modules/pi-mcp-adapter/dist/mcp-auth.js.map +1 -0
  87. package/node_modules/pi-mcp-adapter/dist/mcp-bearer-store.js +7 -140
  88. package/node_modules/pi-mcp-adapter/dist/mcp-bearer-store.js.map +1 -1
  89. package/node_modules/pi-mcp-adapter/dist/mcp-callback-server.d.ts +53 -0
  90. package/node_modules/pi-mcp-adapter/dist/mcp-callback-server.js +441 -0
  91. package/node_modules/pi-mcp-adapter/dist/mcp-callback-server.js.map +1 -0
  92. package/node_modules/pi-mcp-adapter/dist/mcp-oauth-provider.d.ts +148 -0
  93. package/node_modules/pi-mcp-adapter/dist/mcp-oauth-provider.js +689 -0
  94. package/node_modules/pi-mcp-adapter/dist/mcp-oauth-provider.js.map +1 -0
  95. package/node_modules/pi-mcp-adapter/dist/mcp-probe.d.ts +6 -0
  96. package/node_modules/pi-mcp-adapter/dist/mcp-probe.js +166 -0
  97. package/node_modules/pi-mcp-adapter/dist/mcp-probe.js.map +1 -0
  98. package/node_modules/pi-mcp-adapter/dist/mcp-tasks.d.ts +102 -0
  99. package/node_modules/pi-mcp-adapter/dist/mcp-tasks.js +369 -0
  100. package/node_modules/pi-mcp-adapter/dist/mcp-tasks.js.map +1 -0
  101. package/node_modules/pi-mcp-adapter/dist/mcp-trace.d.ts +95 -0
  102. package/node_modules/pi-mcp-adapter/dist/mcp-trace.js +242 -0
  103. package/node_modules/pi-mcp-adapter/dist/mcp-trace.js.map +1 -0
  104. package/node_modules/pi-mcp-adapter/dist/metadata-cache.js +2 -11
  105. package/node_modules/pi-mcp-adapter/dist/metadata-cache.js.map +1 -1
  106. package/node_modules/pi-mcp-adapter/dist/npx-resolver.d.ts +6 -0
  107. package/node_modules/pi-mcp-adapter/dist/npx-resolver.js +505 -0
  108. package/node_modules/pi-mcp-adapter/dist/npx-resolver.js.map +1 -0
  109. package/node_modules/pi-mcp-adapter/dist/request-headers-command.d.ts +10 -0
  110. package/node_modules/pi-mcp-adapter/dist/request-headers-command.js +312 -0
  111. package/node_modules/pi-mcp-adapter/dist/request-headers-command.js.map +1 -0
  112. package/node_modules/pi-mcp-adapter/dist/runtime-owner.d.ts +13 -0
  113. package/node_modules/pi-mcp-adapter/dist/runtime-owner.js +97 -0
  114. package/node_modules/pi-mcp-adapter/dist/runtime-owner.js.map +1 -0
  115. package/node_modules/pi-mcp-adapter/dist/sampling-handler.d.ts +17 -0
  116. package/node_modules/pi-mcp-adapter/dist/sampling-handler.js +203 -0
  117. package/node_modules/pi-mcp-adapter/dist/sampling-handler.js.map +1 -0
  118. package/node_modules/pi-mcp-adapter/dist/secure-keyring.d.ts +11 -0
  119. package/node_modules/pi-mcp-adapter/dist/secure-keyring.js +172 -0
  120. package/node_modules/pi-mcp-adapter/dist/secure-keyring.js.map +1 -0
  121. package/node_modules/pi-mcp-adapter/dist/server-manager.d.ts +157 -0
  122. package/node_modules/pi-mcp-adapter/dist/server-manager.js +1743 -0
  123. package/node_modules/pi-mcp-adapter/dist/server-manager.js.map +1 -0
  124. package/node_modules/pi-mcp-adapter/dist/session-approvals.d.ts +34 -0
  125. package/node_modules/pi-mcp-adapter/dist/session-approvals.js +140 -0
  126. package/node_modules/pi-mcp-adapter/dist/session-approvals.js.map +1 -0
  127. package/node_modules/pi-mcp-adapter/dist/session-recovery.d.ts +26 -0
  128. package/node_modules/pi-mcp-adapter/dist/session-recovery.js +142 -0
  129. package/node_modules/pi-mcp-adapter/dist/session-recovery.js.map +1 -0
  130. package/node_modules/pi-mcp-adapter/dist/state.d.ts +73 -0
  131. package/node_modules/pi-mcp-adapter/dist/state.js +2 -0
  132. package/node_modules/pi-mcp-adapter/dist/state.js.map +1 -0
  133. package/node_modules/pi-mcp-adapter/dist/types.d.ts +36 -0
  134. package/node_modules/pi-mcp-adapter/dist/types.js +18 -0
  135. package/node_modules/pi-mcp-adapter/dist/types.js.map +1 -1
  136. package/node_modules/pi-mcp-adapter/dist/ui-resource-handler.d.ts +17 -0
  137. package/node_modules/pi-mcp-adapter/dist/ui-resource-handler.js +219 -0
  138. package/node_modules/pi-mcp-adapter/dist/ui-resource-handler.js.map +1 -0
  139. package/node_modules/pi-mcp-adapter/dist/unix-socket-transport.d.ts +15 -0
  140. package/node_modules/pi-mcp-adapter/dist/unix-socket-transport.js +85 -0
  141. package/node_modules/pi-mcp-adapter/dist/unix-socket-transport.js.map +1 -0
  142. package/node_modules/pi-mcp-adapter/dist/utils.d.ts +1 -0
  143. package/node_modules/pi-mcp-adapter/dist/utils.js +13 -0
  144. package/node_modules/pi-mcp-adapter/dist/utils.js.map +1 -1
  145. package/node_modules/pi-mcp-adapter/elicitation-handler.ts +29 -19
  146. package/node_modules/pi-mcp-adapter/examples/jev-accessibility-loop.mjs +85 -0
  147. package/node_modules/pi-mcp-adapter/examples/jev-semantic-filter.mjs +34 -0
  148. package/node_modules/pi-mcp-adapter/index.ts +129 -6
  149. package/node_modules/pi-mcp-adapter/init.ts +6 -19
  150. package/node_modules/pi-mcp-adapter/jev-client.ts +273 -0
  151. package/node_modules/pi-mcp-adapter/jev-contracts.ts +53 -0
  152. package/node_modules/pi-mcp-adapter/jev-key-store.ts +79 -0
  153. package/node_modules/pi-mcp-adapter/lifecycle.ts +14 -3
  154. package/node_modules/pi-mcp-adapter/mcp-bearer-store.ts +7 -142
  155. package/node_modules/pi-mcp-adapter/mcp-code.ts +119 -11
  156. package/node_modules/pi-mcp-adapter/mcp-output-guard.ts +4 -0
  157. package/node_modules/pi-mcp-adapter/mcp-references.ts +5 -3
  158. package/node_modules/pi-mcp-adapter/mcp-script-worker.mjs +5 -0
  159. package/node_modules/pi-mcp-adapter/mcp-tasks.ts +467 -0
  160. package/node_modules/pi-mcp-adapter/metadata-cache.ts +2 -13
  161. package/node_modules/pi-mcp-adapter/namespace-tools.ts +1 -1
  162. package/node_modules/pi-mcp-adapter/package.json +17 -9
  163. package/node_modules/pi-mcp-adapter/proxy-modes.ts +319 -166
  164. package/node_modules/pi-mcp-adapter/request-headers-command.ts +6 -3
  165. package/node_modules/pi-mcp-adapter/search-ranking.ts +38 -7
  166. package/node_modules/pi-mcp-adapter/secure-keyring.ts +170 -0
  167. package/node_modules/pi-mcp-adapter/semantic-search.ts +186 -0
  168. package/node_modules/pi-mcp-adapter/server-manager.ts +293 -50
  169. package/node_modules/pi-mcp-adapter/session-approvals.ts +14 -0
  170. package/node_modules/pi-mcp-adapter/skills/mcp-scripting/SKILL.md +2 -0
  171. package/node_modules/pi-mcp-adapter/state.ts +3 -1
  172. package/node_modules/pi-mcp-adapter/tool-approval.ts +26 -4
  173. package/node_modules/pi-mcp-adapter/tool-metadata.ts +6 -14
  174. package/node_modules/pi-mcp-adapter/tool-registrar.ts +8 -6
  175. package/node_modules/pi-mcp-adapter/tool-result-renderer.ts +4 -1
  176. package/node_modules/pi-mcp-adapter/types.ts +53 -0
  177. package/node_modules/pi-mcp-adapter/ui-server.ts +17 -9
  178. package/node_modules/pi-mcp-adapter/utils.ts +16 -0
  179. package/node_modules/pi-multi-account/CHANGELOG.md +1209 -0
  180. package/node_modules/pi-multi-account/CONTRIBUTING.md +61 -0
  181. package/node_modules/pi-multi-account/LICENSE +21 -0
  182. package/node_modules/pi-multi-account/README.md +197 -0
  183. package/node_modules/pi-multi-account/SECURITY.md +27 -0
  184. package/node_modules/pi-multi-account/auth-file-transaction.ts +56 -0
  185. package/node_modules/pi-multi-account/child-usability.ts +233 -0
  186. package/node_modules/pi-multi-account/compaction-summary.ts +32 -0
  187. package/node_modules/pi-multi-account/completion-route-planner.ts +224 -0
  188. package/node_modules/pi-multi-account/context-guard.ts +420 -0
  189. package/node_modules/pi-multi-account/cursor/LICENSE +21 -0
  190. package/node_modules/pi-multi-account/cursor/NOTICE +2 -0
  191. package/node_modules/pi-multi-account/cursor/auth.ts +165 -0
  192. package/node_modules/pi-multi-account/cursor/bridge-handle.ts +155 -0
  193. package/node_modules/pi-multi-account/cursor/conversation-registry.ts +104 -0
  194. package/node_modules/pi-multi-account/cursor/cursor-models-raw.json +611 -0
  195. package/node_modules/pi-multi-account/cursor/cursor-shared.ts +192 -0
  196. package/node_modules/pi-multi-account/cursor/h2-bridge.mjs +175 -0
  197. package/node_modules/pi-multi-account/cursor/index.ts +572 -0
  198. package/node_modules/pi-multi-account/cursor/message-parsing.ts +323 -0
  199. package/node_modules/pi-multi-account/cursor/prompt-usage.ts +53 -0
  200. package/node_modules/pi-multi-account/cursor/proto/agent_pb.ts +15294 -0
  201. package/node_modules/pi-multi-account/cursor/proxy.ts +2510 -0
  202. package/node_modules/pi-multi-account/cursor/session-lifecycle.ts +40 -0
  203. package/node_modules/pi-multi-account/cursor/sse-keepalive.ts +24 -0
  204. package/node_modules/pi-multi-account/cursor/stream-lifecycle.ts +193 -0
  205. package/node_modules/pi-multi-account/cursor/upstream-watchdog.ts +88 -0
  206. package/node_modules/pi-multi-account/cursor-bridge.ts +240 -0
  207. package/node_modules/pi-multi-account/cursor-model-name.ts +12 -0
  208. package/node_modules/pi-multi-account/index.ts +11825 -0
  209. package/node_modules/pi-multi-account/model-catalog.ts +354 -0
  210. package/node_modules/pi-multi-account/package.json +101 -0
  211. package/node_modules/pi-multi-account/pi-contract.ts +281 -0
  212. package/node_modules/pi-multi-account/provider-payload-stream.ts +44 -0
  213. package/node_modules/pi-multi-account/provider-priority.ts +189 -0
  214. package/node_modules/pi-multi-account/slot-proxy-auth.ts +167 -0
  215. package/node_modules/pi-multi-account/slot-proxy.ts +344 -0
  216. package/node_modules/pi-multi-account/state-file-transaction.ts +67 -0
  217. package/node_modules/pi-multi-account/usage.ts +1099 -0
  218. package/node_modules/pi-typesafe/README.md +6 -2
  219. package/node_modules/pi-typesafe/dist/client.d.ts +11 -0
  220. package/node_modules/pi-typesafe/dist/client.js +45 -10
  221. package/node_modules/pi-typesafe/dist/index.d.ts +2 -2
  222. package/node_modules/pi-typesafe/dist/index.js +1 -1
  223. package/node_modules/pi-typesafe/package.json +2 -2
  224. package/node_modules/pi-web-access/CHANGELOG.md +36 -0
  225. package/node_modules/pi-web-access/README.md +75 -18
  226. package/node_modules/pi-web-access/anysearch.ts +4 -15
  227. package/node_modules/pi-web-access/bocha.ts +3 -22
  228. package/node_modules/pi-web-access/brave.ts +3 -21
  229. package/node_modules/pi-web-access/brightdata.ts +5 -32
  230. package/node_modules/pi-web-access/content-find.ts +168 -53
  231. package/node_modules/pi-web-access/curator-page.ts +4 -1
  232. package/node_modules/pi-web-access/curator-run.ts +2 -1
  233. package/node_modules/pi-web-access/curator-server.ts +1 -0
  234. package/node_modules/pi-web-access/dist/index.js +24620 -0
  235. package/node_modules/pi-web-access/domain-filter-normalization.ts +14 -0
  236. package/node_modules/pi-web-access/duckduckgo.ts +3 -21
  237. package/node_modules/pi-web-access/extract.ts +3 -1
  238. package/node_modules/pi-web-access/firecrawl.ts +5 -29
  239. package/node_modules/pi-web-access/gemini-search.ts +81 -32
  240. package/node_modules/pi-web-access/index.ts +149 -148
  241. package/node_modules/pi-web-access/jina-search.ts +4 -15
  242. package/node_modules/pi-web-access/kagi.ts +4 -13
  243. package/node_modules/pi-web-access/kimi-search.ts +5 -30
  244. package/node_modules/pi-web-access/mistral-search.ts +1 -15
  245. package/node_modules/pi-web-access/ollama.ts +2 -7
  246. package/node_modules/pi-web-access/openai-search.ts +174 -36
  247. package/node_modules/pi-web-access/opencode-session-headers.ts +24 -0
  248. package/node_modules/pi-web-access/package.json +10 -4
  249. package/node_modules/pi-web-access/page-query.ts +10 -2
  250. package/node_modules/pi-web-access/parallel.ts +1 -15
  251. package/node_modules/pi-web-access/pdf-extract.ts +3 -0
  252. package/node_modules/pi-web-access/querit.ts +5 -29
  253. package/node_modules/pi-web-access/search-answer-formatting.ts +11 -0
  254. package/node_modules/pi-web-access/search-result-count-normalization.ts +4 -0
  255. package/node_modules/pi-web-access/search1api.ts +5 -29
  256. package/node_modules/pi-web-access/searchinfinity.ts +5 -29
  257. package/node_modules/pi-web-access/searxng.ts +3 -21
  258. package/node_modules/pi-web-access/serpapi.ts +5 -28
  259. package/node_modules/pi-web-access/serpbase.ts +3 -22
  260. package/node_modules/pi-web-access/serpdive.ts +3 -21
  261. package/node_modules/pi-web-access/serper.ts +5 -28
  262. package/node_modules/pi-web-access/serply.ts +197 -0
  263. package/node_modules/pi-web-access/source-check.ts +11 -47
  264. package/node_modules/pi-web-access/summary-review.ts +7 -3
  265. package/node_modules/pi-web-access/tavily.ts +3 -21
  266. package/node_modules/pi-web-access/tinyfish.ts +5 -29
  267. package/node_modules/pi-web-access/utils.ts +9 -1
  268. package/node_modules/pi-web-access/valyu.ts +5 -28
  269. package/node_modules/pi-web-access/xai-search.ts +1 -15
  270. package/node_modules/pi-web-access/xcrawl.ts +5 -32
  271. package/package.json +23 -16
@@ -0,0 +1,323 @@
1
+ /**
2
+ * Turning Pi's OpenAI-shaped message list into the turn structure Cursor wants.
3
+ *
4
+ * This is the module that matters whenever we cannot resume Cursor's own server-side
5
+ * conversation and have to rebuild it from Pi's transcript — which happens on a restored
6
+ * session, on a fresh process, and (since compaction stopped being a no-op) after every
7
+ * compaction. Everything the model will know about the work so far has to survive this
8
+ * function; whatever it drops is gone.
9
+ *
10
+ * It lives apart from proxy.ts because proxy.ts imports generated protobuf code with
11
+ * non-erasable `enum`s, which Node's type stripping refuses to load — so nothing in proxy.ts
12
+ * can be unit tested. This file has no such dependency and is tested directly.
13
+ */
14
+
15
+ export interface ContentPart {
16
+ type: string;
17
+ text?: string;
18
+ }
19
+
20
+ export interface OpenAIToolCall {
21
+ id: string;
22
+ type: "function";
23
+ function: { name: string; arguments: string };
24
+ }
25
+
26
+ export interface OpenAIMessage {
27
+ role: "system" | "user" | "assistant" | "tool";
28
+ content: string | null | ContentPart[];
29
+ tool_call_id?: string;
30
+ tool_calls?: OpenAIToolCall[];
31
+ }
32
+
33
+ export interface ParsedToolResult {
34
+ content: string;
35
+ isError: boolean;
36
+ }
37
+
38
+ export interface ParsedAssistantTextStep {
39
+ kind: "assistantText";
40
+ text: string;
41
+ }
42
+
43
+ export interface ParsedToolCallStep {
44
+ kind: "toolCall";
45
+ toolCallId: string;
46
+ toolName: string;
47
+ arguments: Record<string, unknown>;
48
+ result?: ParsedToolResult;
49
+ }
50
+
51
+ export type ParsedTurnStep = ParsedAssistantTextStep | ParsedToolCallStep;
52
+
53
+ export interface ParsedTurn {
54
+ userText: string;
55
+ steps: ParsedTurnStep[];
56
+ }
57
+
58
+ export interface ToolResultInfo {
59
+ toolCallId: string;
60
+ content: string;
61
+ }
62
+
63
+ export interface ParsedMessages {
64
+ systemPrompt: string;
65
+ userText: string;
66
+ /** Completed turns, ready to be replayed as conversation history. */
67
+ turns: ParsedTurn[];
68
+ toolResults: ToolResultInfo[];
69
+ /**
70
+ * The turn that is still in flight: the user asked something, the assistant called tools,
71
+ * and their results have just come back. On the live path those results are fed straight
72
+ * into the open stream, so this is unused. On the rebuild path it is the only record that
73
+ * the work happened at all — without it the model is handed the original question again
74
+ * with no idea it already ran the tools, or a naked blob of tool output with no question
75
+ * attached to it.
76
+ */
77
+ pendingTurn?: ParsedTurn;
78
+ }
79
+
80
+ export function textContent(content: OpenAIMessage["content"]): string {
81
+ if (content == null) return "";
82
+ if (typeof content === "string") return content;
83
+ return content.filter((p) => p.type === "text" && p.text).map((p) => p.text!).join("\n");
84
+ }
85
+
86
+ export function parseToolCallArguments(raw: string): Record<string, unknown> {
87
+ try {
88
+ const parsed = JSON.parse(raw);
89
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
90
+ return parsed as Record<string, unknown>;
91
+ }
92
+ return { value: parsed };
93
+ } catch {
94
+ return raw ? { __raw: raw } : {};
95
+ }
96
+ }
97
+
98
+ export function isToolCallStep(step: ParsedTurnStep): step is ParsedToolCallStep {
99
+ return step.kind === "toolCall";
100
+ }
101
+
102
+ function stripTurnRuntimeState(turn: ParsedTurn & {
103
+ toolCallById?: Map<string, ParsedToolCallStep>;
104
+ sawToolResult?: boolean;
105
+ sawAssistantAfterToolResult?: boolean;
106
+ }): ParsedTurn {
107
+ return { userText: turn.userText, steps: turn.steps };
108
+ }
109
+
110
+ export function parseMessages(
111
+ messages: OpenAIMessage[],
112
+ debug?: (event: string, data: Record<string, unknown>) => void,
113
+ ): ParsedMessages {
114
+ let systemPrompt = "You are a helpful assistant.";
115
+ const turns: ParsedTurn[] = [];
116
+
117
+ debug?.("parse_messages.start", { messages });
118
+
119
+ const systemParts = messages.filter((m) => m.role === "system").map((m) => textContent(m.content));
120
+ if (systemParts.length > 0) systemPrompt = systemParts.join("\n");
121
+
122
+ const nonSystem = messages.filter((m) => m.role !== "system");
123
+ let currentTurn: (ParsedTurn & {
124
+ toolCallById: Map<string, ParsedToolCallStep>;
125
+ sawToolResult: boolean;
126
+ sawAssistantAfterToolResult: boolean;
127
+ }) | null = null;
128
+
129
+ const finalizeCurrentTurn = () => {
130
+ if (!currentTurn) return;
131
+ turns.push(stripTurnRuntimeState(currentTurn));
132
+ currentTurn = null;
133
+ };
134
+
135
+ for (const msg of nonSystem) {
136
+ if (msg.role === "user") {
137
+ finalizeCurrentTurn();
138
+ currentTurn = {
139
+ userText: textContent(msg.content),
140
+ steps: [],
141
+ toolCallById: new Map(),
142
+ sawToolResult: false,
143
+ sawAssistantAfterToolResult: false,
144
+ };
145
+ continue;
146
+ }
147
+
148
+ if (!currentTurn) continue;
149
+
150
+ if (msg.role === "assistant") {
151
+ const text = textContent(msg.content);
152
+ if (text) {
153
+ if (currentTurn.sawToolResult) currentTurn.sawAssistantAfterToolResult = true;
154
+ currentTurn.steps.push({ kind: "assistantText", text });
155
+ }
156
+
157
+ for (const toolCall of msg.tool_calls ?? []) {
158
+ const step: ParsedToolCallStep = {
159
+ kind: "toolCall",
160
+ toolCallId: toolCall.id,
161
+ toolName: toolCall.function.name,
162
+ arguments: parseToolCallArguments(toolCall.function.arguments),
163
+ };
164
+ currentTurn.steps.push(step);
165
+ currentTurn.toolCallById.set(step.toolCallId, step);
166
+ }
167
+ continue;
168
+ }
169
+
170
+ if (msg.role === "tool") {
171
+ const toolCallId = msg.tool_call_id ?? "";
172
+ const content = textContent(msg.content);
173
+ const existing = toolCallId ? currentTurn.toolCallById.get(toolCallId) : undefined;
174
+ if (existing) {
175
+ existing.result = { content, isError: false };
176
+ } else {
177
+ const step: ParsedToolCallStep = {
178
+ kind: "toolCall",
179
+ toolCallId,
180
+ toolName: "",
181
+ arguments: {},
182
+ result: { content, isError: false },
183
+ };
184
+ currentTurn.steps.push(step);
185
+ if (toolCallId) currentTurn.toolCallById.set(toolCallId, step);
186
+ }
187
+ currentTurn.sawToolResult = true;
188
+ }
189
+ }
190
+
191
+ let userText = "";
192
+ let toolResults: ToolResultInfo[] = [];
193
+ let pendingTurn: ParsedTurn | undefined;
194
+
195
+ if (currentTurn) {
196
+ const toolCallSteps = currentTurn.steps.filter(isToolCallStep);
197
+ const hasAnyToolResults = toolCallSteps.some((step) => step.result);
198
+ const lastStep = currentTurn.steps.at(-1);
199
+ const isToolContinuation = lastStep?.kind === "toolCall";
200
+
201
+ if (currentTurn.steps.length === 0 || isToolContinuation) {
202
+ userText = currentTurn.userText;
203
+ if (hasAnyToolResults) {
204
+ toolResults = toolCallSteps
205
+ .filter((step) => step.result)
206
+ .map((step) => ({ toolCallId: step.toolCallId, content: step.result!.content }));
207
+ }
208
+ // Keep the whole in-flight turn, not just its results. The live path ignores this;
209
+ // the rebuild path cannot reconstruct the session without it.
210
+ if (currentTurn.steps.length > 0) {
211
+ pendingTurn = stripTurnRuntimeState(currentTurn);
212
+ }
213
+ } else {
214
+ turns.push(stripTurnRuntimeState(currentTurn));
215
+ }
216
+ }
217
+
218
+ const parsed: ParsedMessages = { systemPrompt, userText, turns, toolResults, pendingTurn };
219
+ debug?.("parse_messages.end", parsed as unknown as Record<string, unknown>);
220
+ return parsed;
221
+ }
222
+
223
+ /**
224
+ * Rebuilding a conversation Cursor has no memory of.
225
+ *
226
+ * There are three ways to put words in front of the model, and they are not equally reliable:
227
+ *
228
+ * - the request's own user message travels inline in the request, but Cursor stops resuming
229
+ * tool calls when that action grows past roughly 50–60 KiB;
230
+ * - the root system prompt travels through a blob Cursor reliably fetches before the turn;
231
+ * - history turns travel as sha256 blob IDs. The words themselves stay on our side, and
232
+ * Cursor has to come back over the KV channel and ask for each blob. If it does not ask,
233
+ * or asks for something we do not hold, nothing surfaces and nothing complains: a missing
234
+ * blob is answered with an empty result.
235
+ *
236
+ * Replaying the restored session as history therefore put the compaction summary somewhere the
237
+ * model could not read it — present in Pi's chat, invisible to Cursor. Rendering all of it into
238
+ * the action fixed that omission but created a deterministic tool-resume stall on real sessions.
239
+ * The rebuild now keeps the action short and renders prior turns into the fetched system blob.
240
+ */
241
+
242
+ const CONTEXT_OPEN = "=== RESTORED SESSION CONTEXT ===";
243
+ const CONTEXT_CLOSE = "=== END RESTORED SESSION CONTEXT ===";
244
+
245
+ function renderStep(step: ParsedTurnStep): string {
246
+ if (step.kind === "assistantText") return `Assistant: ${step.text}`;
247
+ const args = JSON.stringify(step.arguments ?? {});
248
+ const call = `Assistant called ${step.toolName || "tool"}(${args})`;
249
+ if (!step.result) return `${call}\n -> (no result recorded)`;
250
+ const label = step.result.isError ? "error" : "result";
251
+ return `${call}\n -> ${label}: ${step.result.content}`;
252
+ }
253
+
254
+ /** A faithful plain-text transcript of everything that already happened. */
255
+ export function renderTurnsAsText(turns: ParsedTurn[]): string {
256
+ const blocks: string[] = [];
257
+ for (const turn of turns) {
258
+ const lines: string[] = [];
259
+ if (turn.userText.trim()) lines.push(`User: ${turn.userText}`);
260
+ for (const step of turn.steps) lines.push(renderStep(step));
261
+ if (lines.length > 0) blocks.push(lines.join("\n"));
262
+ }
263
+ return blocks.join("\n\n");
264
+ }
265
+
266
+ /** The turns to replay as Cursor conversation history. Empty on the rebuild path — see above. */
267
+ export function historyForRebuild(_parsed: Pick<ParsedMessages, "turns" | "pendingTurn">): ParsedTurn[] {
268
+ return [];
269
+ }
270
+
271
+ /** Every turn that already happened, in order, including the one still in flight. */
272
+ export function allPriorTurns(parsed: Pick<ParsedMessages, "turns" | "pendingTurn">): ParsedTurn[] {
273
+ return parsed.pendingTurn ? [...parsed.turns, parsed.pendingTurn] : [...parsed.turns];
274
+ }
275
+
276
+ const CONTINUATION_INSTRUCTION =
277
+ "Continue the work above from where it stopped. Do not repeat steps that are already done, "
278
+ + "and do not ask the user to repeat themselves — everything you need is in the context above.";
279
+
280
+ export const CONTINUATION_PROMPT = CONTINUATION_INSTRUCTION;
281
+
282
+ /** Put restored history in Cursor's fetched root prompt, never in its size-sensitive action. */
283
+ export function systemPromptForRebuild(
284
+ systemPrompt: string,
285
+ parsed: Pick<ParsedMessages, "turns" | "pendingTurn">,
286
+ options: { hasCheckpoint: boolean },
287
+ ): string {
288
+ if (options.hasCheckpoint) return systemPrompt;
289
+ const transcript = renderTurnsAsText(allPriorTurns(parsed));
290
+ if (!transcript) return systemPrompt;
291
+ return `${systemPrompt}\n\n${CONTEXT_OPEN}\n${transcript}\n${CONTEXT_CLOSE}`;
292
+ }
293
+
294
+ /**
295
+ * What to send as the request's user message.
296
+ *
297
+ * Rebuild path: restored history is in the system blob, so this stays a normal short question
298
+ * or continuation instruction. Checkpoint path: Cursor already holds the history, but its
299
+ * checkpoint has the assistant's tool calls without their results (the stream died before
300
+ * delivery), so the results travel here instead.
301
+ */
302
+ export function requestActionText(
303
+ parsed: Pick<ParsedMessages, "userText" | "toolResults" | "turns" | "pendingTurn">,
304
+ options: { hasCheckpoint: boolean },
305
+ ): string {
306
+ if (options.hasCheckpoint) {
307
+ if (!parsed.pendingTurn) return parsed.userText;
308
+ const results = parsed.toolResults.map((r) => r.content).join("\n").trim();
309
+ return results ? `${CONTINUATION_INSTRUCTION}\n\n${results}` : CONTINUATION_INSTRUCTION;
310
+ }
311
+
312
+ const prior = allPriorTurns(parsed);
313
+ const transcript = renderTurnsAsText(prior);
314
+ if (!transcript) return parsed.userText;
315
+ return parsed.pendingTurn ? CONTINUATION_INSTRUCTION : parsed.userText;
316
+ }
317
+
318
+ /** @deprecated Use {@link requestActionText}. */
319
+ export function actionTextForRebuild(
320
+ parsed: Pick<ParsedMessages, "userText" | "toolResults" | "turns" | "pendingTurn">,
321
+ ): string {
322
+ return requestActionText(parsed, { hasCheckpoint: false });
323
+ }
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Cursor often omits tokenDetails on tool-call turns. Estimate prompt size from
3
+ * the OpenAI-shaped request so Pi's auto-compact / footer are not stuck at 0.
4
+ */
5
+ export function estimatePromptTokens(messages: unknown, tools?: unknown): number {
6
+ const parts: string[] = [];
7
+ try {
8
+ if (messages !== undefined) parts.push(JSON.stringify(messages));
9
+ } catch {
10
+ /* ignore unserializable messages */
11
+ }
12
+ try {
13
+ if (tools !== undefined) parts.push(JSON.stringify(tools));
14
+ } catch {
15
+ /* ignore unserializable tools */
16
+ }
17
+ return Math.max(1, Math.ceil(parts.join("").length / 4));
18
+ }
19
+
20
+ /**
21
+ * What Pi needs here is "how big was the request I just sent", because that is what it compares
22
+ * against the model's context window. Cursor's `usedTokens` answers a different question: how
23
+ * big is the conversation Cursor is holding on its side. The two agree until Pi compacts — after
24
+ * that Pi's request is small while Cursor's number stays at the old size, and Pi reads its own
25
+ * context as being over the window on every single turn.
26
+ *
27
+ * Real numbers from a session, one reply after a completed compaction:
28
+ * request ≈ 53 000 tokens, Cursor answered 208 632, Pi compacted again three minutes later and
29
+ * lost its summary doing it.
30
+ *
31
+ * So the measured request wins, and Cursor's counter is only a fallback for when we could not
32
+ * measure anything at all.
33
+ */
34
+ export function resolveCursorUsage(state: {
35
+ outputTokens: number;
36
+ totalTokens: number;
37
+ promptTokenEstimate?: number;
38
+ }): { prompt_tokens: number; completion_tokens: number; total_tokens: number } {
39
+ const completion_tokens = Math.max(0, state.outputTokens);
40
+ const measured = Math.max(0, state.promptTokenEstimate ?? 0);
41
+ if (measured > 0) {
42
+ return {
43
+ prompt_tokens: measured,
44
+ completion_tokens,
45
+ total_tokens: measured + completion_tokens,
46
+ };
47
+ }
48
+ if (state.totalTokens > 0) {
49
+ const prompt_tokens = Math.max(0, state.totalTokens - completion_tokens);
50
+ return { prompt_tokens, completion_tokens, total_tokens: state.totalTokens };
51
+ }
52
+ return { prompt_tokens: 0, completion_tokens, total_tokens: completion_tokens };
53
+ }