gsd-pi 2.68.1 → 2.69.0-dev.232905b

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 (304) hide show
  1. package/dist/resources/extensions/gsd/auto-model-selection.js +27 -1
  2. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +7 -0
  3. package/dist/resources/extensions/gsd/bootstrap/system-context.js +6 -2
  4. package/dist/resources/extensions/gsd/bootstrap/write-gate.js +1 -5
  5. package/dist/resources/extensions/gsd/codebase-generator.js +12 -0
  6. package/dist/resources/extensions/gsd/commands-cmux.js +30 -1
  7. package/dist/resources/extensions/gsd/guided-flow.js +25 -70
  8. package/dist/resources/extensions/gsd/model-router.js +85 -2
  9. package/dist/resources/extensions/gsd/prompts/discuss.md +2 -0
  10. package/dist/resources/extensions/gsd/templates/context.md +34 -2
  11. package/dist/resources/extensions/gsd/workflow-mcp.js +53 -6
  12. package/dist/web/standalone/.next/BUILD_ID +1 -1
  13. package/dist/web/standalone/.next/app-path-routes-manifest.json +9 -9
  14. package/dist/web/standalone/.next/build-manifest.json +3 -3
  15. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  16. package/dist/web/standalone/.next/required-server-files.json +3 -3
  17. package/dist/web/standalone/.next/server/app/_global-error/page.js +3 -3
  18. package/dist/web/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  19. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  20. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  21. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  22. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  23. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  24. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  25. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  26. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  27. package/dist/web/standalone/.next/server/app/_not-found/page.js +2 -2
  28. package/dist/web/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  29. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  30. package/dist/web/standalone/.next/server/app/_not-found.rsc +3 -3
  31. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +3 -3
  32. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  33. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +3 -3
  34. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  35. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  36. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  37. package/dist/web/standalone/.next/server/app/api/boot/route.js +1 -1
  38. package/dist/web/standalone/.next/server/app/api/boot/route_client-reference-manifest.js +1 -1
  39. package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route.js +1 -1
  40. package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route_client-reference-manifest.js +1 -1
  41. package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route.js +1 -1
  42. package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route_client-reference-manifest.js +1 -1
  43. package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route.js +2 -2
  44. package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route_client-reference-manifest.js +1 -1
  45. package/dist/web/standalone/.next/server/app/api/browse-directories/route.js +1 -1
  46. package/dist/web/standalone/.next/server/app/api/browse-directories/route_client-reference-manifest.js +1 -1
  47. package/dist/web/standalone/.next/server/app/api/captures/route.js +1 -1
  48. package/dist/web/standalone/.next/server/app/api/captures/route_client-reference-manifest.js +1 -1
  49. package/dist/web/standalone/.next/server/app/api/cleanup/route.js +1 -1
  50. package/dist/web/standalone/.next/server/app/api/cleanup/route_client-reference-manifest.js +1 -1
  51. package/dist/web/standalone/.next/server/app/api/dev-mode/route.js +1 -1
  52. package/dist/web/standalone/.next/server/app/api/dev-mode/route_client-reference-manifest.js +1 -1
  53. package/dist/web/standalone/.next/server/app/api/doctor/route.js +1 -1
  54. package/dist/web/standalone/.next/server/app/api/doctor/route_client-reference-manifest.js +1 -1
  55. package/dist/web/standalone/.next/server/app/api/experimental/route.js +2 -2
  56. package/dist/web/standalone/.next/server/app/api/experimental/route_client-reference-manifest.js +1 -1
  57. package/dist/web/standalone/.next/server/app/api/export-data/route.js +1 -1
  58. package/dist/web/standalone/.next/server/app/api/export-data/route_client-reference-manifest.js +1 -1
  59. package/dist/web/standalone/.next/server/app/api/files/route.js +1 -1
  60. package/dist/web/standalone/.next/server/app/api/files/route_client-reference-manifest.js +1 -1
  61. package/dist/web/standalone/.next/server/app/api/forensics/route.js +1 -1
  62. package/dist/web/standalone/.next/server/app/api/forensics/route_client-reference-manifest.js +1 -1
  63. package/dist/web/standalone/.next/server/app/api/git/route.js +1 -1
  64. package/dist/web/standalone/.next/server/app/api/git/route_client-reference-manifest.js +1 -1
  65. package/dist/web/standalone/.next/server/app/api/history/route.js +1 -1
  66. package/dist/web/standalone/.next/server/app/api/history/route_client-reference-manifest.js +1 -1
  67. package/dist/web/standalone/.next/server/app/api/hooks/route.js +1 -1
  68. package/dist/web/standalone/.next/server/app/api/hooks/route_client-reference-manifest.js +1 -1
  69. package/dist/web/standalone/.next/server/app/api/inspect/route.js +1 -1
  70. package/dist/web/standalone/.next/server/app/api/inspect/route_client-reference-manifest.js +1 -1
  71. package/dist/web/standalone/.next/server/app/api/knowledge/route.js +1 -1
  72. package/dist/web/standalone/.next/server/app/api/knowledge/route_client-reference-manifest.js +1 -1
  73. package/dist/web/standalone/.next/server/app/api/live-state/route.js +1 -1
  74. package/dist/web/standalone/.next/server/app/api/live-state/route_client-reference-manifest.js +1 -1
  75. package/dist/web/standalone/.next/server/app/api/notifications/route.js +2 -2
  76. package/dist/web/standalone/.next/server/app/api/notifications/route_client-reference-manifest.js +1 -1
  77. package/dist/web/standalone/.next/server/app/api/onboarding/route.js +1 -1
  78. package/dist/web/standalone/.next/server/app/api/onboarding/route_client-reference-manifest.js +1 -1
  79. package/dist/web/standalone/.next/server/app/api/preferences/route.js +1 -1
  80. package/dist/web/standalone/.next/server/app/api/preferences/route_client-reference-manifest.js +1 -1
  81. package/dist/web/standalone/.next/server/app/api/projects/route.js +1 -1
  82. package/dist/web/standalone/.next/server/app/api/projects/route_client-reference-manifest.js +1 -1
  83. package/dist/web/standalone/.next/server/app/api/recovery/route.js +1 -1
  84. package/dist/web/standalone/.next/server/app/api/recovery/route_client-reference-manifest.js +1 -1
  85. package/dist/web/standalone/.next/server/app/api/remote-questions/route.js +2 -2
  86. package/dist/web/standalone/.next/server/app/api/remote-questions/route_client-reference-manifest.js +1 -1
  87. package/dist/web/standalone/.next/server/app/api/session/browser/route.js +1 -1
  88. package/dist/web/standalone/.next/server/app/api/session/browser/route_client-reference-manifest.js +1 -1
  89. package/dist/web/standalone/.next/server/app/api/session/command/route.js +1 -1
  90. package/dist/web/standalone/.next/server/app/api/session/command/route_client-reference-manifest.js +1 -1
  91. package/dist/web/standalone/.next/server/app/api/session/events/route.js +2 -2
  92. package/dist/web/standalone/.next/server/app/api/session/events/route_client-reference-manifest.js +1 -1
  93. package/dist/web/standalone/.next/server/app/api/session/manage/route.js +1 -1
  94. package/dist/web/standalone/.next/server/app/api/session/manage/route_client-reference-manifest.js +1 -1
  95. package/dist/web/standalone/.next/server/app/api/settings-data/route.js +1 -1
  96. package/dist/web/standalone/.next/server/app/api/settings-data/route_client-reference-manifest.js +1 -1
  97. package/dist/web/standalone/.next/server/app/api/shutdown/route.js +1 -1
  98. package/dist/web/standalone/.next/server/app/api/shutdown/route_client-reference-manifest.js +1 -1
  99. package/dist/web/standalone/.next/server/app/api/skill-health/route.js +1 -1
  100. package/dist/web/standalone/.next/server/app/api/skill-health/route_client-reference-manifest.js +1 -1
  101. package/dist/web/standalone/.next/server/app/api/steer/route.js +1 -1
  102. package/dist/web/standalone/.next/server/app/api/steer/route_client-reference-manifest.js +1 -1
  103. package/dist/web/standalone/.next/server/app/api/switch-root/route.js +1 -1
  104. package/dist/web/standalone/.next/server/app/api/switch-root/route_client-reference-manifest.js +1 -1
  105. package/dist/web/standalone/.next/server/app/api/terminal/input/route.js +1 -1
  106. package/dist/web/standalone/.next/server/app/api/terminal/input/route_client-reference-manifest.js +1 -1
  107. package/dist/web/standalone/.next/server/app/api/terminal/resize/route.js +2 -2
  108. package/dist/web/standalone/.next/server/app/api/terminal/resize/route_client-reference-manifest.js +1 -1
  109. package/dist/web/standalone/.next/server/app/api/terminal/sessions/route.js +1 -1
  110. package/dist/web/standalone/.next/server/app/api/terminal/sessions/route_client-reference-manifest.js +1 -1
  111. package/dist/web/standalone/.next/server/app/api/terminal/stream/route.js +2 -2
  112. package/dist/web/standalone/.next/server/app/api/terminal/stream/route_client-reference-manifest.js +1 -1
  113. package/dist/web/standalone/.next/server/app/api/terminal/upload/route.js +1 -1
  114. package/dist/web/standalone/.next/server/app/api/terminal/upload/route_client-reference-manifest.js +1 -1
  115. package/dist/web/standalone/.next/server/app/api/undo/route.js +1 -1
  116. package/dist/web/standalone/.next/server/app/api/undo/route_client-reference-manifest.js +1 -1
  117. package/dist/web/standalone/.next/server/app/api/update/route.js +1 -1
  118. package/dist/web/standalone/.next/server/app/api/update/route_client-reference-manifest.js +1 -1
  119. package/dist/web/standalone/.next/server/app/api/visualizer/route.js +1 -1
  120. package/dist/web/standalone/.next/server/app/api/visualizer/route_client-reference-manifest.js +1 -1
  121. package/dist/web/standalone/.next/server/app/index.html +1 -1
  122. package/dist/web/standalone/.next/server/app/index.rsc +4 -4
  123. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
  124. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +4 -4
  125. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  126. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +3 -3
  127. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  128. package/dist/web/standalone/.next/server/app/page.js +2 -2
  129. package/dist/web/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  130. package/dist/web/standalone/.next/server/app-paths-manifest.json +9 -9
  131. package/dist/web/standalone/.next/server/chunks/63.js +3 -3
  132. package/dist/web/standalone/.next/server/chunks/6897.js +3 -3
  133. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  134. package/dist/web/standalone/.next/server/middleware.js +2 -2
  135. package/dist/web/standalone/.next/server/next-font-manifest.js +1 -1
  136. package/dist/web/standalone/.next/server/next-font-manifest.json +1 -1
  137. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  138. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  139. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  140. package/dist/web/standalone/.next/static/chunks/app/_not-found/{page-2f24283c162b6ab3.js → page-f2a7482d42a5614b.js} +1 -1
  141. package/dist/web/standalone/.next/static/chunks/app/{layout-9ecfd95f343793f0.js → layout-a16c7a7ecdf0c2cf.js} +1 -1
  142. package/dist/web/standalone/.next/static/chunks/app/page-f1e30ab6bb269149.js +1 -0
  143. package/dist/web/standalone/.next/static/chunks/main-app-fdab67f7802d7832.js +1 -0
  144. package/dist/web/standalone/.next/static/chunks/next/dist/client/components/builtin/global-error-459824ffb8c323dd.js +1 -0
  145. package/dist/web/standalone/node_modules/node-pty/build/Makefile +2 -2
  146. package/dist/web/standalone/node_modules/node-pty/build/Release/pty.node +0 -0
  147. package/dist/web/standalone/node_modules/node-pty/build/pty.target.mk +14 -14
  148. package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api.target.mk +14 -14
  149. package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api_except.target.mk +14 -14
  150. package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api_maybe.target.mk +14 -14
  151. package/dist/web/standalone/server.js +1 -1
  152. package/package.json +1 -1
  153. package/packages/daemon/src/orchestrator.ts +9 -84
  154. package/packages/mcp-server/README.md +25 -3
  155. package/packages/mcp-server/dist/cli.d.ts +0 -1
  156. package/packages/mcp-server/dist/cli.d.ts.map +1 -1
  157. package/packages/mcp-server/dist/cli.js +4 -2
  158. package/packages/mcp-server/dist/cli.js.map +1 -1
  159. package/packages/mcp-server/dist/server.d.ts +32 -1
  160. package/packages/mcp-server/dist/server.d.ts.map +1 -1
  161. package/packages/mcp-server/dist/server.js +118 -1
  162. package/packages/mcp-server/dist/server.js.map +1 -1
  163. package/packages/mcp-server/dist/tool-credentials.d.ts +6 -0
  164. package/packages/mcp-server/dist/tool-credentials.d.ts.map +1 -0
  165. package/packages/mcp-server/dist/tool-credentials.js +90 -0
  166. package/packages/mcp-server/dist/tool-credentials.js.map +1 -0
  167. package/packages/mcp-server/dist/workflow-tools.d.ts +1 -0
  168. package/packages/mcp-server/dist/workflow-tools.d.ts.map +1 -1
  169. package/packages/mcp-server/dist/workflow-tools.js +274 -2
  170. package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
  171. package/packages/mcp-server/src/cli.ts +5 -3
  172. package/packages/mcp-server/src/mcp-server.test.ts +85 -1
  173. package/packages/mcp-server/src/server.ts +188 -1
  174. package/packages/mcp-server/src/tool-credentials.test.ts +95 -0
  175. package/packages/mcp-server/src/tool-credentials.ts +97 -0
  176. package/packages/mcp-server/src/workflow-tools.test.ts +32 -25
  177. package/packages/mcp-server/src/workflow-tools.ts +365 -2
  178. package/packages/pi-ai/dist/index.d.ts +3 -0
  179. package/packages/pi-ai/dist/index.d.ts.map +1 -1
  180. package/packages/pi-ai/dist/index.js +2 -0
  181. package/packages/pi-ai/dist/index.js.map +1 -1
  182. package/packages/pi-ai/dist/providers/amazon-bedrock.js +2 -2
  183. package/packages/pi-ai/dist/providers/amazon-bedrock.js.map +1 -1
  184. package/packages/pi-ai/dist/providers/anthropic-shared.js +2 -2
  185. package/packages/pi-ai/dist/providers/anthropic-shared.js.map +1 -1
  186. package/packages/pi-ai/dist/providers/anthropic.d.ts.map +1 -1
  187. package/packages/pi-ai/dist/providers/anthropic.js +1 -23
  188. package/packages/pi-ai/dist/providers/anthropic.js.map +1 -1
  189. package/packages/pi-ai/dist/providers/google-shared.js +2 -2
  190. package/packages/pi-ai/dist/providers/google-shared.js.map +1 -1
  191. package/packages/pi-ai/dist/providers/mistral.js +2 -2
  192. package/packages/pi-ai/dist/providers/mistral.js.map +1 -1
  193. package/packages/pi-ai/dist/providers/openai-completions.js +2 -2
  194. package/packages/pi-ai/dist/providers/openai-completions.js.map +1 -1
  195. package/packages/pi-ai/dist/providers/openai-responses-shared.js +2 -2
  196. package/packages/pi-ai/dist/providers/openai-responses-shared.js.map +1 -1
  197. package/packages/pi-ai/dist/providers/provider-capabilities.d.ts +59 -0
  198. package/packages/pi-ai/dist/providers/provider-capabilities.d.ts.map +1 -0
  199. package/packages/pi-ai/dist/providers/provider-capabilities.js +173 -0
  200. package/packages/pi-ai/dist/providers/provider-capabilities.js.map +1 -0
  201. package/packages/pi-ai/dist/providers/provider-capabilities.test.d.ts +2 -0
  202. package/packages/pi-ai/dist/providers/provider-capabilities.test.d.ts.map +1 -0
  203. package/packages/pi-ai/dist/providers/provider-capabilities.test.js +132 -0
  204. package/packages/pi-ai/dist/providers/provider-capabilities.test.js.map +1 -0
  205. package/packages/pi-ai/dist/providers/transform-messages-report.test.d.ts +2 -0
  206. package/packages/pi-ai/dist/providers/transform-messages-report.test.d.ts.map +1 -0
  207. package/packages/pi-ai/dist/providers/transform-messages-report.test.js +172 -0
  208. package/packages/pi-ai/dist/providers/transform-messages-report.test.js.map +1 -0
  209. package/packages/pi-ai/dist/providers/transform-messages.d.ts +34 -1
  210. package/packages/pi-ai/dist/providers/transform-messages.d.ts.map +1 -1
  211. package/packages/pi-ai/dist/providers/transform-messages.js +73 -2
  212. package/packages/pi-ai/dist/providers/transform-messages.js.map +1 -1
  213. package/packages/pi-ai/dist/utils/oauth/index.d.ts +3 -2
  214. package/packages/pi-ai/dist/utils/oauth/index.d.ts.map +1 -1
  215. package/packages/pi-ai/dist/utils/oauth/index.js +3 -5
  216. package/packages/pi-ai/dist/utils/oauth/index.js.map +1 -1
  217. package/packages/pi-ai/src/index.ts +3 -0
  218. package/packages/pi-ai/src/providers/amazon-bedrock.ts +2 -2
  219. package/packages/pi-ai/src/providers/anthropic-shared.ts +2 -2
  220. package/packages/pi-ai/src/providers/anthropic.ts +1 -31
  221. package/packages/pi-ai/src/providers/google-shared.ts +2 -2
  222. package/packages/pi-ai/src/providers/mistral.ts +2 -2
  223. package/packages/pi-ai/src/providers/openai-completions.ts +2 -2
  224. package/packages/pi-ai/src/providers/openai-responses-shared.ts +2 -2
  225. package/packages/pi-ai/src/providers/provider-capabilities.test.ts +174 -0
  226. package/packages/pi-ai/src/providers/provider-capabilities.ts +215 -0
  227. package/packages/pi-ai/src/providers/transform-messages-report.test.ts +189 -0
  228. package/packages/pi-ai/src/providers/transform-messages.ts +94 -1
  229. package/packages/pi-ai/src/utils/oauth/index.ts +3 -5
  230. package/packages/pi-coding-agent/dist/core/extensions/index.d.ts +1 -1
  231. package/packages/pi-coding-agent/dist/core/extensions/index.d.ts.map +1 -1
  232. package/packages/pi-coding-agent/dist/core/extensions/index.js.map +1 -1
  233. package/packages/pi-coding-agent/dist/core/extensions/loader.d.ts.map +1 -1
  234. package/packages/pi-coding-agent/dist/core/extensions/loader.js +10 -1
  235. package/packages/pi-coding-agent/dist/core/extensions/loader.js.map +1 -1
  236. package/packages/pi-coding-agent/dist/core/extensions/runner.d.ts +2 -1
  237. package/packages/pi-coding-agent/dist/core/extensions/runner.d.ts.map +1 -1
  238. package/packages/pi-coding-agent/dist/core/extensions/runner.js +15 -0
  239. package/packages/pi-coding-agent/dist/core/extensions/runner.js.map +1 -1
  240. package/packages/pi-coding-agent/dist/core/extensions/types.d.ts +41 -0
  241. package/packages/pi-coding-agent/dist/core/extensions/types.d.ts.map +1 -1
  242. package/packages/pi-coding-agent/dist/core/extensions/types.js.map +1 -1
  243. package/packages/pi-coding-agent/dist/core/tools/index.d.ts +1 -0
  244. package/packages/pi-coding-agent/dist/core/tools/index.d.ts.map +1 -1
  245. package/packages/pi-coding-agent/dist/core/tools/index.js +1 -0
  246. package/packages/pi-coding-agent/dist/core/tools/index.js.map +1 -1
  247. package/packages/pi-coding-agent/dist/core/tools/tool-compatibility-registry.d.ts +27 -0
  248. package/packages/pi-coding-agent/dist/core/tools/tool-compatibility-registry.d.ts.map +1 -0
  249. package/packages/pi-coding-agent/dist/core/tools/tool-compatibility-registry.js +69 -0
  250. package/packages/pi-coding-agent/dist/core/tools/tool-compatibility-registry.js.map +1 -0
  251. package/packages/pi-coding-agent/dist/index.d.ts +2 -2
  252. package/packages/pi-coding-agent/dist/index.d.ts.map +1 -1
  253. package/packages/pi-coding-agent/dist/index.js +3 -1
  254. package/packages/pi-coding-agent/dist/index.js.map +1 -1
  255. package/packages/pi-coding-agent/package.json +1 -1
  256. package/packages/pi-coding-agent/src/core/extensions/index.ts +4 -0
  257. package/packages/pi-coding-agent/src/core/extensions/loader.ts +11 -1
  258. package/packages/pi-coding-agent/src/core/extensions/runner.ts +18 -0
  259. package/packages/pi-coding-agent/src/core/extensions/types.ts +45 -0
  260. package/packages/pi-coding-agent/src/core/tools/index.ts +7 -0
  261. package/packages/pi-coding-agent/src/core/tools/tool-compatibility-registry.ts +83 -0
  262. package/packages/pi-coding-agent/src/index.ts +9 -0
  263. package/pkg/package.json +1 -1
  264. package/src/resources/extensions/gsd/auto-model-selection.ts +36 -4
  265. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +8 -0
  266. package/src/resources/extensions/gsd/bootstrap/system-context.ts +9 -5
  267. package/src/resources/extensions/gsd/bootstrap/write-gate.ts +1 -5
  268. package/src/resources/extensions/gsd/codebase-generator.ts +16 -0
  269. package/src/resources/extensions/gsd/commands-cmux.ts +32 -1
  270. package/src/resources/extensions/gsd/guided-flow.ts +22 -84
  271. package/src/resources/extensions/gsd/model-router.ts +117 -10
  272. package/src/resources/extensions/gsd/preferences-types.ts +3 -1
  273. package/src/resources/extensions/gsd/prompts/discuss.md +2 -0
  274. package/src/resources/extensions/gsd/templates/context.md +34 -2
  275. package/src/resources/extensions/gsd/tests/capability-router.test.ts +31 -7
  276. package/src/resources/extensions/gsd/tests/cmux.test.ts +67 -1
  277. package/src/resources/extensions/gsd/tests/codebase-generator.test.ts +28 -0
  278. package/src/resources/extensions/gsd/tests/mcp-project-config.test.ts +6 -2
  279. package/src/resources/extensions/gsd/tests/model-router.test.ts +2 -2
  280. package/src/resources/extensions/gsd/tests/tool-compatibility.test.ts +199 -0
  281. package/src/resources/extensions/gsd/tests/workflow-mcp.test.ts +23 -7
  282. package/src/resources/extensions/gsd/tests/write-gate.test.ts +13 -16
  283. package/src/resources/extensions/gsd/workflow-mcp.ts +59 -5
  284. package/dist/resources/extensions/gsd/prompt-validation.js +0 -67
  285. package/dist/resources/extensions/gsd/prompts/discuss-prepared.md +0 -424
  286. package/dist/resources/extensions/gsd/templates/context-enhanced.md +0 -138
  287. package/dist/web/standalone/.next/static/chunks/app/page-7115e62689b5fd84.js +0 -1
  288. package/dist/web/standalone/.next/static/chunks/main-app-d3d4c336195465f9.js +0 -1
  289. package/dist/web/standalone/.next/static/chunks/next/dist/client/components/builtin/global-error-ab5a8926e07ec673.js +0 -1
  290. package/packages/pi-ai/dist/utils/oauth/anthropic.d.ts +0 -17
  291. package/packages/pi-ai/dist/utils/oauth/anthropic.d.ts.map +0 -1
  292. package/packages/pi-ai/dist/utils/oauth/anthropic.js +0 -106
  293. package/packages/pi-ai/dist/utils/oauth/anthropic.js.map +0 -1
  294. package/packages/pi-ai/src/utils/oauth/anthropic.ts +0 -140
  295. package/src/resources/extensions/gsd/prompt-validation.ts +0 -88
  296. package/src/resources/extensions/gsd/prompts/discuss-prepared.md +0 -424
  297. package/src/resources/extensions/gsd/templates/context-enhanced.md +0 -138
  298. package/src/resources/extensions/gsd/tests/adversarial-review-fixes.test.ts +0 -223
  299. package/src/resources/extensions/gsd/tests/integration/test-isolation.ts +0 -53
  300. package/src/resources/extensions/gsd/tests/integration-prepared-discussion.test.ts +0 -525
  301. package/src/resources/extensions/gsd/tests/preparation.test.ts +0 -1211
  302. package/src/resources/extensions/gsd/tests/prompt-builder.test.ts +0 -669
  303. /package/dist/web/standalone/.next/static/{u9mQsApZYm8sVYSAaft8g → CHeIRZw-E97oO51g7zDvv}/_buildManifest.js +0 -0
  304. /package/dist/web/standalone/.next/static/{u9mQsApZYm8sVYSAaft8g → CHeIRZw-E97oO51g7zDvv}/_ssgManifest.js +0 -0
