@herbertgao/pi-extensions 2026.9.2 → 2026.9.4

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 (255) hide show
  1. package/README.md +4 -2
  2. package/THIRD_PARTY_NOTICES.md +49 -0
  3. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/compact-mode.ts +3 -2
  4. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/default-mode.ts +16 -11
  5. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/diff-renderer.ts +20 -6
  6. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/grouping.ts +12 -7
  7. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/result.ts +98 -0
  8. package/node_modules/@herbertgao/pi-cc-extensions/package.json +3 -3
  9. package/node_modules/@herbertgao/sol-pi/LICENSE +19 -0
  10. package/node_modules/@herbertgao/sol-pi/README.md +159 -0
  11. package/node_modules/@herbertgao/sol-pi/SECURITY.md +26 -0
  12. package/node_modules/@herbertgao/sol-pi/THIRD_PARTY_NOTICES.md +19 -0
  13. package/node_modules/@herbertgao/sol-pi/agents-install.md +150 -0
  14. package/node_modules/@herbertgao/sol-pi/assets/sol-pi-hero.png +0 -0
  15. package/node_modules/@herbertgao/sol-pi/docs/compatibility.md +69 -0
  16. package/node_modules/@herbertgao/sol-pi/docs/configuration.md +75 -0
  17. package/node_modules/@herbertgao/sol-pi/package.json +76 -0
  18. package/node_modules/@herbertgao/sol-pi/scripts/check-pi-compat.mjs +32 -0
  19. package/node_modules/@herbertgao/sol-pi/scripts/check-sol-pi-config.mjs +120 -0
  20. package/node_modules/@herbertgao/sol-pi/sol-pi.example.json +10 -0
  21. package/node_modules/@herbertgao/sol-pi/src/sol-pi/config.ts +135 -0
  22. package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/action-fusion/file-queue.ts +74 -0
  23. package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/action-fusion/index.ts +185 -0
  24. package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/action-fusion/then-run.ts +128 -0
  25. package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/evidence-preserving-reducer/archive.ts +53 -0
  26. package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/evidence-preserving-reducer/candidate.ts +101 -0
  27. package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/evidence-preserving-reducer/config.ts +71 -0
  28. package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/evidence-preserving-reducer/index.ts +220 -0
  29. package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/evidence-preserving-reducer/journal.ts +25 -0
  30. package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/evidence-preserving-reducer/provider.ts +164 -0
  31. package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/evidence-preserving-reducer/receipt.ts +177 -0
  32. package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/observation-pack/index.ts +227 -0
  33. package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/observation-pack/ledger.ts +20 -0
  34. package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/observation-pack/observation.ts +252 -0
  35. package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/online-context-compact/economics.ts +237 -0
  36. package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/online-context-compact/extension.ts +455 -0
  37. package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/online-context-compact/index.ts +49 -0
  38. package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/online-context-compact/plan.ts +79 -0
  39. package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/online-context-compact/state.ts +208 -0
  40. package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/online-context-compact/tools.ts +100 -0
  41. package/node_modules/@herbertgao/sol-pi/src/sol-pi/index.ts +42 -0
  42. package/node_modules/@herbertgao/sol-pi/src/sol-pi/runtime-paths.ts +17 -0
  43. package/node_modules/@herbertgao/sol-pi/src/sol-pi/tui.ts +71 -0
  44. package/node_modules/@narumitw/pi-btw/dist/index.ts +12 -2
  45. package/node_modules/@narumitw/pi-btw/dist/index.ts.map +2 -2
  46. package/node_modules/@narumitw/pi-btw/package.json +1 -1
  47. package/node_modules/@narumitw/pi-btw/src/btw.ts +13 -2
  48. package/node_modules/pi-antigravity/LICENSE +21 -0
  49. package/node_modules/pi-antigravity/README.md +194 -0
  50. package/node_modules/pi-antigravity/package.json +67 -0
  51. package/node_modules/pi-antigravity/src/auth/index.ts +14 -0
  52. package/node_modules/pi-antigravity/src/auth/oauth.ts +442 -0
  53. package/node_modules/pi-antigravity/src/client/client.ts +561 -0
  54. package/node_modules/pi-antigravity/src/client/index.ts +1 -0
  55. package/node_modules/pi-antigravity/src/diagnostics/diagnostics.ts +96 -0
  56. package/node_modules/pi-antigravity/src/diagnostics/index.ts +1 -0
  57. package/node_modules/pi-antigravity/src/image/image.ts +336 -0
  58. package/node_modules/pi-antigravity/src/image/index.ts +1 -0
  59. package/node_modules/pi-antigravity/src/index.ts +280 -0
  60. package/node_modules/pi-antigravity/src/models/discovery.ts +154 -0
  61. package/node_modules/pi-antigravity/src/models/grouping.ts +424 -0
  62. package/node_modules/pi-antigravity/src/models/index.ts +3 -0
  63. package/node_modules/pi-antigravity/src/models/models.ts +500 -0
  64. package/node_modules/pi-antigravity/src/stream/index.ts +1 -0
  65. package/node_modules/pi-antigravity/src/stream/stream.ts +1460 -0
  66. package/node_modules/pi-antigravity/src/types/enums.ts +42 -0
  67. package/node_modules/pi-antigravity/src/types/index.ts +2 -0
  68. package/node_modules/pi-antigravity/src/types/types.ts +292 -0
  69. package/node_modules/pi-antigravity/src/usage/index.ts +1 -0
  70. package/node_modules/pi-antigravity/src/usage/usage.ts +371 -0
  71. package/node_modules/pi-antigravity/src/utils/http.ts +91 -0
  72. package/node_modules/pi-antigravity/src/utils/index.ts +3 -0
  73. package/node_modules/pi-antigravity/src/utils/security.ts +73 -0
  74. package/node_modules/pi-antigravity/src/utils/util.ts +132 -0
  75. package/node_modules/pi-antigravity/tsconfig.json +21 -0
  76. package/node_modules/pi-lens/CHANGELOG.md +176 -0
  77. package/node_modules/pi-lens/README.md +13 -8
  78. package/node_modules/pi-lens/config/dependency-cruiser-eager-allowlist.json +3 -1
  79. package/node_modules/pi-lens/dist/clients/analysed-root.js +1 -0
  80. package/node_modules/pi-lens/dist/clients/ast-grep-tool-logger.js +1 -1
  81. package/node_modules/pi-lens/dist/clients/biome-client.js +13 -2
  82. package/node_modules/pi-lens/dist/clients/bounded-telemetry.js +2 -0
  83. package/node_modules/pi-lens/dist/clients/cache-observability.js +122 -1
  84. package/node_modules/pi-lens/dist/clients/complexity-client.js +5 -0
  85. package/node_modules/pi-lens/dist/clients/config-diagnostic-codes.js +2 -0
  86. package/node_modules/pi-lens/dist/clients/config-resolve.js +6 -3
  87. package/node_modules/pi-lens/dist/clients/config-schema.js +17 -0
  88. package/node_modules/pi-lens/dist/clients/config-warn.js +2 -2
  89. package/node_modules/pi-lens/dist/clients/dead-code-client.js +8 -1
  90. package/node_modules/pi-lens/dist/clients/dependency-checker.js +5 -1
  91. package/node_modules/pi-lens/dist/clients/dispatch/dispatcher.js +38 -15
  92. package/node_modules/pi-lens/dist/clients/dispatch/facts/function-facts.js +1 -1
  93. package/node_modules/pi-lens/dist/clients/dispatch/facts/import-facts.js +5 -1
  94. package/node_modules/pi-lens/dist/clients/dispatch/runners/actionlint.js +2 -1
  95. package/node_modules/pi-lens/dist/clients/dispatch/runners/biome-check.js +2 -3
  96. package/node_modules/pi-lens/dist/clients/dispatch/runners/cpp-check.js +4 -3
  97. package/node_modules/pi-lens/dist/clients/dispatch/runners/credo.js +2 -1
  98. package/node_modules/pi-lens/dist/clients/dispatch/runners/cue-vet.js +2 -1
  99. package/node_modules/pi-lens/dist/clients/dispatch/runners/dart-analyze.js +2 -1
  100. package/node_modules/pi-lens/dist/clients/dispatch/runners/detekt.js +2 -1
  101. package/node_modules/pi-lens/dist/clients/dispatch/runners/dotnet-build.js +2 -1
  102. package/node_modules/pi-lens/dist/clients/dispatch/runners/elixir-check.js +2 -1
  103. package/node_modules/pi-lens/dist/clients/dispatch/runners/eslint.js +2 -1
  104. package/node_modules/pi-lens/dist/clients/dispatch/runners/fish-indent.js +2 -1
  105. package/node_modules/pi-lens/dist/clients/dispatch/runners/gleam-check.js +2 -1
  106. package/node_modules/pi-lens/dist/clients/dispatch/runners/go-vet.js +2 -1
  107. package/node_modules/pi-lens/dist/clients/dispatch/runners/golangci-lint.js +2 -1
  108. package/node_modules/pi-lens/dist/clients/dispatch/runners/hadolint.js +2 -1
  109. package/node_modules/pi-lens/dist/clients/dispatch/runners/helm-lint.js +3 -1
  110. package/node_modules/pi-lens/dist/clients/dispatch/runners/helm-render.js +2 -1
  111. package/node_modules/pi-lens/dist/clients/dispatch/runners/htmlhint.js +2 -1
  112. package/node_modules/pi-lens/dist/clients/dispatch/runners/javac.js +2 -1
  113. package/node_modules/pi-lens/dist/clients/dispatch/runners/ktlint.js +2 -1
  114. package/node_modules/pi-lens/dist/clients/dispatch/runners/lsp.js +19 -1
  115. package/node_modules/pi-lens/dist/clients/dispatch/runners/markdownlint.js +3 -2
  116. package/node_modules/pi-lens/dist/clients/dispatch/runners/mypy.js +2 -1
  117. package/node_modules/pi-lens/dist/clients/dispatch/runners/oxlint.js +2 -1
  118. package/node_modules/pi-lens/dist/clients/dispatch/runners/php-lint.js +2 -1
  119. package/node_modules/pi-lens/dist/clients/dispatch/runners/phpstan.js +2 -1
  120. package/node_modules/pi-lens/dist/clients/dispatch/runners/prisma-validate.js +2 -1
  121. package/node_modules/pi-lens/dist/clients/dispatch/runners/psscriptanalyzer.js +12 -12
  122. package/node_modules/pi-lens/dist/clients/dispatch/runners/pyright.js +2 -1
  123. package/node_modules/pi-lens/dist/clients/dispatch/runners/rubocop.js +2 -1
  124. package/node_modules/pi-lens/dist/clients/dispatch/runners/ruff.js +2 -2
  125. package/node_modules/pi-lens/dist/clients/dispatch/runners/rust-clippy.js +12 -11
  126. package/node_modules/pi-lens/dist/clients/dispatch/runners/shellcheck.js +2 -1
  127. package/node_modules/pi-lens/dist/clients/dispatch/runners/shfmt.js +2 -1
  128. package/node_modules/pi-lens/dist/clients/dispatch/runners/spellcheck.js +2 -2
  129. package/node_modules/pi-lens/dist/clients/dispatch/runners/spotbugs.js +2 -1
  130. package/node_modules/pi-lens/dist/clients/dispatch/runners/sqlfluff.js +3 -2
  131. package/node_modules/pi-lens/dist/clients/dispatch/runners/stylelint.js +3 -2
  132. package/node_modules/pi-lens/dist/clients/dispatch/runners/swiftlint.js +2 -1
  133. package/node_modules/pi-lens/dist/clients/dispatch/runners/taplo.js +2 -1
  134. package/node_modules/pi-lens/dist/clients/dispatch/runners/terragrunt.js +2 -1
  135. package/node_modules/pi-lens/dist/clients/dispatch/runners/tflint.js +2 -1
  136. package/node_modules/pi-lens/dist/clients/dispatch/runners/trivy-config.js +2 -1
  137. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/availability-policy.js +5 -0
  138. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/candidate-probe.js +2 -2
  139. package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/runner-helpers.js +8 -2
  140. package/node_modules/pi-lens/dist/clients/dispatch/runners/vale.js +2 -1
  141. package/node_modules/pi-lens/dist/clients/dispatch/runners/yamllint.js +4 -3
  142. package/node_modules/pi-lens/dist/clients/dispatch/runners/zig-check.js +2 -1
  143. package/node_modules/pi-lens/dist/clients/effective-config.js +12 -2
  144. package/node_modules/pi-lens/dist/clients/extension-log.js +2 -0
  145. package/node_modules/pi-lens/dist/clients/file-role.js +18 -1
  146. package/node_modules/pi-lens/dist/clients/finding-delivery-gate.js +4 -0
  147. package/node_modules/pi-lens/dist/clients/formatters.js +54 -188
  148. package/node_modules/pi-lens/dist/clients/generation-guard.js +4 -0
  149. package/node_modules/pi-lens/dist/clients/gitleaks-client.js +42 -6
  150. package/node_modules/pi-lens/dist/clients/govulncheck-client.js +48 -7
  151. package/node_modules/pi-lens/dist/clients/installer/index.js +196 -52
  152. package/node_modules/pi-lens/dist/clients/jscpd-client.js +9 -1
  153. package/node_modules/pi-lens/dist/clients/knip-client.js +9 -2
  154. package/node_modules/pi-lens/dist/clients/language-profile.js +12 -2
  155. package/node_modules/pi-lens/dist/clients/latency-logger.js +2 -0
  156. package/node_modules/pi-lens/dist/clients/lens-config.js +4 -0
  157. package/node_modules/pi-lens/dist/clients/lsp/config.js +36 -7
  158. package/node_modules/pi-lens/dist/clients/lsp/diagnostic-binding.js +24 -1
  159. package/node_modules/pi-lens/dist/clients/lsp/document-drift.js +78 -0
  160. package/node_modules/pi-lens/dist/clients/lsp/index.js +281 -45
  161. package/node_modules/pi-lens/dist/clients/lsp/server.js +62 -15
  162. package/node_modules/pi-lens/dist/clients/lsp/workspace-diagnostics-cache.js +37 -4
  163. package/node_modules/pi-lens/dist/clients/mcp/analyze.js +3 -0
  164. package/node_modules/pi-lens/dist/clients/mcp/session.js +2 -0
  165. package/node_modules/pi-lens/dist/clients/opengrep-client.js +2 -0
  166. package/node_modules/pi-lens/dist/clients/package-manager.js +2 -1
  167. package/node_modules/pi-lens/dist/clients/php-cs-fixer-config.js +8 -3
  168. package/node_modules/pi-lens/dist/clients/pipeline.js +3 -2
  169. package/node_modules/pi-lens/dist/clients/project-diagnostics/fresh-fetch.js +38 -10
  170. package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/gitleaks.js +10 -9
  171. package/node_modules/pi-lens/dist/clients/project-lens-config.js +3 -0
  172. package/node_modules/pi-lens/dist/clients/read-guard-logger.js +2 -0
  173. package/node_modules/pi-lens/dist/clients/review-graph-logger.js +2 -0
  174. package/node_modules/pi-lens/dist/clients/ruff-client.js +18 -9
  175. package/node_modules/pi-lens/dist/clients/runtime-agent-end.js +9 -0
  176. package/node_modules/pi-lens/dist/clients/runtime-coordinator.js +5 -0
  177. package/node_modules/pi-lens/dist/clients/runtime-session.js +13 -4
  178. package/node_modules/pi-lens/dist/clients/runtime-tool-result.js +4 -1
  179. package/node_modules/pi-lens/dist/clients/runtime-turn.js +48 -1
  180. package/node_modules/pi-lens/dist/clients/security-scan-client.js +2 -2
  181. package/node_modules/pi-lens/dist/clients/session-event-guard.js +69 -1
  182. package/node_modules/pi-lens/dist/clients/sg-runner.js +2 -1
  183. package/node_modules/pi-lens/dist/clients/situational-tool-telemetry.js +108 -0
  184. package/node_modules/pi-lens/dist/clients/test-runner-client.js +291 -76
  185. package/node_modules/pi-lens/dist/clients/tool-config.js +199 -0
  186. package/node_modules/pi-lens/dist/clients/tool-cwd.js +299 -0
  187. package/node_modules/pi-lens/dist/clients/tool-probe.js +53 -0
  188. package/node_modules/pi-lens/dist/clients/tool-set-policy.js +42 -0
  189. package/node_modules/pi-lens/dist/clients/trivy-client.js +9 -1
  190. package/node_modules/pi-lens/dist/clients/turn-context.js +52 -0
  191. package/node_modules/pi-lens/dist/clients/widget-state.js +4 -3
  192. package/node_modules/pi-lens/dist/index.js +21969 -20005
  193. package/node_modules/pi-lens/dist/mcp/analyze-cli.js +3 -2
  194. package/node_modules/pi-lens/dist/mcp/server.js +135 -159
  195. package/node_modules/pi-lens/dist/tools/activate-tools.js +10 -7
  196. package/node_modules/pi-lens/dist/tools/ast-grep-outline.js +2 -13
  197. package/node_modules/pi-lens/dist/tools/ast-grep-replace.js +8 -13
  198. package/node_modules/pi-lens/dist/tools/ast-grep-search.js +83 -39
  199. package/node_modules/pi-lens/dist/tools/effective-config.js +4 -3
  200. package/node_modules/pi-lens/dist/tools/lens-diagnostic-mark.js +1 -10
  201. package/node_modules/pi-lens/dist/tools/lens-diagnostics.js +353 -85
  202. package/node_modules/pi-lens/dist/tools/lsp-diagnostics.js +21 -110
  203. package/node_modules/pi-lens/dist/tools/lsp-navigation.js +20 -44
  204. package/node_modules/pi-lens/dist/tools/module-report.js +5 -9
  205. package/node_modules/pi-lens/dist/tools/project-report.js +9 -17
  206. package/node_modules/pi-lens/dist/tools/render-compact.js +280 -1
  207. package/node_modules/pi-lens/dist/tools/symbol-search.js +2 -2
  208. package/node_modules/pi-lens/docs/agent-guide.md +3 -4
  209. package/node_modules/pi-lens/docs/agent-tools.md +46 -22
  210. package/node_modules/pi-lens/docs/configuration.md +9 -0
  211. package/node_modules/pi-lens/docs/environment-variables.md +0 -5
  212. package/node_modules/pi-lens/docs/features.md +5 -5
  213. package/node_modules/pi-lens/docs/globalconfig.md +11 -1
  214. package/node_modules/pi-lens/docs/mcp.md +13 -1
  215. package/node_modules/pi-lens/docs/pi-lens-fixer.md +71 -0
  216. package/node_modules/pi-lens/docs/pi-lens-investigator.md +15 -0
  217. package/node_modules/pi-lens/docs/pi-lens-monitor.md +88 -0
  218. package/node_modules/pi-lens/docs/pi-lens-reviewer.md +35 -0
  219. package/node_modules/pi-lens/docs/pi-lens-subagent.md +12 -4
  220. package/node_modules/pi-lens/docs/public-api-stability.md +1 -0
  221. package/node_modules/pi-lens/docs/real-harness.md +46 -0
  222. package/node_modules/pi-lens/docs/release-qa-baseline.md +5 -1
  223. package/node_modules/pi-lens/docs/servercapabilities.md +1 -6
  224. package/node_modules/pi-lens/docs/settings.md +37 -2
  225. package/node_modules/pi-lens/docs/tools_improvement2.md +4 -4
  226. package/node_modules/pi-lens/docs/tree-sitter_rules_catalog.md +1 -1
  227. package/node_modules/pi-lens/docs/usage.md +13 -3
  228. package/node_modules/pi-lens/package.json +10 -1
  229. package/node_modules/pi-lens/rules/tree-sitter-queries/typescript/sql-injection.yml +7 -3
  230. package/node_modules/pi-lens/skills/pi-lens-ast-grep/SKILL.md +8 -6
  231. package/node_modules/pi-lens/skills/pi-lens-lsp-navigation/SKILL.md +19 -9
  232. package/node_modules/pi-web-access/CHANGELOG.md +29 -0
  233. package/node_modules/pi-web-access/README.md +33 -22
  234. package/node_modules/pi-web-access/abortable.ts +17 -0
  235. package/node_modules/pi-web-access/crawl4ai.ts +204 -0
  236. package/node_modules/pi-web-access/credential-source.ts +1 -0
  237. package/node_modules/pi-web-access/curator-page.ts +20 -2
  238. package/node_modules/pi-web-access/curator-run.ts +44 -0
  239. package/node_modules/pi-web-access/curator-server.ts +3 -1
  240. package/node_modules/pi-web-access/duckduckgo.ts +1 -1
  241. package/node_modules/pi-web-access/extract.ts +57 -12
  242. package/node_modules/pi-web-access/fetch-params.ts +1 -1
  243. package/node_modules/pi-web-access/gemini-search.ts +9 -5
  244. package/node_modules/pi-web-access/index.ts +25 -18
  245. package/node_modules/pi-web-access/openai-search.ts +35 -13
  246. package/node_modules/pi-web-access/package.json +2 -2
  247. package/node_modules/pi-web-access/page-query.ts +5 -2
  248. package/node_modules/pi-web-access/query-rewrite.ts +5 -2
  249. package/node_modules/pi-web-access/serpapi.ts +220 -0
  250. package/node_modules/pi-web-access/ssrf-protection.ts +5 -1
  251. package/node_modules/pi-web-access/storage.ts +10 -0
  252. package/node_modules/pi-web-access/summary-review.ts +34 -21
  253. package/node_modules/pi-web-access/utils.ts +6 -4
  254. package/package.json +11 -5
  255. package/node_modules/pi-lens/dist/tools/ast-dump.js +0 -103
