gsd-pi 2.63.0 → 2.64.0-dev.9c14bd0

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 (448) hide show
  1. package/README.md +46 -134
  2. package/dist/cli.js +48 -6
  3. package/dist/headless-query.js +11 -1
  4. package/dist/help-text.js +4 -1
  5. package/dist/onboarding.js +15 -8
  6. package/dist/resource-loader.js +18 -3
  7. package/dist/resources/extensions/cmux/index.js +21 -12
  8. package/dist/resources/extensions/gsd/auto/detect-stuck.js +27 -0
  9. package/dist/resources/extensions/gsd/auto/finalize-timeout.js +40 -0
  10. package/dist/resources/extensions/gsd/auto/loop.js +4 -0
  11. package/dist/resources/extensions/gsd/auto/phases.js +157 -22
  12. package/dist/resources/extensions/gsd/auto/session.js +12 -0
  13. package/dist/resources/extensions/gsd/auto-dashboard.js +14 -8
  14. package/dist/resources/extensions/gsd/auto-model-selection.js +32 -0
  15. package/dist/resources/extensions/gsd/auto-post-unit.js +222 -11
  16. package/dist/resources/extensions/gsd/auto-prompts.js +25 -0
  17. package/dist/resources/extensions/gsd/auto-recovery.js +15 -7
  18. package/dist/resources/extensions/gsd/auto-start.js +10 -21
  19. package/dist/resources/extensions/gsd/auto-timers.js +2 -1
  20. package/dist/resources/extensions/gsd/auto-tool-tracking.js +17 -0
  21. package/dist/resources/extensions/gsd/auto-verification.js +138 -1
  22. package/dist/resources/extensions/gsd/auto-worktree.js +13 -7
  23. package/dist/resources/extensions/gsd/auto.js +24 -2
  24. package/dist/resources/extensions/gsd/bootstrap/db-tools.js +147 -75
  25. package/dist/resources/extensions/gsd/bootstrap/dynamic-tools.js +13 -0
  26. package/dist/resources/extensions/gsd/bootstrap/query-tools.js +85 -0
  27. package/dist/resources/extensions/gsd/bootstrap/register-extension.js +3 -0
  28. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +32 -1
  29. package/dist/resources/extensions/gsd/bootstrap/sanitize-complete-milestone.js +54 -0
  30. package/dist/resources/extensions/gsd/bootstrap/system-context.js +30 -2
  31. package/dist/resources/extensions/gsd/commands-handlers.js +9 -4
  32. package/dist/resources/extensions/gsd/constants.js +42 -0
  33. package/dist/resources/extensions/gsd/db-writer.js +72 -4
  34. package/dist/resources/extensions/gsd/forensics.js +20 -4
  35. package/dist/resources/extensions/gsd/gsd-db.js +64 -17
  36. package/dist/resources/extensions/gsd/guided-flow.js +19 -0
  37. package/dist/resources/extensions/gsd/metrics.js +27 -1
  38. package/dist/resources/extensions/gsd/native-git-bridge.js +5 -3
  39. package/dist/resources/extensions/gsd/post-execution-checks.js +407 -0
  40. package/dist/resources/extensions/gsd/pre-execution-checks.js +464 -0
  41. package/dist/resources/extensions/gsd/preferences-types.js +6 -0
  42. package/dist/resources/extensions/gsd/preferences-validation.js +33 -0
  43. package/dist/resources/extensions/gsd/preferences.js +11 -2
  44. package/dist/resources/extensions/gsd/prompt-loader.js +7 -0
  45. package/dist/resources/extensions/gsd/prompts/complete-milestone.md +2 -0
  46. package/dist/resources/extensions/gsd/prompts/complete-slice.md +2 -0
  47. package/dist/resources/extensions/gsd/prompts/doctor-heal.md +1 -0
  48. package/dist/resources/extensions/gsd/prompts/forensics.md +2 -0
  49. package/dist/resources/extensions/gsd/prompts/reassess-roadmap.md +2 -0
  50. package/dist/resources/extensions/gsd/prompts/system.md +4 -7
  51. package/dist/resources/extensions/gsd/prompts/validate-milestone.md +2 -0
  52. package/dist/resources/extensions/gsd/roadmap-mutations.js +1 -1
  53. package/dist/resources/extensions/gsd/roadmap-slices.js +9 -5
  54. package/dist/resources/extensions/gsd/safety/content-validator.js +73 -0
  55. package/dist/resources/extensions/gsd/safety/destructive-guard.js +34 -0
  56. package/dist/resources/extensions/gsd/safety/evidence-collector.js +109 -0
  57. package/dist/resources/extensions/gsd/safety/evidence-cross-ref.js +83 -0
  58. package/dist/resources/extensions/gsd/safety/file-change-validator.js +71 -0
  59. package/dist/resources/extensions/gsd/safety/git-checkpoint.js +91 -0
  60. package/dist/resources/extensions/gsd/safety/safety-harness.js +64 -0
  61. package/dist/resources/extensions/gsd/slice-parallel-conflict.js +67 -0
  62. package/dist/resources/extensions/gsd/slice-parallel-eligibility.js +51 -0
  63. package/dist/resources/extensions/gsd/slice-parallel-orchestrator.js +378 -0
  64. package/dist/resources/extensions/gsd/state.js +74 -14
  65. package/dist/resources/extensions/gsd/status-guards.js +11 -0
  66. package/dist/resources/extensions/gsd/tools/complete-milestone.js +17 -12
  67. package/dist/resources/extensions/gsd/tools/complete-slice.js +40 -26
  68. package/dist/resources/extensions/gsd/tools/complete-task.js +12 -12
  69. package/dist/resources/extensions/gsd/tools/plan-milestone.js +33 -25
  70. package/dist/resources/extensions/gsd/tools/plan-slice.js +5 -8
  71. package/dist/resources/extensions/gsd/verification-evidence.js +18 -0
  72. package/dist/resources/extensions/gsd/workflow-projections.js +21 -5
  73. package/dist/resources/extensions/gsd/worktree-manager.js +82 -29
  74. package/dist/resources/extensions/gsd/worktree-resolver.js +4 -3
  75. package/dist/resources/extensions/mcp-client/auth.js +101 -0
  76. package/dist/resources/extensions/mcp-client/index.js +10 -1
  77. package/dist/resources/extensions/ollama/index.js +28 -22
  78. package/dist/resources/extensions/ollama/model-capabilities.js +37 -34
  79. package/dist/resources/extensions/ollama/ndjson-stream.js +54 -0
  80. package/dist/resources/extensions/ollama/ollama-chat-provider.js +380 -0
  81. package/dist/resources/extensions/ollama/ollama-client.js +23 -32
  82. package/dist/resources/extensions/ollama/ollama-discovery.js +2 -7
  83. package/dist/resources/extensions/ollama/ollama-tool.js +62 -0
  84. package/dist/resources/extensions/ollama/thinking-parser.js +104 -0
  85. package/dist/update-cmd.js +4 -2
  86. package/dist/web/standalone/.next/BUILD_ID +1 -1
  87. package/dist/web/standalone/.next/app-path-routes-manifest.json +15 -15
  88. package/dist/web/standalone/.next/build-manifest.json +3 -3
  89. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  90. package/dist/web/standalone/.next/required-server-files.json +4 -4
  91. package/dist/web/standalone/.next/server/app/_global-error/page.js +3 -3
  92. package/dist/web/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  93. package/dist/web/standalone/.next/server/app/_global-error.html +2 -2
  94. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  95. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  96. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  97. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  98. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  99. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  100. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  101. package/dist/web/standalone/.next/server/app/_not-found/page.js +2 -2
  102. package/dist/web/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  103. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  104. package/dist/web/standalone/.next/server/app/_not-found.rsc +3 -3
  105. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +3 -3
  106. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  107. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +3 -3
  108. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  109. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  110. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  111. package/dist/web/standalone/.next/server/app/api/boot/route.js +1 -1
  112. package/dist/web/standalone/.next/server/app/api/boot/route.js.nft.json +1 -1
  113. package/dist/web/standalone/.next/server/app/api/boot/route_client-reference-manifest.js +1 -1
  114. package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route.js +1 -1
  115. package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route.js.nft.json +1 -1
  116. package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route_client-reference-manifest.js +1 -1
  117. package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route.js +1 -1
  118. package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route.js.nft.json +1 -1
  119. package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route_client-reference-manifest.js +1 -1
  120. package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route.js +2 -2
  121. package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route.js.nft.json +1 -1
  122. package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route_client-reference-manifest.js +1 -1
  123. package/dist/web/standalone/.next/server/app/api/browse-directories/route.js +1 -1
  124. package/dist/web/standalone/.next/server/app/api/browse-directories/route_client-reference-manifest.js +1 -1
  125. package/dist/web/standalone/.next/server/app/api/captures/route.js +1 -1
  126. package/dist/web/standalone/.next/server/app/api/captures/route.js.nft.json +1 -1
  127. package/dist/web/standalone/.next/server/app/api/captures/route_client-reference-manifest.js +1 -1
  128. package/dist/web/standalone/.next/server/app/api/cleanup/route.js +1 -1
  129. package/dist/web/standalone/.next/server/app/api/cleanup/route.js.nft.json +1 -1
  130. package/dist/web/standalone/.next/server/app/api/cleanup/route_client-reference-manifest.js +1 -1
  131. package/dist/web/standalone/.next/server/app/api/dev-mode/route.js +1 -1
  132. package/dist/web/standalone/.next/server/app/api/dev-mode/route_client-reference-manifest.js +1 -1
  133. package/dist/web/standalone/.next/server/app/api/doctor/route.js +1 -1
  134. package/dist/web/standalone/.next/server/app/api/doctor/route.js.nft.json +1 -1
  135. package/dist/web/standalone/.next/server/app/api/doctor/route_client-reference-manifest.js +1 -1
  136. package/dist/web/standalone/.next/server/app/api/experimental/route.js +2 -2
  137. package/dist/web/standalone/.next/server/app/api/experimental/route_client-reference-manifest.js +1 -1
  138. package/dist/web/standalone/.next/server/app/api/export-data/route.js +1 -1
  139. package/dist/web/standalone/.next/server/app/api/export-data/route.js.nft.json +1 -1
  140. package/dist/web/standalone/.next/server/app/api/export-data/route_client-reference-manifest.js +1 -1
  141. package/dist/web/standalone/.next/server/app/api/files/route.js +1 -1
  142. package/dist/web/standalone/.next/server/app/api/files/route.js.nft.json +1 -1
  143. package/dist/web/standalone/.next/server/app/api/files/route_client-reference-manifest.js +1 -1
  144. package/dist/web/standalone/.next/server/app/api/forensics/route.js +1 -1
  145. package/dist/web/standalone/.next/server/app/api/forensics/route.js.nft.json +1 -1
  146. package/dist/web/standalone/.next/server/app/api/forensics/route_client-reference-manifest.js +1 -1
  147. package/dist/web/standalone/.next/server/app/api/git/route.js +1 -1
  148. package/dist/web/standalone/.next/server/app/api/git/route.js.nft.json +1 -1
  149. package/dist/web/standalone/.next/server/app/api/git/route_client-reference-manifest.js +1 -1
  150. package/dist/web/standalone/.next/server/app/api/history/route.js +1 -1
  151. package/dist/web/standalone/.next/server/app/api/history/route.js.nft.json +1 -1
  152. package/dist/web/standalone/.next/server/app/api/history/route_client-reference-manifest.js +1 -1
  153. package/dist/web/standalone/.next/server/app/api/hooks/route.js +1 -1
  154. package/dist/web/standalone/.next/server/app/api/hooks/route.js.nft.json +1 -1
  155. package/dist/web/standalone/.next/server/app/api/hooks/route_client-reference-manifest.js +1 -1
  156. package/dist/web/standalone/.next/server/app/api/inspect/route.js +1 -1
  157. package/dist/web/standalone/.next/server/app/api/inspect/route.js.nft.json +1 -1
  158. package/dist/web/standalone/.next/server/app/api/inspect/route_client-reference-manifest.js +1 -1
  159. package/dist/web/standalone/.next/server/app/api/knowledge/route.js +1 -1
  160. package/dist/web/standalone/.next/server/app/api/knowledge/route.js.nft.json +1 -1
  161. package/dist/web/standalone/.next/server/app/api/knowledge/route_client-reference-manifest.js +1 -1
  162. package/dist/web/standalone/.next/server/app/api/live-state/route.js +1 -1
  163. package/dist/web/standalone/.next/server/app/api/live-state/route.js.nft.json +1 -1
  164. package/dist/web/standalone/.next/server/app/api/live-state/route_client-reference-manifest.js +1 -1
  165. package/dist/web/standalone/.next/server/app/api/onboarding/route.js +1 -1
  166. package/dist/web/standalone/.next/server/app/api/onboarding/route.js.nft.json +1 -1
  167. package/dist/web/standalone/.next/server/app/api/onboarding/route_client-reference-manifest.js +1 -1
  168. package/dist/web/standalone/.next/server/app/api/preferences/route.js +1 -1
  169. package/dist/web/standalone/.next/server/app/api/preferences/route_client-reference-manifest.js +1 -1
  170. package/dist/web/standalone/.next/server/app/api/projects/route.js +1 -1
  171. package/dist/web/standalone/.next/server/app/api/projects/route.js.nft.json +1 -1
  172. package/dist/web/standalone/.next/server/app/api/projects/route_client-reference-manifest.js +1 -1
  173. package/dist/web/standalone/.next/server/app/api/recovery/route.js +1 -1
  174. package/dist/web/standalone/.next/server/app/api/recovery/route.js.nft.json +1 -1
  175. package/dist/web/standalone/.next/server/app/api/recovery/route_client-reference-manifest.js +1 -1
  176. package/dist/web/standalone/.next/server/app/api/remote-questions/route.js +2 -2
  177. package/dist/web/standalone/.next/server/app/api/remote-questions/route_client-reference-manifest.js +1 -1
  178. package/dist/web/standalone/.next/server/app/api/session/browser/route.js +1 -1
  179. package/dist/web/standalone/.next/server/app/api/session/browser/route.js.nft.json +1 -1
  180. package/dist/web/standalone/.next/server/app/api/session/browser/route_client-reference-manifest.js +1 -1
  181. package/dist/web/standalone/.next/server/app/api/session/command/route.js +1 -1
  182. package/dist/web/standalone/.next/server/app/api/session/command/route.js.nft.json +1 -1
  183. package/dist/web/standalone/.next/server/app/api/session/command/route_client-reference-manifest.js +1 -1
  184. package/dist/web/standalone/.next/server/app/api/session/events/route.js +2 -2
  185. package/dist/web/standalone/.next/server/app/api/session/events/route.js.nft.json +1 -1
  186. package/dist/web/standalone/.next/server/app/api/session/events/route_client-reference-manifest.js +1 -1
  187. package/dist/web/standalone/.next/server/app/api/session/manage/route.js +1 -1
  188. package/dist/web/standalone/.next/server/app/api/session/manage/route.js.nft.json +1 -1
  189. package/dist/web/standalone/.next/server/app/api/session/manage/route_client-reference-manifest.js +1 -1
  190. package/dist/web/standalone/.next/server/app/api/settings-data/route.js +1 -1
  191. package/dist/web/standalone/.next/server/app/api/settings-data/route.js.nft.json +1 -1
  192. package/dist/web/standalone/.next/server/app/api/settings-data/route_client-reference-manifest.js +1 -1
  193. package/dist/web/standalone/.next/server/app/api/shutdown/route.js +1 -1
  194. package/dist/web/standalone/.next/server/app/api/shutdown/route_client-reference-manifest.js +1 -1
  195. package/dist/web/standalone/.next/server/app/api/skill-health/route.js +1 -1
  196. package/dist/web/standalone/.next/server/app/api/skill-health/route.js.nft.json +1 -1
  197. package/dist/web/standalone/.next/server/app/api/skill-health/route_client-reference-manifest.js +1 -1
  198. package/dist/web/standalone/.next/server/app/api/steer/route.js +1 -1
  199. package/dist/web/standalone/.next/server/app/api/steer/route.js.nft.json +1 -1
  200. package/dist/web/standalone/.next/server/app/api/steer/route_client-reference-manifest.js +1 -1
  201. package/dist/web/standalone/.next/server/app/api/switch-root/route.js +1 -1
  202. package/dist/web/standalone/.next/server/app/api/switch-root/route.js.nft.json +1 -1
  203. package/dist/web/standalone/.next/server/app/api/switch-root/route_client-reference-manifest.js +1 -1
  204. package/dist/web/standalone/.next/server/app/api/terminal/input/route.js +2 -2
  205. package/dist/web/standalone/.next/server/app/api/terminal/input/route_client-reference-manifest.js +1 -1
  206. package/dist/web/standalone/.next/server/app/api/terminal/resize/route.js +2 -2
  207. package/dist/web/standalone/.next/server/app/api/terminal/resize/route_client-reference-manifest.js +1 -1
  208. package/dist/web/standalone/.next/server/app/api/terminal/sessions/route.js +2 -2
  209. package/dist/web/standalone/.next/server/app/api/terminal/sessions/route.js.nft.json +1 -1
  210. package/dist/web/standalone/.next/server/app/api/terminal/sessions/route_client-reference-manifest.js +1 -1
  211. package/dist/web/standalone/.next/server/app/api/terminal/stream/route.js +4 -4
  212. package/dist/web/standalone/.next/server/app/api/terminal/stream/route.js.nft.json +1 -1
  213. package/dist/web/standalone/.next/server/app/api/terminal/stream/route_client-reference-manifest.js +1 -1
  214. package/dist/web/standalone/.next/server/app/api/terminal/upload/route.js +1 -1
  215. package/dist/web/standalone/.next/server/app/api/terminal/upload/route_client-reference-manifest.js +1 -1
  216. package/dist/web/standalone/.next/server/app/api/undo/route.js +1 -1
  217. package/dist/web/standalone/.next/server/app/api/undo/route.js.nft.json +1 -1
  218. package/dist/web/standalone/.next/server/app/api/undo/route_client-reference-manifest.js +1 -1
  219. package/dist/web/standalone/.next/server/app/api/update/route.js +1 -1
  220. package/dist/web/standalone/.next/server/app/api/update/route_client-reference-manifest.js +1 -1
  221. package/dist/web/standalone/.next/server/app/api/visualizer/route.js +1 -1
  222. package/dist/web/standalone/.next/server/app/api/visualizer/route.js.nft.json +1 -1
  223. package/dist/web/standalone/.next/server/app/api/visualizer/route_client-reference-manifest.js +1 -1
  224. package/dist/web/standalone/.next/server/app/index.html +1 -1
  225. package/dist/web/standalone/.next/server/app/index.rsc +4 -4
  226. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
  227. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +4 -4
  228. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  229. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +3 -3
  230. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  231. package/dist/web/standalone/.next/server/app/page.js +2 -2
  232. package/dist/web/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  233. package/dist/web/standalone/.next/server/app-paths-manifest.json +15 -15
  234. package/dist/web/standalone/.next/server/chunks/6897.js +12 -0
  235. package/dist/web/standalone/.next/server/chunks/7471.js +3 -3
  236. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  237. package/dist/web/standalone/.next/server/middleware.js +2 -2
  238. package/dist/web/standalone/.next/server/next-font-manifest.js +1 -1
  239. package/dist/web/standalone/.next/server/next-font-manifest.json +1 -1
  240. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  241. package/dist/web/standalone/.next/server/pages/500.html +2 -2
  242. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  243. package/dist/web/standalone/.next/static/chunks/app/_not-found/{page-2f24283c162b6ab3.js → page-f2a7482d42a5614b.js} +1 -1
  244. package/dist/web/standalone/.next/static/chunks/app/{layout-9ecfd95f343793f0.js → layout-a16c7a7ecdf0c2cf.js} +1 -1
  245. package/dist/web/standalone/.next/static/chunks/app/page-0c485498795110d6.js +1 -0
  246. package/dist/web/standalone/.next/static/chunks/main-app-fdab67f7802d7832.js +1 -0
  247. package/dist/web/standalone/.next/static/chunks/next/dist/client/components/builtin/global-error-459824ffb8c323dd.js +1 -0
  248. package/dist/web/standalone/node_modules/node-pty/build/Makefile +2 -2
  249. package/dist/web/standalone/node_modules/node-pty/build/Release/pty.node +0 -0
  250. package/dist/web/standalone/node_modules/node-pty/build/pty.target.mk +14 -14
  251. package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api.target.mk +14 -14
  252. package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api_except.target.mk +14 -14
  253. package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api_maybe.target.mk +14 -14
  254. package/dist/web/standalone/server.js +1 -1
  255. package/dist/welcome-screen.js +1 -1
  256. package/package.json +1 -1
  257. package/packages/pi-agent-core/dist/agent-loop.d.ts +8 -0
  258. package/packages/pi-agent-core/dist/agent-loop.d.ts.map +1 -1
  259. package/packages/pi-agent-core/dist/agent-loop.js +50 -0
  260. package/packages/pi-agent-core/dist/agent-loop.js.map +1 -1
  261. package/packages/pi-agent-core/src/agent-loop.test.ts +221 -5
  262. package/packages/pi-agent-core/src/agent-loop.ts +53 -0
  263. package/packages/pi-ai/dist/types.d.ts +16 -1
  264. package/packages/pi-ai/dist/types.d.ts.map +1 -1
  265. package/packages/pi-ai/dist/types.js.map +1 -1
  266. package/packages/pi-ai/src/types.ts +18 -1
  267. package/packages/pi-coding-agent/dist/core/auth-storage.d.ts +9 -0
  268. package/packages/pi-coding-agent/dist/core/auth-storage.d.ts.map +1 -1
  269. package/packages/pi-coding-agent/dist/core/auth-storage.js +50 -1
  270. package/packages/pi-coding-agent/dist/core/auth-storage.js.map +1 -1
  271. package/packages/pi-coding-agent/dist/core/auth-storage.test.js +41 -0
  272. package/packages/pi-coding-agent/dist/core/auth-storage.test.js.map +1 -1
  273. package/packages/pi-coding-agent/dist/core/extensions/loader.d.ts +7 -0
  274. package/packages/pi-coding-agent/dist/core/extensions/loader.d.ts.map +1 -1
  275. package/packages/pi-coding-agent/dist/core/extensions/loader.js +31 -4
  276. package/packages/pi-coding-agent/dist/core/extensions/loader.js.map +1 -1
  277. package/packages/pi-coding-agent/dist/core/extensions/loader.test.js +28 -1
  278. package/packages/pi-coding-agent/dist/core/extensions/loader.test.js.map +1 -1
  279. package/packages/pi-coding-agent/dist/core/extensions/provider-registration.test.d.ts +2 -0
  280. package/packages/pi-coding-agent/dist/core/extensions/provider-registration.test.d.ts.map +1 -0
  281. package/packages/pi-coding-agent/dist/core/extensions/provider-registration.test.js +46 -0
  282. package/packages/pi-coding-agent/dist/core/extensions/provider-registration.test.js.map +1 -0
  283. package/packages/pi-coding-agent/dist/core/extensions/types.d.ts +2 -0
  284. package/packages/pi-coding-agent/dist/core/extensions/types.d.ts.map +1 -1
  285. package/packages/pi-coding-agent/dist/core/extensions/types.js.map +1 -1
  286. package/packages/pi-coding-agent/dist/core/model-registry.d.ts +1 -0
  287. package/packages/pi-coding-agent/dist/core/model-registry.d.ts.map +1 -1
  288. package/packages/pi-coding-agent/dist/core/model-registry.js +12 -0
  289. package/packages/pi-coding-agent/dist/core/model-registry.js.map +1 -1
  290. package/packages/pi-coding-agent/dist/core/model-resolver.js +3 -3
  291. package/packages/pi-coding-agent/dist/core/model-resolver.js.map +1 -1
  292. package/packages/pi-coding-agent/dist/core/resource-loader.d.ts +23 -1
  293. package/packages/pi-coding-agent/dist/core/resource-loader.d.ts.map +1 -1
  294. package/packages/pi-coding-agent/dist/core/resource-loader.js +80 -56
  295. package/packages/pi-coding-agent/dist/core/resource-loader.js.map +1 -1
  296. package/packages/pi-coding-agent/dist/core/sdk.d.ts.map +1 -1
  297. package/packages/pi-coding-agent/dist/core/sdk.js +9 -0
  298. package/packages/pi-coding-agent/dist/core/sdk.js.map +1 -1
  299. package/packages/pi-coding-agent/package.json +1 -1
  300. package/packages/pi-coding-agent/src/core/auth-storage.test.ts +53 -0
  301. package/packages/pi-coding-agent/src/core/auth-storage.ts +66 -1
  302. package/packages/pi-coding-agent/src/core/extensions/loader.test.ts +39 -1
  303. package/packages/pi-coding-agent/src/core/extensions/loader.ts +34 -4
  304. package/packages/pi-coding-agent/src/core/extensions/provider-registration.test.ts +81 -0
  305. package/packages/pi-coding-agent/src/core/extensions/types.ts +2 -0
  306. package/packages/pi-coding-agent/src/core/model-registry.ts +14 -0
  307. package/packages/pi-coding-agent/src/core/model-resolver.ts +3 -3
  308. package/packages/pi-coding-agent/src/core/resource-loader.ts +89 -56
  309. package/packages/pi-coding-agent/src/core/sdk.ts +10 -0
  310. package/pkg/package.json +1 -1
  311. package/src/resources/extensions/cmux/index.ts +18 -12
  312. package/src/resources/extensions/gsd/auto/detect-stuck.ts +27 -0
  313. package/src/resources/extensions/gsd/auto/finalize-timeout.ts +46 -0
  314. package/src/resources/extensions/gsd/auto/loop.ts +5 -0
  315. package/src/resources/extensions/gsd/auto/phases.ts +194 -33
  316. package/src/resources/extensions/gsd/auto/session.ts +14 -0
  317. package/src/resources/extensions/gsd/auto-dashboard.ts +16 -7
  318. package/src/resources/extensions/gsd/auto-model-selection.ts +36 -0
  319. package/src/resources/extensions/gsd/auto-post-unit.ts +263 -12
  320. package/src/resources/extensions/gsd/auto-prompts.ts +21 -0
  321. package/src/resources/extensions/gsd/auto-recovery.ts +9 -8
  322. package/src/resources/extensions/gsd/auto-start.ts +11 -20
  323. package/src/resources/extensions/gsd/auto-timers.ts +2 -1
  324. package/src/resources/extensions/gsd/auto-tool-tracking.ts +19 -0
  325. package/src/resources/extensions/gsd/auto-verification.ts +190 -2
  326. package/src/resources/extensions/gsd/auto-worktree.ts +14 -6
  327. package/src/resources/extensions/gsd/auto.ts +26 -1
  328. package/src/resources/extensions/gsd/bootstrap/db-tools.ts +160 -88
  329. package/src/resources/extensions/gsd/bootstrap/dynamic-tools.ts +15 -0
  330. package/src/resources/extensions/gsd/bootstrap/query-tools.ts +98 -0
  331. package/src/resources/extensions/gsd/bootstrap/register-extension.ts +4 -0
  332. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +36 -1
  333. package/src/resources/extensions/gsd/bootstrap/sanitize-complete-milestone.ts +57 -0
  334. package/src/resources/extensions/gsd/bootstrap/system-context.ts +31 -2
  335. package/src/resources/extensions/gsd/commands-handlers.ts +10 -4
  336. package/src/resources/extensions/gsd/constants.ts +44 -0
  337. package/src/resources/extensions/gsd/db-writer.ts +78 -4
  338. package/src/resources/extensions/gsd/forensics.ts +21 -5
  339. package/src/resources/extensions/gsd/gsd-db.ts +64 -17
  340. package/src/resources/extensions/gsd/guided-flow.ts +22 -0
  341. package/src/resources/extensions/gsd/metrics.ts +28 -1
  342. package/src/resources/extensions/gsd/native-git-bridge.ts +5 -3
  343. package/src/resources/extensions/gsd/post-execution-checks.ts +539 -0
  344. package/src/resources/extensions/gsd/pre-execution-checks.ts +573 -0
  345. package/src/resources/extensions/gsd/preferences-types.ts +44 -0
  346. package/src/resources/extensions/gsd/preferences-validation.ts +33 -0
  347. package/src/resources/extensions/gsd/preferences.ts +13 -2
  348. package/src/resources/extensions/gsd/prompt-loader.ts +8 -0
  349. package/src/resources/extensions/gsd/prompts/complete-milestone.md +2 -0
  350. package/src/resources/extensions/gsd/prompts/complete-slice.md +2 -0
  351. package/src/resources/extensions/gsd/prompts/doctor-heal.md +1 -0
  352. package/src/resources/extensions/gsd/prompts/forensics.md +2 -0
  353. package/src/resources/extensions/gsd/prompts/reassess-roadmap.md +2 -0
  354. package/src/resources/extensions/gsd/prompts/system.md +4 -7
  355. package/src/resources/extensions/gsd/prompts/validate-milestone.md +2 -0
  356. package/src/resources/extensions/gsd/roadmap-mutations.ts +1 -1
  357. package/src/resources/extensions/gsd/roadmap-slices.ts +10 -5
  358. package/src/resources/extensions/gsd/safety/content-validator.ts +98 -0
  359. package/src/resources/extensions/gsd/safety/destructive-guard.ts +49 -0
  360. package/src/resources/extensions/gsd/safety/evidence-collector.ts +151 -0
  361. package/src/resources/extensions/gsd/safety/evidence-cross-ref.ts +120 -0
  362. package/src/resources/extensions/gsd/safety/file-change-validator.ts +108 -0
  363. package/src/resources/extensions/gsd/safety/git-checkpoint.ts +106 -0
  364. package/src/resources/extensions/gsd/safety/safety-harness.ts +105 -0
  365. package/src/resources/extensions/gsd/slice-parallel-conflict.ts +86 -0
  366. package/src/resources/extensions/gsd/slice-parallel-eligibility.ts +73 -0
  367. package/src/resources/extensions/gsd/slice-parallel-orchestrator.ts +477 -0
  368. package/src/resources/extensions/gsd/state.ts +67 -12
  369. package/src/resources/extensions/gsd/status-guards.ts +13 -0
  370. package/src/resources/extensions/gsd/tests/artifact-corruption-2630.test.ts +288 -0
  371. package/src/resources/extensions/gsd/tests/auto-loop.test.ts +34 -13
  372. package/src/resources/extensions/gsd/tests/auto-start-time-persistence.test.ts +50 -0
  373. package/src/resources/extensions/gsd/tests/cmux.test.ts +58 -0
  374. package/src/resources/extensions/gsd/tests/cold-resume-db-reopen.test.ts +51 -0
  375. package/src/resources/extensions/gsd/tests/complete-milestone.test.ts +140 -0
  376. package/src/resources/extensions/gsd/tests/complete-slice-string-coercion.test.ts +211 -0
  377. package/src/resources/extensions/gsd/tests/complete-task.test.ts +39 -0
  378. package/src/resources/extensions/gsd/tests/dashboard-model-label-ordering.test.ts +107 -0
  379. package/src/resources/extensions/gsd/tests/db-access-guardrails.test.ts +109 -0
  380. package/src/resources/extensions/gsd/tests/db-path-worktree-symlink.test.ts +13 -9
  381. package/src/resources/extensions/gsd/tests/db-writer.test.ts +134 -0
  382. package/src/resources/extensions/gsd/tests/deferred-slice-dispatch.test.ts +203 -0
  383. package/src/resources/extensions/gsd/tests/discuss-tool-scoping.test.ts +130 -0
  384. package/src/resources/extensions/gsd/tests/doctor-fix-flag.test.ts +92 -0
  385. package/src/resources/extensions/gsd/tests/enhanced-verification-integration.test.ts +526 -0
  386. package/src/resources/extensions/gsd/tests/finalize-timeout-guard.test.ts +116 -0
  387. package/src/resources/extensions/gsd/tests/flat-rate-routing-guard.test.ts +50 -0
  388. package/src/resources/extensions/gsd/tests/forensics-stuck-loops.test.ts +103 -0
  389. package/src/resources/extensions/gsd/tests/git-checkpoint.test.ts +94 -0
  390. package/src/resources/extensions/gsd/tests/insert-slice-no-wipe.test.ts +88 -0
  391. package/src/resources/extensions/gsd/tests/integration/git-service.test.ts +27 -7
  392. package/src/resources/extensions/gsd/tests/integration/idle-recovery.test.ts +34 -0
  393. package/src/resources/extensions/gsd/tests/metrics.test.ts +116 -1
  394. package/src/resources/extensions/gsd/tests/milestone-status-tool.test.ts +201 -0
  395. package/src/resources/extensions/gsd/tests/plan-milestone-title.test.ts +2 -1
  396. package/src/resources/extensions/gsd/tests/plan-milestone.test.ts +82 -18
  397. package/src/resources/extensions/gsd/tests/post-exec-retry-bypass.test.ts +312 -0
  398. package/src/resources/extensions/gsd/tests/post-execution-checks.test.ts +813 -0
  399. package/src/resources/extensions/gsd/tests/pre-execution-checks.test.ts +999 -0
  400. package/src/resources/extensions/gsd/tests/pre-execution-fail-closed.test.ts +266 -0
  401. package/src/resources/extensions/gsd/tests/pre-execution-pause-wiring.test.ts +457 -0
  402. package/src/resources/extensions/gsd/tests/preferences.test.ts +10 -0
  403. package/src/resources/extensions/gsd/tests/prompt-contracts.test.ts +25 -0
  404. package/src/resources/extensions/gsd/tests/roadmap-slices.test.ts +69 -0
  405. package/src/resources/extensions/gsd/tests/shared-wal.test.ts +30 -0
  406. package/src/resources/extensions/gsd/tests/slice-context-injection.test.ts +50 -0
  407. package/src/resources/extensions/gsd/tests/slice-parallel-conflict.test.ts +92 -0
  408. package/src/resources/extensions/gsd/tests/slice-parallel-eligibility.test.ts +95 -0
  409. package/src/resources/extensions/gsd/tests/slice-parallel-orchestrator.test.ts +83 -0
  410. package/src/resources/extensions/gsd/tests/stuck-detection-coverage.test.ts +42 -0
  411. package/src/resources/extensions/gsd/tests/tool-invocation-error-loop-break.test.ts +103 -0
  412. package/src/resources/extensions/gsd/tests/tool-param-optionality.test.ts +349 -0
  413. package/src/resources/extensions/gsd/tests/worktree-health-dispatch.test.ts +35 -2
  414. package/src/resources/extensions/gsd/tests/worktree-health-monorepo.test.ts +73 -0
  415. package/src/resources/extensions/gsd/tests/worktree-resolver.test.ts +34 -0
  416. package/src/resources/extensions/gsd/tests/worktree-submodule-safety.test.ts +1 -1
  417. package/src/resources/extensions/gsd/tests/worktree-teardown-safety.test.ts +148 -0
  418. package/src/resources/extensions/gsd/tools/complete-milestone.ts +34 -20
  419. package/src/resources/extensions/gsd/tools/complete-slice.ts +41 -26
  420. package/src/resources/extensions/gsd/tools/complete-task.ts +12 -12
  421. package/src/resources/extensions/gsd/tools/plan-milestone.ts +55 -30
  422. package/src/resources/extensions/gsd/tools/plan-slice.ts +13 -8
  423. package/src/resources/extensions/gsd/types.ts +44 -22
  424. package/src/resources/extensions/gsd/verification-evidence.ts +68 -0
  425. package/src/resources/extensions/gsd/workflow-logger.ts +2 -1
  426. package/src/resources/extensions/gsd/workflow-projections.ts +23 -5
  427. package/src/resources/extensions/gsd/worktree-manager.ts +76 -28
  428. package/src/resources/extensions/gsd/worktree-resolver.ts +4 -3
  429. package/src/resources/extensions/mcp-client/auth.ts +149 -0
  430. package/src/resources/extensions/mcp-client/index.ts +16 -1
  431. package/src/resources/extensions/ollama/index.ts +26 -25
  432. package/src/resources/extensions/ollama/model-capabilities.ts +41 -34
  433. package/src/resources/extensions/ollama/ndjson-stream.ts +63 -0
  434. package/src/resources/extensions/ollama/ollama-auth-mode.test.ts +20 -0
  435. package/src/resources/extensions/ollama/ollama-chat-provider.ts +459 -0
  436. package/src/resources/extensions/ollama/ollama-client.ts +30 -30
  437. package/src/resources/extensions/ollama/ollama-discovery.ts +5 -8
  438. package/src/resources/extensions/ollama/ollama-tool.ts +69 -0
  439. package/src/resources/extensions/ollama/tests/ollama-chat-provider-stream.test.ts +82 -0
  440. package/src/resources/extensions/ollama/tests/ollama-discovery.test.ts +0 -27
  441. package/src/resources/extensions/ollama/thinking-parser.ts +116 -0
  442. package/src/resources/extensions/ollama/types.ts +23 -0
  443. package/dist/web/standalone/.next/server/chunks/2229.js +0 -12
  444. package/dist/web/standalone/.next/static/chunks/app/page-62be3b5fa91e4c8f.js +0 -1
  445. package/dist/web/standalone/.next/static/chunks/main-app-d3d4c336195465f9.js +0 -1
  446. package/dist/web/standalone/.next/static/chunks/next/dist/client/components/builtin/global-error-ab5a8926e07ec673.js +0 -1
  447. /package/dist/web/standalone/.next/static/{5FLUBNdqolRyyehCyChPd → SoxM61WC_ia7R2gk4VMpJ}/_buildManifest.js +0 -0
  448. /package/dist/web/standalone/.next/static/{5FLUBNdqolRyyehCyChPd → SoxM61WC_ia7R2gk4VMpJ}/_ssgManifest.js +0 -0
