gsd-pi 2.46.1 → 2.47.0-dev.8cfe772

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 (220) hide show
  1. package/README.md +46 -29
  2. package/dist/resources/extensions/claude-code-cli/index.js +25 -0
  3. package/dist/resources/extensions/claude-code-cli/models.js +40 -0
  4. package/dist/resources/extensions/claude-code-cli/package.json +11 -0
  5. package/dist/resources/extensions/claude-code-cli/partial-builder.js +223 -0
  6. package/dist/resources/extensions/claude-code-cli/readiness.js +26 -0
  7. package/dist/resources/extensions/claude-code-cli/sdk-types.js +8 -0
  8. package/dist/resources/extensions/claude-code-cli/stream-adapter.js +309 -0
  9. package/dist/resources/extensions/gsd/auto-start.js +17 -9
  10. package/dist/resources/extensions/gsd/guided-flow.js +78 -2
  11. package/dist/resources/extensions/gsd/prompts/complete-slice.md +1 -1
  12. package/dist/resources/extensions/gsd/prompts/guided-plan-milestone.md +2 -2
  13. package/dist/resources/extensions/gsd/prompts/plan-milestone.md +2 -2
  14. package/dist/resources/extensions/gsd/prompts/plan-slice.md +1 -1
  15. package/dist/resources/extensions/gsd/prompts/research-milestone.md +2 -2
  16. package/dist/resources/extensions/gsd/prompts/run-uat.md +2 -2
  17. package/dist/resources/extensions/gsd/repo-identity.js +5 -2
  18. package/dist/resources/extensions/gsd/session-forensics.js +10 -1
  19. package/dist/resources/extensions/gsd/state.js +29 -2
  20. package/dist/resources/extensions/gsd/workflow-events.js +1 -1
  21. package/dist/web/standalone/.next/BUILD_ID +1 -1
  22. package/dist/web/standalone/.next/app-path-routes-manifest.json +12 -12
  23. package/dist/web/standalone/.next/build-manifest.json +3 -3
  24. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  25. package/dist/web/standalone/.next/required-server-files.json +3 -3
  26. package/dist/web/standalone/.next/server/app/_global-error/page.js +3 -3
  27. package/dist/web/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  28. package/dist/web/standalone/.next/server/app/_global-error.html +2 -2
  29. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  30. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  31. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  32. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  33. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  34. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  35. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  36. package/dist/web/standalone/.next/server/app/_not-found/page.js +2 -2
  37. package/dist/web/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  38. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  39. package/dist/web/standalone/.next/server/app/_not-found.rsc +3 -3
  40. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +3 -3
  41. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  42. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +3 -3
  43. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  44. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  45. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  46. package/dist/web/standalone/.next/server/app/api/boot/route.js +1 -1
  47. package/dist/web/standalone/.next/server/app/api/boot/route_client-reference-manifest.js +1 -1
  48. package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route.js +1 -1
  49. package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route_client-reference-manifest.js +1 -1
  50. package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route.js +1 -1
  51. package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route_client-reference-manifest.js +1 -1
  52. package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route.js +2 -2
  53. package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route_client-reference-manifest.js +1 -1
  54. package/dist/web/standalone/.next/server/app/api/browse-directories/route.js +1 -1
  55. package/dist/web/standalone/.next/server/app/api/browse-directories/route_client-reference-manifest.js +1 -1
  56. package/dist/web/standalone/.next/server/app/api/captures/route.js +1 -1
  57. package/dist/web/standalone/.next/server/app/api/captures/route_client-reference-manifest.js +1 -1
  58. package/dist/web/standalone/.next/server/app/api/cleanup/route.js +1 -1
  59. package/dist/web/standalone/.next/server/app/api/cleanup/route_client-reference-manifest.js +1 -1
  60. package/dist/web/standalone/.next/server/app/api/dev-mode/route.js +1 -1
  61. package/dist/web/standalone/.next/server/app/api/dev-mode/route_client-reference-manifest.js +1 -1
  62. package/dist/web/standalone/.next/server/app/api/doctor/route.js +1 -1
  63. package/dist/web/standalone/.next/server/app/api/doctor/route_client-reference-manifest.js +1 -1
  64. package/dist/web/standalone/.next/server/app/api/export-data/route.js +1 -1
  65. package/dist/web/standalone/.next/server/app/api/export-data/route_client-reference-manifest.js +1 -1
  66. package/dist/web/standalone/.next/server/app/api/files/route.js +1 -1
  67. package/dist/web/standalone/.next/server/app/api/files/route_client-reference-manifest.js +1 -1
  68. package/dist/web/standalone/.next/server/app/api/forensics/route.js +1 -1
  69. package/dist/web/standalone/.next/server/app/api/forensics/route_client-reference-manifest.js +1 -1
  70. package/dist/web/standalone/.next/server/app/api/git/route.js +1 -1
  71. package/dist/web/standalone/.next/server/app/api/git/route_client-reference-manifest.js +1 -1
  72. package/dist/web/standalone/.next/server/app/api/history/route.js +1 -1
  73. package/dist/web/standalone/.next/server/app/api/history/route_client-reference-manifest.js +1 -1
  74. package/dist/web/standalone/.next/server/app/api/hooks/route.js +1 -1
  75. package/dist/web/standalone/.next/server/app/api/hooks/route_client-reference-manifest.js +1 -1
  76. package/dist/web/standalone/.next/server/app/api/inspect/route.js +1 -1
  77. package/dist/web/standalone/.next/server/app/api/inspect/route_client-reference-manifest.js +1 -1
  78. package/dist/web/standalone/.next/server/app/api/knowledge/route.js +1 -1
  79. package/dist/web/standalone/.next/server/app/api/knowledge/route_client-reference-manifest.js +1 -1
  80. package/dist/web/standalone/.next/server/app/api/live-state/route.js +1 -1
  81. package/dist/web/standalone/.next/server/app/api/live-state/route_client-reference-manifest.js +1 -1
  82. package/dist/web/standalone/.next/server/app/api/onboarding/route.js +1 -1
  83. package/dist/web/standalone/.next/server/app/api/onboarding/route_client-reference-manifest.js +1 -1
  84. package/dist/web/standalone/.next/server/app/api/preferences/route.js +1 -1
  85. package/dist/web/standalone/.next/server/app/api/preferences/route_client-reference-manifest.js +1 -1
  86. package/dist/web/standalone/.next/server/app/api/projects/route.js +1 -1
  87. package/dist/web/standalone/.next/server/app/api/projects/route_client-reference-manifest.js +1 -1
  88. package/dist/web/standalone/.next/server/app/api/recovery/route.js +1 -1
  89. package/dist/web/standalone/.next/server/app/api/recovery/route_client-reference-manifest.js +1 -1
  90. package/dist/web/standalone/.next/server/app/api/remote-questions/route.js +5 -5
  91. package/dist/web/standalone/.next/server/app/api/remote-questions/route_client-reference-manifest.js +1 -1
  92. package/dist/web/standalone/.next/server/app/api/session/browser/route.js +1 -1
  93. package/dist/web/standalone/.next/server/app/api/session/browser/route_client-reference-manifest.js +1 -1
  94. package/dist/web/standalone/.next/server/app/api/session/command/route.js +1 -1
  95. package/dist/web/standalone/.next/server/app/api/session/command/route_client-reference-manifest.js +1 -1
  96. package/dist/web/standalone/.next/server/app/api/session/events/route.js +2 -2
  97. package/dist/web/standalone/.next/server/app/api/session/events/route_client-reference-manifest.js +1 -1
  98. package/dist/web/standalone/.next/server/app/api/session/manage/route.js +1 -1
  99. package/dist/web/standalone/.next/server/app/api/session/manage/route_client-reference-manifest.js +1 -1
  100. package/dist/web/standalone/.next/server/app/api/settings-data/route.js +1 -1
  101. package/dist/web/standalone/.next/server/app/api/settings-data/route_client-reference-manifest.js +1 -1
  102. package/dist/web/standalone/.next/server/app/api/shutdown/route.js +1 -1
  103. package/dist/web/standalone/.next/server/app/api/shutdown/route_client-reference-manifest.js +1 -1
  104. package/dist/web/standalone/.next/server/app/api/skill-health/route.js +1 -1
  105. package/dist/web/standalone/.next/server/app/api/skill-health/route_client-reference-manifest.js +1 -1
  106. package/dist/web/standalone/.next/server/app/api/steer/route.js +1 -1
  107. package/dist/web/standalone/.next/server/app/api/steer/route_client-reference-manifest.js +1 -1
  108. package/dist/web/standalone/.next/server/app/api/switch-root/route.js +1 -1
  109. package/dist/web/standalone/.next/server/app/api/switch-root/route_client-reference-manifest.js +1 -1
  110. package/dist/web/standalone/.next/server/app/api/terminal/input/route.js +2 -2
  111. package/dist/web/standalone/.next/server/app/api/terminal/input/route_client-reference-manifest.js +1 -1
  112. package/dist/web/standalone/.next/server/app/api/terminal/resize/route.js +2 -2
  113. package/dist/web/standalone/.next/server/app/api/terminal/resize/route_client-reference-manifest.js +1 -1
  114. package/dist/web/standalone/.next/server/app/api/terminal/sessions/route.js +2 -2
  115. package/dist/web/standalone/.next/server/app/api/terminal/sessions/route_client-reference-manifest.js +1 -1
  116. package/dist/web/standalone/.next/server/app/api/terminal/stream/route.js +4 -4
  117. package/dist/web/standalone/.next/server/app/api/terminal/stream/route_client-reference-manifest.js +1 -1
  118. package/dist/web/standalone/.next/server/app/api/terminal/upload/route.js +1 -1
  119. package/dist/web/standalone/.next/server/app/api/terminal/upload/route_client-reference-manifest.js +1 -1
  120. package/dist/web/standalone/.next/server/app/api/undo/route.js +1 -1
  121. package/dist/web/standalone/.next/server/app/api/undo/route_client-reference-manifest.js +1 -1
  122. package/dist/web/standalone/.next/server/app/api/update/route.js +1 -1
  123. package/dist/web/standalone/.next/server/app/api/update/route_client-reference-manifest.js +1 -1
  124. package/dist/web/standalone/.next/server/app/api/visualizer/route.js +1 -1
  125. package/dist/web/standalone/.next/server/app/api/visualizer/route_client-reference-manifest.js +1 -1
  126. package/dist/web/standalone/.next/server/app/index.html +1 -1
  127. package/dist/web/standalone/.next/server/app/index.rsc +4 -4
  128. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
  129. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +4 -4
  130. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  131. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +3 -3
  132. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  133. package/dist/web/standalone/.next/server/app/page.js +2 -2
  134. package/dist/web/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  135. package/dist/web/standalone/.next/server/app-paths-manifest.json +12 -12
  136. package/dist/web/standalone/.next/server/chunks/229.js +1 -1
  137. package/dist/web/standalone/.next/server/chunks/471.js +3 -3
  138. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  139. package/dist/web/standalone/.next/server/middleware.js +2 -2
  140. package/dist/web/standalone/.next/server/next-font-manifest.js +1 -1
  141. package/dist/web/standalone/.next/server/next-font-manifest.json +1 -1
  142. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  143. package/dist/web/standalone/.next/server/pages/500.html +2 -2
  144. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  145. package/dist/web/standalone/.next/static/chunks/app/_not-found/{page-2f24283c162b6ab3.js → page-f2a7482d42a5614b.js} +1 -1
  146. package/dist/web/standalone/.next/static/chunks/app/{layout-9ecfd95f343793f0.js → layout-a16c7a7ecdf0c2cf.js} +1 -1
  147. package/dist/web/standalone/.next/static/chunks/app/page-6654a8cca61a3d1c.js +1 -0
  148. package/dist/web/standalone/.next/static/chunks/main-app-fdab67f7802d7832.js +1 -0
  149. package/dist/web/standalone/.next/static/chunks/next/dist/client/components/builtin/global-error-459824ffb8c323dd.js +1 -0
  150. package/dist/web/standalone/node_modules/node-pty/build/Makefile +2 -2
  151. package/dist/web/standalone/node_modules/node-pty/build/Release/pty.node +0 -0
  152. package/dist/web/standalone/node_modules/node-pty/build/pty.target.mk +14 -14
  153. package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api.target.mk +14 -14
  154. package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api_except.target.mk +14 -14
  155. package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api_maybe.target.mk +14 -14
  156. package/dist/web/standalone/server.js +1 -1
  157. package/package.json +3 -1
  158. package/packages/pi-agent-core/dist/agent-loop.js +27 -1
  159. package/packages/pi-agent-core/dist/agent-loop.js.map +1 -1
  160. package/packages/pi-agent-core/dist/agent.d.ts +7 -0
  161. package/packages/pi-agent-core/dist/agent.d.ts.map +1 -1
  162. package/packages/pi-agent-core/dist/agent.js +2 -0
  163. package/packages/pi-agent-core/dist/agent.js.map +1 -1
  164. package/packages/pi-agent-core/dist/types.d.ts +9 -0
  165. package/packages/pi-agent-core/dist/types.d.ts.map +1 -1
  166. package/packages/pi-agent-core/dist/types.js.map +1 -1
  167. package/packages/pi-agent-core/src/agent-loop.ts +26 -1
  168. package/packages/pi-agent-core/src/agent.ts +10 -0
  169. package/packages/pi-agent-core/src/types.ts +10 -0
  170. package/packages/pi-coding-agent/dist/core/auth-storage.test.js +27 -2
  171. package/packages/pi-coding-agent/dist/core/auth-storage.test.js.map +1 -1
  172. package/packages/pi-coding-agent/dist/core/model-registry-auth-mode.test.js +43 -0
  173. package/packages/pi-coding-agent/dist/core/model-registry-auth-mode.test.js.map +1 -1
  174. package/packages/pi-coding-agent/dist/core/model-registry.d.ts.map +1 -1
  175. package/packages/pi-coding-agent/dist/core/model-registry.js +26 -3
  176. package/packages/pi-coding-agent/dist/core/model-registry.js.map +1 -1
  177. package/packages/pi-coding-agent/dist/core/sdk.d.ts.map +1 -1
  178. package/packages/pi-coding-agent/dist/core/sdk.js +1 -0
  179. package/packages/pi-coding-agent/dist/core/sdk.js.map +1 -1
  180. package/packages/pi-coding-agent/package.json +1 -1
  181. package/packages/pi-coding-agent/src/core/auth-storage.test.ts +27 -2
  182. package/packages/pi-coding-agent/src/core/model-registry-auth-mode.test.ts +70 -0
  183. package/packages/pi-coding-agent/src/core/model-registry.ts +29 -2
  184. package/packages/pi-coding-agent/src/core/sdk.ts +1 -0
  185. package/packages/pi-tui/dist/components/box.d.ts +1 -0
  186. package/packages/pi-tui/dist/components/box.d.ts.map +1 -1
  187. package/packages/pi-tui/dist/components/box.js +10 -0
  188. package/packages/pi-tui/dist/components/box.js.map +1 -1
  189. package/packages/pi-tui/src/components/box.ts +10 -0
  190. package/pkg/package.json +1 -1
  191. package/src/resources/extensions/claude-code-cli/index.ts +28 -0
  192. package/src/resources/extensions/claude-code-cli/models.ts +42 -0
  193. package/src/resources/extensions/claude-code-cli/package.json +11 -0
  194. package/src/resources/extensions/claude-code-cli/partial-builder.ts +258 -0
  195. package/src/resources/extensions/claude-code-cli/readiness.ts +30 -0
  196. package/src/resources/extensions/claude-code-cli/sdk-types.ts +149 -0
  197. package/src/resources/extensions/claude-code-cli/stream-adapter.ts +370 -0
  198. package/src/resources/extensions/gsd/auto-start.ts +15 -8
  199. package/src/resources/extensions/gsd/guided-flow.ts +96 -2
  200. package/src/resources/extensions/gsd/prompts/complete-slice.md +1 -1
  201. package/src/resources/extensions/gsd/prompts/guided-plan-milestone.md +2 -2
  202. package/src/resources/extensions/gsd/prompts/plan-milestone.md +2 -2
  203. package/src/resources/extensions/gsd/prompts/plan-slice.md +1 -1
  204. package/src/resources/extensions/gsd/prompts/research-milestone.md +2 -2
  205. package/src/resources/extensions/gsd/prompts/run-uat.md +2 -2
  206. package/src/resources/extensions/gsd/repo-identity.ts +5 -2
  207. package/src/resources/extensions/gsd/session-forensics.ts +11 -1
  208. package/src/resources/extensions/gsd/state.ts +33 -1
  209. package/src/resources/extensions/gsd/tests/discuss-queued-milestones.test.ts +241 -0
  210. package/src/resources/extensions/gsd/tests/forensics-error-filter.test.ts +121 -0
  211. package/src/resources/extensions/gsd/tests/inherited-repo-home-dir.test.ts +70 -0
  212. package/src/resources/extensions/gsd/tests/plan-slice-prompt.test.ts +40 -0
  213. package/src/resources/extensions/gsd/tests/preflight-context-draft-filter.test.ts +115 -0
  214. package/src/resources/extensions/gsd/tests/run-uat.test.ts +25 -0
  215. package/src/resources/extensions/gsd/workflow-events.ts +1 -1
  216. package/dist/web/standalone/.next/static/chunks/app/page-12dd5ece0df4badc.js +0 -1
  217. package/dist/web/standalone/.next/static/chunks/main-app-d3d4c336195465f9.js +0 -1
  218. package/dist/web/standalone/.next/static/chunks/next/dist/client/components/builtin/global-error-ab5a8926e07ec673.js +0 -1
  219. /package/dist/web/standalone/.next/static/{P4nF4UcdATjrbNMBH_Ulh → DyrX2zX_4v7KZDbUNxE2q}/_buildManifest.js +0 -0
  220. /package/dist/web/standalone/.next/static/{P4nF4UcdATjrbNMBH_Ulh → DyrX2zX_4v7KZDbUNxE2q}/_ssgManifest.js +0 -0
