@astralyn/pi 1.0.5 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (285) hide show
  1. package/CHANGELOG.md +37 -0
  2. package/dist/bundle/chunks/{anthropic-messages-YIOJIMVL.js → anthropic-messages-3K3DWVY6.js} +1 -1
  3. package/dist/bundle/chunks/anthropic.js +1 -1
  4. package/dist/bundle/chunks/{azure-openai-responses-LDW4FGAQ.js → azure-openai-responses-CH74HGP4.js} +1 -1
  5. package/dist/bundle/chunks/bedrock-converse-stream.js +2 -2
  6. package/dist/bundle/chunks/{chunk-UCXU7KLO.js → chunk-3MMB5I7J.js} +1 -1
  7. package/dist/bundle/chunks/{chunk-GIG5IHES.js → chunk-5KJH3GGH.js} +1 -1
  8. package/dist/bundle/chunks/chunk-6VYRLG5F.js +2 -0
  9. package/dist/bundle/chunks/{chunk-HDHRZ7GT.js → chunk-C2ARIVS3.js} +1 -1
  10. package/dist/bundle/chunks/{chunk-IE3Z7ESB.js → chunk-D3XW5X3Z.js} +371 -246
  11. package/dist/bundle/chunks/{chunk-IFELM7MW.js → chunk-EJEOEBSJ.js} +1 -1
  12. package/dist/bundle/chunks/chunk-H35KWBT5.js +11 -0
  13. package/dist/bundle/chunks/chunk-KAC2YPTW.js +2 -0
  14. package/dist/bundle/chunks/chunk-MAC4N3BW.js +2 -0
  15. package/dist/bundle/chunks/chunk-QLU3LESZ.js +2 -0
  16. package/dist/bundle/chunks/{chunk-A6ALEEY5.js → chunk-TEAAYBTS.js} +1 -1
  17. package/dist/bundle/chunks/cloudflare-workers-ai-system-one-XO45ZT3V.js +2 -0
  18. package/dist/bundle/chunks/codemode-worker.js +2 -2
  19. package/dist/bundle/chunks/github-copilot.js +1 -1
  20. package/dist/bundle/chunks/{google-generative-ai-S5A5IBU4.js → google-generative-ai-VVO64ZRQ.js} +1 -1
  21. package/dist/bundle/chunks/{google-vertex-UBRQFDB4.js → google-vertex-LKS5NUHA.js} +1 -1
  22. package/dist/bundle/chunks/image-resize-worker.js +1 -1
  23. package/dist/bundle/chunks/{llama-cpp-classify-R7DQLTQN.js → llama-cpp-classify-H3IZDR5O.js} +2 -2
  24. package/dist/bundle/chunks/{mistral-conversations-A2N5FSOV.js → mistral-conversations-NJUOG4ET.js} +2 -2
  25. package/dist/bundle/chunks/openai-chatgpt.js +1 -1
  26. package/dist/bundle/chunks/{openai-codex-responses-Q6JHCWLE.js → openai-codex-responses-TZWBTTYM.js} +2 -2
  27. package/dist/bundle/chunks/openai-codex.js +1 -1
  28. package/dist/bundle/chunks/{openai-completions-H4VMO7AU.js → openai-completions-7HTTU6FH.js} +1 -1
  29. package/dist/bundle/chunks/openai-decisions-776H4VDZ.js +5 -0
  30. package/dist/bundle/chunks/{openai-responses-G4PGNM2H.js → openai-responses-7A6BAQ5X.js} +1 -1
  31. package/dist/bundle/chunks/{openrouter-images-YP3YVB54.js → openrouter-images-TDLJV5N4.js} +1 -1
  32. package/dist/bundle/chunks/{pi-messages-HZZ7GEEW.js → pi-messages-ZXNERWJG.js} +1 -1
  33. package/dist/bundle/chunks/typesafe-system-one-2DQLG4R2.js +2 -0
  34. package/dist/bundle/chunks/{virtual-modules-46MSJGKO.js → virtual-modules-2NHGQRS6.js} +1 -1
  35. package/dist/bundle/cli.js +1 -1
  36. package/dist/bundle/index.js +1 -1
  37. package/dist/bundle/rpc-entry.js +1 -1
  38. package/dist/cli/args.d.ts +1 -1
  39. package/dist/cli/args.d.ts.map +1 -1
  40. package/dist/cli/args.js +13 -1
  41. package/dist/cli/args.js.map +1 -1
  42. package/dist/core/agent-session.d.ts +12 -6
  43. package/dist/core/agent-session.d.ts.map +1 -1
  44. package/dist/core/agent-session.js +146 -174
  45. package/dist/core/agent-session.js.map +1 -1
  46. package/dist/core/bash-executor.d.ts.map +1 -1
  47. package/dist/core/bash-executor.js +40 -45
  48. package/dist/core/bash-executor.js.map +1 -1
  49. package/dist/core/context-snapshot.d.ts +4 -1
  50. package/dist/core/context-snapshot.d.ts.map +1 -1
  51. package/dist/core/context-snapshot.js +45 -0
  52. package/dist/core/context-snapshot.js.map +1 -1
  53. package/dist/core/export-html/tool-renderer.d.ts.map +1 -1
  54. package/dist/core/export-html/tool-renderer.js +2 -0
  55. package/dist/core/export-html/tool-renderer.js.map +1 -1
  56. package/dist/core/extensions/types.d.ts +11 -0
  57. package/dist/core/extensions/types.d.ts.map +1 -1
  58. package/dist/core/extensions/types.js.map +1 -1
  59. package/dist/core/model-runtime.d.ts.map +1 -1
  60. package/dist/core/model-runtime.js +2 -1
  61. package/dist/core/model-runtime.js.map +1 -1
  62. package/dist/core/nested-tool-calls.d.ts +1 -0
  63. package/dist/core/nested-tool-calls.d.ts.map +1 -1
  64. package/dist/core/nested-tool-calls.js +1 -0
  65. package/dist/core/nested-tool-calls.js.map +1 -1
  66. package/dist/core/sdk.d.ts +4 -0
  67. package/dist/core/sdk.d.ts.map +1 -1
  68. package/dist/core/sdk.js +17 -5
  69. package/dist/core/sdk.js.map +1 -1
  70. package/dist/core/settings-manager.d.ts +13 -0
  71. package/dist/core/settings-manager.d.ts.map +1 -1
  72. package/dist/core/settings-manager.js +37 -13
  73. package/dist/core/settings-manager.js.map +1 -1
  74. package/dist/core/tasks/session.d.ts +9 -0
  75. package/dist/core/tasks/session.d.ts.map +1 -1
  76. package/dist/core/tasks/session.js +21 -0
  77. package/dist/core/tasks/session.js.map +1 -1
  78. package/dist/core/tools/renderers/bash.d.ts.map +1 -1
  79. package/dist/core/tools/renderers/bash.js +8 -3
  80. package/dist/core/tools/renderers/bash.js.map +1 -1
  81. package/dist/core/tools/renderers/edit.js +1 -1
  82. package/dist/core/tools/renderers/edit.js.map +1 -1
  83. package/dist/extensions/codemode/execute.d.ts.map +1 -1
  84. package/dist/extensions/codemode/execute.js +63 -7
  85. package/dist/extensions/codemode/execute.js.map +1 -1
  86. package/dist/extensions/codemode/tool.js +2 -2
  87. package/dist/extensions/codemode/tool.js.map +1 -1
  88. package/dist/extensions/index.js +2 -2
  89. package/dist/extensions/index.js.map +1 -1
  90. package/dist/extensions/llama/provider.d.ts.map +1 -1
  91. package/dist/extensions/llama/provider.js +34 -11
  92. package/dist/extensions/llama/provider.js.map +1 -1
  93. package/dist/extensions/mcp/cli.js +17 -23
  94. package/dist/extensions/mcp/cli.js.map +1 -1
  95. package/dist/extensions/mcp/index.d.ts.map +1 -1
  96. package/dist/extensions/mcp/index.js +165 -64
  97. package/dist/extensions/mcp/index.js.map +1 -1
  98. package/dist/extensions/mcp/oauth.d.ts +5 -3
  99. package/dist/extensions/mcp/oauth.d.ts.map +1 -1
  100. package/dist/extensions/mcp/oauth.js +36 -10
  101. package/dist/extensions/mcp/oauth.js.map +1 -1
  102. package/dist/extensions/mcp/ui.d.ts +3 -3
  103. package/dist/extensions/mcp/ui.d.ts.map +1 -1
  104. package/dist/extensions/mcp/ui.js +10 -2
  105. package/dist/extensions/mcp/ui.js.map +1 -1
  106. package/dist/extensions/search/code-search.d.ts +29 -0
  107. package/dist/extensions/search/code-search.d.ts.map +1 -0
  108. package/dist/extensions/search/code-search.js +120 -0
  109. package/dist/extensions/search/code-search.js.map +1 -0
  110. package/dist/extensions/search/command.d.ts +5 -0
  111. package/dist/extensions/search/command.d.ts.map +1 -0
  112. package/dist/extensions/search/command.js +69 -0
  113. package/dist/extensions/search/command.js.map +1 -0
  114. package/dist/extensions/search/credential.d.ts +25 -0
  115. package/dist/extensions/search/credential.d.ts.map +1 -0
  116. package/dist/extensions/search/credential.js +117 -0
  117. package/dist/extensions/search/credential.js.map +1 -0
  118. package/dist/extensions/search/devin.d.ts +34 -0
  119. package/dist/extensions/search/devin.d.ts.map +1 -0
  120. package/dist/extensions/search/devin.js +210 -0
  121. package/dist/extensions/search/devin.js.map +1 -0
  122. package/dist/extensions/search/index.d.ts +5 -0
  123. package/dist/extensions/search/index.d.ts.map +1 -0
  124. package/dist/extensions/search/index.js +110 -0
  125. package/dist/extensions/search/index.js.map +1 -0
  126. package/dist/extensions/search/panel.d.ts +53 -0
  127. package/dist/extensions/search/panel.d.ts.map +1 -0
  128. package/dist/extensions/search/panel.js +157 -0
  129. package/dist/extensions/search/panel.js.map +1 -0
  130. package/dist/extensions/search/prompt.d.ts +14 -0
  131. package/dist/extensions/search/prompt.d.ts.map +1 -0
  132. package/dist/extensions/search/prompt.js +221 -0
  133. package/dist/extensions/search/prompt.js.map +1 -0
  134. package/dist/extensions/search/render.d.ts +28 -0
  135. package/dist/extensions/search/render.d.ts.map +1 -0
  136. package/dist/extensions/search/render.js +86 -0
  137. package/dist/extensions/search/render.js.map +1 -0
  138. package/dist/extensions/search/workspace.d.ts +38 -0
  139. package/dist/extensions/search/workspace.d.ts.map +1 -0
  140. package/dist/extensions/search/workspace.js +245 -0
  141. package/dist/extensions/search/workspace.js.map +1 -0
  142. package/dist/modes/interactive/components/bash-execution.d.ts +5 -1
  143. package/dist/modes/interactive/components/bash-execution.d.ts.map +1 -1
  144. package/dist/modes/interactive/components/bash-execution.js +14 -14
  145. package/dist/modes/interactive/components/bash-execution.js.map +1 -1
  146. package/dist/modes/interactive/components/branch-summary-message.d.ts +2 -1
  147. package/dist/modes/interactive/components/branch-summary-message.d.ts.map +1 -1
  148. package/dist/modes/interactive/components/branch-summary-message.js +5 -2
  149. package/dist/modes/interactive/components/branch-summary-message.js.map +1 -1
  150. package/dist/modes/interactive/components/compaction-summary-message.d.ts +2 -1
  151. package/dist/modes/interactive/components/compaction-summary-message.d.ts.map +1 -1
  152. package/dist/modes/interactive/components/compaction-summary-message.js +5 -2
  153. package/dist/modes/interactive/components/compaction-summary-message.js.map +1 -1
  154. package/dist/modes/interactive/components/custom-entry.d.ts +3 -1
  155. package/dist/modes/interactive/components/custom-entry.d.ts.map +1 -1
  156. package/dist/modes/interactive/components/custom-entry.js +7 -2
  157. package/dist/modes/interactive/components/custom-entry.js.map +1 -1
  158. package/dist/modes/interactive/components/custom-message.d.ts.map +1 -1
  159. package/dist/modes/interactive/components/custom-message.js +1 -0
  160. package/dist/modes/interactive/components/custom-message.js.map +1 -1
  161. package/dist/modes/interactive/components/settings-selector.js +1 -1
  162. package/dist/modes/interactive/components/settings-selector.js.map +1 -1
  163. package/dist/modes/interactive/components/skill-invocation-message.d.ts +2 -1
  164. package/dist/modes/interactive/components/skill-invocation-message.d.ts.map +1 -1
  165. package/dist/modes/interactive/components/skill-invocation-message.js +5 -2
  166. package/dist/modes/interactive/components/skill-invocation-message.js.map +1 -1
  167. package/dist/modes/interactive/interactive-mode.d.ts +2 -0
  168. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  169. package/dist/modes/interactive/interactive-mode.js +52 -59
  170. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  171. package/dist/modes/interactive/program-status-reporter.d.ts +34 -0
  172. package/dist/modes/interactive/program-status-reporter.d.ts.map +1 -0
  173. package/dist/modes/interactive/program-status-reporter.js +107 -0
  174. package/dist/modes/interactive/program-status-reporter.js.map +1 -0
  175. package/dist/modes/interactive/tasks/host-hooks.d.ts +14 -0
  176. package/dist/modes/interactive/tasks/host-hooks.d.ts.map +1 -0
  177. package/dist/modes/interactive/tasks/host-hooks.js +31 -0
  178. package/dist/modes/interactive/tasks/host-hooks.js.map +1 -0
  179. package/dist/modes/interactive/tool-view/tool-execution.d.ts +5 -0
  180. package/dist/modes/interactive/tool-view/tool-execution.d.ts.map +1 -1
  181. package/dist/modes/interactive/tool-view/tool-execution.js +7 -0
  182. package/dist/modes/interactive/tool-view/tool-execution.js.map +1 -1
  183. package/dist/package-manager-cli.d.ts.map +1 -1
  184. package/dist/package-manager-cli.js +24 -0
  185. package/dist/package-manager-cli.js.map +1 -1
  186. package/dist/utils/ansi.d.ts +8 -0
  187. package/dist/utils/ansi.d.ts.map +1 -1
  188. package/dist/utils/ansi.js +30 -11
  189. package/dist/utils/ansi.js.map +1 -1
  190. package/dist/utils/clipboard.d.ts.map +1 -1
  191. package/dist/utils/clipboard.js +12 -11
  192. package/dist/utils/clipboard.js.map +1 -1
  193. package/dist/utils/image-resize-core.d.ts +12 -0
  194. package/dist/utils/image-resize-core.d.ts.map +1 -1
  195. package/dist/utils/image-resize-core.js +5 -0
  196. package/dist/utils/image-resize-core.js.map +1 -1
  197. package/dist/utils/image-resize-worker.js +3 -2
  198. package/dist/utils/image-resize-worker.js.map +1 -1
  199. package/dist/utils/image-resize.d.ts.map +1 -1
  200. package/dist/utils/image-resize.js +9 -10
  201. package/dist/utils/image-resize.js.map +1 -1
  202. package/docs/bundled/README.md +1 -1
  203. package/docs/bundled/extensions/provider.md +1 -1
  204. package/docs/bundled/extensions/question.md +1 -1
  205. package/docs/bundled/extensions/search.md +50 -0
  206. package/docs/bundled/extensions/statusline.md +15 -41
  207. package/docs/codemode.md +23 -3
  208. package/docs/docs.json +0 -4
  209. package/docs/environment-variables.md +1 -0
  210. package/docs/extensions.md +5 -1
  211. package/docs/json.md +2 -2
  212. package/docs/llama-cpp.md +8 -1
  213. package/docs/models.md +4 -1
  214. package/docs/providers.md +17 -0
  215. package/docs/rpc.md +6 -3
  216. package/docs/session-format.md +1 -0
  217. package/docs/settings.md +3 -3
  218. package/docs/terminal-setup.md +14 -0
  219. package/docs/usage.md +5 -3
  220. package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
  221. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  222. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  223. package/examples/extensions/gondolin/package-lock.json +2 -2
  224. package/examples/extensions/gondolin/package.json +1 -1
  225. package/examples/extensions/sandbox/package-lock.json +2 -2
  226. package/examples/extensions/sandbox/package.json +1 -1
  227. package/examples/extensions/with-deps/package-lock.json +2 -2
  228. package/examples/extensions/with-deps/package.json +1 -1
  229. package/npm-shrinkwrap.json +54 -54
  230. package/package.json +13 -11
  231. package/dist/bundle/chunks/chunk-57XI7YJH.js +0 -2
  232. package/dist/bundle/chunks/chunk-JI4WGISU.js +0 -2
  233. package/dist/bundle/chunks/chunk-TCQ4HHQD.js +0 -2
  234. package/dist/bundle/chunks/chunk-TXJO4AJ7.js +0 -11
  235. package/dist/bundle/chunks/cloudflare-workers-ai-system-one-OWU5SM6J.js +0 -2
  236. package/dist/bundle/chunks/typesafe-system-one-KYKKNU3E.js +0 -2
  237. package/dist/extensions/web-search/auth.d.ts +0 -12
  238. package/dist/extensions/web-search/auth.d.ts.map +0 -1
  239. package/dist/extensions/web-search/auth.js +0 -90
  240. package/dist/extensions/web-search/auth.js.map +0 -1
  241. package/dist/extensions/web-search/constants.d.ts +0 -13
  242. package/dist/extensions/web-search/constants.d.ts.map +0 -1
  243. package/dist/extensions/web-search/constants.js +0 -26
  244. package/dist/extensions/web-search/constants.js.map +0 -1
  245. package/dist/extensions/web-search/execute.d.ts +0 -9
  246. package/dist/extensions/web-search/execute.d.ts.map +0 -1
  247. package/dist/extensions/web-search/execute.js +0 -96
  248. package/dist/extensions/web-search/execute.js.map +0 -1
  249. package/dist/extensions/web-search/format.d.ts +0 -21
  250. package/dist/extensions/web-search/format.d.ts.map +0 -1
  251. package/dist/extensions/web-search/format.js +0 -117
  252. package/dist/extensions/web-search/format.js.map +0 -1
  253. package/dist/extensions/web-search/index.d.ts +0 -6
  254. package/dist/extensions/web-search/index.d.ts.map +0 -1
  255. package/dist/extensions/web-search/index.js +0 -50
  256. package/dist/extensions/web-search/index.js.map +0 -1
  257. package/dist/extensions/web-search/providers/deepseek.d.ts +0 -13
  258. package/dist/extensions/web-search/providers/deepseek.d.ts.map +0 -1
  259. package/dist/extensions/web-search/providers/deepseek.js +0 -110
  260. package/dist/extensions/web-search/providers/deepseek.js.map +0 -1
  261. package/dist/extensions/web-search/providers/http.d.ts +0 -4
  262. package/dist/extensions/web-search/providers/http.d.ts.map +0 -1
  263. package/dist/extensions/web-search/providers/http.js +0 -42
  264. package/dist/extensions/web-search/providers/http.js.map +0 -1
  265. package/dist/extensions/web-search/providers/minimax.d.ts +0 -12
  266. package/dist/extensions/web-search/providers/minimax.d.ts.map +0 -1
  267. package/dist/extensions/web-search/providers/minimax.js +0 -61
  268. package/dist/extensions/web-search/providers/minimax.js.map +0 -1
  269. package/dist/extensions/web-search/render.d.ts +0 -15
  270. package/dist/extensions/web-search/render.d.ts.map +0 -1
  271. package/dist/extensions/web-search/render.js +0 -142
  272. package/dist/extensions/web-search/render.js.map +0 -1
  273. package/dist/extensions/web-search/results.d.ts +0 -35
  274. package/dist/extensions/web-search/results.d.ts.map +0 -1
  275. package/dist/extensions/web-search/results.js +0 -167
  276. package/dist/extensions/web-search/results.js.map +0 -1
  277. package/dist/extensions/web-search/schema.d.ts +0 -10
  278. package/dist/extensions/web-search/schema.d.ts.map +0 -1
  279. package/dist/extensions/web-search/schema.js +0 -36
  280. package/dist/extensions/web-search/schema.js.map +0 -1
  281. package/dist/extensions/web-search/types.d.ts +0 -51
  282. package/dist/extensions/web-search/types.d.ts.map +0 -1
  283. package/dist/extensions/web-search/types.js +0 -5
  284. package/dist/extensions/web-search/types.js.map +0 -1
  285. package/docs/bundled/extensions/web-search.md +0 -46
