sparkecoder 0.1.16 → 0.1.18

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 (216) hide show
  1. package/README.md +1 -1
  2. package/dist/cli.js +15 -6
  3. package/dist/cli.js.map +1 -1
  4. package/dist/index.js +9 -2
  5. package/dist/index.js.map +1 -1
  6. package/dist/server/index.js +9 -2
  7. package/dist/server/index.js.map +1 -1
  8. package/package.json +1 -1
  9. package/web/.next/BUILD_ID +1 -1
  10. package/web/.next/standalone/web/.next/BUILD_ID +1 -1
  11. package/web/.next/standalone/web/.next/build-manifest.json +2 -2
  12. package/web/.next/standalone/web/.next/prerender-manifest.json +3 -3
  13. package/web/.next/standalone/web/.next/server/app/(main)/page.js +3 -3
  14. package/web/.next/standalone/web/.next/server/app/(main)/page.js.nft.json +1 -1
  15. package/web/.next/standalone/web/.next/server/app/(main)/page_client-reference-manifest.js +1 -1
  16. package/web/.next/standalone/web/.next/server/app/(main)/session/[id]/page.js +3 -3
  17. package/web/.next/standalone/web/.next/server/app/(main)/session/[id]/page.js.nft.json +1 -1
  18. package/web/.next/standalone/web/.next/server/app/(main)/session/[id]/page_client-reference-manifest.js +1 -1
  19. package/web/.next/standalone/web/.next/server/app/_global-error/page.js +2 -2
  20. package/web/.next/standalone/web/.next/server/app/_global-error/page.js.nft.json +1 -1
  21. package/web/.next/standalone/web/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  22. package/web/.next/standalone/web/.next/server/app/_global-error.html +2 -2
  23. package/web/.next/standalone/web/.next/server/app/_global-error.rsc +1 -1
  24. package/web/.next/standalone/web/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  25. package/web/.next/standalone/web/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  26. package/web/.next/standalone/web/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  27. package/web/.next/standalone/web/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  28. package/web/.next/standalone/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  29. package/web/.next/standalone/web/.next/server/app/_not-found/page.js +3 -3
  30. package/web/.next/standalone/web/.next/server/app/_not-found/page.js.nft.json +1 -1
  31. package/web/.next/standalone/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  32. package/web/.next/standalone/web/.next/server/app/_not-found.html +1 -1
  33. package/web/.next/standalone/web/.next/server/app/_not-found.rsc +13 -12
  34. package/web/.next/standalone/web/.next/server/app/_not-found.segments/_full.segment.rsc +13 -12
  35. package/web/.next/standalone/web/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  36. package/web/.next/standalone/web/.next/server/app/_not-found.segments/_index.segment.rsc +4 -3
  37. package/web/.next/standalone/web/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  38. package/web/.next/standalone/web/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  39. package/web/.next/standalone/web/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  40. package/web/.next/standalone/web/.next/server/app/api/config/route.js +1 -1
  41. package/web/.next/standalone/web/.next/server/app/api/config/route.js.nft.json +1 -1
  42. package/web/.next/standalone/web/.next/server/app/api/health/route.js +1 -1
  43. package/web/.next/standalone/web/.next/server/app/api/health/route.js.nft.json +1 -1
  44. package/web/.next/standalone/web/.next/server/app/index.html +1 -1
  45. package/web/.next/standalone/web/.next/server/app/index.rsc +20 -19
  46. package/web/.next/standalone/web/.next/server/app/index.segments/!KG1haW4p/__PAGE__.segment.rsc +2 -2
  47. package/web/.next/standalone/web/.next/server/app/index.segments/!KG1haW4p.segment.rsc +2 -2
  48. package/web/.next/standalone/web/.next/server/app/index.segments/_full.segment.rsc +20 -19
  49. package/web/.next/standalone/web/.next/server/app/index.segments/_head.segment.rsc +1 -1
  50. package/web/.next/standalone/web/.next/server/app/index.segments/_index.segment.rsc +4 -3
  51. package/web/.next/standalone/web/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  52. package/web/.next/standalone/web/.next/server/chunks/[root-of-the-server]__36edac7c._.js +3 -0
  53. package/web/.next/standalone/web/.next/server/chunks/[root-of-the-server]__74ebc442._.js +3 -0
  54. package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_1b669458._.js → 2374f_1d78db71._.js} +1 -1
  55. package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_10a13bff._.js → 2374f_30f9df13._.js} +1 -1
  56. package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_42ae2ff5._.js → 2374f_378282b1._.js} +1 -1
  57. package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_21324e82._.js → 2374f_5de336d2._.js} +1 -1
  58. package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_ba57e34a._.js → 2374f_8825dcc9._.js} +1 -1
  59. package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_47e888fe._.js → 2374f_9bf3c7f3._.js} +2 -2
  60. package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_63823b79._.js → 2374f_bbc99511._.js} +1 -1
  61. package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_9c15fcde._.js → 2374f_d94c2b70._.js} +1 -1
  62. package/web/.next/standalone/web/.next/server/chunks/ssr/[root-of-the-server]__0f6b5fa7._.js +3 -0
  63. package/web/.next/standalone/web/.next/server/chunks/ssr/{[root-of-the-server]__e1f2996b._.js → [root-of-the-server]__513c6b45._.js} +2 -2
  64. package/web/.next/standalone/web/.next/server/chunks/ssr/{[root-of-the-server]__da40fb08._.js → [root-of-the-server]__7f04455b._.js} +2 -2
  65. package/web/.next/standalone/web/.next/server/chunks/ssr/[root-of-the-server]__be5e2967._.js +3 -0
  66. package/web/.next/standalone/web/.next/server/chunks/ssr/{[root-of-the-server]__10aab2ca._.js → [root-of-the-server]__c3a1e22c._.js} +2 -2
  67. package/web/.next/standalone/web/.next/server/chunks/ssr/[root-of-the-server]__d2ce4b79._.js +3 -0
  68. package/web/.next/standalone/web/.next/server/chunks/ssr/[root-of-the-server]__de58a952._.js +3 -0
  69. package/web/.next/standalone/web/.next/server/chunks/ssr/[root-of-the-server]__f18f92f4._.js +10 -0
  70. package/web/.next/standalone/web/.next/server/chunks/ssr/{web_d3c9d897._.js → web_19b6934c._.js} +2 -2
  71. package/web/.next/standalone/web/.next/server/chunks/ssr/web_96bca05b._.js +1 -1
  72. package/web/.next/standalone/web/.next/server/pages/404.html +1 -1
  73. package/web/.next/standalone/web/.next/server/pages/500.html +2 -2
  74. package/web/.next/standalone/web/.next/server/server-reference-manifest.js +1 -1
  75. package/web/.next/standalone/web/.next/server/server-reference-manifest.json +1 -1
  76. package/web/.next/standalone/web/.next/static/chunks/25a97bfee12ea1f6.js +1 -0
  77. package/web/.next/standalone/web/.next/static/chunks/2f9f08f6c6276b0f.js +1 -0
  78. package/web/.next/{static/chunks/c7e9604ccdb5cb09.js → standalone/web/.next/static/chunks/5ec82ce8f3aabaf0.js} +3 -3
  79. package/web/.next/standalone/web/.next/static/static/chunks/25a97bfee12ea1f6.js +1 -0
  80. package/web/.next/standalone/web/.next/static/static/chunks/2f9f08f6c6276b0f.js +1 -0
  81. package/web/.next/standalone/web/.next/static/{chunks/c7e9604ccdb5cb09.js → static/chunks/5ec82ce8f3aabaf0.js} +3 -3
  82. package/web/.next/standalone/web/README.md +63 -0
  83. package/web/.next/standalone/web/components.json +22 -0
  84. package/web/.next/standalone/web/eslint.config.mjs +18 -0
  85. package/web/.next/standalone/web/next.config.ts +9 -0
  86. package/web/.next/standalone/web/package-lock.json +15061 -0
  87. package/web/.next/standalone/web/postcss.config.mjs +7 -0
  88. package/web/.next/standalone/web/runtime-config.json +3 -0
  89. package/web/.next/standalone/web/src/app/(main)/layout.tsx +22 -0
  90. package/web/.next/standalone/web/src/app/(main)/page.tsx +230 -0
  91. package/web/.next/standalone/web/src/app/(main)/session/[id]/page.tsx +64 -0
  92. package/web/.next/standalone/web/src/app/api/config/route.ts +106 -0
  93. package/web/.next/standalone/web/src/app/api/health/route.ts +63 -0
  94. package/web/.next/standalone/web/src/app/apple-icon.png +0 -0
  95. package/web/.next/standalone/web/src/app/favicon.ico +0 -0
  96. package/web/.next/standalone/web/src/app/globals.css +311 -0
  97. package/web/.next/standalone/web/src/app/icon.png +0 -0
  98. package/web/.next/standalone/web/src/app/layout.tsx +100 -0
  99. package/web/.next/standalone/web/src/app/opengraph-image.png +0 -0
  100. package/web/.next/standalone/web/src/app/twitter-image.png +0 -0
  101. package/web/.next/standalone/web/src/components/ai-elements/agent.tsx +141 -0
  102. package/web/.next/standalone/web/src/components/ai-elements/artifact.tsx +147 -0
  103. package/web/.next/standalone/web/src/components/ai-elements/attachments.tsx +421 -0
  104. package/web/.next/standalone/web/src/components/ai-elements/audio-player.tsx +231 -0
  105. package/web/.next/standalone/web/src/components/ai-elements/bash-tool.tsx +299 -0
  106. package/web/.next/standalone/web/src/components/ai-elements/canvas.tsx +22 -0
  107. package/web/.next/standalone/web/src/components/ai-elements/chain-of-thought.tsx +231 -0
  108. package/web/.next/standalone/web/src/components/ai-elements/checkpoint.tsx +71 -0
  109. package/web/.next/standalone/web/src/components/ai-elements/code-block.tsx +529 -0
  110. package/web/.next/standalone/web/src/components/ai-elements/commit.tsx +448 -0
  111. package/web/.next/standalone/web/src/components/ai-elements/confirmation.tsx +176 -0
  112. package/web/.next/standalone/web/src/components/ai-elements/connection.tsx +28 -0
  113. package/web/.next/standalone/web/src/components/ai-elements/context.tsx +408 -0
  114. package/web/.next/standalone/web/src/components/ai-elements/controls.tsx +18 -0
  115. package/web/.next/standalone/web/src/components/ai-elements/conversation.tsx +104 -0
  116. package/web/.next/standalone/web/src/components/ai-elements/edge.tsx +140 -0
  117. package/web/.next/standalone/web/src/components/ai-elements/environment-variables.tsx +295 -0
  118. package/web/.next/standalone/web/src/components/ai-elements/file-tree.tsx +258 -0
  119. package/web/.next/standalone/web/src/components/ai-elements/image.tsx +24 -0
  120. package/web/.next/standalone/web/src/components/ai-elements/inline-citation.tsx +287 -0
  121. package/web/.next/standalone/web/src/components/ai-elements/linter-tool.tsx +330 -0
  122. package/web/.next/standalone/web/src/components/ai-elements/load-skill-tool.tsx +215 -0
  123. package/web/.next/standalone/web/src/components/ai-elements/loader.tsx +96 -0
  124. package/web/.next/standalone/web/src/components/ai-elements/message.tsx +421 -0
  125. package/web/.next/standalone/web/src/components/ai-elements/mic-selector.tsx +370 -0
  126. package/web/.next/standalone/web/src/components/ai-elements/model-selector.tsx +211 -0
  127. package/web/.next/standalone/web/src/components/ai-elements/node.tsx +71 -0
  128. package/web/.next/standalone/web/src/components/ai-elements/open-in-chat.tsx +365 -0
  129. package/web/.next/standalone/web/src/components/ai-elements/package-info.tsx +233 -0
  130. package/web/.next/standalone/web/src/components/ai-elements/panel.tsx +15 -0
  131. package/web/.next/standalone/web/src/components/ai-elements/persona.tsx +270 -0
  132. package/web/.next/standalone/web/src/components/ai-elements/plan.tsx +142 -0
  133. package/web/.next/standalone/web/src/components/ai-elements/prompt-input.tsx +1263 -0
  134. package/web/.next/standalone/web/src/components/ai-elements/queue.tsx +274 -0
  135. package/web/.next/standalone/web/src/components/ai-elements/read-file-tool.tsx +251 -0
  136. package/web/.next/standalone/web/src/components/ai-elements/reasoning.tsx +198 -0
  137. package/web/.next/standalone/web/src/components/ai-elements/sandbox.tsx +131 -0
  138. package/web/.next/standalone/web/src/components/ai-elements/schema-display.tsx +458 -0
  139. package/web/.next/standalone/web/src/components/ai-elements/shimmer.tsx +64 -0
  140. package/web/.next/standalone/web/src/components/ai-elements/snippet.tsx +139 -0
  141. package/web/.next/standalone/web/src/components/ai-elements/sources.tsx +77 -0
  142. package/web/.next/standalone/web/src/components/ai-elements/speech-input.tsx +300 -0
  143. package/web/.next/standalone/web/src/components/ai-elements/stack-trace.tsx +482 -0
  144. package/web/.next/standalone/web/src/components/ai-elements/suggestion.tsx +60 -0
  145. package/web/.next/standalone/web/src/components/ai-elements/task.tsx +87 -0
  146. package/web/.next/standalone/web/src/components/ai-elements/terminal.tsx +261 -0
  147. package/web/.next/standalone/web/src/components/ai-elements/test-results.tsx +485 -0
  148. package/web/.next/standalone/web/src/components/ai-elements/todo-panel.tsx +178 -0
  149. package/web/.next/standalone/web/src/components/ai-elements/todo-tool.tsx +246 -0
  150. package/web/.next/standalone/web/src/components/ai-elements/tool.tsx +174 -0
  151. package/web/.next/standalone/web/src/components/ai-elements/toolbar.tsx +16 -0
  152. package/web/.next/standalone/web/src/components/ai-elements/transcription.tsx +124 -0
  153. package/web/.next/standalone/web/src/components/ai-elements/voice-selector.tsx +479 -0
  154. package/web/.next/standalone/web/src/components/ai-elements/web-preview.tsx +263 -0
  155. package/web/.next/standalone/web/src/components/ai-elements/write-file-tool.tsx +368 -0
  156. package/web/.next/standalone/web/src/components/api-init.tsx +21 -0
  157. package/web/.next/standalone/web/src/components/chat-interface.tsx +2074 -0
  158. package/web/.next/standalone/web/src/components/sessions-sidebar.tsx +875 -0
  159. package/web/.next/standalone/web/src/components/ui/accordion.tsx +66 -0
  160. package/web/.next/standalone/web/src/components/ui/alert.tsx +66 -0
  161. package/web/.next/standalone/web/src/components/ui/avatar.tsx +109 -0
  162. package/web/.next/standalone/web/src/components/ui/badge.tsx +48 -0
  163. package/web/.next/standalone/web/src/components/ui/button-group.tsx +83 -0
  164. package/web/.next/standalone/web/src/components/ui/button.tsx +64 -0
  165. package/web/.next/standalone/web/src/components/ui/card.tsx +92 -0
  166. package/web/.next/standalone/web/src/components/ui/carousel.tsx +241 -0
  167. package/web/.next/standalone/web/src/components/ui/collapsible.tsx +33 -0
  168. package/web/.next/standalone/web/src/components/ui/command.tsx +184 -0
  169. package/web/.next/standalone/web/src/components/ui/dialog.tsx +158 -0
  170. package/web/.next/standalone/web/src/components/ui/dropdown-menu.tsx +257 -0
  171. package/web/.next/standalone/web/src/components/ui/hover-card.tsx +44 -0
  172. package/web/.next/standalone/web/src/components/ui/input-group.tsx +170 -0
  173. package/web/.next/standalone/web/src/components/ui/input.tsx +22 -0
  174. package/web/.next/standalone/web/src/components/ui/label.tsx +24 -0
  175. package/web/.next/standalone/web/src/components/ui/popover.tsx +89 -0
  176. package/web/.next/standalone/web/src/components/ui/progress.tsx +31 -0
  177. package/web/.next/standalone/web/src/components/ui/scroll-area.tsx +58 -0
  178. package/web/.next/standalone/web/src/components/ui/select.tsx +190 -0
  179. package/web/.next/standalone/web/src/components/ui/separator.tsx +28 -0
  180. package/web/.next/standalone/web/src/components/ui/sheet.tsx +143 -0
  181. package/web/.next/standalone/web/src/components/ui/sidebar.tsx +726 -0
  182. package/web/.next/standalone/web/src/components/ui/skeleton.tsx +13 -0
  183. package/web/.next/standalone/web/src/components/ui/switch.tsx +35 -0
  184. package/web/.next/standalone/web/src/components/ui/tabs.tsx +91 -0
  185. package/web/.next/standalone/web/src/components/ui/textarea.tsx +18 -0
  186. package/web/.next/standalone/web/src/components/ui/tooltip.tsx +61 -0
  187. package/web/.next/standalone/web/src/hooks/use-mobile.ts +19 -0
  188. package/web/.next/standalone/web/src/hooks/use-sessions.ts +28 -0
  189. package/web/.next/standalone/web/src/lib/api.ts +568 -0
  190. package/web/.next/standalone/web/src/lib/config.ts +178 -0
  191. package/web/.next/standalone/web/src/lib/utils.ts +6 -0
  192. package/web/.next/standalone/web/src/test/api.test.ts +125 -0
  193. package/web/.next/standalone/web/src/test/setup.ts +1 -0
  194. package/web/.next/standalone/web/tsconfig.json +43 -0
  195. package/web/.next/standalone/web/vitest.config.ts +17 -0
  196. package/web/.next/static/chunks/25a97bfee12ea1f6.js +1 -0
  197. package/web/.next/static/chunks/2f9f08f6c6276b0f.js +1 -0
  198. package/web/.next/{standalone/web/.next/static/static/chunks/c7e9604ccdb5cb09.js → static/chunks/5ec82ce8f3aabaf0.js} +3 -3
  199. package/web/.next/standalone/web/.next/server/chunks/[root-of-the-server]__7ba4776a._.js +0 -3
  200. package/web/.next/standalone/web/.next/server/chunks/[root-of-the-server]__d907af4e._.js +0 -3
  201. package/web/.next/standalone/web/.next/server/chunks/ssr/[root-of-the-server]__3a7ef2b7._.js +0 -3
  202. package/web/.next/standalone/web/.next/server/chunks/ssr/[root-of-the-server]__5369f47d._.js +0 -3
  203. package/web/.next/standalone/web/.next/server/chunks/ssr/[root-of-the-server]__c1f0d54f._.js +0 -3
  204. package/web/.next/standalone/web/.next/server/chunks/ssr/[root-of-the-server]__fd3d4d25._.js +0 -10
  205. package/web/.next/standalone/web/.next/static/chunks/85dcb2949e032468.js +0 -1
  206. package/web/.next/standalone/web/.next/static/static/chunks/85dcb2949e032468.js +0 -1
  207. package/web/.next/static/chunks/85dcb2949e032468.js +0 -1
  208. /package/web/.next/standalone/web/.next/static/{3Y3VQCOuXe9_MLEdym2IJ → omR5ZCfnSKddq7WtwIK6Y}/_buildManifest.js +0 -0
  209. /package/web/.next/standalone/web/.next/static/{3Y3VQCOuXe9_MLEdym2IJ → omR5ZCfnSKddq7WtwIK6Y}/_clientMiddlewareManifest.json +0 -0
  210. /package/web/.next/standalone/web/.next/static/{3Y3VQCOuXe9_MLEdym2IJ → omR5ZCfnSKddq7WtwIK6Y}/_ssgManifest.js +0 -0
  211. /package/web/.next/standalone/web/.next/static/static/{3Y3VQCOuXe9_MLEdym2IJ → omR5ZCfnSKddq7WtwIK6Y}/_buildManifest.js +0 -0
  212. /package/web/.next/standalone/web/.next/static/static/{3Y3VQCOuXe9_MLEdym2IJ → omR5ZCfnSKddq7WtwIK6Y}/_clientMiddlewareManifest.json +0 -0
  213. /package/web/.next/standalone/web/.next/static/static/{3Y3VQCOuXe9_MLEdym2IJ → omR5ZCfnSKddq7WtwIK6Y}/_ssgManifest.js +0 -0
  214. /package/web/.next/static/{3Y3VQCOuXe9_MLEdym2IJ → omR5ZCfnSKddq7WtwIK6Y}/_buildManifest.js +0 -0
  215. /package/web/.next/static/{3Y3VQCOuXe9_MLEdym2IJ → omR5ZCfnSKddq7WtwIK6Y}/_clientMiddlewareManifest.json +0 -0
  216. /package/web/.next/static/{3Y3VQCOuXe9_MLEdym2IJ → omR5ZCfnSKddq7WtwIK6Y}/_ssgManifest.js +0 -0
