@herbertgao/pi-extensions 2026.8.6 → 2026.8.7

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 (343) hide show
  1. package/README.md +5 -5
  2. package/node_modules/@herbertgao/pi-cc-extensions/README.en.md +1 -1
  3. package/node_modules/@herbertgao/pi-cc-extensions/README.md +1 -1
  4. package/node_modules/@herbertgao/pi-cc-extensions/extensions/config/config.ts +19 -7
  5. package/node_modules/@herbertgao/pi-cc-extensions/extensions/config/panel.ts +30 -9
  6. package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/compact-thinking.ts +272 -22
  7. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/compact-mode.ts +94 -77
  8. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/message-display.ts +1 -1
  9. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/mouse/hover.ts +14 -0
  10. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/mouse/interaction.ts +55 -17
  11. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/mouse/layout.ts +41 -2
  12. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/diff-renderer.ts +75 -6
  13. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/types.ts +7 -3
  14. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/grouping.ts +64 -1
  15. package/node_modules/@herbertgao/pi-cc-extensions/package.json +3 -3
  16. package/node_modules/@herbertgao/pi-subagents/CHANGELOG.md +6 -0
  17. package/node_modules/@herbertgao/pi-subagents/README.md +30 -22
  18. package/node_modules/@herbertgao/pi-subagents/examples/agent-tool-description.md +5 -5
  19. package/node_modules/@herbertgao/pi-subagents/package.json +7 -7
  20. package/node_modules/@herbertgao/pi-subagents/src/agent-file-toggle.ts +7 -1
  21. package/node_modules/@herbertgao/pi-subagents/src/agent-manager.ts +48 -19
  22. package/node_modules/@herbertgao/pi-subagents/src/agent-runner.ts +17 -13
  23. package/node_modules/@herbertgao/pi-subagents/src/custom-agents.ts +26 -2
  24. package/node_modules/@herbertgao/pi-subagents/src/index.ts +219 -40
  25. package/node_modules/@herbertgao/pi-subagents/src/invocation-config.ts +101 -3
  26. package/node_modules/@herbertgao/pi-subagents/src/nested-tools.ts +18 -4
  27. package/node_modules/@herbertgao/pi-subagents/src/settings.ts +99 -0
  28. package/node_modules/@herbertgao/pi-subagents/src/types.ts +22 -3
  29. package/node_modules/@herbertgao/pi-subagents/src/ui/agent-widget.ts +51 -3
  30. package/node_modules/@herbertgao/pi-subagents/src/ui/conversation-viewer.ts +20 -2
  31. package/node_modules/@herbertgao/pi-subagents/src/ui/fleet-list.ts +21 -6
  32. package/node_modules/@herbertgao/pi-subagents/src/usage.ts +129 -1
  33. package/node_modules/@herbertgao/pi-subagents/src/worktree.ts +20 -0
  34. package/node_modules/@juicesharp/rpiv-ask-user-question/README.md +1 -0
  35. package/node_modules/@juicesharp/rpiv-ask-user-question/ask-user-question.ts +26 -10
  36. package/node_modules/@juicesharp/rpiv-ask-user-question/config.ts +25 -0
  37. package/node_modules/@juicesharp/rpiv-ask-user-question/docs/configuration.md +13 -4
  38. package/node_modules/@juicesharp/rpiv-ask-user-question/docs/hosts.md +2 -1
  39. package/node_modules/@juicesharp/rpiv-ask-user-question/docs/localization.md +3 -1
  40. package/node_modules/@juicesharp/rpiv-ask-user-question/locales/en.json +2 -2
  41. package/node_modules/@juicesharp/rpiv-ask-user-question/locales/zh.json +2 -2
  42. package/node_modules/@juicesharp/rpiv-ask-user-question/package.json +3 -2
  43. package/node_modules/@juicesharp/rpiv-ask-user-question/state/build-questionnaire.ts +10 -0
  44. package/node_modules/@juicesharp/rpiv-ask-user-question/state/key-router.ts +3 -1
  45. package/node_modules/@juicesharp/rpiv-ask-user-question/state/questionnaire-session.ts +30 -7
  46. package/node_modules/@juicesharp/rpiv-ask-user-question/state/state.ts +14 -6
  47. package/node_modules/@juicesharp/rpiv-ask-user-question/view/dialog-builder.ts +30 -4
  48. package/node_modules/@juicesharp/rpiv-ask-user-question/view/tab-content-strategy.ts +26 -5
  49. package/node_modules/@narumitw/pi-btw/README.md +120 -111
  50. package/node_modules/@narumitw/pi-btw/dist/index.ts +2898 -0
  51. package/node_modules/@narumitw/pi-btw/dist/index.ts.map +7 -0
  52. package/node_modules/@narumitw/pi-btw/package.json +10 -6
  53. package/node_modules/@narumitw/pi-btw/src/btw.ts +36 -40
  54. package/node_modules/@narumitw/pi-btw/src/fullscreen-ui.ts +55 -10
  55. package/node_modules/@narumitw/pi-btw/src/main-tree-picker.ts +390 -0
  56. package/node_modules/@narumitw/pi-btw/src/menu.ts +45 -2
  57. package/node_modules/@narumitw/pi-btw/src/transcript-pager.ts +136 -45
  58. package/node_modules/pi-lens/CHANGELOG.md +909 -0
  59. package/node_modules/pi-lens/README.md +11 -3
  60. package/node_modules/pi-lens/config/ruff/core.toml +42 -0
  61. package/node_modules/pi-lens/dist/clients/actionable-warnings.js +130 -27
  62. package/node_modules/pi-lens/dist/clients/advisory-provenance.js +126 -13
  63. package/node_modules/pi-lens/dist/clients/agent-nudge.js +67 -11
  64. package/node_modules/pi-lens/dist/clients/ast-grep-client.js +9 -3
  65. package/node_modules/pi-lens/dist/clients/atomic-write.js +9 -3
  66. package/node_modules/pi-lens/dist/clients/bash-file-access.js +285 -8
  67. package/node_modules/pi-lens/dist/clients/biome-client.js +61 -30
  68. package/node_modules/pi-lens/dist/clients/blocker-freshness.js +428 -0
  69. package/node_modules/pi-lens/dist/clients/blocker-past-eof.js +119 -0
  70. package/node_modules/pi-lens/dist/clients/bootstrap.js +12 -1
  71. package/node_modules/pi-lens/dist/clients/bounded-pid-file-lock.js +12 -3
  72. package/node_modules/pi-lens/dist/clients/bounded-telemetry.js +192 -0
  73. package/node_modules/pi-lens/dist/clients/cascade-budget.js +196 -0
  74. package/node_modules/pi-lens/dist/clients/cascade-format.js +20 -1
  75. package/node_modules/pi-lens/dist/clients/cascade-types.js +9 -1
  76. package/node_modules/pi-lens/dist/clients/child-unref.js +54 -6
  77. package/node_modules/pi-lens/dist/clients/code-quality-warnings.js +76 -24
  78. package/node_modules/pi-lens/dist/clients/dead-code-client.js +123 -31
  79. package/node_modules/pi-lens/dist/clients/deadline-utils.js +8 -1
  80. package/node_modules/pi-lens/dist/clients/debug-handles.js +5 -0
  81. package/node_modules/pi-lens/dist/clients/degradation-ledger.js +62 -13
  82. package/node_modules/pi-lens/dist/clients/dependency-checker.js +25 -3
  83. package/node_modules/pi-lens/dist/clients/diagnostic-dispositions.js +147 -33
  84. package/node_modules/pi-lens/dist/clients/diagnostic-line-freshness.js +298 -0
  85. package/node_modules/pi-lens/dist/clients/dispatch/dispatcher.js +52 -2
  86. package/node_modules/pi-lens/dist/clients/dispatch/integration.js +256 -88
  87. package/node_modules/pi-lens/dist/clients/dispatch/lazy.js +7 -4
  88. package/node_modules/pi-lens/dist/clients/dispatch/plan.js +5 -0
  89. package/node_modules/pi-lens/dist/clients/dispatch/runners/ast-grep-napi.js +234 -18
  90. package/node_modules/pi-lens/dist/clients/dispatch/runners/biome-check.js +187 -19
  91. package/node_modules/pi-lens/dist/clients/dispatch/runners/cpp-check.js +6 -3
  92. package/node_modules/pi-lens/dist/clients/dispatch/runners/credo.js +11 -9
  93. package/node_modules/pi-lens/dist/clients/dispatch/runners/cue-vet.js +398 -0
  94. package/node_modules/pi-lens/dist/clients/dispatch/runners/elixir-check.js +11 -3
  95. package/node_modules/pi-lens/dist/clients/dispatch/runners/eslint.js +12 -7
  96. package/node_modules/pi-lens/dist/clients/dispatch/runners/go-vet.js +18 -0
  97. package/node_modules/pi-lens/dist/clients/dispatch/runners/helm-render.js +895 -0
  98. package/node_modules/pi-lens/dist/clients/dispatch/runners/index.js +4 -0
  99. package/node_modules/pi-lens/dist/clients/dispatch/runners/javac.js +16 -3
  100. package/node_modules/pi-lens/dist/clients/dispatch/runners/lsp.js +60 -53
  101. package/node_modules/pi-lens/dist/clients/dispatch/runners/markdownlint.js +16 -0
  102. package/node_modules/pi-lens/dist/clients/dispatch/runners/mypy.js +23 -0
  103. package/node_modules/pi-lens/dist/clients/dispatch/runners/psscriptanalyzer.js +331 -55
  104. package/node_modules/pi-lens/dist/clients/dispatch/runners/pyright.js +38 -5
  105. package/node_modules/pi-lens/dist/clients/dispatch/runners/rust-clippy.js +97 -19
  106. package/node_modules/pi-lens/dist/clients/dispatch/runners/spellcheck.js +14 -0
  107. package/node_modules/pi-lens/dist/clients/dispatch/runners/sqlfluff.js +20 -0
  108. package/node_modules/pi-lens/dist/clients/dispatch/runners/stylelint.js +22 -0
  109. package/node_modules/pi-lens/dist/clients/dispatch/runners/swiftlint.js +10 -0
  110. package/node_modules/pi-lens/dist/clients/dispatch/runners/taplo.js +18 -6
  111. package/node_modules/pi-lens/dist/clients/dispatch/runners/trivy-config.js +73 -12
  112. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/availability-policy.js +275 -13
  113. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/candidate-probe.js +21 -6
  114. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/lazy-installer.js +260 -27
  115. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/runner-helpers.js +734 -64
  116. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/spawn-outcome.js +86 -11
  117. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/tool-failure.js +72 -0
  118. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/toolchain-availability.js +9 -0
  119. package/node_modules/pi-lens/dist/clients/dispatch/runners/vale.js +9 -0
  120. package/node_modules/pi-lens/dist/clients/dispatch/runners/yamllint.js +18 -1
  121. package/node_modules/pi-lens/dist/clients/dispatch/runners/zig-check.js +4 -2
  122. package/node_modules/pi-lens/dist/clients/event-loop-monitor.js +28 -5
  123. package/node_modules/pi-lens/dist/clients/extension-log.js +22 -1
  124. package/node_modules/pi-lens/dist/clients/file-kinds.js +8 -2
  125. package/node_modules/pi-lens/dist/clients/finding-delivery-gate.js +294 -0
  126. package/node_modules/pi-lens/dist/clients/finding-identity.js +88 -0
  127. package/node_modules/pi-lens/dist/clients/formatters-lazy.js +3 -2
  128. package/node_modules/pi-lens/dist/clients/formatters.js +500 -115
  129. package/node_modules/pi-lens/dist/clients/generation-guard.js +216 -0
  130. package/node_modules/pi-lens/dist/clients/git-guard.js +31 -0
  131. package/node_modules/pi-lens/dist/clients/gitleaks-client.js +181 -8
  132. package/node_modules/pi-lens/dist/clients/govulncheck-client.js +109 -9
  133. package/node_modules/pi-lens/dist/clients/grammar-source.js +333 -10
  134. package/node_modules/pi-lens/dist/clients/host-ports.js +8 -0
  135. package/node_modules/pi-lens/dist/clients/installer/index.js +1114 -100
  136. package/node_modules/pi-lens/dist/clients/installer/managed-tool-refresh-session.js +58 -0
  137. package/node_modules/pi-lens/dist/clients/installer/managed-tool-refresh.js +715 -0
  138. package/node_modules/pi-lens/dist/clients/instance-reaper.js +643 -97
  139. package/node_modules/pi-lens/dist/clients/instance-registry.js +87 -17
  140. package/node_modules/pi-lens/dist/clients/jscpd-client.js +82 -6
  141. package/node_modules/pi-lens/dist/clients/knip-client.js +310 -10
  142. package/node_modules/pi-lens/dist/clients/language-policy.js +8 -2
  143. package/node_modules/pi-lens/dist/clients/language-profile.js +2 -0
  144. package/node_modules/pi-lens/dist/clients/latency-logger.js +440 -11
  145. package/node_modules/pi-lens/dist/clients/lazy-import.js +21 -0
  146. package/node_modules/pi-lens/dist/clients/ledger-bounds.js +21 -0
  147. package/node_modules/pi-lens/dist/clients/lens-flag-registry.js +8 -0
  148. package/node_modules/pi-lens/dist/clients/lsp/cascade-tier.js +102 -7
  149. package/node_modules/pi-lens/dist/clients/lsp/client.js +1516 -159
  150. package/node_modules/pi-lens/dist/clients/lsp/diagnostic-binding.js +83 -1
  151. package/node_modules/pi-lens/dist/clients/lsp/document-drift.js +389 -0
  152. package/node_modules/pi-lens/dist/clients/lsp/index.js +2274 -147
  153. package/node_modules/pi-lens/dist/clients/lsp/inferred-project.js +215 -0
  154. package/node_modules/pi-lens/dist/clients/lsp/jvm-runtime.js +146 -14
  155. package/node_modules/pi-lens/dist/clients/lsp/language.js +48 -4
  156. package/node_modules/pi-lens/dist/clients/lsp/server.js +277 -16
  157. package/node_modules/pi-lens/dist/clients/lsp/spawn-history.js +22 -0
  158. package/node_modules/pi-lens/dist/clients/lsp/sync-kind.js +43 -0
  159. package/node_modules/pi-lens/dist/clients/lsp/tsserver-sync.js +79 -3
  160. package/node_modules/pi-lens/dist/clients/lsp/wait-policy/strategies.js +43 -0
  161. package/node_modules/pi-lens/dist/clients/lsp/workspace-diagnostics-cache.js +422 -28
  162. package/node_modules/pi-lens/dist/clients/lsp/workspace-diagnostics-session.js +30 -0
  163. package/node_modules/pi-lens/dist/clients/lsp/workspace-sweep-hold.js +175 -0
  164. package/node_modules/pi-lens/dist/clients/lsp-lazy.js +3 -2
  165. package/node_modules/pi-lens/dist/clients/mcp/session.js +10 -2
  166. package/node_modules/pi-lens/dist/clients/module-report.js +8 -0
  167. package/node_modules/pi-lens/dist/clients/opengrep-client.js +15 -1
  168. package/node_modules/pi-lens/dist/clients/package-manager.js +170 -14
  169. package/node_modules/pi-lens/dist/clients/path-attribution-telemetry.js +27 -0
  170. package/node_modules/pi-lens/dist/clients/path-utils.js +160 -0
  171. package/node_modules/pi-lens/dist/clients/pipeline.js +154 -33
  172. package/node_modules/pi-lens/dist/clients/post-autofix-notice.js +37 -0
  173. package/node_modules/pi-lens/dist/clients/project-diagnostics/cache.js +17 -3
  174. package/node_modules/pi-lens/dist/clients/project-diagnostics/extractors.js +89 -22
  175. package/node_modules/pi-lens/dist/clients/project-diagnostics/fresh-fetch.js +84 -14
  176. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/gitleaks.js +15 -3
  177. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/trivy.js +50 -2
  178. package/node_modules/pi-lens/dist/clients/project-diagnostics/scanner.js +5 -0
  179. package/node_modules/pi-lens/dist/clients/project-lens-config.js +3 -0
  180. package/node_modules/pi-lens/dist/clients/project-snapshot.js +299 -0
  181. package/node_modules/pi-lens/dist/clients/quiet-window-config.js +47 -0
  182. package/node_modules/pi-lens/dist/clients/quiet-window.js +11 -25
  183. package/node_modules/pi-lens/dist/clients/read-guard-logger.js +1 -1
  184. package/node_modules/pi-lens/dist/clients/read-guard.js +184 -10
  185. package/node_modules/pi-lens/dist/clients/resource-sampler.js +49 -17
  186. package/node_modules/pi-lens/dist/clients/reverse-deps.js +50 -11
  187. package/node_modules/pi-lens/dist/clients/review-graph/query.js +52 -4
  188. package/node_modules/pi-lens/dist/clients/runtime-agent-end.js +96 -7
  189. package/node_modules/pi-lens/dist/clients/runtime-context.js +8 -1
  190. package/node_modules/pi-lens/dist/clients/runtime-coordinator.js +308 -45
  191. package/node_modules/pi-lens/dist/clients/runtime-session.js +332 -9
  192. package/node_modules/pi-lens/dist/clients/runtime-tool-call.js +217 -22
  193. package/node_modules/pi-lens/dist/clients/runtime-tool-result.js +212 -79
  194. package/node_modules/pi-lens/dist/clients/runtime-turn.js +575 -67
  195. package/node_modules/pi-lens/dist/clients/safe-spawn.js +220 -7
  196. package/node_modules/pi-lens/dist/clients/scratch-tree-policy.js +161 -0
  197. package/node_modules/pi-lens/dist/clients/search-read-registration.js +34 -6
  198. package/node_modules/pi-lens/dist/clients/security-scan-client.js +157 -24
  199. package/node_modules/pi-lens/dist/clients/sg-runner.js +116 -22
  200. package/node_modules/pi-lens/dist/clients/single-flight.js +158 -0
  201. package/node_modules/pi-lens/dist/clients/stale-marker.js +17 -0
  202. package/node_modules/pi-lens/dist/clients/startup-timing.js +19 -1
  203. package/node_modules/pi-lens/dist/clients/test-runner-client.js +151 -16
  204. package/node_modules/pi-lens/dist/clients/tool-event.js +56 -5
  205. package/node_modules/pi-lens/dist/clients/tool-policy.js +125 -20
  206. package/node_modules/pi-lens/dist/clients/tree-sitter-cache.js +75 -1
  207. package/node_modules/pi-lens/dist/clients/tree-sitter-client.js +769 -64
  208. package/node_modules/pi-lens/dist/clients/tree-sitter-shared.js +11 -0
  209. package/node_modules/pi-lens/dist/clients/tree-sitter-symbol-extractor.js +90 -0
  210. package/node_modules/pi-lens/dist/clients/trivy-client.js +19 -1
  211. package/node_modules/pi-lens/dist/clients/widget-state.js +336 -36
  212. package/node_modules/pi-lens/dist/clients/zizmor-config.js +214 -14
  213. package/node_modules/pi-lens/dist/index.js +67738 -55739
  214. package/node_modules/pi-lens/dist/tools/lens-diagnostics.js +466 -54
  215. package/node_modules/pi-lens/dist/tools/lsp-diagnostics.js +132 -43
  216. package/node_modules/pi-lens/dist/tools/lsp-navigation.js +46 -7
  217. package/node_modules/pi-lens/docs/agent-guide.md +6 -1
  218. package/node_modules/pi-lens/docs/ast-grep_rules_catalog.md +515 -506
  219. package/node_modules/pi-lens/docs/dispositions.md +21 -0
  220. package/node_modules/pi-lens/docs/features.md +19 -5
  221. package/node_modules/pi-lens/docs/globalconfig.md +3 -0
  222. package/node_modules/pi-lens/docs/language-coverage.md +71 -5
  223. package/node_modules/pi-lens/docs/mcp.md +2 -2
  224. package/node_modules/pi-lens/docs/servercapabilities.md +4 -1
  225. package/node_modules/pi-lens/docs/settings.md +19 -1
  226. package/node_modules/pi-lens/docs/usage.md +1 -0
  227. package/node_modules/pi-lens/package.json +10 -6
  228. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/__snapshots__/no-reflect-get-js-snapshot.yml +23 -0
  229. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/__snapshots__/no-reflect-get-snapshot.yml +23 -0
  230. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/__snapshots__/ts-void-zero-snapshot.yml +23 -0
  231. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-bare-object-param-test.yml +16 -0
  232. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-chained-type-assertions-test.yml +15 -1
  233. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-compile-call-test.yml +29 -1
  234. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-dupe-class-members-test.yml +33 -0
  235. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-inner-html-js-test.yml +7 -1
  236. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-inner-html-test.yml +7 -1
  237. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-known-value-widening-test.yml +9 -0
  238. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-non-null-assertion-test.yml +216 -0
  239. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-reflect-apply-js-test.yml +2 -0
  240. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-reflect-apply-test.yml +2 -0
  241. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-reflect-get-js-test.yml +2 -1
  242. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-reflect-get-test.yml +2 -1
  243. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-return-value-in-generator-test.yml +12 -0
  244. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-runtime-typeof-test.yml +33 -0
  245. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-server-bind-wildcard-test.yml +9 -0
  246. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-shape-in-symbol-names-test.yml +13 -0
  247. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-sql-in-code-js-test.yml +5 -1
  248. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-sql-in-code-test.yml +5 -1
  249. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-unknown-parameters-test.yml +12 -0
  250. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-unknown-returns-test.yml +16 -0
  251. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-unsafe-dictionary-any-test.yml +10 -0
  252. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/no-unsafe-dictionary-unknown-test.yml +8 -0
  253. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/redundant-unsafe-function-test.yml +35 -0
  254. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/require-safety-comment-for-as-unknown-as-test.yml +177 -0
  255. package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/ts-void-zero-test.yml +1 -0
  256. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-await-expression-member-js.yml +13 -2
  257. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-await-expression-member.yml +13 -2
  258. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-bare-host-path-in-win32-branch.yml +2 -0
  259. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-bare-object-param.yml +60 -0
  260. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-chained-type-assertions.yml +57 -12
  261. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-compile-call.yml +63 -5
  262. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-dupe-class-members.yml +52 -8
  263. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-inner-html-js.yml +27 -2
  264. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-inner-html.yml +27 -2
  265. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-known-value-widening.yml +71 -0
  266. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-non-null-assertion.yml +316 -0
  267. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-raw-json-store-write.yml +7 -0
  268. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-reflect-apply-js.yml +22 -2
  269. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-reflect-apply.yml +23 -2
  270. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-reflect-get-js.yml +33 -5
  271. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-reflect-get.yml +34 -8
  272. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-return-value-in-generator.yml +22 -5
  273. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-runtime-typeof.yml +121 -0
  274. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-server-bind-wildcard.yml +23 -7
  275. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-shape-in-symbol-names.yml +68 -0
  276. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-sql-in-code-js.yml +41 -2
  277. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-sql-in-code.yml +41 -2
  278. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-unknown-laundering.yml +6 -1
  279. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-unknown-parameters.yml +67 -0
  280. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-unknown-returns.yml +106 -0
  281. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-unsafe-dictionary-any.yml +50 -0
  282. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-unsafe-dictionary-unknown.yml +56 -0
  283. package/node_modules/pi-lens/rules/ast-grep-rules/rules/no-win32-isabsolute-for-qualification.yml +2 -0
  284. package/node_modules/pi-lens/rules/ast-grep-rules/rules/redundant-unsafe-function.yml +32 -0
  285. package/node_modules/pi-lens/rules/ast-grep-rules/rules/require-safety-comment-for-as-unknown-as.yml +184 -0
  286. package/node_modules/pi-lens/rules/ast-grep-rules/rules/ts-void-zero.yml +5 -0
  287. package/node_modules/pi-lens/rules/ast-grep-rules/self-scan-baseline.json +5 -0
  288. package/node_modules/pi-lens/scripts/analyze-pi-lens-logs.mjs +53 -1
  289. package/node_modules/pi-lens/scripts/download-grammars.js +25 -0
  290. package/node_modules/pi-lens/scripts/grammars.lock.json +9 -0
  291. package/node_modules/pi-lens/skills/pi-lens-ast-grep/SKILL.md +2 -0
  292. package/node_modules/pi-lens/skills/pi-lens-write-ast-grep-rule/SKILL.md +80 -3
  293. package/node_modules/pi-lens/skills/pi-lens-write-ast-grep-rule/reference.md +41 -0
  294. package/node_modules/pi-lens/vendor/grammars/tree-sitter-cue.wasm +0 -0
  295. package/node_modules/pi-mcp-adapter/CHANGELOG.md +36 -0
  296. package/node_modules/pi-mcp-adapter/README.md +47 -5
  297. package/node_modules/pi-mcp-adapter/cli.js +163 -1
  298. package/node_modules/pi-mcp-adapter/commands.ts +128 -22
  299. package/node_modules/pi-mcp-adapter/config.ts +25 -7
  300. package/node_modules/pi-mcp-adapter/direct-tools.ts +5 -4
  301. package/node_modules/pi-mcp-adapter/index.ts +219 -32
  302. package/node_modules/pi-mcp-adapter/init.ts +36 -4
  303. package/node_modules/pi-mcp-adapter/lifecycle.ts +315 -24
  304. package/node_modules/pi-mcp-adapter/mcp-auth-flow.ts +39 -6
  305. package/node_modules/pi-mcp-adapter/mcp-bearer-store.ts +387 -0
  306. package/node_modules/pi-mcp-adapter/mcp-callback-server.ts +27 -3
  307. package/node_modules/pi-mcp-adapter/mcp-code.ts +4 -0
  308. package/node_modules/pi-mcp-adapter/mcp-panel.ts +42 -13
  309. package/node_modules/pi-mcp-adapter/metadata-cache.ts +32 -21
  310. package/node_modules/pi-mcp-adapter/package-mcp-loader.ts +168 -0
  311. package/node_modules/pi-mcp-adapter/package.json +3 -1
  312. package/node_modules/pi-mcp-adapter/proxy-modes.ts +5 -4
  313. package/node_modules/pi-mcp-adapter/request-headers-command.ts +31 -37
  314. package/node_modules/pi-mcp-adapter/search-ranking.ts +92 -22
  315. package/node_modules/pi-mcp-adapter/server-manager.ts +140 -2
  316. package/node_modules/pi-mcp-adapter/session-recovery.ts +7 -0
  317. package/node_modules/pi-mcp-adapter/tool-approval.ts +15 -2
  318. package/node_modules/pi-mcp-adapter/tool-result-renderer.ts +44 -4
  319. package/node_modules/pi-mcp-adapter/types.ts +2 -0
  320. package/node_modules/pi-mcp-adapter/ui-server.ts +18 -16
  321. package/node_modules/pi-mcp-adapter/utils.ts +70 -0
  322. package/node_modules/pi-web-access/CHANGELOG.md +23 -0
  323. package/node_modules/pi-web-access/README.md +27 -9
  324. package/node_modules/pi-web-access/anysearch.ts +4 -2
  325. package/node_modules/pi-web-access/brave.ts +16 -4
  326. package/node_modules/pi-web-access/curator-page.ts +6 -3
  327. package/node_modules/pi-web-access/curator-server.ts +4 -1
  328. package/node_modules/pi-web-access/exa.ts +18 -7
  329. package/node_modules/pi-web-access/extract.ts +28 -3
  330. package/node_modules/pi-web-access/gemini-search.ts +16 -4
  331. package/node_modules/pi-web-access/index.ts +41 -57
  332. package/node_modules/pi-web-access/package.json +1 -1
  333. package/node_modules/pi-web-access/page-query.ts +4 -2
  334. package/node_modules/pi-web-access/parallel-mcp.ts +249 -0
  335. package/node_modules/pi-web-access/parallel.ts +2 -2
  336. package/node_modules/pi-web-access/query-rewrite.ts +51 -0
  337. package/node_modules/pi-web-access/serper.ts +211 -0
  338. package/node_modules/pi-web-access/summary-model-scope.ts +12 -4
  339. package/node_modules/pi-web-access/summary-review.ts +44 -22
  340. package/node_modules/pi-web-access/tavily.ts +16 -4
  341. package/node_modules/pi-web-access/utils.ts +87 -0
  342. package/node_modules/pi-web-access/valyu.ts +199 -0
  343. package/package.json +13 -13