@@ -1,9 +1,64 @@
1
+ /**
2
+ * Create an empty provider switch report.
3
+ */
4
+ export function createEmptyReport(fromApi, toApi) {
5
+ return {
6
+ fromApi,
7
+ toApi,
8
+ thinkingBlocksDropped: 0,
9
+ thinkingBlocksDowngraded: 0,
10
+ toolCallIdsRemapped: 0,
11
+ syntheticToolResultsInserted: 0,
12
+ thoughtSignaturesDropped: 0,
13
+ };
14
+ }
15
+ /**
16
+ * Check if a provider switch report has any non-zero transformations.
17
+ */
18
+ export function hasTransformations(report) {
19
+ return (report.thinkingBlocksDropped > 0 ||
20
+ report.thinkingBlocksDowngraded > 0 ||
21
+ report.toolCallIdsRemapped > 0 ||
22
+ report.syntheticToolResultsInserted > 0 ||
23
+ report.thoughtSignaturesDropped > 0);
24
+ }
25
+ /**
26
+ * Create a report, run transformMessages, and log if non-empty.
27
+ * Convenience wrapper for provider adapters (ADR-005).
28
+ */
29
+ export function transformMessagesWithReport(messages, model, normalizeToolCallId, sourceApi) {
30
+ const report = createEmptyReport(sourceApi ?? "unknown", model.api);
31
+ const result = transformMessages(messages, model, normalizeToolCallId, report);
32
+ if (hasTransformations(report)) {
33
+ logProviderSwitchReport(report);
34
+ }
35
+ return result;
36
+ }
37
+ /** Log a non-empty ProviderSwitchReport as a debug-level warning. */
38
+ function logProviderSwitchReport(report) {
39
+ const parts = [`Provider switch ${report.fromApi} → ${report.toApi}:`];
40
+ if (report.thinkingBlocksDropped > 0)
41
+ parts.push(`${report.thinkingBlocksDropped} thinking blocks dropped`);
42
+ if (report.thinkingBlocksDowngraded > 0)
43
+ parts.push(`${report.thinkingBlocksDowngraded} thinking blocks downgraded`);
44
+ if (report.toolCallIdsRemapped > 0)
45
+ parts.push(`${report.toolCallIdsRemapped} tool call IDs remapped`);
46
+ if (report.syntheticToolResultsInserted > 0)
47
+ parts.push(`${report.syntheticToolResultsInserted} synthetic tool results inserted`);
48
+ if (report.thoughtSignaturesDropped > 0)
49
+ parts.push(`${report.thoughtSignaturesDropped} thought signatures dropped`);
50
+ // Use process.stderr for debug output — this is observable in verbose/debug modes
51
+ // without polluting stdout which may be used for structured output (RPC/MCP).
52
+ if (process.env.GSD_VERBOSE === "1" || process.env.PI_VERBOSE === "1") {
53
+ process.stderr.write(`[provider-switch] ${parts.join(", ")}\n`);
54
+ }
55
+ }
1
56
  /**
2
57
  * Normalize tool call ID for cross-provider compatibility.
3
58
  * OpenAI Responses API generates IDs that are 450+ chars with special characters like `|`.
4
59
  * Anthropic APIs require IDs matching ^[a-zA-Z0-9_-]+$ (max 64 chars).
5
60
  */