@@ -0,0 +1,568 @@
1
+ import { getApiUrl } from './config';
2
+
3
+ // Get API base URL synchronously from localStorage
4
+ function getApiBase(): string {
5
+ return getApiUrl();
6
+ }
7
+
8
+ export interface SessionConfig {
9
+ toolApprovals?: Record<string, boolean>;
10
+ }
11
+
12
+ export interface Session {
13
+ id: string;
14
+ name: string;
15
+ model: string;
16
+ workingDirectory: string;
17
+ status: 'active' | 'waiting' | 'completed' | 'error';
18
+ isStreaming?: boolean;
19
+ config?: SessionConfig;
20
+ createdAt: string;
21
+ updatedAt?: string;
22
+ messageCount?: number;
23
+ }
24
+
25
+ // AI SDK ModelMessage content types
26
+ export interface TextContentPart {
27
+ type: 'text';
28
+ text: string;
29
+ }
30
+
31
+ export interface ReasoningContentPart {
32
+ type: 'reasoning';
33
+ text: string;
34
+ }
35
+
36
+ export interface ToolCallContentPart {
37
+ type: 'tool-call';
38
+ toolCallId: string;
39
+ toolName: string;
40
+ input?: unknown;
41
+ args?: unknown; // AI SDK uses 'args' in some places
42
+ }
43
+
44
+ export interface ToolResultContentPart {
45
+ type: 'tool-result';
46
+ toolCallId: string;
47
+ toolName: string;
48
+ output?: unknown;
49
+ result?: unknown; // AI SDK uses 'result' in some places
50
+ }
51
+
52
+ export type ContentPart = TextContentPart | ReasoningContentPart | ToolCallContentPart | ToolResultContentPart;
53
+
54
+ // AI SDK ModelMessage format (as returned by backend)
55
+ export interface Message {
56
+ id: string;
57
+ role: 'user' | 'assistant' | 'tool' | 'system';
58
+ // Content can be a string OR array of content parts
59
+ content: string | ContentPart[];
60
+ createdAt: string;
61
+ }
62
+
63
+ export interface ToolCall {
64
+ toolCallId: string;
65
+ toolName: string;
66
+ input: unknown;
67
+ }
68
+
69
+ export interface ToolResult {
70
+ toolCallId: string;
71
+ toolName: string;
72
+ output: unknown;
73
+ }
74
+
75
+ export interface PendingApproval {
76
+ id: string;
77
+ toolCallId: string;
78
+ toolName: string;
79
+ input: unknown;
80
+ createdAt: string;
81
+ }
82
+
83
+ export interface TodoItem {
84
+ id: string;
85
+ content: string;
86
+ status: 'pending' | 'in_progress' | 'completed' | 'cancelled';
87
+ order: number;
88
+ createdAt: string;
89
+ updatedAt: string;
90
+ }
91
+
92
+ export interface TodosResponse {
93
+ todos: TodoItem[];
94
+ stats: {
95
+ total: number;
96
+ pending: number;
97
+ inProgress: number;
98
+ completed: number;
99
+ cancelled: number;
100
+ };
101
+ nextTodo: {
102
+ id: string;
103
+ content: string;
104
+ status: string;
105
+ } | null;
106
+ }
107
+
108
+ // Sessions API
109
+ export async function getSessions(): Promise<Session[]> {
110
+ const res = await fetch(`${getApiBase()}/sessions`);
111
+ const data = await res.json();
112
+ return data.sessions || [];
113
+ }
114
+
115
+ export async function getSession(id: string): Promise<Session> {
116
+ const res = await fetch(`${getApiBase()}/sessions/${id}`);
117
+ return res.json();
118
+ }
119
+
120
+ export async function createSession(params: {
121
+ name?: string;
122
+ model?: string;
123
+ workingDirectory?: string;
124
+ toolApprovals?: Record<string, boolean>;
125
+ }): Promise<Session> {
126
+ const res = await fetch(`${getApiBase()}/sessions`, {
127
+ method: 'POST',
128
+ headers: { 'Content-Type': 'application/json' },
129
+ body: JSON.stringify(params),
130
+ });
131
+ return res.json();
132
+ }
133
+
134
+ export async function deleteSession(id: string): Promise<void> {
135
+ await fetch(`${getApiBase()}/sessions/${id}`, { method: 'DELETE' });
136
+ }
137
+
138
+ export async function getSessionTodos(sessionId: string): Promise<TodosResponse> {
139
+ const res = await fetch(`${getApiBase()}/sessions/${sessionId}/todos`);
140
+ return res.json();
141
+ }
142
+
143
+ export async function updateSession(id: string, updates: { model?: string; name?: string; toolApprovals?: Record<string, boolean> }): Promise<Session> {
144
+ const res = await fetch(`${getApiBase()}/sessions/${id}`, {
145
+ method: 'PATCH',
146
+ headers: { 'Content-Type': 'application/json' },
147
+ body: JSON.stringify(updates),
148
+ });
149
+ return res.json();
150
+ }
151
+
152
+ /**
153
+ * Update a specific tool's approval setting for a session
154
+ * If `requiresApproval` is false, the tool will run without asking
155
+ * Note: Backend merges with existing tool approvals, so we only need to send the changed tool
156
+ */
157
+ export async function updateToolApproval(
158
+ sessionId: string,
159
+ toolName: string,
160
+ requiresApproval: boolean,
161
+ _currentConfig?: SessionConfig // Kept for backwards compatibility but not used
162
+ ): Promise<Session> {
163
+ return updateSession(sessionId, {
164
+ toolApprovals: {
165
+ [toolName]: requiresApproval,
166
+ },
167
+ });
168
+ }
169
+
170
+ // Agent API
171
+ export async function getSessionMessages(sessionId: string): Promise<Message[]> {
172
+ const res = await fetch(`${getApiBase()}/sessions/${sessionId}/messages`);
173
+ const data = await res.json();
174
+ return data.messages || [];
175
+ }
176
+
177
+ export async function getPendingApprovals(sessionId: string): Promise<PendingApproval[]> {
178
+ const res = await fetch(`${getApiBase()}/agents/${sessionId}/approvals`);
179
+ const data = await res.json();
180
+ return data.pendingApprovals || [];
181
+ }
182
+
183
+ export async function approveExecution(sessionId: string, executionId: string): Promise<void> {
184
+ await fetch(`${getApiBase()}/agents/${sessionId}/approve/${executionId}`, {
185
+ method: 'POST',
186
+ });
187
+ }
188
+
189
+ export async function rejectExecution(
190
+ sessionId: string,
191
+ executionId: string,
192
+ reason?: string
193
+ ): Promise<void> {
194
+ await fetch(`${getApiBase()}/agents/${sessionId}/reject/${executionId}`, {
195
+ method: 'POST',
196
+ headers: { 'Content-Type': 'application/json' },
197
+ body: JSON.stringify({ reason }),
198
+ });
199
+ }
200
+
201
+ // Get active stream info for a session
202
+ export async function getActiveStream(sessionId: string): Promise<{
203
+ hasActiveStream: boolean;
204
+ stream: { streamId: string; status: string; createdAt: string } | null;
205
+ }> {
206
+ const res = await fetch(`${getApiBase()}/agents/${sessionId}/stream`);
207
+ return res.json();
208
+ }
209
+
210
+ // Abort/stop an active stream for a session
211
+ export async function abortStream(sessionId: string): Promise<{
212
+ success: boolean;
213
+ streamId?: string;
214
+ aborted?: boolean;
215
+ message?: string;
216
+ }> {
217
+ const res = await fetch(`${getApiBase()}/agents/${sessionId}/abort`, {
218
+ method: 'POST',
219
+ });
220
+ return res.json();
221
+ }
222
+
223
+ // Internal helper to parse SSE stream
224
+ function parseSSEStream(
225
+ response: Response,
226
+ onEvent: (event: SSEEvent) => void,
227
+ onStreamId?: (streamId: string) => void
228
+ ): Promise<void> {
229
+ return new Promise(async (resolve, reject) => {
230
+ const reader = response.body?.getReader();
231
+ if (!reader) {
232
+ reject(new Error('No response body'));
233
+ return;
234
+ }
235
+
236
+ const decoder = new TextDecoder();
237
+ let buffer = '';
238
+
239
+ try {
240
+ while (true) {
241
+ const { done, value } = await reader.read();
242
+ if (done) break;
243
+
244
+ buffer += decoder.decode(value, { stream: true });
245
+ const lines = buffer.split('\n');
246
+ buffer = lines.pop() || '';
247
+
248
+ for (const line of lines) {
249
+ if (line.startsWith('data: ')) {
250
+ const data = line.slice(6);
251
+ if (data === '[DONE]') continue;
252
+
253
+ try {
254
+ const event = JSON.parse(data);
255
+
256
+ // Capture stream ID for reconnection
257
+ if (event.type === 'data-stream-id' && onStreamId) {
258
+ onStreamId(event.streamId);
259
+ }
260
+
261
+ onEvent(event as SSEEvent);
262
+ } catch {
263
+ // Skip invalid JSON
264
+ }
265
+ }
266
+ }
267
+ }
268
+ resolve();
269
+ } catch (err) {
270
+ reject(err);
271
+ }
272
+ });
273
+ }
274
+
275
+ // Streaming run with resumable stream support
276
+ export function runAgent(
277
+ sessionId: string,
278
+ prompt: string,
279
+ onEvent: (event: SSEEvent) => void,
280
+ options?: {
281
+ onStreamId?: (streamId: string) => void;
282
+ }
283
+ ): () => void {
284
+ const controller = new AbortController();
285
+
286
+ fetch(`${getApiBase()}/agents/${sessionId}/run`, {
287
+ method: 'POST',
288
+ headers: { 'Content-Type': 'application/json' },
289
+ body: JSON.stringify({ prompt }),
290
+ signal: controller.signal,
291
+ })
292
+ .then(async (response) => {
293
+ const streamId = response.headers.get('x-stream-id');
294
+ if (streamId && options?.onStreamId) {
295
+ options.onStreamId(streamId);
296
+ }
297
+ await parseSSEStream(response, onEvent, options?.onStreamId);
298
+ })
299
+ .catch((err) => {
300
+ if (err.name !== 'AbortError') {
301
+ console.error('Stream error:', err);
302
+ }
303
+ });
304
+
305
+ return () => controller.abort();
306
+ }
307
+
308
+ // Watch/subscribe to an existing stream (for multiple tabs/clients)
309
+ export function watchStream(
310
+ sessionId: string,
311
+ onEvent: (event: SSEEvent) => void,
312
+ options?: {
313
+ streamId?: string;
314
+ resumeAt?: number;
315
+ onStreamId?: (streamId: string) => void;
316
+ }
317
+ ): () => void {
318
+ const controller = new AbortController();
319
+
320
+ const params = new URLSearchParams();
321
+ if (options?.streamId) params.set('streamId', options.streamId);
322
+ if (options?.resumeAt !== undefined) params.set('resumeAt', String(options.resumeAt));
323
+
324
+ const url = `${getApiBase()}/agents/${sessionId}/watch${params.toString() ? '?' + params.toString() : ''}`;
325
+
326
+ fetch(url, { signal: controller.signal })
327
+ .then(async (response) => {
328
+ if (!response.ok) {
329
+ const error = await response.json().catch(() => ({ error: 'Unknown error' }));
330
+ console.error('Watch stream error:', error);
331
+ onEvent({ type: 'error', errorText: error.error || 'Failed to watch stream' });
332
+ return;
333
+ }
334
+
335
+ const streamId = response.headers.get('x-stream-id');
336
+ if (streamId && options?.onStreamId) {
337
+ options.onStreamId(streamId);
338
+ }
339
+
340
+ await parseSSEStream(response, onEvent, options?.onStreamId);
341
+ })
342
+ .catch((err) => {
343
+ if (err.name !== 'AbortError') {
344
+ console.error('Watch stream error:', err);
345
+ }
346
+ });
347
+
348
+ return () => controller.abort();
349
+ }
350
+
351
+ export type SSEEvent =
352
+ | { type: 'start'; messageId: string }
353
+ | { type: 'text-start'; id: string }
354
+ | { type: 'text-delta'; id: string; delta: string }
355
+ | { type: 'text-end'; id: string }
356
+ | { type: 'reasoning-start'; id: string }
357
+ | { type: 'reasoning-delta'; id: string; delta: string }
358
+ | { type: 'reasoning-end'; id: string }
359
+ | { type: 'tool-input-start'; toolCallId: string; toolName: string }
360
+ | { type: 'tool-input-delta'; toolCallId: string; argsTextDelta: string }
361
+ | { type: 'tool-input-available'; toolCallId: string; toolName: string; input: unknown }
362
+ | { type: 'tool-output-available'; toolCallId: string; output: unknown }
363
+ | { type: 'tool-progress'; toolName: string; data: { terminalId: string; status: 'started' | 'running' | 'completed'; command?: string } }
364
+ | { type: 'data-approval-required'; data: PendingApproval }
365
+ | { type: 'data-stream-id'; streamId: string }
366
+ | { type: 'data-session'; data: { id: string; name: string; workingDirectory: string; model: string } }
367
+ | { type: 'data-user-message'; data: { id: string; content: string } }
368
+ | { type: 'finish-step' }
369
+ | { type: 'finish'; finishReason?: string }
370
+ | { type: 'abort' }
371
+ | { type: 'error'; errorText: string };
372
+
373
+ // Terminal stream events
374
+ export interface TerminalStreamEvent {
375
+ type: 'status' | 'stdout' | 'exit';
376
+ data?: string;
377
+ terminalId?: string;
378
+ status?: string;
379
+ }
380
+
381
+ /**
382
+ * Subscribe to a terminal's output stream
383
+ * Returns a cancel function
384
+ */
385
+ export function streamTerminal(
386
+ terminalId: string,
387
+ onOutput: (data: string) => void,
388
+ onExit?: () => void
389
+ ): () => void {
390
+ const controller = new AbortController();
391
+
392
+ const connect = async () => {
393
+ try {
394
+ const response = await fetch(`${getApiBase()}/terminals/stream/${terminalId}`, {
395
+ signal: controller.signal,
396
+ });
397
+
398
+ if (!response.ok || !response.body) {
399
+ console.error('Failed to connect to terminal stream');
400
+ return;
401
+ }
402
+
403
+ const reader = response.body.getReader();
404
+ const decoder = new TextDecoder();
405
+ let buffer = '';
406
+
407
+ while (true) {
408
+ const { done, value } = await reader.read();
409
+ if (done) break;
410
+
411
+ buffer += decoder.decode(value, { stream: true });
412
+ const lines = buffer.split('\n\n');
413
+ buffer = lines.pop() || '';
414
+
415
+ for (const chunk of lines) {
416
+ if (!chunk.trim()) continue;
417
+
418
+ // Parse SSE format: "event: type\ndata: {...}"
419
+ const eventMatch = chunk.match(/event:\s*(\w+)/);
420
+ const dataMatch = chunk.match(/data:\s*(.+)/);
421
+
422
+ if (eventMatch && dataMatch) {
423
+ const eventType = eventMatch[1];
424
+ try {
425
+ const data = JSON.parse(dataMatch[1]);
426
+
427
+ if (eventType === 'stdout' && data.data) {
428
+ onOutput(data.data);
429
+ } else if (eventType === 'exit') {
430
+ onExit?.();
431
+ }
432
+ } catch {
433
+ // Skip invalid JSON
434
+ }
435
+ }
436
+ }
437
+ }
438
+ } catch (error) {
439
+ if (error instanceof Error && error.name !== 'AbortError') {
440
+ console.error('Terminal stream error:', error);
441
+ }
442
+ }
443
+ };
444
+
445
+ connect();
446
+
447
+ return () => controller.abort();
448
+ }
449
+
450
+ // ============================================
451
+ // API Key Management
452
+ // ============================================
453
+
454
+ export interface ApiKeyStatus {
455
+ provider: string;
456
+ envVar: string;
457
+ configured: boolean;
458
+ source: 'env' | 'storage' | 'none';
459
+ maskedKey: string | null;
460
+ }
461
+
462
+ export interface ApiKeysResponse {
463
+ providers: ApiKeyStatus[];
464
+ supportedProviders: string[];
465
+ }
466
+
467
+ export async function getApiKeys(): Promise<ApiKeysResponse> {
468
+ const res = await fetch(`${getApiBase()}/health/api-keys`);
469
+ return res.json();
470
+ }
471
+
472
+ export async function setApiKey(provider: string, apiKey: string): Promise<{
473
+ success: boolean;
474
+ provider: string;
475
+ maskedKey: string;
476
+ message: string;
477
+ }> {
478
+ const res = await fetch(`${getApiBase()}/health/api-keys`, {
479
+ method: 'POST',
480
+ headers: { 'Content-Type': 'application/json' },
481
+ body: JSON.stringify({ provider, apiKey }),
482
+ });
483
+ return res.json();
484
+ }
485
+
486
+ export async function removeApiKey(provider: string): Promise<{
487
+ success: boolean;
488
+ provider: string;
489
+ message: string;
490
+ }> {
491
+ const res = await fetch(`${getApiBase()}/health/api-keys/${provider}`, {
492
+ method: 'DELETE',
493
+ });
494
+ return res.json();
495
+ }
496
+
497
+ // ============================================
498
+ // Checkpoint / Revert API
499
+ // ============================================
500
+
501
+ export interface Checkpoint {
502
+ id: string;
503
+ messageSequence: number;
504
+ gitHead: string | null;
505
+ createdAt: string;
506
+ }
507
+
508
+ export interface CheckpointsResponse {
509
+ sessionId: string;
510
+ checkpoints: Checkpoint[];
511
+ count: number;
512
+ }
513
+
514
+ export interface RevertResponse {
515
+ success: boolean;
516
+ sessionId: string;
517
+ checkpointId: string;
518
+ filesRestored: number;
519
+ filesDeleted: number;
520
+ messagesDeleted: number;
521
+ checkpointsDeleted: number;
522
+ error?: string;
523
+ }
524
+
525
+ export interface SessionDiffFile {
526
+ path: string;
527
+ status: 'created' | 'modified' | 'deleted';
528
+ hasOriginal: boolean;
529
+ hasCurrent: boolean;
530
+ }
531
+
532
+ export interface SessionDiffResponse {
533
+ sessionId: string;
534
+ files: SessionDiffFile[];
535
+ summary: {
536
+ created: number;
537
+ modified: number;
538
+ deleted: number;
539
+ total: number;
540
+ };
541
+ }
542
+
543
+ export async function getSessionCheckpoints(sessionId: string): Promise<CheckpointsResponse> {
544
+ const res = await fetch(`${getApiBase()}/sessions/${sessionId}/checkpoints`);
545
+ if (!res.ok) {
546
+ throw new Error(`Failed to get checkpoints: ${res.statusText}`);
547
+ }
548
+ return res.json();
549
+ }
550
+
551
+ export async function revertToCheckpoint(sessionId: string, checkpointId: string): Promise<RevertResponse> {
552
+ const res = await fetch(`${getApiBase()}/sessions/${sessionId}/revert/${checkpointId}`, {
553
+ method: 'POST',
554
+ });
555
+ if (!res.ok) {
556
+ const error = await res.json();
557
+ throw new Error(error.error || `Failed to revert: ${res.statusText}`);
558
+ }
559
+ return res.json();
560
+ }
561
+
562
+ export async function getSessionDiff(sessionId: string): Promise<SessionDiffResponse> {
563
+ const res = await fetch(`${getApiBase()}/sessions/${sessionId}/diff`);
564
+ if (!res.ok) {
565
+ throw new Error(`Failed to get diff: ${res.statusText}`);
566
+ }
567
+ return res.json();
568
+ }