@herbertgao/pi-extensions 2026.9.20 → 2026.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (282) hide show
  1. package/README.md +13 -8
  2. package/THIRD_PARTY_NOTICES.md +29 -3
  3. package/node_modules/@herbertgao/pi-bark/README.md +39 -3
  4. package/node_modules/@herbertgao/pi-bark/package.json +1 -1
  5. package/node_modules/@herbertgao/pi-bark/src/index.ts +275 -15
  6. package/node_modules/@herbertgao/pi-cc-extensions/README.en.md +50 -50
  7. package/node_modules/@herbertgao/pi-cc-extensions/README.md +7 -7
  8. package/node_modules/@herbertgao/pi-cc-extensions/extensions/config/config.ts +25 -2
  9. package/node_modules/@herbertgao/pi-cc-extensions/extensions/config/panel.ts +24 -1
  10. package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/compact-thinking.ts +7 -4
  11. package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/shell/footer.ts +47 -5
  12. package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/shell/mcp-chip.ts +136 -0
  13. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/compact-mode.ts +56 -11
  14. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/default-mode.ts +36 -4
  15. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/mouse/interaction.ts +11 -2
  16. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/codemode.ts +303 -0
  17. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/diff-header.ts +1 -1
  18. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/diff-highlight.ts +36 -0
  19. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/diff-presentation.ts +2 -2
  20. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/diff-write-render.ts +2 -2
  21. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/index.ts +88 -34
  22. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/line-width-safety.ts +6 -5
  23. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/diff/write-execution.ts +1 -1
  24. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/grouping.ts +31 -6
  25. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/message-display.ts +3 -3
  26. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/names.ts +3 -0
  27. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/result.ts +173 -36
  28. package/node_modules/@herbertgao/pi-cc-extensions/extensions/utils/ansi-text.ts +1 -0
  29. package/node_modules/@herbertgao/pi-cc-extensions/package.json +6 -4
  30. package/node_modules/@herbertgao/pi-subagents/CHANGELOG.md +6 -0
  31. package/node_modules/@herbertgao/pi-subagents/package.json +1 -1
  32. package/node_modules/@herbertgao/pi-subagents/src/mention-clone.ts +3 -2
  33. package/node_modules/@herbertgao/sol-pi/CONTRIBUTING.md +22 -0
  34. package/node_modules/@herbertgao/sol-pi/README.md +36 -17
  35. package/node_modules/@herbertgao/sol-pi/THIRD_PARTY_NOTICES.md +10 -6
  36. package/node_modules/@herbertgao/sol-pi/agents-install.md +15 -12
  37. package/node_modules/@herbertgao/sol-pi/docs/compatibility.md +24 -8
  38. package/node_modules/@herbertgao/sol-pi/docs/configuration.md +2 -2
  39. package/node_modules/@herbertgao/sol-pi/docs/star-history.md +18 -0
  40. package/node_modules/@herbertgao/sol-pi/package.json +4 -2
  41. package/node_modules/@herbertgao/sol-pi/scripts/check-sol-pi-config.mjs +1 -1
  42. package/node_modules/@herbertgao/sol-pi/src/sol-pi/config.ts +1 -1
  43. package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/action-fusion/file-queue.ts +23 -4
  44. package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/action-fusion/index.ts +32 -1
  45. package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/action-fusion/then-run.ts +3 -2
  46. package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/observation-pack/observation.ts +1 -1
  47. package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/online-context-compact/economics.ts +27 -7
  48. package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/online-context-compact/extension.ts +137 -41
  49. package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/online-context-compact/index.ts +1 -0
  50. package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/online-context-compact/plan.ts +2 -1
  51. package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/online-context-compact/projection.ts +58 -0
  52. package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/online-context-compact/state.ts +28 -6
  53. package/node_modules/@herbertgao/sol-pi/src/sol-pi/extensions/online-context-compact/tools.ts +2 -1
  54. package/node_modules/@herbertgao/sol-pi/src/sol-pi/runtime-paths.ts +15 -2
  55. package/node_modules/@juicesharp/rpiv-ask-user-question/package.json +2 -2
  56. package/node_modules/@pi-plugins/fast-mode/README.md +7 -1
  57. package/node_modules/@pi-plugins/fast-mode/dist/index.d.mts.map +1 -1
  58. package/node_modules/@pi-plugins/fast-mode/dist/index.mjs +7 -1
  59. package/node_modules/@pi-plugins/fast-mode/dist/index.mjs.map +1 -1
  60. package/node_modules/@pi-plugins/fast-mode/package.json +5 -5
  61. package/node_modules/pi-antigravity/README.md +3 -0
  62. package/node_modules/pi-antigravity/package.json +8 -8
  63. package/node_modules/pi-antigravity/src/auth/oauth.ts +6 -3
  64. package/node_modules/pi-antigravity/src/index.ts +114 -110
  65. package/node_modules/pi-antigravity/src/stream/stream.ts +1 -1
  66. package/node_modules/pi-antigravity/src/utils/util.ts +18 -1
  67. package/node_modules/pi-multi-account/CHANGELOG.md +20 -0
  68. package/node_modules/pi-multi-account/README.md +46 -8
  69. package/node_modules/pi-multi-account/config-file-transaction.ts +42 -0
  70. package/node_modules/pi-multi-account/index.ts +255 -269
  71. package/node_modules/pi-multi-account/model-pins.ts +36 -0
  72. package/node_modules/pi-multi-account/package.json +9 -7
  73. package/node_modules/pi-multi-account/slot-proxy-auth.ts +12 -2
  74. package/node_modules/pi-multi-account/slot-proxy.ts +27 -5
  75. package/node_modules/pi-web-access/CHANGELOG.md +17 -0
  76. package/node_modules/pi-web-access/README.md +6 -3
  77. package/node_modules/pi-web-access/brave-rate-limit.ts +170 -0
  78. package/node_modules/pi-web-access/brave.ts +28 -11
  79. package/node_modules/pi-web-access/dist/index.js +202 -33
  80. package/node_modules/pi-web-access/gemini-search.ts +2 -5
  81. package/node_modules/pi-web-access/index.ts +13 -7
  82. package/node_modules/pi-web-access/openai-search.ts +24 -10
  83. package/node_modules/pi-web-access/package.json +1 -1
  84. package/node_modules/pi-web-access/summary-review.ts +8 -2
  85. package/node_modules/{@herbertgao/resume-from → resume-from}/README.md +16 -12
  86. package/node_modules/{@herbertgao/resume-from → resume-from}/dist/adapters/claude-code/adapter.d.ts +0 -5
  87. package/node_modules/{@herbertgao/resume-from → resume-from}/dist/adapters/claude-code/adapter.js +2 -3
  88. package/node_modules/{@herbertgao/resume-from → resume-from}/dist/adapters/claude-code/contract.d.ts +2 -2
  89. package/node_modules/{@herbertgao/resume-from → resume-from}/dist/adapters/claude-code/reader.d.ts +3 -0
  90. package/node_modules/{@herbertgao/resume-from → resume-from}/dist/adapters/claude-code/reader.js +27 -15
  91. package/node_modules/{@herbertgao/resume-from/dist/adapters/pi → resume-from/dist/adapters/claude-code}/redaction.js +16 -3
  92. package/node_modules/{@herbertgao/resume-from → resume-from}/dist/adapters/claude-code/writer.d.ts +2 -4
  93. package/node_modules/{@herbertgao/resume-from → resume-from}/dist/adapters/claude-code/writer.js +1 -1
  94. package/node_modules/{@herbertgao/resume-from → resume-from}/dist/adapters/codex/contract.d.ts +2 -4
  95. package/node_modules/{@herbertgao/resume-from → resume-from}/dist/adapters/codex/index.js +1 -2
  96. package/node_modules/{@herbertgao/resume-from → resume-from}/dist/adapters/codex/read.d.ts +16 -0
  97. package/node_modules/resume-from/dist/adapters/codex/read.js +723 -0
  98. package/node_modules/{@herbertgao/resume-from/dist/adapters/claude-code → resume-from/dist/adapters/codex}/redaction.js +16 -3
  99. package/node_modules/{@herbertgao/resume-from → resume-from}/dist/adapters/codex/rollout.d.ts +44 -4
  100. package/node_modules/{@herbertgao/resume-from → resume-from}/dist/adapters/codex/rollout.js +88 -4
  101. package/node_modules/{@herbertgao/resume-from → resume-from}/dist/adapters/codex/write.d.ts +2 -2
  102. package/node_modules/{@herbertgao/resume-from → resume-from}/dist/adapters/codex/write.js +2 -2
  103. package/node_modules/{@herbertgao/resume-from → resume-from}/dist/adapters/contract.d.ts +5 -1
  104. package/node_modules/{@herbertgao/resume-from → resume-from}/dist/adapters/pi/adapter.js +10 -6
  105. package/node_modules/{@herbertgao/resume-from → resume-from}/dist/adapters/pi/contract.d.ts +2 -2
  106. package/node_modules/{@herbertgao/resume-from/dist/adapters/codex → resume-from/dist/adapters/pi}/redaction.js +16 -3
  107. package/node_modules/{@herbertgao/resume-from → resume-from}/dist/adapters/pi/serialize.d.ts +2 -4
  108. package/node_modules/{@herbertgao/resume-from → resume-from}/dist/adapters/pi/serialize.js +2 -2
  109. package/node_modules/{@herbertgao/resume-from → resume-from}/dist/host/cli/render.d.ts +1 -1
  110. package/node_modules/{@herbertgao/resume-from → resume-from}/dist/host/cli/render.js +4 -4
  111. package/node_modules/{@herbertgao/resume-from → resume-from}/dist/host/cli/runner.js +2 -2
  112. package/node_modules/{@herbertgao/resume-from → resume-from}/dist/host/contract.d.ts +2 -2
  113. package/node_modules/{@herbertgao/resume-from → resume-from}/dist/host/pi-extension/command.js +31 -8
  114. package/node_modules/{@herbertgao/resume-from → resume-from}/dist/host/wiring.d.ts +1 -1
  115. package/node_modules/{@herbertgao/resume-from → resume-from}/dist/host/wiring.js +1 -2
  116. package/node_modules/{@herbertgao/resume-from → resume-from}/dist/import/confirmation.d.ts +4 -1
  117. package/node_modules/{@herbertgao/resume-from → resume-from}/dist/import/confirmation.js +2 -2
  118. package/node_modules/{@herbertgao/resume-from → resume-from}/dist/import/contract.d.ts +8 -6
  119. package/node_modules/{@herbertgao/resume-from → resume-from}/dist/import/discovery/contract.d.ts +10 -5
  120. package/node_modules/{@herbertgao/resume-from → resume-from}/dist/import/discovery/finder.d.ts +2 -1
  121. package/node_modules/resume-from/dist/import/discovery/finder.js +269 -0
  122. package/node_modules/{@herbertgao/resume-from → resume-from}/dist/import/landing/contract.d.ts +3 -3
  123. package/node_modules/{@herbertgao/resume-from → resume-from}/dist/import/landing/lander.js +3 -3
  124. package/node_modules/resume-from/dist/import/pipeline.d.ts +28 -0
  125. package/node_modules/{@herbertgao/resume-from → resume-from}/dist/import/pipeline.js +31 -11
  126. package/node_modules/{@herbertgao/resume-from → resume-from}/dist/import/preview/builder.d.ts +2 -2
  127. package/node_modules/{@herbertgao/resume-from → resume-from}/dist/import/preview/builder.js +9 -5
  128. package/node_modules/resume-from/dist/import/preview/warnings.d.ts +14 -0
  129. package/node_modules/{@herbertgao/resume-from → resume-from}/dist/import/preview/warnings.js +26 -20
  130. package/node_modules/{@herbertgao/resume-from → resume-from}/dist/import/wiring.js +12 -2
  131. package/node_modules/{@herbertgao/resume-from → resume-from}/dist/platform/repo/contract.d.ts +9 -3
  132. package/node_modules/{@herbertgao/resume-from → resume-from}/dist/platform/repo/git.d.ts +4 -4
  133. package/node_modules/{@herbertgao/resume-from → resume-from}/dist/platform/repo/git.js +7 -4
  134. package/node_modules/resume-from/dist/platform/repo/reader.d.ts +3 -0
  135. package/node_modules/{@herbertgao/resume-from → resume-from}/dist/platform/repo/reader.js +60 -20
  136. package/node_modules/{@herbertgao/resume-from → resume-from}/dist/session/contract.d.ts +8 -1
  137. package/node_modules/{@herbertgao/resume-from → resume-from}/package.json +19 -32
  138. package/node_modules/{@herbertgao/resume-from → resume-from}/shims/pi/extensions/resume-from.js +2 -2
  139. package/package.json +17 -17
  140. package/node_modules/@herbertgao/resume-from/dist/adapters/codex/read.js +0 -270
  141. package/node_modules/@herbertgao/resume-from/dist/import/discovery/finder.js +0 -129
  142. package/node_modules/@herbertgao/resume-from/dist/import/pipeline.d.ts +0 -17
  143. package/node_modules/@herbertgao/resume-from/dist/import/preview/warnings.d.ts +0 -11
  144. package/node_modules/@herbertgao/resume-from/dist/platform/repo/reader.d.ts +0 -8
  145. /package/node_modules/{@herbertgao/resume-from → resume-from}/LICENSE +0 -0
  146. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/adapters/claude-code/contract.js +0 -0
  147. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/adapters/claude-code/entries.d.ts +0 -0
  148. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/adapters/claude-code/entries.js +0 -0
  149. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/adapters/claude-code/index.d.ts +0 -0
  150. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/adapters/claude-code/index.js +0 -0
  151. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/adapters/claude-code/layout.d.ts +0 -0
  152. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/adapters/claude-code/layout.js +0 -0
  153. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/adapters/claude-code/readback.d.ts +0 -0
  154. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/adapters/claude-code/readback.js +0 -0
  155. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/adapters/claude-code/redaction.d.ts +0 -0
  156. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/adapters/claude-code/validation.d.ts +0 -0
  157. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/adapters/claude-code/validation.js +0 -0
  158. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/adapters/codex/contract.js +0 -0
  159. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/adapters/codex/index.d.ts +0 -0
  160. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/adapters/codex/readback.d.ts +0 -0
  161. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/adapters/codex/readback.js +0 -0
  162. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/adapters/codex/redaction.d.ts +0 -0
  163. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/adapters/codex/validation.d.ts +0 -0
  164. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/adapters/codex/validation.js +0 -0
  165. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/adapters/contract.js +0 -0
  166. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/adapters/pi/adapter.d.ts +0 -0
  167. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/adapters/pi/contract.js +0 -0
  168. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/adapters/pi/format.d.ts +0 -0
  169. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/adapters/pi/format.js +0 -0
  170. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/adapters/pi/index.d.ts +0 -0
  171. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/adapters/pi/index.js +0 -0
  172. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/adapters/pi/parse.d.ts +0 -0
  173. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/adapters/pi/parse.js +0 -0
  174. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/adapters/pi/redaction.d.ts +0 -0
  175. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/adapters/pi/validate.d.ts +0 -0
  176. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/adapters/pi/validate.js +0 -0
  177. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/bin.d.ts +0 -0
  178. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/bin.js +0 -0
  179. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/contract.d.ts +0 -0
  180. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/contract.js +0 -0
  181. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/host/agents.d.ts +0 -0
  182. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/host/agents.js +0 -0
  183. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/host/cli/args.d.ts +0 -0
  184. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/host/cli/args.js +0 -0
  185. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/host/cli/contract.d.ts +0 -0
  186. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/host/cli/contract.js +0 -0
  187. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/host/cli/index.d.ts +0 -0
  188. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/host/cli/index.js +0 -0
  189. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/host/cli/presentation.d.ts +0 -0
  190. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/host/cli/presentation.js +0 -0
  191. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/host/cli/runner.d.ts +0 -0
  192. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/host/contract.js +0 -0
  193. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/host/entry.d.ts +0 -0
  194. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/host/entry.js +0 -0
  195. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/host/index.d.ts +0 -0
  196. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/host/index.js +0 -0
  197. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/host/pi-extension/command.d.ts +0 -0
  198. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/host/pi-extension/contract.d.ts +0 -0
  199. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/host/pi-extension/contract.js +0 -0
  200. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/host/pi-extension/index.d.ts +0 -0
  201. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/host/pi-extension/index.js +0 -0
  202. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/host/pi-extension/picker.d.ts +0 -0
  203. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/host/pi-extension/picker.js +0 -0
  204. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/host/pi-extension/presentation.d.ts +0 -0
  205. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/host/pi-extension/presentation.js +0 -0
  206. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/host/pi-extension/register.d.ts +0 -0
  207. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/host/pi-extension/register.js +0 -0
  208. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/host/pi-extension/ui.d.ts +0 -0
  209. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/host/pi-extension/ui.js +0 -0
  210. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/host/profile.d.ts +0 -0
  211. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/host/profile.js +0 -0
  212. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/host/registry.d.ts +0 -0
  213. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/host/registry.js +0 -0
  214. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/import/contract.js +0 -0
  215. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/import/discovery/contract.js +0 -0
  216. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/import/discovery/errors.d.ts +0 -0
  217. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/import/discovery/errors.js +0 -0
  218. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/import/discovery/homes.d.ts +0 -0
  219. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/import/discovery/homes.js +0 -0
  220. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/import/discovery/index.d.ts +0 -0
  221. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/import/discovery/index.js +0 -0
  222. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/import/discovery/ordering.d.ts +0 -0
  223. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/import/discovery/ordering.js +0 -0
  224. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/import/errors.d.ts +0 -0
  225. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/import/errors.js +0 -0
  226. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/import/index.d.ts +0 -0
  227. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/import/index.js +0 -0
  228. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/import/landing/contract.js +0 -0
  229. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/import/landing/errors.d.ts +0 -0
  230. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/import/landing/errors.js +0 -0
  231. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/import/landing/handover.d.ts +0 -0
  232. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/import/landing/handover.js +0 -0
  233. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/import/landing/index.d.ts +0 -0
  234. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/import/landing/index.js +0 -0
  235. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/import/landing/lander.d.ts +0 -0
  236. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/import/landing/marker.d.ts +0 -0
  237. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/import/landing/marker.js +0 -0
  238. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/import/preview/contract.d.ts +0 -0
  239. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/import/preview/contract.js +0 -0
  240. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/import/preview/format.d.ts +0 -0
  241. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/import/preview/format.js +0 -0
  242. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/import/preview/index.d.ts +0 -0
  243. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/import/preview/index.js +0 -0
  244. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/import/transfer/contract.d.ts +0 -0
  245. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/import/transfer/contract.js +0 -0
  246. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/import/transfer/index.d.ts +0 -0
  247. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/import/transfer/index.js +0 -0
  248. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/import/transfer/rules.d.ts +0 -0
  249. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/import/transfer/rules.js +0 -0
  250. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/import/wiring.d.ts +0 -0
  251. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/index.d.ts +0 -0
  252. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/index.js +0 -0
  253. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/platform/config/contract.d.ts +0 -0
  254. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/platform/config/contract.js +0 -0
  255. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/platform/config/defaults.d.ts +0 -0
  256. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/platform/config/defaults.js +0 -0
  257. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/platform/config/errors.d.ts +0 -0
  258. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/platform/config/errors.js +0 -0
  259. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/platform/config/index.d.ts +0 -0
  260. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/platform/config/index.js +0 -0
  261. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/platform/config/loader.d.ts +0 -0
  262. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/platform/config/loader.js +0 -0
  263. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/platform/config/paths.d.ts +0 -0
  264. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/platform/config/paths.js +0 -0
  265. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/platform/config/validate.d.ts +0 -0
  266. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/platform/config/validate.js +0 -0
  267. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/platform/repo/contract.js +0 -0
  268. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/platform/repo/index.d.ts +0 -0
  269. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/platform/repo/index.js +0 -0
  270. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/platform/store/contract.d.ts +0 -0
  271. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/platform/store/contract.js +0 -0
  272. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/platform/store/file-committer.d.ts +0 -0
  273. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/platform/store/file-committer.js +0 -0
  274. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/platform/store/index.d.ts +0 -0
  275. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/platform/store/index.js +0 -0
  276. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/platform/tokens/contract.d.ts +0 -0
  277. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/platform/tokens/contract.js +0 -0
  278. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/platform/tokens/estimator.d.ts +0 -0
  279. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/platform/tokens/estimator.js +0 -0
  280. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/platform/tokens/index.d.ts +0 -0
  281. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/platform/tokens/index.js +0 -0
  282. /package/node_modules/{@herbertgao/resume-from → resume-from}/dist/session/contract.js +0 -0