6
- export function transformMessages(messages, model, normalizeToolCallId) {
61
+ export function transformMessages(messages, model, normalizeToolCallId, report) {
7
62
  // Build a map of original tool call IDs to normalized IDs
8
63
  const toolCallIdMap = new Map();
9
64
  // First pass: transform messages (thinking blocks, tool call ID normalization)
@@ -31,6 +86,8 @@ export function transformMessages(messages, model, normalizeToolCallId) {
31
86
  // Redacted thinking is opaque encrypted content, only valid for the same model.
32
87
  // Drop it for cross-model to avoid API errors.
33
88
  if (block.redacted) {
89
+ if (!isSameModel && report)
90
+ report.thinkingBlocksDropped++;
34
91
  return isSameModel ? block : [];
35
92
  }
36
93
  // For same model: keep thinking blocks with signatures (needed for replay)
@@ -38,10 +95,16 @@ export function transformMessages(messages, model, normalizeToolCallId) {
38
95
  if (isSameModel && block.thinkingSignature)
39
96
  return block;
40
97
  // Skip empty thinking blocks, convert others to plain text
41
- if (!block.thinking || block.thinking.trim() === "")
98
+ if (!block.thinking || block.thinking.trim() === "") {
99
+ if (!isSameModel && report)
100
+ report.thinkingBlocksDropped++;
42
101
  return [];
102
+ }
43
103
  if (isSameModel)
44
104
  return block;
105
+ // Downgrade: structured thinking → plain text
106
+ if (report)
107
+ report.thinkingBlocksDowngraded++;
45
108
  return {
46
109
  type: "text",
47
110
  text: block.thinking,
@@ -61,12 +124,16 @@ export function transformMessages(messages, model, normalizeToolCallId) {
61
124
  if (!isSameModel && toolCall.thoughtSignature) {
62
125
  normalizedToolCall = { ...toolCall };
63
126
  delete normalizedToolCall.thoughtSignature;
127
+ if (report)
128
+ report.thoughtSignaturesDropped++;
64
129
  }
65
130
  if (!isSameModel && normalizeToolCallId) {
66
131
  const normalizedId = normalizeToolCallId(toolCall.id, model, assistantMsg);
67
132
  if (normalizedId !== toolCall.id) {
68
133
  toolCallIdMap.set(toolCall.id, normalizedId);
69
134
  normalizedToolCall = { ...normalizedToolCall, id: normalizedId };
135
+ if (report)
136
+ report.toolCallIdsRemapped++;
70
137
  }
71
138
  }
72
139
  return normalizedToolCall;
@@ -100,6 +167,8 @@ export function transformMessages(messages, model, normalizeToolCallId) {
100
167
  isError: true,
101
168
  timestamp: Date.now(),
102
169
  });
170
+ if (report)
171
+ report.syntheticToolResultsInserted++;
103
172
  }
104
173
  }
105
174
  pendingToolCalls = [];
@@ -139,6 +208,8 @@ export function transformMessages(messages, model, normalizeToolCallId) {
139
208
  isError: true,
140
209
  timestamp: Date.now(),
141
210
  });
211
+ if (report)
212
+ report.syntheticToolResultsInserted++;
142
213
  }
143
214
  }
144
215
  pendingToolCalls = [];
