gsd-pi 2.78.1-dev.84a383f51 → 2.78.1

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 (474) hide show
  1. package/README.md +7 -7
  2. package/dist/cli.js +55 -95
  3. package/dist/headless-query.d.ts +0 -22
  4. package/dist/headless-query.js +4 -24
  5. package/dist/headless.d.ts +0 -10
  6. package/dist/headless.js +1 -16
  7. package/dist/loader.js +10 -7
  8. package/dist/onboarding.d.ts +0 -10
  9. package/dist/onboarding.js +2 -2
  10. package/dist/provider-migrations.d.ts +2 -2
  11. package/dist/provider-migrations.js +2 -5
  12. package/dist/resource-loader.d.ts +2 -5
  13. package/dist/resource-loader.js +5 -28
  14. package/dist/resources/extensions/browser-tools/tests/browser-tools-integration.test.mjs +601 -0
  15. package/dist/resources/extensions/browser-tools/tests/browser-tools-unit.test.cjs +651 -0
  16. package/dist/resources/extensions/browser-tools/tests/capture-sharp-optional.test.cjs +91 -0
  17. package/dist/resources/extensions/gsd/auto/loop.js +0 -23
  18. package/dist/resources/extensions/gsd/auto/phases.js +2 -2
  19. package/dist/resources/extensions/gsd/auto/run-unit.js +1 -3
  20. package/dist/resources/extensions/gsd/auto/session.js +0 -3
  21. package/dist/resources/extensions/gsd/auto-recovery.js +4 -43
  22. package/dist/resources/extensions/gsd/auto-start.js +1 -1
  23. package/dist/resources/extensions/gsd/auto-tool-tracking.js +2 -2
  24. package/dist/resources/extensions/gsd/auto-worktree.js +0 -30
  25. package/dist/resources/extensions/gsd/auto.js +5 -14
  26. package/dist/resources/extensions/gsd/bootstrap/db-tools.js +2 -14
  27. package/dist/resources/extensions/gsd/bootstrap/exec-tools.js +5 -7
  28. package/dist/resources/extensions/gsd/bootstrap/query-tools.js +2 -2
  29. package/dist/resources/extensions/gsd/bootstrap/register-extension.js +4 -5
  30. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +31 -94
  31. package/dist/resources/extensions/gsd/bootstrap/register-shortcuts.js +6 -11
  32. package/dist/resources/extensions/gsd/bootstrap/system-context.js +8 -34
  33. package/dist/resources/extensions/gsd/bootstrap/write-gate.js +2 -38
  34. package/dist/resources/extensions/gsd/commands/catalog.js +5 -69
  35. package/dist/resources/extensions/gsd/commands/handlers/core.js +1 -22
  36. package/dist/resources/extensions/gsd/commands-mcp-status.js +1 -3
  37. package/dist/resources/extensions/gsd/commands-prefs-wizard.js +1 -10
  38. package/dist/resources/extensions/gsd/dashboard-overlay.js +1 -1
  39. package/dist/resources/extensions/gsd/docs/preferences-reference.md +0 -4
  40. package/dist/resources/extensions/gsd/doctor-runtime-checks.js +1 -39
  41. package/dist/resources/extensions/gsd/error-classifier.js +1 -1
  42. package/dist/resources/extensions/gsd/forensics.js +2 -2
  43. package/dist/resources/extensions/gsd/git-service.js +5 -12
  44. package/dist/resources/extensions/gsd/gsd-db.js +2 -11
  45. package/dist/resources/extensions/gsd/guided-flow.js +23 -23
  46. package/dist/resources/extensions/gsd/memory-store.js +31 -66
  47. package/dist/resources/extensions/gsd/model-router.js +9 -114
  48. package/dist/resources/extensions/gsd/native-git-bridge.js +1 -7
  49. package/dist/resources/extensions/gsd/preferences-models.js +15 -91
  50. package/dist/resources/extensions/gsd/preferences-types.js +0 -2
  51. package/dist/resources/extensions/gsd/preferences-validation.js +0 -32
  52. package/dist/resources/extensions/gsd/preferences.js +3 -5
  53. package/dist/resources/extensions/gsd/prompt-loader.js +12 -23
  54. package/dist/resources/extensions/gsd/slice-parallel-orchestrator.js +3 -9
  55. package/dist/resources/extensions/gsd/state.js +0 -42
  56. package/dist/resources/extensions/gsd/templates/PREFERENCES.md +0 -1
  57. package/dist/resources/extensions/gsd/tests/auto-supervisor.test.mjs +53 -0
  58. package/dist/resources/extensions/gsd/tests/dist-redirect.mjs +112 -0
  59. package/dist/resources/extensions/gsd/tests/resolve-ts-hooks.mjs +23 -0
  60. package/dist/resources/extensions/gsd/tests/resolve-ts.mjs +5 -0
  61. package/dist/resources/extensions/gsd/tools/memory-tools.js +1 -18
  62. package/dist/resources/extensions/gsd/visualizer-overlay.js +1 -1
  63. package/dist/resources/extensions/gsd/watch/header-renderer.js +1 -3
  64. package/dist/resources/extensions/gsd/worktree-command.js +46 -26
  65. package/dist/resources/extensions/mcp-client/index.js +3 -6
  66. package/dist/resources/extensions/slash-commands/create-extension.js +22 -36
  67. package/dist/resources/skills/create-gsd-extension/SKILL.md +5 -9
  68. package/dist/resources/skills/create-gsd-extension/references/custom-commands.md +1 -1
  69. package/dist/resources/skills/create-gsd-extension/references/custom-rendering.md +5 -5
  70. package/dist/resources/skills/create-gsd-extension/references/custom-tools.md +4 -4
  71. package/dist/resources/skills/create-gsd-extension/references/custom-ui.md +6 -6
  72. package/dist/resources/skills/create-gsd-extension/references/events-reference.md +3 -3
  73. package/dist/resources/skills/create-gsd-extension/references/packaging-distribution.md +1 -1
  74. package/dist/resources/skills/create-gsd-extension/references/remote-execution-overrides.md +3 -3
  75. package/dist/resources/skills/create-gsd-extension/workflows/create-extension.md +12 -32
  76. package/dist/resources/skills/github-workflows/references/gh/tests/__init__.py +0 -0
  77. package/dist/resources/skills/github-workflows/references/gh/tests/test_github_project_setup.py +608 -0
  78. package/dist/rtk-shared.d.ts +0 -3
  79. package/dist/rtk-shared.js +0 -17
  80. package/dist/rtk.d.ts +5 -2
  81. package/dist/rtk.js +20 -3
  82. package/dist/tsconfig.extensions.tsbuildinfo +1 -1
  83. package/dist/web/standalone/.next/BUILD_ID +1 -1
  84. package/dist/web/standalone/.next/app-path-routes-manifest.json +13 -13
  85. package/dist/web/standalone/.next/build-manifest.json +4 -4
  86. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  87. package/dist/web/standalone/.next/react-loadable-manifest.json +4 -44
  88. package/dist/web/standalone/.next/required-server-files.json +3 -3
  89. package/dist/web/standalone/.next/server/app/_global-error/page.js +3 -3
  90. package/dist/web/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  91. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  92. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  93. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  94. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  95. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  96. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  97. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  98. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  99. package/dist/web/standalone/.next/server/app/_not-found/page.js +2 -2
  100. package/dist/web/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  101. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  102. package/dist/web/standalone/.next/server/app/_not-found.rsc +3 -3
  103. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +3 -3
  104. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  105. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +3 -3
  106. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  107. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  108. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  109. package/dist/web/standalone/.next/server/app/api/boot/route.js +1 -1
  110. package/dist/web/standalone/.next/server/app/api/boot/route_client-reference-manifest.js +1 -1
  111. package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route.js +1 -1
  112. package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route_client-reference-manifest.js +1 -1
  113. package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route.js +1 -1
  114. package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route_client-reference-manifest.js +1 -1
  115. package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route.js +2 -2
  116. package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route_client-reference-manifest.js +1 -1
  117. package/dist/web/standalone/.next/server/app/api/browse-directories/route.js +1 -1
  118. package/dist/web/standalone/.next/server/app/api/browse-directories/route_client-reference-manifest.js +1 -1
  119. package/dist/web/standalone/.next/server/app/api/captures/route.js +1 -1
  120. package/dist/web/standalone/.next/server/app/api/captures/route_client-reference-manifest.js +1 -1
  121. package/dist/web/standalone/.next/server/app/api/cleanup/route.js +1 -1
  122. package/dist/web/standalone/.next/server/app/api/cleanup/route_client-reference-manifest.js +1 -1
  123. package/dist/web/standalone/.next/server/app/api/dev-mode/route.js +1 -1
  124. package/dist/web/standalone/.next/server/app/api/dev-mode/route_client-reference-manifest.js +1 -1
  125. package/dist/web/standalone/.next/server/app/api/doctor/route.js +1 -1
  126. package/dist/web/standalone/.next/server/app/api/doctor/route_client-reference-manifest.js +1 -1
  127. package/dist/web/standalone/.next/server/app/api/experimental/route.js +2 -2
  128. package/dist/web/standalone/.next/server/app/api/experimental/route_client-reference-manifest.js +1 -1
  129. package/dist/web/standalone/.next/server/app/api/export-data/route.js +1 -1
  130. package/dist/web/standalone/.next/server/app/api/export-data/route_client-reference-manifest.js +1 -1
  131. package/dist/web/standalone/.next/server/app/api/files/route.js +1 -1
  132. package/dist/web/standalone/.next/server/app/api/files/route_client-reference-manifest.js +1 -1
  133. package/dist/web/standalone/.next/server/app/api/forensics/route.js +1 -1
  134. package/dist/web/standalone/.next/server/app/api/forensics/route_client-reference-manifest.js +1 -1
  135. package/dist/web/standalone/.next/server/app/api/git/route.js +1 -1
  136. package/dist/web/standalone/.next/server/app/api/git/route_client-reference-manifest.js +1 -1
  137. package/dist/web/standalone/.next/server/app/api/history/route.js +1 -1
  138. package/dist/web/standalone/.next/server/app/api/history/route_client-reference-manifest.js +1 -1
  139. package/dist/web/standalone/.next/server/app/api/hooks/route.js +1 -1
  140. package/dist/web/standalone/.next/server/app/api/hooks/route_client-reference-manifest.js +1 -1
  141. package/dist/web/standalone/.next/server/app/api/inspect/route.js +1 -1
  142. package/dist/web/standalone/.next/server/app/api/inspect/route_client-reference-manifest.js +1 -1
  143. package/dist/web/standalone/.next/server/app/api/knowledge/route.js +1 -1
  144. package/dist/web/standalone/.next/server/app/api/knowledge/route_client-reference-manifest.js +1 -1
  145. package/dist/web/standalone/.next/server/app/api/live-state/route.js +1 -1
  146. package/dist/web/standalone/.next/server/app/api/live-state/route_client-reference-manifest.js +1 -1
  147. package/dist/web/standalone/.next/server/app/api/notifications/route.js +2 -2
  148. package/dist/web/standalone/.next/server/app/api/notifications/route_client-reference-manifest.js +1 -1
  149. package/dist/web/standalone/.next/server/app/api/onboarding/route.js +1 -1
  150. package/dist/web/standalone/.next/server/app/api/onboarding/route_client-reference-manifest.js +1 -1
  151. package/dist/web/standalone/.next/server/app/api/preferences/route.js +1 -1
  152. package/dist/web/standalone/.next/server/app/api/preferences/route_client-reference-manifest.js +1 -1
  153. package/dist/web/standalone/.next/server/app/api/projects/route.js +1 -1
  154. package/dist/web/standalone/.next/server/app/api/projects/route_client-reference-manifest.js +1 -1
  155. package/dist/web/standalone/.next/server/app/api/recovery/route.js +1 -1
  156. package/dist/web/standalone/.next/server/app/api/recovery/route_client-reference-manifest.js +1 -1
  157. package/dist/web/standalone/.next/server/app/api/remote-questions/route.js +2 -2
  158. package/dist/web/standalone/.next/server/app/api/remote-questions/route_client-reference-manifest.js +1 -1
  159. package/dist/web/standalone/.next/server/app/api/session/browser/route.js +1 -1
  160. package/dist/web/standalone/.next/server/app/api/session/browser/route_client-reference-manifest.js +1 -1
  161. package/dist/web/standalone/.next/server/app/api/session/command/route.js +1 -1
  162. package/dist/web/standalone/.next/server/app/api/session/command/route_client-reference-manifest.js +1 -1
  163. package/dist/web/standalone/.next/server/app/api/session/events/route.js +2 -4
  164. package/dist/web/standalone/.next/server/app/api/session/events/route_client-reference-manifest.js +1 -1
  165. package/dist/web/standalone/.next/server/app/api/session/manage/route.js +1 -1
  166. package/dist/web/standalone/.next/server/app/api/session/manage/route_client-reference-manifest.js +1 -1
  167. package/dist/web/standalone/.next/server/app/api/settings-data/route.js +1 -1
  168. package/dist/web/standalone/.next/server/app/api/settings-data/route_client-reference-manifest.js +1 -1
  169. package/dist/web/standalone/.next/server/app/api/shutdown/route.js +1 -1
  170. package/dist/web/standalone/.next/server/app/api/shutdown/route_client-reference-manifest.js +1 -1
  171. package/dist/web/standalone/.next/server/app/api/skill-health/route.js +1 -1
  172. package/dist/web/standalone/.next/server/app/api/skill-health/route_client-reference-manifest.js +1 -1
  173. package/dist/web/standalone/.next/server/app/api/steer/route.js +1 -1
  174. package/dist/web/standalone/.next/server/app/api/steer/route_client-reference-manifest.js +1 -1
  175. package/dist/web/standalone/.next/server/app/api/switch-root/route.js +1 -1
  176. package/dist/web/standalone/.next/server/app/api/switch-root/route_client-reference-manifest.js +1 -1
  177. package/dist/web/standalone/.next/server/app/api/terminal/input/route.js +2 -2
  178. package/dist/web/standalone/.next/server/app/api/terminal/input/route_client-reference-manifest.js +1 -1
  179. package/dist/web/standalone/.next/server/app/api/terminal/resize/route.js +2 -2
  180. package/dist/web/standalone/.next/server/app/api/terminal/resize/route_client-reference-manifest.js +1 -1
  181. package/dist/web/standalone/.next/server/app/api/terminal/sessions/route.js +2 -2
  182. package/dist/web/standalone/.next/server/app/api/terminal/sessions/route_client-reference-manifest.js +1 -1
  183. package/dist/web/standalone/.next/server/app/api/terminal/stream/route.js +4 -4
  184. package/dist/web/standalone/.next/server/app/api/terminal/stream/route_client-reference-manifest.js +1 -1
  185. package/dist/web/standalone/.next/server/app/api/terminal/upload/route.js +1 -1
  186. package/dist/web/standalone/.next/server/app/api/terminal/upload/route_client-reference-manifest.js +1 -1
  187. package/dist/web/standalone/.next/server/app/api/undo/route.js +1 -1
  188. package/dist/web/standalone/.next/server/app/api/undo/route_client-reference-manifest.js +1 -1
  189. package/dist/web/standalone/.next/server/app/api/update/route.js +1 -1
  190. package/dist/web/standalone/.next/server/app/api/update/route_client-reference-manifest.js +1 -1
  191. package/dist/web/standalone/.next/server/app/api/visualizer/route.js +1 -1
  192. package/dist/web/standalone/.next/server/app/api/visualizer/route_client-reference-manifest.js +1 -1
  193. package/dist/web/standalone/.next/server/app/index.html +1 -1
  194. package/dist/web/standalone/.next/server/app/index.rsc +4 -4
  195. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
  196. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +4 -4
  197. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  198. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +3 -3
  199. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  200. package/dist/web/standalone/.next/server/app/page.js +2 -2
  201. package/dist/web/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  202. package/dist/web/standalone/.next/server/app-paths-manifest.json +13 -13
  203. package/dist/web/standalone/.next/server/chunks/63.js +3 -3
  204. package/dist/web/standalone/.next/server/chunks/6897.js +1 -1
  205. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  206. package/dist/web/standalone/.next/server/middleware-manifest.json +5 -5
  207. package/dist/web/standalone/.next/server/middleware-react-loadable-manifest.js +1 -1
  208. package/dist/web/standalone/.next/server/middleware.js +2 -2
  209. package/dist/web/standalone/.next/server/next-font-manifest.js +1 -1
  210. package/dist/web/standalone/.next/server/next-font-manifest.json +1 -1
  211. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  212. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  213. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  214. package/dist/web/standalone/.next/server/webpack-runtime.js +1 -1
  215. package/dist/web/standalone/.next/static/chunks/2826.e9f5195e91f9cad2.js +11 -0
  216. package/dist/web/standalone/.next/static/chunks/3621.fc7480022c972438.js +20 -0
  217. package/dist/web/standalone/.next/static/chunks/app/_not-found/{page-f2a7482d42a5614b.js → page-2f24283c162b6ab3.js} +1 -1
  218. package/dist/web/standalone/.next/static/chunks/app/{layout-a16c7a7ecdf0c2cf.js → layout-9ecfd95f343793f0.js} +1 -1
  219. package/dist/web/standalone/.next/static/chunks/app/page-151349214571e2b6.js +1 -0
  220. package/dist/web/standalone/.next/static/chunks/main-app-d3d4c336195465f9.js +1 -0
  221. package/dist/web/standalone/.next/static/chunks/next/dist/client/components/builtin/global-error-ab5a8926e07ec673.js +1 -0
  222. package/dist/web/standalone/.next/static/chunks/webpack-2e68521d7c82f7c2.js +1 -0
  223. package/dist/web/standalone/node_modules/node-pty/build/Makefile +2 -2
  224. package/dist/web/standalone/node_modules/node-pty/build/Release/pty.node +0 -0
  225. package/dist/web/standalone/node_modules/node-pty/build/pty.target.mk +14 -14
  226. package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api.target.mk +14 -14
  227. package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api_except.target.mk +14 -14
  228. package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api_maybe.target.mk +14 -14
  229. package/dist/web/standalone/package.json +1 -2
  230. package/dist/web/standalone/server.js +1 -1
  231. package/package.json +1 -1
  232. package/packages/mcp-server/dist/workflow-tools.d.ts.map +1 -1
  233. package/packages/mcp-server/dist/workflow-tools.js +46 -74
  234. package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
  235. package/packages/mcp-server/src/workflow-tools.test.ts +0 -26
  236. package/packages/mcp-server/src/workflow-tools.ts +58 -93
  237. package/packages/mcp-server/tsconfig.tsbuildinfo +1 -1
  238. package/packages/pi-agent-core/tsconfig.tsbuildinfo +1 -1
  239. package/packages/pi-ai/dist/providers/anthropic-shared.d.ts.map +1 -1
  240. package/packages/pi-ai/dist/providers/anthropic-shared.js +19 -48
  241. package/packages/pi-ai/dist/providers/anthropic-shared.js.map +1 -1
  242. package/packages/pi-ai/dist/types.d.ts +0 -13
  243. package/packages/pi-ai/dist/types.d.ts.map +1 -1
  244. package/packages/pi-ai/dist/types.js.map +1 -1
  245. package/packages/pi-ai/dist/utils/repair-tool-json.d.ts.map +1 -1
  246. package/packages/pi-ai/dist/utils/repair-tool-json.js +3 -24
  247. package/packages/pi-ai/dist/utils/repair-tool-json.js.map +1 -1
  248. package/packages/pi-ai/dist/utils/tests/repair-tool-json.test.js +0 -26
  249. package/packages/pi-ai/dist/utils/tests/repair-tool-json.test.js.map +1 -1
  250. package/packages/pi-ai/src/providers/anthropic-shared.ts +20 -52
  251. package/packages/pi-ai/src/types.ts +0 -13
  252. package/packages/pi-ai/src/utils/repair-tool-json.ts +3 -24
  253. package/packages/pi-ai/src/utils/tests/repair-tool-json.test.ts +0 -32
  254. package/packages/pi-ai/tsconfig.tsbuildinfo +1 -1
  255. package/packages/pi-coding-agent/dist/core/agent-session.d.ts.map +1 -1
  256. package/packages/pi-coding-agent/dist/core/agent-session.js +0 -6
  257. package/packages/pi-coding-agent/dist/core/agent-session.js.map +1 -1
  258. package/packages/pi-coding-agent/dist/core/messages.d.ts.map +1 -1
  259. package/packages/pi-coding-agent/dist/core/messages.js +0 -4
  260. package/packages/pi-coding-agent/dist/core/messages.js.map +1 -1
  261. package/packages/pi-coding-agent/dist/core/model-registry-auth-mode.test.js +2 -19
  262. package/packages/pi-coding-agent/dist/core/model-registry-auth-mode.test.js.map +1 -1
  263. package/packages/pi-coding-agent/dist/core/model-registry.d.ts +0 -10
  264. package/packages/pi-coding-agent/dist/core/model-registry.d.ts.map +1 -1
  265. package/packages/pi-coding-agent/dist/core/model-registry.js +0 -18
  266. package/packages/pi-coding-agent/dist/core/model-registry.js.map +1 -1
  267. package/packages/pi-coding-agent/dist/core/system-prompt.d.ts +0 -13
  268. package/packages/pi-coding-agent/dist/core/system-prompt.d.ts.map +1 -1
  269. package/packages/pi-coding-agent/dist/core/system-prompt.js +16 -20
  270. package/packages/pi-coding-agent/dist/core/system-prompt.js.map +1 -1
  271. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.js +1 -1
  272. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.js.map +1 -1
  273. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.test.js +1 -14
  274. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.test.js.map +1 -1
  275. package/packages/pi-coding-agent/src/core/agent-session.ts +0 -7
  276. package/packages/pi-coding-agent/src/core/messages.ts +0 -4
  277. package/packages/pi-coding-agent/src/core/model-registry-auth-mode.test.ts +2 -32
  278. package/packages/pi-coding-agent/src/core/model-registry.ts +0 -21
  279. package/packages/pi-coding-agent/src/core/system-prompt.ts +15 -33
  280. package/packages/pi-coding-agent/src/modes/interactive/controllers/input-controller.test.ts +1 -17
  281. package/packages/pi-coding-agent/src/modes/interactive/controllers/input-controller.ts +1 -1
  282. package/packages/pi-coding-agent/tsconfig.tsbuildinfo +1 -1
  283. package/packages/pi-tui/dist/__tests__/autocomplete.test.js +3 -17
  284. package/packages/pi-tui/dist/__tests__/autocomplete.test.js.map +1 -1
  285. package/packages/pi-tui/src/__tests__/autocomplete.test.ts +3 -20
  286. package/packages/pi-tui/tsconfig.tsbuildinfo +1 -1
  287. package/src/resources/extensions/gsd/auto/loop.ts +2 -24
  288. package/src/resources/extensions/gsd/auto/phases.ts +3 -3
  289. package/src/resources/extensions/gsd/auto/run-unit.ts +1 -3
  290. package/src/resources/extensions/gsd/auto/session.ts +0 -3
  291. package/src/resources/extensions/gsd/auto/types.ts +0 -1
  292. package/src/resources/extensions/gsd/auto-recovery.ts +8 -46
  293. package/src/resources/extensions/gsd/auto-start.ts +1 -1
  294. package/src/resources/extensions/gsd/auto-tool-tracking.ts +4 -2
  295. package/src/resources/extensions/gsd/auto-worktree.ts +0 -38
  296. package/src/resources/extensions/gsd/auto.ts +4 -14
  297. package/src/resources/extensions/gsd/bootstrap/db-tools.ts +13 -15
  298. package/src/resources/extensions/gsd/bootstrap/exec-tools.ts +7 -8
  299. package/src/resources/extensions/gsd/bootstrap/query-tools.ts +2 -2
  300. package/src/resources/extensions/gsd/bootstrap/register-extension.ts +9 -10
  301. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +31 -102
  302. package/src/resources/extensions/gsd/bootstrap/register-shortcuts.ts +6 -12
  303. package/src/resources/extensions/gsd/bootstrap/system-context.ts +8 -39
  304. package/src/resources/extensions/gsd/bootstrap/write-gate.ts +11 -39
  305. package/src/resources/extensions/gsd/commands/catalog.ts +5 -75
  306. package/src/resources/extensions/gsd/commands/handlers/core.ts +1 -22
  307. package/src/resources/extensions/gsd/commands-mcp-status.ts +1 -3
  308. package/src/resources/extensions/gsd/commands-prefs-wizard.ts +1 -15
  309. package/src/resources/extensions/gsd/dashboard-overlay.ts +1 -1
  310. package/src/resources/extensions/gsd/docs/preferences-reference.md +0 -4
  311. package/src/resources/extensions/gsd/doctor-runtime-checks.ts +1 -39
  312. package/src/resources/extensions/gsd/doctor-types.ts +1 -3
  313. package/src/resources/extensions/gsd/error-classifier.ts +1 -1
  314. package/src/resources/extensions/gsd/forensics.ts +2 -2
  315. package/src/resources/extensions/gsd/git-service.ts +5 -13
  316. package/src/resources/extensions/gsd/gsd-db.ts +2 -12
  317. package/src/resources/extensions/gsd/guided-flow.ts +25 -25
  318. package/src/resources/extensions/gsd/memory-store.ts +28 -81
  319. package/src/resources/extensions/gsd/model-router.ts +9 -172
  320. package/src/resources/extensions/gsd/native-git-bridge.ts +1 -7
  321. package/src/resources/extensions/gsd/preferences-models.ts +15 -101
  322. package/src/resources/extensions/gsd/preferences-types.ts +0 -6
  323. package/src/resources/extensions/gsd/preferences-validation.ts +0 -35
  324. package/src/resources/extensions/gsd/preferences.ts +2 -16
  325. package/src/resources/extensions/gsd/prompt-loader.ts +12 -26
  326. package/src/resources/extensions/gsd/slice-parallel-orchestrator.ts +3 -9
  327. package/src/resources/extensions/gsd/state.ts +0 -42
  328. package/src/resources/extensions/gsd/templates/PREFERENCES.md +0 -1
  329. package/src/resources/extensions/gsd/tests/auto-loop.test.ts +1 -178
  330. package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +0 -58
  331. package/src/resources/extensions/gsd/tests/auto-session-encapsulation.test.ts +5 -9
  332. package/src/resources/extensions/gsd/tests/auto-supervisor.test.mjs +4 -21
  333. package/src/resources/extensions/gsd/tests/bootstrap-derive-state-db-open.test.ts +1 -1
  334. package/src/resources/extensions/gsd/tests/budget-prediction.test.ts +211 -138
  335. package/src/resources/extensions/gsd/tests/complete-slice-verification-gate.test.ts +59 -142
  336. package/src/resources/extensions/gsd/tests/complete-slice.test.ts +4 -7
  337. package/src/resources/extensions/gsd/tests/completed-at-reconcile.test.ts +32 -89
  338. package/src/resources/extensions/gsd/tests/copy-planning-artifacts-samepath.test.ts +22 -0
  339. package/src/resources/extensions/gsd/tests/custom-engine-loop-integration.test.ts +23 -41
  340. package/src/resources/extensions/gsd/tests/db-path-worktree-symlink.test.ts +43 -3
  341. package/src/resources/extensions/gsd/tests/debug-logger.test.ts +3 -5
  342. package/src/resources/extensions/gsd/tests/discuss-empty-db-fallback.test.ts +87 -22
  343. package/src/resources/extensions/gsd/tests/discuss-queued-milestones.test.ts +118 -7
  344. package/src/resources/extensions/gsd/tests/discuss-slice-structured-questions.test.ts +47 -0
  345. package/src/resources/extensions/gsd/tests/discuss-tool-scope-leak.test.ts +60 -18
  346. package/src/resources/extensions/gsd/tests/double-merge-guard.test.ts +76 -14
  347. package/src/resources/extensions/gsd/tests/empty-content-abort-loop.test.ts +75 -0
  348. package/src/resources/extensions/gsd/tests/false-degraded-mode-warning.test.ts +83 -22
  349. package/src/resources/extensions/gsd/tests/finalize-timeout-guard.test.ts +63 -1
  350. package/src/resources/extensions/gsd/tests/forensics-stuck-loops.test.ts +1 -26
  351. package/src/resources/extensions/gsd/tests/gsd-db.test.ts +0 -30
  352. package/src/resources/extensions/gsd/tests/headless-answers.test.ts +4 -14
  353. package/src/resources/extensions/gsd/tests/health-widget.test.ts +12 -22
  354. package/src/resources/extensions/gsd/tests/init-prefs-routing.test.ts +1 -64
  355. package/src/resources/extensions/gsd/tests/integration/auto-worktree.test.ts +0 -22
  356. package/src/resources/extensions/gsd/tests/integration/token-savings.test.ts +23 -0
  357. package/src/resources/extensions/gsd/tests/memory-store.test.ts +0 -128
  358. package/src/resources/extensions/gsd/tests/memory-tools.test.ts +1 -33
  359. package/src/resources/extensions/gsd/tests/model-router.test.ts +8 -169
  360. package/src/resources/extensions/gsd/tests/orphaned-worktree-audit.test.ts +0 -8
  361. package/src/resources/extensions/gsd/tests/parallel-crash-recovery.test.ts +43 -32
  362. package/src/resources/extensions/gsd/tests/phases-merge-error-stops-auto.test.ts +10 -4
  363. package/src/resources/extensions/gsd/tests/preferences.test.ts +0 -127
  364. package/src/resources/extensions/gsd/tests/prompt-step-ordering.test.ts +0 -16
  365. package/src/resources/extensions/gsd/tests/provider-errors.test.ts +0 -7
  366. package/src/resources/extensions/gsd/tests/quick-turn-end-cleanup.test.ts +6 -6
  367. package/src/resources/extensions/gsd/tests/session-start-footer.test.ts +19 -168
  368. package/src/resources/extensions/gsd/tests/slice-parallel-orchestrator.test.ts +1 -7
  369. package/src/resources/extensions/gsd/tests/smart-entry-complete.test.ts +1 -23
  370. package/src/resources/extensions/gsd/tests/token-profile.test.ts +4 -51
  371. package/src/resources/extensions/gsd/tests/turn-epoch.test.ts +16 -7
  372. package/src/resources/extensions/gsd/tests/unstructured-continue-context-injection.test.ts +7 -5
  373. package/src/resources/extensions/gsd/tests/uok-gitops-turn-action.test.ts +1 -15
  374. package/src/resources/extensions/gsd/tests/visualizer-overlay.test.ts +6 -6
  375. package/src/resources/extensions/gsd/tests/write-gate-planning-unit.test.ts +0 -15
  376. package/src/resources/extensions/gsd/tools/memory-tools.ts +1 -17
  377. package/src/resources/extensions/gsd/unit-context-manifest.ts +8 -8
  378. package/src/resources/extensions/gsd/visualizer-overlay.ts +1 -1
  379. package/src/resources/extensions/gsd/watch/header-renderer.ts +1 -3
  380. package/src/resources/extensions/gsd/workflow-logger.ts +0 -1
  381. package/src/resources/extensions/gsd/worktree-command.ts +44 -31
  382. package/src/resources/extensions/mcp-client/index.ts +3 -6
  383. package/src/resources/extensions/slash-commands/create-extension.ts +24 -38
  384. package/src/resources/skills/create-gsd-extension/SKILL.md +5 -9
  385. package/src/resources/skills/create-gsd-extension/references/custom-commands.md +1 -1
  386. package/src/resources/skills/create-gsd-extension/references/custom-rendering.md +5 -5
  387. package/src/resources/skills/create-gsd-extension/references/custom-tools.md +4 -4
  388. package/src/resources/skills/create-gsd-extension/references/custom-ui.md +6 -6
  389. package/src/resources/skills/create-gsd-extension/references/events-reference.md +3 -3
  390. package/src/resources/skills/create-gsd-extension/references/packaging-distribution.md +1 -1
  391. package/src/resources/skills/create-gsd-extension/references/remote-execution-overrides.md +3 -3
  392. package/src/resources/skills/create-gsd-extension/templates/extension-skeleton.ts +2 -2
  393. package/src/resources/skills/create-gsd-extension/templates/stateful-tool-skeleton.ts +3 -3
  394. package/src/resources/skills/create-gsd-extension/workflows/create-extension.md +12 -32
  395. package/dist/cli-policy.d.ts +0 -13
  396. package/dist/cli-policy.js +0 -17
  397. package/dist/resources/.managed-resources-content-hash +0 -1
  398. package/dist/resources/extensions/gsd/auto-runtime-state.js +0 -31
  399. package/dist/resources/extensions/gsd/milestone-id-reservation.js +0 -36
  400. package/dist/resources/extensions/gsd/worktree-session-state.js +0 -33
  401. package/dist/runtime-checks.d.ts +0 -27
  402. package/dist/runtime-checks.js +0 -38
  403. package/dist/web/standalone/.next/static/chunks/2556.0527fea66e123b7f.js +0 -1
  404. package/dist/web/standalone/.next/static/chunks/2824.08296bc2f9654698.js +0 -1
  405. package/dist/web/standalone/.next/static/chunks/3026.3af53b279375f082.js +0 -1
  406. package/dist/web/standalone/.next/static/chunks/315.6f68ae79b67d25cf.js +0 -1
  407. package/dist/web/standalone/.next/static/chunks/3497.4bfc60a3b3dea717.js +0 -1
  408. package/dist/web/standalone/.next/static/chunks/5516.4a07c872b5c3a663.js +0 -1
  409. package/dist/web/standalone/.next/static/chunks/8336.31b019697882acfb.js +0 -10
  410. package/dist/web/standalone/.next/static/chunks/8845.c9702695e8c5a9c5.js +0 -2
  411. package/dist/web/standalone/.next/static/chunks/9058.01ef3a463bda88f1.js +0 -20
  412. package/dist/web/standalone/.next/static/chunks/9441.1081da1125d1764f.js +0 -1
  413. package/dist/web/standalone/.next/static/chunks/app/page-9bf2e0c50fb2ca05.js +0 -1
  414. package/dist/web/standalone/.next/static/chunks/main-app-fdab67f7802d7832.js +0 -1
  415. package/dist/web/standalone/.next/static/chunks/next/dist/client/components/builtin/global-error-459824ffb8c323dd.js +0 -1
  416. package/dist/web/standalone/.next/static/chunks/webpack-f9f0dc45e4f3ac10.js +0 -1
  417. package/dist/worktree-status-banner.d.ts +0 -1
  418. package/dist/worktree-status-banner.js +0 -132
  419. package/packages/mcp-server/dist/alias-telemetry.d.ts +0 -8
  420. package/packages/mcp-server/dist/alias-telemetry.d.ts.map +0 -1
  421. package/packages/mcp-server/dist/alias-telemetry.js +0 -30
  422. package/packages/mcp-server/dist/alias-telemetry.js.map +0 -1
  423. package/packages/mcp-server/src/alias-telemetry.test.ts +0 -78
  424. package/packages/mcp-server/src/alias-telemetry.ts +0 -30
  425. package/packages/pi-ai/dist/providers/anthropic-shared.cache-breakpoint.test.d.ts +0 -2
  426. package/packages/pi-ai/dist/providers/anthropic-shared.cache-breakpoint.test.d.ts.map +0 -1
  427. package/packages/pi-ai/dist/providers/anthropic-shared.cache-breakpoint.test.js +0 -231
  428. package/packages/pi-ai/dist/providers/anthropic-shared.cache-breakpoint.test.js.map +0 -1
  429. package/packages/pi-ai/src/providers/anthropic-shared.cache-breakpoint.test.ts +0 -289
  430. package/packages/pi-coding-agent/dist/core/token-telemetry.d.ts +0 -37
  431. package/packages/pi-coding-agent/dist/core/token-telemetry.d.ts.map +0 -1
  432. package/packages/pi-coding-agent/dist/core/token-telemetry.js +0 -49
  433. package/packages/pi-coding-agent/dist/core/token-telemetry.js.map +0 -1
  434. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-card-cleanup-and-success-runtime.test.d.ts +0 -2
  435. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-card-cleanup-and-success-runtime.test.d.ts.map +0 -1
  436. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-card-cleanup-and-success-runtime.test.js +0 -133
  437. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-card-cleanup-and-success-runtime.test.js.map +0 -1
  438. package/packages/pi-coding-agent/dist/tests/system-prompt-cache-stability.test.d.ts +0 -2
  439. package/packages/pi-coding-agent/dist/tests/system-prompt-cache-stability.test.d.ts.map +0 -1
  440. package/packages/pi-coding-agent/dist/tests/system-prompt-cache-stability.test.js +0 -78
  441. package/packages/pi-coding-agent/dist/tests/system-prompt-cache-stability.test.js.map +0 -1
  442. package/packages/pi-coding-agent/dist/tests/token-telemetry.test.d.ts +0 -2
  443. package/packages/pi-coding-agent/dist/tests/token-telemetry.test.d.ts.map +0 -1
  444. package/packages/pi-coding-agent/dist/tests/token-telemetry.test.js +0 -181
  445. package/packages/pi-coding-agent/dist/tests/token-telemetry.test.js.map +0 -1
  446. package/packages/pi-coding-agent/src/core/token-telemetry.ts +0 -77
  447. package/packages/pi-coding-agent/src/modes/interactive/components/tool-card-cleanup-and-success-runtime.test.ts +0 -212
  448. package/packages/pi-coding-agent/src/tests/system-prompt-cache-stability.test.ts +0 -102
  449. package/packages/pi-coding-agent/src/tests/token-telemetry.test.ts +0 -200
  450. package/packages/pi-tui/dist/components/__tests__/leak-fixes-runtime.test.d.ts +0 -2
  451. package/packages/pi-tui/dist/components/__tests__/leak-fixes-runtime.test.d.ts.map +0 -1
  452. package/packages/pi-tui/dist/components/__tests__/leak-fixes-runtime.test.js +0 -161
  453. package/packages/pi-tui/dist/components/__tests__/leak-fixes-runtime.test.js.map +0 -1
  454. package/packages/pi-tui/src/components/__tests__/leak-fixes-runtime.test.ts +0 -219
  455. package/src/resources/extensions/gsd/auto-runtime-state.ts +0 -51
  456. package/src/resources/extensions/gsd/milestone-id-reservation.ts +0 -47
  457. package/src/resources/extensions/gsd/tests/deferred-milestone-dir-4996.test.ts +0 -116
  458. package/src/resources/extensions/gsd/tests/doctor-orphan-milestone-4996.test.ts +0 -100
  459. package/src/resources/extensions/gsd/tests/ensure-preconditions-guard-4996.test.ts +0 -93
  460. package/src/resources/extensions/gsd/tests/find-missing-summaries-closed-runtime.test.ts +0 -47
  461. package/src/resources/extensions/gsd/tests/gitignore-bg-shell-runtime.test.ts +0 -63
  462. package/src/resources/extensions/gsd/tests/gsd-no-project-error-runtime.test.ts +0 -81
  463. package/src/resources/extensions/gsd/tests/help-menu-coverage.test.ts +0 -57
  464. package/src/resources/extensions/gsd/tests/import-done-milestones-runtime.test.ts +0 -145
  465. package/src/resources/extensions/gsd/tests/merge-self-branch-guard.test.ts +0 -124
  466. package/src/resources/extensions/gsd/tests/milestone-id-gap-reuse-4996.test.ts +0 -152
  467. package/src/resources/extensions/gsd/tests/native-git-infra-errors.test.ts +0 -50
  468. package/src/resources/extensions/gsd/tests/register-hooks-compaction-checkpoint.test.ts +0 -93
  469. package/src/resources/extensions/gsd/tests/system-context-message-routing.test.ts +0 -101
  470. package/src/resources/extensions/gsd/worktree-session-state.ts +0 -35
  471. package/src/resources/extensions/mcp-client/tests/global-config.test.ts +0 -91
  472. package/src/resources/skills/create-gsd-extension/templates/templates.test.ts +0 -58
  473. /package/dist/web/standalone/.next/static/{UF5VF4F1tB0miEtJS7LyX → 7afp7gq8-DVbxum83zRQ-}/_buildManifest.js +0 -0
  474. /package/dist/web/standalone/.next/static/{UF5VF4F1tB0miEtJS7LyX → 7afp7gq8-DVbxum83zRQ-}/_ssgManifest.js +0 -0
