@herbertgao/pi-extensions 2026.9.17 → 2026.9.18

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 (266) hide show
  1. package/README.md +14 -15
  2. package/node_modules/@herbertgao/pi-bark/package.json +2 -2
  3. package/node_modules/@herbertgao/pi-cc-extensions/README.en.md +4 -1
  4. package/node_modules/@herbertgao/pi-cc-extensions/README.md +4 -1
  5. package/node_modules/@herbertgao/pi-cc-extensions/package.json +75 -75
  6. package/node_modules/@herbertgao/pi-subagents/CHANGELOG.md +6 -0
  7. package/node_modules/@herbertgao/pi-subagents/package.json +7 -7
  8. package/node_modules/@herbertgao/resume-from/package.json +4 -2
  9. package/node_modules/@herbertgao/sol-pi/package.json +6 -6
  10. package/node_modules/@juicesharp/rpiv-ask-user-question/package.json +99 -99
  11. package/node_modules/@pi-plugins/fast-mode/package.json +1 -1
  12. package/node_modules/@tifan/pi-handoff/package.json +1 -1
  13. package/node_modules/@tifan/pi-handoff/src/index.ts +56 -26
  14. package/node_modules/@tifan/pi-mermaid-open/package.json +1 -1
  15. package/node_modules/@tifan/pi-preferred-thinking/package.json +1 -1
  16. package/node_modules/@tifan/pi-recap/package.json +1 -1
  17. package/node_modules/@tifan/pi-rename/package.json +1 -1
  18. package/node_modules/pi-antigravity/package.json +2 -2
  19. package/node_modules/pi-lens/package.json +158 -158
  20. package/node_modules/pi-multi-account/package.json +2 -2
  21. package/node_modules/pi-typesafe/README.md +1 -1
  22. package/node_modules/pi-typesafe/dist/auth.d.ts +9 -5
  23. package/node_modules/pi-typesafe/dist/auth.js +7 -3
  24. package/node_modules/pi-typesafe/dist/backends.d.ts +33 -3
  25. package/node_modules/pi-typesafe/dist/backends.js +95 -1
  26. package/node_modules/pi-typesafe/dist/client.d.ts +5 -5
  27. package/node_modules/pi-typesafe/dist/client.js +12 -9
  28. package/node_modules/pi-typesafe/dist/credentials.d.ts +14 -8
  29. package/node_modules/pi-typesafe/dist/credentials.js +10 -7
  30. package/node_modules/pi-typesafe/dist/errors.d.ts +2 -2
  31. package/node_modules/pi-typesafe/dist/index.d.ts +2 -2
  32. package/node_modules/pi-typesafe/dist/index.js +1 -1
  33. package/node_modules/pi-typesafe/dist/login.d.ts +2 -2
  34. package/node_modules/pi-typesafe/dist/login.js +2 -2
  35. package/node_modules/pi-typesafe/package.json +5 -5
  36. package/node_modules/pi-web-access/package.json +2 -2
  37. package/node_modules/remote-pi/package.json +5 -1
  38. package/package.json +16 -28
  39. package/node_modules/pi-mcp-adapter/CHANGELOG.md +0 -1013
  40. package/node_modules/pi-mcp-adapter/LICENSE +0 -21
  41. package/node_modules/pi-mcp-adapter/OAUTH.md +0 -369
  42. package/node_modules/pi-mcp-adapter/README.md +0 -1021
  43. package/node_modules/pi-mcp-adapter/abort.ts +0 -34
  44. package/node_modules/pi-mcp-adapter/agent-dir.ts +0 -68
  45. package/node_modules/pi-mcp-adapter/agent-plugin-loader.ts +0 -416
  46. package/node_modules/pi-mcp-adapter/agent-plugin-provenance.ts +0 -36
  47. package/node_modules/pi-mcp-adapter/app-bridge.bundle.js +0 -67
  48. package/node_modules/pi-mcp-adapter/banner.png +0 -0
  49. package/node_modules/pi-mcp-adapter/bearer-command-resolver.ts +0 -193
  50. package/node_modules/pi-mcp-adapter/claude-plugin-loader.ts +0 -365
  51. package/node_modules/pi-mcp-adapter/cli.js +0 -454
  52. package/node_modules/pi-mcp-adapter/commands.ts +0 -861
  53. package/node_modules/pi-mcp-adapter/config.ts +0 -1565
  54. package/node_modules/pi-mcp-adapter/consent-manager.ts +0 -95
  55. package/node_modules/pi-mcp-adapter/direct-tool-surface.ts +0 -244
  56. package/node_modules/pi-mcp-adapter/direct-tools.ts +0 -428
  57. package/node_modules/pi-mcp-adapter/dist/abort.d.ts +0 -2
  58. package/node_modules/pi-mcp-adapter/dist/abort.js +0 -36
  59. package/node_modules/pi-mcp-adapter/dist/abort.js.map +0 -1
  60. package/node_modules/pi-mcp-adapter/dist/agent-dir.d.ts +0 -10
  61. package/node_modules/pi-mcp-adapter/dist/agent-dir.js +0 -63
  62. package/node_modules/pi-mcp-adapter/dist/agent-dir.js.map +0 -1
  63. package/node_modules/pi-mcp-adapter/dist/agent-plugin-loader.d.ts +0 -8
  64. package/node_modules/pi-mcp-adapter/dist/agent-plugin-loader.js +0 -398
  65. package/node_modules/pi-mcp-adapter/dist/agent-plugin-loader.js.map +0 -1
  66. package/node_modules/pi-mcp-adapter/dist/agent-plugin-provenance.d.ts +0 -1
  67. package/node_modules/pi-mcp-adapter/dist/agent-plugin-provenance.js +0 -31
  68. package/node_modules/pi-mcp-adapter/dist/agent-plugin-provenance.js.map +0 -1
  69. package/node_modules/pi-mcp-adapter/dist/bearer-command-resolver.d.ts +0 -6
  70. package/node_modules/pi-mcp-adapter/dist/bearer-command-resolver.js +0 -194
  71. package/node_modules/pi-mcp-adapter/dist/bearer-command-resolver.js.map +0 -1
  72. package/node_modules/pi-mcp-adapter/dist/claude-plugin-loader.d.ts +0 -9
  73. package/node_modules/pi-mcp-adapter/dist/claude-plugin-loader.js +0 -362
  74. package/node_modules/pi-mcp-adapter/dist/claude-plugin-loader.js.map +0 -1
  75. package/node_modules/pi-mcp-adapter/dist/config.d.ts +0 -134
  76. package/node_modules/pi-mcp-adapter/dist/config.js +0 -1361
  77. package/node_modules/pi-mcp-adapter/dist/config.js.map +0 -1
  78. package/node_modules/pi-mcp-adapter/dist/consent-manager.d.ts +0 -18
  79. package/node_modules/pi-mcp-adapter/dist/consent-manager.js +0 -88
  80. package/node_modules/pi-mcp-adapter/dist/consent-manager.js.map +0 -1
  81. package/node_modules/pi-mcp-adapter/dist/elicitation-handler.d.ts +0 -17
  82. package/node_modules/pi-mcp-adapter/dist/elicitation-handler.js +0 -316
  83. package/node_modules/pi-mcp-adapter/dist/elicitation-handler.js.map +0 -1
  84. package/node_modules/pi-mcp-adapter/dist/errors.d.ts +0 -131
  85. package/node_modules/pi-mcp-adapter/dist/errors.js +0 -278
  86. package/node_modules/pi-mcp-adapter/dist/errors.js.map +0 -1
  87. package/node_modules/pi-mcp-adapter/dist/http-ca.d.ts +0 -8
  88. package/node_modules/pi-mcp-adapter/dist/http-ca.js +0 -88
  89. package/node_modules/pi-mcp-adapter/dist/http-ca.js.map +0 -1
  90. package/node_modules/pi-mcp-adapter/dist/jev-client.d.ts +0 -14
  91. package/node_modules/pi-mcp-adapter/dist/jev-client.js +0 -367
  92. package/node_modules/pi-mcp-adapter/dist/jev-client.js.map +0 -1
  93. package/node_modules/pi-mcp-adapter/dist/jev-contracts.d.ts +0 -77
  94. package/node_modules/pi-mcp-adapter/dist/jev-contracts.js +0 -2
  95. package/node_modules/pi-mcp-adapter/dist/jev-contracts.js.map +0 -1
  96. package/node_modules/pi-mcp-adapter/dist/jev-key-store.d.ts +0 -51
  97. package/node_modules/pi-mcp-adapter/dist/jev-key-store.js +0 -205
  98. package/node_modules/pi-mcp-adapter/dist/jev-key-store.js.map +0 -1
  99. package/node_modules/pi-mcp-adapter/dist/json-schema-validator.d.ts +0 -2
  100. package/node_modules/pi-mcp-adapter/dist/json-schema-validator.js +0 -68
  101. package/node_modules/pi-mcp-adapter/dist/json-schema-validator.js.map +0 -1
  102. package/node_modules/pi-mcp-adapter/dist/lifecycle.d.ts +0 -56
  103. package/node_modules/pi-mcp-adapter/dist/lifecycle.js +0 -410
  104. package/node_modules/pi-mcp-adapter/dist/lifecycle.js.map +0 -1
  105. package/node_modules/pi-mcp-adapter/dist/logger.d.ts +0 -51
  106. package/node_modules/pi-mcp-adapter/dist/logger.js +0 -131
  107. package/node_modules/pi-mcp-adapter/dist/logger.js.map +0 -1
  108. package/node_modules/pi-mcp-adapter/dist/mcp-auth-fetch.d.ts +0 -26
  109. package/node_modules/pi-mcp-adapter/dist/mcp-auth-fetch.js +0 -103
  110. package/node_modules/pi-mcp-adapter/dist/mcp-auth-fetch.js.map +0 -1
  111. package/node_modules/pi-mcp-adapter/dist/mcp-auth-flow.d.ts +0 -118
  112. package/node_modules/pi-mcp-adapter/dist/mcp-auth-flow.js +0 -1097
  113. package/node_modules/pi-mcp-adapter/dist/mcp-auth-flow.js.map +0 -1
  114. package/node_modules/pi-mcp-adapter/dist/mcp-auth.d.ts +0 -170
  115. package/node_modules/pi-mcp-adapter/dist/mcp-auth.js +0 -1142
  116. package/node_modules/pi-mcp-adapter/dist/mcp-auth.js.map +0 -1
  117. package/node_modules/pi-mcp-adapter/dist/mcp-bearer-store.d.ts +0 -24
  118. package/node_modules/pi-mcp-adapter/dist/mcp-bearer-store.js +0 -203
  119. package/node_modules/pi-mcp-adapter/dist/mcp-bearer-store.js.map +0 -1
  120. package/node_modules/pi-mcp-adapter/dist/mcp-callback-server.d.ts +0 -53
  121. package/node_modules/pi-mcp-adapter/dist/mcp-callback-server.js +0 -441
  122. package/node_modules/pi-mcp-adapter/dist/mcp-callback-server.js.map +0 -1
  123. package/node_modules/pi-mcp-adapter/dist/mcp-oauth-provider.d.ts +0 -148
  124. package/node_modules/pi-mcp-adapter/dist/mcp-oauth-provider.js +0 -689
  125. package/node_modules/pi-mcp-adapter/dist/mcp-oauth-provider.js.map +0 -1
  126. package/node_modules/pi-mcp-adapter/dist/mcp-probe.d.ts +0 -6
  127. package/node_modules/pi-mcp-adapter/dist/mcp-probe.js +0 -166
  128. package/node_modules/pi-mcp-adapter/dist/mcp-probe.js.map +0 -1
  129. package/node_modules/pi-mcp-adapter/dist/mcp-tasks.d.ts +0 -102
  130. package/node_modules/pi-mcp-adapter/dist/mcp-tasks.js +0 -369
  131. package/node_modules/pi-mcp-adapter/dist/mcp-tasks.js.map +0 -1
  132. package/node_modules/pi-mcp-adapter/dist/mcp-trace.d.ts +0 -95
  133. package/node_modules/pi-mcp-adapter/dist/mcp-trace.js +0 -242
  134. package/node_modules/pi-mcp-adapter/dist/mcp-trace.js.map +0 -1
  135. package/node_modules/pi-mcp-adapter/dist/metadata-cache.d.ts +0 -19
  136. package/node_modules/pi-mcp-adapter/dist/metadata-cache.js +0 -301
  137. package/node_modules/pi-mcp-adapter/dist/metadata-cache.js.map +0 -1
  138. package/node_modules/pi-mcp-adapter/dist/npx-resolver.d.ts +0 -6
  139. package/node_modules/pi-mcp-adapter/dist/npx-resolver.js +0 -505
  140. package/node_modules/pi-mcp-adapter/dist/npx-resolver.js.map +0 -1
  141. package/node_modules/pi-mcp-adapter/dist/package-mcp-loader.d.ts +0 -2
  142. package/node_modules/pi-mcp-adapter/dist/package-mcp-loader.js +0 -144
  143. package/node_modules/pi-mcp-adapter/dist/package-mcp-loader.js.map +0 -1
  144. package/node_modules/pi-mcp-adapter/dist/request-headers-command.d.ts +0 -10
  145. package/node_modules/pi-mcp-adapter/dist/request-headers-command.js +0 -312
  146. package/node_modules/pi-mcp-adapter/dist/request-headers-command.js.map +0 -1
  147. package/node_modules/pi-mcp-adapter/dist/resource-tools.d.ts +0 -1
  148. package/node_modules/pi-mcp-adapter/dist/resource-tools.js +0 -15
  149. package/node_modules/pi-mcp-adapter/dist/resource-tools.js.map +0 -1
  150. package/node_modules/pi-mcp-adapter/dist/runtime-owner.d.ts +0 -13
  151. package/node_modules/pi-mcp-adapter/dist/runtime-owner.js +0 -97
  152. package/node_modules/pi-mcp-adapter/dist/runtime-owner.js.map +0 -1
  153. package/node_modules/pi-mcp-adapter/dist/sampling-handler.d.ts +0 -17
  154. package/node_modules/pi-mcp-adapter/dist/sampling-handler.js +0 -203
  155. package/node_modules/pi-mcp-adapter/dist/sampling-handler.js.map +0 -1
  156. package/node_modules/pi-mcp-adapter/dist/secure-keyring.d.ts +0 -11
  157. package/node_modules/pi-mcp-adapter/dist/secure-keyring.js +0 -172
  158. package/node_modules/pi-mcp-adapter/dist/secure-keyring.js.map +0 -1
  159. package/node_modules/pi-mcp-adapter/dist/server-manager.d.ts +0 -157
  160. package/node_modules/pi-mcp-adapter/dist/server-manager.js +0 -1745
  161. package/node_modules/pi-mcp-adapter/dist/server-manager.js.map +0 -1
  162. package/node_modules/pi-mcp-adapter/dist/session-approvals.d.ts +0 -34
  163. package/node_modules/pi-mcp-adapter/dist/session-approvals.js +0 -140
  164. package/node_modules/pi-mcp-adapter/dist/session-approvals.js.map +0 -1
  165. package/node_modules/pi-mcp-adapter/dist/session-recovery.d.ts +0 -26
  166. package/node_modules/pi-mcp-adapter/dist/session-recovery.js +0 -142
  167. package/node_modules/pi-mcp-adapter/dist/session-recovery.js.map +0 -1
  168. package/node_modules/pi-mcp-adapter/dist/state.d.ts +0 -73
  169. package/node_modules/pi-mcp-adapter/dist/state.js +0 -2
  170. package/node_modules/pi-mcp-adapter/dist/state.js.map +0 -1
  171. package/node_modules/pi-mcp-adapter/dist/types.d.ts +0 -642
  172. package/node_modules/pi-mcp-adapter/dist/types.js +0 -331
  173. package/node_modules/pi-mcp-adapter/dist/types.js.map +0 -1
  174. package/node_modules/pi-mcp-adapter/dist/ui-app-bridge-helpers.d.ts +0 -6
  175. package/node_modules/pi-mcp-adapter/dist/ui-app-bridge-helpers.js +0 -37
  176. package/node_modules/pi-mcp-adapter/dist/ui-app-bridge-helpers.js.map +0 -1
  177. package/node_modules/pi-mcp-adapter/dist/ui-resource-handler.d.ts +0 -17
  178. package/node_modules/pi-mcp-adapter/dist/ui-resource-handler.js +0 -219
  179. package/node_modules/pi-mcp-adapter/dist/ui-resource-handler.js.map +0 -1
  180. package/node_modules/pi-mcp-adapter/dist/ui-stream-types.d.ts +0 -104
  181. package/node_modules/pi-mcp-adapter/dist/ui-stream-types.js +0 -59
  182. package/node_modules/pi-mcp-adapter/dist/ui-stream-types.js.map +0 -1
  183. package/node_modules/pi-mcp-adapter/dist/ui-tool-visibility.d.ts +0 -4
  184. package/node_modules/pi-mcp-adapter/dist/ui-tool-visibility.js +0 -27
  185. package/node_modules/pi-mcp-adapter/dist/ui-tool-visibility.js.map +0 -1
  186. package/node_modules/pi-mcp-adapter/dist/unix-socket-transport.d.ts +0 -15
  187. package/node_modules/pi-mcp-adapter/dist/unix-socket-transport.js +0 -85
  188. package/node_modules/pi-mcp-adapter/dist/unix-socket-transport.js.map +0 -1
  189. package/node_modules/pi-mcp-adapter/dist/utils.d.ts +0 -41
  190. package/node_modules/pi-mcp-adapter/dist/utils.js +0 -417
  191. package/node_modules/pi-mcp-adapter/dist/utils.js.map +0 -1
  192. package/node_modules/pi-mcp-adapter/elicitation-handler.ts +0 -358
  193. package/node_modules/pi-mcp-adapter/error-signal.ts +0 -32
  194. package/node_modules/pi-mcp-adapter/errors.ts +0 -364
  195. package/node_modules/pi-mcp-adapter/examples/jev-accessibility-loop.mjs +0 -85
  196. package/node_modules/pi-mcp-adapter/examples/jev-semantic-filter.mjs +0 -34
  197. package/node_modules/pi-mcp-adapter/failure-backoff.ts +0 -23
  198. package/node_modules/pi-mcp-adapter/glimpse-ui.ts +0 -80
  199. package/node_modules/pi-mcp-adapter/host-html-template.ts +0 -515
  200. package/node_modules/pi-mcp-adapter/http-ca.ts +0 -84
  201. package/node_modules/pi-mcp-adapter/index.ts +0 -2106
  202. package/node_modules/pi-mcp-adapter/init.ts +0 -721
  203. package/node_modules/pi-mcp-adapter/jev-client.ts +0 -300
  204. package/node_modules/pi-mcp-adapter/jev-contracts.ts +0 -53
  205. package/node_modules/pi-mcp-adapter/jev-key-store.ts +0 -192
  206. package/node_modules/pi-mcp-adapter/json-schema-validator.ts +0 -83
  207. package/node_modules/pi-mcp-adapter/lazy-loader.ts +0 -7
  208. package/node_modules/pi-mcp-adapter/lifecycle.ts +0 -457
  209. package/node_modules/pi-mcp-adapter/logger.ts +0 -169
  210. package/node_modules/pi-mcp-adapter/mcp-auth-fetch.ts +0 -113
  211. package/node_modules/pi-mcp-adapter/mcp-auth-flow.ts +0 -1296
  212. package/node_modules/pi-mcp-adapter/mcp-auth.ts +0 -1287
  213. package/node_modules/pi-mcp-adapter/mcp-bearer-store.ts +0 -250
  214. package/node_modules/pi-mcp-adapter/mcp-callback-server.ts +0 -534
  215. package/node_modules/pi-mcp-adapter/mcp-code.ts +0 -495
  216. package/node_modules/pi-mcp-adapter/mcp-install.ts +0 -60
  217. package/node_modules/pi-mcp-adapter/mcp-keyring-helper.cjs +0 -89
  218. package/node_modules/pi-mcp-adapter/mcp-oauth-provider.ts +0 -819
  219. package/node_modules/pi-mcp-adapter/mcp-output-guard.ts +0 -511
  220. package/node_modules/pi-mcp-adapter/mcp-panel-theme.ts +0 -104
  221. package/node_modules/pi-mcp-adapter/mcp-panel.ts +0 -1099
  222. package/node_modules/pi-mcp-adapter/mcp-probe.ts +0 -201
  223. package/node_modules/pi-mcp-adapter/mcp-references.ts +0 -373
  224. package/node_modules/pi-mcp-adapter/mcp-script-worker.mjs +0 -137
  225. package/node_modules/pi-mcp-adapter/mcp-setup-panel.ts +0 -747
  226. package/node_modules/pi-mcp-adapter/mcp-status.ts +0 -105
  227. package/node_modules/pi-mcp-adapter/mcp-tasks.ts +0 -467
  228. package/node_modules/pi-mcp-adapter/mcp-trace.ts +0 -306
  229. package/node_modules/pi-mcp-adapter/metadata-cache.ts +0 -362
  230. package/node_modules/pi-mcp-adapter/namespace-tools.ts +0 -334
  231. package/node_modules/pi-mcp-adapter/npx-resolver.ts +0 -543
  232. package/node_modules/pi-mcp-adapter/oauth-handler.ts +0 -30
  233. package/node_modules/pi-mcp-adapter/oauth.ts +0 -52
  234. package/node_modules/pi-mcp-adapter/onboarding-state.ts +0 -76
  235. package/node_modules/pi-mcp-adapter/package-mcp-loader.ts +0 -156
  236. package/node_modules/pi-mcp-adapter/package.json +0 -219
  237. package/node_modules/pi-mcp-adapter/panel-keys.ts +0 -53
  238. package/node_modules/pi-mcp-adapter/prompts.ts +0 -385
  239. package/node_modules/pi-mcp-adapter/proxy-modes.ts +0 -1735
  240. package/node_modules/pi-mcp-adapter/request-headers-command.ts +0 -333
  241. package/node_modules/pi-mcp-adapter/resource-tools.ts +0 -17
  242. package/node_modules/pi-mcp-adapter/runtime-owner.ts +0 -106
  243. package/node_modules/pi-mcp-adapter/sampling-handler.ts +0 -261
  244. package/node_modules/pi-mcp-adapter/sandbox-proxy-template.ts +0 -144
  245. package/node_modules/pi-mcp-adapter/search-ranking.ts +0 -309
  246. package/node_modules/pi-mcp-adapter/secure-keyring.ts +0 -170
  247. package/node_modules/pi-mcp-adapter/semantic-search.ts +0 -186
  248. package/node_modules/pi-mcp-adapter/server-manager.ts +0 -2088
  249. package/node_modules/pi-mcp-adapter/session-approvals.ts +0 -201
  250. package/node_modules/pi-mcp-adapter/session-recovery.ts +0 -158
  251. package/node_modules/pi-mcp-adapter/skills/mcp-scripting/SKILL.md +0 -49
  252. package/node_modules/pi-mcp-adapter/state.ts +0 -71
  253. package/node_modules/pi-mcp-adapter/tool-approval.ts +0 -205
  254. package/node_modules/pi-mcp-adapter/tool-metadata.ts +0 -302
  255. package/node_modules/pi-mcp-adapter/tool-registrar.ts +0 -255
  256. package/node_modules/pi-mcp-adapter/tool-result-renderer.ts +0 -573
  257. package/node_modules/pi-mcp-adapter/ts-shape.ts +0 -149
  258. package/node_modules/pi-mcp-adapter/types.ts +0 -1120
  259. package/node_modules/pi-mcp-adapter/ui-app-bridge-helpers.ts +0 -40
  260. package/node_modules/pi-mcp-adapter/ui-resource-handler.ts +0 -266
  261. package/node_modules/pi-mcp-adapter/ui-server.ts +0 -993
  262. package/node_modules/pi-mcp-adapter/ui-session.ts +0 -624
  263. package/node_modules/pi-mcp-adapter/ui-stream-types.ts +0 -89
  264. package/node_modules/pi-mcp-adapter/ui-tool-visibility.ts +0 -25
  265. package/node_modules/pi-mcp-adapter/unix-socket-transport.ts +0 -85
  266. package/node_modules/pi-mcp-adapter/utils.ts +0 -464