@@ -6,6 +6,7 @@ import {
6
6
  type Api,
7
7
  type AssistantMessageEventStream,
8
8
  type Context,
9
+ getApiProvider,
9
10
  getModels,
10
11
  getProviders,
11
12
  type KnownProvider,
@@ -635,11 +636,37 @@ export class ModelRegistry {
635
636
  })
636
637
  : rawStreamSimple;
637
638
 
639
+ // Guard: if there's already a handler registered for this API, wrap
640
+ // the new one so it only fires for models from this provider and
641
+ // delegates to the previous handler for all other providers. Without
642
+ // this, a custom provider using api:"anthropic-messages" would clobber
643
+ // the built-in Anthropic stream handler (#2536).
644
+ const existingProvider = getApiProvider(config.api as Api);
645
+ const scopedStream = existingProvider
646
+ ? (model: Model<Api>, context: Context, options?: SimpleStreamOptions): AssistantMessageEventStream => {
647
+ if (model.provider === providerName) {
648
+ return streamSimple(model, context, options);
649
+ }
650
+ return existingProvider.streamSimple(model, context, options);
651
+ }
652
+ : streamSimple;
653
+
654
+ const newFullStream = (model: Model<Api>, context: Context, options?: SimpleStreamOptions) =>
655
+ scopedStream(model, context, options as SimpleStreamOptions);
656
+ const scopedFullStream = existingProvider
657
+ ? (model: Model<Api>, context: Context, options?: Record<string, unknown>) => {
658
+ if (model.provider === providerName) {
659
+ return newFullStream(model, context, options as SimpleStreamOptions);
660
+ }
661
+ return existingProvider.stream(model, context, options);
662
+ }
663
+ : newFullStream;
664
+
638
665
  registerApiProvider(
639
666
  {
640
667
  api: config.api,
641
- stream: (model, context, options) => streamSimple(model, context, options as SimpleStreamOptions),
642
- streamSimple,
668
+ stream: scopedFullStream as any,
669
+ streamSimple: scopedStream,
643
670
  },
644
671
  `provider:${providerName}`,
645
672
  );