@@ -0,0 +1,211 @@
1
+ import { existsSync, readFileSync } from "node:fs";
2
+ import { activityMonitor } from "./activity.ts";
3
+ import type { SearchOptions, SearchResponse } from "./perplexity.ts";
4
+ import { hasCredentialSource, redactCredential, resolveCredential } from "./credential-source.ts";
5
+ import { getWebSearchConfigPath } from "./utils.ts";
6
+
7
+ const SERPER_SEARCH_URL = "https://google.serper.dev/search";
8
+ const CONFIG_PATH = getWebSearchConfigPath();
9
+ const SEARCH_TIMEOUT_MS = 60_000;
10
+ const RECENCY_TBS: Record<NonNullable<SearchOptions["recencyFilter"]>, string> = {
11
+ day: "qdr:d",
12
+ week: "qdr:w",
13
+ month: "qdr:m",
14
+ year: "qdr:y",
15
+ };
16
+
17
+ interface WebSearchConfig {
18
+ serperApiKey?: unknown;
19
+ }
20
+
21
+ interface SerperResult {
22
+ title?: unknown;
23
+ link?: unknown;
24
+ snippet?: unknown;
25
+ }
26
+
27
+ let cachedConfig: WebSearchConfig | null = null;
28
+
29
+ function loadConfig(): WebSearchConfig {
30
+ if (cachedConfig) return cachedConfig;
31
+ if (!existsSync(CONFIG_PATH)) {
32
+ cachedConfig = {};
33
+ return cachedConfig;
34
+ }
35
+ const raw = readFileSync(CONFIG_PATH, "utf-8");
36
+ let parsed: unknown;
37
+ try {
38
+ parsed = JSON.parse(raw);
39
+ } catch (err) {
40
+ const message = err instanceof Error ? err.message : String(err);
41
+ throw new Error(`Failed to parse ${CONFIG_PATH}: ${message}`);
42
+ }
43
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
44
+ throw new Error(`Invalid config in ${CONFIG_PATH}: expected a JSON object`);
45
+ }
46
+ cachedConfig = parsed as WebSearchConfig;
47
+ return cachedConfig;
48
+ }
49
+
50
+ async function getApiKey(signal?: AbortSignal): Promise<string | null> {
51
+ return resolveCredential({
52
+ provider: "Serper",
53
+ configuredValue: loadConfig().serperApiKey,
54
+ environmentValue: process.env.SERPER_API_KEY,
55
+ signal,
56
+ });
57
+ }
58
+
59
+ async function requireApiKey(signal?: AbortSignal): Promise<string> {
60
+ const apiKey = await getApiKey(signal);
61
+ if (!apiKey) {
62
+ throw new Error(
63
+ "Serper API key not found. Either:\n" +
64
+ ` 1. Create ${CONFIG_PATH} with { "serperApiKey": "your-key" }\n` +
65
+ " 2. Set SERPER_API_KEY environment variable\n" +
66
+ "Get a key at https://serper.dev",
67
+ );
68
+ }
69
+ return apiKey;
70
+ }
71
+
72
+ function normalizeCount(value: number | undefined): number {
73
+ if (typeof value !== "number" || !Number.isFinite(value)) return 5;
74
+ return Math.max(1, Math.min(Math.floor(value), 20));
75
+ }
76
+
77
+ interface DomainFilters {
78
+ include: string[];
79
+ exclude: string[];
80
+ }
81
+
82
+ function normalizeDomain(value: string): string | null {
83
+ let input = value.trim().toLowerCase();
84
+ if (!input) return null;
85
+ if (input.startsWith("-")) input = input.slice(1).trim();
86
+ if (!input) return null;
87
+ try {
88
+ const parsed = input.includes("://") ? new URL(input) : new URL(`https://${input}`);
89
+ input = parsed.hostname;
90
+ } catch {
91
+ input = input.split("/")[0]?.split(":")[0] ?? "";
92
+ }
93
+ input = input.replace(/^\.+|\.+$/g, "");
94
+ return /^[a-z0-9][a-z0-9.-]*\.[a-z]{2,}$/i.test(input) ? input : null;
95
+ }
96
+
97
+ function parseDomainFilter(domainFilter: string[] | undefined): DomainFilters {
98
+ const filters: DomainFilters = { include: [], exclude: [] };
99
+ for (const raw of domainFilter ?? []) {
100
+ const domain = normalizeDomain(raw);
101
+ if (!domain) continue;
102
+ const target = raw.trim().startsWith("-") ? filters.exclude : filters.include;
103
+ if (!target.includes(domain)) target.push(domain);
104
+ }
105
+ return filters;
106
+ }
107
+
108
+ function passesDomainFilters(url: string, filters: DomainFilters): boolean {
109
+ if (filters.include.length === 0 && filters.exclude.length === 0) return true;
110
+ let hostname: string;
111
+ try {
112
+ hostname = new URL(url).hostname.toLowerCase();
113
+ } catch {
114
+ return false;
115
+ }
116
+ const matches = (domain: string) => hostname === domain || hostname.endsWith(`.${domain}`);
117
+ if (filters.exclude.some(matches)) return false;
118
+ return filters.include.length === 0 || filters.include.some(matches);
119
+ }
120
+
121
+ function buildQuery(query: string, filters: DomainFilters): string {
122
+ const parts = [query];
123
+ if (filters.include.length === 1) parts.push(`site:${filters.include[0]}`);
124
+ if (filters.include.length > 1) parts.push(`(${filters.include.map(domain => `site:${domain}`).join(" OR ")})`);
125
+ for (const domain of filters.exclude) parts.push(`-site:${domain}`);
126
+ return parts.join(" ");
127
+ }
128
+
129
+ function errorMessage(err: unknown): string {
130
+ return err instanceof Error ? err.message : String(err);
131
+ }
132
+
133
+ function invalidResponse(message: string): Error {
134
+ return new Error(`Serper API returned invalid response: ${message}`);
135
+ }
136
+
137
+ function parseResponse(value: unknown): SerperResult[] {
138
+ if (!value || typeof value !== "object" || Array.isArray(value)) throw invalidResponse("expected an object envelope");
139
+ const envelope = value as Record<string, unknown>;
140
+ if (!Array.isArray(envelope.organic)) throw invalidResponse("expected organic array");
141
+ return envelope.organic as SerperResult[];
142
+ }
143
+
144
+ function buildAnswer(results: SearchResponse["results"]): string {
145
+ return results.map((result) => result.snippet
146
+ ? `${result.snippet}\nSource: ${result.title} (${result.url})`
147
+ : `Source: ${result.title} (${result.url})`).join("\n\n");
148
+ }
149
+
150
+ export function isSerperAvailable(): boolean {
151
+ return hasCredentialSource({ provider: "Serper", configuredValue: loadConfig().serperApiKey, environmentValue: process.env.SERPER_API_KEY });
152
+ }
153
+
154
+ export async function searchWithSerper(query: string, options: SearchOptions = {}): Promise<SearchResponse> {
155
+ const apiKey = await requireApiKey(options.signal);
156
+ const numResults = normalizeCount(options.numResults);
157
+ const filters = parseDomainFilter(options.domainFilter);
158
+ const requestCount = options.domainFilter?.length ? Math.min(20, numResults + 5) : numResults;
159
+ const activityId = activityMonitor.logStart({ type: "api", query });
160
+ let response: Response;
161
+ try {
162
+ response = await fetch(SERPER_SEARCH_URL, {
163
+ method: "POST",
164
+ headers: { "X-API-KEY": apiKey, "Content-Type": "application/json", Accept: "application/json" },
165
+ body: JSON.stringify({ q: buildQuery(query, filters), num: requestCount, ...(options.recencyFilter ? { tbs: RECENCY_TBS[options.recencyFilter] } : {}) }),
166
+ signal: options.signal ? AbortSignal.any([AbortSignal.timeout(SEARCH_TIMEOUT_MS), options.signal]) : AbortSignal.timeout(SEARCH_TIMEOUT_MS),
167
+ });
168
+ } catch (err) {
169
+ const message = errorMessage(err);
170
+ const redactedMessage = redactCredential(message, apiKey);
171
+ if (redactedMessage.toLowerCase().includes("abort")) activityMonitor.logComplete(activityId, 0);
172
+ else activityMonitor.logError(activityId, redactedMessage);
173
+ if (redactedMessage === message) throw err;
174
+ const redactedError = new Error(redactedMessage);
175
+ if (err instanceof Error) redactedError.name = err.name;
176
+ throw redactedError;
177
+ }
178
+ if (!response.ok) {
179
+ activityMonitor.logComplete(activityId, response.status);
180
+ const errorText = redactCredential(await response.text(), apiKey);
181
+ throw new Error(`Serper API error ${response.status}: ${errorText.slice(0, 300)}`);
182
+ }
183
+ let rawData: unknown;
184
+ try {
185
+ rawData = await response.json();
186
+ } catch (err) {
187
+ activityMonitor.logComplete(activityId, response.status);
188
+ throw new Error(`Serper API returned invalid JSON: ${errorMessage(err)}`);
189
+ }
190
+ let entries: SerperResult[];
191
+ try {
192
+ entries = parseResponse(rawData);
193
+ } catch (err) {
194
+ activityMonitor.logComplete(activityId, response.status);
195
+ throw err;
196
+ }
197
+ activityMonitor.logComplete(activityId, response.status);
198
+ const results: SearchResponse["results"] = [];
199
+ for (const entry of entries) {
200
+ if (!entry || typeof entry !== "object") continue;
201
+ if (typeof entry.link !== "string" || !entry.link) continue;
202
+ if (!passesDomainFilters(entry.link, filters)) continue;
203
+ results.push({
204
+ title: typeof entry.title === "string" && entry.title.trim() ? entry.title.trim() : `Source ${results.length + 1}`,
205
+ url: entry.link,
206
+ snippet: typeof entry.snippet === "string" ? entry.snippet : "",
207
+ });
208
+ if (results.length >= numResults) break;
209
+ }
210
+ return { answer: buildAnswer(results), results };
211
+ }
@@ -4,6 +4,8 @@ import { join } from "node:path";
4
4
 