@@ -0,0 +1,36 @@
1
+ /** Explicit routing preferences; never foreground selections or authorization for a cheaper tier. */
2
+ import { mutateConfigFile } from "./config-file-transaction.ts";
3
+
4
+ export function validModelPinScope(scope: string): boolean {
5
+ return /^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(scope);
6
+ }
7
+
8
+ export function normalizeModelPins(value: unknown): Record<string, string> {
9
+ if (!value || typeof value !== "object" || Array.isArray(value)) return {};
10
+ return Object.fromEntries(Object.entries(value).filter(([scope, model]) =>
11
+ validModelPinScope(scope) && typeof model === "string" && model.trim().length > 0 &&
12
+ !/[\s\x00-\x1f]/.test(model.trim()),
13
+ ).map(([scope, model]) => [scope, (model as string).trim()]));
14
+ }
15
+
16
+ /** Change only one group's pin through the shared extension configuration transaction. */
17
+ export function persistModelPin(
18
+ path: string,
19
+ scope: string,
20
+ model: string | undefined,
21
+ canCommit: () => boolean = () => true,
22
+ ): Record<string, string> {
23
+ if (!validModelPinScope(scope) || (model !== undefined && normalizeModelPins({ [scope]: model })[scope] !== model)) {
24
+ throw new Error("invalid model pin");
25
+ }
26
+ const next = mutateConfigFile(path, disk => {
27
+ if (disk.pinnedModels !== undefined && (!disk.pinnedModels || typeof disk.pinnedModels !== "object" || Array.isArray(disk.pinnedModels))) {
28
+ throw new Error("pinnedModels must be an object");
29
+ }
30
+ const pins = { ...disk.pinnedModels };
31
+ if (model === undefined) delete pins[scope];
32
+ else pins[scope] = model;
33
+ return { pinnedModels: pins };
34
+ }, canCommit);
35
+ return normalizeModelPins(next.pinnedModels);
36
+ }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-multi-account",
3
- "version": "1.23.2",
3
+ "version": "1.24.0",
4
4
  "description": "Automatic multi-account failover & rotation for Pi Agent across Anthropic (Claude), OpenAI/ChatGPT Codex, Kimi For Coding, Cursor, Qwen/Alibaba, and Ollama. Auto-discovers authenticated accounts, grows the rotation on login, and drops accounts on logout, expiry, or quota/rate-limit errors",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -45,7 +45,9 @@