@@ -1,309 +0,0 @@
1
- import type { McpExtensionState } from "./state.ts";
2
- import type { ServerEntry, ToolMetadata, ToolPrefix } from "./types.ts";
3
- import { getServerPrefix, getToolNameCandidates, isServerDisabled, matchesToolPattern, resolveToolPrefix } from "./types.ts";
4
- import { isServerInActiveFailureBackoff } from "./failure-backoff.ts";
5
-
6
- /**
7
- * Shortest field token allowed to stem-match a longer query token.
8
- * Real descriptions tokenize possessives into single letters ("project's" -> ["project", "s"]),
9
- * which would otherwise make every query starting with that letter a match.
10
- */
11
- const MIN_STEM_LENGTH = 4;
12
-
13
- // Keep ASCII tokens unchanged; bound adjacent bigrams for non-ASCII Unicode word runs.
14
- // Whole-run fallbacks preserve single characters and prevent truncated queries from matching only a prefix.
15
- const MAX_UNICODE_BIGRAMS_PER_RUN = 64;
16
- const SEARCH_RUN = /[a-z0-9]+|(?:(?![a-z0-9])[\p{L}\p{N}\p{M}])+/gu;
17
- const ASCII_RUN = /^[a-z0-9]+$/;
18
-
19
- const FIELD_WEIGHTS = {
20
- name: 12,
21
- originalName: 10,
22
- server: 8,
23
- description: 5,
24
- keywords: 5,
25
- } as const;
26
-
27
- export interface RankedToolMatch {
28
- server: string;
29
- tool: ToolMetadata;
30
- score: number;
31
- }
32
-
33
- type SearchField = Exclude<keyof typeof FIELD_WEIGHTS, "keywords">;
34
-
35
- interface PreparedToolSearch {
36
- tool: ToolMetadata;
37
- fields: Array<[SearchField, string, string[]]>;
38
- nameTokens: string[];
39
- keywordPhrases: string[];
40
- keywordTokens: string[];
41
- }
42
-
43
- interface CachedServerSearch {
44
- metadata: ToolMetadata[];
45
- searchKeywords: ServerEntry["searchKeywords"];
46
- toolPrefix: ReturnType<typeof resolveToolPrefix>;
47
- withKeywords?: PreparedToolSearch[];
48
- withoutKeywords?: PreparedToolSearch[];
49
- }
50
-
51
- const searchCache = new WeakMap<McpExtensionState, Map<string, CachedServerSearch>>();
52
-
53
- /**
54
- * Resolve the configured searchKeywords entries that apply to a tool.
55
- * Keys match by original name, prefixed name, or glob — the same candidate
56
- * set includeTools/excludeTools use — and all matching entries are unioned.
57
- */
58
- export function resolveSearchKeywords(
59
- definition: ServerEntry | undefined,
60
- toolOriginalName: string,
61
- serverName: string,
62
- globalPrefix: ToolPrefix,
63
- ): string[] {
64
- const map = definition?.searchKeywords;
65
- if (!map || typeof map !== "object" || Array.isArray(map)) return [];
66
- const candidates = getToolNameCandidates(toolOriginalName, serverName, resolveToolPrefix(definition, globalPrefix));
67
- const keywords: string[] = [];
68
- const seen = new Set<string>();
69
- for (const [pattern, values] of Object.entries(map)) {
70
- if (!Array.isArray(values)) continue;
71
- if (!matchesToolPattern(candidates, [pattern])) continue;
72
- for (const value of values) {
73
- if (typeof value !== "string") continue;
74
- const trimmed = value.trim();
75
- if (!trimmed || seen.has(trimmed)) continue;
76
- seen.add(trimmed);
77
- keywords.push(trimmed);
78
- }
79
- }
80
- return keywords;
81
- }
82
-
83
- export function normalizeSearchText(value: string): string {
84
- return value
85
- .replace(/([a-z0-9])([A-Z])/g, "$1 $2")
86
- .replace(/[_./:-]+/g, " ")
87
- .toLowerCase();
88
- }
89
-
90
- export function tokenize(value: string): string[] {
91
- const tokens: string[] = [];
92
- for (const run of normalizeSearchText(value).match(SEARCH_RUN) ?? []) {
93
- if (ASCII_RUN.test(run)) {
94
- tokens.push(run);
95
- continue;
96
- }
97
- const characters = [...run];
98
- if (characters.length === 1 || characters.length > MAX_UNICODE_BIGRAMS_PER_RUN + 1) {
99
- tokens.push(run);
100
- continue;
101
- }
102
- let previous = "";
103
- for (const character of characters) {
104
- if (previous) {
105
- tokens.push(previous + character);
106
- }
107
- previous = character;
108
- }
109
- }
110
- return tokens;
111
- }
112
-
113
- function matchesAsciiStem(fieldToken: string, queryToken: string): boolean {
114
- return ASCII_RUN.test(fieldToken)
115
- && ASCII_RUN.test(queryToken)
116
- && (fieldToken.startsWith(queryToken) || (fieldToken.length >= MIN_STEM_LENGTH && queryToken.startsWith(fieldToken)));
117
- }
118
-
119
- function prepareToolSearch(tool: ToolMetadata, server: string, keywords?: string[]): PreparedToolSearch {
120
- const fields = {
121
- name: normalizeSearchText(tool.name),
122
- originalName: normalizeSearchText(tool.originalName),
123
- server: normalizeSearchText(server),
124
- description: normalizeSearchText(tool.description),
125
- };
126
- const preparedFields = (Object.entries(fields) as Array<[SearchField, string]>)
127
- .map(([field, value]): [SearchField, string, string[]] => [field, value, tokenize(value)]);
128
- const keywordPhrases = keywords?.map(keyword => normalizeSearchText(keyword).trim()).filter(Boolean) ?? [];
129
- return {
130
- tool,
131
- fields: preparedFields,
132
- nameTokens: preparedFields[0]![2],
133
- keywordPhrases,
134
- keywordTokens: keywordPhrases.flatMap(tokenize),
135
- };
136
- }
137
-
138
- function scorePreparedToolMatch(
139
- prepared: PreparedToolSearch,
140
- normalizedQuery: string,
141
- queryTokens: string[],
142
- ): number | null {
143
- let score = 0;
144
- let phraseMatched = false;
145
- let wholeFieldExact = false;
146
- const matchedTokens = new Set<string>();
147
-
148
- for (const [field, value, fieldTokens] of prepared.fields) {
149
- const weight = FIELD_WEIGHTS[field];
150
- if (value === normalizedQuery) {
151
- score += weight * 14;
152
- phraseMatched = true;
153
- wholeFieldExact = true;
154
- } else if (value.startsWith(normalizedQuery)) {
155
- score += weight * 9;
156
- phraseMatched = true;
157
- } else if (value.includes(normalizedQuery)) {
158
- score += weight * 6;
159
- phraseMatched = true;
160
- }
161
-
162
- for (const token of queryTokens) {
163
- if (fieldTokens.includes(token)) {
164
- score += weight * 4;
165
- matchedTokens.add(token);
166
- } else if (fieldTokens.some(fieldToken => matchesAsciiStem(fieldToken, token))) {
167
- score += weight * 2;
168
- matchedTokens.add(token);
169
- } else if (value.includes(token)) {
170
- score += weight;
171
- matchedTokens.add(token);
172
- }
173
- }
174
- }
175
-
176
- // Configured keywords are discrete phrases, so the phrase-level bonus is
177
- // computed per phrase (best match wins) rather than on a joined string,
178
- // which would phrase-match queries spanning two unrelated keywords.
179
- if (prepared.keywordPhrases.length > 0) {
180
- const weight = FIELD_WEIGHTS.keywords;
181
- let phraseScore = 0;
182
- for (const phrase of prepared.keywordPhrases) {
183
- if (phrase === normalizedQuery) {
184
- phraseScore = Math.max(phraseScore, weight * 14);
185
- phraseMatched = true;
186
- wholeFieldExact = true;
187
- } else if (phrase.startsWith(normalizedQuery)) {
188
- phraseScore = Math.max(phraseScore, weight * 9);
189
- phraseMatched = true;
190
- } else if (phrase.includes(normalizedQuery)) {
191
- phraseScore = Math.max(phraseScore, weight * 6);
192
- phraseMatched = true;
193
- }
194
- }
195
- score += phraseScore;
196
-
197
- for (const token of queryTokens) {
198
- if (prepared.keywordTokens.includes(token)) {
199
- score += weight * 4;
200
- matchedTokens.add(token);
201
- } else if (prepared.keywordTokens.some(keywordToken => matchesAsciiStem(keywordToken, token))) {
202
- score += weight * 2;
203
- matchedTokens.add(token);
204
- } else if (prepared.keywordPhrases.some(phrase => phrase.includes(token))) {
205
- score += weight;
206
- matchedTokens.add(token);
207
- }
208
- }
209
- }
210
-
211
- const coverage = matchedTokens.size / queryTokens.length;
212
- if (!phraseMatched && (queryTokens.length <= 2 ? coverage !== 1 : coverage < 0.6)) return null;
213
-
214
- score += coverage === 1 ? 25 : Math.round(coverage * 10);
215
- const firstQueryToken = queryTokens[0];
216
- if (firstQueryToken !== undefined && prepared.nameTokens.includes(firstQueryToken)) score += 8;
217
- if (wholeFieldExact) score += 20;
218
- return score;
219
- }
220
-
221
- export function scoreToolMatch(tool: ToolMetadata, server: string, query: string, keywords?: string[]): number | null {
222
- const normalizedQuery = normalizeSearchText(query).trim();
223
- const queryTokens = tokenize(query);
224
- if (queryTokens.length === 0) return null;
225
- return scorePreparedToolMatch(prepareToolSearch(tool, server, keywords), normalizedQuery, queryTokens);
226
- }
227
-
228
- function getPreparedTools(
229
- state: McpExtensionState,
230
- serverName: string,
231
- metadata: ToolMetadata[],
232
- definition: ServerEntry | undefined,
233
- globalPrefix: ToolPrefix,
234
- includeKeywords: boolean,
235
- ): PreparedToolSearch[] {
236
- let stateCache = searchCache.get(state);
237
- if (!stateCache) {
238
- stateCache = new Map();
239
- searchCache.set(state, stateCache);
240
- }
241
- const toolPrefix = resolveToolPrefix(definition, globalPrefix);
242
- let cached = stateCache.get(serverName);
243
- if (cached?.metadata !== metadata || cached.searchKeywords !== definition?.searchKeywords || cached.toolPrefix !== toolPrefix) {
244
- cached = { metadata, searchKeywords: definition?.searchKeywords, toolPrefix };
245
- stateCache.set(serverName, cached);
246
- }
247
- const cacheKey = includeKeywords ? "withKeywords" : "withoutKeywords";
248
- let prepared = cached[cacheKey];
249
- if (!prepared) {
250
- prepared = metadata.map(tool => prepareToolSearch(
251
- tool,
252
- serverName,
253
- includeKeywords && definition?.searchKeywords !== undefined
254
- ? resolveSearchKeywords(definition, tool.originalName, serverName, globalPrefix)
255
- : undefined,
256
- ));
257
- cached[cacheKey] = prepared;
258
- }
259
- return prepared;
260
- }
261
-
262
- export function rankToolMatches(
263
- state: McpExtensionState,
264
- query: string,
265
- server?: string,
266
- includeKeywords = true,
267
- ): RankedToolMatch[] {
268
- const matches: RankedToolMatch[] = [];
269
- const normalizedQuery = normalizeSearchText(query).trim();
270
- const queryTokens = tokenize(query);
271
- if (queryTokens.length === 0) return matches;
272
- const globalPrefix = state.config.settings?.toolPrefix ?? "server";
273
- for (const [serverName, metadata] of state.toolMetadata.entries()) {
274
- if (server && serverName !== server) continue;
275
- const definition = state.config.mcpServers[serverName];
276
- if (isServerDisabled(definition)) continue;
277
- if (isServerInActiveFailureBackoff(state, serverName)) continue;
278
- for (const prepared of getPreparedTools(state, serverName, metadata, definition, globalPrefix, includeKeywords)) {
279
- const score = scorePreparedToolMatch(prepared, normalizedQuery, queryTokens);
280
- if (score !== null) matches.push({ server: serverName, tool: prepared.tool, score });
281
- }
282
- }
283
- return matches.sort((a, b) => b.score - a.score || a.tool.name.localeCompare(b.tool.name));
284
- }
285
-
286
- export function paginate<T>(items: T[], offset: number, limit: number): { items: T[]; total: number; hasMore: boolean; nextOffset: number | null } {
287
- const safeOffset = Number.isFinite(offset) ? Math.max(0, Math.trunc(offset)) : 0;
288
- const safeLimit = Number.isFinite(limit) ? Math.max(1, Math.trunc(limit)) : 1;
289
- const total = items.length;
290
- const page = items.slice(safeOffset, safeOffset + safeLimit);
291
- const nextOffset = safeOffset + page.length;
292
- return {
293
- items: page,
294
- total,
295
- hasMore: nextOffset < total,
296
- nextOffset: nextOffset < total ? nextOffset : null,
297
- };
298
- }
299
-
300
- export function rankSuggestions(state: McpExtensionState, name: string, limit: number, server?: string): string[] {
301
- const stripped = Object.keys(state.config.mcpServers)
302
- .flatMap(serverName => (["server", "short", "mcp"] as const)
303
- .map(prefix => getServerPrefix(serverName, prefix)))
304
- .filter((candidate): candidate is string => Boolean(candidate) && name.startsWith(`${candidate}_`))
305
- .sort((a, b) => b.length - a.length)
306
- .map(candidate => name.slice(candidate.length + 1));
307
- const query = stripped[0] ?? name;
308
- return rankToolMatches(state, query, server, false).slice(0, limit).map(match => match.tool.name);
309
- }
@@ -1,170 +0,0 @@
1
- import { spawnSync } from "node:child_process";
2
- import { existsSync } from "node:fs";
3
- import { createRequire } from "node:module";
4
- import { dirname, join } from "node:path";
5
- import { fileURLToPath } from "node:url";
6
-
7
- export interface SecureKeyringStore {
8
- read(account: string): string | undefined;
9
- write(account: string, payload: string): void;
10
- remove(account: string): void;
11
- }
12
-
13
- type KeyringEntry = {
14
- getPassword(): string | null;
15
- setPassword(password: string): void;
16
- deleteCredential(): boolean;
17
- };
18
- type KeyringEntryConstructor = new (service: string, account: string) => KeyringEntry;
19
- type KeyringModule = { Entry: KeyringEntryConstructor };
20
- type KeyringRequire = ((id: string) => unknown) & { resolve(id: string): string };
21
-
22
- const require = createRequire(import.meta.url);
23
- const TEST_STORE_ENV = "PI_MCP_ADAPTER_TEST_AUTH_STORE";
24
- const memoryEntries = new Map<string, string>();
25
- let memoryReadCount = 0;
26
- let KeyringEntryClass: KeyringEntryConstructor | undefined;
27
-
28
- function key(service: string, account: string): string {
29
- return `${service}\0${account}`;
30
- }
31
-
32
- function nativeStore(service: string): SecureKeyringStore {
33
- const entry = (account: string): KeyringEntry => {
34
- try {
35
- KeyringEntryClass ??= loadKeyringEntryClass();
36
- return new KeyringEntryClass(service, account);
37
- } catch (error) {
38
- throw new Error("OS secure credential storage is unavailable. Configure or unlock the OS credential store and retry.", { cause: error });
39
- }
40
- };
41
- const run = (operation: "read" | "write" | "remove", account: string, payload?: string): string | undefined => {
42
- try {
43
- const credential = entry(account);
44
- if (operation === "read") return credential.getPassword() ?? undefined;
45
- if (operation === "write") credential.setPassword(payload!);
46
- else credential.deleteCredential();
47
- return undefined;
48
- } catch (error) {
49
- if (!shouldRecoverKeyring(error)) throw error;
50
- return recoverKeyring(operation, service, account, payload);
51
- }
52
- };
53
- return {
54
- read: account => run("read", account),
55
- write: (account, payload) => { run("write", account, payload); },
56
- remove: account => { run("remove", account); },
57
- };
58
- }
59
-
60
- function shouldRecoverKeyring(error: unknown): boolean {
61
- if (process.platform !== "linux" || process.env.PI_MCP_ADAPTER_DISABLE_KEYRING_RECOVERY === "1") return false;
62
- const seen = new Set<unknown>();
63
- for (let depth = 0; depth < 16 && error !== undefined && !seen.has(error); depth++) {
64
- seen.add(error);
65
- const candidate = typeof error === "object" && error !== null
66
- ? error as { name?: unknown; message?: unknown; code?: unknown; cause?: unknown }
67
- : { message: error };
68
- if ([candidate.name, candidate.message, candidate.code].some(value =>
69
- typeof value === "string" && /\b(?:KeyRevoked|key\s+(?:has been\s+)?revoked)\b/i.test(value))) return true;
70
- error = candidate.cause;
71
- }
72
- return false;
73
- }
74
-
75
- function recoverKeyring(operation: "read" | "write" | "remove", service: string, account: string, payload?: string): string | undefined {
76
- try {
77
- const adjacentHelper = new URL("./mcp-keyring-helper.cjs", import.meta.url);
78
- const helper = existsSync(adjacentHelper) ? adjacentHelper : new URL("../mcp-keyring-helper.cjs", import.meta.url);
79
- const result = spawnSync("keyctl", ["session", "-", process.execPath, fileURLToPath(helper)], {
80
- input: `${JSON.stringify({ operation, service, account, payload })}\n`,
81
- encoding: "utf8",
82
- stdio: "pipe",
83
- shell: false,
84
- timeout: 10_000,
85
- killSignal: "SIGKILL",
86
- maxBuffer: 1024 * 1024,
87
- windowsHide: true,
88
- });
89
- if (result.error || result.status !== 0 || result.signal) throw new Error();
90
- const response: unknown = JSON.parse(result.stdout);
91
- if (typeof response !== "object" || response === null || Array.isArray(response)) throw new Error();
92
- const value = response as { ok?: unknown; found?: unknown; value?: unknown };
93
- if (value.ok !== true) throw new Error();
94
- const allowedKeys = operation === "read" ? ["ok", "found", "value"] : ["ok"];
95
- if (Object.keys(response).some(key => !allowedKeys.includes(key))) throw new Error();
96
- if (operation === "read") {
97
- if (value.found === true && typeof value.value === "string") return value.value;
98
- if (value.found === false && value.value === undefined) return undefined;
99
- throw new Error();
100
- }
101
- return undefined;
102
- } catch {
103
- throw new Error("Linux keyring recovery failed. Configure or unlock the OS credential store and retry.");
104
- }
105
- }
106
-
107
- export function createSecureKeyringStore(service: string): SecureKeyringStore {
108
- const mode = process.env[TEST_STORE_ENV];
109
- if (mode === "memory" || mode === "sizelimited") {
110
- return {
111
- read(account) { memoryReadCount++; return memoryEntries.get(key(service, account)); },
112
- write(account, payload) {
113
- if (mode === "sizelimited" && payload.length > 1280) throw new Error("secure credential value exceeds platform limit");
114
- memoryEntries.set(key(service, account), payload);
115
- },
116
- remove(account) { memoryEntries.delete(key(service, account)); },
117
- };
118
- }
119
- if (mode === "unavailable") {
120
- return {
121
- read() { memoryReadCount++; throw new Error("simulated secure credential store unavailable"); },
122
- write() { throw new Error("simulated secure credential store unavailable"); },
123
- remove() { throw new Error("simulated secure credential store unavailable"); },
124
- };
125
- }
126
- return nativeStore(service);
127
- }
128
-
129
- function loadKeyringEntryClass(keyringRequire: KeyringRequire = require, platform: NodeJS.Platform = process.platform, arch: NodeJS.Architecture = process.arch): KeyringEntryConstructor {
130
- try {
131
- return (keyringRequire("@napi-rs/keyring") as KeyringModule).Entry;
132
- } catch (loaderError) {
133
- try {
134
- return loadKeyringNativeBindingFallback(keyringRequire, platform, arch).Entry;
135
- } catch (fallbackError) {
136
- const message = fallbackError instanceof Error ? fallbackError.message : String(fallbackError);
137
- throw new Error(`Failed to load @napi-rs/keyring; absolute-path native binding fallback also failed: ${message}`, { cause: loaderError });
138
- }
139
- }
140
- }
141
-
142
- function loadKeyringNativeBindingFallback(keyringRequire: KeyringRequire, platform: NodeJS.Platform, arch: NodeJS.Architecture): KeyringModule {
143
- let lastError: unknown;
144
- for (const suffix of getKeyringNativeBindingSuffixes(platform, arch)) {
145
- try {
146
- const packageName = `@napi-rs/keyring-${suffix}`;
147
- const packageJsonPath = keyringRequire.resolve(`${packageName}/package.json`);
148
- return keyringRequire(join(dirname(packageJsonPath), `keyring.${suffix}.node`)) as KeyringModule;
149
- } catch (error) { lastError = error; }
150
- }
151
- throw lastError instanceof Error ? lastError : new Error(String(lastError));
152
- }
153
-
154
- function getKeyringNativeBindingSuffixes(platform: NodeJS.Platform, arch: NodeJS.Architecture): string[] {
155
- if (platform === "darwin") return arch === "arm64" ? ["darwin-arm64"] : arch === "x64" ? ["darwin-x64"] : [];
156
- if (platform === "win32") return arch === "arm64" ? ["win32-arm64-msvc"] : arch === "x64" ? ["win32-x64-msvc"] : arch === "ia32" ? ["win32-ia32-msvc"] : [];
157
- if (platform === "linux") {
158
- if (arch === "arm64") return ["linux-arm64-gnu", "linux-arm64-musl"];
159
- if (arch === "arm") return ["linux-arm-gnueabihf"];
160
- if (arch === "riscv64") return ["linux-riscv64-gnu"];
161
- if (arch === "x64") return ["linux-x64-gnu", "linux-x64-musl"];
162
- }
163
- return platform === "freebsd" && arch === "x64" ? ["freebsd-x64"] : [];
164
- }
165
-
166
- export function resetTestSecureKeyring(): void { memoryEntries.clear(); memoryReadCount = 0; }
167
- export function getTestSecureKeyringReadCount(): number { return memoryReadCount; }
168
- export function getTestSecureKeyringEntries(): [string, string][] { return [...memoryEntries.entries()]; }
169
- export function setTestSecureKeyringEntry(service: string, account: string, payload: string): void { memoryEntries.set(key(service, account), payload); }
170
- export function removeTestSecureKeyringEntry(service: string, account: string): void { memoryEntries.delete(key(service, account)); }
@@ -1,186 +0,0 @@
1
- import { createHash } from "node:crypto";
2
- import { areJevSourcesAllowed, evaluateJev, resolveSemanticJevSettings } from "./jev-client.ts";
3
- import type { JevEvaluateInput, JevEvaluationEnvelope } from "./jev-contracts.ts";
4
- import { isServerInActiveFailureBackoff } from "./failure-backoff.ts";
5
- import { rankToolMatches, type RankedToolMatch } from "./search-ranking.ts";
6
- import type { McpExtensionState } from "./state.ts";
7
- import type { ToolMetadata } from "./types.ts";
8
- import { isServerDisabled } from "./types.ts";
9
-
10
- export type SemanticSearchEvaluator = (
11
- state: McpExtensionState,
12
- input: JevEvaluateInput,
13
- options: { purpose: "semantic-search"; signal?: AbortSignal; observedSources?: readonly string[] },
14
- ) => Promise<JevEvaluationEnvelope>;
15
-
16
- export type SemanticSearchBackend =
17
- | { requested: "semantic"; used: "semantic"; degraded: false; model: string; usage: { inputTokens: number; outputTokens: number }; abstained: boolean }
18
- | { requested: "semantic"; used: "lexical"; degraded: true; reason: "timeout" | "rate_limited" | "service_unavailable" };
19
-
20
- export type SemanticSearchResult =
21
- | { ok: true; matches: RankedToolMatch[]; backend: SemanticSearchBackend }
22
- | { ok: false; error: { code: string; message: string } };
23
-
24
- interface Candidate {
25
- id: string;
26
- server: string;
27
- description: string;
28
- tool: ToolMetadata;
29
- }
30
-
31
- function truncateUtf8(value: string, maxBytes: number): string {
32
- const bytes = Buffer.from(value, "utf8");
33
- if (bytes.length <= maxBytes) return value;
34
- let end = maxBytes;
35
- while (end > 0 && ((bytes[end] ?? 0) & 0xc0) === 0x80) end -= 1;
36
- return bytes.subarray(0, end).toString("utf8");
37
- }
38
-
39
- function stableHash(value: string): string {
40
- return createHash("sha256").update(value).digest("hex");
41
- }
42
-
43
- function eligibleMatches(state: McpExtensionState, server: string | undefined, allowed: Set<string>): RankedToolMatch[] {
44
- const matches: RankedToolMatch[] = [];
45
- for (const [serverName, metadata] of state.toolMetadata) {
46
- if ((server && serverName !== server) || !allowed.has(serverName)
47
- || isServerDisabled(state.config.mcpServers[serverName]) || isServerInActiveFailureBackoff(state, serverName)) continue;
48
- for (const tool of metadata) matches.push({ server: serverName, tool, score: 0 });
49
- }
50
- return matches;
51
- }
52
-
53
- function roundRobinNonLexical(query: string, matches: RankedToolMatch[]): RankedToolMatch[] {
54
- const buckets = new Map<string, RankedToolMatch[]>();
55
- for (const match of matches) {
56
- const bucket = buckets.get(match.server) ?? [];
57
- bucket.push(match);
58
- buckets.set(match.server, bucket);
59
- }
60
- const servers = [...buckets.keys()].sort((a, b) => a.localeCompare(b));
61
- for (const bucket of buckets.values()) {
62
- bucket.sort((a, b) => stableHash(query + a.tool.name).localeCompare(stableHash(query + b.tool.name)) || a.tool.name.localeCompare(b.tool.name));
63
- }
64
- if (servers.length > 1) {
65
- const start = Number.parseInt(stableHash(query).slice(0, 8), 16) % servers.length;
66
- servers.push(...servers.splice(0, start));
67
- }
68
- const result: RankedToolMatch[] = [];
69
- let index = 0;
70
- while (result.length < matches.length) {
71
- let added = false;
72
- for (const server of servers) {
73
- const match = buckets.get(server)?.[index];
74
- if (match) { result.push(match); added = true; }
75
- }
76
- if (!added) break;
77
- index += 1;
78
- }
79
- return result;
80
- }
81
-
82
- function selectSemanticCandidates(
83
- state: McpExtensionState,
84
- query: string,
85
- server: string | undefined,
86
- allowedServers: string[],
87
- limit: number,
88
- ): Candidate[] {
89
- const eligible = eligibleMatches(state, server, new Set(allowedServers));
90
- let selected = eligible;
91
- if (eligible.length > limit) {
92
- const eligiblePaths = new Set(eligible.map(match => `${match.server}\0${match.tool.name}`));
93
- const lexical = rankToolMatches(state, query, server)
94
- .filter(match => eligiblePaths.has(`${match.server}\0${match.tool.name}`));
95
- const lexicalCount = Math.floor(limit / 2);
96
- const first = lexical.slice(0, lexicalCount);
97
- const lexicalPaths = new Set(lexical.map(match => `${match.server}\0${match.tool.name}`));
98
- const broad = roundRobinNonLexical(query, eligible.filter(match => !lexicalPaths.has(`${match.server}\0${match.tool.name}`)));
99
- selected = [...first, ...broad.slice(0, limit - first.length)];
100
- }
101
- return selected.map((match, index) => ({ id: `c${index}`, server: match.server,
102
- description: truncateUtf8(match.tool.description ?? "", 512), tool: match.tool }));
103
- }
104
-
105
- export async function semanticSearch(
106
- state: McpExtensionState,
107
- query: string,
108
- server?: string,
109
- signal?: AbortSignal,
110
- evaluator: SemanticSearchEvaluator = evaluateJev,
111
- observedSources: readonly string[] = [],
112
- ): Promise<SemanticSearchResult> {
113
- let settings;
114
- try {
115
- settings = resolveSemanticJevSettings(state);
116
- } catch {
117
- return { ok: false, error: { code: "invalid_request", message: "Invalid Jev semantic search settings." } };
118
- }
119
- if (!settings.semanticSearch) {
120
- return { ok: false, error: { code: "disabled", message: "Jev semantic search is disabled." } };
121
- }
122
- if (settings.allowedServers.length === 0) {
123
- return { ok: false, error: { code: "data_policy_denied", message: "Semantic search is enabled, but settings.jev.allowedServers is empty. Run /mcp jev setup or allow specific MCP servers." } };
124
- }
125
- if (server && !settings.allowedServers.includes(server)) {
126
- return { ok: false, error: { code: "data_policy_denied", message: `Server "${server}" is not allowed by settings.jev.allowedServers.` } };
127
- }
128
- if (!areJevSourcesAllowed(state, settings, observedSources)) {
129
- return { ok: false, error: { code: "data_policy_denied", message: "Evaluation sources are not allowed by policy." } };
130
- }
131
- if (query.trim().length === 0) {
132
- return { ok: false, error: { code: "empty_query", message: "Semantic search query cannot be empty." } };
133
- }
134
- const candidates = selectSemanticCandidates(state, query, server, settings.allowedServers, settings.semanticCandidateLimit);
135
- if (candidates.length === 0) {
136
- return { ok: false, error: { code: "no_eligible_tools", message: "Semantic search has no eligible cached tools from the allowed servers. Connect an allowed server or update settings.jev.allowedServers." } };
137
- }
138
- const input: JevEvaluateInput = {
139
- state: {
140
- query,
141
- candidates: candidates.map(candidate => ({ id: candidate.id, path: candidate.tool.name,
142
- name: candidate.tool.originalName, server: candidate.server, description: candidate.description })),
143
- },
144
- questions: {
145
- match: {
146
- type: "choice",
147
- instructions: "Rank which tool best matches the query. Choose none when no tool is suitable.",
148
- criteria: Object.fromEntries([
149
- ...candidates.map(candidate => [candidate.id, { path: candidate.tool.name }]),
150
- ["none", { noSuitableTool: true }],
151
- ]),
152
- },
153
- },
154
- sources: [...new Set(candidates.map(candidate => candidate.server))],
155
- };
156
- const envelope = await evaluator(state, input, { purpose: "semantic-search", ...(signal ? { signal } : {}), ...(observedSources.length > 0 ? { observedSources } : {}) });
157
- if (!envelope.ok) {
158
- if (envelope.error.code === "timeout" || envelope.error.code === "rate_limited" || envelope.error.code === "service_unavailable") {
159
- return {
160
- ok: true,
161
- matches: rankToolMatches(state, query, server),
162
- backend: { requested: "semantic", used: "lexical", degraded: true, reason: envelope.error.code },
163
- };
164
- }
165
- return { ok: false, error: { code: envelope.error.code, message: envelope.error.message } };
166
- }
167
- const answer = envelope.data.answers.match;
168
- if (!answer || answer.type !== "choice") {
169
- return { ok: false, error: { code: "invalid_response", message: "Jev returned an invalid semantic search response." } };
170
- }
171
- const ranked = candidates
172
- .map(candidate => ({ candidate, probability: answer.probabilities[candidate.id] ?? 0 }))
173
- .sort((a, b) => b.probability - a.probability || a.candidate.id.localeCompare(b.candidate.id));
174
- const top = ranked[0];
175
- const noneProbability = answer.probabilities.none ?? 0;
176
- const abstained = answer.choice === "none" || !top || noneProbability >= top.probability || top.probability < settings.semanticMinProbability;
177
- const matches = abstained ? [] : ranked.map(({ candidate, probability }) => ({ server: candidate.server, tool: candidate.tool, score: probability }));
178
- return {
179
- ok: true,
180
- matches,
181
- backend: {
182
- requested: "semantic", used: "semantic", degraded: false, model: envelope.data.model,
183
- usage: envelope.data.usage, abstained,
184
- },
185
- };
186
- }