5
5
  const THINKING_LEVELS = new Set(["off", "minimal", "low", "medium", "high", "xhigh", "max"]);
6
6
 
7
+ export type SummaryThinkingLevel = "off" | "minimal" | "low" | "medium" | "high" | "xhigh" | "max";
8
+
7
9
  interface SummaryModelScopeContext {
8
10
  cwd: string;
9
11
  isProjectTrusted(): boolean;
@@ -78,11 +80,17 @@ export function summaryModelValue(model: ModelLike): string {
78
80
  return `${model.provider}/${model.id}`;
79
81
  }
80
82
 
83
+ export function splitThinkingSuffix(value: string): { value: string; thinkingLevel?: SummaryThinkingLevel } {
84
+ const index = value.lastIndexOf(":");
85
+ if (index < 0) return { value };
86
+ const suffix = value.slice(index + 1);
87
+ return THINKING_LEVELS.has(suffix)
88
+ ? { value: value.slice(0, index), thinkingLevel: suffix as SummaryThinkingLevel }
89
+ : { value };
90
+ }
91
+
81
92
  function stripThinkingSuffix(pattern: string): string {
82
- const index = pattern.lastIndexOf(":");
83
- if (index < 0) return pattern;
84
- const suffix = pattern.slice(index + 1);
85
- return THINKING_LEVELS.has(suffix) ? pattern.slice(0, index) : pattern;
93
+ return splitThinkingSuffix(pattern).value;
86
94
  }
87
95
 
88
96
  function globToRegExp(pattern: string): RegExp {
@@ -1,6 +1,7 @@
1
- import { complete, type Api, type Message, type Model } from "@earendil-works/pi-ai/compat";
1
+ import { clampThinkingLevel, type ModelThinkingLevel, type ThinkingLevel } from "@earendil-works/pi-ai";
2
+ import { complete, completeSimple, type Api, type Message, type Model } from "@earendil-works/pi-ai/compat";
2
3
  import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
3
- import { findModelWithProviderRouting, loadEnabledModelPatterns, modelMatchesEnabledPatterns } from "./summary-model-scope.ts";
4
+ import { findModelWithProviderRouting, loadEnabledModelPatterns, modelMatchesEnabledPatterns, splitThinkingSuffix, type SummaryThinkingLevel } from "./summary-model-scope.ts";
4
5
  import type { QueryResultData } from "./storage.ts";
5
6
 
6
7
  type ProviderHeaders = Record<string, string | null>;
@@ -9,7 +10,11 @@ type SummaryModelRegistry = SummaryGenerationContext["modelRegistry"] & { comple
9
10
 
10
11
  const PREFERRED_SUMMARY_MODELS = [
11
12
  { provider: "anthropic", id: "claude-haiku-4-5" },
12
- { provider: "openai-codex", id: "gpt-5.3-codex-spark" },
13
+ { provider: "openai-codex", id: "gpt-5.6-luna" },
14
+ { provider: "openai-codex", id: "gpt-5.6-terra" },
15
+ { provider: "google", id: "gemini-3.6-flash" },
16
+ { provider: "openai", id: "gpt-5-mini" },
17
+ { provider: "deepseek", id: "deepseek-v4-flash" },
13
18
  ] as const;
14
19
 
15
20
  export const SUMMARY_GENERATION_DEADLINE_MS = 30_000;
@@ -186,28 +191,35 @@ export function buildDeterministicSummary(results: QueryResultData[]): { summary
186
191
  };
187
192
  }
188
193
 
189
- function parseModelSelector(value: string): { provider: string; id: string } {
190
- const slashIndex = value.indexOf("/");
191
- if (slashIndex <= 0 || slashIndex >= value.length - 1) {
194
+ function parseModelSelector(value: string): { provider: string; id: string; thinkingLevel?: SummaryThinkingLevel } {
195
+ const selector = splitThinkingSuffix(value);
196
+ const slashIndex = selector.value.indexOf("/");
197
+ if (slashIndex <= 0 || slashIndex >= selector.value.length - 1) {
192
198
  throw new Error(`Invalid summary model: ${value}. Use provider/model-id.`);
193
199
  }
194
200
  return {
195
- provider: value.slice(0, slashIndex),
196
- id: value.slice(slashIndex + 1),
201
+ provider: selector.value.slice(0, slashIndex),
202
+ id: selector.value.slice(slashIndex + 1),
203
+ thinkingLevel: selector.thinkingLevel,
197
204
  };
198
205
  }
199
206
 
207
+ function resolveThinkingLevel(model: Model<Api>, requested?: SummaryThinkingLevel): ModelThinkingLevel | undefined {
208
+ if (!requested) return undefined;
209
+ return clampThinkingLevel(model, requested as ModelThinkingLevel);
210
+ }
211
+
200
212
  async function resolveSummaryModelCandidates(
201
213
  ctx: SummaryGenerationContext,
202
214
  modelOverride?: string,
203
- ): Promise<{ candidates: Array<{ model: Model<Api>; apiKey?: string; headers?: ProviderHeaders }>; errors: string[] }> {
215
+ ): Promise<{ candidates: Array<{ model: Model<Api>; apiKey?: string; headers?: ProviderHeaders; thinkingLevel?: SummaryThinkingLevel }>; errors: string[] }> {
204
216
  const enabledModelPatterns = loadEnabledModelPatterns(ctx);
205
- const specs: Array<{ provider: string; id: string }> = [];
217
+ const specs: Array<{ provider: string; id: string; thinkingLevel?: SummaryThinkingLevel }> = [];
206
218
  const normalizedOverride = typeof modelOverride === "string" ? modelOverride.trim() : "";
207
219
  if (normalizedOverride.length > 0) specs.push(parseModelSelector(normalizedOverride));
208
220
  specs.push(...PREFERRED_SUMMARY_MODELS);
209
221
 
210
- const candidates: Array<{ model: Model<Api>; apiKey?: string; headers?: ProviderHeaders }> = [];
222
+ const candidates: Array<{ model: Model<Api>; apiKey?: string; headers?: ProviderHeaders; thinkingLevel?: SummaryThinkingLevel }> = [];
211
223
  const errors: string[] = [];
212
224
  const seen = new Set<string>();
213
225
  for (const spec of specs) {
@@ -229,7 +241,7 @@ async function resolveSummaryModelCandidates(
229
241
  errors.push(`No API key available for summary model ${value}`);
230
242
  continue;
231
243
  }
232
- candidates.push({ model, apiKey: auth.apiKey, headers: auth.headers });
244
+ candidates.push({ model, apiKey: auth.apiKey, headers: auth.headers, thinkingLevel: spec.thinkingLevel });
233
245
  }
234
246
  return { candidates, errors };
235
247
  }
@@ -285,7 +297,8 @@ export async function generateSummaryDraft(
285
297
  }
286
298
 
287
299
  const registry = ctx.modelRegistry as SummaryModelRegistry;
288
- const usesRegistryComplete = !completeFn && typeof registry.complete === "function";
300
+ const customCompleteFn = completeFn !== undefined;
301
+ const usesRegistryComplete = !customCompleteFn && typeof registry.complete === "function";
289
302
  completeFn ??= usesRegistryComplete ? registry.complete!.bind(registry) as CompleteFunction : complete;
290
303
 
291
304
  const generationStartedAt = Date.now();
@@ -343,7 +356,7 @@ export async function generateSummaryDraft(
343
356
  }
344
357
 
345
358
  let lastError = resolved.errors.at(-1);
346
- for (const { model, apiKey, headers } of resolved.candidates) {
359
+ for (const { model, apiKey, headers, thinkingLevel } of resolved.candidates) {
347
360
  const startedAt = Date.now();
348
361
  try {
349
362
  const userMessage: Message = {
@@ -351,25 +364,34 @@ export async function generateSummaryDraft(
351
364
  content: [{ type: "text", text: prompt }],
352
365
  timestamp: Date.now(),
353
366
  };
367
+ const requestedThinkingLevel = resolveThinkingLevel(model, thinkingLevel);
368
+ const enabledThinkingLevel = requestedThinkingLevel && requestedThinkingLevel !== "off"
369
+ ? requestedThinkingLevel as ThinkingLevel
370
+ : undefined;
371
+ const completionOptions = {
372
+ ...(usesRegistryComplete ? {} : { apiKey, headers }),
373
+ signal: completionSignal,
374
+ ...(requestedThinkingLevel ? { reasoning: requestedThinkingLevel } : {}),
375
+ ...(enabledThinkingLevel ? { reasoningEffort: enabledThinkingLevel } : {}),
376
+ };
377
+ const completion = thinkingLevel !== undefined && !customCompleteFn && !usesRegistryComplete
378
+ ? completeSimple(model, { messages: [userMessage] }, { apiKey, headers, signal: completionSignal, ...(enabledThinkingLevel ? { reasoning: enabledThinkingLevel } : {}) })
379
+ : completeFn(model, { messages: [userMessage] }, completionOptions);
354
380
 
355
- const response = await raceSummaryOperation(Promise.resolve(completeFn(
356
- model,
357
- { messages: [userMessage] },
358
- usesRegistryComplete ? { signal: completionSignal } : { apiKey, headers, signal: completionSignal },
359
- )));
381
+ const response = await raceSummaryOperation(Promise.resolve(completion));
360
382
  if (response.stopReason === "aborted") {
361
383
  throw new Error("Aborted");
362
384
  }
363
385
 
364
386
  const contentParts = Array.isArray(response.content) ? response.content : [];
365
387
  const summary = contentParts
366
- .map(part => getTextFromContentPart(part))
367
- .filter(text => text.trim().length > 0)
388
+ .map((part: unknown) => getTextFromContentPart(part))
389
+ .filter((text: string) => text.trim().length > 0)
368
390
  .join("\n")
369
391
  .trim();
370
392
 
371
393
  if (summary.length === 0) {
372
- const partTypes = contentParts.map(part => getContentPartType(part));
394
+ const partTypes = contentParts.map((part: unknown) => getContentPartType(part));
373
395
  const typesLabel = partTypes.length > 0 ? partTypes.join(", ") : "none";
374
396
  throw new Error(`Summary model returned empty response (content parts: ${typesLabel})`);
375
397
  }
@@ -3,14 +3,15 @@ import { activityMonitor } from "./activity.ts";
3
3
  import type { ExtractedContent } from "./extract.ts";
4
4
  import type { SearchOptions, SearchResponse } from "./perplexity.ts";
5
5
  import { hasCredentialSource, redactCredential, resolveCredential } from "./credential-source.ts";
6
- import { getWebSearchConfigPath } from "./utils.ts";
6
+ import { fetchWithCredentialRedirects, getWebSearchConfigPath, resolveApiBaseUrl } from "./utils.ts";
7
7
 
8
- const TAVILY_API_URL = "https://api.tavily.com/search";
8
+ const TAVILY_API_BASE_URL = "https://api.tavily.com";
9
9
  const CONFIG_PATH = getWebSearchConfigPath();
10
10
  const SEARCH_TIMEOUT_MS = 60_000;
11
11
 
12
12
  interface WebSearchConfig {
13
13
  tavilyApiKey?: unknown;
14
+ tavilyBaseUrl?: unknown;
14
15
  }
15
16
 
16
17
  interface TavilyResult {
@@ -57,6 +58,16 @@ async function getApiKey(signal?: AbortSignal): Promise<string | null> {
57
58
  });
58
59
  }
59
60
 
61
+ function getApiUrl(): string {
62
+ return `${resolveApiBaseUrl({
63
+ configKey: "tavilyBaseUrl",
64
+ configuredValue: loadConfig().tavilyBaseUrl,
65
+ defaultValue: TAVILY_API_BASE_URL,
66
+ environmentKey: "TAVILY_BASE_URL",
67
+ environmentValue: process.env.TAVILY_BASE_URL,
68
+ })}/search`;
69
+ }
70
+
60
71
  async function requireApiKey(signal?: AbortSignal): Promise<string> {
61
72
  const apiKey = await getApiKey(signal);
62
73
  if (!apiKey) {
@@ -152,6 +163,7 @@ export function isTavilyAvailable(): boolean {
152
163
  }
153
164
 
154
165
  export async function searchWithTavily(query: string, options: TavilySearchOptions = {}): Promise<SearchResponse> {
166
+ const apiUrl = getApiUrl();
155
167
  const apiKey = await requireApiKey(options.signal);
156
168
  const numResults = normalizeCount(options.numResults);
157
169
  const body: Record<string, unknown> = {
@@ -167,7 +179,7 @@ export async function searchWithTavily(query: string, options: TavilySearchOptio
167
179
  const activityId = activityMonitor.logStart({ type: "api", query });
168
180
  let response: Response;
169
181
  try {
170
- response = await fetch(TAVILY_API_URL, {
182
+ response = await fetchWithCredentialRedirects(apiUrl, {
171
183
  method: "POST",
172
184
  headers: {
173
185
  "Authorization": `Bearer ${apiKey}`,
@@ -175,7 +187,7 @@ export async function searchWithTavily(query: string, options: TavilySearchOptio
175
187
  },
176
188
  body: JSON.stringify(body),
177
189
  signal: requestSignal(options.signal),
178
- });
190
+ }, ["Authorization"]);
179
191
  } catch (err) {
180
192
  const message = errorMessage(err);
181
193
  const redactedMessage = redactCredential(message, apiKey);
@@ -12,6 +12,93 @@ export function getWebSearchConfigPath(): string {
12
12
  return join(getWebSearchConfigDir(), "web-search.json");
13
13
  }
14
14
 
15
+ interface ApiBaseUrlOptions {
16
+ configKey: string;
17
+ configuredValue: unknown;
18
+ defaultValue: string;
19
+ environmentKey: string;
20
+ environmentValue: string | undefined;
21
+ }
22
+
23
+ export function resolveApiBaseUrl(options: ApiBaseUrlOptions): string {
24
+ const fromEnvironment = options.environmentValue !== undefined;
25
+ const value = fromEnvironment ? options.environmentValue : options.configuredValue;
26
+ if (value === undefined) return options.defaultValue;
27
+
28
+ const source = fromEnvironment
29
+ ? options.environmentKey
30
+ : `${options.configKey} in ${getWebSearchConfigPath()}`;
31
+ if (typeof value !== "string" || value.trim().length === 0) {
32
+ throw new Error(`${source} must be an absolute HTTP(S) URL`);
33
+ }
34
+
35
+ let url: URL;
36
+ try {
37
+ url = new URL(value.trim());
38
+ } catch {
39
+ throw new Error(`${source} must be an absolute HTTP(S) URL`);
40
+ }
41
+ if (url.protocol !== "https:") {
42
+ throw new Error(`${source} must be an absolute HTTPS URL`);
43
+ }
44
+ if (url.username || url.password) {
45
+ throw new Error(`${source} must not include credentials`);
46
+ }
47
+ if (url.search || url.hash) {
48
+ throw new Error(`${source} must not include query parameters or fragments`);
49
+ }
50
+
51
+ url.search = "";
52
+ url.hash = "";
53
+ url.pathname = url.pathname.replace(/\/+$/, "");
54
+ return url.toString().replace(/\/+$/, "");
55
+ }
56
+
57
+ const API_REDIRECT_STATUSES = new Set([301, 302, 303, 307, 308]);
58
+ const API_REQUEST_BODY_HEADERS = ["Content-Encoding", "Content-Language", "Content-Location", "Content-Type"];
59
+ const MAX_API_REDIRECTS = 5;
60
+
61
+ export async function fetchWithCredentialRedirects(
62
+ url: string,
63
+ init: RequestInit,
64
+ credentialHeaders: readonly string[],
65
+ ): Promise<Response> {
66
+ let current = new URL(url);
67
+ let requestInit = init;
68
+
69
+ for (let redirects = 0; ; redirects++) {
70
+ const response = await fetch(current, { ...requestInit, redirect: "manual" });
71
+ if (!API_REDIRECT_STATUSES.has(response.status)) return response;
72
+
73
+ const location = response.headers.get("location");
74
+ if (!location) return response;
75
+ if (redirects === MAX_API_REDIRECTS) {
76
+ throw new Error(`Too many API redirects from ${url}`);
77
+ }
78
+
79
+ const next = new URL(location, current);
80
+ if (next.protocol !== "http:" && next.protocol !== "https:") {
81
+ throw new Error(`API redirect from ${current.origin} must use HTTP(S)`);
82
+ }
83
+ const method = requestInit.method?.toUpperCase() ?? "GET";
84
+ if (
85
+ ((response.status === 301 || response.status === 302) && method === "POST")
86
+ || (response.status === 303 && method !== "GET" && method !== "HEAD")
87
+ ) {
88
+ const headers = new Headers(requestInit.headers);
89
+ for (const name of API_REQUEST_BODY_HEADERS) headers.delete(name);
90
+ const { body: _body, ...withoutBody } = requestInit;
91
+ requestInit = { ...withoutBody, method: "GET", headers };
92
+ }
93
+ if (next.origin !== current.origin) {
94
+ const headers = new Headers(requestInit.headers);
95
+ for (const name of credentialHeaders) headers.delete(name);
96
+ requestInit = { ...requestInit, headers };
97
+ }
98
+ current = next;
99
+ }
100
+ }
101
+
15
102
  export interface CuratorNetworkConfig {
16
103
  /** Whether remote access was opted into via curatorRemote. */
17
104
  enabled: boolean;