45
45
  "provider-payload-stream.ts",
46
46
  "auth-file-transaction.ts",
47
47
  "state-file-transaction.ts",
48
+ "config-file-transaction.ts",
48
49
  "model-catalog.ts",
50
+ "model-pins.ts",
49
51
  "usage.ts",
50
52
  "cursor-bridge.ts",
51
53
  "cursor-model-name.ts",
@@ -78,13 +80,13 @@
78
80
  "access": "public"
79
81
  },
80
82
  "peerDependencies": {
81
- "@earendil-works/pi-coding-agent": "*",
82
- "@earendil-works/pi-ai": "*"
83
+ "@earendil-works/pi-ai": "*",
84
+ "@earendil-works/pi-coding-agent": "*"
83
85
  },
84
86
  "devDependencies": {
85
- "@earendil-works/pi-coding-agent": "0.85.1",
86
- "@earendil-works/pi-ai": "0.85.1",
87
- "@types/node": "^22.20.1",
87
+ "@earendil-works/pi-ai": "0.87.1",
88
+ "@earendil-works/pi-coding-agent": "0.87.1",
89
+ "@types/node": "^22.20.4",
88
90
  "typescript": "^6.0.3"
89
91
  },
90
92
  "pi": {
@@ -93,7 +95,7 @@
93
95
  ]