@@ -0,0 +1,49 @@
1
+ /*
2
+ * SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
3
+ * SPDX-License-Identifier: MIT
4
+ */
5
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
6
+ import { DEFAULT_CACHE_WRITE_READ_RATIO } from "../../config.ts";
7
+ import { createOnlineContextCompactExtension } from "./extension.ts";
8
+
9
+ export {
10
+ DEFAULT_COMPACTION_ECONOMICS,
11
+ decideCompaction,
12
+ estimateRemainingRequests,
13
+ type CompactionDecision,
14
+ type CompactionEconomics,
15
+ type CompactionReason,
16
+ } from "./economics.ts";
17
+ export {
18
+ BOUNDARY_COMPACTION_INSTRUCTIONS,
19
+ createOnlineContextCompactExtension,
20
+ DEFAULT_KEEP_RECENT_TOKENS,
21
+ DEFAULT_NATIVE_SUMMARY_TOKEN_ESTIMATE,
22
+ POST_COMPACTION_PLAN_REMINDER,
23
+ type OnlineContextCompactOptions,
24
+ resolveKeepRecentTokens,
25
+ } from "./extension.ts";
26
+ export {
27
+ analyzePlanTransition,
28
+ formatPlanSnapshot,
29
+ parsePlanSteps,
30
+ type PlanStatus,
31
+ type PlanStep,
32
+ } from "./plan.ts";
33
+ export {
34
+ initialOnlineState,
35
+ ONLINE_STATE_ENTRY,
36
+ restoreOnlineState,
37
+ type OnlineState,
38
+ type ProgressSummary,
39
+ } from "./state.ts";
40
+ export type { PlanProgress, PlanUpdateInput } from "./tools.ts";
41
+
42
+ export function registerOnlineContextCompact(
43
+ pi: ExtensionAPI,
44
+ cacheWriteReadRatio = DEFAULT_CACHE_WRITE_READ_RATIO,
45
+ ): void {
46
+ createOnlineContextCompactExtension({ cacheWriteReadRatio })(pi);
47
+ }
48
+
49
+ export default registerOnlineContextCompact;
@@ -0,0 +1,79 @@
1
+ /*
2
+ * SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
3
+ * SPDX-License-Identifier: MIT
4
+ */
5
+
6
+ export const PLAN_STATUSES = ["pending", "in_progress", "completed"] as const;
7
+
8
+ export type PlanStatus = (typeof PLAN_STATUSES)[number];
9
+
10
+ export type PlanStep = {
11
+ readonly id: string;
12
+ readonly goal: string;
13
+ readonly status: PlanStatus;
14
+ };
15
+
16
+ export type PlanTransition = {
17
+ readonly completedSteps: readonly PlanStep[];
18
+ readonly advice: readonly string[];
19
+ };
20
+
21
+ const MAX_PLAN_STEPS = 128;
22
+ const MAX_PLAN_STRING_BYTES = 16_384;
23
+
24
+ function isRecord(value: unknown): value is Readonly<Record<string, unknown>> {
25
+ return typeof value === "object" && value !== null && !Array.isArray(value);
26
+ }
27
+
28
+ function isBoundedString(value: unknown): value is string {
29
+ return typeof value === "string" && value.length > 0 && Buffer.byteLength(value) <= MAX_PLAN_STRING_BYTES;
30
+ }
31
+
32
+ function isPlanStatus(value: unknown): value is PlanStatus {
33
+ return PLAN_STATUSES.some((status) => status === value);
34
+ }
35
+
36
+ export function parsePlanSteps(value: unknown): readonly PlanStep[] | undefined {
37
+ if (!Array.isArray(value) || value.length > MAX_PLAN_STEPS) return;
38
+ const steps: PlanStep[] = [];
39
+ for (const item of value) {
40
+ if (
41
+ !isRecord(item) ||
42
+ Object.keys(item).length !== 3 ||
43
+ !isBoundedString(item.id) ||
44
+ !isBoundedString(item.goal) ||
45
+ !isPlanStatus(item.status)
46
+ ) {
47
+ return;
48
+ }
49
+ steps.push({ id: item.id, goal: item.goal, status: item.status });
50
+ }
51
+ if (new Set(steps.map((step) => step.id)).size !== steps.length) return;
52
+ return steps;
53
+ }
54
+
55
+ export function analyzePlanTransition(previous: readonly PlanStep[], next: readonly PlanStep[]): PlanTransition {
56
+ const previousById = new Map(previous.map((step) => [step.id, step]));
57
+ const completedSteps: PlanStep[] = [];
58
+ const advice: string[] = [];
59
+
60
+ for (const step of next) {
61
+ const prior = previousById.get(step.id);
62
+ if ((!prior || prior.status !== "completed") && step.status === "completed") completedSteps.push(step);
63
+ if (prior && prior.goal !== step.goal) {
64
+ advice.push(`Plan step ${JSON.stringify(step.id)} changed goal; reuse an id only for the same goal.`);
65
+ }
66
+ }
67
+
68
+ const inProgress = next.filter((step) => step.status === "in_progress").length;
69
+ if (inProgress > 1) advice.push("Keep at most one plan step in_progress.");
70
+ if (inProgress === 0 && next.some((step) => step.status === "pending")) {
71
+ advice.push("Mark one pending plan step in_progress before starting it.");
72
+ }
73
+
74
+ return { completedSteps, advice };
75
+ }
76
+
77
+ export function formatPlanSnapshot(steps: readonly PlanStep[]): string {
78
+ return `<sol-pi-plan task_status="active">${JSON.stringify({ steps })}</sol-pi-plan>`;
79
+ }
@@ -0,0 +1,208 @@
1
+ /*
2
+ * SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
3
+ * SPDX-License-Identifier: MIT
4
+ */
5
+ import type { ExtensionAPI, SessionEntry } from "@earendil-works/pi-coding-agent";
6
+ import { parsePlanSteps, type PlanStep } from "./plan.ts";
7
+
8
+ export const ONLINE_STATE_ENTRY = "sol-pi-online-context-state-v1";
9
+
10
+ export type ProgressSummary = {
11
+ readonly stepId: string;
12
+ readonly goal: string;
13
+ readonly filesChanged: readonly string[];
14
+ readonly verification: readonly string[];
15
+ readonly decisions: readonly string[];
16
+ readonly nextWork: readonly string[];
17
+ };
18
+
19
+ export type OnlineState = {
20
+ readonly version: 1;
21
+ readonly epoch: number;
22
+ readonly plan: readonly PlanStep[];
23
+ readonly pendingProgress: readonly ProgressSummary[];
24
+ readonly requestCount: number;
25
+ readonly lastBoundaryRequestCount: number;
26
+ readonly completedBoundaryRequestCounts: readonly number[];
27
+ readonly lastContextTokens: number | null;
28
+ readonly positiveContextDeltaTotal: number;
29
+ readonly positiveContextDeltaCount: number;
30
+ readonly nativeCompactionCount: number;
31
+ readonly cacheDebtTokens: number;
32
+ readonly cacheDebtRepaymentTokens: number;
33
+ };
34
+
35
+ export function initialOnlineState(): OnlineState {
36
+ return {
37
+ version: 1,
38
+ epoch: 0,
39
+ plan: [],
40
+ pendingProgress: [],
41
+ requestCount: 0,
42
+ lastBoundaryRequestCount: 0,
43
+ completedBoundaryRequestCounts: [],
44
+ lastContextTokens: null,
45
+ positiveContextDeltaTotal: 0,
46
+ positiveContextDeltaCount: 0,
47
+ nativeCompactionCount: 0,
48
+ cacheDebtTokens: 0,
49
+ cacheDebtRepaymentTokens: 0,
50
+ };
51
+ }
52
+
53
+ function nonNegativeInteger(value: unknown): value is number {
54
+ return typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
55
+ }
56
+
57
+ function finiteNonNegative(value: unknown): value is number {
58
+ return typeof value === "number" && Number.isFinite(value) && value >= 0;
59
+ }
60
+
61
+ function stringArray(value: unknown): readonly string[] | undefined {
62
+ if (!Array.isArray(value) || !value.every((item) => typeof item === "string")) return;
63
+ return [...value];
64
+ }
65
+
66
+ function progressSummary(value: unknown): ProgressSummary | undefined {
67
+ if (typeof value !== "object" || value === null || Array.isArray(value)) return;
68
+ const record = value as Record<string, unknown>;
69
+ const filesChanged = stringArray(record.filesChanged);
70
+ const verification = stringArray(record.verification);
71
+ const decisions = stringArray(record.decisions);
72
+ const nextWork = stringArray(record.nextWork);
73
+ if (
74
+ typeof record.stepId !== "string" ||
75
+ typeof record.goal !== "string" ||
76
+ !filesChanged ||
77
+ !verification ||
78
+ !decisions ||
79
+ !nextWork
80
+ ) {
81
+ return;
82
+ }
83
+ return { stepId: record.stepId, goal: record.goal, filesChanged, verification, decisions, nextWork };
84
+ }
85
+
86
+ function parseOnlineState(value: unknown): OnlineState | undefined {
87
+ if (typeof value !== "object" || value === null || Array.isArray(value)) return;
88
+ const record = value as Record<string, unknown>;
89
+ const plan = parsePlanSteps(record.plan);
90
+ const pendingProgress = Array.isArray(record.pendingProgress)
91
+ ? record.pendingProgress.map(progressSummary)
92
+ : undefined;
93
+ const completedBoundaryRequestCounts = Array.isArray(record.completedBoundaryRequestCounts)
94
+ ? record.completedBoundaryRequestCounts
95
+ : undefined;
96
+ if (
97
+ record.version !== 1 ||
98
+ !plan ||
99
+ !pendingProgress ||
100
+ pendingProgress.some((item) => item === undefined) ||
101
+ !completedBoundaryRequestCounts ||
102
+ !completedBoundaryRequestCounts.every(nonNegativeInteger) ||
103
+ !nonNegativeInteger(record.epoch) ||
104
+ !nonNegativeInteger(record.requestCount) ||
105
+ !nonNegativeInteger(record.lastBoundaryRequestCount) ||
106
+ record.lastBoundaryRequestCount > record.requestCount ||
107
+ !(record.lastContextTokens === null || nonNegativeInteger(record.lastContextTokens)) ||
108
+ !finiteNonNegative(record.positiveContextDeltaTotal) ||
109
+ !nonNegativeInteger(record.positiveContextDeltaCount) ||
110
+ !nonNegativeInteger(record.nativeCompactionCount) ||
111
+ !finiteNonNegative(record.cacheDebtTokens) ||
112
+ !finiteNonNegative(record.cacheDebtRepaymentTokens)
113
+ ) {
114
+ return;
115
+ }
116
+ return {
117
+ version: 1,
118
+ epoch: record.epoch,
119
+ plan,
120
+ pendingProgress: pendingProgress as readonly ProgressSummary[],
121
+ requestCount: record.requestCount,
122
+ lastBoundaryRequestCount: record.lastBoundaryRequestCount,
123
+ completedBoundaryRequestCounts: completedBoundaryRequestCounts as readonly number[],
124
+ lastContextTokens: record.lastContextTokens,
125
+ positiveContextDeltaTotal: record.positiveContextDeltaTotal,
126
+ positiveContextDeltaCount: record.positiveContextDeltaCount,
127
+ nativeCompactionCount: record.nativeCompactionCount,
128
+ cacheDebtTokens: record.cacheDebtTokens,
129
+ cacheDebtRepaymentTokens: record.cacheDebtRepaymentTokens,
130
+ };
131
+ }
132
+
133
+ export function restoreOnlineState(entries: readonly SessionEntry[]): OnlineState {
134
+ for (let index = entries.length - 1; index >= 0; index--) {
135
+ const entry = entries[index];
136
+ if (entry?.type !== "custom" || entry.customType !== ONLINE_STATE_ENTRY) continue;
137
+ const state = parseOnlineState(entry.data);
138
+ if (state) return state;
139
+ }
140
+ return initialOnlineState();
141
+ }
142
+
143
+ export function appendOnlineState(pi: ExtensionAPI, state: OnlineState): void {
144
+ pi.appendEntry(ONLINE_STATE_ENTRY, state);
145
+ }
146
+
147
+ export function recordProviderRequest(state: OnlineState, contextTokens: number): OnlineState {
148
+ const delta = state.lastContextTokens === null ? 0 : contextTokens - state.lastContextTokens;
149
+ const cacheDebtTokens = Math.max(0, state.cacheDebtTokens - state.cacheDebtRepaymentTokens);
150
+ return {
151
+ ...state,
152
+ requestCount: state.requestCount + 1,
153
+ lastContextTokens: contextTokens,
154
+ positiveContextDeltaTotal: state.positiveContextDeltaTotal + Math.max(0, delta),
155
+ positiveContextDeltaCount: state.positiveContextDeltaCount + (delta > 0 ? 1 : 0),
156
+ cacheDebtTokens,
157
+ cacheDebtRepaymentTokens: cacheDebtTokens === 0 ? 0 : state.cacheDebtRepaymentTokens,
158
+ };
159
+ }
160
+
161
+ export function recordBoundary(
162
+ state: OnlineState,
163
+ plan: readonly PlanStep[],
164
+ progress: ProgressSummary | undefined,
165
+ ): OnlineState {
166
+ const interval = Math.max(0, state.requestCount - state.lastBoundaryRequestCount);
167
+ return {
168
+ ...state,
169
+ plan: [...plan],
170
+ pendingProgress: progress ? [...state.pendingProgress, progress] : state.pendingProgress,
171
+ lastBoundaryRequestCount: state.requestCount,
172
+ completedBoundaryRequestCounts: [...state.completedBoundaryRequestCounts, interval],
173
+ };
174
+ }
175
+
176
+ export function recordCompaction(
177
+ state: OnlineState,
178
+ debt: { readonly debtTokens: number; readonly repaymentTokens: number },
179
+ ): OnlineState {
180
+ return {
181
+ ...state,
182
+ epoch: state.epoch + 1,
183
+ plan: [],
184
+ pendingProgress: [],
185
+ lastContextTokens: null,
186
+ positiveContextDeltaTotal: 0,
187
+ positiveContextDeltaCount: 0,
188
+ nativeCompactionCount: state.nativeCompactionCount + 1,
189
+ cacheDebtTokens: Math.max(0, debt.debtTokens),
190
+ cacheDebtRepaymentTokens: Math.max(0, debt.repaymentTokens),
191
+ };
192
+ }
193
+
194
+ export function recordCorrection(state: OnlineState): OnlineState {
195
+ return {
196
+ ...state,
197
+ epoch: state.epoch + 1,
198
+ plan: [],
199
+ pendingProgress: [],
200
+ lastBoundaryRequestCount: state.requestCount,
201
+ completedBoundaryRequestCounts: [],
202
+ lastContextTokens: null,
203
+ positiveContextDeltaTotal: 0,
204
+ positiveContextDeltaCount: 0,
205
+ cacheDebtTokens: 0,
206
+ cacheDebtRepaymentTokens: 0,
207
+ };
208
+ }
@@ -0,0 +1,100 @@
1
+ /*
2
+ * SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
3
+ * SPDX-License-Identifier: MIT
4
+ */
5
+ import type { AgentToolResult } from "@earendil-works/pi-agent-core";
6
+ import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
7
+ import { Text } from "@earendil-works/pi-tui";
8
+ import { Type } from "typebox";
9
+ import { renderSolPiTool } from "../../tui.ts";
10
+ import { PLAN_STATUSES, type PlanStep } from "./plan.ts";
11
+
12
+ export type PlanProgress = {
13
+ readonly files_changed: readonly string[];
14
+ readonly verification: readonly string[];
15
+ readonly decisions: readonly string[];
16
+ };
17
+
18
+ export type PlanUpdateInput = {
19
+ readonly toolCallId: string;
20
+ readonly steps: readonly PlanStep[];
21
+ readonly progress: PlanProgress | undefined;
22
+ readonly signal: AbortSignal | undefined;
23
+ readonly context: ExtensionContext;
24
+ };
25
+
26
+ export type OnlineToolHandlers = {
27
+ readonly updatePlan: (input: PlanUpdateInput) => Promise<AgentToolResult<Readonly<Record<string, unknown>>>>;
28
+ };
29
+
30
+ const progressSchema = Type.Object(
31
+ {
32
+ files_changed: Type.Array(Type.String({ maxLength: 1000 }), { maxItems: 128 }),
33
+ verification: Type.Array(Type.String({ maxLength: 1000 }), { maxItems: 64 }),
34
+ decisions: Type.Array(Type.String({ maxLength: 1000 }), { maxItems: 64 }),
35
+ },
36
+ { additionalProperties: false },
37
+ );
38
+
39
+ const planStepSchema = Type.Object(
40
+ {
41
+ id: Type.String({ minLength: 1, maxLength: 16_384 }),
42
+ goal: Type.String({ minLength: 1, maxLength: 16_384 }),
43
+ status: Type.Union(PLAN_STATUSES.map((status) => Type.Literal(status))),
44
+ },
45
+ { additionalProperties: false },
46
+ );
47
+
48
+ export function registerOnlineTools(pi: ExtensionAPI, handlers: OnlineToolHandlers): void {
49
+ pi.registerTool({
50
+ name: "update_plan",
51
+ label: "Update plan",
52
+ description:
53
+ "Replace the complete working plan. A newly completed step becomes a safe point where SoL-Pi may compact context if doing so is economical.",
54
+ promptSnippet: "Keep the working plan current",
55
+ promptGuidelines: [
56
+ "Send the complete plan on every update_plan call.",
57
+ "Keep at most one step in_progress and mark finished steps completed.",
58
+ "When completing a step, include concise progress evidence when available.",
59
+ ],
60
+ renderShell: "self",
61
+ parameters: Type.Object(
62
+ {
63
+ steps: Type.Array(planStepSchema, { minItems: 1, maxItems: 128 }),
64
+ progress: Type.Optional(progressSchema),
65
+ },
66
+ { additionalProperties: false },
67
+ ),
68
+ executionMode: "sequential",
69
+ execute: async (toolCallId, params, signal, _onUpdate, context) =>
70
+ await handlers.updatePlan({
71
+ toolCallId,
72
+ steps: params.steps,
73
+ progress: params.progress,
74
+ signal,
75
+ context,
76
+ }),
77
+ renderCall(params, theme) {
78
+ const completed = params.steps.filter((step) => step.status === "completed").length;
79
+ return renderSolPiTool(
80
+ theme,
81
+ "Online Context Compact",
82
+ "compacts only when projected savings are positive",
83
+ new Text(theme.fg("dim", `Plan: ${params.steps.length} steps, ${completed} completed`), 0, 0),
84
+ );
85
+ },
86
+ renderResult(result, { isPartial }, theme) {
87
+ const boundary = (result.details as { boundary?: boolean } | undefined)?.boundary === true;
88
+ return renderSolPiTool(
89
+ theme,
90
+ "Online Context Compact",
91
+ "compacts only when projected savings are positive",
92
+ new Text(
93
+ theme.fg(isPartial ? "warning" : "dim", isPartial ? "Updating plan..." : boundary ? "Progress boundary recorded" : "Plan recorded"),
94
+ 0,
95
+ 0,
96
+ ),
97
+ );
98
+ },
99
+ });
100
+ }
@@ -0,0 +1,42 @@
1
+ /*
2
+ * SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
3
+ * SPDX-License-Identifier: MIT
4
+ */
5
+
6
+ import { getAgentDir, type ExtensionAPI, type ExtensionContext, type ExtensionFactory } from "@earendil-works/pi-coding-agent";
7
+ import { loadSolPiConfig, type SolPiConfig } from "./config.ts";
8
+ import { registerActionFusion } from "./extensions/action-fusion/index.ts";
9
+ import { registerEvidencePreservingReducer } from "./extensions/evidence-preserving-reducer/index.ts";
10
+ import { registerObservationPack } from "./extensions/observation-pack/index.ts";
11
+ import { registerOnlineContextCompact } from "./extensions/online-context-compact/index.ts";
12
+
13
+ export function registerConfiguredFeatures(pi: ExtensionAPI, config: SolPiConfig): void {
14
+ if (config.actionFusion) registerActionFusion(pi);
15
+ if (config.observationPack) registerObservationPack(pi);
16
+ if (config.evidencePreservingReducer) {
17
+ registerEvidencePreservingReducer(pi, {
18
+ reducerModel: config.evidencePreservingReducerModel,
19
+ reducerProvider: config.evidencePreservingReducerProvider,
20
+ });
21
+ }
22
+ if (config.onlineContextCompact) registerOnlineContextCompact(pi, config.cacheWriteReadRatio);
23
+ }
24
+
25
+ export type SolPiConfigLoader = (ctx: ExtensionContext) => SolPiConfig;
26
+
27
+ export function createSolPiExtension(
28
+ loadConfig: SolPiConfigLoader = (ctx) => loadSolPiConfig(ctx.cwd, getAgentDir(), ctx.isProjectTrusted()),
29
+ ): ExtensionFactory {
30
+ return (pi) => {
31
+ let initialized = false;
32
+ pi.on("session_start", (_event, ctx) => {
33
+ if (initialized) return;
34
+ initialized = true;
35
+ registerConfiguredFeatures(pi, loadConfig(ctx));
36
+ });
37
+ };
38
+ }
39
+
40
+ export default function solPiExtension(pi: ExtensionAPI): void {
41
+ createSolPiExtension()(pi);
42
+ }
@@ -0,0 +1,17 @@
1
+ /*
2
+ * SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
3
+ * SPDX-License-Identifier: MIT
4
+ */
5
+
6
+ import { join } from "node:path";
7
+ import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
8
+
9
+ export function runtimeRoot(ctx: ExtensionContext): string {
10
+ const sessionDir = ctx.sessionManager.getSessionDir();
11
+ if (!sessionDir) throw new Error("SoL-Pi requires a persistent Pi session directory");
12
+ const sessionId = ctx.sessionManager.getSessionId();
13
+ if (!/^[a-z0-9][a-z0-9._-]*$/iu.test(sessionId)) {
14
+ throw new Error("SoL-Pi requires a safe Pi session id");
15
+ }
16
+ return join(sessionDir, "sol-pi", sessionId);
17
+ }
@@ -0,0 +1,71 @@
1
+ /*
2
+ * SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
3
+ * SPDX-License-Identifier: MIT
4
+ */
5
+
6
+ import type { ExtensionContext, Theme } from "@earendil-works/pi-coding-agent";
7
+ import { type Component, Container, Text } from "@earendil-works/pi-tui";
8
+
9
+ export type SolPiTuiMechanism =
10
+ | "Action Fusion"
11
+ | "Observation Pack"
12
+ | "Luna Delegating"
13
+ | "Online Context Compact";
14
+
15
+ const STATUS_KEY = "sol-pi-savings";
16
+ const STATUS_DURATION_MS = 4_000;
17
+ const INTEGER_FORMAT = new Intl.NumberFormat("en-US", { maximumFractionDigits: 0 });
18
+ const statusTimers = new WeakMap<ExtensionContext["ui"], ReturnType<typeof setTimeout>>();
19
+
20
+ export function formatSavingsCount(value: number, unit: string): string {
21
+ const count = Number.isFinite(value) ? Math.max(0, Math.round(value)) : 0;
22
+ return `${INTEGER_FORMAT.format(count)} ${unit}`;
23
+ }
24
+
25
+ function compactDecimal(value: number): string {
26
+ return value.toFixed(1).replace(/\.0$/u, "");
27
+ }
28
+
29
+ export function formatSavingsBytes(value: number): string {
30
+ const bytes = Number.isFinite(value) ? Math.max(0, Math.round(value)) : 0;
31
+ if (bytes >= 1024 * 1024) {
32
+ return `${compactDecimal(bytes / (1024 * 1024))} MiB removed from future prompts`;
33
+ }
34
+ if (bytes >= 1024) return `${compactDecimal(bytes / 1024)} KiB removed from future prompts`;
35
+ return `${INTEGER_FORMAT.format(bytes)} B removed from future prompts`;
36
+ }
37
+
38
+ export function renderSolPiTool(
39
+ theme: Theme,
40
+ mechanism: SolPiTuiMechanism,
41
+ saving: string,
42
+ base?: Component,
43
+ ): Component {
44
+ const container = new Container();
45
+ const title = `${theme.fg("warning", "⚡")} ${theme.fg("accent", theme.bold(`SoL-Pi · ${mechanism}`))}`;
46
+ container.addChild(new Text(title, 0, 0));
47
+ container.addChild(new Text(theme.fg("success", `Money saved · ${saving}`), 0, 0));
48
+ if (base) container.addChild(base);
49
+ return container;
50
+ }
51
+
52
+ export function showSolPiSavings(
53
+ context: ExtensionContext,
54
+ mechanism: SolPiTuiMechanism,
55
+ saving: string,
56
+ ): void {
57
+ if (context.mode !== "tui") return;
58
+ const message = `⚡ SoL-Pi · ${mechanism}\nMoney saved · ${saving}`;
59
+ context.ui.notify(message, "info");
60
+ context.ui.setStatus(STATUS_KEY, `⚡ ${mechanism} · ${saving}`);
61
+
62
+ const previous = statusTimers.get(context.ui);
63
+ if (previous) clearTimeout(previous);
64
+ const timer = setTimeout(() => {
65
+ if (statusTimers.get(context.ui) !== timer) return;
66
+ statusTimers.delete(context.ui);
67
+ context.ui.setStatus(STATUS_KEY, undefined);
68
+ }, STATUS_DURATION_MS);
69
+ if (typeof timer === "object" && "unref" in timer) timer.unref();
70
+ statusTimers.set(context.ui, timer);
71
+ }
@@ -2998,7 +2998,12 @@ async function resolveBtwModel({
2998
2998
  const fallbackAction = sameAsCurrent ? "no distinct current model is available" : `falling back to ${fallback}`;
2999
2999
  try {
3000
3000
  const auth = await modelRegistry.getApiKeyAndHeaders(configuredModel);
3001
- if (auth.ok && hasRequestAuth(auth)) return { model: configuredModel, auth };
3001
+ if (auth.ok && hasRequestAuth(auth)) {
3002
+ return {
3003
+ model: auth.baseUrl ? { ...configuredModel, baseUrl: auth.baseUrl } : configuredModel,
3004
+ auth
3005
+ };
3006
+ }
3002
3007
  const reason = auth.ok ? "has no request credentials" : auth.error;
3003
3008
  reportWarning(
3004
3009
  `pi-btw model ${settings.model} is unavailable (${reason}); ${fallbackAction}.`
@@ -3014,7 +3019,12 @@ async function resolveBtwModel({
3014
3019
  if (!currentModel) return void 0;
3015
3020
  try {
3016
3021
  const auth = await modelRegistry.getApiKeyAndHeaders(currentModel);
3017
- if (auth.ok && hasRequestAuth(auth)) return { model: currentModel, auth };
3022
+ if (auth.ok && hasRequestAuth(auth)) {
3023
+ return {
3024
+ model: auth.baseUrl ? { ...currentModel, baseUrl: auth.baseUrl } : currentModel,
3025
+ auth
3026
+ };
3027
+ }
3018
3028
  } catch {
3019
3029
  }
3020
3030
  return void 0;