@@ -26,8 +26,11 @@ const CLIENT_METADATA_BASE_URL = "https://pi.dev/oauth";
26
26
  const FALLBACK_REDIRECT_URL = `http://${CALLBACK_HOST}${CALLBACK_PATH}`;
27
27
  /** Access tokens this close to expiry are refreshed before they are sent. */
28
28
  const REFRESH_SKEW_MS = 30_000;
29
- /** Bounds each request of a refresh, so it cannot hold the refresh lock or delay shutdown for long. */
30
- const REFRESH_REQUEST_TIMEOUT_MS = 15_000;
29
+ /**
30
+ * Bounds each request to the authorization server, so an unresponsive one cannot hold the refresh lock,
31
+ * delay shutdown, or keep a sign-in waiting for long.
32
+ */
33
+ const OAUTH_REQUEST_TIMEOUT_MS = 15_000;
31
34
  /** A refresh lock that its holder stopped renewing (the process was killed) is taken over after this. */
32
35
  const REFRESH_LOCK_STALE_MS = 20_000;
33
36
  /** How long to wait for another process's refresh: longer than a stale lock lives. */
@@ -154,6 +157,13 @@ export class McpOAuthCredentialStore {
154
157
  });
155
158
  }
156
159
  }
160
+ /** `fetch` with `OAUTH_REQUEST_TIMEOUT_MS` per request, on top of the request's own signal. */
161
+ function timedFetch(fetch = globalThis.fetch) {
162
+ return (input, init) => {
163
+ const timeout = AbortSignal.timeout(OAUTH_REQUEST_TIMEOUT_MS);
164
+ return fetch(input, { ...init, signal: init?.signal ? AbortSignal.any([init.signal, timeout]) : timeout });
165
+ };
166
+ }
157
167
  function registeredRedirectUrls(client) {
158
168
  return client && "redirect_uris" in client ? client.redirect_uris : [];
159
169
  }
