gsd-pi 2.63.0 → 2.64.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (353) 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 +9 -3
  14. package/dist/resources/extensions/gsd/auto-model-selection.js +32 -0
  15. package/dist/resources/extensions/gsd/auto-post-unit.js +124 -10
  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-worktree.js +13 -7
  22. package/dist/resources/extensions/gsd/auto.js +19 -2
  23. package/dist/resources/extensions/gsd/bootstrap/db-tools.js +147 -75
  24. package/dist/resources/extensions/gsd/bootstrap/dynamic-tools.js +13 -0
  25. package/dist/resources/extensions/gsd/bootstrap/query-tools.js +85 -0
  26. package/dist/resources/extensions/gsd/bootstrap/register-extension.js +3 -0
  27. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +32 -1
  28. package/dist/resources/extensions/gsd/bootstrap/sanitize-complete-milestone.js +54 -0
  29. package/dist/resources/extensions/gsd/bootstrap/system-context.js +30 -2
  30. package/dist/resources/extensions/gsd/commands-handlers.js +9 -4
  31. package/dist/resources/extensions/gsd/constants.js +42 -0
  32. package/dist/resources/extensions/gsd/db-writer.js +72 -4
  33. package/dist/resources/extensions/gsd/forensics.js +20 -4
  34. package/dist/resources/extensions/gsd/gsd-db.js +64 -17
  35. package/dist/resources/extensions/gsd/guided-flow.js +19 -0
  36. package/dist/resources/extensions/gsd/metrics.js +27 -1
  37. package/dist/resources/extensions/gsd/native-git-bridge.js +5 -3
  38. package/dist/resources/extensions/gsd/preferences-types.js +2 -0
  39. package/dist/resources/extensions/gsd/preferences.js +7 -2
  40. package/dist/resources/extensions/gsd/prompt-loader.js +7 -0
  41. package/dist/resources/extensions/gsd/prompts/complete-milestone.md +2 -0
  42. package/dist/resources/extensions/gsd/prompts/complete-slice.md +2 -0
  43. package/dist/resources/extensions/gsd/prompts/doctor-heal.md +1 -0
  44. package/dist/resources/extensions/gsd/prompts/forensics.md +2 -0
  45. package/dist/resources/extensions/gsd/prompts/reassess-roadmap.md +2 -0
  46. package/dist/resources/extensions/gsd/prompts/system.md +4 -7
  47. package/dist/resources/extensions/gsd/prompts/validate-milestone.md +2 -0
  48. package/dist/resources/extensions/gsd/roadmap-mutations.js +1 -1
  49. package/dist/resources/extensions/gsd/roadmap-slices.js +9 -5
  50. package/dist/resources/extensions/gsd/safety/content-validator.js +73 -0
  51. package/dist/resources/extensions/gsd/safety/destructive-guard.js +34 -0
  52. package/dist/resources/extensions/gsd/safety/evidence-collector.js +109 -0
  53. package/dist/resources/extensions/gsd/safety/evidence-cross-ref.js +83 -0
  54. package/dist/resources/extensions/gsd/safety/file-change-validator.js +71 -0
  55. package/dist/resources/extensions/gsd/safety/git-checkpoint.js +91 -0
  56. package/dist/resources/extensions/gsd/safety/safety-harness.js +64 -0
  57. package/dist/resources/extensions/gsd/slice-parallel-conflict.js +67 -0
  58. package/dist/resources/extensions/gsd/slice-parallel-eligibility.js +51 -0
  59. package/dist/resources/extensions/gsd/slice-parallel-orchestrator.js +378 -0
  60. package/dist/resources/extensions/gsd/state.js +74 -14
  61. package/dist/resources/extensions/gsd/status-guards.js +11 -0
  62. package/dist/resources/extensions/gsd/tools/complete-milestone.js +17 -12
  63. package/dist/resources/extensions/gsd/tools/complete-slice.js +40 -26
  64. package/dist/resources/extensions/gsd/tools/complete-task.js +12 -12
  65. package/dist/resources/extensions/gsd/tools/plan-milestone.js +33 -25
  66. package/dist/resources/extensions/gsd/tools/plan-slice.js +5 -8
  67. package/dist/resources/extensions/gsd/workflow-projections.js +21 -5
  68. package/dist/resources/extensions/gsd/worktree-manager.js +82 -29
  69. package/dist/resources/extensions/gsd/worktree-resolver.js +4 -3
  70. package/dist/resources/extensions/mcp-client/auth.js +101 -0
  71. package/dist/resources/extensions/mcp-client/index.js +10 -1
  72. package/dist/resources/extensions/ollama/index.js +28 -22
  73. package/dist/resources/extensions/ollama/model-capabilities.js +37 -34
  74. package/dist/resources/extensions/ollama/ndjson-stream.js +54 -0
  75. package/dist/resources/extensions/ollama/ollama-chat-provider.js +380 -0
  76. package/dist/resources/extensions/ollama/ollama-client.js +23 -32
  77. package/dist/resources/extensions/ollama/ollama-discovery.js +2 -7
  78. package/dist/resources/extensions/ollama/ollama-tool.js +62 -0
  79. package/dist/resources/extensions/ollama/thinking-parser.js +104 -0
  80. package/dist/update-cmd.js +4 -2
  81. package/dist/web/standalone/.next/BUILD_ID +1 -1
  82. package/dist/web/standalone/.next/app-path-routes-manifest.json +12 -12
  83. package/dist/web/standalone/.next/build-manifest.json +2 -2
  84. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  85. package/dist/web/standalone/.next/server/app/_global-error.html +2 -2
  86. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  87. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  88. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  89. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  90. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  91. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  92. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  93. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  94. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  95. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  96. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  97. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  98. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  99. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  100. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  101. package/dist/web/standalone/.next/server/app/api/boot/route.js +1 -1
  102. package/dist/web/standalone/.next/server/app/api/boot/route.js.nft.json +1 -1
  103. package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route.js +1 -1
  104. package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route.js.nft.json +1 -1
  105. package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route.js +1 -1
  106. package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route.js.nft.json +1 -1
  107. package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route.js +2 -2
  108. package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route.js.nft.json +1 -1
  109. package/dist/web/standalone/.next/server/app/api/captures/route.js +1 -1
  110. package/dist/web/standalone/.next/server/app/api/captures/route.js.nft.json +1 -1
  111. package/dist/web/standalone/.next/server/app/api/cleanup/route.js +1 -1
  112. package/dist/web/standalone/.next/server/app/api/cleanup/route.js.nft.json +1 -1
  113. package/dist/web/standalone/.next/server/app/api/doctor/route.js +1 -1
  114. package/dist/web/standalone/.next/server/app/api/doctor/route.js.nft.json +1 -1
  115. package/dist/web/standalone/.next/server/app/api/export-data/route.js +1 -1
  116. package/dist/web/standalone/.next/server/app/api/export-data/route.js.nft.json +1 -1
  117. package/dist/web/standalone/.next/server/app/api/files/route.js +1 -1
  118. package/dist/web/standalone/.next/server/app/api/files/route.js.nft.json +1 -1
  119. package/dist/web/standalone/.next/server/app/api/forensics/route.js +1 -1
  120. package/dist/web/standalone/.next/server/app/api/forensics/route.js.nft.json +1 -1
  121. package/dist/web/standalone/.next/server/app/api/git/route.js +1 -1
  122. package/dist/web/standalone/.next/server/app/api/git/route.js.nft.json +1 -1
  123. package/dist/web/standalone/.next/server/app/api/history/route.js +1 -1
  124. package/dist/web/standalone/.next/server/app/api/history/route.js.nft.json +1 -1
  125. package/dist/web/standalone/.next/server/app/api/hooks/route.js +1 -1
  126. package/dist/web/standalone/.next/server/app/api/hooks/route.js.nft.json +1 -1
  127. package/dist/web/standalone/.next/server/app/api/inspect/route.js +1 -1
  128. package/dist/web/standalone/.next/server/app/api/inspect/route.js.nft.json +1 -1
  129. package/dist/web/standalone/.next/server/app/api/knowledge/route.js +1 -1
  130. package/dist/web/standalone/.next/server/app/api/knowledge/route.js.nft.json +1 -1
  131. package/dist/web/standalone/.next/server/app/api/live-state/route.js +1 -1
  132. package/dist/web/standalone/.next/server/app/api/live-state/route.js.nft.json +1 -1
  133. package/dist/web/standalone/.next/server/app/api/onboarding/route.js +1 -1
  134. package/dist/web/standalone/.next/server/app/api/onboarding/route.js.nft.json +1 -1
  135. package/dist/web/standalone/.next/server/app/api/projects/route.js +1 -1
  136. package/dist/web/standalone/.next/server/app/api/projects/route.js.nft.json +1 -1
  137. package/dist/web/standalone/.next/server/app/api/recovery/route.js +1 -1
  138. package/dist/web/standalone/.next/server/app/api/recovery/route.js.nft.json +1 -1
  139. package/dist/web/standalone/.next/server/app/api/session/browser/route.js +1 -1
  140. package/dist/web/standalone/.next/server/app/api/session/browser/route.js.nft.json +1 -1
  141. package/dist/web/standalone/.next/server/app/api/session/command/route.js +1 -1
  142. package/dist/web/standalone/.next/server/app/api/session/command/route.js.nft.json +1 -1
  143. package/dist/web/standalone/.next/server/app/api/session/events/route.js +2 -2
  144. package/dist/web/standalone/.next/server/app/api/session/events/route.js.nft.json +1 -1
  145. package/dist/web/standalone/.next/server/app/api/session/manage/route.js +1 -1
  146. package/dist/web/standalone/.next/server/app/api/session/manage/route.js.nft.json +1 -1
  147. package/dist/web/standalone/.next/server/app/api/settings-data/route.js +1 -1
  148. package/dist/web/standalone/.next/server/app/api/settings-data/route.js.nft.json +1 -1
  149. package/dist/web/standalone/.next/server/app/api/skill-health/route.js +1 -1
  150. package/dist/web/standalone/.next/server/app/api/skill-health/route.js.nft.json +1 -1
  151. package/dist/web/standalone/.next/server/app/api/steer/route.js +1 -1
  152. package/dist/web/standalone/.next/server/app/api/steer/route.js.nft.json +1 -1
  153. package/dist/web/standalone/.next/server/app/api/switch-root/route.js +1 -1
  154. package/dist/web/standalone/.next/server/app/api/switch-root/route.js.nft.json +1 -1
  155. package/dist/web/standalone/.next/server/app/api/terminal/sessions/route.js +1 -1
  156. package/dist/web/standalone/.next/server/app/api/terminal/sessions/route.js.nft.json +1 -1
  157. package/dist/web/standalone/.next/server/app/api/terminal/stream/route.js +2 -2
  158. package/dist/web/standalone/.next/server/app/api/terminal/stream/route.js.nft.json +1 -1
  159. package/dist/web/standalone/.next/server/app/api/undo/route.js +1 -1
  160. package/dist/web/standalone/.next/server/app/api/undo/route.js.nft.json +1 -1
  161. package/dist/web/standalone/.next/server/app/api/visualizer/route.js +1 -1
  162. package/dist/web/standalone/.next/server/app/api/visualizer/route.js.nft.json +1 -1
  163. package/dist/web/standalone/.next/server/app/index.html +1 -1
  164. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  165. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  166. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  167. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  168. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  169. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  170. package/dist/web/standalone/.next/server/app-paths-manifest.json +12 -12
  171. package/dist/web/standalone/.next/server/chunks/6897.js +12 -0
  172. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  173. package/dist/web/standalone/.next/server/pages/500.html +2 -2
  174. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  175. package/dist/welcome-screen.js +1 -1
  176. package/package.json +1 -1
  177. package/packages/pi-agent-core/dist/agent-loop.d.ts +8 -0
  178. package/packages/pi-agent-core/dist/agent-loop.d.ts.map +1 -1
  179. package/packages/pi-agent-core/dist/agent-loop.js +50 -0
  180. package/packages/pi-agent-core/dist/agent-loop.js.map +1 -1
  181. package/packages/pi-agent-core/src/agent-loop.test.ts +221 -5
  182. package/packages/pi-agent-core/src/agent-loop.ts +53 -0
  183. package/packages/pi-ai/dist/types.d.ts +16 -1
  184. package/packages/pi-ai/dist/types.d.ts.map +1 -1
  185. package/packages/pi-ai/dist/types.js.map +1 -1
  186. package/packages/pi-ai/src/types.ts +18 -1
  187. package/packages/pi-coding-agent/dist/core/auth-storage.d.ts +9 -0
  188. package/packages/pi-coding-agent/dist/core/auth-storage.d.ts.map +1 -1
  189. package/packages/pi-coding-agent/dist/core/auth-storage.js +50 -1
  190. package/packages/pi-coding-agent/dist/core/auth-storage.js.map +1 -1
  191. package/packages/pi-coding-agent/dist/core/auth-storage.test.js +41 -0
  192. package/packages/pi-coding-agent/dist/core/auth-storage.test.js.map +1 -1
  193. package/packages/pi-coding-agent/dist/core/extensions/loader.d.ts +7 -0
  194. package/packages/pi-coding-agent/dist/core/extensions/loader.d.ts.map +1 -1
  195. package/packages/pi-coding-agent/dist/core/extensions/loader.js +31 -4
  196. package/packages/pi-coding-agent/dist/core/extensions/loader.js.map +1 -1
  197. package/packages/pi-coding-agent/dist/core/extensions/loader.test.js +28 -1
  198. package/packages/pi-coding-agent/dist/core/extensions/loader.test.js.map +1 -1
  199. package/packages/pi-coding-agent/dist/core/extensions/provider-registration.test.d.ts +2 -0
  200. package/packages/pi-coding-agent/dist/core/extensions/provider-registration.test.d.ts.map +1 -0
  201. package/packages/pi-coding-agent/dist/core/extensions/provider-registration.test.js +46 -0
  202. package/packages/pi-coding-agent/dist/core/extensions/provider-registration.test.js.map +1 -0
  203. package/packages/pi-coding-agent/dist/core/extensions/types.d.ts +2 -0
  204. package/packages/pi-coding-agent/dist/core/extensions/types.d.ts.map +1 -1
  205. package/packages/pi-coding-agent/dist/core/extensions/types.js.map +1 -1
  206. package/packages/pi-coding-agent/dist/core/model-registry.d.ts +1 -0
  207. package/packages/pi-coding-agent/dist/core/model-registry.d.ts.map +1 -1
  208. package/packages/pi-coding-agent/dist/core/model-registry.js +12 -0
  209. package/packages/pi-coding-agent/dist/core/model-registry.js.map +1 -1
  210. package/packages/pi-coding-agent/dist/core/model-resolver.js +3 -3
  211. package/packages/pi-coding-agent/dist/core/model-resolver.js.map +1 -1
  212. package/packages/pi-coding-agent/dist/core/resource-loader.d.ts +23 -1
  213. package/packages/pi-coding-agent/dist/core/resource-loader.d.ts.map +1 -1
  214. package/packages/pi-coding-agent/dist/core/resource-loader.js +80 -56
  215. package/packages/pi-coding-agent/dist/core/resource-loader.js.map +1 -1
  216. package/packages/pi-coding-agent/dist/core/sdk.d.ts.map +1 -1
  217. package/packages/pi-coding-agent/dist/core/sdk.js +9 -0
  218. package/packages/pi-coding-agent/dist/core/sdk.js.map +1 -1
  219. package/packages/pi-coding-agent/package.json +1 -1
  220. package/packages/pi-coding-agent/src/core/auth-storage.test.ts +53 -0
  221. package/packages/pi-coding-agent/src/core/auth-storage.ts +66 -1
  222. package/packages/pi-coding-agent/src/core/extensions/loader.test.ts +39 -1
  223. package/packages/pi-coding-agent/src/core/extensions/loader.ts +34 -4
  224. package/packages/pi-coding-agent/src/core/extensions/provider-registration.test.ts +81 -0
  225. package/packages/pi-coding-agent/src/core/extensions/types.ts +2 -0
  226. package/packages/pi-coding-agent/src/core/model-registry.ts +14 -0
  227. package/packages/pi-coding-agent/src/core/model-resolver.ts +3 -3
  228. package/packages/pi-coding-agent/src/core/resource-loader.ts +89 -56
  229. package/packages/pi-coding-agent/src/core/sdk.ts +10 -0
  230. package/pkg/package.json +1 -1
  231. package/src/resources/extensions/cmux/index.ts +18 -12
  232. package/src/resources/extensions/gsd/auto/detect-stuck.ts +27 -0
  233. package/src/resources/extensions/gsd/auto/finalize-timeout.ts +46 -0
  234. package/src/resources/extensions/gsd/auto/loop.ts +5 -0
  235. package/src/resources/extensions/gsd/auto/phases.ts +194 -33
  236. package/src/resources/extensions/gsd/auto/session.ts +14 -0
  237. package/src/resources/extensions/gsd/auto-dashboard.ts +11 -3
  238. package/src/resources/extensions/gsd/auto-model-selection.ts +36 -0
  239. package/src/resources/extensions/gsd/auto-post-unit.ts +141 -12
  240. package/src/resources/extensions/gsd/auto-prompts.ts +21 -0
  241. package/src/resources/extensions/gsd/auto-recovery.ts +9 -8
  242. package/src/resources/extensions/gsd/auto-start.ts +11 -20
  243. package/src/resources/extensions/gsd/auto-timers.ts +2 -1
  244. package/src/resources/extensions/gsd/auto-tool-tracking.ts +19 -0
  245. package/src/resources/extensions/gsd/auto-worktree.ts +14 -6
  246. package/src/resources/extensions/gsd/auto.ts +22 -1
  247. package/src/resources/extensions/gsd/bootstrap/db-tools.ts +160 -88
  248. package/src/resources/extensions/gsd/bootstrap/dynamic-tools.ts +15 -0
  249. package/src/resources/extensions/gsd/bootstrap/query-tools.ts +98 -0
  250. package/src/resources/extensions/gsd/bootstrap/register-extension.ts +4 -0
  251. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +36 -1
  252. package/src/resources/extensions/gsd/bootstrap/sanitize-complete-milestone.ts +57 -0
  253. package/src/resources/extensions/gsd/bootstrap/system-context.ts +31 -2
  254. package/src/resources/extensions/gsd/commands-handlers.ts +10 -4
  255. package/src/resources/extensions/gsd/constants.ts +44 -0
  256. package/src/resources/extensions/gsd/db-writer.ts +78 -4
  257. package/src/resources/extensions/gsd/forensics.ts +21 -5
  258. package/src/resources/extensions/gsd/gsd-db.ts +64 -17
  259. package/src/resources/extensions/gsd/guided-flow.ts +22 -0
  260. package/src/resources/extensions/gsd/metrics.ts +28 -1
  261. package/src/resources/extensions/gsd/native-git-bridge.ts +5 -3
  262. package/src/resources/extensions/gsd/preferences-types.ts +16 -0
  263. package/src/resources/extensions/gsd/preferences.ts +9 -2
  264. package/src/resources/extensions/gsd/prompt-loader.ts +8 -0
  265. package/src/resources/extensions/gsd/prompts/complete-milestone.md +2 -0
  266. package/src/resources/extensions/gsd/prompts/complete-slice.md +2 -0
  267. package/src/resources/extensions/gsd/prompts/doctor-heal.md +1 -0
  268. package/src/resources/extensions/gsd/prompts/forensics.md +2 -0
  269. package/src/resources/extensions/gsd/prompts/reassess-roadmap.md +2 -0
  270. package/src/resources/extensions/gsd/prompts/system.md +4 -7
  271. package/src/resources/extensions/gsd/prompts/validate-milestone.md +2 -0
  272. package/src/resources/extensions/gsd/roadmap-mutations.ts +1 -1
  273. package/src/resources/extensions/gsd/roadmap-slices.ts +10 -5
  274. package/src/resources/extensions/gsd/safety/content-validator.ts +98 -0
  275. package/src/resources/extensions/gsd/safety/destructive-guard.ts +49 -0
  276. package/src/resources/extensions/gsd/safety/evidence-collector.ts +151 -0
  277. package/src/resources/extensions/gsd/safety/evidence-cross-ref.ts +120 -0
  278. package/src/resources/extensions/gsd/safety/file-change-validator.ts +108 -0
  279. package/src/resources/extensions/gsd/safety/git-checkpoint.ts +106 -0
  280. package/src/resources/extensions/gsd/safety/safety-harness.ts +105 -0
  281. package/src/resources/extensions/gsd/slice-parallel-conflict.ts +86 -0
  282. package/src/resources/extensions/gsd/slice-parallel-eligibility.ts +73 -0
  283. package/src/resources/extensions/gsd/slice-parallel-orchestrator.ts +477 -0
  284. package/src/resources/extensions/gsd/state.ts +67 -12
  285. package/src/resources/extensions/gsd/status-guards.ts +13 -0
  286. package/src/resources/extensions/gsd/tests/artifact-corruption-2630.test.ts +288 -0
  287. package/src/resources/extensions/gsd/tests/auto-loop.test.ts +34 -13
  288. package/src/resources/extensions/gsd/tests/cmux.test.ts +58 -0
  289. package/src/resources/extensions/gsd/tests/cold-resume-db-reopen.test.ts +51 -0
  290. package/src/resources/extensions/gsd/tests/complete-milestone.test.ts +140 -0
  291. package/src/resources/extensions/gsd/tests/complete-slice-string-coercion.test.ts +211 -0
  292. package/src/resources/extensions/gsd/tests/complete-task.test.ts +39 -0
  293. package/src/resources/extensions/gsd/tests/dashboard-model-label-ordering.test.ts +107 -0
  294. package/src/resources/extensions/gsd/tests/db-access-guardrails.test.ts +109 -0
  295. package/src/resources/extensions/gsd/tests/db-path-worktree-symlink.test.ts +13 -9
  296. package/src/resources/extensions/gsd/tests/db-writer.test.ts +134 -0
  297. package/src/resources/extensions/gsd/tests/deferred-slice-dispatch.test.ts +203 -0
  298. package/src/resources/extensions/gsd/tests/discuss-tool-scoping.test.ts +130 -0
  299. package/src/resources/extensions/gsd/tests/doctor-fix-flag.test.ts +92 -0
  300. package/src/resources/extensions/gsd/tests/finalize-timeout-guard.test.ts +116 -0
  301. package/src/resources/extensions/gsd/tests/flat-rate-routing-guard.test.ts +50 -0
  302. package/src/resources/extensions/gsd/tests/forensics-stuck-loops.test.ts +103 -0
  303. package/src/resources/extensions/gsd/tests/git-checkpoint.test.ts +94 -0
  304. package/src/resources/extensions/gsd/tests/insert-slice-no-wipe.test.ts +88 -0
  305. package/src/resources/extensions/gsd/tests/integration/git-service.test.ts +27 -7
  306. package/src/resources/extensions/gsd/tests/integration/idle-recovery.test.ts +34 -0
  307. package/src/resources/extensions/gsd/tests/metrics.test.ts +116 -1
  308. package/src/resources/extensions/gsd/tests/milestone-status-tool.test.ts +201 -0
  309. package/src/resources/extensions/gsd/tests/plan-milestone-title.test.ts +2 -1
  310. package/src/resources/extensions/gsd/tests/plan-milestone.test.ts +82 -18
  311. package/src/resources/extensions/gsd/tests/preferences.test.ts +10 -0
  312. package/src/resources/extensions/gsd/tests/prompt-contracts.test.ts +25 -0
  313. package/src/resources/extensions/gsd/tests/roadmap-slices.test.ts +69 -0
  314. package/src/resources/extensions/gsd/tests/shared-wal.test.ts +30 -0
  315. package/src/resources/extensions/gsd/tests/slice-context-injection.test.ts +50 -0
  316. package/src/resources/extensions/gsd/tests/slice-parallel-conflict.test.ts +92 -0
  317. package/src/resources/extensions/gsd/tests/slice-parallel-eligibility.test.ts +95 -0
  318. package/src/resources/extensions/gsd/tests/slice-parallel-orchestrator.test.ts +83 -0
  319. package/src/resources/extensions/gsd/tests/stuck-detection-coverage.test.ts +42 -0
  320. package/src/resources/extensions/gsd/tests/tool-invocation-error-loop-break.test.ts +103 -0
  321. package/src/resources/extensions/gsd/tests/tool-param-optionality.test.ts +349 -0
  322. package/src/resources/extensions/gsd/tests/worktree-health-dispatch.test.ts +35 -2
  323. package/src/resources/extensions/gsd/tests/worktree-health-monorepo.test.ts +73 -0
  324. package/src/resources/extensions/gsd/tests/worktree-resolver.test.ts +34 -0
  325. package/src/resources/extensions/gsd/tests/worktree-submodule-safety.test.ts +1 -1
  326. package/src/resources/extensions/gsd/tests/worktree-teardown-safety.test.ts +148 -0
  327. package/src/resources/extensions/gsd/tools/complete-milestone.ts +34 -20
  328. package/src/resources/extensions/gsd/tools/complete-slice.ts +41 -26
  329. package/src/resources/extensions/gsd/tools/complete-task.ts +12 -12
  330. package/src/resources/extensions/gsd/tools/plan-milestone.ts +55 -30
  331. package/src/resources/extensions/gsd/tools/plan-slice.ts +13 -8
  332. package/src/resources/extensions/gsd/types.ts +44 -22
  333. package/src/resources/extensions/gsd/workflow-logger.ts +2 -1
  334. package/src/resources/extensions/gsd/workflow-projections.ts +23 -5
  335. package/src/resources/extensions/gsd/worktree-manager.ts +76 -28
  336. package/src/resources/extensions/gsd/worktree-resolver.ts +4 -3
  337. package/src/resources/extensions/mcp-client/auth.ts +149 -0
  338. package/src/resources/extensions/mcp-client/index.ts +16 -1
  339. package/src/resources/extensions/ollama/index.ts +26 -25
  340. package/src/resources/extensions/ollama/model-capabilities.ts +41 -34
  341. package/src/resources/extensions/ollama/ndjson-stream.ts +63 -0
  342. package/src/resources/extensions/ollama/ollama-auth-mode.test.ts +20 -0
  343. package/src/resources/extensions/ollama/ollama-chat-provider.ts +459 -0
  344. package/src/resources/extensions/ollama/ollama-client.ts +30 -30
  345. package/src/resources/extensions/ollama/ollama-discovery.ts +5 -8
  346. package/src/resources/extensions/ollama/ollama-tool.ts +69 -0
  347. package/src/resources/extensions/ollama/tests/ollama-chat-provider-stream.test.ts +82 -0
  348. package/src/resources/extensions/ollama/tests/ollama-discovery.test.ts +0 -27
  349. package/src/resources/extensions/ollama/thinking-parser.ts +116 -0
  350. package/src/resources/extensions/ollama/types.ts +23 -0
  351. package/dist/web/standalone/.next/server/chunks/2229.js +0 -12
  352. /package/dist/web/standalone/.next/static/{5FLUBNdqolRyyehCyChPd → eebXKteM9EaWyseHKTjqp}/_buildManifest.js +0 -0
  353. /package/dist/web/standalone/.next/static/{5FLUBNdqolRyyehCyChPd → eebXKteM9EaWyseHKTjqp}/_ssgManifest.js +0 -0