@@ -0,0 +1,63 @@
1
+ // GSD2 — Ollama Extension: NDJSON streaming parser
2
+
3
+ /**
4
+ * Parses a streaming NDJSON (newline-delimited JSON) response body into
5
+ * typed objects. Used for Ollama's /api/chat and /api/pull endpoints.
6
+ *
7
+ * @param strict When true, malformed JSON lines throw instead of being skipped.
8
+ * Use strict mode for inference streams where silent data loss is unacceptable.
9
+ * Use permissive mode (default) for progress endpoints like /api/pull.
10
+ */
11
+
12
+ export async function* parseNDJsonStream<T>(
13
+ body: ReadableStream<Uint8Array>,
14
+ signal?: AbortSignal,
15
+ strict = false,
16
+ ): AsyncGenerator<T> {
17
+ const reader = body.getReader();
18
+ const decoder = new TextDecoder();
19
+ let buffer = "";
20
+
21
+ try {
22
+ while (true) {
23
+ if (signal?.aborted) break;
24
+
25
+ const { done, value } = await reader.read();
26
+ if (done) break;
27
+
28
+ buffer += decoder.decode(value, { stream: true });
29
+ const lines = buffer.split("\n");
30
+ buffer = lines.pop() ?? "";
31
+
32
+ for (const line of lines) {
33
+ const trimmed = line.trim();
34
+ if (!trimmed) continue;
35
+ try {
36
+ yield JSON.parse(trimmed) as T;
37
+ } catch (err) {
38
+ if (strict) {
39
+ throw new Error(
40
+ `Malformed NDJSON line from Ollama: ${trimmed.slice(0, 200)}`,
41
+ );
42
+ }
43
+ // Permissive mode: skip malformed lines
44
+ }
45
+ }
46
+ }
47
+
48
+ // Flush remaining buffer (skip if aborted)
49
+ if (buffer.trim() && !signal?.aborted) {
50
+ try {
51
+ yield JSON.parse(buffer.trim()) as T;
52
+ } catch (err) {
53
+ if (strict) {
54
+ throw new Error(
55
+ `Malformed NDJSON line from Ollama: ${buffer.trim().slice(0, 200)}`,
56
+ );
57
+ }
58
+ }
59
+ }
60
+ } finally {
61
+ reader.releaseLock();
62
+ }
63
+ }
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Regression test for #3440: Ollama extension must register with
3
+ * authMode "apiKey" (not "none") to avoid streamSimple requirement.
4
+ */
5
+ import { test } from "node:test";
6
+ import assert from "node:assert/strict";
7
+ import { readFileSync } from "node:fs";
8
+ import { join, dirname } from "node:path";
9
+ import { fileURLToPath } from "node:url";
10
+
11
+ const __dirname = dirname(fileURLToPath(import.meta.url));
12
+
13
+ test("Ollama registers with authMode apiKey, not none (#3440)", () => {
14
+ const src = readFileSync(join(__dirname, "index.ts"), "utf-8");
15
+ // Find the registerProvider call
16
+ const registerBlock = src.slice(src.indexOf("pi.registerProvider(\"ollama\""));
17
+ const authLine = registerBlock.match(/authMode:\s*"(\w+)"/);
18
+ assert.ok(authLine, "registerProvider must specify authMode");
19
+ assert.equal(authLine![1], "apiKey", "authMode must be apiKey, not none");
20
+ });
@@ -0,0 +1,459 @@
1
+ // GSD2 — Ollama Extension: Native /api/chat stream provider
2
+
3
+ /**
4
+ * Implements the "ollama-chat" API provider, streaming responses directly
5
+ * from Ollama's native /api/chat endpoint instead of the OpenAI compatibility
6
+ * shim. This exposes Ollama-specific options (num_ctx, keep_alive, num_gpu,
7
+ * sampling parameters) and surfaces inference performance metrics.
8
+ */
9
+
10
+ import {
11
+ type Api,
12
+ type AssistantMessage,
13
+ type AssistantMessageEvent,
14
+ type AssistantMessageEventStream,
15
+ type Context,
16
+ type ImageContent,
17
+ type InferenceMetrics,
18
+ type Message,
19
+ type Model,
20
+ type SimpleStreamOptions,
21
+ type StopReason,
22
+ type TextContent,
23
+ type ThinkingContent,
24
+ type Tool,
25
+ type ToolCall,
26
+ type Usage,
27
+ EventStream,
28
+ } from "@gsd/pi-ai";
29
+ import { chat } from "./ollama-client.js";
30
+ import type {
31
+ OllamaChatMessage,
32
+ OllamaChatOptions,
33
+ OllamaChatRequest,
34
+ OllamaChatResponse,
35
+ OllamaTool,
36
+ OllamaToolCall,
37
+ } from "./types.js";
38
+ import { ThinkingTagParser, type ParsedChunk } from "./thinking-parser.js";
39
+
40
+ /** Create an AssistantMessageEventStream using the base EventStream class. */
41
+ function createStream(): AssistantMessageEventStream {
42
+ return new EventStream<AssistantMessageEvent, AssistantMessage>(
43
+ (event) => event.type === "done" || event.type === "error",
44
+ (event) => {
45
+ if (event.type === "done") return event.message;
46
+ if (event.type === "error") return event.error;
47
+ throw new Error("Unexpected event type for final result");
48
+ },
49
+ ) as AssistantMessageEventStream;
50
+ }
51
+
52
+ // ─── Stream handler ─────────────────────────────────────────────────────────
53
+
54
+ export function streamOllamaChat(
55
+ model: Model<Api>,
56
+ context: Context,
57
+ options?: SimpleStreamOptions,
58
+ ): AssistantMessageEventStream {
59
+ const stream = createStream();
60
+
61
+ (async () => {
62
+ const output = buildInitialOutput(model);
63
+
64
+ try {
65
+ const request = buildRequest(model, context, options);
66
+ stream.push({ type: "start", partial: output });
67
+
68
+ const useThinkingParser = model.reasoning;
69
+ const thinkParser = useThinkingParser ? new ThinkingTagParser() : null;
70
+
71
+ let contentIndex = -1;
72
+ let currentBlockType: "text" | "thinking" | null = null;
73
+
74
+ function startBlock(type: "text" | "thinking") {
75
+ contentIndex++;
76
+ currentBlockType = type;
77
+ if (type === "text") {
78
+ output.content.push({ type: "text", text: "" });
79
+ stream.push({ type: "text_start", contentIndex, partial: output });
80
+ } else {
81
+ output.content.push({ type: "thinking", thinking: "" });
82
+ stream.push({ type: "thinking_start", contentIndex, partial: output });
83
+ }
84
+ }
85
+
86
+ function endBlock() {
87
+ if (currentBlockType === null) return;
88
+ if (currentBlockType === "text") {
89
+ const block = output.content[contentIndex] as TextContent;
90
+ stream.push({ type: "text_end", contentIndex, content: block.text, partial: output });
91
+ } else {
92
+ const block = output.content[contentIndex] as ThinkingContent;
93
+ stream.push({ type: "thinking_end", contentIndex, content: block.thinking, partial: output });
94
+ }
95
+ currentBlockType = null;
96
+ }
97
+
98
+ function emitDelta(type: "text" | "thinking", text: string) {
99
+ if (!text) return;
100
+ if (currentBlockType !== type) {
101
+ endBlock();
102
+ startBlock(type);
103
+ }
104
+ if (type === "text") {
105
+ (output.content[contentIndex] as TextContent).text += text;
106
+ stream.push({ type: "text_delta", contentIndex, delta: text, partial: output });
107
+ } else {
108
+ (output.content[contentIndex] as ThinkingContent).thinking += text;
109
+ stream.push({ type: "thinking_delta", contentIndex, delta: text, partial: output });
110
+ }
111
+ }
112
+
113
+ function processChunks(chunks: ParsedChunk[]) {
114
+ for (const chunk of chunks) {
115
+ emitDelta(chunk.type, chunk.text);
116
+ }
117
+ }
118
+
119
+ function processToolCalls(toolCalls: OllamaToolCall[]) {
120
+ endBlock();
121
+ for (const tc of toolCalls) {
122
+ contentIndex++;
123
+ const toolCall: ToolCall = {
124
+ type: "toolCall",
125
+ id: `ollama_tc_${contentIndex}`,
126
+ name: tc.function.name,
127
+ arguments: tc.function.arguments,
128
+ };
129
+ output.content.push(toolCall);
130
+ stream.push({ type: "toolcall_start", contentIndex, partial: output });
131
+ // Emit a delta with the serialized arguments (convention: start/delta/end)
132
+ stream.push({
133
+ type: "toolcall_delta",
134
+ contentIndex,
135
+ delta: JSON.stringify(tc.function.arguments),
136
+ partial: output,
137
+ });
138
+ stream.push({
139
+ type: "toolcall_end",
140
+ contentIndex,
141
+ toolCall,
142
+ partial: output,
143
+ });
144
+ }
145
+ output.stopReason = "toolUse";
146
+ }
147
+
148
+ for await (const chunk of chat(request, options?.signal)) {
149
+ // Handle text content — process independently of tool_calls
150
+ // (a chunk may contain both content and tool_calls)
151
+ const content = chunk.message?.content ?? "";
152
+ if (content) {
153
+ if (thinkParser) {
154
+ processChunks(thinkParser.push(content));
155
+ } else {
156
+ emitDelta("text", content);
157
+ }
158
+ }
159
+
160
+ // Handle tool calls (Ollama sends them complete, may be on done:true chunk)
161
+ if (chunk.message?.tool_calls?.length) {
162
+ processToolCalls(chunk.message.tool_calls);
163
+ }
164
+
165
+ if (chunk.done) {
166
+ // Final chunk — extract metrics and usage
167
+ if (thinkParser) processChunks(thinkParser.flush());
168
+ endBlock();
169
+
170
+ output.usage = buildUsage(chunk);
171
+ output.inferenceMetrics = extractMetrics(chunk);
172
+ // Preserve "toolUse" if tool calls were processed
173
+ if (output.stopReason !== "toolUse") {
174
+ output.stopReason = mapStopReason(chunk.done_reason);
175
+ }
176
+ break;
177
+ }
178
+ }
179
+
180
+ assertStreamSuccess(output, options?.signal);
181
+ finalizeStream(stream, output);
182
+ } catch (error) {
183
+ handleStreamError(stream, output, error, options?.signal);
184
+ }
185
+ })();
186
+
187
+ return stream;
188
+ }
189
+
190
+ // ─── Request building ───────────────────────────────────────────────────────
191
+
192
+ function buildRequest(
193
+ model: Model<Api>,
194
+ context: Context,
195
+ options?: SimpleStreamOptions,
196
+ ): OllamaChatRequest {
197
+ const ollamaOpts = (model.providerOptions ?? {}) as OllamaChatOptions;
198
+
199
+ const request: OllamaChatRequest = {
200
+ model: model.id,
201
+ messages: convertMessages(context),
202
+ stream: true,
203
+ };
204
+
205
+ // Build options block with all Ollama-specific parameters
206
+ const reqOptions: NonNullable<OllamaChatRequest["options"]> = {};
207
+
208
+ // Context window — only sent when explicitly configured via providerOptions.
209
+ // Sending inferred/estimated values risks OOM on constrained hosts.
210
+ // Users can set num_ctx per-model in models.json ollamaOptions or the
211
+ // capability table can provide it for known model families.
212
+ if (ollamaOpts.num_ctx !== undefined && ollamaOpts.num_ctx > 0) {
213
+ reqOptions.num_ctx = ollamaOpts.num_ctx;
214
+ }
215
+
216
+ // Max output tokens
217
+ const maxTokens = options?.maxTokens ?? model.maxTokens;
218
+ if (maxTokens > 0) {
219
+ reqOptions.num_predict = maxTokens;
220
+ }
221
+
222
+ // Temperature
223
+ if (options?.temperature !== undefined) {
224
+ reqOptions.temperature = options.temperature;
225
+ }
226
+
227
+ // Per-model sampling options from providerOptions
228
+ if (ollamaOpts.top_p !== undefined) reqOptions.top_p = ollamaOpts.top_p;
229
+ if (ollamaOpts.top_k !== undefined) reqOptions.top_k = ollamaOpts.top_k;
230
+ if (ollamaOpts.repeat_penalty !== undefined) reqOptions.repeat_penalty = ollamaOpts.repeat_penalty;
231
+ if (ollamaOpts.seed !== undefined) reqOptions.seed = ollamaOpts.seed;
232
+ if (ollamaOpts.num_gpu !== undefined) reqOptions.num_gpu = ollamaOpts.num_gpu;
233
+
234
+ if (Object.keys(reqOptions).length > 0) {
235
+ request.options = reqOptions;
236
+ }
237
+
238
+ // Keep alive
239
+ if (ollamaOpts.keep_alive !== undefined) {
240
+ request.keep_alive = ollamaOpts.keep_alive;
241
+ }
242
+
243
+ // Tools
244
+ if (context.tools?.length) {
245
+ request.tools = convertTools(context.tools);
246
+ }
247
+
248
+ return request;
249
+ }
250
+
251
+ // ─── Message conversion ─────────────────────────────────────────────────────
252
+
253
+ function convertMessages(context: Context): OllamaChatMessage[] {
254
+ const messages: OllamaChatMessage[] = [];
255
+
256
+ // System prompt
257
+ if (context.systemPrompt) {
258
+ messages.push({ role: "system", content: context.systemPrompt });
259
+ }
260
+
261
+ for (const msg of context.messages) {
262
+ switch (msg.role) {
263
+ case "user":
264
+ messages.push(convertUserMessage(msg));
265
+ break;
266
+ case "assistant":
267
+ messages.push(convertAssistantMessage(msg));
268
+ break;
269
+ case "toolResult":
270
+ messages.push({
271
+ role: "tool",
272
+ content: msg.content
273
+ .filter((c): c is TextContent => c.type === "text")
274
+ .map((c) => c.text)
275
+ .join("\n"),
276
+ name: msg.toolName,
277
+ });
278
+ break;
279
+ }
280
+ }
281
+
282
+ return messages;
283
+ }
284
+
285
+ function convertUserMessage(msg: Message & { role: "user" }): OllamaChatMessage {
286
+ if (typeof msg.content === "string") {
287
+ return { role: "user", content: msg.content };
288
+ }
289
+
290
+ const textParts: string[] = [];
291
+ const images: string[] = [];
292
+
293
+ for (const part of msg.content) {
294
+ if (part.type === "text") {
295
+ textParts.push(part.text);
296
+ } else if (part.type === "image") {
297
+ // Strip data URI prefix if present
298
+ let data = (part as ImageContent).data;
299
+ const commaIdx = data.indexOf(",");
300
+ if (commaIdx !== -1 && data.startsWith("data:")) {
301
+ data = data.slice(commaIdx + 1);
302
+ }
303
+ images.push(data);
304
+ }
305
+ }
306
+
307
+ const result: OllamaChatMessage = {
308
+ role: "user",
309
+ content: textParts.join("\n"),
310
+ };
311
+ if (images.length > 0) {
312
+ result.images = images;
313
+ }
314
+ return result;
315
+ }
316
+
317
+ function convertAssistantMessage(msg: Message & { role: "assistant" }): OllamaChatMessage {
318
+ let content = "";
319
+ const toolCalls: OllamaChatMessage["tool_calls"] = [];
320
+
321
+ for (const block of msg.content) {
322
+ if (block.type === "thinking") {
323
+ // Serialize thinking back inline for round-trip with Ollama
324
+ content += `<think>${(block as ThinkingContent).thinking}</think>`;
325
+ } else if (block.type === "text") {
326
+ content += (block as TextContent).text;
327
+ } else if (block.type === "toolCall") {
328
+ const tc = block as ToolCall;
329
+ toolCalls.push({
330
+ function: {
331
+ name: tc.name,
332
+ arguments: tc.arguments,
333
+ },
334
+ });
335
+ }
336
+ }
337
+
338
+ const result: OllamaChatMessage = { role: "assistant", content };
339
+ if (toolCalls.length > 0) {
340
+ result.tool_calls = toolCalls;
341
+ }
342
+ return result;
343
+ }
344
+
345
+ // ─── Tool conversion ────────────────────────────────────────────────────────
346
+
347
+ function convertTools(tools: Tool[]): OllamaTool[] {
348
+ return tools.map((tool) => {
349
+ const params = tool.parameters as Record<string, unknown>;
350
+ return {
351
+ type: "function" as const,
352
+ function: {
353
+ name: tool.name,
354
+ description: tool.description,
355
+ parameters: {
356
+ type: "object" as const,
357
+ required: params.required as string[] | undefined,
358
+ properties: (params.properties as Record<string, unknown>) ?? {},
359
+ },
360
+ },
361
+ };
362
+ });
363
+ }
364
+
365
+ // ─── Response mapping ───────────────────────────────────────────────────────
366
+
367
+ function mapStopReason(doneReason?: string): StopReason {
368
+ switch (doneReason) {
369
+ case "stop":
370
+ return "stop";
371
+ case "length":
372
+ return "length";
373
+ default:
374
+ return "stop";
375
+ }
376
+ }
377
+
378
+ function buildUsage(chunk: OllamaChatResponse): Usage {
379
+ const input = chunk.prompt_eval_count ?? 0;
380
+ const outputTokens = chunk.eval_count ?? 0;
381
+ return {
382
+ input,
383
+ output: outputTokens,
384
+ cacheRead: 0,
385
+ cacheWrite: 0,
386
+ totalTokens: input + outputTokens,
387
+ cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
388
+ };
389
+ }
390
+
391
+ function extractMetrics(chunk: OllamaChatResponse): InferenceMetrics | undefined {
392
+ if (!chunk.eval_duration && !chunk.total_duration) return undefined;
393
+
394
+ const evalCount = chunk.eval_count ?? 0;
395
+ const evalDurationNs = chunk.eval_duration ?? 0;
396
+ const evalDurationMs = evalDurationNs / 1e6;
397
+ const tokensPerSecond = evalDurationNs > 0 ? evalCount / (evalDurationNs / 1e9) : 0;
398
+
399
+ return {
400
+ tokensPerSecond,
401
+ totalDurationMs: (chunk.total_duration ?? 0) / 1e6,
402
+ evalDurationMs,
403
+ promptEvalDurationMs: (chunk.prompt_eval_duration ?? 0) / 1e6,
404
+ };
405
+ }
406
+
407
+ // ─── Stream lifecycle helpers ───────────────────────────────────────────────
408
+ // Replicated from openai-shared.ts (not exported from @gsd/pi-ai)
409
+
410
+ function buildInitialOutput(model: Model<Api>): AssistantMessage {
411
+ return {
412
+ role: "assistant",
413
+ content: [],
414
+ api: model.api as Api,
415
+ provider: model.provider,
416
+ model: model.id,
417
+ usage: {
418
+ input: 0,
419
+ output: 0,
420
+ cacheRead: 0,
421
+ cacheWrite: 0,
422
+ totalTokens: 0,
423
+ cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
424
+ },
425
+ stopReason: "stop",
426
+ timestamp: Date.now(),
427
+ };
428
+ }
429
+
430
+ function assertStreamSuccess(output: AssistantMessage, signal?: AbortSignal): void {
431
+ if (signal?.aborted) {
432
+ throw new Error("Request was aborted");
433
+ }
434
+ if (output.stopReason === "aborted" || output.stopReason === "error") {
435
+ throw new Error("An unknown error occurred");
436
+ }
437
+ }
438
+
439
+ function finalizeStream(stream: AssistantMessageEventStream, output: AssistantMessage): void {
440
+ stream.push({
441
+ type: "done",
442
+ reason: output.stopReason as Extract<StopReason, "stop" | "length" | "toolUse" | "pauseTurn">,
443
+ message: output,
444
+ });
445
+ stream.end();
446
+ }
447
+
448
+ function handleStreamError(
449
+ stream: AssistantMessageEventStream,
450
+ output: AssistantMessage,
451
+ error: unknown,
452
+ signal?: AbortSignal,
453
+ ): void {
454
+ for (const block of output.content) delete (block as { index?: number }).index;
455
+ output.stopReason = signal?.aborted ? "aborted" : "error";
456
+ output.errorMessage = error instanceof Error ? error.message : JSON.stringify(error);
457
+ stream.push({ type: "error", reason: output.stopReason, error: output });
458
+ stream.end();
459
+ }
@@ -8,12 +8,15 @@
8
8
  */