@@ -226,13 +236,14 @@ export function createMcpAuthProvider(options) {
226
236
  registeredRedirectUrls(state.clientInformation)[0] ??
227
237
  FALLBACK_REDIRECT_URL;
228
238
  const provider = createProvider(serverUrl, store, settings, redirectUrl, () => { });
229
- // Refreshes the tokens, or reports that a new sign-in is needed.
239
+ // Refreshes the tokens, or reports that a new sign-in is needed. Not cancellable: a refresh the
240
+ // server answered may have rotated the refresh token, so its answer must be saved.
230
241
  const result = await authorizeMcp(provider, {
231
242
  serverUrl,
232
243
  resourceMetadataUrl: challenge?.resourceMetadataUrl,
233
244
  authorizationServerMetadataUrl: settings.authServerMetadataUrl,
234
245
  scope: challenge?.scope,
235
- fetch: (input, init) => fetch(input, { ...init, signal: AbortSignal.timeout(REFRESH_REQUEST_TIMEOUT_MS) }),
246
+ fetch: timedFetch(fetch),
236
247
  });
237
248
  if (result === "REDIRECT")
238
249
  throw new McpOAuthAuthorizationRequiredError();
@@ -295,11 +306,15 @@ function responseFromRedirectUrl(input, state, redirectUrl) {
295
306
  throw new Error("The redirect URL does not contain an authorization code");
296
307
  return { code, iss: url.searchParams.get("iss") ?? undefined };
297
308
  }
298
- /** Wait for the browser callback or a pasted redirect URL, whichever comes first. */
299
- async function waitForAuthorizationResponse(callback, state, redirectUrl, prompt) {
309
+ /**
310
+ * Wait for the browser callback or a pasted redirect URL, whichever comes first. Aborting `signal`
311
+ * aborts the prompt, which then cancels the sign-in.
312
+ */
313
+ async function waitForAuthorizationResponse(callback, state, redirectUrl, prompt, signal) {
300
314
  const controller = new AbortController();
315
+ const promptSignal = signal ? AbortSignal.any([controller.signal, signal]) : controller.signal;
301
316
  const fromBrowser = callback.waitForCallback(state, redirectUrl.pathname);
302
- const fromUser = prompt.promptForRedirectUrl(controller.signal).then((input) => {
317
+ const fromUser = prompt.promptForRedirectUrl(promptSignal).then((input) => {
303
318
  if (!input?.trim())
304
319
  throw new McpSignInCancelledError();
305
320
  return responseFromRedirectUrl(input, state, redirectUrl);
@@ -336,10 +351,13 @@ async function listenForCallback(settings, extraPaths, port, required) {
336
351
  }
337
352
  /**
338
353
  * Sign in to an MCP server. Uses the stored refresh token when possible; otherwise runs the browser
339
- * authorization code flow. Tokens are saved to `store`.
354
+ * authorization code flow. Tokens are saved to `store`. Aborting `signal` stops the sign-in at any
355
+ * step with `McpSignInCancelledError`; requests to the authorization server are also time-limited.
340
356
  */
341
357
  export async function signInMcpServer(options) {
342
- const { serverUrl, store, settings } = options;
358
+ const { serverUrl, store, settings, signal } = options;
359
+ if (signal?.aborted)
360
+ throw new McpSignInCancelledError();
343
361
  const stored = await store.load();
344
362
  const stepUp = options.challenge?.error === "insufficient_scope";
345
363
  const callbackOptions = callbackSettings(settings);
@@ -373,6 +391,8 @@ export async function signInMcpServer(options) {
373
391
  });
374
392
  const flow = {
375
393
  serverUrl,
394
+ fetch: timedFetch(),
395
+ signal,
376
396
  resourceMetadataUrl: options.challenge?.resourceMetadataUrl,
377
397
  authorizationServerMetadataUrl: settings.authServerMetadataUrl,
378
398
  // A server asking for more scope gets it on top of the configured scope and, since the challenge
@@ -389,9 +409,15 @@ export async function signInMcpServer(options) {
389
409
  // The flow picks the redirect URI, which may be specific to the MCP server.
390
410
  const authorizationRedirectUrl = new URL(authorizationUrl.searchParams.get("redirect_uri") ?? redirectUrl);
391
411
  options.prompt.showAuthorizationUrl(authorizationUrl);
392
- const { code, iss } = await waitForAuthorizationResponse(callback, state, authorizationRedirectUrl, options.prompt);
412
+ const { code, iss } = await waitForAuthorizationResponse(callback, state, authorizationRedirectUrl, options.prompt, signal);
393
413
  await authorizeMcp(provider, { ...flow, authorizationCode: code, iss });
394
414
  }
415
+ catch (error) {
416
+ // Aborted requests fail with the signal's reason; report them as the cancellation they are.
417
+ if (signal?.aborted)
418
+ throw new McpSignInCancelledError();
419
+ throw error;
420
+ }
395
421
  finally {
396
422
  await callback.close();
397
423
  }
@@ -1 +1 @@
1
- {"version":3,"file":"oauth.js","sourceRoot":"","sources":["../../../src/extensions/mcp/oauth.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * OAuth sign-in for remote MCP servers.\n *\n * Connections never start a browser flow on their own. They send the stored access token and, after\n * a 401, try the stored refresh token. When that is not possible they fail with\n * `McpOAuthAuthorizationRequiredError`, and the user signs in through `/mcp`, which runs\n * the authorization code flow (PKCE, a Client ID Metadata Document or dynamic client registration)\n * against a loopback callback.\n *\n * Credentials live in `<agent-dir>/mcp-auth.json`, keyed by server name and URL.\n */\n\nimport { createHash } from \"node:crypto\";\nimport { mkdirSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { oauthErrorHtml, oauthSuccessHtml } from \"@earendil-works/pi-ai/utils/oauth-page\";\nimport type { AuthProvider, McpFetch } from \"@earendil-works/pi-mcp\";\nimport {\n\ttype AuthorizationServerMetadata,\n\tauthorizeMcp,\n\tMcpOAuthAuthorizationRequiredError,\n\tMcpOAuthProvider,\n\ttype McpOAuthState,\n\ttype McpOAuthStateStore,\n\ttype OAuthCallback,\n\ttype OAuthCallbackPage,\n\tOAuthCallbackServer,\n\ttype OAuthChallenge,\n\ttype OAuthClientInformationMixed,\n\ttype OAuthClientMetadataDocument,\n\tparseWwwAuthenticate,\n\tstepUpScope,\n} from \"@earendil-works/pi-mcp/oauth\";\nimport lockfile from \"proper-lockfile\";\nimport { APP_NAME, getAgentDir } from \"../../config.ts\";\nimport { type AuthStorageBackend, FileAuthStorageBackend } from \"../../core/auth-storage.ts\";\nimport { mcpNamespace } from \"../../core/mcp-servers.ts\";\n\nconst CALLBACK_HOST = \"127.0.0.1\";\nconst CALLBACK_PATH = \"/callback\";\n/** Where pi.dev serves pi's Client ID Metadata Documents: `client.json` and `<callback ID>/client.json`. */\nconst CLIENT_METADATA_BASE_URL = \"https://pi.dev/oauth\";\n/** Redirect URI for refreshes when none is stored. Refreshing never redirects the user. */\nconst FALLBACK_REDIRECT_URL = `http://${CALLBACK_HOST}${CALLBACK_PATH}`;\n/** Access tokens this close to expiry are refreshed before they are sent. */\nconst REFRESH_SKEW_MS = 30_000;\n/** Bounds each request of a refresh, so it cannot hold the refresh lock or delay shutdown for long. */\nconst REFRESH_REQUEST_TIMEOUT_MS = 15_000;\n/** A refresh lock that its holder stopped renewing (the process was killed) is taken over after this. */\nconst REFRESH_LOCK_STALE_MS = 20_000;\n/** How long to wait for another process's refresh: longer than a stale lock lives. */\nconst REFRESH_LOCK_WAIT_MS = 25_000;\nconst REFRESH_LOCK_RETRY_MS = 100;\n\nexport interface McpOAuthSettings {\n\tclientId?: string;\n\t/** Already resolved. */\n\tclientSecret?: string;\n\tcallbackPort?: number;\n\t/** Loopback redirect URI; see `McpOAuthConfig.callbackUrl`. */\n\tcallbackUrl?: string;\n\t/** Scopes to request, separated by spaces. */\n\tscope?: string;\n\t/** `client_name` for dynamic client registration. Default: `APP_NAME`. */\n\tclientName?: string;\n\t/** See `McpOAuthConfig.clientRegistration`. */\n\tclientRegistration?: \"dcr\" | \"cimd\";\n\t/** See `McpOAuthConfig.authServerMetadataUrl`. */\n\tauthServerMetadataUrl?: URL;\n}\n\n/** Where the loopback callback server listens and the redirect URI it serves. */\ninterface CallbackSettings {\n\t/** Address to listen on. */\n\thost: string;\n\t/** Host name in the redirect URI. */\n\tredirectHost: string;\n\tport: number | undefined;\n\tpath: string;\n\t/** The exact redirect URI, when the port is fixed. */\n\tfixedRedirectUrl: string | undefined;\n}\n\nfunction callbackSettings(settings: McpOAuthSettings): CallbackSettings {\n\tconst url = new URL(settings.callbackUrl ?? `http://${CALLBACK_HOST}${CALLBACK_PATH}`);\n\tconst address = url.hostname.replace(/^\\[|\\]$/g, \"\");\n\tconst port = url.port ? Number(url.port) : settings.callbackPort;\n\tlet fixedRedirectUrl: string | undefined;\n\t// A configured URI with a port is sent exactly as written, since servers compare it as a string.\n\tif (url.port) fixedRedirectUrl = settings.callbackUrl;\n\telse if (port !== undefined) {\n\t\turl.port = String(port);\n\t\tfixedRedirectUrl = url.href;\n\t}\n\treturn {\n\t\t// `localhost` is served on 127.0.0.1; browsers fall back to it when ::1 refuses.\n\t\thost: address === \"localhost\" ? CALLBACK_HOST : address,\n\t\tredirectHost: address,\n\t\tport,\n\t\tpath: url.pathname,\n\t\tfixedRedirectUrl,\n\t};\n}\n\n/** Scopes of both lists, each once. */\nfunction mergeScopes(...scopes: (string | undefined)[]): string | undefined {\n\tconst merged = [...new Set(scopes.flatMap((scope) => scope?.split(/\\s+/).filter(Boolean) ?? []))];\n\treturn merged.length > 0 ? merged.join(\" \") : undefined;\n}\n\ntype StoredStates = Record<string, McpOAuthState>;\n\nfunction parseStates(content: string | undefined): StoredStates {\n\tif (!content?.trim()) return {};\n\tconst parsed: unknown = JSON.parse(content);\n\treturn typeof parsed === \"object\" && parsed !== null && !Array.isArray(parsed) ? (parsed as StoredStates) : {};\n}\n\nfunction serializeStates(states: StoredStates): string {\n\treturn `${JSON.stringify(states, null, 2)}\\n`;\n}\n\n/**\n * Keys of a server's state: by name and URL, so servers sharing a URL keep separate accounts, and the\n * legacy key by URL alone, written by older versions.\n */\nfunction storeKeys(name: string, serverUrl: string): { key: string; legacyKey: string } {\n\tconst legacyKey = String(new URL(serverUrl));\n\treturn { key: `${mcpNamespace(name)}|${legacyKey}`, legacyKey };\n}\n\nexport interface McpOAuthServerStore extends McpOAuthStateStore {\n\t/** Run `fn` while no other process refreshes the server's tokens. */\n\twithRefreshLock<T>(fn: () => Promise<T>): Promise<T>;\n}\n\n/** Per-server OAuth state (client registration, tokens, pending PKCE verifier) in `mcp-auth.json`. */\nexport class McpOAuthCredentialStore {\n\tprivate readonly backend: AuthStorageBackend;\n\t/** Directory for the refresh lock files. Without one, refreshes are only serialized in this process. */\n\tprivate readonly lockDir: string | undefined;\n\n\tconstructor(backend?: AuthStorageBackend, lockDir?: string) {\n\t\tthis.backend = backend ?? new FileAuthStorageBackend(join(getAgentDir(), \"mcp-auth.json\"));\n\t\tthis.lockDir = backend ? lockDir : getAgentDir();\n\t}\n\n\tforServer(name: string, serverUrl: string): McpOAuthServerStore {\n\t\tconst { key, legacyKey } = storeKeys(name, serverUrl);\n\t\treturn {\n\t\t\t// The first server to load legacy state takes it over; others with the same URL sign in again.\n\t\t\tload: () =>\n\t\t\t\tthis.backend.withLock((current) => {\n\t\t\t\t\tconst states = parseStates(current);\n\t\t\t\t\tif (states[key] || !states[legacyKey]) return { result: states[key] };\n\t\t\t\t\tstates[key] = states[legacyKey];\n\t\t\t\t\tdelete states[legacyKey];\n\t\t\t\t\treturn { result: states[key], next: serializeStates(states) };\n\t\t\t\t}),\n\t\t\tsave: (state) =>\n\t\t\t\tthis.write((states) => {\n\t\t\t\t\tstates[key] = state;\n\t\t\t\t}),\n\t\t\twithRefreshLock: (fn) => this.withRefreshLock(key, fn),\n\t\t};\n\t}\n\n\t/**\n\t * A lock file per server. When the process exits, proper-lockfile removes the locks it holds; when\n\t * it is killed, the lock goes stale because it is no longer renewed, and the next process takes it over.\n\t */\n\tprivate async withRefreshLock<T>(key: string, fn: () => Promise<T>): Promise<T> {\n\t\tif (!this.lockDir) return fn();\n\t\tmkdirSync(this.lockDir, { recursive: true, mode: 0o700 });\n\t\tconst hash = createHash(\"sha256\").update(key).digest(\"hex\").slice(0, 16);\n\t\tconst release = await lockfile.lock(join(this.lockDir, `mcp-auth-refresh-${hash}`), {\n\t\t\trealpath: false,\n\t\t\tstale: REFRESH_LOCK_STALE_MS,\n\t\t\tretries: {\n\t\t\t\tretries: REFRESH_LOCK_WAIT_MS / REFRESH_LOCK_RETRY_MS,\n\t\t\t\tfactor: 1,\n\t\t\t\tminTimeout: REFRESH_LOCK_RETRY_MS,\n\t\t\t\tmaxTimeout: REFRESH_LOCK_RETRY_MS,\n\t\t\t},\n\t\t\t// The default throws from a timer. A lost lock at worst lets two refreshes overlap.\n\t\t\tonCompromised: () => {},\n\t\t});\n\t\ttry {\n\t\t\treturn await fn();\n\t\t} finally {\n\t\t\tawait release().catch(() => undefined);\n\t\t}\n\t}\n\n\t/** The stored tokens of a server, for noticing sign-ins done by another process. Does not take over legacy state. */\n\ttokens(name: string, serverUrl: string): McpOAuthState[\"tokens\"] {\n\t\tconst { key, legacyKey } = storeKeys(name, serverUrl);\n\t\tconst states = this.backend.withLock((current) => ({ result: parseStates(current) }));\n\t\treturn (states[key] ?? states[legacyKey])?.tokens;\n\t}\n\n\t/** Returns whether credentials were stored for the server. Removes legacy state the server would take over. */\n\tremove(name: string, serverUrl: string): boolean {\n\t\tconst { key, legacyKey } = storeKeys(name, serverUrl);\n\t\treturn this.backend.withLock((current) => {\n\t\t\tconst states = parseStates(current);\n\t\t\tconst stored = key in states ? key : legacyKey in states ? legacyKey : undefined;\n\t\t\tif (!stored) return { result: false };\n\t\t\tdelete states[stored];\n\t\t\treturn { result: true, next: serializeStates(states) };\n\t\t});\n\t}\n\n\tprivate write(update: (states: StoredStates) => void): void {\n\t\tthis.backend.withLock((current) => {\n\t\t\tconst states = parseStates(current);\n\t\t\tupdate(states);\n\t\t\treturn { result: undefined, next: serializeStates(states) };\n\t\t});\n\t}\n}\n\nfunction registeredRedirectUrls(client: OAuthClientInformationMixed | undefined): string[] {\n\treturn client && \"redirect_uris\" in client ? client.redirect_uris : [];\n}\n\n/** 12 characters identifying an MCP server URL in callback paths, computed like Codex does. */\nfunction callbackId(serverUrl: string): string {\n\tconst url = new URL(serverUrl);\n\turl.hash = \"\";\n\treturn createHash(\"sha256\").update(url.href).digest().subarray(0, 9).toString(\"base64url\");\n}\n\n/**\n * pi's Client ID Metadata Document, for `clientRegistration: \"cimd\"`, chosen like Codex chooses its own.\n * The configuration ensures the default callback path. Without the `iss` parameter in authorization\n * responses (RFC 9207), the redirect URI and the document are specific to the MCP server, so a response\n * cannot be mixed up with one from another authorization server (RFC 9700 section 4.4.2.2).\n */\nfunction clientMetadataDocument(\n\tserverUrl: string,\n\tredirectUrl: string,\n\tmetadata: AuthorizationServerMetadata | undefined,\n): OAuthClientMetadataDocument {\n\tif (\n\t\t!metadata?.client_id_metadata_document_supported ||\n\t\t!metadata.token_endpoint_auth_methods_supported?.includes(\"none\")\n\t) {\n\t\tthrow new Error(\n\t\t\t'The authorization server does not support Client ID Metadata Documents for public clients; remove oauth.clientRegistration \"cimd\"',\n\t\t);\n\t}\n\tif (metadata.authorization_response_iss_parameter_supported) {\n\t\treturn { url: `${CLIENT_METADATA_BASE_URL}/client.json`, redirectUrl };\n\t}\n\tconst id = callbackId(serverUrl);\n\tconst redirect = new URL(redirectUrl);\n\tredirect.pathname = `${CALLBACK_PATH}/${id}`;\n\treturn { url: `${CLIENT_METADATA_BASE_URL}/${id}/client.json`, redirectUrl: redirect.href };\n}\n\nfunction createProvider(\n\tserverUrl: string,\n\tstore: McpOAuthStateStore,\n\tsettings: McpOAuthSettings,\n\tredirectUrl: string,\n\tonRedirect: (url: URL) => void,\n): McpOAuthProvider {\n\treturn new McpOAuthProvider({\n\t\tserverUrl,\n\t\tredirectUrl,\n\t\tclientMetadata: { client_name: settings.clientName ?? APP_NAME },\n\t\tclientMetadataDocument:\n\t\t\tsettings.clientRegistration === \"cimd\"\n\t\t\t\t? (metadata) => clientMetadataDocument(serverUrl, redirectUrl, metadata)\n\t\t\t\t: undefined,\n\t\tclientId: settings.clientId,\n\t\tclientSecret: settings.clientSecret,\n\t\tstore,\n\t\tonRedirect,\n\t});\n}\n\nexport interface McpAuthProvider extends AuthProvider {\n\t/** Resolves when no refresh is running, so shutdown does not drop rotated tokens before they are saved. */\n\tsettled(): Promise<void>;\n}\n\n/**\n * Auth provider for MCP connections: sends the stored access token and refreshes it when it is about\n * to expire or after a 401. Throws `McpOAuthAuthorizationRequiredError` when the user has to sign in,\n * including when the server asks for more scope (`insufficient_scope`). `onChallenge` receives the\n * server's `WWW-Authenticate` challenge so sign-in can use its resource metadata URL and scope.\n * `settings` is only called when a refresh is needed, so a secret that fails to resolve fails the\n * refresh instead of the whole connection setup.\n *\n * Many servers rotate refresh tokens, so two refreshes with the same refresh token lose the grant.\n * Requests in this process share one refresh, and other processes are kept out by the store's\n * refresh lock, held from reading the tokens to saving new ones. Tokens that changed meanwhile\n * (another process refreshed them, or the user signed in) are used without refreshing.\n */\nexport function createMcpAuthProvider(options: {\n\tserverUrl: string;\n\tstore: McpOAuthServerStore;\n\tsettings: () => McpOAuthSettings;\n\tonChallenge: (challenge: OAuthChallenge) => void;\n}): McpAuthProvider {\n\tconst { serverUrl, store } = options;\n\tlet refreshing: Promise<void> | undefined;\n\n\t/** Replace `staleToken`, the access token that expired or was rejected. */\n\tconst refresh = (staleToken: string | undefined, fetch: McpFetch = globalThis.fetch, challenge?: OAuthChallenge) => {\n\t\trefreshing ??= store\n\t\t\t.withRefreshLock(async () => {\n\t\t\t\tconst state = await store.load();\n\t\t\t\tif (state?.tokens?.access_token !== staleToken) return;\n\t\t\t\tif (!state?.tokens?.refresh_token) throw new McpOAuthAuthorizationRequiredError();\n\t\t\t\tconst settings = options.settings();\n\t\t\t\tconst redirectUrl =\n\t\t\t\t\tcallbackSettings(settings).fixedRedirectUrl ??\n\t\t\t\t\tregisteredRedirectUrls(state.clientInformation)[0] ??\n\t\t\t\t\tFALLBACK_REDIRECT_URL;\n\t\t\t\tconst provider = createProvider(serverUrl, store, settings, redirectUrl, () => {});\n\t\t\t\t// Refreshes the tokens, or reports that a new sign-in is needed.\n\t\t\t\tconst result = await authorizeMcp(provider, {\n\t\t\t\t\tserverUrl,\n\t\t\t\t\tresourceMetadataUrl: challenge?.resourceMetadataUrl,\n\t\t\t\t\tauthorizationServerMetadataUrl: settings.authServerMetadataUrl,\n\t\t\t\t\tscope: challenge?.scope,\n\t\t\t\t\tfetch: (input, init) =>\n\t\t\t\t\t\tfetch(input, { ...init, signal: AbortSignal.timeout(REFRESH_REQUEST_TIMEOUT_MS) }),\n\t\t\t\t});\n\t\t\t\tif (result === \"REDIRECT\") throw new McpOAuthAuthorizationRequiredError();\n\t\t\t})\n\t\t\t.finally(() => {\n\t\t\t\trefreshing = undefined;\n\t\t\t});\n\t\treturn refreshing;\n\t};\n\n\treturn {\n\t\ttoken: async () => {\n\t\t\tawait refreshing?.catch(() => undefined);\n\t\t\tconst state = await store.load();\n\t\t\tconst token = state?.tokens?.access_token;\n\t\t\tconst expired = state?.tokensExpireAt !== undefined && state.tokensExpireAt - REFRESH_SKEW_MS <= Date.now();\n\t\t\tif (!expired || !state?.tokens?.refresh_token) return token;\n\t\t\t// Failures fall through: the request goes out with the old token and a 401 decides what happens.\n\t\t\tawait refresh(token).catch(() => undefined);\n\t\t\treturn (await store.load())?.tokens?.access_token;\n\t\t},\n\t\tonUnauthorized: async (context) => {\n\t\t\tconst challenge = parseWwwAuthenticate(context.response.headers.get(\"www-authenticate\"));\n\t\t\toptions.onChallenge(challenge);\n\t\t\t// A refresh keeps the granted scope, so more scope needs a new sign-in.\n\t\t\tif (challenge.error === \"insufficient_scope\") throw new McpOAuthAuthorizationRequiredError();\n\t\t\tawait refresh(context.token, context.fetch, challenge);\n\t\t},\n\t\tsettled: async () => {\n\t\t\tawait refreshing?.catch(() => undefined);\n\t\t},\n\t};\n}\n\nexport interface McpSignInPrompt {\n\t/** Show the authorization URL to the user and open it in a browser. */\n\tshowAuthorizationUrl(url: URL): void;\n\t/**\n\t * Ask for the redirect URL from the browser address bar, for when the browser cannot reach the\n\t * loopback callback (for example over SSH). Aborted once the callback arrives. Resolves to\n\t * `undefined` or an empty string when the user cancels.\n\t */\n\tpromptForRedirectUrl(signal: AbortSignal): Promise<string | undefined>;\n}\n\nexport class McpSignInCancelledError extends Error {\n\tconstructor() {\n\t\tsuper(\"Sign-in cancelled\");\n\t\tthis.name = \"McpSignInCancelledError\";\n\t}\n}\n\ntype AuthorizationResponse = Pick<OAuthCallback, \"code\" | \"iss\">;\n\nfunction responseFromRedirectUrl(input: string, state: string, redirectUrl: URL): AuthorizationResponse {\n\tlet url: URL;\n\ttry {\n\t\turl = new URL(input.trim());\n\t} catch {\n\t\tthrow new Error(\"Expected the full redirect URL from the browser address bar\");\n\t}\n\t// A server-specific redirect URI tells authorization servers apart, so it must match exactly.\n\tif (url.origin !== redirectUrl.origin || url.pathname !== redirectUrl.pathname) {\n\t\tthrow new Error(\"The redirect URL does not match this sign-in's redirect URI\");\n\t}\n\tconst error = url.searchParams.get(\"error\");\n\tif (error) throw new Error(url.searchParams.get(\"error_description\") ?? error);\n\tif (url.searchParams.get(\"state\") !== state) throw new Error(\"The redirect URL belongs to a different sign-in\");\n\tconst code = url.searchParams.get(\"code\");\n\tif (!code) throw new Error(\"The redirect URL does not contain an authorization code\");\n\treturn { code, iss: url.searchParams.get(\"iss\") ?? undefined };\n}\n\n/** Wait for the browser callback or a pasted redirect URL, whichever comes first. */\nasync function waitForAuthorizationResponse(\n\tcallback: OAuthCallbackServer,\n\tstate: string,\n\tredirectUrl: URL,\n\tprompt: McpSignInPrompt,\n): Promise<AuthorizationResponse> {\n\tconst controller = new AbortController();\n\tconst fromBrowser = callback.waitForCallback(state, redirectUrl.pathname);\n\tconst fromUser = prompt.promptForRedirectUrl(controller.signal).then((input) => {\n\t\tif (!input?.trim()) throw new McpSignInCancelledError();\n\t\treturn responseFromRedirectUrl(input, state, redirectUrl);\n\t});\n\ttry {\n\t\treturn await Promise.race([fromBrowser, fromUser]);\n\t} finally {\n\t\t// The losing side rejects once the prompt is aborted or the callback server closes.\n\t\tcontroller.abort();\n\t\tfromBrowser.catch(() => undefined);\n\t\tfromUser.catch(() => undefined);\n\t}\n}\n\n/** Listen on `port`, or on a free port when it is taken and not `required`. */\nasync function listenForCallback(\n\tsettings: CallbackSettings,\n\textraPaths: string[],\n\tport: number | undefined,\n\trequired: boolean,\n): Promise<OAuthCallbackServer> {\n\tconst options = {\n\t\thost: settings.host,\n\t\tredirectHost: settings.redirectHost,\n\t\tpath: settings.path,\n\t\textraPaths,\n\t\trenderPage: (page: OAuthCallbackPage) =>\n\t\t\tpage.ok\n\t\t\t\t? oauthSuccessHtml(\"Signed in to the MCP server. You may now close this page.\")\n\t\t\t\t: oauthErrorHtml(page.message, page.details),\n\t};\n\ttry {\n\t\treturn await OAuthCallbackServer.listen({ ...options, port: port ?? 0 });\n\t} catch (error) {\n\t\tif (required || port === undefined) throw error;\n\t\treturn OAuthCallbackServer.listen(options);\n\t}\n}\n\n/**\n * Sign in to an MCP server. Uses the stored refresh token when possible; otherwise runs the browser\n * authorization code flow. Tokens are saved to `store`.\n */\nexport async function signInMcpServer(options: {\n\tserverUrl: string;\n\tstore: McpOAuthStateStore;\n\tsettings: McpOAuthSettings;\n\tchallenge?: OAuthChallenge;\n\tprompt: McpSignInPrompt;\n}): Promise<void> {\n\tconst { serverUrl, store, settings } = options;\n\tconst stored = await store.load();\n\tconst stepUp = options.challenge?.error === \"insufficient_scope\";\n\tconst callbackOptions = callbackSettings(settings);\n\t// Reuse the port of the registered redirect URI so the registered client stays valid.\n\tconst registered = registeredRedirectUrls(stored?.clientInformation)[0];\n\tconst preferredPort =\n\t\tcallbackOptions.port ?? (registered ? Number(new URL(registered).port) || undefined : undefined);\n\tconst cimd = settings.clientRegistration === \"cimd\";\n\tconst callback = await listenForCallback(\n\t\tcallbackOptions,\n\t\t// The redirect URI of a server-specific Client ID Metadata Document.\n\t\tcimd ? [`${CALLBACK_PATH}/${callbackId(serverUrl)}`] : [],\n\t\tpreferredPort,\n\t\tcallbackOptions.port !== undefined,\n\t);\n\tconst redirectUrl = callbackOptions.fixedRedirectUrl ?? callback.redirectUrl;\n\ttry {\n\t\tif (stored) {\n\t\t\tconst next: McpOAuthState = { ...stored };\n\t\t\t// Every sign-in gets a fresh `state` parameter.\n\t\t\tdelete next.oauthState;\n\t\t\t// A registered client cannot use another redirect URI, and its tokens belong to it. A Client ID\n\t\t\t// Metadata Document is not stored, so with one, a stored client was registered before and is replaced.\n\t\t\tconst keepClient =\n\t\t\t\tsettings.clientId ||\n\t\t\t\t(cimd ? !stored.clientInformation : registeredRedirectUrls(stored.clientInformation).includes(redirectUrl));\n\t\t\tif (!keepClient) {\n\t\t\t\tdelete next.clientInformation;\n\t\t\t\tdelete next.tokens;\n\t\t\t\tdelete next.tokensExpireAt;\n\t\t\t}\n\t\t\tawait store.save(next);\n\t\t}\n\n\t\tlet authorizationUrl: URL | undefined;\n\t\tconst provider = createProvider(serverUrl, store, settings, redirectUrl, (url) => {\n\t\t\tauthorizationUrl = url;\n\t\t});\n\t\tconst flow = {\n\t\t\tserverUrl,\n\t\t\tresourceMetadataUrl: options.challenge?.resourceMetadataUrl,\n\t\t\tauthorizationServerMetadataUrl: settings.authServerMetadataUrl,\n\t\t\t// A server asking for more scope gets it on top of the configured scope and, since the challenge\n\t\t\t// may list only the missing scopes, on top of the scope granted so far.\n\t\t\tscope: mergeScopes(\n\t\t\t\tsettings.scope,\n\t\t\t\tstepUp ? stepUpScope(stored?.tokens?.scope, options.challenge?.scope) : options.challenge?.scope,\n\t\t\t),\n\t\t};\n\t\t// A refresh keeps the granted scope; a server asking for more needs the browser flow.\n\t\tconst skipRefresh = stepUp;\n\t\tif ((await authorizeMcp(provider, { ...flow, skipRefresh })) === \"AUTHORIZED\") return;\n\t\tif (!authorizationUrl) throw new Error(\"OAuth flow did not produce an authorization URL\");\n\n\t\tconst state = await provider.state();\n\t\t// The flow picks the redirect URI, which may be specific to the MCP server.\n\t\tconst authorizationRedirectUrl = new URL(authorizationUrl.searchParams.get(\"redirect_uri\") ?? redirectUrl);\n\t\toptions.prompt.showAuthorizationUrl(authorizationUrl);\n\t\tconst { code, iss } = await waitForAuthorizationResponse(\n\t\t\tcallback,\n\t\t\tstate,\n\t\t\tauthorizationRedirectUrl,\n\t\t\toptions.prompt,\n\t\t);\n\t\tawait authorizeMcp(provider, { ...flow, authorizationCode: code, iss });\n\t} finally {\n\t\tawait callback.close();\n\t}\n}\n"]}
1
+ {"version":3,"file":"oauth.js","sourceRoot":"","sources":["../../../src/extensions/mcp/oauth.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * OAuth sign-in for remote MCP servers.\n *\n * Connections never start a browser flow on their own. They send the stored access token and, after\n * a 401, try the stored refresh token. When that is not possible they fail with\n * `McpOAuthAuthorizationRequiredError`, and the user signs in through `/mcp`, which runs\n * the authorization code flow (PKCE, a Client ID Metadata Document or dynamic client registration)\n * against a loopback callback.\n *\n * Credentials live in `<agent-dir>/mcp-auth.json`, keyed by server name and URL.\n */\n\nimport { createHash } from \"node:crypto\";\nimport { mkdirSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { oauthErrorHtml, oauthSuccessHtml } from \"@earendil-works/pi-ai/utils/oauth-page\";\nimport type { AuthProvider, McpFetch } from \"@earendil-works/pi-mcp\";\nimport {\n\ttype AuthorizationServerMetadata,\n\tauthorizeMcp,\n\tMcpOAuthAuthorizationRequiredError,\n\tMcpOAuthProvider,\n\ttype McpOAuthState,\n\ttype McpOAuthStateStore,\n\ttype OAuthCallback,\n\ttype OAuthCallbackPage,\n\tOAuthCallbackServer,\n\ttype OAuthChallenge,\n\ttype OAuthClientInformationMixed,\n\ttype OAuthClientMetadataDocument,\n\tparseWwwAuthenticate,\n\tstepUpScope,\n} from \"@earendil-works/pi-mcp/oauth\";\nimport lockfile from \"proper-lockfile\";\nimport { APP_NAME, getAgentDir } from \"../../config.ts\";\nimport { type AuthStorageBackend, FileAuthStorageBackend } from \"../../core/auth-storage.ts\";\nimport { mcpNamespace } from \"../../core/mcp-servers.ts\";\n\nconst CALLBACK_HOST = \"127.0.0.1\";\nconst CALLBACK_PATH = \"/callback\";\n/** Where pi.dev serves pi's Client ID Metadata Documents: `client.json` and `<callback ID>/client.json`. */\nconst CLIENT_METADATA_BASE_URL = \"https://pi.dev/oauth\";\n/** Redirect URI for refreshes when none is stored. Refreshing never redirects the user. */\nconst FALLBACK_REDIRECT_URL = `http://${CALLBACK_HOST}${CALLBACK_PATH}`;\n/** Access tokens this close to expiry are refreshed before they are sent. */\nconst REFRESH_SKEW_MS = 30_000;\n/**\n * Bounds each request to the authorization server, so an unresponsive one cannot hold the refresh lock,\n * delay shutdown, or keep a sign-in waiting for long.\n */\nconst OAUTH_REQUEST_TIMEOUT_MS = 15_000;\n/** A refresh lock that its holder stopped renewing (the process was killed) is taken over after this. */\nconst REFRESH_LOCK_STALE_MS = 20_000;\n/** How long to wait for another process's refresh: longer than a stale lock lives. */\nconst REFRESH_LOCK_WAIT_MS = 25_000;\nconst REFRESH_LOCK_RETRY_MS = 100;\n\nexport interface McpOAuthSettings {\n\tclientId?: string;\n\t/** Already resolved. */\n\tclientSecret?: string;\n\tcallbackPort?: number;\n\t/** Loopback redirect URI; see `McpOAuthConfig.callbackUrl`. */\n\tcallbackUrl?: string;\n\t/** Scopes to request, separated by spaces. */\n\tscope?: string;\n\t/** `client_name` for dynamic client registration. Default: `APP_NAME`. */\n\tclientName?: string;\n\t/** See `McpOAuthConfig.clientRegistration`. */\n\tclientRegistration?: \"dcr\" | \"cimd\";\n\t/** See `McpOAuthConfig.authServerMetadataUrl`. */\n\tauthServerMetadataUrl?: URL;\n}\n\n/** Where the loopback callback server listens and the redirect URI it serves. */\ninterface CallbackSettings {\n\t/** Address to listen on. */\n\thost: string;\n\t/** Host name in the redirect URI. */\n\tredirectHost: string;\n\tport: number | undefined;\n\tpath: string;\n\t/** The exact redirect URI, when the port is fixed. */\n\tfixedRedirectUrl: string | undefined;\n}\n\nfunction callbackSettings(settings: McpOAuthSettings): CallbackSettings {\n\tconst url = new URL(settings.callbackUrl ?? `http://${CALLBACK_HOST}${CALLBACK_PATH}`);\n\tconst address = url.hostname.replace(/^\\[|\\]$/g, \"\");\n\tconst port = url.port ? Number(url.port) : settings.callbackPort;\n\tlet fixedRedirectUrl: string | undefined;\n\t// A configured URI with a port is sent exactly as written, since servers compare it as a string.\n\tif (url.port) fixedRedirectUrl = settings.callbackUrl;\n\telse if (port !== undefined) {\n\t\turl.port = String(port);\n\t\tfixedRedirectUrl = url.href;\n\t}\n\treturn {\n\t\t// `localhost` is served on 127.0.0.1; browsers fall back to it when ::1 refuses.\n\t\thost: address === \"localhost\" ? CALLBACK_HOST : address,\n\t\tredirectHost: address,\n\t\tport,\n\t\tpath: url.pathname,\n\t\tfixedRedirectUrl,\n\t};\n}\n\n/** Scopes of both lists, each once. */\nfunction mergeScopes(...scopes: (string | undefined)[]): string | undefined {\n\tconst merged = [...new Set(scopes.flatMap((scope) => scope?.split(/\\s+/).filter(Boolean) ?? []))];\n\treturn merged.length > 0 ? merged.join(\" \") : undefined;\n}\n\ntype StoredStates = Record<string, McpOAuthState>;\n\nfunction parseStates(content: string | undefined): StoredStates {\n\tif (!content?.trim()) return {};\n\tconst parsed: unknown = JSON.parse(content);\n\treturn typeof parsed === \"object\" && parsed !== null && !Array.isArray(parsed) ? (parsed as StoredStates) : {};\n}\n\nfunction serializeStates(states: StoredStates): string {\n\treturn `${JSON.stringify(states, null, 2)}\\n`;\n}\n\n/**\n * Keys of a server's state: by name and URL, so servers sharing a URL keep separate accounts, and the\n * legacy key by URL alone, written by older versions.\n */\nfunction storeKeys(name: string, serverUrl: string): { key: string; legacyKey: string } {\n\tconst legacyKey = String(new URL(serverUrl));\n\treturn { key: `${mcpNamespace(name)}|${legacyKey}`, legacyKey };\n}\n\nexport interface McpOAuthServerStore extends McpOAuthStateStore {\n\t/** Run `fn` while no other process refreshes the server's tokens. */\n\twithRefreshLock<T>(fn: () => Promise<T>): Promise<T>;\n}\n\n/** Per-server OAuth state (client registration, tokens, pending PKCE verifier) in `mcp-auth.json`. */\nexport class McpOAuthCredentialStore {\n\tprivate readonly backend: AuthStorageBackend;\n\t/** Directory for the refresh lock files. Without one, refreshes are only serialized in this process. */\n\tprivate readonly lockDir: string | undefined;\n\n\tconstructor(backend?: AuthStorageBackend, lockDir?: string) {\n\t\tthis.backend = backend ?? new FileAuthStorageBackend(join(getAgentDir(), \"mcp-auth.json\"));\n\t\tthis.lockDir = backend ? lockDir : getAgentDir();\n\t}\n\n\tforServer(name: string, serverUrl: string): McpOAuthServerStore {\n\t\tconst { key, legacyKey } = storeKeys(name, serverUrl);\n\t\treturn {\n\t\t\t// The first server to load legacy state takes it over; others with the same URL sign in again.\n\t\t\tload: () =>\n\t\t\t\tthis.backend.withLock((current) => {\n\t\t\t\t\tconst states = parseStates(current);\n\t\t\t\t\tif (states[key] || !states[legacyKey]) return { result: states[key] };\n\t\t\t\t\tstates[key] = states[legacyKey];\n\t\t\t\t\tdelete states[legacyKey];\n\t\t\t\t\treturn { result: states[key], next: serializeStates(states) };\n\t\t\t\t}),\n\t\t\tsave: (state) =>\n\t\t\t\tthis.write((states) => {\n\t\t\t\t\tstates[key] = state;\n\t\t\t\t}),\n\t\t\twithRefreshLock: (fn) => this.withRefreshLock(key, fn),\n\t\t};\n\t}\n\n\t/**\n\t * A lock file per server. When the process exits, proper-lockfile removes the locks it holds; when\n\t * it is killed, the lock goes stale because it is no longer renewed, and the next process takes it over.\n\t */\n\tprivate async withRefreshLock<T>(key: string, fn: () => Promise<T>): Promise<T> {\n\t\tif (!this.lockDir) return fn();\n\t\tmkdirSync(this.lockDir, { recursive: true, mode: 0o700 });\n\t\tconst hash = createHash(\"sha256\").update(key).digest(\"hex\").slice(0, 16);\n\t\tconst release = await lockfile.lock(join(this.lockDir, `mcp-auth-refresh-${hash}`), {\n\t\t\trealpath: false,\n\t\t\tstale: REFRESH_LOCK_STALE_MS,\n\t\t\tretries: {\n\t\t\t\tretries: REFRESH_LOCK_WAIT_MS / REFRESH_LOCK_RETRY_MS,\n\t\t\t\tfactor: 1,\n\t\t\t\tminTimeout: REFRESH_LOCK_RETRY_MS,\n\t\t\t\tmaxTimeout: REFRESH_LOCK_RETRY_MS,\n\t\t\t},\n\t\t\t// The default throws from a timer. A lost lock at worst lets two refreshes overlap.\n\t\t\tonCompromised: () => {},\n\t\t});\n\t\ttry {\n\t\t\treturn await fn();\n\t\t} finally {\n\t\t\tawait release().catch(() => undefined);\n\t\t}\n\t}\n\n\t/** The stored tokens of a server, for noticing sign-ins done by another process. Does not take over legacy state. */\n\ttokens(name: string, serverUrl: string): McpOAuthState[\"tokens\"] {\n\t\tconst { key, legacyKey } = storeKeys(name, serverUrl);\n\t\tconst states = this.backend.withLock((current) => ({ result: parseStates(current) }));\n\t\treturn (states[key] ?? states[legacyKey])?.tokens;\n\t}\n\n\t/** Returns whether credentials were stored for the server. Removes legacy state the server would take over. */\n\tremove(name: string, serverUrl: string): boolean {\n\t\tconst { key, legacyKey } = storeKeys(name, serverUrl);\n\t\treturn this.backend.withLock((current) => {\n\t\t\tconst states = parseStates(current);\n\t\t\tconst stored = key in states ? key : legacyKey in states ? legacyKey : undefined;\n\t\t\tif (!stored) return { result: false };\n\t\t\tdelete states[stored];\n\t\t\treturn { result: true, next: serializeStates(states) };\n\t\t});\n\t}\n\n\tprivate write(update: (states: StoredStates) => void): void {\n\t\tthis.backend.withLock((current) => {\n\t\t\tconst states = parseStates(current);\n\t\t\tupdate(states);\n\t\t\treturn { result: undefined, next: serializeStates(states) };\n\t\t});\n\t}\n}\n\n/** `fetch` with `OAUTH_REQUEST_TIMEOUT_MS` per request, on top of the request's own signal. */\nfunction timedFetch(fetch: McpFetch = globalThis.fetch): McpFetch {\n\treturn (input, init) => {\n\t\tconst timeout = AbortSignal.timeout(OAUTH_REQUEST_TIMEOUT_MS);\n\t\treturn fetch(input, { ...init, signal: init?.signal ? AbortSignal.any([init.signal, timeout]) : timeout });\n\t};\n}\n\nfunction registeredRedirectUrls(client: OAuthClientInformationMixed | undefined): string[] {\n\treturn client && \"redirect_uris\" in client ? client.redirect_uris : [];\n}\n\n/** 12 characters identifying an MCP server URL in callback paths, computed like Codex does. */\nfunction callbackId(serverUrl: string): string {\n\tconst url = new URL(serverUrl);\n\turl.hash = \"\";\n\treturn createHash(\"sha256\").update(url.href).digest().subarray(0, 9).toString(\"base64url\");\n}\n\n/**\n * pi's Client ID Metadata Document, for `clientRegistration: \"cimd\"`, chosen like Codex chooses its own.\n * The configuration ensures the default callback path. Without the `iss` parameter in authorization\n * responses (RFC 9207), the redirect URI and the document are specific to the MCP server, so a response\n * cannot be mixed up with one from another authorization server (RFC 9700 section 4.4.2.2).\n */\nfunction clientMetadataDocument(\n\tserverUrl: string,\n\tredirectUrl: string,\n\tmetadata: AuthorizationServerMetadata | undefined,\n): OAuthClientMetadataDocument {\n\tif (\n\t\t!metadata?.client_id_metadata_document_supported ||\n\t\t!metadata.token_endpoint_auth_methods_supported?.includes(\"none\")\n\t) {\n\t\tthrow new Error(\n\t\t\t'The authorization server does not support Client ID Metadata Documents for public clients; remove oauth.clientRegistration \"cimd\"',\n\t\t);\n\t}\n\tif (metadata.authorization_response_iss_parameter_supported) {\n\t\treturn { url: `${CLIENT_METADATA_BASE_URL}/client.json`, redirectUrl };\n\t}\n\tconst id = callbackId(serverUrl);\n\tconst redirect = new URL(redirectUrl);\n\tredirect.pathname = `${CALLBACK_PATH}/${id}`;\n\treturn { url: `${CLIENT_METADATA_BASE_URL}/${id}/client.json`, redirectUrl: redirect.href };\n}\n\nfunction createProvider(\n\tserverUrl: string,\n\tstore: McpOAuthStateStore,\n\tsettings: McpOAuthSettings,\n\tredirectUrl: string,\n\tonRedirect: (url: URL) => void,\n): McpOAuthProvider {\n\treturn new McpOAuthProvider({\n\t\tserverUrl,\n\t\tredirectUrl,\n\t\tclientMetadata: { client_name: settings.clientName ?? APP_NAME },\n\t\tclientMetadataDocument:\n\t\t\tsettings.clientRegistration === \"cimd\"\n\t\t\t\t? (metadata) => clientMetadataDocument(serverUrl, redirectUrl, metadata)\n\t\t\t\t: undefined,\n\t\tclientId: settings.clientId,\n\t\tclientSecret: settings.clientSecret,\n\t\tstore,\n\t\tonRedirect,\n\t});\n}\n\nexport interface McpAuthProvider extends AuthProvider {\n\t/** Resolves when no refresh is running, so shutdown does not drop rotated tokens before they are saved. */\n\tsettled(): Promise<void>;\n}\n\n/**\n * Auth provider for MCP connections: sends the stored access token and refreshes it when it is about\n * to expire or after a 401. Throws `McpOAuthAuthorizationRequiredError` when the user has to sign in,\n * including when the server asks for more scope (`insufficient_scope`). `onChallenge` receives the\n * server's `WWW-Authenticate` challenge so sign-in can use its resource metadata URL and scope.\n * `settings` is only called when a refresh is needed, so a secret that fails to resolve fails the\n * refresh instead of the whole connection setup.\n *\n * Many servers rotate refresh tokens, so two refreshes with the same refresh token lose the grant.\n * Requests in this process share one refresh, and other processes are kept out by the store's\n * refresh lock, held from reading the tokens to saving new ones. Tokens that changed meanwhile\n * (another process refreshed them, or the user signed in) are used without refreshing.\n */\nexport function createMcpAuthProvider(options: {\n\tserverUrl: string;\n\tstore: McpOAuthServerStore;\n\tsettings: () => McpOAuthSettings;\n\tonChallenge: (challenge: OAuthChallenge) => void;\n}): McpAuthProvider {\n\tconst { serverUrl, store } = options;\n\tlet refreshing: Promise<void> | undefined;\n\n\t/** Replace `staleToken`, the access token that expired or was rejected. */\n\tconst refresh = (staleToken: string | undefined, fetch: McpFetch = globalThis.fetch, challenge?: OAuthChallenge) => {\n\t\trefreshing ??= store\n\t\t\t.withRefreshLock(async () => {\n\t\t\t\tconst state = await store.load();\n\t\t\t\tif (state?.tokens?.access_token !== staleToken) return;\n\t\t\t\tif (!state?.tokens?.refresh_token) throw new McpOAuthAuthorizationRequiredError();\n\t\t\t\tconst settings = options.settings();\n\t\t\t\tconst redirectUrl =\n\t\t\t\t\tcallbackSettings(settings).fixedRedirectUrl ??\n\t\t\t\t\tregisteredRedirectUrls(state.clientInformation)[0] ??\n\t\t\t\t\tFALLBACK_REDIRECT_URL;\n\t\t\t\tconst provider = createProvider(serverUrl, store, settings, redirectUrl, () => {});\n\t\t\t\t// Refreshes the tokens, or reports that a new sign-in is needed. Not cancellable: a refresh the\n\t\t\t\t// server answered may have rotated the refresh token, so its answer must be saved.\n\t\t\t\tconst result = await authorizeMcp(provider, {\n\t\t\t\t\tserverUrl,\n\t\t\t\t\tresourceMetadataUrl: challenge?.resourceMetadataUrl,\n\t\t\t\t\tauthorizationServerMetadataUrl: settings.authServerMetadataUrl,\n\t\t\t\t\tscope: challenge?.scope,\n\t\t\t\t\tfetch: timedFetch(fetch),\n\t\t\t\t});\n\t\t\t\tif (result === \"REDIRECT\") throw new McpOAuthAuthorizationRequiredError();\n\t\t\t})\n\t\t\t.finally(() => {\n\t\t\t\trefreshing = undefined;\n\t\t\t});\n\t\treturn refreshing;\n\t};\n\n\treturn {\n\t\ttoken: async () => {\n\t\t\tawait refreshing?.catch(() => undefined);\n\t\t\tconst state = await store.load();\n\t\t\tconst token = state?.tokens?.access_token;\n\t\t\tconst expired = state?.tokensExpireAt !== undefined && state.tokensExpireAt - REFRESH_SKEW_MS <= Date.now();\n\t\t\tif (!expired || !state?.tokens?.refresh_token) return token;\n\t\t\t// Failures fall through: the request goes out with the old token and a 401 decides what happens.\n\t\t\tawait refresh(token).catch(() => undefined);\n\t\t\treturn (await store.load())?.tokens?.access_token;\n\t\t},\n\t\tonUnauthorized: async (context) => {\n\t\t\tconst challenge = parseWwwAuthenticate(context.response.headers.get(\"www-authenticate\"));\n\t\t\toptions.onChallenge(challenge);\n\t\t\t// A refresh keeps the granted scope, so more scope needs a new sign-in.\n\t\t\tif (challenge.error === \"insufficient_scope\") throw new McpOAuthAuthorizationRequiredError();\n\t\t\tawait refresh(context.token, context.fetch, challenge);\n\t\t},\n\t\tsettled: async () => {\n\t\t\tawait refreshing?.catch(() => undefined);\n\t\t},\n\t};\n}\n\nexport interface McpSignInPrompt {\n\t/** Show the authorization URL to the user and open it in a browser. */\n\tshowAuthorizationUrl(url: URL): void;\n\t/**\n\t * Ask for the redirect URL from the browser address bar, for when the browser cannot reach the\n\t * loopback callback (for example over SSH). Aborted once the callback arrives or the sign-in is\n\t * cancelled. Resolves to `undefined` or an empty string when the user cancels.\n\t */\n\tpromptForRedirectUrl(signal: AbortSignal): Promise<string | undefined>;\n}\n\nexport class McpSignInCancelledError extends Error {\n\tconstructor() {\n\t\tsuper(\"Sign-in cancelled\");\n\t\tthis.name = \"McpSignInCancelledError\";\n\t}\n}\n\ntype AuthorizationResponse = Pick<OAuthCallback, \"code\" | \"iss\">;\n\nfunction responseFromRedirectUrl(input: string, state: string, redirectUrl: URL): AuthorizationResponse {\n\tlet url: URL;\n\ttry {\n\t\turl = new URL(input.trim());\n\t} catch {\n\t\tthrow new Error(\"Expected the full redirect URL from the browser address bar\");\n\t}\n\t// A server-specific redirect URI tells authorization servers apart, so it must match exactly.\n\tif (url.origin !== redirectUrl.origin || url.pathname !== redirectUrl.pathname) {\n\t\tthrow new Error(\"The redirect URL does not match this sign-in's redirect URI\");\n\t}\n\tconst error = url.searchParams.get(\"error\");\n\tif (error) throw new Error(url.searchParams.get(\"error_description\") ?? error);\n\tif (url.searchParams.get(\"state\") !== state) throw new Error(\"The redirect URL belongs to a different sign-in\");\n\tconst code = url.searchParams.get(\"code\");\n\tif (!code) throw new Error(\"The redirect URL does not contain an authorization code\");\n\treturn { code, iss: url.searchParams.get(\"iss\") ?? undefined };\n}\n\n/**\n * Wait for the browser callback or a pasted redirect URL, whichever comes first. Aborting `signal`\n * aborts the prompt, which then cancels the sign-in.\n */\nasync function waitForAuthorizationResponse(\n\tcallback: OAuthCallbackServer,\n\tstate: string,\n\tredirectUrl: URL,\n\tprompt: McpSignInPrompt,\n\tsignal: AbortSignal | undefined,\n): Promise<AuthorizationResponse> {\n\tconst controller = new AbortController();\n\tconst promptSignal = signal ? AbortSignal.any([controller.signal, signal]) : controller.signal;\n\tconst fromBrowser = callback.waitForCallback(state, redirectUrl.pathname);\n\tconst fromUser = prompt.promptForRedirectUrl(promptSignal).then((input) => {\n\t\tif (!input?.trim()) throw new McpSignInCancelledError();\n\t\treturn responseFromRedirectUrl(input, state, redirectUrl);\n\t});\n\ttry {\n\t\treturn await Promise.race([fromBrowser, fromUser]);\n\t} finally {\n\t\t// The losing side rejects once the prompt is aborted or the callback server closes.\n\t\tcontroller.abort();\n\t\tfromBrowser.catch(() => undefined);\n\t\tfromUser.catch(() => undefined);\n\t}\n}\n\n/** Listen on `port`, or on a free port when it is taken and not `required`. */\nasync function listenForCallback(\n\tsettings: CallbackSettings,\n\textraPaths: string[],\n\tport: number | undefined,\n\trequired: boolean,\n): Promise<OAuthCallbackServer> {\n\tconst options = {\n\t\thost: settings.host,\n\t\tredirectHost: settings.redirectHost,\n\t\tpath: settings.path,\n\t\textraPaths,\n\t\trenderPage: (page: OAuthCallbackPage) =>\n\t\t\tpage.ok\n\t\t\t\t? oauthSuccessHtml(\"Signed in to the MCP server. You may now close this page.\")\n\t\t\t\t: oauthErrorHtml(page.message, page.details),\n\t};\n\ttry {\n\t\treturn await OAuthCallbackServer.listen({ ...options, port: port ?? 0 });\n\t} catch (error) {\n\t\tif (required || port === undefined) throw error;\n\t\treturn OAuthCallbackServer.listen(options);\n\t}\n}\n\n/**\n * Sign in to an MCP server. Uses the stored refresh token when possible; otherwise runs the browser\n * authorization code flow. Tokens are saved to `store`. Aborting `signal` stops the sign-in at any\n * step with `McpSignInCancelledError`; requests to the authorization server are also time-limited.\n */\nexport async function signInMcpServer(options: {\n\tserverUrl: string;\n\tstore: McpOAuthStateStore;\n\tsettings: McpOAuthSettings;\n\tchallenge?: OAuthChallenge;\n\tprompt: McpSignInPrompt;\n\tsignal?: AbortSignal;\n}): Promise<void> {\n\tconst { serverUrl, store, settings, signal } = options;\n\tif (signal?.aborted) throw new McpSignInCancelledError();\n\tconst stored = await store.load();\n\tconst stepUp = options.challenge?.error === \"insufficient_scope\";\n\tconst callbackOptions = callbackSettings(settings);\n\t// Reuse the port of the registered redirect URI so the registered client stays valid.\n\tconst registered = registeredRedirectUrls(stored?.clientInformation)[0];\n\tconst preferredPort =\n\t\tcallbackOptions.port ?? (registered ? Number(new URL(registered).port) || undefined : undefined);\n\tconst cimd = settings.clientRegistration === \"cimd\";\n\tconst callback = await listenForCallback(\n\t\tcallbackOptions,\n\t\t// The redirect URI of a server-specific Client ID Metadata Document.\n\t\tcimd ? [`${CALLBACK_PATH}/${callbackId(serverUrl)}`] : [],\n\t\tpreferredPort,\n\t\tcallbackOptions.port !== undefined,\n\t);\n\tconst redirectUrl = callbackOptions.fixedRedirectUrl ?? callback.redirectUrl;\n\ttry {\n\t\tif (stored) {\n\t\t\tconst next: McpOAuthState = { ...stored };\n\t\t\t// Every sign-in gets a fresh `state` parameter.\n\t\t\tdelete next.oauthState;\n\t\t\t// A registered client cannot use another redirect URI, and its tokens belong to it. A Client ID\n\t\t\t// Metadata Document is not stored, so with one, a stored client was registered before and is replaced.\n\t\t\tconst keepClient =\n\t\t\t\tsettings.clientId ||\n\t\t\t\t(cimd ? !stored.clientInformation : registeredRedirectUrls(stored.clientInformation).includes(redirectUrl));\n\t\t\tif (!keepClient) {\n\t\t\t\tdelete next.clientInformation;\n\t\t\t\tdelete next.tokens;\n\t\t\t\tdelete next.tokensExpireAt;\n\t\t\t}\n\t\t\tawait store.save(next);\n\t\t}\n\n\t\tlet authorizationUrl: URL | undefined;\n\t\tconst provider = createProvider(serverUrl, store, settings, redirectUrl, (url) => {\n\t\t\tauthorizationUrl = url;\n\t\t});\n\t\tconst flow = {\n\t\t\tserverUrl,\n\t\t\tfetch: timedFetch(),\n\t\t\tsignal,\n\t\t\tresourceMetadataUrl: options.challenge?.resourceMetadataUrl,\n\t\t\tauthorizationServerMetadataUrl: settings.authServerMetadataUrl,\n\t\t\t// A server asking for more scope gets it on top of the configured scope and, since the challenge\n\t\t\t// may list only the missing scopes, on top of the scope granted so far.\n\t\t\tscope: mergeScopes(\n\t\t\t\tsettings.scope,\n\t\t\t\tstepUp ? stepUpScope(stored?.tokens?.scope, options.challenge?.scope) : options.challenge?.scope,\n\t\t\t),\n\t\t};\n\t\t// A refresh keeps the granted scope; a server asking for more needs the browser flow.\n\t\tconst skipRefresh = stepUp;\n\t\tif ((await authorizeMcp(provider, { ...flow, skipRefresh })) === \"AUTHORIZED\") return;\n\t\tif (!authorizationUrl) throw new Error(\"OAuth flow did not produce an authorization URL\");\n\n\t\tconst state = await provider.state();\n\t\t// The flow picks the redirect URI, which may be specific to the MCP server.\n\t\tconst authorizationRedirectUrl = new URL(authorizationUrl.searchParams.get(\"redirect_uri\") ?? redirectUrl);\n\t\toptions.prompt.showAuthorizationUrl(authorizationUrl);\n\t\tconst { code, iss } = await waitForAuthorizationResponse(\n\t\t\tcallback,\n\t\t\tstate,\n\t\t\tauthorizationRedirectUrl,\n\t\t\toptions.prompt,\n\t\t\tsignal,\n\t\t);\n\t\tawait authorizeMcp(provider, { ...flow, authorizationCode: code, iss });\n\t} catch (error) {\n\t\t// Aborted requests fail with the signal's reason; report them as the cancellation they are.\n\t\tif (signal?.aborted) throw new McpSignInCancelledError();\n\t\tthrow error;\n\t} finally {\n\t\tawait callback.close();\n\t}\n}\n"]}
@@ -28,8 +28,8 @@ export interface McpUi {
28
28
  * rebuilds the menu on every change, keeping the selected item.
29
29
  */
30
30
  menu(build: () => McpMenu, subscribe?: (listener: () => void) => () => void): Promise<string | undefined>;
31
- /** Show a message while an operation runs. */
32
- status(title: string, message: string): void;
31
+ /** Show a message while an operation runs. With `onCancel`, the cancel key calls it. */
32
+ status(title: string, message: string, onCancel?: () => void): void;
33
33
  /**
34
34
  * Show the authorization URL and wait for a pasted redirect URL. Resolves to undefined when
35
35
  * cancelled or when `signal` aborts (the browser reached the callback).
@@ -49,7 +49,7 @@ export declare class McpManagerView implements McpUi, Component, Focusable {
49
49
  set focused(value: boolean);
50
50
  private setContent;
51
51
  menu(build: () => McpMenu, subscribe?: (listener: () => void) => () => void): Promise<string | undefined>;
52
- status(title: string, message: string): void;
52
+ status(title: string, message: string, onCancel?: () => void): void;
53
53
  redirectUrl(title: string, authorizationUrl: string, signal: AbortSignal): Promise<string | undefined>;
54
54
  handleInput(data: string): void;
55
55
  render(width: number): string[];
@@ -1 +1 @@
1
- {"version":3,"file":"ui.d.ts","sourceRoot":"","sources":["../../../src/extensions/mcp/ui.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EACN,KAAK,SAAS,EAEd,KAAK,SAAS,EAEd,KAAK,UAAU,EAIf,KAAK,GAAG,EAGR,MAAM,wBAAwB,CAAC;AAChC,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,gCAAgC,CAAC;AAC9E,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAIpE,OAAO,EAAsB,KAAK,KAAK,EAAE,MAAM,wCAAwC,CAAC;AAExF,MAAM,WAAW,OAAO;IACvB,KAAK,EAAE,MAAM,CAAC;IACd,6BAA6B;IAC7B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,kDAAkD;IAClD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,UAAU,EAAE,CAAC;IACpB,qCAAqC;IACrC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,sDAAsD;IACtD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,mDAAmD;IACnD,YAAY,EAAE,MAAM,CAAC;IACrB,kDAAkD;IAClD,WAAW,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,KAAK;IACrB;;;OAGG;IACH,IAAI,CAAC,KAAK,EAAE,MAAM,OAAO,EAAE,SAAS,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,IAAI,KAAK,MAAM,IAAI,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAAC;IAC1G,8CAA8C;IAC9C,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7C;;;OAGG;IACH,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,gBAAgB,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAAC;CACvG;AAiBD,qBAAa,cAAe,YAAW,KAAK,EAAE,SAAS,EAAE,SAAS;IACjE,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAM;IAC1B,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAQ;IAC9B,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAqB;IACjD,OAAO,CAAC,OAAO,CAAY;IAC3B,OAAO,CAAC,YAAY,CAAuC;IAC3D,OAAO,CAAC,WAAW,CAAwB;IAC3C,OAAO,CAAC,QAAQ,CAAS;gBAEb,GAAG,EAAE,GAAG,EAAE,KAAK,EAAE,KAAK,EAAE,WAAW,EAAE,kBAAkB;IAOnE,IAAI,OAAO,IAAI,OAAO,CAErB;IAED,IAAI,OAAO,CAAC,KAAK,EAAE,OAAO,EAGzB;IAED,OAAO,CAAC,UAAU;IASlB,IAAI,CAAC,KAAK,EAAE,MAAM,OAAO,EAAE,SAAS,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,IAAI,KAAK,MAAM,IAAI,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC;IAoDzG,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI;IAI5C,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,gBAAgB,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC;IAyDtG,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAK/B,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE;IAM/B,UAAU,IAAI,IAAI;CAGlB;AAED,yDAAyD;AACzD,wBAAsB,cAAc,CACnC,GAAG,EAAE,uBAAuB,EAC5B,MAAM,EAAE,CAAC,EAAE,EAAE,KAAK,KAAK,OAAO,CAAC,IAAI,CAAC,GAClC,OAAO,CAAC,IAAI,CAAC,CAYf"}
1
+ {"version":3,"file":"ui.d.ts","sourceRoot":"","sources":["../../../src/extensions/mcp/ui.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EACN,KAAK,SAAS,EAEd,KAAK,SAAS,EAEd,KAAK,UAAU,EAIf,KAAK,GAAG,EAGR,MAAM,wBAAwB,CAAC;AAChC,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,gCAAgC,CAAC;AAC9E,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAIpE,OAAO,EAAsB,KAAK,KAAK,EAAE,MAAM,wCAAwC,CAAC;AAExF,MAAM,WAAW,OAAO;IACvB,KAAK,EAAE,MAAM,CAAC;IACd,6BAA6B;IAC7B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,kDAAkD;IAClD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,UAAU,EAAE,CAAC;IACpB,qCAAqC;IACrC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,sDAAsD;IACtD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,mDAAmD;IACnD,YAAY,EAAE,MAAM,CAAC;IACrB,kDAAkD;IAClD,WAAW,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,KAAK;IACrB;;;OAGG;IACH,IAAI,CAAC,KAAK,EAAE,MAAM,OAAO,EAAE,SAAS,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,IAAI,KAAK,MAAM,IAAI,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAAC;IAC1G,wFAAwF;IACxF,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,IAAI,GAAG,IAAI,CAAC;IACpE;;;OAGG;IACH,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,gBAAgB,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAAC;CACvG;AAiBD,qBAAa,cAAe,YAAW,KAAK,EAAE,SAAS,EAAE,SAAS;IACjE,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAM;IAC1B,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAQ;IAC9B,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAqB;IACjD,OAAO,CAAC,OAAO,CAAY;IAC3B,OAAO,CAAC,YAAY,CAAuC;IAC3D,OAAO,CAAC,WAAW,CAAwB;IAC3C,OAAO,CAAC,QAAQ,CAAS;gBAEb,GAAG,EAAE,GAAG,EAAE,KAAK,EAAE,KAAK,EAAE,WAAW,EAAE,kBAAkB;IAOnE,IAAI,OAAO,IAAI,OAAO,CAErB;IAED,IAAI,OAAO,CAAC,KAAK,EAAE,OAAO,EAGzB;IAED,OAAO,CAAC,UAAU;IASlB,IAAI,CAAC,KAAK,EAAE,MAAM,OAAO,EAAE,SAAS,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,IAAI,KAAK,MAAM,IAAI,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC;IAoDzG,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,IAAI,GAAG,IAAI;IAWnE,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,gBAAgB,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC;IAyDtG,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAK/B,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE;IAM/B,UAAU,IAAI,IAAI;CAGlB;AAED,yDAAyD;AACzD,wBAAsB,cAAc,CACnC,GAAG,EAAE,uBAAuB,EAC5B,MAAM,EAAE,CAAC,EAAE,EAAE,KAAK,KAAK,OAAO,CAAC,IAAI,CAAC,GAClC,OAAO,CAAC,IAAI,CAAC,CAYf"}
@@ -97,8 +97,16 @@ export class McpManagerView {
97
97
  });
98
98
  });
99
99
  }
100
- status(title, message) {
101
- this.setContent(frame(this.theme, title, [new Spacer(1), new Text(this.theme.fg("muted", message), 1, 0)]));
100
+ status(title, message, onCancel) {
101
+ const body = [new Spacer(1), new Text(this.theme.fg("muted", message), 1, 0)];
102
+ if (!onCancel) {
103
+ this.setContent(frame(this.theme, title, body));
104
+ return;
105
+ }
106
+ this.setContent(frame(this.theme, title, body, keyHint("tui.select.cancel", "cancel")), (data) => {
107
+ if (this.keybindings.matches(data, "tui.select.cancel"))
108
+ onCancel();
109
+ });
102
110
  }
103
111
  redirectUrl(title, authorizationUrl, signal) {
104
112
  return new Promise((resolve) => {
@@ -1 +1 @@
1
- {"version":3,"file":"ui.js","sourceRoot":"","sources":["../../../src/extensions/mcp/ui.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The `/mcp` manager view: menus that rebuild while servers connect, a read-only status screen, and\n * the sign-in screen that accepts a pasted redirect URL.\n */\n\nimport {\n\ttype Component,\n\tContainer,\n\ttype Focusable,\n\tInput,\n\ttype SelectItem,\n\tSelectList,\n\tSpacer,\n\tText,\n\ttype TUI,\n\ttruncateToWidth,\n\tvisibleWidth,\n} from \"@earendil-works/pi-tui\";\nimport type { ExtensionCommandContext } from \"../../core/extensions/types.ts\";\nimport type { KeybindingsManager } from \"../../core/keybindings.ts\";\nimport { AuthUrlComponent } from \"../../modes/interactive/components/auth-url.ts\";\nimport { DynamicBorder } from \"../../modes/interactive/components/dynamic-border.ts\";\nimport { keyHint } from \"../../modes/interactive/components/keybinding-hints.ts\";\nimport { getSelectListTheme, type Theme } from \"../../modes/interactive/theme/theme.ts\";\n\nexport interface McpMenu {\n\ttitle: string;\n\t/** Shown below the title. */\n\tdetails?: string;\n\t/** Shown below the details in the error color. */\n\terror?: string;\n\titems: SelectItem[];\n\t/** Shown when there are no items. */\n\tempty?: string;\n\t/** Value of the item selected when the menu opens. */\n\tselected?: string;\n\t/** What the confirm key does, for the key hint. */\n\tconfirmLabel: string;\n\t/** What the cancel key does, for the key hint. */\n\tcancelLabel: string;\n}\n\nexport interface McpUi {\n\t/**\n\t * Show a menu and resolve to the chosen item's value, or undefined when cancelled. `subscribe`\n\t * rebuilds the menu on every change, keeping the selected item.\n\t */\n\tmenu(build: () => McpMenu, subscribe?: (listener: () => void) => () => void): Promise<string | undefined>;\n\t/** Show a message while an operation runs. */\n\tstatus(title: string, message: string): void;\n\t/**\n\t * Show the authorization URL and wait for a pasted redirect URL. Resolves to undefined when\n\t * cancelled or when `signal` aborts (the browser reached the callback).\n\t */\n\tredirectUrl(title: string, authorizationUrl: string, signal: AbortSignal): Promise<string | undefined>;\n}\n\nfunction frame(theme: Theme, title: string, body: Component[], footer?: string): Container {\n\tconst container = new Container();\n\tcontainer.addChild(new DynamicBorder((text: string) => theme.fg(\"accent\", text)));\n\tcontainer.addChild(new Text(theme.fg(\"accent\", theme.bold(title)), 1, 0));\n\tfor (const child of body) container.addChild(child);\n\tif (footer) {\n\t\tcontainer.addChild(new Spacer(1));\n\t\tcontainer.addChild(new Text(theme.fg(\"dim\", footer), 1, 0));\n\t}\n\tcontainer.addChild(new DynamicBorder((text: string) => theme.fg(\"accent\", text)));\n\treturn container;\n}\n\nconst MAX_VISIBLE_ITEMS = 12;\n\nexport class McpManagerView implements McpUi, Component, Focusable {\n\tprivate readonly tui: TUI;\n\tprivate readonly theme: Theme;\n\tprivate readonly keybindings: KeybindingsManager;\n\tprivate content: Container;\n\tprivate inputHandler: ((data: string) => void) | undefined;\n\tprivate inputTarget: Focusable | undefined;\n\tprivate _focused = false;\n\n\tconstructor(tui: TUI, theme: Theme, keybindings: KeybindingsManager) {\n\t\tthis.tui = tui;\n\t\tthis.theme = theme;\n\t\tthis.keybindings = keybindings;\n\t\tthis.content = frame(theme, \"MCP servers\", [new Text(theme.fg(\"muted\", \"Loading…\"), 1, 1)]);\n\t}\n\n\tget focused(): boolean {\n\t\treturn this._focused;\n\t}\n\n\tset focused(value: boolean) {\n\t\tthis._focused = value;\n\t\tif (this.inputTarget) this.inputTarget.focused = value;\n\t}\n\n\tprivate setContent(content: Container, inputHandler?: (data: string) => void, inputTarget?: Focusable): void {\n\t\tif (this.inputTarget) this.inputTarget.focused = false;\n\t\tthis.content = content;\n\t\tthis.inputHandler = inputHandler;\n\t\tthis.inputTarget = inputTarget;\n\t\tif (this.inputTarget) this.inputTarget.focused = this._focused;\n\t\tthis.tui.requestRender();\n\t}\n\n\tmenu(build: () => McpMenu, subscribe?: (listener: () => void) => () => void): Promise<string | undefined> {\n\t\treturn new Promise((resolve) => {\n\t\t\tlet unsubscribe: (() => void) | undefined;\n\t\t\tlet settled = false;\n\t\t\tconst finish = (value: string | undefined) => {\n\t\t\t\tif (settled) return;\n\t\t\t\tsettled = true;\n\t\t\t\tunsubscribe?.();\n\t\t\t\tresolve(value);\n\t\t\t};\n\t\t\tlet selected: string | undefined;\n\t\t\tconst render = () => {\n\t\t\t\tconst menu = build();\n\t\t\t\tconst wanted = selected ?? menu.selected;\n\t\t\t\tconst body: Component[] = [];\n\t\t\t\tif (menu.details) body.push(new Text(this.theme.fg(\"muted\", menu.details), 1, 0));\n\t\t\t\tif (menu.error) body.push(new Text(this.theme.fg(\"error\", menu.error), 1, 0));\n\t\t\t\tbody.push(new Spacer(1));\n\t\t\t\tconst footer = `${keyHint(\"tui.select.confirm\", menu.confirmLabel)} • ${keyHint(\"tui.select.cancel\", menu.cancelLabel)}`;\n\t\t\t\tif (menu.items.length === 0) {\n\t\t\t\t\tbody.push(new Text(this.theme.fg(\"muted\", menu.empty ?? \"Nothing to show.\"), 1, 0));\n\t\t\t\t\tthis.setContent(\n\t\t\t\t\t\tframe(this.theme, menu.title, body, keyHint(\"tui.select.cancel\", menu.cancelLabel)),\n\t\t\t\t\t\t(data) => {\n\t\t\t\t\t\t\tif (this.keybindings.matches(data, \"tui.select.cancel\")) finish(undefined);\n\t\t\t\t\t\t},\n\t\t\t\t\t);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tconst list = new SelectList(\n\t\t\t\t\tmenu.items,\n\t\t\t\t\tMath.min(menu.items.length, MAX_VISIBLE_ITEMS),\n\t\t\t\t\tgetSelectListTheme(),\n\t\t\t\t);\n\t\t\t\tconst index = menu.items.findIndex((item) => item.value === wanted);\n\t\t\t\tif (index !== -1) list.setSelectedIndex(index);\n\t\t\t\tselected = list.getSelectedItem()?.value;\n\t\t\t\tlist.onSelectionChange = (item) => {\n\t\t\t\t\tselected = item.value;\n\t\t\t\t};\n\t\t\t\tlist.onSelect = (item) => finish(item.value);\n\t\t\t\tlist.onCancel = () => finish(undefined);\n\t\t\t\tbody.push(list);\n\t\t\t\tthis.setContent(frame(this.theme, menu.title, body, footer), (data) => list.handleInput(data));\n\t\t\t};\n\t\t\trender();\n\t\t\tunsubscribe = subscribe?.(() => {\n\t\t\t\tif (!settled) render();\n\t\t\t});\n\t\t});\n\t}\n\n\tstatus(title: string, message: string): void {\n\t\tthis.setContent(frame(this.theme, title, [new Spacer(1), new Text(this.theme.fg(\"muted\", message), 1, 0)]));\n\t}\n\n\tredirectUrl(title: string, authorizationUrl: string, signal: AbortSignal): Promise<string | undefined> {\n\t\treturn new Promise((resolve) => {\n\t\t\tlet settled = false;\n\t\t\tconst finish = (value: string | undefined) => {\n\t\t\t\tif (settled) return;\n\t\t\t\tsettled = true;\n\t\t\t\tsignal.removeEventListener(\"abort\", onAbort);\n\t\t\t\tresolve(value);\n\t\t\t};\n\t\t\tconst onAbort = () => finish(undefined);\n\t\t\tif (signal.aborted) {\n\t\t\t\tfinish(undefined);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tsignal.addEventListener(\"abort\", onAbort, { once: true });\n\t\t\tconst input = new Input();\n\t\t\tconst link = new AuthUrlComponent(this.tui, authorizationUrl);\n\t\t\tconst body: Component[] = [\n\t\t\t\tnew Spacer(1),\n\t\t\t\tnew Text(this.theme.fg(\"muted\", \"Approve access in your browser. If it did not open, visit:\"), 1, 0),\n\t\t\t\tlink,\n\t\t\t\tnew Spacer(1),\n\t\t\t\tnew Text(\n\t\t\t\t\tthis.theme.fg(\"muted\", \"If the browser runs on another machine, paste the URL it was redirected to:\"),\n\t\t\t\t\t1,\n\t\t\t\t\t0,\n\t\t\t\t),\n\t\t\t\tinput,\n\t\t\t];\n\t\t\tthis.setContent(\n\t\t\t\tframe(\n\t\t\t\t\tthis.theme,\n\t\t\t\t\ttitle,\n\t\t\t\t\tbody,\n\t\t\t\t\t`${keyHint(\"tui.select.confirm\", \"submit\")} • ${keyHint(\"tui.select.cancel\", \"cancel\")}`,\n\t\t\t\t),\n\t\t\t\t(data) => {\n\t\t\t\t\tif (this.keybindings.matches(data, \"tui.select.confirm\")) {\n\t\t\t\t\t\tconst value = input.getValue().trim();\n\t\t\t\t\t\tif (value) finish(value);\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tif (this.keybindings.matches(data, \"tui.select.cancel\")) {\n\t\t\t\t\t\tfinish(undefined);\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tif (this.keybindings.matches(data, \"app.message.copy\")) {\n\t\t\t\t\t\tvoid link.copy();\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tinput.handleInput(data);\n\t\t\t\t},\n\t\t\t\tinput,\n\t\t\t);\n\t\t});\n\t}\n\n\thandleInput(data: string): void {\n\t\tthis.inputHandler?.(data);\n\t\tthis.tui.requestRender();\n\t}\n\n\trender(width: number): string[] {\n\t\treturn this.content\n\t\t\t.render(width)\n\t\t\t.map((line) => (visibleWidth(line) > width ? truncateToWidth(line, width, \"\") : line));\n\t}\n\n\tinvalidate(): void {\n\t\tthis.content.invalidate();\n\t}\n}\n\n/** Run `manage` in the manager view until it returns. */\nexport async function showMcpManager(\n\tctx: ExtensionCommandContext,\n\tmanage: (ui: McpUi) => Promise<void>,\n): Promise<void> {\n\tawait ctx.ui.custom<void>((tui, theme, keybindings, done) => {\n\t\tconst view = new McpManagerView(tui, theme, keybindings);\n\t\tvoid manage(view).then(\n\t\t\t() => done(),\n\t\t\t(error: unknown) => {\n\t\t\t\tctx.ui.notify(error instanceof Error ? error.message : String(error), \"error\");\n\t\t\t\tdone();\n\t\t\t},\n\t\t);\n\t\treturn view;\n\t});\n}\n"]}
1
+ {"version":3,"file":"ui.js","sourceRoot":"","sources":["../../../src/extensions/mcp/ui.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The `/mcp` manager view: menus that rebuild while servers connect, a read-only status screen, and\n * the sign-in screen that accepts a pasted redirect URL.\n */\n\nimport {\n\ttype Component,\n\tContainer,\n\ttype Focusable,\n\tInput,\n\ttype SelectItem,\n\tSelectList,\n\tSpacer,\n\tText,\n\ttype TUI,\n\ttruncateToWidth,\n\tvisibleWidth,\n} from \"@earendil-works/pi-tui\";\nimport type { ExtensionCommandContext } from \"../../core/extensions/types.ts\";\nimport type { KeybindingsManager } from \"../../core/keybindings.ts\";\nimport { AuthUrlComponent } from \"../../modes/interactive/components/auth-url.ts\";\nimport { DynamicBorder } from \"../../modes/interactive/components/dynamic-border.ts\";\nimport { keyHint } from \"../../modes/interactive/components/keybinding-hints.ts\";\nimport { getSelectListTheme, type Theme } from \"../../modes/interactive/theme/theme.ts\";\n\nexport interface McpMenu {\n\ttitle: string;\n\t/** Shown below the title. */\n\tdetails?: string;\n\t/** Shown below the details in the error color. */\n\terror?: string;\n\titems: SelectItem[];\n\t/** Shown when there are no items. */\n\tempty?: string;\n\t/** Value of the item selected when the menu opens. */\n\tselected?: string;\n\t/** What the confirm key does, for the key hint. */\n\tconfirmLabel: string;\n\t/** What the cancel key does, for the key hint. */\n\tcancelLabel: string;\n}\n\nexport interface McpUi {\n\t/**\n\t * Show a menu and resolve to the chosen item's value, or undefined when cancelled. `subscribe`\n\t * rebuilds the menu on every change, keeping the selected item.\n\t */\n\tmenu(build: () => McpMenu, subscribe?: (listener: () => void) => () => void): Promise<string | undefined>;\n\t/** Show a message while an operation runs. With `onCancel`, the cancel key calls it. */\n\tstatus(title: string, message: string, onCancel?: () => void): void;\n\t/**\n\t * Show the authorization URL and wait for a pasted redirect URL. Resolves to undefined when\n\t * cancelled or when `signal` aborts (the browser reached the callback).\n\t */\n\tredirectUrl(title: string, authorizationUrl: string, signal: AbortSignal): Promise<string | undefined>;\n}\n\nfunction frame(theme: Theme, title: string, body: Component[], footer?: string): Container {\n\tconst container = new Container();\n\tcontainer.addChild(new DynamicBorder((text: string) => theme.fg(\"accent\", text)));\n\tcontainer.addChild(new Text(theme.fg(\"accent\", theme.bold(title)), 1, 0));\n\tfor (const child of body) container.addChild(child);\n\tif (footer) {\n\t\tcontainer.addChild(new Spacer(1));\n\t\tcontainer.addChild(new Text(theme.fg(\"dim\", footer), 1, 0));\n\t}\n\tcontainer.addChild(new DynamicBorder((text: string) => theme.fg(\"accent\", text)));\n\treturn container;\n}\n\nconst MAX_VISIBLE_ITEMS = 12;\n\nexport class McpManagerView implements McpUi, Component, Focusable {\n\tprivate readonly tui: TUI;\n\tprivate readonly theme: Theme;\n\tprivate readonly keybindings: KeybindingsManager;\n\tprivate content: Container;\n\tprivate inputHandler: ((data: string) => void) | undefined;\n\tprivate inputTarget: Focusable | undefined;\n\tprivate _focused = false;\n\n\tconstructor(tui: TUI, theme: Theme, keybindings: KeybindingsManager) {\n\t\tthis.tui = tui;\n\t\tthis.theme = theme;\n\t\tthis.keybindings = keybindings;\n\t\tthis.content = frame(theme, \"MCP servers\", [new Text(theme.fg(\"muted\", \"Loading…\"), 1, 1)]);\n\t}\n\n\tget focused(): boolean {\n\t\treturn this._focused;\n\t}\n\n\tset focused(value: boolean) {\n\t\tthis._focused = value;\n\t\tif (this.inputTarget) this.inputTarget.focused = value;\n\t}\n\n\tprivate setContent(content: Container, inputHandler?: (data: string) => void, inputTarget?: Focusable): void {\n\t\tif (this.inputTarget) this.inputTarget.focused = false;\n\t\tthis.content = content;\n\t\tthis.inputHandler = inputHandler;\n\t\tthis.inputTarget = inputTarget;\n\t\tif (this.inputTarget) this.inputTarget.focused = this._focused;\n\t\tthis.tui.requestRender();\n\t}\n\n\tmenu(build: () => McpMenu, subscribe?: (listener: () => void) => () => void): Promise<string | undefined> {\n\t\treturn new Promise((resolve) => {\n\t\t\tlet unsubscribe: (() => void) | undefined;\n\t\t\tlet settled = false;\n\t\t\tconst finish = (value: string | undefined) => {\n\t\t\t\tif (settled) return;\n\t\t\t\tsettled = true;\n\t\t\t\tunsubscribe?.();\n\t\t\t\tresolve(value);\n\t\t\t};\n\t\t\tlet selected: string | undefined;\n\t\t\tconst render = () => {\n\t\t\t\tconst menu = build();\n\t\t\t\tconst wanted = selected ?? menu.selected;\n\t\t\t\tconst body: Component[] = [];\n\t\t\t\tif (menu.details) body.push(new Text(this.theme.fg(\"muted\", menu.details), 1, 0));\n\t\t\t\tif (menu.error) body.push(new Text(this.theme.fg(\"error\", menu.error), 1, 0));\n\t\t\t\tbody.push(new Spacer(1));\n\t\t\t\tconst footer = `${keyHint(\"tui.select.confirm\", menu.confirmLabel)} • ${keyHint(\"tui.select.cancel\", menu.cancelLabel)}`;\n\t\t\t\tif (menu.items.length === 0) {\n\t\t\t\t\tbody.push(new Text(this.theme.fg(\"muted\", menu.empty ?? \"Nothing to show.\"), 1, 0));\n\t\t\t\t\tthis.setContent(\n\t\t\t\t\t\tframe(this.theme, menu.title, body, keyHint(\"tui.select.cancel\", menu.cancelLabel)),\n\t\t\t\t\t\t(data) => {\n\t\t\t\t\t\t\tif (this.keybindings.matches(data, \"tui.select.cancel\")) finish(undefined);\n\t\t\t\t\t\t},\n\t\t\t\t\t);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tconst list = new SelectList(\n\t\t\t\t\tmenu.items,\n\t\t\t\t\tMath.min(menu.items.length, MAX_VISIBLE_ITEMS),\n\t\t\t\t\tgetSelectListTheme(),\n\t\t\t\t);\n\t\t\t\tconst index = menu.items.findIndex((item) => item.value === wanted);\n\t\t\t\tif (index !== -1) list.setSelectedIndex(index);\n\t\t\t\tselected = list.getSelectedItem()?.value;\n\t\t\t\tlist.onSelectionChange = (item) => {\n\t\t\t\t\tselected = item.value;\n\t\t\t\t};\n\t\t\t\tlist.onSelect = (item) => finish(item.value);\n\t\t\t\tlist.onCancel = () => finish(undefined);\n\t\t\t\tbody.push(list);\n\t\t\t\tthis.setContent(frame(this.theme, menu.title, body, footer), (data) => list.handleInput(data));\n\t\t\t};\n\t\t\trender();\n\t\t\tunsubscribe = subscribe?.(() => {\n\t\t\t\tif (!settled) render();\n\t\t\t});\n\t\t});\n\t}\n\n\tstatus(title: string, message: string, onCancel?: () => void): void {\n\t\tconst body = [new Spacer(1), new Text(this.theme.fg(\"muted\", message), 1, 0)];\n\t\tif (!onCancel) {\n\t\t\tthis.setContent(frame(this.theme, title, body));\n\t\t\treturn;\n\t\t}\n\t\tthis.setContent(frame(this.theme, title, body, keyHint(\"tui.select.cancel\", \"cancel\")), (data) => {\n\t\t\tif (this.keybindings.matches(data, \"tui.select.cancel\")) onCancel();\n\t\t});\n\t}\n\n\tredirectUrl(title: string, authorizationUrl: string, signal: AbortSignal): Promise<string | undefined> {\n\t\treturn new Promise((resolve) => {\n\t\t\tlet settled = false;\n\t\t\tconst finish = (value: string | undefined) => {\n\t\t\t\tif (settled) return;\n\t\t\t\tsettled = true;\n\t\t\t\tsignal.removeEventListener(\"abort\", onAbort);\n\t\t\t\tresolve(value);\n\t\t\t};\n\t\t\tconst onAbort = () => finish(undefined);\n\t\t\tif (signal.aborted) {\n\t\t\t\tfinish(undefined);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tsignal.addEventListener(\"abort\", onAbort, { once: true });\n\t\t\tconst input = new Input();\n\t\t\tconst link = new AuthUrlComponent(this.tui, authorizationUrl);\n\t\t\tconst body: Component[] = [\n\t\t\t\tnew Spacer(1),\n\t\t\t\tnew Text(this.theme.fg(\"muted\", \"Approve access in your browser. If it did not open, visit:\"), 1, 0),\n\t\t\t\tlink,\n\t\t\t\tnew Spacer(1),\n\t\t\t\tnew Text(\n\t\t\t\t\tthis.theme.fg(\"muted\", \"If the browser runs on another machine, paste the URL it was redirected to:\"),\n\t\t\t\t\t1,\n\t\t\t\t\t0,\n\t\t\t\t),\n\t\t\t\tinput,\n\t\t\t];\n\t\t\tthis.setContent(\n\t\t\t\tframe(\n\t\t\t\t\tthis.theme,\n\t\t\t\t\ttitle,\n\t\t\t\t\tbody,\n\t\t\t\t\t`${keyHint(\"tui.select.confirm\", \"submit\")} • ${keyHint(\"tui.select.cancel\", \"cancel\")}`,\n\t\t\t\t),\n\t\t\t\t(data) => {\n\t\t\t\t\tif (this.keybindings.matches(data, \"tui.select.confirm\")) {\n\t\t\t\t\t\tconst value = input.getValue().trim();\n\t\t\t\t\t\tif (value) finish(value);\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tif (this.keybindings.matches(data, \"tui.select.cancel\")) {\n\t\t\t\t\t\tfinish(undefined);\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tif (this.keybindings.matches(data, \"app.message.copy\")) {\n\t\t\t\t\t\tvoid link.copy();\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tinput.handleInput(data);\n\t\t\t\t},\n\t\t\t\tinput,\n\t\t\t);\n\t\t});\n\t}\n\n\thandleInput(data: string): void {\n\t\tthis.inputHandler?.(data);\n\t\tthis.tui.requestRender();\n\t}\n\n\trender(width: number): string[] {\n\t\treturn this.content\n\t\t\t.render(width)\n\t\t\t.map((line) => (visibleWidth(line) > width ? truncateToWidth(line, width, \"\") : line));\n\t}\n\n\tinvalidate(): void {\n\t\tthis.content.invalidate();\n\t}\n}\n\n/** Run `manage` in the manager view until it returns. */\nexport async function showMcpManager(\n\tctx: ExtensionCommandContext,\n\tmanage: (ui: McpUi) => Promise<void>,\n): Promise<void> {\n\tawait ctx.ui.custom<void>((tui, theme, keybindings, done) => {\n\t\tconst view = new McpManagerView(tui, theme, keybindings);\n\t\tvoid manage(view).then(\n\t\t\t() => done(),\n\t\t\t(error: unknown) => {\n\t\t\t\tctx.ui.notify(error instanceof Error ? error.message : String(error), \"error\");\n\t\t\t\tdone();\n\t\t\t},\n\t\t);\n\t\treturn view;\n\t});\n}\n"]}
@@ -0,0 +1,29 @@
1
+ import { Workspace } from "./workspace.ts";
2
+ export interface CodeLocation {
3
+ /** Relative to the session's working directory. */
4
+ path: string;
5
+ ranges: Array<[number, number]>;
6
+ }
7
+ export interface CodeSearchOptions {
8
+ apiKey: string;
9
+ query: string;
10
+ /** Directory to search; already confined to the working directory by the caller. */
11
+ root: string;
12
+ cwd: string;
13
+ signal?: AbortSignal;
14
+ onProgress?: (message: string) => void;
15
+ /** Test seam for the local `rg` backend. */
16
+ workspace?: Workspace;
17
+ }
18
+ export declare function codeSearch(options: CodeSearchOptions): Promise<CodeLocation[]>;
19
+ export interface ToolCall {
20
+ thinking: string;
21
+ name: string;
22
+ args: Record<string, unknown>;
23
+ }
24
+ /** `thinking[TOOL_CALLS]name[ARGS]{json}`. The JSON is the first balanced object, repaired if needed. */
25
+ export declare function parseToolCall(text: string): ToolCall | null;
26
+ /** The files of an `<ANSWER>`, each re-checked against the workspace; anything outside it is dropped. */
27
+ export declare function parseAnswer(xml: string, workspace: Workspace, cwd: string): CodeLocation[];
28
+ export declare function formatLocations(locations: CodeLocation[]): string;
29
+ //# sourceMappingURL=code-search.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"code-search.d.ts","sourceRoot":"","sources":["../../../src/extensions/search/code-search.ts"],"names":[],"mappings":"AAmBA,OAAO,EAA8B,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAEvE,MAAM,WAAW,YAAY;IAC5B,mDAAmD;IACnD,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,KAAK,CAAC,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;CAChC;AAED,MAAM,WAAW,iBAAiB;IACjC,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,oFAAoF;IACpF,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,UAAU,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IACvC,4CAA4C;IAC5C,SAAS,CAAC,EAAE,SAAS,CAAC;CACtB;AAED,wBAAsB,UAAU,CAAC,OAAO,EAAE,iBAAiB,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC,CAgDpF;AAED,MAAM,WAAW,QAAQ;IACxB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAC9B;AAED,yGAAyG;AACzG,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,QAAQ,GAAG,IAAI,CAa3D;AAyBD,yGAAyG;AACzG,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,EAAE,SAAS,EAAE,SAAS,EAAE,GAAG,EAAE,MAAM,GAAG,YAAY,EAAE,CAc1F;AAED,wBAAgB,eAAe,CAAC,SAAS,EAAE,YAAY,EAAE,GAAG,MAAM,CAOjE"}
@@ -0,0 +1,120 @@
1
+ /**
2
+ * code_search: Devin's SWE-grep model plans, this process executes.
3
+ *
4
+ * Each round sends the transcript to Devin, which replies with one tool call: `restricted_exec`
5
+ * (up to eight rg/readfile/tree commands, run locally by `Workspace`) or `answer` (an `<ANSWER>`
6
+ * document of files and line ranges). After `MAX_TURNS` rounds the answer is demanded.
7
+ */
8
+ import { randomUUID } from "node:crypto";
9
+ import { relative } from "node:path";
10
+ import { chat } from "./devin.js";
11
+ import { FORCE_ANSWER, MAX_COMMANDS, MAX_RESULTS, MAX_TURNS, RETRY_TOOL_CALL, SYSTEM_PROMPT, TOOL_DEFINITIONS, } from "./prompt.js";
12
+ import { VIRTUAL_ROOT, Workspace } from "./workspace.js";
13
+ export async function codeSearch(options) {
14
+ const { apiKey, query, signal } = options;
15
+ const progress = options.onProgress ?? (() => { });
16
+ const workspace = options.workspace ?? new Workspace(options.root);
17
+ const map = workspace.repoMap();
18
+ const messages = [
19
+ { role: "system", content: SYSTEM_PROMPT },
20
+ {
21
+ role: "user",
22
+ content: `Problem Statement: ${query}\n\nRepo Map (tree -L ${map.depth} ${VIRTUAL_ROOT}):\n\`\`\`text\n${map.tree}\n\`\`\``,
23
+ },
24
+ ];
25
+ for (let turn = 1; turn <= MAX_TURNS + 1; turn++) {
26
+ if (turn === MAX_TURNS + 1)
27
+ messages.push({ role: "user", content: FORCE_ANSWER });
28
+ progress(turn > MAX_TURNS ? "Collecting the answer…" : `Planning (round ${turn}/${MAX_TURNS})…`);
29
+ const reply = await chat(apiKey, messages, TOOL_DEFINITIONS, signal);
30
+ const call = parseToolCall(reply);
31
+ if (call?.name === "answer") {
32
+ return parseAnswer(String(call.args.answer ?? ""), workspace, options.cwd);
33
+ }
34
+ if (call?.name !== "restricted_exec") {
35
+ messages.push({ role: "assistant", content: reply }, { role: "user", content: RETRY_TOOL_CALL });
36
+ continue;
37
+ }
38
+ const keys = Object.keys(call.args)
39
+ .filter((k) => /^command\d+$/.test(k))
40
+ .sort((a, b) => Number(a.slice(7)) - Number(b.slice(7)))
41
+ .slice(0, MAX_COMMANDS);
42
+ progress(`Running ${keys.length} ${keys.length === 1 ? "command" : "commands"} (round ${turn}/${MAX_TURNS})…`);
43
+ const results = await Promise.all(keys.map(async (k) => `<${k}_result>\n${await workspace.run(call.args[k], signal)}\n</${k}_result>`));
44
+ const id = randomUUID();
45
+ messages.push({
46
+ role: "assistant",
47
+ content: call.thinking,
48
+ call: { id, name: call.name, arguments: JSON.stringify(call.args) },
49
+ }, { role: "tool", content: results.join(""), callId: id });
50
+ }
51
+ return [];
52
+ }
53
+ /** `thinking[TOOL_CALLS]name[ARGS]{json}`. The JSON is the first balanced object, repaired if needed. */
54
+ export function parseToolCall(text) {
55
+ const m = text.match(/\[TOOL_CALLS\]\s*(\w+)\s*\[ARGS\]\s*(\{[\s\S]*)/);
56
+ if (!m)
57
+ return null;
58
+ // Repair before scanning: a stray key quote would otherwise end the scan inside a "string".
59
+ for (const candidate of [firstObject(m[2]), firstObject(repairKeys(m[2]))]) {
60
+ try {
61
+ const args = JSON.parse(candidate);
62
+ if (args && typeof args === "object" && !Array.isArray(args)) {
63
+ return { thinking: text.slice(0, m.index).trim(), name: m[1], args: args };
64
+ }
65
+ }
66
+ catch { }
67
+ }
68
+ return null;
69
+ }
70
+ function firstObject(text) {
71
+ let depth = 0;
72
+ let inString = false;
73
+ for (let i = 0; i < text.length; i++) {
74
+ const c = text[i];
75
+ if (inString) {
76
+ if (c === "\\")
77
+ i++;
78
+ else if (c === '"')
79
+ inString = false;
80
+ }
81
+ else if (c === '"')
82
+ inString = true;
83
+ else if (c === "{")
84
+ depth++;
85
+ else if (c === "}" && --depth === 0)
86
+ return text.slice(0, i + 1);
87
+ }
88
+ return text;
89
+ }
90
+ /** The planner sometimes drops a key's quotes, as in `,exclude":[]` or `{path:"…"}`. */
91
+ function repairKeys(json) {
92
+ return json.replace(/([{,]\s*)"?([A-Za-z_]\w*)"?\s*:/g, '$1"$2":');
93
+ }
94
+ const FILE_RE = /<file\s+path=(["'])(.+?)\1\s*>([\s\S]*?)<\/file>/g;
95
+ const RANGE_RE = /<range>\s*(\d+)\s*-\s*(\d+)\s*<\/range>/g;
96
+ /** The files of an `<ANSWER>`, each re-checked against the workspace; anything outside it is dropped. */
97
+ export function parseAnswer(xml, workspace, cwd) {
98
+ const locations = [];
99
+ for (const [, , path, body] of xml.matchAll(FILE_RE)) {
100
+ const real = workspace.toReal(path);
101
+ if (!real)
102
+ continue;
103
+ locations.push({
104
+ path: relative(cwd, real).replace(/\\/g, "/") || ".",
105
+ ranges: [...body.matchAll(RANGE_RE)]
106
+ .map(([, start, end]) => [Number(start), Number(end)])
107
+ .sort((a, b) => a[0] - b[0]),
108
+ });
109
+ if (locations.length === MAX_RESULTS)
110
+ break;
111
+ }
112
+ return locations;
113
+ }
114
+ export function formatLocations(locations) {
115
+ if (!locations.length)
116
+ return "No relevant code found. Try a narrower behavioral query, or grep for a known identifier.";
117
+ const lines = locations.map(({ path, ranges }) => `${path}${ranges.length ? ` (${ranges.map(([s, e]) => `L${s}-${e}`).join(", ")})` : ""}`);
118
+ return `${locations.length} candidate ${locations.length === 1 ? "location" : "locations"}; read them before relying on them:\n${lines.join("\n")}`;
119
+ }
120
+ //# sourceMappingURL=code-search.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"code-search.js","sourceRoot":"","sources":["../../../src/extensions/search/code-search.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * code_search: Devin's SWE-grep model plans, this process executes.\n *\n * Each round sends the transcript to Devin, which replies with one tool call: `restricted_exec`\n * (up to eight rg/readfile/tree commands, run locally by `Workspace`) or `answer` (an `<ANSWER>`\n * document of files and line ranges). After `MAX_TURNS` rounds the answer is demanded.\n */\nimport { randomUUID } from \"node:crypto\";\nimport { relative } from \"node:path\";\nimport { type ChatMessage, chat } from \"./devin.ts\";\nimport {\n\tFORCE_ANSWER,\n\tMAX_COMMANDS,\n\tMAX_RESULTS,\n\tMAX_TURNS,\n\tRETRY_TOOL_CALL,\n\tSYSTEM_PROMPT,\n\tTOOL_DEFINITIONS,\n} from \"./prompt.ts\";\nimport { type Command, VIRTUAL_ROOT, Workspace } from \"./workspace.ts\";\n\nexport interface CodeLocation {\n\t/** Relative to the session's working directory. */\n\tpath: string;\n\tranges: Array<[number, number]>;\n}\n\nexport interface CodeSearchOptions {\n\tapiKey: string;\n\tquery: string;\n\t/** Directory to search; already confined to the working directory by the caller. */\n\troot: string;\n\tcwd: string;\n\tsignal?: AbortSignal;\n\tonProgress?: (message: string) => void;\n\t/** Test seam for the local `rg` backend. */\n\tworkspace?: Workspace;\n}\n\nexport async function codeSearch(options: CodeSearchOptions): Promise<CodeLocation[]> {\n\tconst { apiKey, query, signal } = options;\n\tconst progress = options.onProgress ?? (() => {});\n\tconst workspace = options.workspace ?? new Workspace(options.root);\n\tconst map = workspace.repoMap();\n\tconst messages: ChatMessage[] = [\n\t\t{ role: \"system\", content: SYSTEM_PROMPT },\n\t\t{\n\t\t\trole: \"user\",\n\t\t\tcontent: `Problem Statement: ${query}\\n\\nRepo Map (tree -L ${map.depth} ${VIRTUAL_ROOT}):\\n\\`\\`\\`text\\n${map.tree}\\n\\`\\`\\``,\n\t\t},\n\t];\n\n\tfor (let turn = 1; turn <= MAX_TURNS + 1; turn++) {\n\t\tif (turn === MAX_TURNS + 1) messages.push({ role: \"user\", content: FORCE_ANSWER });\n\t\tprogress(turn > MAX_TURNS ? \"Collecting the answer…\" : `Planning (round ${turn}/${MAX_TURNS})…`);\n\t\tconst reply = await chat(apiKey, messages, TOOL_DEFINITIONS, signal);\n\t\tconst call = parseToolCall(reply);\n\n\t\tif (call?.name === \"answer\") {\n\t\t\treturn parseAnswer(String(call.args.answer ?? \"\"), workspace, options.cwd);\n\t\t}\n\t\tif (call?.name !== \"restricted_exec\") {\n\t\t\tmessages.push({ role: \"assistant\", content: reply }, { role: \"user\", content: RETRY_TOOL_CALL });\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst keys = Object.keys(call.args)\n\t\t\t.filter((k) => /^command\\d+$/.test(k))\n\t\t\t.sort((a, b) => Number(a.slice(7)) - Number(b.slice(7)))\n\t\t\t.slice(0, MAX_COMMANDS);\n\t\tprogress(`Running ${keys.length} ${keys.length === 1 ? \"command\" : \"commands\"} (round ${turn}/${MAX_TURNS})…`);\n\t\tconst results = await Promise.all(\n\t\t\tkeys.map(\n\t\t\t\tasync (k) => `<${k}_result>\\n${await workspace.run(call.args[k] as Command, signal)}\\n</${k}_result>`,\n\t\t\t),\n\t\t);\n\t\tconst id = randomUUID();\n\t\tmessages.push(\n\t\t\t{\n\t\t\t\trole: \"assistant\",\n\t\t\t\tcontent: call.thinking,\n\t\t\t\tcall: { id, name: call.name, arguments: JSON.stringify(call.args) },\n\t\t\t},\n\t\t\t{ role: \"tool\", content: results.join(\"\"), callId: id },\n\t\t);\n\t}\n\treturn [];\n}\n\nexport interface ToolCall {\n\tthinking: string;\n\tname: string;\n\targs: Record<string, unknown>;\n}\n\n/** `thinking[TOOL_CALLS]name[ARGS]{json}`. The JSON is the first balanced object, repaired if needed. */\nexport function parseToolCall(text: string): ToolCall | null {\n\tconst m = text.match(/\\[TOOL_CALLS\\]\\s*(\\w+)\\s*\\[ARGS\\]\\s*(\\{[\\s\\S]*)/);\n\tif (!m) return null;\n\t// Repair before scanning: a stray key quote would otherwise end the scan inside a \"string\".\n\tfor (const candidate of [firstObject(m[2]!), firstObject(repairKeys(m[2]!))]) {\n\t\ttry {\n\t\t\tconst args = JSON.parse(candidate) as unknown;\n\t\t\tif (args && typeof args === \"object\" && !Array.isArray(args)) {\n\t\t\t\treturn { thinking: text.slice(0, m.index).trim(), name: m[1]!, args: args as Record<string, unknown> };\n\t\t\t}\n\t\t} catch {}\n\t}\n\treturn null;\n}\n\nfunction firstObject(text: string): string {\n\tlet depth = 0;\n\tlet inString = false;\n\tfor (let i = 0; i < text.length; i++) {\n\t\tconst c = text[i];\n\t\tif (inString) {\n\t\t\tif (c === \"\\\\\") i++;\n\t\t\telse if (c === '\"') inString = false;\n\t\t} else if (c === '\"') inString = true;\n\t\telse if (c === \"{\") depth++;\n\t\telse if (c === \"}\" && --depth === 0) return text.slice(0, i + 1);\n\t}\n\treturn text;\n}\n\n/** The planner sometimes drops a key's quotes, as in `,exclude\":[]` or `{path:\"…\"}`. */\nfunction repairKeys(json: string): string {\n\treturn json.replace(/([{,]\\s*)\"?([A-Za-z_]\\w*)\"?\\s*:/g, '$1\"$2\":');\n}\n\nconst FILE_RE = /<file\\s+path=([\"'])(.+?)\\1\\s*>([\\s\\S]*?)<\\/file>/g;\nconst RANGE_RE = /<range>\\s*(\\d+)\\s*-\\s*(\\d+)\\s*<\\/range>/g;\n\n/** The files of an `<ANSWER>`, each re-checked against the workspace; anything outside it is dropped. */\nexport function parseAnswer(xml: string, workspace: Workspace, cwd: string): CodeLocation[] {\n\tconst locations: CodeLocation[] = [];\n\tfor (const [, , path, body] of xml.matchAll(FILE_RE)) {\n\t\tconst real = workspace.toReal(path);\n\t\tif (!real) continue;\n\t\tlocations.push({\n\t\t\tpath: relative(cwd, real).replace(/\\\\/g, \"/\") || \".\",\n\t\t\tranges: [...body!.matchAll(RANGE_RE)]\n\t\t\t\t.map(([, start, end]): [number, number] => [Number(start), Number(end)])\n\t\t\t\t.sort((a, b) => a[0] - b[0]),\n\t\t});\n\t\tif (locations.length === MAX_RESULTS) break;\n\t}\n\treturn locations;\n}\n\nexport function formatLocations(locations: CodeLocation[]): string {\n\tif (!locations.length)\n\t\treturn \"No relevant code found. Try a narrower behavioral query, or grep for a known identifier.\";\n\tconst lines = locations.map(\n\t\t({ path, ranges }) => `${path}${ranges.length ? ` (${ranges.map(([s, e]) => `L${s}-${e}`).join(\", \")})` : \"\"}`,\n\t);\n\treturn `${locations.length} candidate ${locations.length === 1 ? \"location\" : \"locations\"}; read them before relying on them:\\n${lines.join(\"\\n\")}`;\n}\n"]}