@@ -1 +1 @@
1
- {"version":3,"file":"transform-messages.js","sourceRoot":"","sources":["../../src/providers/transform-messages.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Api, AssistantMessage, Message, Model, ToolCall, ToolResultMessage } from \"../types.js\";\n\n/**\n * Normalize tool call ID for cross-provider compatibility.\n * OpenAI Responses API generates IDs that are 450+ chars with special characters like `|`.\n * Anthropic APIs require IDs matching ^[a-zA-Z0-9_-]+$ (max 64 chars).\n */\nexport function transformMessages<TApi extends Api>(\n\tmessages: Message[],\n\tmodel: Model<TApi>,\n\tnormalizeToolCallId?: (id: string, model: Model<TApi>, source: AssistantMessage) => string,\n): Message[] {\n\t// Build a map of original tool call IDs to normalized IDs\n\tconst toolCallIdMap = new Map<string, string>();\n\n\t// First pass: transform messages (thinking blocks, tool call ID normalization)\n\tconst transformed = messages.map((msg) => {\n\t\t// User messages pass through unchanged\n\t\tif (msg.role === \"user\") {\n\t\t\treturn msg;\n\t\t}\n\n\t\t// Handle toolResult messages - normalize toolCallId if we have a mapping\n\t\tif (msg.role === \"toolResult\") {\n\t\t\tconst normalizedId = toolCallIdMap.get(msg.toolCallId);\n\t\t\tif (normalizedId && normalizedId !== msg.toolCallId) {\n\t\t\t\treturn { ...msg, toolCallId: normalizedId };\n\t\t\t}\n\t\t\treturn msg;\n\t\t}\n\n\t\t// Assistant messages need transformation check\n\t\tif (msg.role === \"assistant\") {\n\t\t\tconst assistantMsg = msg as AssistantMessage;\n\t\t\tconst isSameModel =\n\t\t\t\tassistantMsg.provider === model.provider &&\n\t\t\t\tassistantMsg.api === model.api &&\n\t\t\t\tassistantMsg.model === model.id;\n\n\t\t\tconst transformedContent = assistantMsg.content.flatMap((block) => {\n\t\t\t\tif (block.type === \"thinking\") {\n\t\t\t\t\t// Redacted thinking is opaque encrypted content, only valid for the same model.\n\t\t\t\t\t// Drop it for cross-model to avoid API errors.\n\t\t\t\t\tif (block.redacted) {\n\t\t\t\t\t\treturn isSameModel ? block : [];\n\t\t\t\t\t}\n\t\t\t\t\t// For same model: keep thinking blocks with signatures (needed for replay)\n\t\t\t\t\t// even if the thinking text is empty (OpenAI encrypted reasoning)\n\t\t\t\t\tif (isSameModel && block.thinkingSignature) return block;\n\t\t\t\t\t// Skip empty thinking blocks, convert others to plain text\n\t\t\t\t\tif (!block.thinking || block.thinking.trim() === \"\") return [];\n\t\t\t\t\tif (isSameModel) return block;\n\t\t\t\t\treturn {\n\t\t\t\t\t\ttype: \"text\" as const,\n\t\t\t\t\t\ttext: block.thinking,\n\t\t\t\t\t};\n\t\t\t\t}\n\n\t\t\t\tif (block.type === \"text\") {\n\t\t\t\t\tif (isSameModel) return block;\n\t\t\t\t\treturn {\n\t\t\t\t\t\ttype: \"text\" as const,\n\t\t\t\t\t\ttext: block.text,\n\t\t\t\t\t};\n\t\t\t\t}\n\n\t\t\t\tif (block.type === \"toolCall\") {\n\t\t\t\t\tconst toolCall = block as ToolCall;\n\t\t\t\t\tlet normalizedToolCall: ToolCall = toolCall;\n\n\t\t\t\t\tif (!isSameModel && toolCall.thoughtSignature) {\n\t\t\t\t\t\tnormalizedToolCall = { ...toolCall };\n\t\t\t\t\t\tdelete (normalizedToolCall as { thoughtSignature?: string }).thoughtSignature;\n\t\t\t\t\t}\n\n\t\t\t\t\tif (!isSameModel && normalizeToolCallId) {\n\t\t\t\t\t\tconst normalizedId = normalizeToolCallId(toolCall.id, model, assistantMsg);\n\t\t\t\t\t\tif (normalizedId !== toolCall.id) {\n\t\t\t\t\t\t\ttoolCallIdMap.set(toolCall.id, normalizedId);\n\t\t\t\t\t\t\tnormalizedToolCall = { ...normalizedToolCall, id: normalizedId };\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\treturn normalizedToolCall;\n\t\t\t\t}\n\n\t\t\t\treturn block;\n\t\t\t});\n\n\t\t\treturn {\n\t\t\t\t...assistantMsg,\n\t\t\t\tcontent: transformedContent,\n\t\t\t};\n\t\t}\n\t\treturn msg;\n\t});\n\n\t// Second pass: insert synthetic empty tool results for orphaned tool calls\n\t// This preserves thinking signatures and satisfies API requirements\n\tconst result: Message[] = [];\n\tlet pendingToolCalls: ToolCall[] = [];\n\tlet existingToolResultIds = new Set<string>();\n\n\tfor (let i = 0; i < transformed.length; i++) {\n\t\tconst msg = transformed[i];\n\n\t\tif (msg.role === \"assistant\") {\n\t\t\t// If we have pending orphaned tool calls from a previous assistant, insert synthetic results now\n\t\t\tif (pendingToolCalls.length > 0) {\n\t\t\t\tfor (const tc of pendingToolCalls) {\n\t\t\t\t\tif (!existingToolResultIds.has(tc.id)) {\n\t\t\t\t\t\tresult.push({\n\t\t\t\t\t\t\trole: \"toolResult\",\n\t\t\t\t\t\t\ttoolCallId: tc.id,\n\t\t\t\t\t\t\ttoolName: tc.name,\n\t\t\t\t\t\t\tcontent: [{ type: \"text\", text: \"No result provided\" }],\n\t\t\t\t\t\t\tisError: true,\n\t\t\t\t\t\t\ttimestamp: Date.now(),\n\t\t\t\t\t\t} as ToolResultMessage);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tpendingToolCalls = [];\n\t\t\t\texistingToolResultIds = new Set();\n\t\t\t}\n\n\t\t\t// Skip errored/aborted assistant messages entirely.\n\t\t\t// These are incomplete turns that shouldn't be replayed:\n\t\t\t// - May have partial content (reasoning without message, incomplete tool calls)\n\t\t\t// - Replaying them can cause API errors (e.g., OpenAI \"reasoning without following item\")\n\t\t\t// - The model should retry from the last valid state\n\t\t\tconst assistantMsg = msg as AssistantMessage;\n\t\t\tif (assistantMsg.stopReason === \"error\" || assistantMsg.stopReason === \"aborted\") {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t// Track tool calls from this assistant message\n\t\t\tconst toolCalls = assistantMsg.content.filter((b) => b.type === \"toolCall\") as ToolCall[];\n\t\t\tif (toolCalls.length > 0) {\n\t\t\t\tpendingToolCalls = toolCalls;\n\t\t\t\texistingToolResultIds = new Set();\n\t\t\t}\n\n\t\t\tresult.push(msg);\n\t\t} else if (msg.role === \"toolResult\") {\n\t\t\texistingToolResultIds.add(msg.toolCallId);\n\t\t\tresult.push(msg);\n\t\t} else if (msg.role === \"user\") {\n\t\t\t// User message interrupts tool flow - insert synthetic results for orphaned calls\n\t\t\tif (pendingToolCalls.length > 0) {\n\t\t\t\tfor (const tc of pendingToolCalls) {\n\t\t\t\t\tif (!existingToolResultIds.has(tc.id)) {\n\t\t\t\t\t\tresult.push({\n\t\t\t\t\t\t\trole: \"toolResult\",\n\t\t\t\t\t\t\ttoolCallId: tc.id,\n\t\t\t\t\t\t\ttoolName: tc.name,\n\t\t\t\t\t\t\tcontent: [{ type: \"text\", text: \"No result provided\" }],\n\t\t\t\t\t\t\tisError: true,\n\t\t\t\t\t\t\ttimestamp: Date.now(),\n\t\t\t\t\t\t} as ToolResultMessage);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tpendingToolCalls = [];\n\t\t\t\texistingToolResultIds = new Set();\n\t\t\t}\n\t\t\tresult.push(msg);\n\t\t} else {\n\t\t\tresult.push(msg);\n\t\t}\n\t}\n\n\treturn result;\n}\n"]}
1
+ {"version":3,"file":"transform-messages.js","sourceRoot":"","sources":["../../src/providers/transform-messages.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Api, AssistantMessage, Message, Model, ToolCall, ToolResultMessage } from \"../types.js\";\n\n/**\n * Report of context transformations during a cross-provider switch (ADR-005 Phase 3).\n * Tracks what was lost or downgraded when replaying conversation history to a different provider.\n */\nexport interface ProviderSwitchReport {\n\t/** API of the messages being transformed from */\n\tfromApi: string;\n\t/** API of the target model */\n\ttoApi: string;\n\t/** Number of thinking blocks completely dropped (redacted/encrypted, cross-model) */\n\tthinkingBlocksDropped: number;\n\t/** Number of thinking blocks downgraded from structured to plain text */\n\tthinkingBlocksDowngraded: number;\n\t/** Number of tool call IDs that were remapped/normalized */\n\ttoolCallIdsRemapped: number;\n\t/** Number of synthetic tool results inserted for orphaned tool calls */\n\tsyntheticToolResultsInserted: number;\n\t/** Number of thought signatures dropped (Google-specific opaque context) */\n\tthoughtSignaturesDropped: number;\n}\n\n/**\n * Create an empty provider switch report.\n */\nexport function createEmptyReport(fromApi: string, toApi: string): ProviderSwitchReport {\n\treturn {\n\t\tfromApi,\n\t\ttoApi,\n\t\tthinkingBlocksDropped: 0,\n\t\tthinkingBlocksDowngraded: 0,\n\t\ttoolCallIdsRemapped: 0,\n\t\tsyntheticToolResultsInserted: 0,\n\t\tthoughtSignaturesDropped: 0,\n\t};\n}\n\n/**\n * Check if a provider switch report has any non-zero transformations.\n */\nexport function hasTransformations(report: ProviderSwitchReport): boolean {\n\treturn (\n\t\treport.thinkingBlocksDropped > 0 ||\n\t\treport.thinkingBlocksDowngraded > 0 ||\n\t\treport.toolCallIdsRemapped > 0 ||\n\t\treport.syntheticToolResultsInserted > 0 ||\n\t\treport.thoughtSignaturesDropped > 0\n\t);\n}\n\n/**\n * Create a report, run transformMessages, and log if non-empty.\n * Convenience wrapper for provider adapters (ADR-005).\n */\nexport function transformMessagesWithReport<TApi extends Api>(\n\tmessages: Message[],\n\tmodel: Model<TApi>,\n\tnormalizeToolCallId?: (id: string, model: Model<TApi>, source: AssistantMessage) => string,\n\tsourceApi?: string,\n): Message[] {\n\tconst report = createEmptyReport(sourceApi ?? \"unknown\", model.api);\n\tconst result = transformMessages(messages, model, normalizeToolCallId, report);\n\tif (hasTransformations(report)) {\n\t\tlogProviderSwitchReport(report);\n\t}\n\treturn result;\n}\n\n/** Log a non-empty ProviderSwitchReport as a debug-level warning. */\nfunction logProviderSwitchReport(report: ProviderSwitchReport): void {\n\tconst parts: string[] = [`Provider switch ${report.fromApi} → ${report.toApi}:`];\n\tif (report.thinkingBlocksDropped > 0) parts.push(`${report.thinkingBlocksDropped} thinking blocks dropped`);\n\tif (report.thinkingBlocksDowngraded > 0) parts.push(`${report.thinkingBlocksDowngraded} thinking blocks downgraded`);\n\tif (report.toolCallIdsRemapped > 0) parts.push(`${report.toolCallIdsRemapped} tool call IDs remapped`);\n\tif (report.syntheticToolResultsInserted > 0) parts.push(`${report.syntheticToolResultsInserted} synthetic tool results inserted`);\n\tif (report.thoughtSignaturesDropped > 0) parts.push(`${report.thoughtSignaturesDropped} thought signatures dropped`);\n\t// Use process.stderr for debug output — this is observable in verbose/debug modes\n\t// without polluting stdout which may be used for structured output (RPC/MCP).\n\tif (process.env.GSD_VERBOSE === \"1\" || process.env.PI_VERBOSE === \"1\") {\n\t\tprocess.stderr.write(`[provider-switch] ${parts.join(\", \")}\\n`);\n\t}\n}\n\n/**\n * Normalize tool call ID for cross-provider compatibility.\n * OpenAI Responses API generates IDs that are 450+ chars with special characters like `|`.\n * Anthropic APIs require IDs matching ^[a-zA-Z0-9_-]+$ (max 64 chars).\n */\nexport function transformMessages<TApi extends Api>(\n\tmessages: Message[],\n\tmodel: Model<TApi>,\n\tnormalizeToolCallId?: (id: string, model: Model<TApi>, source: AssistantMessage) => string,\n\treport?: ProviderSwitchReport,\n): Message[] {\n\t// Build a map of original tool call IDs to normalized IDs\n\tconst toolCallIdMap = new Map<string, string>();\n\n\t// First pass: transform messages (thinking blocks, tool call ID normalization)\n\tconst transformed = messages.map((msg) => {\n\t\t// User messages pass through unchanged\n\t\tif (msg.role === \"user\") {\n\t\t\treturn msg;\n\t\t}\n\n\t\t// Handle toolResult messages - normalize toolCallId if we have a mapping\n\t\tif (msg.role === \"toolResult\") {\n\t\t\tconst normalizedId = toolCallIdMap.get(msg.toolCallId);\n\t\t\tif (normalizedId && normalizedId !== msg.toolCallId) {\n\t\t\t\treturn { ...msg, toolCallId: normalizedId };\n\t\t\t}\n\t\t\treturn msg;\n\t\t}\n\n\t\t// Assistant messages need transformation check\n\t\tif (msg.role === \"assistant\") {\n\t\t\tconst assistantMsg = msg as AssistantMessage;\n\t\t\tconst isSameModel =\n\t\t\t\tassistantMsg.provider === model.provider &&\n\t\t\t\tassistantMsg.api === model.api &&\n\t\t\t\tassistantMsg.model === model.id;\n\n\t\t\tconst transformedContent = assistantMsg.content.flatMap((block) => {\n\t\t\t\tif (block.type === \"thinking\") {\n\t\t\t\t\t// Redacted thinking is opaque encrypted content, only valid for the same model.\n\t\t\t\t\t// Drop it for cross-model to avoid API errors.\n\t\t\t\t\tif (block.redacted) {\n\t\t\t\t\t\tif (!isSameModel && report) report.thinkingBlocksDropped++;\n\t\t\t\t\t\treturn isSameModel ? block : [];\n\t\t\t\t\t}\n\t\t\t\t\t// For same model: keep thinking blocks with signatures (needed for replay)\n\t\t\t\t\t// even if the thinking text is empty (OpenAI encrypted reasoning)\n\t\t\t\t\tif (isSameModel && block.thinkingSignature) return block;\n\t\t\t\t\t// Skip empty thinking blocks, convert others to plain text\n\t\t\t\t\tif (!block.thinking || block.thinking.trim() === \"\") {\n\t\t\t\t\t\tif (!isSameModel && report) report.thinkingBlocksDropped++;\n\t\t\t\t\t\treturn [];\n\t\t\t\t\t}\n\t\t\t\t\tif (isSameModel) return block;\n\t\t\t\t\t// Downgrade: structured thinking → plain text\n\t\t\t\t\tif (report) report.thinkingBlocksDowngraded++;\n\t\t\t\t\treturn {\n\t\t\t\t\t\ttype: \"text\" as const,\n\t\t\t\t\t\ttext: block.thinking,\n\t\t\t\t\t};\n\t\t\t\t}\n\n\t\t\t\tif (block.type === \"text\") {\n\t\t\t\t\tif (isSameModel) return block;\n\t\t\t\t\treturn {\n\t\t\t\t\t\ttype: \"text\" as const,\n\t\t\t\t\t\ttext: block.text,\n\t\t\t\t\t};\n\t\t\t\t}\n\n\t\t\t\tif (block.type === \"toolCall\") {\n\t\t\t\t\tconst toolCall = block as ToolCall;\n\t\t\t\t\tlet normalizedToolCall: ToolCall = toolCall;\n\n\t\t\t\t\tif (!isSameModel && toolCall.thoughtSignature) {\n\t\t\t\t\t\tnormalizedToolCall = { ...toolCall };\n\t\t\t\t\t\tdelete (normalizedToolCall as { thoughtSignature?: string }).thoughtSignature;\n\t\t\t\t\t\tif (report) report.thoughtSignaturesDropped++;\n\t\t\t\t\t}\n\n\t\t\t\t\tif (!isSameModel && normalizeToolCallId) {\n\t\t\t\t\t\tconst normalizedId = normalizeToolCallId(toolCall.id, model, assistantMsg);\n\t\t\t\t\t\tif (normalizedId !== toolCall.id) {\n\t\t\t\t\t\t\ttoolCallIdMap.set(toolCall.id, normalizedId);\n\t\t\t\t\t\t\tnormalizedToolCall = { ...normalizedToolCall, id: normalizedId };\n\t\t\t\t\t\t\tif (report) report.toolCallIdsRemapped++;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\treturn normalizedToolCall;\n\t\t\t\t}\n\n\t\t\t\treturn block;\n\t\t\t});\n\n\t\t\treturn {\n\t\t\t\t...assistantMsg,\n\t\t\t\tcontent: transformedContent,\n\t\t\t};\n\t\t}\n\t\treturn msg;\n\t});\n\n\t// Second pass: insert synthetic empty tool results for orphaned tool calls\n\t// This preserves thinking signatures and satisfies API requirements\n\tconst result: Message[] = [];\n\tlet pendingToolCalls: ToolCall[] = [];\n\tlet existingToolResultIds = new Set<string>();\n\n\tfor (let i = 0; i < transformed.length; i++) {\n\t\tconst msg = transformed[i];\n\n\t\tif (msg.role === \"assistant\") {\n\t\t\t// If we have pending orphaned tool calls from a previous assistant, insert synthetic results now\n\t\t\tif (pendingToolCalls.length > 0) {\n\t\t\t\tfor (const tc of pendingToolCalls) {\n\t\t\t\t\tif (!existingToolResultIds.has(tc.id)) {\n\t\t\t\t\t\tresult.push({\n\t\t\t\t\t\t\trole: \"toolResult\",\n\t\t\t\t\t\t\ttoolCallId: tc.id,\n\t\t\t\t\t\t\ttoolName: tc.name,\n\t\t\t\t\t\t\tcontent: [{ type: \"text\", text: \"No result provided\" }],\n\t\t\t\t\t\t\tisError: true,\n\t\t\t\t\t\t\ttimestamp: Date.now(),\n\t\t\t\t\t\t} as ToolResultMessage);\n\t\t\t\t\t\tif (report) report.syntheticToolResultsInserted++;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tpendingToolCalls = [];\n\t\t\t\texistingToolResultIds = new Set();\n\t\t\t}\n\n\t\t\t// Skip errored/aborted assistant messages entirely.\n\t\t\t// These are incomplete turns that shouldn't be replayed:\n\t\t\t// - May have partial content (reasoning without message, incomplete tool calls)\n\t\t\t// - Replaying them can cause API errors (e.g., OpenAI \"reasoning without following item\")\n\t\t\t// - The model should retry from the last valid state\n\t\t\tconst assistantMsg = msg as AssistantMessage;\n\t\t\tif (assistantMsg.stopReason === \"error\" || assistantMsg.stopReason === \"aborted\") {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t// Track tool calls from this assistant message\n\t\t\tconst toolCalls = assistantMsg.content.filter((b) => b.type === \"toolCall\") as ToolCall[];\n\t\t\tif (toolCalls.length > 0) {\n\t\t\t\tpendingToolCalls = toolCalls;\n\t\t\t\texistingToolResultIds = new Set();\n\t\t\t}\n\n\t\t\tresult.push(msg);\n\t\t} else if (msg.role === \"toolResult\") {\n\t\t\texistingToolResultIds.add(msg.toolCallId);\n\t\t\tresult.push(msg);\n\t\t} else if (msg.role === \"user\") {\n\t\t\t// User message interrupts tool flow - insert synthetic results for orphaned calls\n\t\t\tif (pendingToolCalls.length > 0) {\n\t\t\t\tfor (const tc of pendingToolCalls) {\n\t\t\t\t\tif (!existingToolResultIds.has(tc.id)) {\n\t\t\t\t\t\tresult.push({\n\t\t\t\t\t\t\trole: \"toolResult\",\n\t\t\t\t\t\t\ttoolCallId: tc.id,\n\t\t\t\t\t\t\ttoolName: tc.name,\n\t\t\t\t\t\t\tcontent: [{ type: \"text\", text: \"No result provided\" }],\n\t\t\t\t\t\t\tisError: true,\n\t\t\t\t\t\t\ttimestamp: Date.now(),\n\t\t\t\t\t\t} as ToolResultMessage);\n\t\t\t\t\t\tif (report) report.syntheticToolResultsInserted++;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tpendingToolCalls = [];\n\t\t\t\texistingToolResultIds = new Set();\n\t\t\t}\n\t\t\tresult.push(msg);\n\t\t} else {\n\t\t\tresult.push(msg);\n\t\t}\n\t}\n\n\treturn result;\n}\n"]}
@@ -3,12 +3,13 @@
3
3
  *