94
96
  },
95
97
  "dependencies": {
96
- "@bufbuild/protobuf": "^2.14.0",
98
+ "@bufbuild/protobuf": "^2.15.0",
97
99
  "proper-lockfile": "4.1.2"
98
100
  },
99
101
  "overrides": {
@@ -28,6 +28,7 @@ import {
28
28
  } from "./cursor-bridge.ts";
29
29
  import {
30
30
  needsChildFacingApiKey,
31
+ isPlaceholderFor,
31
32
  placeholderKeyFor,
32
33
  proxyFamilyFor,
33
34
  type ProxyFamily,
@@ -67,7 +68,7 @@ export function isChildFacingPlaceholder(
67
68
  entry: AuthBlob | undefined,
68
69
  family: ProxyFamily,
69
70
  ): boolean {
70
- return entry?.type === "api_key" && entry.key === placeholderKeyFor(family);
71
+ return entry?.type === "api_key" && isPlaceholderFor(family, entry.key);
71
72
  }
72
73
 
73
74
  export function isChildFacingPlaceholderForSlot(
@@ -75,7 +76,9 @@ export function isChildFacingPlaceholderForSlot(
75
76
  slotId: string,
76
77
  ): boolean {
77
78
  const key = childFacingPlaceholderKey(slotId);
78
- return !!key && entry?.type === "api_key" && entry.key === key;
79
+ const family = proxyFamilyFor(slotId);
80
+ return !!key && entry?.type === "api_key" &&
81
+ (family ? isPlaceholderFor(family, entry.key) : entry.key === key);
79
82
  }
80
83
 
81
84
  /** What the parent should use for refresh/upstream: sidecar OAuth wins over a child-facing placeholder. */
@@ -101,6 +104,13 @@ export function applyShadowPlan(
101
104
  return { auth: { ...auth }, sidecar: { ...sidecar }, changed: false };
102
105
  }
103
106
  const entry = auth[slotId];
107
+ const hidden = sidecar[slotId];
108
+ if (isChildFacingPlaceholderForSlot(entry, slotId) && entry?.key !== facing.key &&
109
+ hidden?.type === "oauth" && typeof hidden.access === "string" && hidden.access.length > 0) {
110
+ // Upgrade only a publication backed by the parent's actual OAuth blob. Never rewrite a
111
+ // user's real API key or replace the hidden credential with a placeholder during migration.
112
+ return { auth: { ...auth, [slotId]: facing }, sidecar: { ...sidecar }, changed: true };
113
+ }
104
114
  if (entry?.type === "oauth" && typeof entry.access === "string" && entry.access.length > 0) {
105
115
  return {
106
116
  auth: { ...auth, [slotId]: facing },
@@ -36,6 +36,14 @@
36
36
  */
37
37
  export const PROXY_PLACEHOLDER_KEY = "pi-multi-account-proxy";
38
38
 
39
+ /**
40
+ * Non-secret Anthropic placeholder with the shape Pi's native transport recognizes as OAuth.
41
+ * Request identity, tool-name conversion and response mapping all depend on this marker; adding
42
+ * a Bearer header at the proxy is too late. The proxy replaces it with the real token on egress.
43
+ * The legacy generic placeholder remains recognizable for restoration during an upgrade.
44
+ */
45
+ export const ANTHROPIC_PROXY_PLACEHOLDER_KEY = "sk-ant-oat01-pi-multi-account-proxy";
46
+
39
47
  /**
40
48
  * The `apiKey` published for a **Codex** slot.
41
49
  *
@@ -60,9 +68,10 @@ export const PROXY_PLACEHOLDER_JWT = [
60
68
  "",
61
69
  ].join(".");
62
70
 
63
- /** Either published placeholder — neither is a credential, both mark "a child we published for". */
71
+ /** Current or legacy published placeholder — none is a real credential. */
64
72
  export function isPublishedPlaceholder(value: string | undefined): boolean {
65
- return value === PROXY_PLACEHOLDER_KEY || value === PROXY_PLACEHOLDER_JWT;
73
+ return value === PROXY_PLACEHOLDER_KEY || value === PROXY_PLACEHOLDER_JWT ||
74
+ value === ANTHROPIC_PROXY_PLACEHOLDER_KEY;
66
75
  }
67
76
 
68
77
  export type ProxyFamily = "anthropic" | "codex";
@@ -273,7 +282,14 @@ export function shapeUpstreamRequest(args: {
273
282
  }
274
283
  }
275
284
 
276
- return { ok: true, url: `${base}${rest}`, headers };
285
+ // Image tools append these endpoints directly to the slot base; chat already sends /codex.
286
+ // Leave all other paths and families intact, including usage requests.
287
+ const upstreamRest =
288
+ route.family === "codex" && /^\/images\/(?:generations|edits)(?:\?.*)?$/.test(rest)
289
+ ? `/codex${rest}`
290
+ : rest;
291
+
292
+ return { ok: true, url: `${base}${upstreamRest}`, headers };
277
293
  }
278
294
 
279
295
  /** Keep any beta flags the caller asked for, and add ours once. */
@@ -289,7 +305,13 @@ function mergeBeta(existing: string | undefined, required: string): string {
289
305
 
290
306
  /** The `apiKey` to publish for a slot of this family. */
291
307
  export function placeholderKeyFor(family: ProxyFamily): string {
292
- return family === "codex" ? PROXY_PLACEHOLDER_JWT : PROXY_PLACEHOLDER_KEY;
308
+ return family === "codex" ? PROXY_PLACEHOLDER_JWT : ANTHROPIC_PROXY_PLACEHOLDER_KEY;
309
+ }
310
+
311
+ /** Recognize the previous Anthropic publication without mistaking real tokens for placeholders. */
312
+ export function isPlaceholderFor(family: ProxyFamily, value: unknown): boolean {
313
+ return value === placeholderKeyFor(family) ||
314
+ (family === "anthropic" && value === PROXY_PLACEHOLDER_KEY);
293
315
  }
294
316
 
295
317
  /** The `models.json` route to publish for a slot served by this proxy. */
@@ -305,7 +327,7 @@ export function isOwnLoopbackPublication(
305
327
  if (!existing || typeof existing !== "object") return false;
306
328
  const rec = existing as Record<string, unknown>;
307
329
  return (
308
- rec.apiKey === placeholderKeyFor(family) &&
330
+ isPlaceholderFor(family, rec.apiKey) &&
309
331
  typeof rec.baseUrl === "string" &&
310
332
  rec.baseUrl.startsWith("http://127.0.0.1:")
311
333
  );
@@ -4,6 +4,23 @@ All notable changes to this project will be documented in this file.
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [0.35.0] - 2026-09-30
8
+
9
+ ### Highlights
10
+
11
+ - Tell the summarizer what matters to you, such as keeping prices, limits, and versions exactly as written.
12
+ - Brave search holds up on low-rate plans instead of failing when several searches run at once.
13
+ - OpenAI web search works when you signed in to Pi with your ChatGPT account.
14
+
15
+ ### Added
16
+
17
+ - Add your own instructions to search summaries with `summaryInstructions` in `web-search.json`, for example "keep prices, limits, and versions verbatim." They apply to summaries from the curator and the `auto-summary` workflow, and the built-in safety rules still apply. Thanks to [@rcharrisg](https://github.com/rcharrisg) for [PR #489](https://github.com/nicobailon/pi-web-access/pull/489).
18
+
19
+ ### Fixed
20
+
21
+ - Brave searches now wait their turn according to the rate limits Brave reports, so running several searches at once no longer overruns low-rate plans. If Brave says to slow down and the wait is short, the search retries once. If the wait is long, it fails right away and later searches respect the same wait. The 30-second search timeout includes this waiting. Thanks to [@saschaSpoonbill](https://github.com/saschaSpoonbill) for [PR #487](https://github.com/nicobailon/pi-web-access/pull/487).
22
+ - OpenAI web search now works when you signed in to Pi with "Sign in with ChatGPT" on the `openai` provider. Searches go to OpenAI's official API, and `auto` search tries OpenAI first for this login, as it already did for `openai-codex`. With an `openai` API key, `auto` still tries Exa first. Thanks to [@kasumikira](https://github.com/kasumikira) for [issue #488](https://github.com/nicobailon/pi-web-access/issues/488).
23
+
7
24
  ## [0.34.0] - 2026-09-30
8
25
 
9
26
  ### Highlights
@@ -18,7 +18,7 @@
18
18
 
19
19
  **Video Understanding** — Point it at a YouTube video or local screen recording and ask questions about what's on screen. Full transcripts, visual descriptions, and frame extraction at exact timestamps.
20
20
 
21
- **Smart Fallbacks** — Every capability has a fallback chain. Search tries configured SearXNG first for local/private search. When the active Pi model is `openai-codex`, it then tries Codex-backed OpenAI search. Otherwise it tries Exa before OpenAI, then Brave, Parallel, TinyFish, Search1API, Searchinfinity, Querit, Tavily, Firecrawl, Jina, SERPdive, Kagi, Bocha, Ollama, Perplexity, Gemini API, and Gemini Web when browser cookies are enabled. YouTube tries Gemini Web when enabled, then API, then Perplexity. Blocked pages try configured self-hosted Firecrawl or Crawl4AI first. Third-party hosted page fetchers require explicit `fetchRouting.allowRemoteHostedProviders` opt-in for remote HTTP(S) targets.
21
+ **Smart Fallbacks** — Every capability has a fallback chain. Search tries configured SearXNG first for local/private search. When the active Pi model uses a ChatGPT subscription (`openai-codex`, or `openai` with Sign in with ChatGPT), it then tries subscription-backed OpenAI search. Otherwise it tries Exa before OpenAI, then Brave, Parallel, TinyFish, Search1API, Searchinfinity, Querit, Tavily, Firecrawl, Jina, SERPdive, Kagi, Bocha, Ollama, Perplexity, Gemini API, and Gemini Web when browser cookies are enabled. YouTube tries Gemini Web when enabled, then API, then Perplexity. Blocked pages try configured self-hosted Firecrawl or Crawl4AI first. Third-party hosted page fetchers require explicit `fetchRouting.allowRemoteHostedProviders` opt-in for remote HTTP(S) targets.
22
22
 
23
23
  **GitHub Cloning** — GitHub URLs are cloned locally instead of scraped. The agent gets real file contents and a local path to explore, not rendered HTML.
24
24
 
@@ -47,7 +47,7 @@ Works immediately with no API keys — Exa MCP provides zero-config search. If P
47
47
  }
48
48
  ```
49
49
 
50
- In `auto` mode (default), `web_search` tries a configured SearXNG endpoint first for local/private search. When the active Pi model is `openai-codex`, it then tries Codex-backed OpenAI search. Otherwise it tries Exa (direct API if keyed, MCP if not) before OpenAI, then Brave, Parallel, TinyFish, Search1API, Searchinfinity, Querit, Tavily, Firecrawl, Jina, SERPdive, Perplexity, Gemini API, and Gemini Web when browser-cookie access is enabled. Exa handles search; curator summary drafts are generated separately by the configured Pi summary model, defaulting to Claude Haiku, Codex Luna, Codex Terra, Gemini 3.6 Flash, GPT-5 mini, then DeepSeek V4 Flash when available. Slow summary drafts fall back to a deterministic result summary after a bounded deadline.
50
+ In `auto` mode (default), `web_search` tries a configured SearXNG endpoint first for local/private search. When the active Pi model uses a ChatGPT subscription (`openai-codex`, or `openai` with Sign in with ChatGPT), it then tries subscription-backed OpenAI search. Otherwise it tries Exa (direct API if keyed, MCP if not) before OpenAI, then Brave, Parallel, TinyFish, Search1API, Searchinfinity, Querit, Tavily, Firecrawl, Jina, SERPdive, Perplexity, Gemini API, and Gemini Web when browser-cookie access is enabled. Exa handles search; curator summary drafts are generated separately by the configured Pi summary model, defaulting to Claude Haiku, Codex Luna, Codex Terra, Gemini 3.6 Flash, GPT-5 mini, then DeepSeek V4 Flash when available. Slow summary drafts fall back to a deterministic result summary after a bounded deadline.
51
51
 
52
52
  For a third-party Responses-compatible gateway, set `openaiResponsesUrl` to its full Responses endpoint, or opt into reusing the selected Pi provider's base URL as described below. Without either opt-in, Pi credentials with a custom `baseUrl` are refused before sending a request, and availability checks mark OpenAI unavailable without blocking other providers. The auth-resolved base URL takes precedence over the model's; gateway Responses/`web_search` support is not inferred. An explicit endpoint overrides this guard, including an explicit official endpoint. Existing Codex subscription endpoint selection is preserved when provider URL reuse is not active.
53
53
 
@@ -190,7 +190,7 @@ web_search({ queries: ["query 1", "query 2"], workflow: "auto-summary" })
190
190
  | `includeContent` | Fetch full page content from sources in background |
191
191
  | `workflow` | `none` (skip curator; fresh-install default), `summary-review` (open curator and auto-generate a summary draft), or `auto-summary` (generate a summary without opening the curator) |
192
192
 
193
- Batch searches run up to three queries concurrently. Provider routing and fallback within each query remain sequential.
193
+ Batch searches run up to three queries concurrently. Provider routing and fallback within each query remain sequential. Brave requests share an in-process queue and honor the rate-limit buckets returned in `X-RateLimit-*`; exhausted short windows delay the next request with a small safety margin, while long quota resets fail fast and keep the advertised cooldown active for later queued calls. HTTP 429 responses are retried once after the advertised `Retry-After` or rate-limit reset. Brave's 30-second search deadline covers queueing, cooldown waits, and both request attempts. The queue coordinates one Pi process; separate Pi processes still rely on Brave's 429 response.
194
194
 
195
195
  ### fetch_content
196
196
 
@@ -517,6 +517,7 @@ Config defaults to `~/.pi/agent/web-search.json` when neither `PI_CODING_AGENT_D
517
517
  "searchModel": "gemini-3.6-flash",
518
518
  "summaryModel": "anthropic/claude-haiku-4-5",
519
519
  "summaryGenerationDeadlineMs": 30000,
520
+ "summaryInstructions": "- Preserve concrete figures verbatim: prices, limits, versions, dates.\n- Name the product version each claim applies to.",
520
521
  "maxInlineContentChars": 30000,
521
522
  "workflow": "summary-review",
522
523
  "curatorTimeoutSeconds": 20,
@@ -571,6 +572,8 @@ Config defaults to `~/.pi/agent/web-search.json` when neither `PI_CODING_AGENT_D
571
572
 
572
573
  `summaryModel` accepts an optional thinking-level suffix, such as `anthropic/claude-haiku-4-5:low`. Supported suffixes are `off`, `minimal`, `low`, `medium`, `high`, `xhigh`, and `max`.
573
574
 
575
+ `summaryInstructions` appends custom text to the Requirements section of the summary prompt used by the curator UI and `auto-summary` mode (e.g. `"- Preserve concrete figures verbatim: prices, limits, versions, dates.\n- Name the product version each claim applies to."`). The text is passed verbatim to the summary model, so use one `- ` bullet per requirement. It is additive: the built-in guardrails (no invented sources, explicit weak/conflicting-evidence notes, the Sources section) always stay in place, and curator regeneration feedback is still appended afterwards. Missing, non-string, or blank values leave the default prompt unchanged.
576
+
574
577
  All provider API-key fields (`openaiApiKey`, `braveApiKey`, `parallelApiKey`, `tinyfishApiKey`, `search1apiApiKey`, `searchinfinityApiKey`, `queritApiKey`, `tavilyApiKey`, `youApiKey`, `jinaApiKey`, `serpdiveApiKey`, `kagiApiKey`, `bochaApiKey`, `ollamaApiKey`, `valyuApiKey`, `serpbaseApiKey`, `serpapiApiKey`, `serperApiKey`, `serplyApiKey`, `baizhiApiKey`, `zaiApiKey`, `anysearchApiKey`, `xcrawlApiKey`, `xaiApiKey`, `mistralApiKey`, `brightdataApiKey`, `firecrawlApiKey`, `crawl4aiApiToken`, `exaApiKey`, `perplexityApiKey`, `geminiApiKey`, `datalabApiKey`, and `cloudflareApiKey`) accept explicit credential sources. Use `$NAME` or `${NAME}` to read one named environment variable, or prefix a trusted local shell command with `!` to resolve one value at provider request time. Escape `$$` as a literal leading `$` and `$!` as a literal leading `!`:
575
578
 
576
579
  ```json
@@ -0,0 +1,170 @@
1
+ const DEFAULT_SAFETY_MARGIN_MS = 100;
2
+ const DEFAULT_MAX_WAIT_MS = 30_000;
3
+
4
+ export interface BraveRateLimitBucket {
5
+ limit: number;
6
+ windowSeconds: number | null;
7
+ remaining: number;
8
+ resetSeconds: number;
9
+ }
10
+
11
+ export interface BraveRateLimitCoordinatorOptions {
12
+ now?: () => number;
13
+ sleep?: (ms: number, signal?: AbortSignal) => Promise<void>;
14
+ safetyMarginMs?: number;
15
+ maxWaitMs?: number;
16
+ }
17
+
18
+ function parseNumberList(value: string | null): number[] {
19
+ if (!value) return [];
20
+ return value.split(",").map(part => Number(part.trim()));
21
+ }
22
+
23
+ function parsePolicyWindows(value: string | null): Array<number | null> {
24
+ if (!value) return [];
25
+ return value.split(",").map(part => {
26
+ const match = /(?:^|;)\s*w=(\d+(?:\.\d+)?)\s*(?:;|$)/i.exec(part.trim());
27
+ if (!match) return null;
28
+ const seconds = Number(match[1]);
29
+ return Number.isFinite(seconds) && seconds >= 0 ? seconds : null;
30
+ });
31
+ }
32
+
33
+ /** Parse Brave's parallel rate-limit bucket headers by list position. */
34
+ export function parseBraveRateLimitHeaders(headers: Headers): BraveRateLimitBucket[] {
35
+ const limits = parseNumberList(headers.get("x-ratelimit-limit"));
36
+ const remaining = parseNumberList(headers.get("x-ratelimit-remaining"));
37
+ const resets = parseNumberList(headers.get("x-ratelimit-reset"));
38
+ const windows = parsePolicyWindows(headers.get("x-ratelimit-policy"));
39
+ const count = Math.max(limits.length, remaining.length, resets.length, windows.length);
40
+ const buckets: BraveRateLimitBucket[] = [];
41
+
42
+ for (let index = 0; index < count; index++) {
43
+ const limit = limits[index];
44
+ const left = remaining[index];
45
+ const resetSeconds = resets[index];
46
+ if (![limit, left, resetSeconds].every(Number.isFinite) || limit < 0 || left < 0 || resetSeconds < 0) continue;
47
+ buckets.push({
48
+ limit,
49
+ windowSeconds: windows[index] ?? null,
50
+ remaining: left,
51
+ resetSeconds,
52
+ });
53
+ }
54
+ return buckets;
55
+ }
56
+
57
+ export function parseRetryAfterMs(headers: Headers, now = Date.now()): number | null {
58
+ const value = headers.get("retry-after")?.trim();
59
+ if (!value) return null;
60
+ const seconds = Number(value);
61
+ if (Number.isFinite(seconds) && seconds >= 0) return seconds * 1000;
62
+ const date = Date.parse(value);
63
+ if (!Number.isFinite(date)) return null;
64
+ return Math.max(0, date - now);
65
+ }
66
+
67
+ function defaultSleep(ms: number, signal?: AbortSignal): Promise<void> {
68
+ if (signal?.aborted) return Promise.reject(new Error("Aborted"));
69
+ return new Promise((resolve, reject) => {
70
+ const cleanup = () => signal?.removeEventListener("abort", onAbort);
71
+ const timer = setTimeout(() => {
72
+ cleanup();
73
+ resolve();
74
+ }, ms);
75
+ const onAbort = () => {
76
+ clearTimeout(timer);
77
+ cleanup();
78
+ reject(new Error("Aborted"));
79
+ };
80
+ signal?.addEventListener("abort", onAbort, { once: true });
81
+ });
82
+ }
83
+
84
+ function formatWait(ms: number): string {
85
+ const seconds = Math.ceil(ms / 1000);
86
+ if (seconds < 60) return `${seconds}s`;
87
+ const minutes = Math.ceil(seconds / 60);
88
+ if (minutes < 60) return `${minutes}m`;
89
+ const hours = Math.ceil(minutes / 60);
90
+ return `${hours}h`;
91
+ }
92
+
93
+ export class BraveRateLimitCoordinator {
94
+ private queue: Promise<void> = Promise.resolve();
95
+ private blockedUntil = 0;
96
+ private readonly now: () => number;
97
+ private readonly sleep: (ms: number, signal?: AbortSignal) => Promise<void>;
98
+ private readonly safetyMarginMs: number;
99
+ private readonly maxWaitMs: number;
100
+
101
+ constructor(options: BraveRateLimitCoordinatorOptions = {}) {
102
+ this.now = options.now ?? Date.now;
103
+ this.sleep = options.sleep ?? defaultSleep;
104
+ this.safetyMarginMs = options.safetyMarginMs ?? DEFAULT_SAFETY_MARGIN_MS;
105
+ this.maxWaitMs = options.maxWaitMs ?? DEFAULT_MAX_WAIT_MS;
106
+ }
107
+
108
+ run<T>(operation: () => Promise<T>, signal?: AbortSignal): Promise<T> {
109
+ const scheduled = this.queue.then(async () => {
110
+ await this.waitForAvailability(signal);
111
+ if (signal?.aborted) throw new Error("Aborted");
112
+ return operation();
113
+ });
114
+ this.queue = scheduled.then(() => undefined, () => undefined);
115
+ if (!signal) return scheduled;
116
+ if (signal.aborted) return Promise.reject(new Error("Aborted"));
117
+ return new Promise<T>((resolve, reject) => {
118
+ const onAbort = () => reject(new Error("Aborted"));
119
+ signal.addEventListener("abort", onAbort, { once: true });
120
+ scheduled.then(resolve, reject).finally(() => signal.removeEventListener("abort", onAbort));
121
+ });
122
+ }
123
+
124
+ observe(headers: Headers): void {
125
+ const exhausted = parseBraveRateLimitHeaders(headers).filter(bucket => bucket.remaining === 0);
126
+ if (exhausted.length === 0) return;
127
+ const resetMs = Math.max(...exhausted.map(bucket => bucket.resetSeconds * 1000));
128
+ this.blockFor(resetMs);
129
+ }
130
+
131
+ retryDelay(headers: Headers): number | null {
132
+ const advertised = parseRetryAfterMs(headers, this.now());
133
+ const exhausted = parseBraveRateLimitHeaders(headers).filter(bucket => bucket.remaining === 0);
134
+ const bucketDelay = exhausted.length > 0
135
+ ? Math.max(...exhausted.map(bucket => bucket.resetSeconds * 1000))
136
+ : null;
137
+ if (advertised === null && bucketDelay === null) return null;
138
+ return Math.max(advertised ?? 0, bucketDelay ?? 0) + this.safetyMarginMs;
139
+ }
140
+
141
+ recordRetryDelay(delayMs: number): void {
142
+ this.blockFor(delayMs, false);
143
+ }
144
+
145
+ async waitForRecordedCooldown(signal?: AbortSignal): Promise<void> {
146
+ await this.waitForAvailability(signal);
147
+ }
148
+
149
+ async waitForRetry(delayMs: number, signal?: AbortSignal): Promise<void> {
150
+ this.recordRetryDelay(delayMs);
151
+ await this.waitForRecordedCooldown(signal);
152
+ }
153
+
154
+ private blockFor(delayMs: number, addMargin = true): void {
155
+ const boundedDelay = Math.max(0, delayMs) + (addMargin ? this.safetyMarginMs : 0);
156
+ this.blockedUntil = Math.max(this.blockedUntil, this.now() + boundedDelay);
157
+ }
158
+
159
+ private async waitForAvailability(signal?: AbortSignal): Promise<void> {
160
+ const waitMs = Math.max(0, this.blockedUntil - this.now());
161
+ if (waitMs === 0) return;
162
+ this.assertWaitIsBounded(waitMs);
163
+ await this.sleep(waitMs, signal);
164
+ }
165
+
166
+ private assertWaitIsBounded(waitMs: number): void {
167
+ if (waitMs <= this.maxWaitMs) return;
168
+ throw new Error(`Brave Search quota exhausted; estimated reset in ${formatWait(waitMs)}. Refusing to wait longer than ${formatWait(this.maxWaitMs)} inside one search call.`);
169
+ }
170
+ }
@@ -1,5 +1,6 @@
1
1
  import { existsSync, readFileSync } from "node:fs";
2
2
  import { activityMonitor } from "./activity.ts";
3
+ import { BraveRateLimitCoordinator } from "./brave-rate-limit.ts";
3
4
  import { normalizeDomain } from "./domain-filter-normalization.ts";
4
5
  import { normalizeSearchResultCount } from "./search-result-count-normalization.ts";
5
6
  import type { SearchOptions, SearchResult, SearchResponse } from "./perplexity.ts";
@@ -9,6 +10,7 @@ import { fetchWithCredentialRedirects, getWebSearchConfigPath, resolveApiBaseUrl
9
10
  const BRAVE_API_BASE_URL = "https://api.search.brave.com/res/v1";
10
11
  const CONFIG_PATH = getWebSearchConfigPath();
11
12
  const SEARCH_TIMEOUT_MS = 30_000;
13
+ const braveRateLimit = new BraveRateLimitCoordinator();
12
14
 
13
15
  interface WebSearchConfig {
14
16
  braveApiKey?: unknown;
@@ -154,17 +156,32 @@ export async function searchWithBrave(
154
156
  }
155
157
 
156
158
  try {
157
- const response = await fetchWithCredentialRedirects(`${apiUrl}?${params.toString()}`, {
158
- method: "GET",
159
- headers: {
160
- "X-Subscription-Token": apiKey,
161
- "Accept": "application/json",
162
- "Accept-Encoding": "gzip",
163
- },
164
- signal: options.signal
165
- ? AbortSignal.any([AbortSignal.timeout(SEARCH_TIMEOUT_MS), options.signal])
166
- : AbortSignal.timeout(SEARCH_TIMEOUT_MS),
167
- }, ["X-Subscription-Token"]);
159
+ const searchDeadline = AbortSignal.timeout(SEARCH_TIMEOUT_MS);
160
+ const searchSignal = options.signal
161
+ ? AbortSignal.any([searchDeadline, options.signal])
162
+ : searchDeadline;
163
+ const response = await braveRateLimit.run(async () => {
164
+ for (let attempt = 0; attempt < 2; attempt++) {
165
+ const current = await fetchWithCredentialRedirects(`${apiUrl}?${params.toString()}`, {
166
+ method: "GET",
167
+ headers: {
168
+ "X-Subscription-Token": apiKey,
169
+ "Accept": "application/json",
170
+ "Accept-Encoding": "gzip",
171
+ },
172
+ signal: searchSignal,
173
+ }, ["X-Subscription-Token"]);
174
+
175
+ braveRateLimit.observe(current.headers);
176
+ if (current.status !== 429 || attempt === 1) return current;
177
+ const retryDelay = braveRateLimit.retryDelay(current.headers);
178
+ if (retryDelay === null) return current;
179
+ braveRateLimit.recordRetryDelay(retryDelay);
180
+ await current.body?.cancel().catch(() => undefined);
181
+ await braveRateLimit.waitForRecordedCooldown(searchSignal);
182
+ }
183
+ throw new Error("Brave Search retry loop exited unexpectedly");
184
+ }, searchSignal);
168
185
 
169
186
  if (!response.ok) {
170
187
  activityMonitor.logError(activityId, `HTTP ${response.status}`);