@@ -326,6 +326,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
326
326
  transport: settingsManager.getTransport(),
327
327
  thinkingBudgets: settingsManager.getThinkingBudgets(),
328
328
  maxRetryDelayMs: settingsManager.getRetrySettings().maxDelayMs,
329
+ externalToolExecution: (m) => modelRegistry.getProviderAuthMode(m.provider) === "externalCli",
329
330
  getApiKey: async (provider) => {
330
331
  // Use the provider argument from the in-flight request;
331
332
  // agent.state.model may already be switched mid-turn.
@@ -10,6 +10,7 @@ export declare class Box implements Component {
10
10
  private cache?;
11
11
  constructor(paddingX?: number, paddingY?: number, bgFn?: (text: string) => string);
12
12
  addChild(component: Component): void;
13
+ insertChildBefore(component: Component, before: Component): void;
13
14
  removeChild(component: Component): void;
14
15
  clear(): void;
15
16
  setBgFn(bgFn?: (text: string) => string): void;
@@ -1 +1 @@
1
- {"version":3,"file":"box.d.ts","sourceRoot":"","sources":["../../src/components/box.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAU3C;;GAEG;AACH,qBAAa,GAAI,YAAW,SAAS;IACpC,QAAQ,EAAE,SAAS,EAAE,CAAM;IAC3B,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,IAAI,CAAC,CAA2B;IAGxC,OAAO,CAAC,KAAK,CAAC,CAAc;gBAEhB,QAAQ,SAAI,EAAE,QAAQ,SAAI,EAAE,IAAI,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM;IAMvE,QAAQ,CAAC,SAAS,EAAE,SAAS,GAAG,IAAI;IAKpC,WAAW,CAAC,SAAS,EAAE,SAAS,GAAG,IAAI;IAQvC,KAAK,IAAI,IAAI;IAKb,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,GAAG,IAAI;IAK9C,OAAO,CAAC,eAAe;IAIvB,OAAO,CAAC,UAAU;IAWlB,UAAU,IAAI,IAAI;IAOlB,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE;IAqD/B,OAAO,CAAC,OAAO;CAUf"}
1
+ {"version":3,"file":"box.d.ts","sourceRoot":"","sources":["../../src/components/box.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAU3C;;GAEG;AACH,qBAAa,GAAI,YAAW,SAAS;IACpC,QAAQ,EAAE,SAAS,EAAE,CAAM;IAC3B,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,IAAI,CAAC,CAA2B;IAGxC,OAAO,CAAC,KAAK,CAAC,CAAc;gBAEhB,QAAQ,SAAI,EAAE,QAAQ,SAAI,EAAE,IAAI,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM;IAMvE,QAAQ,CAAC,SAAS,EAAE,SAAS,GAAG,IAAI;IAKpC,iBAAiB,CAAC,SAAS,EAAE,SAAS,EAAE,MAAM,EAAE,SAAS,GAAG,IAAI;IAUhE,WAAW,CAAC,SAAS,EAAE,SAAS,GAAG,IAAI;IAQvC,KAAK,IAAI,IAAI;IAKb,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,GAAG,IAAI;IAK9C,OAAO,CAAC,eAAe;IAIvB,OAAO,CAAC,UAAU;IAWlB,UAAU,IAAI,IAAI;IAOlB,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE;IAqD/B,OAAO,CAAC,OAAO;CAUf"}
@@ -13,6 +13,16 @@ export class Box {
13
13
  this.children.push(component);
14
14
  this.invalidateCache();
15
15
  }
16
+ insertChildBefore(component, before) {
17
+ const index = this.children.indexOf(before);
18
+ if (index !== -1) {
19
+ this.children.splice(index, 0, component);
20
+ }
21
+ else {
22
+ this.children.push(component);
23
+ }
24
+ this.invalidateCache();
25
+ }
16
26
  removeChild(component) {
17
27
  const index = this.children.indexOf(component);
18
28
  if (index !== -1) {
@@ -1 +1 @@
1
- {"version":3,"file":"box.js","sourceRoot":"","sources":["../../src/components/box.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Component } from \"../tui.js\";\nimport { applyBackgroundToLine, visibleWidth } from \"../utils.js\";\n\ntype RenderCache = {\n\tchildLines: string[];\n\twidth: number;\n\tbgSample: string | undefined;\n\tlines: string[];\n};\n\n/**\n * Box component - a container that applies padding and background to all children\n */\nexport class Box implements Component {\n\tchildren: Component[] = [];\n\tprivate paddingX: number;\n\tprivate paddingY: number;\n\tprivate bgFn?: (text: string) => string;\n\n\t// Cache for rendered output\n\tprivate cache?: RenderCache;\n\n\tconstructor(paddingX = 1, paddingY = 1, bgFn?: (text: string) => string) {\n\t\tthis.paddingX = paddingX;\n\t\tthis.paddingY = paddingY;\n\t\tthis.bgFn = bgFn;\n\t}\n\n\taddChild(component: Component): void {\n\t\tthis.children.push(component);\n\t\tthis.invalidateCache();\n\t}\n\n\tremoveChild(component: Component): void {\n\t\tconst index = this.children.indexOf(component);\n\t\tif (index !== -1) {\n\t\t\tthis.children.splice(index, 1);\n\t\t\tthis.invalidateCache();\n\t\t}\n\t}\n\n\tclear(): void {\n\t\tthis.children = [];\n\t\tthis.invalidateCache();\n\t}\n\n\tsetBgFn(bgFn?: (text: string) => string): void {\n\t\tthis.bgFn = bgFn;\n\t\t// Don't invalidate here - we'll detect bgFn changes by sampling output\n\t}\n\n\tprivate invalidateCache(): void {\n\t\tthis.cache = undefined;\n\t}\n\n\tprivate matchCache(width: number, childLines: string[], bgSample: string | undefined): boolean {\n\t\tconst cache = this.cache;\n\t\treturn (\n\t\t\t!!cache &&\n\t\t\tcache.width === width &&\n\t\t\tcache.bgSample === bgSample &&\n\t\t\tcache.childLines.length === childLines.length &&\n\t\t\tcache.childLines.every((line, i) => line === childLines[i])\n\t\t);\n\t}\n\n\tinvalidate(): void {\n\t\tthis.invalidateCache();\n\t\tfor (const child of this.children) {\n\t\t\tchild.invalidate?.();\n\t\t}\n\t}\n\n\trender(width: number): string[] {\n\t\tif (this.children.length === 0) {\n\t\t\treturn [];\n\t\t}\n\n\t\tconst contentWidth = Math.max(1, width - this.paddingX * 2);\n\t\tconst leftPad = \" \".repeat(this.paddingX);\n\n\t\t// Render all children\n\t\tconst childLines: string[] = [];\n\t\tfor (const child of this.children) {\n\t\t\tconst lines = child.render(contentWidth);\n\t\t\tfor (const line of lines) {\n\t\t\t\tchildLines.push(leftPad + line);\n\t\t\t}\n\t\t}\n\n\t\tif (childLines.length === 0) {\n\t\t\treturn [];\n\t\t}\n\n\t\t// Check if bgFn output changed by sampling\n\t\tconst bgSample = this.bgFn ? this.bgFn(\"test\") : undefined;\n\n\t\t// Check cache validity\n\t\tif (this.matchCache(width, childLines, bgSample)) {\n\t\t\treturn this.cache!.lines;\n\t\t}\n\n\t\t// Apply background and padding\n\t\tconst result: string[] = [];\n\n\t\t// Top padding\n\t\tfor (let i = 0; i < this.paddingY; i++) {\n\t\t\tresult.push(this.applyBg(\"\", width));\n\t\t}\n\n\t\t// Content\n\t\tfor (const line of childLines) {\n\t\t\tresult.push(this.applyBg(line, width));\n\t\t}\n\n\t\t// Bottom padding\n\t\tfor (let i = 0; i < this.paddingY; i++) {\n\t\t\tresult.push(this.applyBg(\"\", width));\n\t\t}\n\n\t\t// Update cache\n\t\tthis.cache = { childLines, width, bgSample, lines: result };\n\n\t\treturn result;\n\t}\n\n\tprivate applyBg(line: string, width: number): string {\n\t\tconst visLen = visibleWidth(line);\n\t\tconst padNeeded = Math.max(0, width - visLen);\n\t\tconst padded = line + \" \".repeat(padNeeded);\n\n\t\tif (this.bgFn) {\n\t\t\treturn applyBackgroundToLine(padded, width, this.bgFn);\n\t\t}\n\t\treturn padded;\n\t}\n}\n"]}
1
+ {"version":3,"file":"box.js","sourceRoot":"","sources":["../../src/components/box.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Component } from \"../tui.js\";\nimport { applyBackgroundToLine, visibleWidth } from \"../utils.js\";\n\ntype RenderCache = {\n\tchildLines: string[];\n\twidth: number;\n\tbgSample: string | undefined;\n\tlines: string[];\n};\n\n/**\n * Box component - a container that applies padding and background to all children\n */\nexport class Box implements Component {\n\tchildren: Component[] = [];\n\tprivate paddingX: number;\n\tprivate paddingY: number;\n\tprivate bgFn?: (text: string) => string;\n\n\t// Cache for rendered output\n\tprivate cache?: RenderCache;\n\n\tconstructor(paddingX = 1, paddingY = 1, bgFn?: (text: string) => string) {\n\t\tthis.paddingX = paddingX;\n\t\tthis.paddingY = paddingY;\n\t\tthis.bgFn = bgFn;\n\t}\n\n\taddChild(component: Component): void {\n\t\tthis.children.push(component);\n\t\tthis.invalidateCache();\n\t}\n\n\tinsertChildBefore(component: Component, before: Component): void {\n\t\tconst index = this.children.indexOf(before);\n\t\tif (index !== -1) {\n\t\t\tthis.children.splice(index, 0, component);\n\t\t} else {\n\t\t\tthis.children.push(component);\n\t\t}\n\t\tthis.invalidateCache();\n\t}\n\n\tremoveChild(component: Component): void {\n\t\tconst index = this.children.indexOf(component);\n\t\tif (index !== -1) {\n\t\t\tthis.children.splice(index, 1);\n\t\t\tthis.invalidateCache();\n\t\t}\n\t}\n\n\tclear(): void {\n\t\tthis.children = [];\n\t\tthis.invalidateCache();\n\t}\n\n\tsetBgFn(bgFn?: (text: string) => string): void {\n\t\tthis.bgFn = bgFn;\n\t\t// Don't invalidate here - we'll detect bgFn changes by sampling output\n\t}\n\n\tprivate invalidateCache(): void {\n\t\tthis.cache = undefined;\n\t}\n\n\tprivate matchCache(width: number, childLines: string[], bgSample: string | undefined): boolean {\n\t\tconst cache = this.cache;\n\t\treturn (\n\t\t\t!!cache &&\n\t\t\tcache.width === width &&\n\t\t\tcache.bgSample === bgSample &&\n\t\t\tcache.childLines.length === childLines.length &&\n\t\t\tcache.childLines.every((line, i) => line === childLines[i])\n\t\t);\n\t}\n\n\tinvalidate(): void {\n\t\tthis.invalidateCache();\n\t\tfor (const child of this.children) {\n\t\t\tchild.invalidate?.();\n\t\t}\n\t}\n\n\trender(width: number): string[] {\n\t\tif (this.children.length === 0) {\n\t\t\treturn [];\n\t\t}\n\n\t\tconst contentWidth = Math.max(1, width - this.paddingX * 2);\n\t\tconst leftPad = \" \".repeat(this.paddingX);\n\n\t\t// Render all children\n\t\tconst childLines: string[] = [];\n\t\tfor (const child of this.children) {\n\t\t\tconst lines = child.render(contentWidth);\n\t\t\tfor (const line of lines) {\n\t\t\t\tchildLines.push(leftPad + line);\n\t\t\t}\n\t\t}\n\n\t\tif (childLines.length === 0) {\n\t\t\treturn [];\n\t\t}\n\n\t\t// Check if bgFn output changed by sampling\n\t\tconst bgSample = this.bgFn ? this.bgFn(\"test\") : undefined;\n\n\t\t// Check cache validity\n\t\tif (this.matchCache(width, childLines, bgSample)) {\n\t\t\treturn this.cache!.lines;\n\t\t}\n\n\t\t// Apply background and padding\n\t\tconst result: string[] = [];\n\n\t\t// Top padding\n\t\tfor (let i = 0; i < this.paddingY; i++) {\n\t\t\tresult.push(this.applyBg(\"\", width));\n\t\t}\n\n\t\t// Content\n\t\tfor (const line of childLines) {\n\t\t\tresult.push(this.applyBg(line, width));\n\t\t}\n\n\t\t// Bottom padding\n\t\tfor (let i = 0; i < this.paddingY; i++) {\n\t\t\tresult.push(this.applyBg(\"\", width));\n\t\t}\n\n\t\t// Update cache\n\t\tthis.cache = { childLines, width, bgSample, lines: result };\n\n\t\treturn result;\n\t}\n\n\tprivate applyBg(line: string, width: number): string {\n\t\tconst visLen = visibleWidth(line);\n\t\tconst padNeeded = Math.max(0, width - visLen);\n\t\tconst padded = line + \" \".repeat(padNeeded);\n\n\t\tif (this.bgFn) {\n\t\t\treturn applyBackgroundToLine(padded, width, this.bgFn);\n\t\t}\n\t\treturn padded;\n\t}\n}\n"]}
@@ -31,6 +31,16 @@ export class Box implements Component {
31
31
  this.invalidateCache();
32
32
  }
33
33
 
34
+ insertChildBefore(component: Component, before: Component): void {
35
+ const index = this.children.indexOf(before);
36
+ if (index !== -1) {
37
+ this.children.splice(index, 0, component);
38
+ } else {
39
+ this.children.push(component);
40
+ }
41
+ this.invalidateCache();
42
+ }
43
+
34
44
  removeChild(component: Component): void {
35
45
  const index = this.children.indexOf(component);
36
46
  if (index !== -1) {
package/pkg/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@glittercowboy/gsd",
3
- "version": "2.46.1",
3
+ "version": "2.47.0",
4
4
  "piConfig": {
5
5
  "name": "gsd",
6
6
  "configDir": ".gsd"
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Claude Code CLI Provider Extension
3
+ *
4
+ * Registers a model provider that delegates inference to the user's
5
+ * locally-installed Claude Code CLI via the official Agent SDK.
6
+ *
7
+ * Users with a Claude Code subscription (Pro/Max/Team) get access to
8
+ * subsidized inference through GSD's UI — no API key required.
9
+ *
10
+ * TOS-compliant: uses Anthropic's official `@anthropic-ai/claude-agent-sdk`,
11
+ * never touches credentials, never offers a login flow.
12
+ */
13
+
14
+ import type { ExtensionAPI } from "@gsd/pi-coding-agent";
15
+ import { CLAUDE_CODE_MODELS } from "./models.js";
16
+ import { isClaudeCodeReady } from "./readiness.js";
17
+ import { streamViaClaudeCode } from "./stream-adapter.js";
18
+
19
+ export default function claudeCodeCli(pi: ExtensionAPI) {
20
+ pi.registerProvider("claude-code", {
21
+ authMode: "externalCli",
22
+ api: "anthropic-messages",
23
+ baseUrl: "local://claude-code",
24
+ isReady: isClaudeCodeReady,
25
+ streamSimple: streamViaClaudeCode,
26
+ models: CLAUDE_CODE_MODELS,
27
+ });
28
+ }
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Model definitions for the Claude Code CLI provider.
3
+ *
4
+ * Costs are zero because inference is covered by the user's Claude Code
5
+ * subscription. The SDK's `result` message still provides token counts
6
+ * for display in the TUI.
7
+ *
8
+ * Context windows and max tokens match the Anthropic API definitions
9
+ * in models.generated.ts.
10
+ */
11
+
12
+ const ZERO_COST = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 };
13
+
14
+ export const CLAUDE_CODE_MODELS = [
15
+ {
16
+ id: "claude-opus-4-6",
17
+ name: "Claude Opus 4.6 (via Claude Code)",
18
+ reasoning: true,
19
+ input: ["text", "image"] as ("text" | "image")[],
20
+ cost: ZERO_COST,
21
+ contextWindow: 1_000_000,
22
+ maxTokens: 128_000,
23
+ },
24
+ {
25
+ id: "claude-sonnet-4-6",
26
+ name: "Claude Sonnet 4.6 (via Claude Code)",
27
+ reasoning: true,
28
+ input: ["text", "image"] as ("text" | "image")[],
29
+ cost: ZERO_COST,
30
+ contextWindow: 1_000_000,
31
+ maxTokens: 64_000,
32
+ },
33
+ {
34
+ id: "claude-haiku-4-5",
35
+ name: "Claude Haiku 4.5 (via Claude Code)",
36
+ reasoning: true,
37
+ input: ["text", "image"] as ("text" | "image")[],
38
+ cost: ZERO_COST,
39
+ contextWindow: 200_000,
40
+ maxTokens: 64_000,
41
+ },
42
+ ];
@@ -0,0 +1,11 @@
1
+ {
2
+ "name": "@gsd/claude-code-cli",
3
+ "private": true,
4
+ "version": "1.0.0",
5
+ "type": "module",
6
+ "pi": {
7
+ "extensions": [
8
+ "./index.ts"
9
+ ]
10
+ }
11
+ }
@@ -0,0 +1,258 @@
1
+ /**
2
+ * Content-block mapping helpers and streaming state tracker.
3
+ *
4
+ * Translates the Claude Agent SDK's `BetaRawMessageStreamEvent` sequence
5
+ * into GSD's `AssistantMessageEvent` deltas for incremental TUI rendering.
6
+ */
7
+
8
+ import type {
9
+ AssistantMessage,
10
+ AssistantMessageEvent,
11
+ ServerToolUseContent,
12
+ StopReason,
13
+ TextContent,
14
+ ThinkingContent,
15
+ ToolCall,
16
+ Usage,
17
+ WebSearchResultContent,
18
+ } from "@gsd/pi-ai";
19
+ import type { BetaContentBlock, BetaRawMessageStreamEvent, NonNullableUsage } from "./sdk-types.js";
20
+
21
+ // ---------------------------------------------------------------------------
22
+ // Content-block mapping helpers
23
+ // ---------------------------------------------------------------------------
24
+
25
+ /**
26
+ * Convert a single BetaContentBlock to the corresponding GSD content type.
27
+ */
28
+ export function mapContentBlock(
29
+ block: BetaContentBlock,
30
+ ): TextContent | ThinkingContent | ToolCall | ServerToolUseContent | WebSearchResultContent {
31
+ switch (block.type) {
32
+ case "text":
33
+ return { type: "text", text: block.text } satisfies TextContent;
34
+
35
+ case "thinking":
36
+ return {
37
+ type: "thinking",
38
+ thinking: block.thinking,
39
+ ...(block.signature ? { thinkingSignature: block.signature } : {}),
40
+ } satisfies ThinkingContent;
41
+
42
+ case "tool_use":
43
+ return {
44
+ type: "toolCall",
45
+ id: block.id,
46
+ name: block.name,
47
+ arguments: block.input,
48
+ } satisfies ToolCall;
49
+
50
+ case "server_tool_use":
51
+ return {
52
+ type: "serverToolUse",
53
+ id: block.id,
54
+ name: block.name,
55
+ input: block.input,
56
+ } satisfies ServerToolUseContent;
57
+
58
+ case "web_search_tool_result":
59
+ return {
60
+ type: "webSearchResult",
61
+ toolUseId: block.tool_use_id,
62
+ content: block.content,
63
+ } satisfies WebSearchResultContent;
64
+
65
+ default: {
66
+ const unknown = block as Record<string, unknown>;
67
+ return { type: "text", text: `[unknown content block: ${JSON.stringify(unknown)}]` };
68
+ }
69
+ }
70
+ }
71
+
72
+ export function mapStopReason(reason: string | null): StopReason {
73
+ switch (reason) {
74
+ case "end_turn":
75
+ case "stop_sequence":
76
+ return "stop";
77
+ case "max_tokens":
78
+ return "length";
79
+ case "tool_use":
80
+ return "toolUse";
81
+ default:
82
+ return "stop";
83
+ }
84
+ }
85
+
86
+ /**
87
+ * Convert SDK usage + total_cost_usd into GSD's Usage shape.
88
+ *
89
+ * The SDK does not break cost down per-bucket, so all cost is
90
+ * attributed to `cost.total`.
91
+ */
92
+ export function mapUsage(sdkUsage: NonNullableUsage, totalCostUsd: number): Usage {
93
+ return {
94
+ input: sdkUsage.input_tokens,
95
+ output: sdkUsage.output_tokens,
96
+ cacheRead: sdkUsage.cache_read_input_tokens,
97
+ cacheWrite: sdkUsage.cache_creation_input_tokens,
98
+ totalTokens:
99
+ sdkUsage.input_tokens +
100
+ sdkUsage.output_tokens +
101
+ sdkUsage.cache_read_input_tokens +
102
+ sdkUsage.cache_creation_input_tokens,
103
+ cost: {
104
+ input: 0,
105
+ output: 0,
106
+ cacheRead: 0,
107
+ cacheWrite: 0,
108
+ total: totalCostUsd,
109
+ },
110
+ };
111
+ }
112
+
113
+ // ---------------------------------------------------------------------------
114
+ // Zero-cost usage constant
115
+ // ---------------------------------------------------------------------------
116
+
117
+ export const ZERO_USAGE: Usage = {
118
+ input: 0,
119
+ output: 0,
120
+ cacheRead: 0,
121
+ cacheWrite: 0,
122
+ totalTokens: 0,
123
+ cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
124
+ };
125
+
126
+ // ---------------------------------------------------------------------------
127
+ // Streaming partial-message state tracker
128
+ // ---------------------------------------------------------------------------
129
+
130
+ /**
131
+ * Mutable accumulator that tracks the partial AssistantMessage being built
132
+ * from a sequence of stream_event messages. Produces AssistantMessageEvent
133
+ * deltas that the TUI can render incrementally.
134
+ */
135
+ export class PartialMessageBuilder {
136
+ private partial: AssistantMessage;
137
+ /** Map from stream-event `index` to our content array index. */
138
+ private indexMap = new Map<number, number>();
139
+ /** Accumulated JSON input string per tool_use block (keyed by stream index). */
140
+ private toolJsonAccum = new Map<number, string>();
141
+
142
+ constructor(model: string) {
143
+ this.partial = {
144
+ role: "assistant",
145
+ content: [],
146
+ api: "anthropic-messages",
147
+ provider: "claude-code",
148
+ model,
149
+ usage: { ...ZERO_USAGE },
150
+ stopReason: "stop",
151
+ timestamp: Date.now(),
152
+ };
153
+ }
154
+
155
+ get message(): AssistantMessage {
156
+ return this.partial;
157
+ }
158
+
159
+ /**
160
+ * Feed a BetaRawMessageStreamEvent and return the corresponding
161
+ * AssistantMessageEvent (or null if the event is not mapped).
162
+ */
163
+ handleEvent(event: BetaRawMessageStreamEvent): AssistantMessageEvent | null {
164
+ const streamIndex = event.index ?? 0;
165
+
166
+ switch (event.type) {
167
+ // ---- Block start ----
168
+ case "content_block_start": {
169
+ const block = event.content_block;
170
+ if (!block) return null;
171
+
172
+ const contentIndex = this.partial.content.length;
173
+ this.indexMap.set(streamIndex, contentIndex);
174
+
175
+ if (block.type === "text") {
176
+ this.partial.content.push({ type: "text", text: "" });
177
+ return { type: "text_start", contentIndex, partial: this.partial };
178
+ }
179
+ if (block.type === "thinking") {
180
+ this.partial.content.push({ type: "thinking", thinking: "" });
181
+ return { type: "thinking_start", contentIndex, partial: this.partial };
182
+ }
183
+ if (block.type === "tool_use") {
184
+ this.toolJsonAccum.set(streamIndex, "");
185
+ this.partial.content.push({
186
+ type: "toolCall",
187
+ id: block.id,
188
+ name: block.name,
189
+ arguments: {},
190
+ });
191
+ return { type: "toolcall_start", contentIndex, partial: this.partial };
192
+ }
193
+ if (block.type === "server_tool_use") {
194
+ this.partial.content.push({
195
+ type: "serverToolUse",
196
+ id: block.id,
197
+ name: block.name,
198
+ input: block.input,
199
+ });
200
+ return { type: "server_tool_use", contentIndex, partial: this.partial };
201
+ }
202
+ return null;
203
+ }
204
+
205
+ // ---- Block delta ----
206
+ case "content_block_delta": {
207
+ const contentIndex = this.indexMap.get(streamIndex);
208
+ if (contentIndex === undefined) return null;
209
+ const delta = event.delta;
210
+ if (!delta) return null;
211
+
212
+ if (delta.type === "text_delta" && typeof delta.text === "string") {
213
+ const existing = this.partial.content[contentIndex] as TextContent;
214
+ existing.text += delta.text;
215
+ return { type: "text_delta", contentIndex, delta: delta.text, partial: this.partial };
216
+ }
217
+ if (delta.type === "thinking_delta" && typeof delta.thinking === "string") {
218
+ const existing = this.partial.content[contentIndex] as ThinkingContent;
219
+ existing.thinking += delta.thinking;
220
+ return { type: "thinking_delta", contentIndex, delta: delta.thinking, partial: this.partial };
221
+ }
222
+ if (delta.type === "input_json_delta" && typeof delta.partial_json === "string") {
223
+ const accum = (this.toolJsonAccum.get(streamIndex) ?? "") + delta.partial_json;
224
+ this.toolJsonAccum.set(streamIndex, accum);
225
+ return { type: "toolcall_delta", contentIndex, delta: delta.partial_json, partial: this.partial };
226
+ }
227
+ return null;
228
+ }
229
+
230
+ // ---- Block stop ----
231
+ case "content_block_stop": {
232
+ const contentIndex = this.indexMap.get(streamIndex);
233
+ if (contentIndex === undefined) return null;
234
+ const block = this.partial.content[contentIndex];
235
+
236
+ if (block.type === "text") {
237
+ return { type: "text_end", contentIndex, content: block.text, partial: this.partial };
238
+ }
239
+ if (block.type === "thinking") {
240
+ return { type: "thinking_end", contentIndex, content: block.thinking, partial: this.partial };
241
+ }
242
+ if (block.type === "toolCall") {
243
+ const jsonStr = this.toolJsonAccum.get(streamIndex) ?? "{}";
244
+ try {
245
+ block.arguments = JSON.parse(jsonStr);
246
+ } catch {
247
+ block.arguments = { _raw: jsonStr };
248
+ }
249
+ return { type: "toolcall_end", contentIndex, toolCall: block, partial: this.partial };
250
+ }
251
+ return null;
252
+ }
253
+
254
+ default:
255
+ return null;
256
+ }
257
+ }
258
+ }
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Readiness check for the Claude Code CLI provider.
3
+ *
4
+ * Verifies the `claude` binary is installed and responsive.
5
+ * Result is cached for 30 seconds to avoid shelling out on every
6
+ * model-availability check.
7
+ */
8
+
9
+ import { execSync } from "node:child_process";
10
+
11
+ let cachedReady: boolean | null = null;
12
+ let lastCheckMs = 0;
13
+ const CHECK_INTERVAL_MS = 30_000;
14
+
15
+ export function isClaudeCodeReady(): boolean {
16
+ const now = Date.now();
17
+ if (cachedReady !== null && now - lastCheckMs < CHECK_INTERVAL_MS) {
18
+ return cachedReady;
19
+ }
20
+
21
+ try {
22
+ execSync("claude --version", { timeout: 5_000, stdio: "pipe" });
23
+ cachedReady = true;
24
+ } catch {
25
+ cachedReady = false;
26
+ }
27
+
28
+ lastCheckMs = now;
29
+ return cachedReady;
30
+ }
@@ -0,0 +1,149 @@
1
+ /**
2
+ * Lightweight type mirrors for the Claude Agent SDK.
3
+ *
4
+ * These stubs allow the extension to compile without a hard dependency on
5
+ * `@anthropic-ai/claude-agent-sdk`. The real SDK is imported dynamically
6
+ * at runtime in stream-adapter.ts.
7
+ */
8
+
9
+ /** UUID branded string from the SDK. */
10
+ export type UUID = string;
11
+
12
+ /** BetaMessage from the Anthropic SDK, as wrapped by SDKAssistantMessage. */
13
+ export interface BetaMessage {
14
+ id: string;
15
+ type: "message";
16
+ role: "assistant";
17
+ content: BetaContentBlock[];
18
+ model: string;
19
+ stop_reason: "end_turn" | "max_tokens" | "stop_sequence" | "tool_use" | null;
20
+ usage: { input_tokens: number; output_tokens: number };
21
+ }
22
+
23
+ export type BetaContentBlock =
24
+ | { type: "text"; text: string }
25
+ | { type: "thinking"; thinking: string; signature?: string }
26
+ | { type: "tool_use"; id: string; name: string; input: Record<string, unknown> }
27
+ | { type: "server_tool_use"; id: string; name: string; input: unknown }
28
+ | { type: "web_search_tool_result"; tool_use_id: string; content: unknown };
29
+
30
+ /** Streaming event emitted when includePartialMessages is true. */
31
+ export interface BetaRawMessageStreamEvent {
32
+ type: string;
33
+ index?: number;
34
+ content_block?: BetaContentBlock;
35
+ delta?: Record<string, unknown>;
36
+ }
37
+
38
+ export interface SDKAssistantMessage {
39
+ type: "assistant";
40
+ uuid: UUID;
41
+ session_id: string;
42
+ message: BetaMessage;
43
+ parent_tool_use_id: string | null;
44
+ error?: { type: string; message: string };
45
+ }
46
+
47
+ export interface SDKUserMessage {
48
+ type: "user";
49
+ uuid?: UUID;
50
+ session_id: string;
51
+ message: unknown;
52
+ parent_tool_use_id: string | null;
53
+ isSynthetic?: boolean;
54
+ tool_use_result?: unknown;
55
+ }
56
+
57
+ export interface SDKSystemMessage {
58
+ type: "system";
59
+ subtype: "init";
60
+ [key: string]: unknown;
61
+ }
62
+
63
+ export interface SDKStatusMessage {
64
+ type: "system";
65
+ subtype: "status";
66
+ status: "compacting" | null;
67
+ uuid: UUID;
68
+ session_id: string;
69
+ }
70
+
71
+ export interface SDKPartialAssistantMessage {
72
+ type: "stream_event";
73
+ event: BetaRawMessageStreamEvent;
74
+ parent_tool_use_id: string | null;
75
+ uuid: UUID;
76
+ session_id: string;
77
+ }
78
+
79
+ export interface SDKToolProgressMessage {
80
+ type: "tool_progress";
81
+ tool_use_id: string;
82
+ tool_name: string;
83
+ parent_tool_use_id: string | null;
84
+ elapsed_time_seconds: number;
85
+ task_id?: string;
86
+ uuid: UUID;
87
+ session_id: string;
88
+ }
89
+
90
+ export interface NonNullableUsage {
91
+ input_tokens: number;
92
+ output_tokens: number;
93
+ cache_read_input_tokens: number;
94
+ cache_creation_input_tokens: number;
95
+ }
96
+
97
+ export type SDKResultMessage =
98
+ | {
99
+ type: "result";
100
+ subtype: "success";
101
+ uuid: UUID;
102
+ session_id: string;
103
+ duration_ms: number;
104
+ duration_api_ms: number;
105
+ is_error: boolean;
106
+ num_turns: number;
107
+ result: string;
108
+ stop_reason: string | null;
109
+ total_cost_usd: number;
110
+ usage: NonNullableUsage;
111
+ }
112
+ | {
113
+ type: "result";
114
+ subtype:
115
+ | "error_max_turns"
116
+ | "error_during_execution"
117
+ | "error_max_budget_usd"
118
+ | "error_max_structured_output_retries";
119
+ uuid: UUID;
120
+ session_id: string;
121
+ duration_ms: number;
122
+ duration_api_ms: number;
123
+ is_error: boolean;
124
+ num_turns: number;
125
+ stop_reason: string | null;
126
+ total_cost_usd: number;
127
+ usage: NonNullableUsage;
128
+ errors: string[];
129
+ };
130
+
131
+ /** Catch-all for SDK message types we don't map. */
132
+ export interface SDKOtherMessage {
133
+ type: string;
134
+ [key: string]: unknown;
135
+ }
136
+
137
+ /**
138
+ * Union of all SDK message types this extension handles.
139
+ * Mirrors the real `SDKMessage` from `@anthropic-ai/claude-agent-sdk`.
140
+ */
141
+ export type SDKMessage =
142
+ | SDKAssistantMessage
143
+ | SDKUserMessage
144
+ | SDKResultMessage
145
+ | SDKSystemMessage
146
+ | SDKStatusMessage
147
+ | SDKPartialAssistantMessage
148
+ | SDKToolProgressMessage
149
+ | SDKOtherMessage;