4
4
  * This module handles login, token refresh, and credential storage
5
5
  * for OAuth-based providers:
6
- * - Anthropic (Claude Pro/Max)
7
6
  * - GitHub Copilot
8
7
  * - Google Cloud Code Assist (Gemini CLI)
9
8
  * - Antigravity (Gemini 3, Claude, GPT-OSS via Google Cloud)
9
+ *
10
+ * Note: Anthropic OAuth was removed per TOS compliance (see docs/user-docs/claude-code-auth-compliance.md).
11
+ * Use API keys or the local Claude Code CLI for Anthropic access.
10
12
  */
11
- export { anthropicOAuthProvider, loginAnthropic, refreshAnthropicToken } from "./anthropic.js";
12
13
  export { getGitHubCopilotBaseUrl, githubCopilotOAuthProvider, loginGitHubCopilot, normalizeDomain, refreshGitHubCopilotToken, } from "./github-copilot.js";
13
14
  export { antigravityOAuthProvider, loginAntigravity, refreshAntigravityToken } from "./google-antigravity.js";
14
15
  export { geminiCliOAuthProvider, loginGeminiCli, refreshGoogleCloudToken } from "./google-gemini-cli.js";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/utils/oauth/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAGH,OAAO,EAAE,sBAAsB,EAAE,cAAc,EAAE,qBAAqB,EAAE,MAAM,gBAAgB,CAAC;AAE/F,OAAO,EACN,uBAAuB,EACvB,0BAA0B,EAC1B,kBAAkB,EAClB,eAAe,EACf,yBAAyB,GACzB,MAAM,qBAAqB,CAAC;AAE7B,OAAO,EAAE,wBAAwB,EAAE,gBAAgB,EAAE,uBAAuB,EAAE,MAAM,yBAAyB,CAAC;AAE9G,OAAO,EAAE,sBAAsB,EAAE,cAAc,EAAE,uBAAuB,EAAE,MAAM,wBAAwB,CAAC;AAEzG,OAAO,EAAE,gBAAgB,EAAE,wBAAwB,EAAE,uBAAuB,EAAE,MAAM,mBAAmB,CAAC;AAExG,cAAc,YAAY,CAAC;AAW3B,OAAO,KAAK,EAAE,gBAAgB,EAAE,eAAe,EAAE,sBAAsB,EAAE,MAAM,YAAY,CAAC;AAc5F;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,EAAE,EAAE,eAAe,GAAG,sBAAsB,GAAG,SAAS,CAExF;AAED;;GAEG;AACH,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,sBAAsB,GAAG,IAAI,CAE5E;AAED;;;;;GAKG;AACH,wBAAgB,uBAAuB,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,CAOxD;AAED;;GAEG;AACH,wBAAgB,mBAAmB,IAAI,IAAI,CAK1C;AAED;;GAEG;AACH,wBAAgB,iBAAiB,IAAI,sBAAsB,EAAE,CAE5D;AAMD;;;;;;GAMG;AACH,wBAAsB,cAAc,CACnC,UAAU,EAAE,eAAe,EAC3B,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,GAC3C,OAAO,CAAC;IAAE,cAAc,EAAE,gBAAgB,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAAC,CAsBtE"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/utils/oauth/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAGH,OAAO,EACN,uBAAuB,EACvB,0BAA0B,EAC1B,kBAAkB,EAClB,eAAe,EACf,yBAAyB,GACzB,MAAM,qBAAqB,CAAC;AAE7B,OAAO,EAAE,wBAAwB,EAAE,gBAAgB,EAAE,uBAAuB,EAAE,MAAM,yBAAyB,CAAC;AAE9G,OAAO,EAAE,sBAAsB,EAAE,cAAc,EAAE,uBAAuB,EAAE,MAAM,wBAAwB,CAAC;AAEzG,OAAO,EAAE,gBAAgB,EAAE,wBAAwB,EAAE,uBAAuB,EAAE,MAAM,mBAAmB,CAAC;AAExG,cAAc,YAAY,CAAC;AAU3B,OAAO,KAAK,EAAE,gBAAgB,EAAE,eAAe,EAAE,sBAAsB,EAAE,MAAM,YAAY,CAAC;AAa5F;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,EAAE,EAAE,eAAe,GAAG,sBAAsB,GAAG,SAAS,CAExF;AAED;;GAEG;AACH,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,sBAAsB,GAAG,IAAI,CAE5E;AAED;;;;;GAKG;AACH,wBAAgB,uBAAuB,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,CAOxD;AAED;;GAEG;AACH,wBAAgB,mBAAmB,IAAI,IAAI,CAK1C;AAED;;GAEG;AACH,wBAAgB,iBAAiB,IAAI,sBAAsB,EAAE,CAE5D;AAMD;;;;;;GAMG;AACH,wBAAsB,cAAc,CACnC,UAAU,EAAE,eAAe,EAC3B,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,GAC3C,OAAO,CAAC;IAAE,cAAc,EAAE,gBAAgB,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAAC,CAsBtE"}