@@ -1,231 +0,0 @@
1
- // @gsd/pi-ai + anthropic-shared.cache-breakpoint.test — coverage for #5027.
2
- // `convertMessages` must apply Anthropic `cache_control` to:
3
- // - the last message (existing volatile-suffix anchor — preserved)
4
- // - the most recent message flagged with `cacheBreakpoint: true`
5
- // (new compaction-boundary anchor)
6
- // And it must NOT exceed the 4-breakpoint limit by treating multiple
7
- // breakpoints as one — only the most recent earns the marker.
8
- import { describe, test } from "node:test";
9
- import assert from "node:assert/strict";
10
- import { buildParams, convertMessages } from "./anthropic-shared.js";
11
- // Minimal model stub — convertMessages only reads `input` to decide whether to
12
- // drop image blocks. Returning ["image"] keeps the conversion paths exercised.
13
- const model = { input: ["text", "image"] };
14
- const cacheControl = { type: "ephemeral" };
15
- function userMsg(text, opts = {}) {
16
- return {
17
- role: "user",
18
- content: text,
19
- timestamp: 0,
20
- ...(opts.cacheBreakpoint ? { cacheBreakpoint: true } : {}),
21
- };
22
- }
23
- /** Produces a UserMessage whose content is an array of text blocks —
24
- * the production shape emitted by `convertToLlm()` for compaction summaries. */
25
- function userMsgArray(text, opts = {}) {
26
- return {
27
- role: "user",
28
- content: [{ type: "text", text }],
29
- timestamp: 0,
30
- ...(opts.cacheBreakpoint ? { cacheBreakpoint: true } : {}),
31
- };
32
- }
33
- function assistantMsg(text) {
34
- return {
35
- role: "assistant",
36
- content: [{ type: "text", text }],
37
- api: "anthropic-messages",
38
- provider: "anthropic",
39
- model: "claude-sonnet-4-6",
40
- usage: {
41
- input: 0, output: 0, cacheRead: 0, cacheWrite: 0, totalTokens: 0,
42
- cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
43
- },
44
- stopReason: "stop",
45
- timestamp: 0,
46
- };
47
- }
48
- /** Returns whether the message at the given index has cache_control on its last block. */
49
- function hasCacheControl(params, index) {
50
- const param = params[index];
51
- if (!param || param.role !== "user")
52
- return false;
53
- if (!Array.isArray(param.content))
54
- return false;
55
- const lastBlock = param.content[param.content.length - 1];
56
- return Boolean(lastBlock && lastBlock.cache_control);
57
- }
58
- describe("convertMessages — cache breakpoints (#5027)", () => {
59
- test("with no cacheControl option: no breakpoints are placed", () => {
60
- const result = convertMessages([userMsg("hello"), assistantMsg("hi"), userMsg("again")], model, false);
61
- for (let i = 0; i < result.length; i++) {
62
- assert.equal(hasCacheControl(result, i), false, `index ${i} should have no cache_control`);
63
- }
64
- });
65
- test("no cacheBreakpoint anywhere: only the last message gets cache_control (existing behavior preserved)", () => {
66
- const result = convertMessages([userMsg("first"), assistantMsg("response"), userMsg("second")], model, false, cacheControl);
67
- assert.equal(hasCacheControl(result, 0), false, "first user msg has no breakpoint");
68
- assert.equal(hasCacheControl(result, result.length - 1), true, "last msg gets the volatile-suffix anchor");
69
- });
70
- test("one cacheBreakpoint message: both that message AND the last message get breakpoints", () => {
71
- const result = convertMessages([
72
- userMsg("ancient"),
73
- assistantMsg("ancient response"),
74
- userMsg("[COMPACTION SUMMARY]", { cacheBreakpoint: true }),
75
- assistantMsg("post-compaction response"),
76
- userMsg("new turn"),
77
- ], model, false, cacheControl);
78
- // Find the compaction-summary index (it's the third user-shaped param)
79
- const compactionIdx = result.findIndex((p) => p.role === "user" && Array.isArray(p.content) && p.content[0]?.text?.includes("COMPACTION SUMMARY"));
80
- assert.ok(compactionIdx >= 0, "compaction summary should be in the params");
81
- assert.equal(hasCacheControl(result, compactionIdx), true, "compaction boundary gets a breakpoint");
82
- assert.equal(hasCacheControl(result, result.length - 1), true, "last msg still gets the volatile-suffix anchor");
83
- });
84
- test("array-content cacheBreakpoint message: breakpoint is applied (production shape for compaction summary)", () => {
85
- // convertToLlm() emits compaction summaries as content:[{type:"text",...}];
86
- // this exercises the array-backed branch in anthropic-shared.ts.
87
- const result = convertMessages([
88
- userMsgArray("[COMPACTION SUMMARY]", { cacheBreakpoint: true }),
89
- assistantMsg("post-compaction response"),
90
- userMsg("post-compaction turn"),
91
- ], model, false, cacheControl);
92
- const compactionIdx = result.findIndex((p) => p.role === "user" &&
93
- Array.isArray(p.content) &&
94
- p.content[0]?.text?.includes("COMPACTION SUMMARY"));
95
- assert.ok(compactionIdx >= 0, "compaction summary param should be present");
96
- assert.equal(hasCacheControl(result, compactionIdx), true, "array-content boundary gets cache_control");
97
- assert.equal(hasCacheControl(result, result.length - 1), true, "last msg still gets the volatile-suffix anchor");
98
- });
99
- test("multiple cacheBreakpoint messages: only the most recent one earns a breakpoint (4-limit safety)", () => {
100
- const result = convertMessages([
101
- userMsg("[OLD COMPACTION]", { cacheBreakpoint: true }),
102
- assistantMsg("post-old response"),
103
- userMsg("[NEW COMPACTION]", { cacheBreakpoint: true }),
104
- assistantMsg("post-new response"),
105
- userMsg("latest turn"),
106
- ], model, false, cacheControl);
107
- const oldIdx = result.findIndex((p) => p.role === "user" && Array.isArray(p.content) && p.content[0]?.text?.includes("OLD COMPACTION"));
108
- const newIdx = result.findIndex((p) => p.role === "user" && Array.isArray(p.content) && p.content[0]?.text?.includes("NEW COMPACTION"));
109
- assert.equal(hasCacheControl(result, oldIdx), false, "older boundary should not earn a breakpoint");
110
- assert.equal(hasCacheControl(result, newIdx), true, "most recent boundary earns the breakpoint");
111
- assert.equal(hasCacheControl(result, result.length - 1), true, "last msg still gets the volatile-suffix anchor");
112
- });
113
- test("cacheBreakpoint on the LAST message: only one breakpoint applied (deduplication)", () => {
114
- // When the boundary message IS the last message, applying twice would be
115
- // a no-op overwrite but the deduplication guard avoids the double-call.
116
- const result = convertMessages([userMsg("hello"), userMsg("[BOUNDARY AS LAST]", { cacheBreakpoint: true })], model, false, cacheControl);
117
- assert.equal(hasCacheControl(result, result.length - 1), true);
118
- // Only one user message besides the last, with no breakpoint
119
- assert.equal(hasCacheControl(result, 0), false);
120
- });
121
- test("cacheBreakpoint flag is ignored when no cacheControl option is provided", () => {
122
- const result = convertMessages([userMsg("[COMPACTION]", { cacheBreakpoint: true }), userMsg("turn")], model, false);
123
- for (let i = 0; i < result.length; i++) {
124
- assert.equal(hasCacheControl(result, i), false, `index ${i} should have no cache_control`);
125
- }
126
- });
127
- test("array-content cacheBreakpoint on last message: deduplication guard prevents double application", () => {
128
- // The boundary IS the last message — both anchors target the same param,
129
- // so cache_control should appear exactly once.
130
- const result = convertMessages([userMsg("prior turn"), userMsgArray("[BOUNDARY AS LAST]", { cacheBreakpoint: true })], model, false, cacheControl);
131
- const lastParam = result[result.length - 1];
132
- assert.ok(lastParam && Array.isArray(lastParam.content), "last param has array content");
133
- const cacheBlocks = lastParam.content.filter((b) => b.cache_control);
134
- assert.equal(cacheBlocks.length, 1, "cache_control applied exactly once");
135
- assert.equal(hasCacheControl(result, 0), false, "prior turn has no cache_control");
136
- });
137
- });
138
- // ─── 4-breakpoint-limit safety at buildParams level (OAuth path) ──────────
139
- /** Count cache_control occurrences across system + tools + messages params. */
140
- function countBreakpoints(params) {
141
- let n = 0;
142
- if (Array.isArray(params.system)) {
143
- for (const block of params.system)
144
- if (block.cache_control)
145
- n++;
146
- }
147
- if (Array.isArray(params.tools)) {
148
- for (const tool of params.tools)
149
- if (tool.cache_control)
150
- n++;
151
- }
152
- for (const m of params.messages) {
153
- if (m.role === "user" && Array.isArray(m.content)) {
154
- for (const block of m.content)
155
- if (block.cache_control)
156
- n++;
157
- }
158
- }
159
- return n;
160
- }
161
- const buildParamsModel = {
162
- id: "claude-sonnet-4-6",
163
- baseUrl: "https://api.anthropic.com",
164
- api: "anthropic-messages",
165
- input: ["text", "image"],
166
- maxTokens: 64000,
167
- };
168
- describe("buildParams — 4-breakpoint limit safety in OAuth + boundary scenario (#5027)", () => {
169
- test("OAuth + system prompt + last user: ≤2 breakpoints (no boundary, no tools)", () => {
170
- const ctx = {
171
- messages: [userMsg("hello")],
172
- systemPrompt: "You are a helpful coding assistant.",
173
- };
174
- const params = buildParams(buildParamsModel, ctx, true);
175
- assert.ok(countBreakpoints(params) <= 4, `expected ≤4 breakpoints, got ${countBreakpoints(params)}`);
176
- // One on user system block, one on last user msg.
177
- assert.equal(countBreakpoints(params), 2);
178
- });
179
- test("OAuth + system prompt + boundary + last user: ≤3 breakpoints (system de-duplicated)", () => {
180
- const ctx = {
181
- messages: [
182
- userMsg("[COMPACTION SUMMARY]", { cacheBreakpoint: true }),
183
- userMsg("post-compaction turn"),
184
- ],
185
- systemPrompt: "You are a helpful coding assistant.",
186
- };
187
- const params = buildParams(buildParamsModel, ctx, true);
188
- const count = countBreakpoints(params);
189
- assert.ok(count <= 4, `must stay under Anthropic's 4-breakpoint limit, got ${count}`);
190
- // system(1, the user's prompt — Claude Code header skipped) + boundary(1) + last(1) = 3.
191
- assert.equal(count, 3);
192
- });
193
- test("OAuth + system prompt + tools + boundary + last user: exactly 4 breakpoints (ceiling)", () => {
194
- // Worst-case breakpoint budget:
195
- // system(user prompt, 1) + last tool(1) + boundary(1) + last user(1) = 4.
196
- // The "You are Claude Code" header intentionally carries NO cache_control
197
- // when a user systemPrompt is present (#5027), which keeps us at 4 rather than 5.
198
- const tool = {
199
- name: "Read",
200
- description: "Read a file from disk.",
201
- parameters: {
202
- type: "object",
203
- properties: {
204
- path: { type: "string" },
205
- },
206
- required: ["path"],
207
- },
208
- };
209
- const ctx = {
210
- messages: [
211
- userMsg("[COMPACTION SUMMARY]", { cacheBreakpoint: true }),
212
- userMsg("post-compaction turn"),
213
- ],
214
- systemPrompt: "You are a helpful coding assistant.",
215
- tools: [tool],
216
- };
217
- const params = buildParams(buildParamsModel, ctx, true);
218
- const count = countBreakpoints(params);
219
- assert.ok(count <= 4, `must stay under Anthropic's 4-breakpoint limit, got ${count}`);
220
- // system(1) + tool(1) + boundary(1) + last-user(1) = 4 exactly.
221
- assert.equal(count, 4);
222
- });
223
- test("OAuth header WITHOUT user systemPrompt still cache-marks the header", () => {
224
- // When there's no user systemPrompt, the Claude Code header IS the
225
- // last system block, so it correctly carries cache_control.
226
- const ctx = { messages: [userMsg("hello")] };
227
- const params = buildParams(buildParamsModel, ctx, true);
228
- assert.equal(countBreakpoints(params), 2, "header(1) + last user(1) = 2");
229
- });
230
- });
231
- //# sourceMappingURL=anthropic-shared.cache-breakpoint.test.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"anthropic-shared.cache-breakpoint.test.js","sourceRoot":"","sources":["../../src/providers/anthropic-shared.cache-breakpoint.test.ts"],"names":[],"mappings":"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","sourcesContent":["// @gsd/pi-ai + anthropic-shared.cache-breakpoint.test — coverage for #5027.\n// `convertMessages` must apply Anthropic `cache_control` to:\n// - the last message (existing volatile-suffix anchor — preserved)\n// - the most recent message flagged with `cacheBreakpoint: true`\n// (new compaction-boundary anchor)\n// And it must NOT exceed the 4-breakpoint limit by treating multiple\n// breakpoints as one — only the most recent earns the marker.\n\nimport { describe, test } from \"node:test\";\nimport assert from \"node:assert/strict\";\n\nimport { buildParams, convertMessages } from \"./anthropic-shared.js\";\nimport type { Context, Message, Model, Tool } from \"../types.js\";\nimport type { AnthropicApi } from \"./anthropic-shared.js\";\n\n// Minimal model stub — convertMessages only reads `input` to decide whether to\n// drop image blocks. Returning [\"image\"] keeps the conversion paths exercised.\nconst model = { input: [\"text\", \"image\"] } as unknown as Model<AnthropicApi>;\nconst cacheControl = { type: \"ephemeral\" as const };\n\nfunction userMsg(text: string, opts: { cacheBreakpoint?: boolean } = {}): Message {\n\treturn {\n\t\trole: \"user\",\n\t\tcontent: text,\n\t\ttimestamp: 0,\n\t\t...(opts.cacheBreakpoint ? { cacheBreakpoint: true } : {}),\n\t} as Message;\n}\n\n/** Produces a UserMessage whose content is an array of text blocks —\n * the production shape emitted by `convertToLlm()` for compaction summaries. */\nfunction userMsgArray(text: string, opts: { cacheBreakpoint?: boolean } = {}): Message {\n\treturn {\n\t\trole: \"user\",\n\t\tcontent: [{ type: \"text\", text }],\n\t\ttimestamp: 0,\n\t\t...(opts.cacheBreakpoint ? { cacheBreakpoint: true } : {}),\n\t} as Message;\n}\n\nfunction assistantMsg(text: string): Message {\n\treturn {\n\t\trole: \"assistant\",\n\t\tcontent: [{ type: \"text\", text }],\n\t\tapi: \"anthropic-messages\",\n\t\tprovider: \"anthropic\",\n\t\tmodel: \"claude-sonnet-4-6\",\n\t\tusage: {\n\t\t\tinput: 0, output: 0, cacheRead: 0, cacheWrite: 0, totalTokens: 0,\n\t\t\tcost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },\n\t\t},\n\t\tstopReason: \"stop\",\n\t\ttimestamp: 0,\n\t} as Message;\n}\n\n/** Returns whether the message at the given index has cache_control on its last block. */\nfunction hasCacheControl(params: ReturnType<typeof convertMessages>, index: number): boolean {\n\tconst param = params[index];\n\tif (!param || param.role !== \"user\") return false;\n\tif (!Array.isArray(param.content)) return false;\n\tconst lastBlock = param.content[param.content.length - 1];\n\treturn Boolean(lastBlock && (lastBlock as any).cache_control);\n}\n\ndescribe(\"convertMessages — cache breakpoints (#5027)\", () => {\n\ttest(\"with no cacheControl option: no breakpoints are placed\", () => {\n\t\tconst result = convertMessages([userMsg(\"hello\"), assistantMsg(\"hi\"), userMsg(\"again\")], model, false);\n\t\tfor (let i = 0; i < result.length; i++) {\n\t\t\tassert.equal(hasCacheControl(result, i), false, `index ${i} should have no cache_control`);\n\t\t}\n\t});\n\n\ttest(\"no cacheBreakpoint anywhere: only the last message gets cache_control (existing behavior preserved)\", () => {\n\t\tconst result = convertMessages(\n\t\t\t[userMsg(\"first\"), assistantMsg(\"response\"), userMsg(\"second\")],\n\t\t\tmodel,\n\t\t\tfalse,\n\t\t\tcacheControl,\n\t\t);\n\t\tassert.equal(hasCacheControl(result, 0), false, \"first user msg has no breakpoint\");\n\t\tassert.equal(hasCacheControl(result, result.length - 1), true, \"last msg gets the volatile-suffix anchor\");\n\t});\n\n\ttest(\"one cacheBreakpoint message: both that message AND the last message get breakpoints\", () => {\n\t\tconst result = convertMessages(\n\t\t\t[\n\t\t\t\tuserMsg(\"ancient\"),\n\t\t\t\tassistantMsg(\"ancient response\"),\n\t\t\t\tuserMsg(\"[COMPACTION SUMMARY]\", { cacheBreakpoint: true }),\n\t\t\t\tassistantMsg(\"post-compaction response\"),\n\t\t\t\tuserMsg(\"new turn\"),\n\t\t\t],\n\t\t\tmodel,\n\t\t\tfalse,\n\t\t\tcacheControl,\n\t\t);\n\t\t// Find the compaction-summary index (it's the third user-shaped param)\n\t\tconst compactionIdx = result.findIndex(\n\t\t\t(p) => p.role === \"user\" && Array.isArray(p.content) && (p.content as any)[0]?.text?.includes(\"COMPACTION SUMMARY\"),\n\t\t);\n\t\tassert.ok(compactionIdx >= 0, \"compaction summary should be in the params\");\n\t\tassert.equal(hasCacheControl(result, compactionIdx), true, \"compaction boundary gets a breakpoint\");\n\t\tassert.equal(hasCacheControl(result, result.length - 1), true, \"last msg still gets the volatile-suffix anchor\");\n\t});\n\n\ttest(\"array-content cacheBreakpoint message: breakpoint is applied (production shape for compaction summary)\", () => {\n\t\t// convertToLlm() emits compaction summaries as content:[{type:\"text\",...}];\n\t\t// this exercises the array-backed branch in anthropic-shared.ts.\n\t\tconst result = convertMessages(\n\t\t\t[\n\t\t\t\tuserMsgArray(\"[COMPACTION SUMMARY]\", { cacheBreakpoint: true }),\n\t\t\t\tassistantMsg(\"post-compaction response\"),\n\t\t\t\tuserMsg(\"post-compaction turn\"),\n\t\t\t],\n\t\t\tmodel,\n\t\t\tfalse,\n\t\t\tcacheControl,\n\t\t);\n\t\tconst compactionIdx = result.findIndex(\n\t\t\t(p) =>\n\t\t\t\tp.role === \"user\" &&\n\t\t\t\tArray.isArray(p.content) &&\n\t\t\t\t(p.content as any)[0]?.text?.includes(\"COMPACTION SUMMARY\"),\n\t\t);\n\t\tassert.ok(compactionIdx >= 0, \"compaction summary param should be present\");\n\t\tassert.equal(hasCacheControl(result, compactionIdx), true, \"array-content boundary gets cache_control\");\n\t\tassert.equal(hasCacheControl(result, result.length - 1), true, \"last msg still gets the volatile-suffix anchor\");\n\t});\n\n\ttest(\"multiple cacheBreakpoint messages: only the most recent one earns a breakpoint (4-limit safety)\", () => {\n\t\tconst result = convertMessages(\n\t\t\t[\n\t\t\t\tuserMsg(\"[OLD COMPACTION]\", { cacheBreakpoint: true }),\n\t\t\t\tassistantMsg(\"post-old response\"),\n\t\t\t\tuserMsg(\"[NEW COMPACTION]\", { cacheBreakpoint: true }),\n\t\t\t\tassistantMsg(\"post-new response\"),\n\t\t\t\tuserMsg(\"latest turn\"),\n\t\t\t],\n\t\t\tmodel,\n\t\t\tfalse,\n\t\t\tcacheControl,\n\t\t);\n\t\tconst oldIdx = result.findIndex(\n\t\t\t(p) => p.role === \"user\" && Array.isArray(p.content) && (p.content as any)[0]?.text?.includes(\"OLD COMPACTION\"),\n\t\t);\n\t\tconst newIdx = result.findIndex(\n\t\t\t(p) => p.role === \"user\" && Array.isArray(p.content) && (p.content as any)[0]?.text?.includes(\"NEW COMPACTION\"),\n\t\t);\n\t\tassert.equal(hasCacheControl(result, oldIdx), false, \"older boundary should not earn a breakpoint\");\n\t\tassert.equal(hasCacheControl(result, newIdx), true, \"most recent boundary earns the breakpoint\");\n\t\tassert.equal(hasCacheControl(result, result.length - 1), true, \"last msg still gets the volatile-suffix anchor\");\n\t});\n\n\ttest(\"cacheBreakpoint on the LAST message: only one breakpoint applied (deduplication)\", () => {\n\t\t// When the boundary message IS the last message, applying twice would be\n\t\t// a no-op overwrite but the deduplication guard avoids the double-call.\n\t\tconst result = convertMessages(\n\t\t\t[userMsg(\"hello\"), userMsg(\"[BOUNDARY AS LAST]\", { cacheBreakpoint: true })],\n\t\t\tmodel,\n\t\t\tfalse,\n\t\t\tcacheControl,\n\t\t);\n\t\tassert.equal(hasCacheControl(result, result.length - 1), true);\n\t\t// Only one user message besides the last, with no breakpoint\n\t\tassert.equal(hasCacheControl(result, 0), false);\n\t});\n\n\ttest(\"cacheBreakpoint flag is ignored when no cacheControl option is provided\", () => {\n\t\tconst result = convertMessages(\n\t\t\t[userMsg(\"[COMPACTION]\", { cacheBreakpoint: true }), userMsg(\"turn\")],\n\t\t\tmodel,\n\t\t\tfalse,\n\t\t);\n\t\tfor (let i = 0; i < result.length; i++) {\n\t\t\tassert.equal(hasCacheControl(result, i), false, `index ${i} should have no cache_control`);\n\t\t}\n\t});\n\n\ttest(\"array-content cacheBreakpoint on last message: deduplication guard prevents double application\", () => {\n\t\t// The boundary IS the last message — both anchors target the same param,\n\t\t// so cache_control should appear exactly once.\n\t\tconst result = convertMessages(\n\t\t\t[userMsg(\"prior turn\"), userMsgArray(\"[BOUNDARY AS LAST]\", { cacheBreakpoint: true })],\n\t\t\tmodel,\n\t\t\tfalse,\n\t\t\tcacheControl,\n\t\t);\n\t\tconst lastParam = result[result.length - 1];\n\t\tassert.ok(lastParam && Array.isArray(lastParam.content), \"last param has array content\");\n\t\tconst cacheBlocks = (lastParam!.content as any[]).filter((b) => b.cache_control);\n\t\tassert.equal(cacheBlocks.length, 1, \"cache_control applied exactly once\");\n\t\tassert.equal(hasCacheControl(result, 0), false, \"prior turn has no cache_control\");\n\t});\n});\n\n// ─── 4-breakpoint-limit safety at buildParams level (OAuth path) ──────────\n\n/** Count cache_control occurrences across system + tools + messages params. */\nfunction countBreakpoints(params: { system?: any; tools?: any[]; messages: any[] }): number {\n\tlet n = 0;\n\tif (Array.isArray(params.system)) {\n\t\tfor (const block of params.system) if (block.cache_control) n++;\n\t}\n\tif (Array.isArray(params.tools)) {\n\t\tfor (const tool of params.tools) if ((tool as any).cache_control) n++;\n\t}\n\tfor (const m of params.messages) {\n\t\tif (m.role === \"user\" && Array.isArray(m.content)) {\n\t\t\tfor (const block of m.content) if (block.cache_control) n++;\n\t\t}\n\t}\n\treturn n;\n}\n\nconst buildParamsModel = {\n\tid: \"claude-sonnet-4-6\",\n\tbaseUrl: \"https://api.anthropic.com\",\n\tapi: \"anthropic-messages\",\n\tinput: [\"text\", \"image\"],\n\tmaxTokens: 64000,\n} as unknown as Model<AnthropicApi>;\n\ndescribe(\"buildParams — 4-breakpoint limit safety in OAuth + boundary scenario (#5027)\", () => {\n\ttest(\"OAuth + system prompt + last user: ≤2 breakpoints (no boundary, no tools)\", () => {\n\t\tconst ctx: Context = {\n\t\t\tmessages: [userMsg(\"hello\")],\n\t\t\tsystemPrompt: \"You are a helpful coding assistant.\",\n\t\t} as Context;\n\t\tconst params = buildParams(buildParamsModel, ctx, true) as any;\n\t\tassert.ok(countBreakpoints(params) <= 4, `expected ≤4 breakpoints, got ${countBreakpoints(params)}`);\n\t\t// One on user system block, one on last user msg.\n\t\tassert.equal(countBreakpoints(params), 2);\n\t});\n\n\ttest(\"OAuth + system prompt + boundary + last user: ≤3 breakpoints (system de-duplicated)\", () => {\n\t\tconst ctx: Context = {\n\t\t\tmessages: [\n\t\t\t\tuserMsg(\"[COMPACTION SUMMARY]\", { cacheBreakpoint: true }),\n\t\t\t\tuserMsg(\"post-compaction turn\"),\n\t\t\t],\n\t\t\tsystemPrompt: \"You are a helpful coding assistant.\",\n\t\t} as Context;\n\t\tconst params = buildParams(buildParamsModel, ctx, true) as any;\n\t\tconst count = countBreakpoints(params);\n\t\tassert.ok(count <= 4, `must stay under Anthropic's 4-breakpoint limit, got ${count}`);\n\t\t// system(1, the user's prompt — Claude Code header skipped) + boundary(1) + last(1) = 3.\n\t\tassert.equal(count, 3);\n\t});\n\n\ttest(\"OAuth + system prompt + tools + boundary + last user: exactly 4 breakpoints (ceiling)\", () => {\n\t\t// Worst-case breakpoint budget:\n\t\t// system(user prompt, 1) + last tool(1) + boundary(1) + last user(1) = 4.\n\t\t// The \"You are Claude Code\" header intentionally carries NO cache_control\n\t\t// when a user systemPrompt is present (#5027), which keeps us at 4 rather than 5.\n\t\tconst tool: Tool = {\n\t\t\tname: \"Read\",\n\t\t\tdescription: \"Read a file from disk.\",\n\t\t\tparameters: {\n\t\t\t\ttype: \"object\" as const,\n\t\t\t\tproperties: {\n\t\t\t\t\tpath: { type: \"string\" },\n\t\t\t\t},\n\t\t\t\trequired: [\"path\"],\n\t\t\t} as any,\n\t\t};\n\t\tconst ctx: Context = {\n\t\t\tmessages: [\n\t\t\t\tuserMsg(\"[COMPACTION SUMMARY]\", { cacheBreakpoint: true }),\n\t\t\t\tuserMsg(\"post-compaction turn\"),\n\t\t\t],\n\t\t\tsystemPrompt: \"You are a helpful coding assistant.\",\n\t\t\ttools: [tool],\n\t\t} as Context;\n\t\tconst params = buildParams(buildParamsModel, ctx, true) as any;\n\t\tconst count = countBreakpoints(params);\n\t\tassert.ok(count <= 4, `must stay under Anthropic's 4-breakpoint limit, got ${count}`);\n\t\t// system(1) + tool(1) + boundary(1) + last-user(1) = 4 exactly.\n\t\tassert.equal(count, 4);\n\t});\n\n\ttest(\"OAuth header WITHOUT user systemPrompt still cache-marks the header\", () => {\n\t\t// When there's no user systemPrompt, the Claude Code header IS the\n\t\t// last system block, so it correctly carries cache_control.\n\t\tconst ctx: Context = { messages: [userMsg(\"hello\")] } as Context;\n\t\tconst params = buildParams(buildParamsModel, ctx, true) as any;\n\t\tassert.equal(countBreakpoints(params), 2, \"header(1) + last user(1) = 2\");\n\t});\n});\n"]}
@@ -1,289 +0,0 @@
1
- // @gsd/pi-ai + anthropic-shared.cache-breakpoint.test — coverage for #5027.
2
- // `convertMessages` must apply Anthropic `cache_control` to:
3
- // - the last message (existing volatile-suffix anchor — preserved)
4
- // - the most recent message flagged with `cacheBreakpoint: true`
5
- // (new compaction-boundary anchor)
6
- // And it must NOT exceed the 4-breakpoint limit by treating multiple
7
- // breakpoints as one — only the most recent earns the marker.
8
-
9
- import { describe, test } from "node:test";
10
- import assert from "node:assert/strict";
11
-
12
- import { buildParams, convertMessages } from "./anthropic-shared.js";
13
- import type { Context, Message, Model, Tool } from "../types.js";
14
- import type { AnthropicApi } from "./anthropic-shared.js";
15
-
16
- // Minimal model stub — convertMessages only reads `input` to decide whether to
17
- // drop image blocks. Returning ["image"] keeps the conversion paths exercised.
18
- const model = { input: ["text", "image"] } as unknown as Model<AnthropicApi>;
19
- const cacheControl = { type: "ephemeral" as const };
20
-
21
- function userMsg(text: string, opts: { cacheBreakpoint?: boolean } = {}): Message {
22
- return {
23
- role: "user",
24
- content: text,
25
- timestamp: 0,
26
- ...(opts.cacheBreakpoint ? { cacheBreakpoint: true } : {}),
27
- } as Message;
28
- }
29
-
30
- /** Produces a UserMessage whose content is an array of text blocks —
31
- * the production shape emitted by `convertToLlm()` for compaction summaries. */
32
- function userMsgArray(text: string, opts: { cacheBreakpoint?: boolean } = {}): Message {
33
- return {
34
- role: "user",
35
- content: [{ type: "text", text }],
36
- timestamp: 0,
37
- ...(opts.cacheBreakpoint ? { cacheBreakpoint: true } : {}),
38
- } as Message;
39
- }
40
-
41
- function assistantMsg(text: string): Message {
42
- return {
43
- role: "assistant",
44
- content: [{ type: "text", text }],
45
- api: "anthropic-messages",
46
- provider: "anthropic",
47
- model: "claude-sonnet-4-6",
48
- usage: {
49
- input: 0, output: 0, cacheRead: 0, cacheWrite: 0, totalTokens: 0,
50
- cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
51
- },
52
- stopReason: "stop",
53
- timestamp: 0,
54
- } as Message;
55
- }
56
-
57
- /** Returns whether the message at the given index has cache_control on its last block. */
58
- function hasCacheControl(params: ReturnType<typeof convertMessages>, index: number): boolean {
59
- const param = params[index];
60
- if (!param || param.role !== "user") return false;
61
- if (!Array.isArray(param.content)) return false;
62
- const lastBlock = param.content[param.content.length - 1];
63
- return Boolean(lastBlock && (lastBlock as any).cache_control);
64
- }
65
-
66
- describe("convertMessages — cache breakpoints (#5027)", () => {
67
- test("with no cacheControl option: no breakpoints are placed", () => {
68
- const result = convertMessages([userMsg("hello"), assistantMsg("hi"), userMsg("again")], model, false);
69
- for (let i = 0; i < result.length; i++) {
70
- assert.equal(hasCacheControl(result, i), false, `index ${i} should have no cache_control`);
71
- }
72
- });
73
-
74
- test("no cacheBreakpoint anywhere: only the last message gets cache_control (existing behavior preserved)", () => {
75
- const result = convertMessages(
76
- [userMsg("first"), assistantMsg("response"), userMsg("second")],
77
- model,
78
- false,
79
- cacheControl,
80
- );
81
- assert.equal(hasCacheControl(result, 0), false, "first user msg has no breakpoint");
82
- assert.equal(hasCacheControl(result, result.length - 1), true, "last msg gets the volatile-suffix anchor");
83
- });
84
-
85
- test("one cacheBreakpoint message: both that message AND the last message get breakpoints", () => {
86
- const result = convertMessages(
87
- [
88
- userMsg("ancient"),
89
- assistantMsg("ancient response"),
90
- userMsg("[COMPACTION SUMMARY]", { cacheBreakpoint: true }),
91
- assistantMsg("post-compaction response"),
92
- userMsg("new turn"),
93
- ],
94
- model,
95
- false,
96
- cacheControl,
97
- );
98
- // Find the compaction-summary index (it's the third user-shaped param)
99
- const compactionIdx = result.findIndex(
100
- (p) => p.role === "user" && Array.isArray(p.content) && (p.content as any)[0]?.text?.includes("COMPACTION SUMMARY"),
101
- );
102
- assert.ok(compactionIdx >= 0, "compaction summary should be in the params");
103
- assert.equal(hasCacheControl(result, compactionIdx), true, "compaction boundary gets a breakpoint");
104
- assert.equal(hasCacheControl(result, result.length - 1), true, "last msg still gets the volatile-suffix anchor");
105
- });
106
-
107
- test("array-content cacheBreakpoint message: breakpoint is applied (production shape for compaction summary)", () => {
108
- // convertToLlm() emits compaction summaries as content:[{type:"text",...}];
109
- // this exercises the array-backed branch in anthropic-shared.ts.
110
- const result = convertMessages(
111
- [
112
- userMsgArray("[COMPACTION SUMMARY]", { cacheBreakpoint: true }),
113
- assistantMsg("post-compaction response"),
114
- userMsg("post-compaction turn"),
115
- ],
116
- model,
117
- false,
118
- cacheControl,
119
- );
120
- const compactionIdx = result.findIndex(
121
- (p) =>
122
- p.role === "user" &&
123
- Array.isArray(p.content) &&
124
- (p.content as any)[0]?.text?.includes("COMPACTION SUMMARY"),
125
- );
126
- assert.ok(compactionIdx >= 0, "compaction summary param should be present");
127
- assert.equal(hasCacheControl(result, compactionIdx), true, "array-content boundary gets cache_control");
128
- assert.equal(hasCacheControl(result, result.length - 1), true, "last msg still gets the volatile-suffix anchor");
129
- });
130
-
131
- test("multiple cacheBreakpoint messages: only the most recent one earns a breakpoint (4-limit safety)", () => {
132
- const result = convertMessages(
133
- [
134
- userMsg("[OLD COMPACTION]", { cacheBreakpoint: true }),
135
- assistantMsg("post-old response"),
136
- userMsg("[NEW COMPACTION]", { cacheBreakpoint: true }),
137
- assistantMsg("post-new response"),
138
- userMsg("latest turn"),
139
- ],
140
- model,
141
- false,
142
- cacheControl,
143
- );
144
- const oldIdx = result.findIndex(
145
- (p) => p.role === "user" && Array.isArray(p.content) && (p.content as any)[0]?.text?.includes("OLD COMPACTION"),
146
- );
147
- const newIdx = result.findIndex(
148
- (p) => p.role === "user" && Array.isArray(p.content) && (p.content as any)[0]?.text?.includes("NEW COMPACTION"),
149
- );
150
- assert.equal(hasCacheControl(result, oldIdx), false, "older boundary should not earn a breakpoint");
151
- assert.equal(hasCacheControl(result, newIdx), true, "most recent boundary earns the breakpoint");
152
- assert.equal(hasCacheControl(result, result.length - 1), true, "last msg still gets the volatile-suffix anchor");
153
- });
154
-
155
- test("cacheBreakpoint on the LAST message: only one breakpoint applied (deduplication)", () => {
156
- // When the boundary message IS the last message, applying twice would be
157
- // a no-op overwrite but the deduplication guard avoids the double-call.
158
- const result = convertMessages(
159
- [userMsg("hello"), userMsg("[BOUNDARY AS LAST]", { cacheBreakpoint: true })],
160
- model,
161
- false,
162
- cacheControl,
163
- );
164
- assert.equal(hasCacheControl(result, result.length - 1), true);
165
- // Only one user message besides the last, with no breakpoint
166
- assert.equal(hasCacheControl(result, 0), false);
167
- });
168
-
169
- test("cacheBreakpoint flag is ignored when no cacheControl option is provided", () => {
170
- const result = convertMessages(
171
- [userMsg("[COMPACTION]", { cacheBreakpoint: true }), userMsg("turn")],
172
- model,
173
- false,
174
- );
175
- for (let i = 0; i < result.length; i++) {
176
- assert.equal(hasCacheControl(result, i), false, `index ${i} should have no cache_control`);
177
- }
178
- });
179
-
180
- test("array-content cacheBreakpoint on last message: deduplication guard prevents double application", () => {
181
- // The boundary IS the last message — both anchors target the same param,
182
- // so cache_control should appear exactly once.
183
- const result = convertMessages(
184
- [userMsg("prior turn"), userMsgArray("[BOUNDARY AS LAST]", { cacheBreakpoint: true })],
185
- model,
186
- false,
187
- cacheControl,
188
- );
189
- const lastParam = result[result.length - 1];
190
- assert.ok(lastParam && Array.isArray(lastParam.content), "last param has array content");
191
- const cacheBlocks = (lastParam!.content as any[]).filter((b) => b.cache_control);
192
- assert.equal(cacheBlocks.length, 1, "cache_control applied exactly once");
193
- assert.equal(hasCacheControl(result, 0), false, "prior turn has no cache_control");
194
- });
195
- });
196
-
197
- // ─── 4-breakpoint-limit safety at buildParams level (OAuth path) ──────────
198
-
199
- /** Count cache_control occurrences across system + tools + messages params. */
200
- function countBreakpoints(params: { system?: any; tools?: any[]; messages: any[] }): number {
201
- let n = 0;
202
- if (Array.isArray(params.system)) {
203
- for (const block of params.system) if (block.cache_control) n++;
204
- }
205
- if (Array.isArray(params.tools)) {
206
- for (const tool of params.tools) if ((tool as any).cache_control) n++;
207
- }
208
- for (const m of params.messages) {
209
- if (m.role === "user" && Array.isArray(m.content)) {
210
- for (const block of m.content) if (block.cache_control) n++;
211
- }
212
- }
213
- return n;
214
- }
215
-
216
- const buildParamsModel = {
217
- id: "claude-sonnet-4-6",
218
- baseUrl: "https://api.anthropic.com",
219
- api: "anthropic-messages",
220
- input: ["text", "image"],
221
- maxTokens: 64000,
222
- } as unknown as Model<AnthropicApi>;
223
-
224
- describe("buildParams — 4-breakpoint limit safety in OAuth + boundary scenario (#5027)", () => {
225
- test("OAuth + system prompt + last user: ≤2 breakpoints (no boundary, no tools)", () => {
226
- const ctx: Context = {
227
- messages: [userMsg("hello")],
228
- systemPrompt: "You are a helpful coding assistant.",
229
- } as Context;
230
- const params = buildParams(buildParamsModel, ctx, true) as any;
231
- assert.ok(countBreakpoints(params) <= 4, `expected ≤4 breakpoints, got ${countBreakpoints(params)}`);
232
- // One on user system block, one on last user msg.
233
- assert.equal(countBreakpoints(params), 2);
234
- });
235
-
236
- test("OAuth + system prompt + boundary + last user: ≤3 breakpoints (system de-duplicated)", () => {
237
- const ctx: Context = {
238
- messages: [
239
- userMsg("[COMPACTION SUMMARY]", { cacheBreakpoint: true }),
240
- userMsg("post-compaction turn"),
241
- ],
242
- systemPrompt: "You are a helpful coding assistant.",
243
- } as Context;
244
- const params = buildParams(buildParamsModel, ctx, true) as any;
245
- const count = countBreakpoints(params);
246
- assert.ok(count <= 4, `must stay under Anthropic's 4-breakpoint limit, got ${count}`);
247
- // system(1, the user's prompt — Claude Code header skipped) + boundary(1) + last(1) = 3.
248
- assert.equal(count, 3);
249
- });
250
-
251
- test("OAuth + system prompt + tools + boundary + last user: exactly 4 breakpoints (ceiling)", () => {
252
- // Worst-case breakpoint budget:
253
- // system(user prompt, 1) + last tool(1) + boundary(1) + last user(1) = 4.
254
- // The "You are Claude Code" header intentionally carries NO cache_control
255
- // when a user systemPrompt is present (#5027), which keeps us at 4 rather than 5.
256
- const tool: Tool = {
257
- name: "Read",
258
- description: "Read a file from disk.",
259
- parameters: {
260
- type: "object" as const,
261
- properties: {
262
- path: { type: "string" },
263
- },
264
- required: ["path"],
265
- } as any,
266
- };
267
- const ctx: Context = {
268
- messages: [
269
- userMsg("[COMPACTION SUMMARY]", { cacheBreakpoint: true }),
270
- userMsg("post-compaction turn"),
271
- ],
272
- systemPrompt: "You are a helpful coding assistant.",
273
- tools: [tool],
274
- } as Context;
275
- const params = buildParams(buildParamsModel, ctx, true) as any;
276
- const count = countBreakpoints(params);
277
- assert.ok(count <= 4, `must stay under Anthropic's 4-breakpoint limit, got ${count}`);
278
- // system(1) + tool(1) + boundary(1) + last-user(1) = 4 exactly.
279
- assert.equal(count, 4);
280
- });
281
-
282
- test("OAuth header WITHOUT user systemPrompt still cache-marks the header", () => {
283
- // When there's no user systemPrompt, the Claude Code header IS the
284
- // last system block, so it correctly carries cache_control.
285
- const ctx: Context = { messages: [userMsg("hello")] } as Context;
286
- const params = buildParams(buildParamsModel, ctx, true) as any;
287
- assert.equal(countBreakpoints(params), 2, "header(1) + last user(1) = 2");
288
- });
289
- });
@@ -1,37 +0,0 @@
1
- import type { AssistantMessage } from "@gsd/pi-ai";
2
- /** Schema of one telemetry line. JSON-stable for downstream ingestion. */
3
- export interface TokenTelemetryRecord {
4
- ts: number;
5
- model: string;
6
- stopReason: string;
7
- input: number;
8
- output: number;
9
- cacheRead: number;
10
- cacheWrite: number;
11
- /**
12
- * `usage.cost.total` from the provider. `0` when the provider's cost
13
- * registry has no rates for this model (e.g. unknown third-party providers)
14
- * — distinguish from a true zero-cost call by checking your model registry.
15
- */
16
- costTotal: number;
17
- /**
18
- * Fraction of new prompt tokens served from cache:
19
- * `cacheRead / (cacheRead + input)`. Range [0, 1].
20
- * - `0` when neither cacheRead nor input is present (no division by zero).
21
- * - `1` on a full cache hit (input = 0, cacheRead > 0).
22
- * Note: `input` here is `input_tokens` from the API, which already excludes
23
- * cache reads/writes — the denominator is total prompt tokens consumed.
24
- */
25
- cacheHitRatio: number;
26
- }
27
- /** Build a telemetry record from a finished assistant message. */
28
- export declare function buildTokenTelemetryRecord(msg: AssistantMessage): TokenTelemetryRecord;
29
- /**
30
- * Emit a token-telemetry line if `PI_TOKEN_TELEMETRY=1`. No-op otherwise.
31
- *
32
- * Writes to stderr so it doesn't interfere with TUI/stdout. One JSON object
33
- * per line. Errors during emission are swallowed — telemetry must never
34
- * break the agent loop.
35
- */
36
- export declare function emitTokenTelemetry(msg: AssistantMessage): void;
37
- //# sourceMappingURL=token-telemetry.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"token-telemetry.d.ts","sourceRoot":"","sources":["../../src/core/token-telemetry.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAEnD,0EAA0E;AAC1E,MAAM,WAAW,oBAAoB;IACpC,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB;;;;OAIG;IACH,SAAS,EAAE,MAAM,CAAC;IAClB;;;;;;;OAOG;IACH,aAAa,EAAE,MAAM,CAAC;CACtB;AAED,kEAAkE;AAClE,wBAAgB,yBAAyB,CAAC,GAAG,EAAE,gBAAgB,GAAG,oBAAoB,CAoBrF;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,gBAAgB,GAAG,IAAI,CAQ9D"}
@@ -1,49 +0,0 @@
1
- // @gsd/pi-coding-agent + token-telemetry — opt-in per-call token observability
2
- //
3
- // Emits a single JSON line per assistant message to stderr when
4
- // `PI_TOKEN_TELEMETRY=1` is set. Captures the cache_read_input_tokens and
5
- // cache_creation_input_tokens fields the providers already extract — so we
6
- // can empirically measure prompt-cache effectiveness (e.g. for #5019 and
7
- // future cache strategy work). Off by default — no behavior change.
8
- //
9
- // Capture pattern: `PI_TOKEN_TELEMETRY=1 npm start 2> token-telemetry.jsonl`
10
- /** Build a telemetry record from a finished assistant message. */
11
- export function buildTokenTelemetryRecord(msg) {
12
- const input = msg.usage?.input ?? 0;
13
- const output = msg.usage?.output ?? 0;
14
- const cacheRead = msg.usage?.cacheRead ?? 0;
15
- const cacheWrite = msg.usage?.cacheWrite ?? 0;
16
- const costTotal = msg.usage?.cost?.total ?? 0;
17
- const denom = cacheRead + input;
18
- const cacheHitRatio = denom > 0 ? cacheRead / denom : 0;
19
- return {
20
- ts: msg.timestamp,
21
- model: msg.model,
22
- stopReason: msg.stopReason,
23
- input,
24
- output,
25
- cacheRead,
26
- cacheWrite,
27
- costTotal,
28
- cacheHitRatio,
29
- };
30
- }
31
- /**
32
- * Emit a token-telemetry line if `PI_TOKEN_TELEMETRY=1`. No-op otherwise.
33
- *
34
- * Writes to stderr so it doesn't interfere with TUI/stdout. One JSON object
35
- * per line. Errors during emission are swallowed — telemetry must never
36
- * break the agent loop.
37
- */
38
- export function emitTokenTelemetry(msg) {
39
- if (process.env.PI_TOKEN_TELEMETRY !== "1")
40
- return;
41
- try {
42
- const record = buildTokenTelemetryRecord(msg);
43
- process.stderr.write(`${JSON.stringify(record)}\n`);
44
- }
45
- catch {
46
- // Telemetry must never break the agent loop. Swallow.
47
- }
48
- }
49
- //# sourceMappingURL=token-telemetry.js.map