@@ -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
- }
@@ -31,6 +31,8 @@ export function registerOllamaTool(pi: ExtensionAPI): void {
31
31
  promptGuidelines: [
32
32
  "Use 'list' to see what models are available locally before trying to use one.",
33
33
  "Use 'pull' to download a model that isn't available yet.",
34
+ "Use 'remove' to delete a local model that is no longer needed.",
35
+ "Use 'show' to get detailed info about a model (parameters, quantization, families).",
34
36
  "Use 'status' to check if Ollama is running.",
35
37
  "Use 'ps' to see which models are loaded in memory and VRAM usage.",
36
38
  "Common models: llama3.1:8b, qwen2.5-coder:7b, deepseek-r1:8b, codestral:22b",
@@ -40,6 +42,8 @@ export function registerOllamaTool(pi: ExtensionAPI): void {
40
42
  [
41
43
  Type.Literal("list"),
42
44
  Type.Literal("pull"),
45
+ Type.Literal("remove"),
46
+ Type.Literal("show"),
43
47
  Type.Literal("status"),
44
48
  Type.Literal("ps"),
45
49
  ],
@@ -164,6 +168,71 @@ export function registerOllamaTool(pi: ExtensionAPI): void {
164
168
  };
165
169
  }
166
170
 
171
+ case "remove": {
172
+ if (!model) {
173
+ return {
174
+ content: [{ type: "text", text: "Error: 'model' parameter is required for remove action." }],
175
+ isError: true,
176
+ details: { action, durationMs: Date.now() - startTime, error: "missing_model" } as OllamaToolDetails,
177
+ };
178
+ }
179
+
180
+ const running = await client.isRunning();
181
+ if (!running) {
182
+ return {
183
+ content: [{ type: "text", text: "Ollama is not running." }],
184
+ isError: true,
185
+ details: { action, model, durationMs: Date.now() - startTime, error: "not_running" } as OllamaToolDetails,
186
+ };
187
+ }
188
+
189
+ await client.deleteModel(model);
190
+ return {
191
+ content: [{ type: "text", text: `Successfully removed ${model}` }],
192
+ details: { action, model, durationMs: Date.now() - startTime } as OllamaToolDetails,
193
+ };
194
+ }
195
+
196
+ case "show": {
197
+ if (!model) {
198
+ return {
199
+ content: [{ type: "text", text: "Error: 'model' parameter is required for show action." }],
200
+ isError: true,
201
+ details: { action, durationMs: Date.now() - startTime, error: "missing_model" } as OllamaToolDetails,
202
+ };
203
+ }
204
+
205
+ const running = await client.isRunning();
206
+ if (!running) {
207
+ return {
208
+ content: [{ type: "text", text: "Ollama is not running." }],
209
+ isError: true,
210
+ details: { action, model, durationMs: Date.now() - startTime, error: "not_running" } as OllamaToolDetails,
211
+ };
212
+ }
213
+
214
+ const info = await client.showModel(model);
215
+ const details = info.details;
216
+ const infoLines = [
217
+ `Model: ${model}`,
218
+ `Family: ${details.family}`,
219
+ `Parameters: ${details.parameter_size}`,
220
+ `Quantization: ${details.quantization_level}`,
221
+ `Format: ${details.format}`,
222
+ ];
223
+ if (details.families?.length) {
224
+ infoLines.push(`Families: ${details.families.join(", ")}`);
225
+ }
226
+ if (info.parameters) {
227
+ infoLines.push(`\nModelfile parameters:\n${info.parameters}`);
228
+ }
229
+
230
+ return {
231
+ content: [{ type: "text", text: infoLines.join("\n") }],
232
+ details: { action, model, durationMs: Date.now() - startTime } as OllamaToolDetails,
233
+ };
234
+ }
235
+
167
236
  default:
168
237
  return {
169
238
  content: [{ type: "text", text: `Unknown action: ${action}` }],