9
9
 
10
10
  import type {
11
+ OllamaChatRequest,
12
+ OllamaChatResponse,
11
13
  OllamaPsResponse,
12
14
  OllamaPullProgress,
13
15
  OllamaShowResponse,
14
16
  OllamaTagsResponse,
15
17
  OllamaVersionResponse,
16
18
  } from "./types.js";
19
+ import { parseNDJsonStream } from "./ndjson-stream.js";
17
20
 
18
21
  const DEFAULT_HOST = "http://localhost:11434";
19
22
  const PROBE_TIMEOUT_MS = 1500;
@@ -130,39 +133,36 @@ export async function pullModel(
130
133
  throw new Error("Ollama /api/pull returned no body");
131
134
  }
132
135
 
133
- const reader = response.body.getReader();
134
- const decoder = new TextDecoder();
135
- let buffer = "";
136
-
137
- while (true) {
138
- const { done, value } = await reader.read();
139
- if (done) break;
140
-
141
- buffer += decoder.decode(value, { stream: true });
142
- const lines = buffer.split("\n");
143
- buffer = lines.pop() ?? "";
144
-
145
- for (const line of lines) {
146
- const trimmed = line.trim();
147
- if (!trimmed) continue;
148
- try {
149
- const progress = JSON.parse(trimmed) as OllamaPullProgress;
150
- onProgress?.(progress);
151
- } catch {
152
- // Skip malformed lines
153
- }
154
- }
136
+ for await (const progress of parseNDJsonStream<OllamaPullProgress>(response.body, signal)) {
137
+ onProgress?.(progress);
155
138
  }
139
+ }
156
140
 