@@ -3,13 +3,13 @@
3
3
  *
4
4
  * This module handles login, token refresh, and credential storage
5
5
  * for OAuth-based providers:
6
- * - Anthropic (Claude Pro/Max)
7
6
  * - GitHub Copilot
8
7
  * - Google Cloud Code Assist (Gemini CLI)
9
8
  * - Antigravity (Gemini 3, Claude, GPT-OSS via Google Cloud)
9
+ *
10
+ * Note: Anthropic OAuth was removed per TOS compliance (see docs/user-docs/claude-code-auth-compliance.md).
11
+ * Use API keys or the local Claude Code CLI for Anthropic access.
10
12
  */
11
- // Anthropic
12
- export { anthropicOAuthProvider, loginAnthropic, refreshAnthropicToken } from "./anthropic.js";
13
13
  // GitHub Copilot
14
14
  export { getGitHubCopilotBaseUrl, githubCopilotOAuthProvider, loginGitHubCopilot, normalizeDomain, refreshGitHubCopilotToken, } from "./github-copilot.js";
15
15
  // Google Antigravity
@@ -22,13 +22,11 @@ export * from "./types.js";
22
22
  // ============================================================================
23
23
  // Provider Registry
24
24
  // ============================================================================
25
- import { anthropicOAuthProvider } from "./anthropic.js";
26
25
  import { githubCopilotOAuthProvider } from "./github-copilot.js";
27
26
  import { antigravityOAuthProvider } from "./google-antigravity.js";
28
27
  import { geminiCliOAuthProvider } from "./google-gemini-cli.js";
29
28
  import { openaiCodexOAuthProvider } from "./openai-codex.js";
30
29
  const BUILT_IN_OAUTH_PROVIDERS = [
31
- anthropicOAuthProvider,
32
30
  githubCopilotOAuthProvider,
33
31
  geminiCliOAuthProvider,
34
32
  antigravityOAuthProvider,
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/utils/oauth/index.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * OAuth credential management for AI providers.\n *\n * This module handles login, token refresh, and credential storage\n * for OAuth-based providers:\n * - Anthropic (Claude Pro/Max)\n * - GitHub Copilot\n * - Google Cloud Code Assist (Gemini CLI)\n * - Antigravity (Gemini 3, Claude, GPT-OSS via Google Cloud)\n */\n\n// Anthropic\nexport { anthropicOAuthProvider, loginAnthropic, refreshAnthropicToken } from \"./anthropic.js\";\n// GitHub Copilot\nexport {\n\tgetGitHubCopilotBaseUrl,\n\tgithubCopilotOAuthProvider,\n\tloginGitHubCopilot,\n\tnormalizeDomain,\n\trefreshGitHubCopilotToken,\n} from \"./github-copilot.js\";\n// Google Antigravity\nexport { antigravityOAuthProvider, loginAntigravity, refreshAntigravityToken } from \"./google-antigravity.js\";\n// Google Gemini CLI\nexport { geminiCliOAuthProvider, loginGeminiCli, refreshGoogleCloudToken } from \"./google-gemini-cli.js\";\n// OpenAI Codex (ChatGPT OAuth)\nexport { loginOpenAICodex, openaiCodexOAuthProvider, refreshOpenAICodexToken } from \"./openai-codex.js\";\n\nexport * from \"./types.js\";\n\n// ============================================================================\n// Provider Registry\n// ============================================================================\n\nimport { anthropicOAuthProvider } from \"./anthropic.js\";\nimport { githubCopilotOAuthProvider } from \"./github-copilot.js\";\nimport { antigravityOAuthProvider } from \"./google-antigravity.js\";\nimport { geminiCliOAuthProvider } from \"./google-gemini-cli.js\";\nimport { openaiCodexOAuthProvider } from \"./openai-codex.js\";\nimport type { OAuthCredentials, OAuthProviderId, OAuthProviderInterface } from \"./types.js\";\n\nconst BUILT_IN_OAUTH_PROVIDERS: OAuthProviderInterface[] = [\n\tanthropicOAuthProvider,\n\tgithubCopilotOAuthProvider,\n\tgeminiCliOAuthProvider,\n\tantigravityOAuthProvider,\n\topenaiCodexOAuthProvider,\n];\n\nconst oauthProviderRegistry = new Map<string, OAuthProviderInterface>(\n\tBUILT_IN_OAUTH_PROVIDERS.map((provider) => [provider.id, provider]),\n);\n\n/**\n * Get an OAuth provider by ID\n */\nexport function getOAuthProvider(id: OAuthProviderId): OAuthProviderInterface | undefined {\n\treturn oauthProviderRegistry.get(id);\n}\n\n/**\n * Register a custom OAuth provider\n */\nexport function registerOAuthProvider(provider: OAuthProviderInterface): void {\n\toauthProviderRegistry.set(provider.id, provider);\n}\n\n/**\n * Unregister an OAuth provider.\n *\n * If the provider is built-in, restores the built-in implementation.\n * Custom providers are removed completely.\n */\nexport function unregisterOAuthProvider(id: string): void {\n\tconst builtInProvider = BUILT_IN_OAUTH_PROVIDERS.find((provider) => provider.id === id);\n\tif (builtInProvider) {\n\t\toauthProviderRegistry.set(id, builtInProvider);\n\t\treturn;\n\t}\n\toauthProviderRegistry.delete(id);\n}\n\n/**\n * Reset OAuth providers to built-ins.\n */\nexport function resetOAuthProviders(): void {\n\toauthProviderRegistry.clear();\n\tfor (const provider of BUILT_IN_OAUTH_PROVIDERS) {\n\t\toauthProviderRegistry.set(provider.id, provider);\n\t}\n}\n\n/**\n * Get all registered OAuth providers\n */\nexport function getOAuthProviders(): OAuthProviderInterface[] {\n\treturn Array.from(oauthProviderRegistry.values());\n}\n\n// ============================================================================\n// High-level API (uses provider registry)\n// ============================================================================\n\n/**\n * Get API key for a provider from OAuth credentials.\n * Automatically refreshes expired tokens.\n *\n * @returns API key string and updated credentials, or null if no credentials\n * @throws Error if refresh fails\n */\nexport async function getOAuthApiKey(\n\tproviderId: OAuthProviderId,\n\tcredentials: Record<string, OAuthCredentials>,\n): Promise<{ newCredentials: OAuthCredentials; apiKey: string } | null> {\n\tconst provider = getOAuthProvider(providerId);\n\tif (!provider) {\n\t\tthrow new Error(`Unknown OAuth provider: ${providerId}`);\n\t}\n\n\tlet creds = credentials[providerId];\n\tif (!creds) {\n\t\treturn null;\n\t}\n\n\t// Refresh if expired\n\tif (Date.now() >= creds.expires) {\n\t\ttry {\n\t\t\tcreds = await provider.refreshToken(creds);\n\t\t} catch (error) {\n\t\t\tthrow new Error(`Failed to refresh OAuth token for ${providerId}`, { cause: error });\n\t\t}\n\t}\n\n\tconst apiKey = provider.getApiKey(creds);\n\treturn { newCredentials: creds, apiKey };\n}\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/utils/oauth/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,iBAAiB;AACjB,OAAO,EACN,uBAAuB,EACvB,0BAA0B,EAC1B,kBAAkB,EAClB,eAAe,EACf,yBAAyB,GACzB,MAAM,qBAAqB,CAAC;AAC7B,qBAAqB;AACrB,OAAO,EAAE,wBAAwB,EAAE,gBAAgB,EAAE,uBAAuB,EAAE,MAAM,yBAAyB,CAAC;AAC9G,oBAAoB;AACpB,OAAO,EAAE,sBAAsB,EAAE,cAAc,EAAE,uBAAuB,EAAE,MAAM,wBAAwB,CAAC;AACzG,+BAA+B;AAC/B,OAAO,EAAE,gBAAgB,EAAE,wBAAwB,EAAE,uBAAuB,EAAE,MAAM,mBAAmB,CAAC;AAExG,cAAc,YAAY,CAAC;AAE3B,+EAA+E;AAC/E,oBAAoB;AACpB,+EAA+E;AAE/E,OAAO,EAAE,0BAA0B,EAAE,MAAM,qBAAqB,CAAC;AACjE,OAAO,EAAE,wBAAwB,EAAE,MAAM,yBAAyB,CAAC;AACnE,OAAO,EAAE,sBAAsB,EAAE,MAAM,wBAAwB,CAAC;AAChE,OAAO,EAAE,wBAAwB,EAAE,MAAM,mBAAmB,CAAC;AAG7D,MAAM,wBAAwB,GAA6B;IAC1D,0BAA0B;IAC1B,sBAAsB;IACtB,wBAAwB;IACxB,wBAAwB;CACxB,CAAC;AAEF,MAAM,qBAAqB,GAAG,IAAI,GAAG,CACpC,wBAAwB,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC,QAAQ,CAAC,EAAE,EAAE,QAAQ,CAAC,CAAC,CACnE,CAAC;AAEF;;GAEG;AACH,MAAM,UAAU,gBAAgB,CAAC,EAAmB;IACnD,OAAO,qBAAqB,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;AACtC,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,qBAAqB,CAAC,QAAgC;IACrE,qBAAqB,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,EAAE,QAAQ,CAAC,CAAC;AAClD,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,uBAAuB,CAAC,EAAU;IACjD,MAAM,eAAe,GAAG,wBAAwB,CAAC,IAAI,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,QAAQ,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC;IACxF,IAAI,eAAe,EAAE,CAAC;QACrB,qBAAqB,CAAC,GAAG,CAAC,EAAE,EAAE,eAAe,CAAC,CAAC;QAC/C,OAAO;IACR,CAAC;IACD,qBAAqB,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;AAClC,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,mBAAmB;IAClC,qBAAqB,CAAC,KAAK,EAAE,CAAC;IAC9B,KAAK,MAAM,QAAQ,IAAI,wBAAwB,EAAE,CAAC;QACjD,qBAAqB,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,EAAE,QAAQ,CAAC,CAAC;IAClD,CAAC;AACF,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,iBAAiB;IAChC,OAAO,KAAK,CAAC,IAAI,CAAC,qBAAqB,CAAC,MAAM,EAAE,CAAC,CAAC;AACnD,CAAC;AAED,+EAA+E;AAC/E,0CAA0C;AAC1C,+EAA+E;AAE/E;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc,CACnC,UAA2B,EAC3B,WAA6C;IAE7C,MAAM,QAAQ,GAAG,gBAAgB,CAAC,UAAU,CAAC,CAAC;IAC9C,IAAI,CAAC,QAAQ,EAAE,CAAC;QACf,MAAM,IAAI,KAAK,CAAC,2BAA2B,UAAU,EAAE,CAAC,CAAC;IAC1D,CAAC;IAED,IAAI,KAAK,GAAG,WAAW,CAAC,UAAU,CAAC,CAAC;IACpC,IAAI,CAAC,KAAK,EAAE,CAAC;QACZ,OAAO,IAAI,CAAC;IACb,CAAC;IAED,qBAAqB;IACrB,IAAI,IAAI,CAAC,GAAG,EAAE,IAAI,KAAK,CAAC,OAAO,EAAE,CAAC;QACjC,IAAI,CAAC;YACJ,KAAK,GAAG,MAAM,QAAQ,CAAC,YAAY,CAAC,KAAK,CAAC,CAAC;QAC5C,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YAChB,MAAM,IAAI,KAAK,CAAC,qCAAqC,UAAU,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,CAAC;QACtF,CAAC;IACF,CAAC;IAED,MAAM,MAAM,GAAG,QAAQ,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;IACzC,OAAO,EAAE,cAAc,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC;AAC1C,CAAC","sourcesContent":["/**\n * OAuth credential management for AI providers.\n *\n * This module handles login, token refresh, and credential storage\n * for OAuth-based providers:\n * - GitHub Copilot\n * - Google Cloud Code Assist (Gemini CLI)\n * - Antigravity (Gemini 3, Claude, GPT-OSS via Google Cloud)\n *\n * Note: Anthropic OAuth was removed per TOS compliance (see docs/user-docs/claude-code-auth-compliance.md).\n * Use API keys or the local Claude Code CLI for Anthropic access.\n */\n\n// GitHub Copilot\nexport {\n\tgetGitHubCopilotBaseUrl,\n\tgithubCopilotOAuthProvider,\n\tloginGitHubCopilot,\n\tnormalizeDomain,\n\trefreshGitHubCopilotToken,\n} from \"./github-copilot.js\";\n// Google Antigravity\nexport { antigravityOAuthProvider, loginAntigravity, refreshAntigravityToken } from \"./google-antigravity.js\";\n// Google Gemini CLI\nexport { geminiCliOAuthProvider, loginGeminiCli, refreshGoogleCloudToken } from \"./google-gemini-cli.js\";\n// OpenAI Codex (ChatGPT OAuth)\nexport { loginOpenAICodex, openaiCodexOAuthProvider, refreshOpenAICodexToken } from \"./openai-codex.js\";\n\nexport * from \"./types.js\";\n\n// ============================================================================\n// Provider Registry\n// ============================================================================\n\nimport { githubCopilotOAuthProvider } from \"./github-copilot.js\";\nimport { antigravityOAuthProvider } from \"./google-antigravity.js\";\nimport { geminiCliOAuthProvider } from \"./google-gemini-cli.js\";\nimport { openaiCodexOAuthProvider } from \"./openai-codex.js\";\nimport type { OAuthCredentials, OAuthProviderId, OAuthProviderInterface } from \"./types.js\";\n\nconst BUILT_IN_OAUTH_PROVIDERS: OAuthProviderInterface[] = [\n\tgithubCopilotOAuthProvider,\n\tgeminiCliOAuthProvider,\n\tantigravityOAuthProvider,\n\topenaiCodexOAuthProvider,\n];\n\nconst oauthProviderRegistry = new Map<string, OAuthProviderInterface>(\n\tBUILT_IN_OAUTH_PROVIDERS.map((provider) => [provider.id, provider]),\n);\n\n/**\n * Get an OAuth provider by ID\n */\nexport function getOAuthProvider(id: OAuthProviderId): OAuthProviderInterface | undefined {\n\treturn oauthProviderRegistry.get(id);\n}\n\n/**\n * Register a custom OAuth provider\n */\nexport function registerOAuthProvider(provider: OAuthProviderInterface): void {\n\toauthProviderRegistry.set(provider.id, provider);\n}\n\n/**\n * Unregister an OAuth provider.\n *\n * If the provider is built-in, restores the built-in implementation.\n * Custom providers are removed completely.\n */\nexport function unregisterOAuthProvider(id: string): void {\n\tconst builtInProvider = BUILT_IN_OAUTH_PROVIDERS.find((provider) => provider.id === id);\n\tif (builtInProvider) {\n\t\toauthProviderRegistry.set(id, builtInProvider);\n\t\treturn;\n\t}\n\toauthProviderRegistry.delete(id);\n}\n\n/**\n * Reset OAuth providers to built-ins.\n */\nexport function resetOAuthProviders(): void {\n\toauthProviderRegistry.clear();\n\tfor (const provider of BUILT_IN_OAUTH_PROVIDERS) {\n\t\toauthProviderRegistry.set(provider.id, provider);\n\t}\n}\n\n/**\n * Get all registered OAuth providers\n */\nexport function getOAuthProviders(): OAuthProviderInterface[] {\n\treturn Array.from(oauthProviderRegistry.values());\n}\n\n// ============================================================================\n// High-level API (uses provider registry)\n// ============================================================================\n\n/**\n * Get API key for a provider from OAuth credentials.\n * Automatically refreshes expired tokens.\n *\n * @returns API key string and updated credentials, or null if no credentials\n * @throws Error if refresh fails\n */\nexport async function getOAuthApiKey(\n\tproviderId: OAuthProviderId,\n\tcredentials: Record<string, OAuthCredentials>,\n): Promise<{ newCredentials: OAuthCredentials; apiKey: string } | null> {\n\tconst provider = getOAuthProvider(providerId);\n\tif (!provider) {\n\t\tthrow new Error(`Unknown OAuth provider: ${providerId}`);\n\t}\n\n\tlet creds = credentials[providerId];\n\tif (!creds) {\n\t\treturn null;\n\t}\n\n\t// Refresh if expired\n\tif (Date.now() >= creds.expires) {\n\t\ttry {\n\t\t\tcreds = await provider.refreshToken(creds);\n\t\t} catch (error) {\n\t\t\tthrow new Error(`Failed to refresh OAuth token for ${providerId}`, { cause: error });\n\t\t}\n\t}\n\n\tconst apiKey = provider.getApiKey(creds);\n\treturn { newCredentials: creds, apiKey };\n}\n"]}
@@ -12,7 +12,10 @@ export * from "./providers/google-vertex.js";
12
12
  export * from "./providers/mistral.js";
13
13
  export * from "./providers/openai-completions.js";
14
14
  export * from "./providers/openai-responses.js";
15
+ export * from "./providers/provider-capabilities.js";
15
16
  export * from "./providers/register-builtins.js";
17
+ export type { ProviderSwitchReport } from "./providers/transform-messages.js";
18
+ export { createEmptyReport, hasTransformations, transformMessagesWithReport } from "./providers/transform-messages.js";
16
19
  export * from "./stream.js";
17
20
  export * from "./types.js";
18
21
  export * from "./utils/event-stream.js";
@@ -43,7 +43,7 @@ import { AssistantMessageEventStream } from "../utils/event-stream.js";
43
43
  import { parseStreamingJson } from "../utils/json-parse.js";
44
44
  import { sanitizeSurrogates } from "../utils/sanitize-unicode.js";
45
45
  import { adjustMaxTokensForThinking, buildBaseOptions, clampReasoning } from "./simple-options.js";
46
- import { transformMessages } from "./transform-messages.js";
46
+ import { transformMessagesWithReport } from "./transform-messages.js";
47
47
 
48
48
  export interface BedrockOptions extends StreamOptions {
49
49
  region?: string;
@@ -487,7 +487,7 @@ function convertMessages(
487
487
  cacheRetention: CacheRetention,
488
488
  ): Message[] {
489
489
  const result: Message[] = [];
490
- const transformedMessages = transformMessages(context.messages, model, normalizeToolCallId);
490
+ const transformedMessages = transformMessagesWithReport(context.messages, model, normalizeToolCallId, "bedrock-converse-stream");
491
491
 
492
492
  for (let i = 0; i < transformedMessages.length; i++) {
493
493
  const m = transformedMessages[i];
@@ -33,7 +33,7 @@ import type { AssistantMessageEventStream } from "../utils/event-stream.js";
33
33
  import { parseStreamingJson } from "../utils/json-parse.js";
34
34
  import { hasXmlParameterTags, repairToolJson } from "../utils/repair-tool-json.js";
35
35
  import { sanitizeSurrogates } from "../utils/sanitize-unicode.js";
36
- import { transformMessages } from "./transform-messages.js";
36
+ import { transformMessagesWithReport } from "./transform-messages.js";
37
37
 
38
38
  export type AnthropicEffort = "low" | "medium" | "high" | "max";
39
39
 
@@ -235,7 +235,7 @@ export function convertMessages(
235
235
  ): MessageParam[] {
236
236
  const params: MessageParam[] = [];
237
237
 
238
- const transformedMessages = transformMessages(messages, model, normalizeToolCallId);
238
+ const transformedMessages = transformMessagesWithReport(messages, model, normalizeToolCallId, "anthropic-messages");
239
239
 
240
240
  for (let i = 0; i < transformedMessages.length; i++) {
241
241
  const msg = transformedMessages[i];
@@ -34,9 +34,6 @@ async function getAnthropicClass(): Promise<typeof Anthropic> {
34
34
  return _AnthropicClass;
35
35
  }
36
36
 
37
- // Stealth mode: Mimic Claude Code's tool naming exactly
38
- const claudeCodeVersion = "2.1.62";
39
-
40
37
  function mergeHeaders(...headerSources: (Record<string, string> | undefined)[]): Record<string, string> {
41
38
  const merged: Record<string, string> = {};
42
39
  for (const headers of headerSources) {
@@ -47,10 +44,6 @@ function mergeHeaders(...headerSources: (Record<string, string> | undefined)[]):
47
44
  return merged;
48
45
  }
49
46
 
50
- function isOAuthToken(apiKey: string): boolean {
51
- return apiKey.includes("sk-ant-oat");
52
- }
53
-
54
47
  async function createClient(
55
48
  model: Model<"anthropic-messages">,
56
49
  apiKey: string,
@@ -97,30 +90,7 @@ async function createClient(
97
90
  betaFeatures.push("interleaved-thinking-2025-05-14");
98
91
  }
99
92
 
100
- // OAuth: Bearer auth, Claude Code identity headers
101
- if (isOAuthToken(apiKey)) {
102
- const client = new AnthropicClass({
103
- apiKey: null,
104
- authToken: apiKey,
105
- baseURL: model.baseUrl,
106
- dangerouslyAllowBrowser: true,
107
- defaultHeaders: mergeHeaders(
108
- {
109
- accept: "application/json",
110
- "anthropic-dangerous-direct-browser-access": "true",
111
- ...(betaFeatures.length > 0 ? { "anthropic-beta": `claude-code-20250219,oauth-2025-04-20,${betaFeatures.join(",")}` } : {}),
112
- "user-agent": `claude-cli/${claudeCodeVersion}`,
113
- "x-app": "cli",
114
- },
115
- model.headers,
116
- optionsHeaders,
117
- ),
118
- });
119
-
120
- return { client, isOAuthToken: true };
121
- }
122
-
123
- // API key auth
93
+ // API key auth (Anthropic OAuth removed per TOS compliance — use API keys or Claude CLI)
124
94
  // Alibaba Coding Plan uses Bearer token auth instead of x-api-key
125
95
  const isAlibabaProvider = model.provider === "alibaba-coding-plan";
126
96
  const client = new AnthropicClass({
@@ -5,7 +5,7 @@
5
5
  import { type Content, FinishReason, FunctionCallingConfigMode, type Part } from "@google/genai";
6
6
  import type { Context, ImageContent, Model, StopReason, TextContent, Tool } from "../types.js";
7
7
  import { sanitizeSurrogates } from "../utils/sanitize-unicode.js";
8
- import { transformMessages } from "./transform-messages.js";
8
+ import { transformMessagesWithReport } from "./transform-messages.js";
9
9
 
10
10
  type GoogleApiType = "google-generative-ai" | "google-gemini-cli" | "google-vertex";
11
11
 
@@ -80,7 +80,7 @@ export function convertMessages<T extends GoogleApiType>(model: Model<T>, contex
80
80
  return id.replace(/[^a-zA-Z0-9_-]/g, "_").slice(0, 64);
81
81
  };
82
82
 
83
- const transformedMessages = transformMessages(context.messages, model, normalizeToolCallId);
83
+ const transformedMessages = transformMessagesWithReport(context.messages, model, normalizeToolCallId, "google-generative-ai");
84
84
 
85
85
  for (const msg of transformedMessages) {
86
86
  if (msg.role === "user") {
@@ -39,7 +39,7 @@ import { shortHash } from "../utils/hash.js";
39
39
  import { parseStreamingJson } from "../utils/json-parse.js";
40
40
  import { sanitizeSurrogates } from "../utils/sanitize-unicode.js";
41
41
  import { buildBaseOptions, clampReasoning } from "./simple-options.js";
42
- import { transformMessages } from "./transform-messages.js";
42
+ import { transformMessagesWithReport } from "./transform-messages.js";
43
43
 
44
44
  const MISTRAL_TOOL_CALL_ID_LENGTH = 9;
45
45
  const MAX_MISTRAL_ERROR_BODY_CHARS = 4000;
@@ -79,7 +79,7 @@ export const streamMistral: StreamFunction<"mistral-conversations", MistralOptio
79
79
  });
80
80
 
81
81
  const normalizeMistralToolCallId = createMistralToolCallIdNormalizer();
82
- const transformedMessages = transformMessages(context.messages, model, (id) => normalizeMistralToolCallId(id));
82
+ const transformedMessages = transformMessagesWithReport(context.messages, model, (id) => normalizeMistralToolCallId(id), "mistral-conversations");
83
83
 
84
84
  let payload = buildChatPayload(model, context, transformedMessages, options);
85
85
  const nextPayload = await options?.onPayload?.(payload, model);
@@ -39,7 +39,7 @@ import {
39
39
  finalizeStream,
40
40
  handleStreamError,
41
41
  } from "./openai-shared.js";
42
- import { transformMessages } from "./transform-messages.js";
42
+ import { transformMessagesWithReport } from "./transform-messages.js";
43
43
 
44
44
  /**
45
45
  * Check if conversation messages contain tool calls or tool results.
@@ -441,7 +441,7 @@ export function convertMessages(
441
441
  return id;
442
442
  };
443
443
 
444
- const transformedMessages = transformMessages(context.messages, model, (id) => normalizeToolCallId(id));
444
+ const transformedMessages = transformMessagesWithReport(context.messages, model, (id) => normalizeToolCallId(id), "openai-completions");
445
445
 
446
446
  if (context.systemPrompt) {
447
447
  const useDeveloperRole = model.reasoning && compat.supportsDeveloperRole;
@@ -30,7 +30,7 @@ import type { AssistantMessageEventStream } from "../utils/event-stream.js";
30
30
  import { shortHash } from "../utils/hash.js";
31
31
  import { parseStreamingJson } from "../utils/json-parse.js";
32
32
  import { sanitizeSurrogates } from "../utils/sanitize-unicode.js";
33
- import { transformMessages } from "./transform-messages.js";
33
+ import { transformMessagesWithReport } from "./transform-messages.js";
34
34
 
35
35
  // =============================================================================
36
36
  // Utilities
@@ -108,7 +108,7 @@ export function convertResponsesMessages<TApi extends Api>(
108
108
  return `${normalizedCallId}|${normalizedItemId}`;
109
109
  };
110
110
 
111
- const transformedMessages = transformMessages(context.messages, model, normalizeToolCallId);
111
+ const transformedMessages = transformMessagesWithReport(context.messages, model, normalizeToolCallId, "openai-responses");
112
112
 
113
113
  const includeSystemPrompt = options?.includeSystemPrompt ?? true;
114
114
  if (includeSystemPrompt && context.systemPrompt) {
@@ -0,0 +1,174 @@
1
+ // GSD-2 — Provider Capabilities Registry Tests (ADR-005 Phase 1)
2
+ import { describe, test } from "node:test";
3
+ import assert from "node:assert/strict";
4
+
5
+ import {
6
+ PROVIDER_CAPABILITIES,
7
+ getProviderCapabilities,
8
+ getUnsupportedFeatures,
9
+ mergeCapabilityOverrides,
10
+ getRegisteredApis,
11
+ } from "./provider-capabilities.js";
12
+
13
+ // ─── Registry Completeness ──────────────────────────────────────────────────
14
+
15
+ describe("PROVIDER_CAPABILITIES registry", () => {
16
+ const EXPECTED_APIS = [
17
+ "anthropic-messages",
18
+ "anthropic-vertex",
19
+ "openai-responses",
20
+ "azure-openai-responses",
21
+ "openai-codex-responses",
22
+ "openai-completions",
23
+ "google-generative-ai",
24
+ "google-gemini-cli",
25
+ "google-vertex",
26
+ "mistral-conversations",
27
+ "bedrock-converse-stream",
28
+ "ollama-chat",
29
+ ];
30
+
31
+ test("covers all expected API providers", () => {
32
+ for (const api of EXPECTED_APIS) {
33
+ assert.ok(
34
+ PROVIDER_CAPABILITIES[api],
35
+ `Missing capability entry for API: ${api}`,
36
+ );
37
+ }
38
+ });
39
+
40
+ test("getRegisteredApis returns all entries", () => {
41
+ const registered = getRegisteredApis();
42
+ for (const api of EXPECTED_APIS) {
43
+ assert.ok(registered.includes(api), `getRegisteredApis missing: ${api}`);
44
+ }
45
+ });
46
+
47
+ test("all entries have required fields", () => {
48
+ for (const [api, caps] of Object.entries(PROVIDER_CAPABILITIES)) {
49
+ assert.equal(typeof caps.toolCalling, "boolean", `${api}.toolCalling`);
50
+ assert.equal(typeof caps.maxTools, "number", `${api}.maxTools`);
51
+ assert.equal(typeof caps.imageToolResults, "boolean", `${api}.imageToolResults`);
52
+ assert.equal(typeof caps.structuredOutput, "boolean", `${api}.structuredOutput`);
53
+ assert.ok(caps.toolCallIdFormat, `${api}.toolCallIdFormat`);
54
+ assert.equal(typeof caps.toolCallIdFormat.maxLength, "number", `${api}.toolCallIdFormat.maxLength`);
55
+ assert.ok(caps.toolCallIdFormat.allowedChars instanceof RegExp, `${api}.toolCallIdFormat.allowedChars`);
56
+ assert.ok(
57
+ ["full", "text-only", "none"].includes(caps.thinkingPersistence),
58
+ `${api}.thinkingPersistence is "${caps.thinkingPersistence}"`,
59
+ );
60
+ assert.ok(Array.isArray(caps.unsupportedSchemaFeatures), `${api}.unsupportedSchemaFeatures`);
61
+ }
62
+ });
63
+ });
64
+
65
+ // ─── Provider-specific Values ───────────────────────────────────────────────
66
+
67
+ describe("provider-specific capabilities", () => {
68
+ test("Anthropic supports full thinking persistence", () => {
69
+ assert.equal(PROVIDER_CAPABILITIES["anthropic-messages"].thinkingPersistence, "full");
70
+ });
71
+
72
+ test("Anthropic supports image tool results", () => {
73
+ assert.equal(PROVIDER_CAPABILITIES["anthropic-messages"].imageToolResults, true);
74
+ });
75
+
76
+ test("Anthropic tool call ID is 64 chars max", () => {
77
+ assert.equal(PROVIDER_CAPABILITIES["anthropic-messages"].toolCallIdFormat.maxLength, 64);
78
+ });
79
+
80
+ test("Mistral tool call ID is 9 chars max", () => {
81
+ assert.equal(PROVIDER_CAPABILITIES["mistral-conversations"].toolCallIdFormat.maxLength, 9);
82
+ });
83
+
84
+ test("Mistral has no thinking persistence", () => {
85
+ assert.equal(PROVIDER_CAPABILITIES["mistral-conversations"].thinkingPersistence, "none");
86
+ });
87
+
88
+ test("Google does not support patternProperties", () => {
89
+ assert.ok(
90
+ PROVIDER_CAPABILITIES["google-generative-ai"].unsupportedSchemaFeatures.includes("patternProperties"),
91
+ );
92
+ });
93
+
94
+ test("Google does not support const", () => {
95
+ assert.ok(
96
+ PROVIDER_CAPABILITIES["google-generative-ai"].unsupportedSchemaFeatures.includes("const"),
97
+ );
98
+ });
99
+
100
+ test("OpenAI Responses does not support image tool results", () => {
101
+ assert.equal(PROVIDER_CAPABILITIES["openai-responses"].imageToolResults, false);
102
+ });
103
+
104
+ test("OpenAI Responses has text-only thinking persistence", () => {
105
+ assert.equal(PROVIDER_CAPABILITIES["openai-responses"].thinkingPersistence, "text-only");
106
+ });
107
+ });
108
+
109
+ // ─── getProviderCapabilities ────────────────────────────────────────────────
110
+
111
+ describe("getProviderCapabilities", () => {
112
+ test("returns known provider capabilities", () => {
113
+ const caps = getProviderCapabilities("anthropic-messages");
114
+ assert.equal(caps.toolCalling, true);
115
+ assert.equal(caps.thinkingPersistence, "full");
116
+ });
117
+
118
+ test("returns permissive defaults for unknown providers", () => {
119
+ const caps = getProviderCapabilities("unknown-provider-xyz");
120
+ assert.equal(caps.toolCalling, true);
121
+ assert.equal(caps.imageToolResults, true);
122
+ assert.deepEqual(caps.unsupportedSchemaFeatures, []);
123
+ });
124
+ });
125
+
126
+ // ─── getUnsupportedFeatures ─────────────────────────────────────────────────
127
+
128
+ describe("getUnsupportedFeatures", () => {
129
+ test("returns unsupported features for Google", () => {
130
+ const unsupported = getUnsupportedFeatures("google-generative-ai", ["patternProperties", "const"]);
131
+ assert.deepEqual(unsupported, ["patternProperties", "const"]);
132
+ });
133
+
134
+ test("returns empty for Anthropic with any features", () => {
135
+ const unsupported = getUnsupportedFeatures("anthropic-messages", ["patternProperties", "const"]);
136
+ assert.deepEqual(unsupported, []);
137
+ });
138
+
139
+ test("returns empty for unknown provider", () => {
140
+ const unsupported = getUnsupportedFeatures("unknown-xyz", ["patternProperties"]);
141
+ assert.deepEqual(unsupported, []);
142
+ });
143
+ });
144
+
145
+ // ─── mergeCapabilityOverrides ───────────────────────────────────────────────
146
+
147
+ describe("mergeCapabilityOverrides", () => {
148
+ test("overrides individual fields", () => {
149
+ const merged = mergeCapabilityOverrides("openai-responses", {
150
+ imageToolResults: true,
151
+ });
152
+ assert.equal(merged.imageToolResults, true);
153
+ // Non-overridden fields preserved
154
+ assert.equal(merged.toolCalling, true);
155
+ assert.equal(merged.thinkingPersistence, "text-only");
156
+ });
157
+
158
+ test("deep-merges toolCallIdFormat", () => {
159
+ const merged = mergeCapabilityOverrides("anthropic-messages", {
160
+ toolCallIdFormat: { maxLength: 128 },
161
+ });
162
+ assert.equal(merged.toolCallIdFormat.maxLength, 128);
163
+ // allowedChars preserved from base
164
+ assert.ok(merged.toolCallIdFormat.allowedChars instanceof RegExp);
165
+ });
166
+
167
+ test("uses permissive defaults for unknown provider", () => {
168
+ const merged = mergeCapabilityOverrides("unknown-xyz", {
169
+ imageToolResults: false,
170
+ });
171
+ assert.equal(merged.imageToolResults, false);
172
+ assert.equal(merged.toolCalling, true); // from default
173
+ });
174
+ });