157
- // Process remaining buffer
158
- if (buffer.trim()) {
159
- try {
160
- const progress = JSON.parse(buffer.trim()) as OllamaPullProgress;
161
- onProgress?.(progress);
162
- } catch {
163
- // Ignore
164
- }
141
+ /**
142
+ * Stream a chat completion via /api/chat.
143
+ * Returns an async generator yielding each NDJSON response chunk.
144
+ */
145
+ export async function* chat(
146
+ request: OllamaChatRequest,
147
+ signal?: AbortSignal,
148
+ ): AsyncGenerator<OllamaChatResponse> {
149
+ const response = await fetch(`${getOllamaHost()}/api/chat`, {
150
+ method: "POST",
151
+ headers: { "Content-Type": "application/json" },
152
+ body: JSON.stringify(request),
153
+ signal,
154
+ });
155
+
156
+ if (!response.ok) {
157
+ const text = await response.text();
158
+ throw new Error(`Ollama /api/chat returned ${response.status}: ${text}`);
165
159
  }
160
+
161
+ if (!response.body) {
162
+ throw new Error("Ollama /api/chat returned no body");
163
+ }
164
+
165
+ yield* parseNDJsonStream<OllamaChatResponse>(response.body, signal, true);
166
166
  }
167
167
 
168
168
  /**
@@ -8,14 +8,14 @@
8
8
  * Returns models in the format expected by pi.registerProvider().
9
9
  */
10
10
 
11
- import { listModels, getOllamaHost } from "./ollama-client.js";
11
+ import { listModels } from "./ollama-client.js";
12
12
  import {
13
13
  estimateContextFromParams,
14
14
  formatModelSize,
15
15
  getModelCapabilities,
16
16
  humanizeModelName,
17
17
  } from "./model-capabilities.js";
18
- import type { OllamaModelInfo } from "./types.js";
18
+ import type { OllamaChatOptions, OllamaModelInfo } from "./types.js";
19
19
 
20
20
  export interface DiscoveredOllamaModel {
21
21
  id: string;
@@ -29,6 +29,8 @@ export interface DiscoveredOllamaModel {
29
29
  sizeBytes: number;
30
30
  /** Parameter size string from Ollama (e.g. "7B") */
31
31
  parameterSize: string;
32
+ /** Ollama-specific inference options for this model */
33
+ ollamaOptions?: OllamaChatOptions;
32
34
  }
33
35
 
34
36
  const ZERO_COST = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 };
@@ -64,6 +66,7 @@ function enrichModel(info: OllamaModelInfo): DiscoveredOllamaModel {
64
66
  maxTokens,
65
67
  sizeBytes: info.size,
66
68
  parameterSize,
69
+ ollamaOptions: caps.ollamaOptions,
67
70
  };
68
71
  }
69
72
 
@@ -98,9 +101,3 @@ export function formatModelForDisplay(model: DiscoveredOllamaModel): string {
98
101
  return parts.join(" ");
99
102
  }
100
103
 
101
- /**
102
- * Build the OpenAI-compat base URL for Ollama.
103
- */
104
- export function getOllamaOpenAIBaseUrl(): string {
105
- return `${getOllamaHost()}/v1`;
106
- }