reigncode-app 1.3.2

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 (300) hide show
  1. package/AGENTS.md +30 -0
  2. package/Dockerfile +21 -0
  3. package/README.md +51 -0
  4. package/bunfig.toml +3 -0
  5. package/create-effect-simplification-spec.md +515 -0
  6. package/e2e/AGENTS.md +226 -0
  7. package/e2e/actions.ts +1018 -0
  8. package/e2e/app/home.spec.ts +24 -0
  9. package/e2e/app/navigation.spec.ts +10 -0
  10. package/e2e/app/palette.spec.ts +20 -0
  11. package/e2e/app/server-default.spec.ts +58 -0
  12. package/e2e/app/session.spec.ts +16 -0
  13. package/e2e/app/titlebar-history.spec.ts +120 -0
  14. package/e2e/commands/input-focus.spec.ts +15 -0
  15. package/e2e/commands/panels.spec.ts +33 -0
  16. package/e2e/commands/tab-close.spec.ts +32 -0
  17. package/e2e/files/file-open.spec.ts +31 -0
  18. package/e2e/files/file-tree.spec.ts +56 -0
  19. package/e2e/files/file-viewer.spec.ts +156 -0
  20. package/e2e/fixtures.ts +154 -0
  21. package/e2e/models/model-picker.spec.ts +48 -0
  22. package/e2e/models/models-visibility.spec.ts +61 -0
  23. package/e2e/projects/project-edit.spec.ts +43 -0
  24. package/e2e/projects/projects-close.spec.ts +54 -0
  25. package/e2e/projects/projects-switch.spec.ts +116 -0
  26. package/e2e/projects/workspace-new-session.spec.ts +94 -0
  27. package/e2e/projects/workspaces.spec.ts +375 -0
  28. package/e2e/prompt/context.spec.ts +95 -0
  29. package/e2e/prompt/prompt-async.spec.ts +76 -0
  30. package/e2e/prompt/prompt-drop-file-uri.spec.ts +22 -0
  31. package/e2e/prompt/prompt-drop-file.spec.ts +30 -0
  32. package/e2e/prompt/prompt-history.spec.ts +184 -0
  33. package/e2e/prompt/prompt-mention.spec.ts +26 -0
  34. package/e2e/prompt/prompt-multiline.spec.ts +24 -0
  35. package/e2e/prompt/prompt-shell.spec.ts +62 -0
  36. package/e2e/prompt/prompt-slash-open.spec.ts +22 -0
  37. package/e2e/prompt/prompt-slash-share.spec.ts +64 -0
  38. package/e2e/prompt/prompt-slash-terminal.spec.ts +18 -0
  39. package/e2e/prompt/prompt.spec.ts +55 -0
  40. package/e2e/selectors.ts +75 -0
  41. package/e2e/session/session-child-navigation.spec.ts +37 -0
  42. package/e2e/session/session-composer-dock.spec.ts +530 -0
  43. package/e2e/session/session-model-persistence.spec.ts +359 -0
  44. package/e2e/session/session-review.spec.ts +426 -0
  45. package/e2e/session/session-undo-redo.spec.ts +233 -0
  46. package/e2e/session/session.spec.ts +174 -0
  47. package/e2e/settings/settings-keybinds.spec.ts +389 -0
  48. package/e2e/settings/settings-models.spec.ts +122 -0
  49. package/e2e/settings/settings-providers.spec.ts +136 -0
  50. package/e2e/settings/settings.spec.ts +519 -0
  51. package/e2e/sidebar/sidebar-popover-actions.spec.ts +118 -0
  52. package/e2e/sidebar/sidebar-session-links.spec.ts +30 -0
  53. package/e2e/sidebar/sidebar.spec.ts +40 -0
  54. package/e2e/status/status-popover.spec.ts +94 -0
  55. package/e2e/terminal/terminal-init.spec.ts +28 -0
  56. package/e2e/terminal/terminal-reconnect.spec.ts +46 -0
  57. package/e2e/terminal/terminal-tabs.spec.ts +168 -0
  58. package/e2e/terminal/terminal.spec.ts +18 -0
  59. package/e2e/thinking-level.spec.ts +25 -0
  60. package/e2e/tsconfig.json +9 -0
  61. package/e2e/utils.ts +63 -0
  62. package/happydom.ts +75 -0
  63. package/index.html +23 -0
  64. package/package.json +77 -0
  65. package/playwright.config.ts +45 -0
  66. package/public/_headers +17 -0
  67. package/public/oc-theme-preload.js +35 -0
  68. package/script/e2e-local.ts +180 -0
  69. package/src/addons/serialize.test.ts +319 -0
  70. package/src/addons/serialize.ts +634 -0
  71. package/src/app.tsx +308 -0
  72. package/src/components/debug-bar.tsx +443 -0
  73. package/src/components/dialog-connect-provider.tsx +617 -0
  74. package/src/components/dialog-custom-provider-form.ts +158 -0
  75. package/src/components/dialog-custom-provider.test.ts +80 -0
  76. package/src/components/dialog-custom-provider.tsx +329 -0
  77. package/src/components/dialog-edit-project.tsx +255 -0
  78. package/src/components/dialog-fork.tsx +108 -0
  79. package/src/components/dialog-manage-models.tsx +101 -0
  80. package/src/components/dialog-release-notes.tsx +144 -0
  81. package/src/components/dialog-select-directory.tsx +392 -0
  82. package/src/components/dialog-select-file.tsx +466 -0
  83. package/src/components/dialog-select-mcp.tsx +107 -0
  84. package/src/components/dialog-select-model-unpaid.tsx +137 -0
  85. package/src/components/dialog-select-model.tsx +220 -0
  86. package/src/components/dialog-select-provider.tsx +86 -0
  87. package/src/components/dialog-select-server.tsx +649 -0
  88. package/src/components/dialog-settings.tsx +73 -0
  89. package/src/components/file-tree.test.ts +78 -0
  90. package/src/components/file-tree.tsx +507 -0
  91. package/src/components/link.tsx +26 -0
  92. package/src/components/model-tooltip.tsx +91 -0
  93. package/src/components/prompt-input/attachments.test.ts +44 -0
  94. package/src/components/prompt-input/attachments.ts +201 -0
  95. package/src/components/prompt-input/build-request-parts.test.ts +312 -0
  96. package/src/components/prompt-input/build-request-parts.ts +175 -0
  97. package/src/components/prompt-input/context-items.tsx +88 -0
  98. package/src/components/prompt-input/drag-overlay.tsx +25 -0
  99. package/src/components/prompt-input/editor-dom.test.ts +99 -0
  100. package/src/components/prompt-input/editor-dom.ts +148 -0
  101. package/src/components/prompt-input/files.ts +66 -0
  102. package/src/components/prompt-input/history.test.ts +153 -0
  103. package/src/components/prompt-input/history.ts +256 -0
  104. package/src/components/prompt-input/image-attachments.tsx +58 -0
  105. package/src/components/prompt-input/paste.ts +24 -0
  106. package/src/components/prompt-input/placeholder.test.ts +48 -0
  107. package/src/components/prompt-input/placeholder.ts +15 -0
  108. package/src/components/prompt-input/slash-popover.tsx +141 -0
  109. package/src/components/prompt-input/submit.test.ts +346 -0
  110. package/src/components/prompt-input/submit.ts +579 -0
  111. package/src/components/prompt-input.tsx +1595 -0
  112. package/src/components/server/server-row.tsx +130 -0
  113. package/src/components/session/index.ts +5 -0
  114. package/src/components/session/session-context-breakdown.test.ts +61 -0
  115. package/src/components/session/session-context-breakdown.ts +132 -0
  116. package/src/components/session/session-context-format.ts +20 -0
  117. package/src/components/session/session-context-metrics.test.ts +101 -0
  118. package/src/components/session/session-context-metrics.ts +82 -0
  119. package/src/components/session/session-context-tab.tsx +339 -0
  120. package/src/components/session/session-header.tsx +486 -0
  121. package/src/components/session/session-new-view.tsx +91 -0
  122. package/src/components/session/session-sortable-tab.tsx +70 -0
  123. package/src/components/session/session-sortable-terminal-tab.tsx +193 -0
  124. package/src/components/session-context-usage.tsx +122 -0
  125. package/src/components/settings-general.tsx +585 -0
  126. package/src/components/settings-keybinds.tsx +453 -0
  127. package/src/components/settings-list.tsx +5 -0
  128. package/src/components/settings-models.tsx +137 -0
  129. package/src/components/settings-providers.tsx +251 -0
  130. package/src/components/status-popover.tsx +419 -0
  131. package/src/components/terminal.tsx +653 -0
  132. package/src/components/titlebar-history.test.ts +63 -0
  133. package/src/components/titlebar-history.ts +57 -0
  134. package/src/components/titlebar.tsx +312 -0
  135. package/src/constants/file-picker.ts +89 -0
  136. package/src/context/command-keybind.test.ts +69 -0
  137. package/src/context/command.test.ts +25 -0
  138. package/src/context/command.tsx +437 -0
  139. package/src/context/comments.test.ts +186 -0
  140. package/src/context/comments.tsx +243 -0
  141. package/src/context/file/content-cache.ts +88 -0
  142. package/src/context/file/path.test.ts +360 -0
  143. package/src/context/file/path.ts +151 -0
  144. package/src/context/file/tree-store.ts +170 -0
  145. package/src/context/file/types.ts +41 -0
  146. package/src/context/file/view-cache.ts +146 -0
  147. package/src/context/file/watcher.test.ts +149 -0
  148. package/src/context/file/watcher.ts +53 -0
  149. package/src/context/file-content-eviction-accounting.test.ts +65 -0
  150. package/src/context/file.tsx +280 -0
  151. package/src/context/global-sdk.tsx +232 -0
  152. package/src/context/global-sync/bootstrap.ts +206 -0
  153. package/src/context/global-sync/child-store.test.ts +38 -0
  154. package/src/context/global-sync/child-store.ts +281 -0
  155. package/src/context/global-sync/event-reducer.test.ts +552 -0
  156. package/src/context/global-sync/event-reducer.ts +359 -0
  157. package/src/context/global-sync/eviction.ts +28 -0
  158. package/src/context/global-sync/queue.ts +83 -0
  159. package/src/context/global-sync/session-cache.test.ts +102 -0
  160. package/src/context/global-sync/session-cache.ts +62 -0
  161. package/src/context/global-sync/session-load.ts +25 -0
  162. package/src/context/global-sync/session-prefetch.test.ts +96 -0
  163. package/src/context/global-sync/session-prefetch.ts +100 -0
  164. package/src/context/global-sync/session-trim.test.ts +59 -0
  165. package/src/context/global-sync/session-trim.ts +56 -0
  166. package/src/context/global-sync/types.ts +133 -0
  167. package/src/context/global-sync/utils.ts +25 -0
  168. package/src/context/global-sync.test.ts +122 -0
  169. package/src/context/global-sync.tsx +408 -0
  170. package/src/context/highlights.tsx +233 -0
  171. package/src/context/language.tsx +248 -0
  172. package/src/context/layout-scroll.test.ts +64 -0
  173. package/src/context/layout-scroll.ts +126 -0
  174. package/src/context/layout.test.ts +69 -0
  175. package/src/context/layout.tsx +937 -0
  176. package/src/context/local.tsx +422 -0
  177. package/src/context/model-variant.test.ts +86 -0
  178. package/src/context/model-variant.ts +52 -0
  179. package/src/context/models.tsx +163 -0
  180. package/src/context/notification.tsx +373 -0
  181. package/src/context/permission-auto-respond.test.ts +102 -0
  182. package/src/context/permission-auto-respond.ts +51 -0
  183. package/src/context/permission.tsx +277 -0
  184. package/src/context/platform.tsx +99 -0
  185. package/src/context/prompt.tsx +297 -0
  186. package/src/context/sdk.tsx +49 -0
  187. package/src/context/server.tsx +295 -0
  188. package/src/context/settings.tsx +241 -0
  189. package/src/context/sync-optimistic.test.ts +123 -0
  190. package/src/context/sync.tsx +618 -0
  191. package/src/context/terminal-title.ts +51 -0
  192. package/src/context/terminal.test.ts +82 -0
  193. package/src/context/terminal.tsx +437 -0
  194. package/src/entry.tsx +144 -0
  195. package/src/env.d.ts +18 -0
  196. package/src/hooks/use-providers.ts +44 -0
  197. package/src/i18n/ar.ts +855 -0
  198. package/src/i18n/br.ts +867 -0
  199. package/src/i18n/bs.ts +943 -0
  200. package/src/i18n/da.ts +937 -0
  201. package/src/i18n/de.ts +879 -0
  202. package/src/i18n/en.ts +948 -0
  203. package/src/i18n/es.ts +950 -0
  204. package/src/i18n/fr.ts +878 -0
  205. package/src/i18n/ja.ts +861 -0
  206. package/src/i18n/ko.ts +860 -0
  207. package/src/i18n/no.ts +944 -0
  208. package/src/i18n/parity.test.ts +32 -0
  209. package/src/i18n/pl.ts +865 -0
  210. package/src/i18n/ru.ts +946 -0
  211. package/src/i18n/th.ts +933 -0
  212. package/src/i18n/tr.ts +952 -0
  213. package/src/i18n/zh.ts +930 -0
  214. package/src/i18n/zht.ts +925 -0
  215. package/src/index.css +29 -0
  216. package/src/index.ts +6 -0
  217. package/src/pages/directory-layout.tsx +88 -0
  218. package/src/pages/error.tsx +327 -0
  219. package/src/pages/home.tsx +131 -0
  220. package/src/pages/layout/deep-links.ts +50 -0
  221. package/src/pages/layout/helpers.test.ts +211 -0
  222. package/src/pages/layout/helpers.ts +98 -0
  223. package/src/pages/layout/inline-editor.tsx +126 -0
  224. package/src/pages/layout/sidebar-items.tsx +437 -0
  225. package/src/pages/layout/sidebar-project.tsx +384 -0
  226. package/src/pages/layout/sidebar-shell.tsx +125 -0
  227. package/src/pages/layout/sidebar-workspace.tsx +504 -0
  228. package/src/pages/layout.tsx +2509 -0
  229. package/src/pages/session/composer/index.ts +2 -0
  230. package/src/pages/session/composer/session-composer-region.tsx +255 -0
  231. package/src/pages/session/composer/session-composer-state.test.ts +128 -0
  232. package/src/pages/session/composer/session-composer-state.ts +249 -0
  233. package/src/pages/session/composer/session-followup-dock.tsx +109 -0
  234. package/src/pages/session/composer/session-permission-dock.tsx +74 -0
  235. package/src/pages/session/composer/session-question-dock.tsx +449 -0
  236. package/src/pages/session/composer/session-request-tree.ts +52 -0
  237. package/src/pages/session/composer/session-revert-dock.tsx +99 -0
  238. package/src/pages/session/composer/session-todo-dock.tsx +330 -0
  239. package/src/pages/session/file-tab-scroll.test.ts +40 -0
  240. package/src/pages/session/file-tab-scroll.ts +67 -0
  241. package/src/pages/session/file-tabs.tsx +456 -0
  242. package/src/pages/session/handoff.ts +36 -0
  243. package/src/pages/session/helpers.test.ts +181 -0
  244. package/src/pages/session/helpers.ts +198 -0
  245. package/src/pages/session/message-gesture.test.ts +62 -0
  246. package/src/pages/session/message-gesture.ts +21 -0
  247. package/src/pages/session/message-id-from-hash.ts +6 -0
  248. package/src/pages/session/message-timeline.tsx +1013 -0
  249. package/src/pages/session/review-tab.tsx +170 -0
  250. package/src/pages/session/session-layout.ts +20 -0
  251. package/src/pages/session/session-model-helpers.test.ts +51 -0
  252. package/src/pages/session/session-model-helpers.ts +16 -0
  253. package/src/pages/session/session-side-panel.tsx +453 -0
  254. package/src/pages/session/terminal-label.ts +16 -0
  255. package/src/pages/session/terminal-panel.test.ts +25 -0
  256. package/src/pages/session/terminal-panel.tsx +326 -0
  257. package/src/pages/session/use-session-commands.tsx +495 -0
  258. package/src/pages/session/use-session-hash-scroll.test.ts +16 -0
  259. package/src/pages/session/use-session-hash-scroll.ts +197 -0
  260. package/src/pages/session.tsx +1841 -0
  261. package/src/sst-env.d.ts +12 -0
  262. package/src/testing/model-selection.ts +80 -0
  263. package/src/testing/prompt.ts +56 -0
  264. package/src/testing/session-composer.ts +84 -0
  265. package/src/testing/terminal.ts +118 -0
  266. package/src/theme-preload.test.ts +46 -0
  267. package/src/utils/agent.ts +23 -0
  268. package/src/utils/aim.ts +138 -0
  269. package/src/utils/base64.ts +10 -0
  270. package/src/utils/comment-note.ts +88 -0
  271. package/src/utils/id.ts +99 -0
  272. package/src/utils/notification-click.test.ts +27 -0
  273. package/src/utils/notification-click.ts +13 -0
  274. package/src/utils/persist.test.ts +115 -0
  275. package/src/utils/persist.ts +476 -0
  276. package/src/utils/prompt.test.ts +44 -0
  277. package/src/utils/prompt.ts +203 -0
  278. package/src/utils/runtime-adapters.test.ts +62 -0
  279. package/src/utils/runtime-adapters.ts +39 -0
  280. package/src/utils/same.ts +6 -0
  281. package/src/utils/scoped-cache.test.ts +69 -0
  282. package/src/utils/scoped-cache.ts +104 -0
  283. package/src/utils/server-errors.test.ts +131 -0
  284. package/src/utils/server-errors.ts +80 -0
  285. package/src/utils/server-health.test.ts +123 -0
  286. package/src/utils/server-health.ts +91 -0
  287. package/src/utils/server.ts +22 -0
  288. package/src/utils/solid-dnd.tsx +49 -0
  289. package/src/utils/sound.ts +117 -0
  290. package/src/utils/terminal-writer.test.ts +64 -0
  291. package/src/utils/terminal-writer.ts +65 -0
  292. package/src/utils/time.ts +22 -0
  293. package/src/utils/uuid.test.ts +78 -0
  294. package/src/utils/uuid.ts +12 -0
  295. package/src/utils/worktree.test.ts +46 -0
  296. package/src/utils/worktree.ts +73 -0
  297. package/sst-env.d.ts +10 -0
  298. package/tsconfig.json +26 -0
  299. package/vite.config.ts +15 -0
  300. package/vite.js +26 -0
@@ -0,0 +1,617 @@
1
+ import type { ProviderAuthAuthorization } from "@reign-labs/sdk/v2/client"
2
+ import { Button } from "@reign-labs/ui/button"
3
+ import { useDialog } from "@reign-labs/ui/context/dialog"
4
+ import { Dialog } from "@reign-labs/ui/dialog"
5
+ import { Icon } from "@reign-labs/ui/icon"
6
+ import { IconButton } from "@reign-labs/ui/icon-button"
7
+ import { List, type ListRef } from "@reign-labs/ui/list"
8
+ import { ProviderIcon } from "@reign-labs/ui/provider-icon"
9
+ import { Spinner } from "@reign-labs/ui/spinner"
10
+ import { TextField } from "@reign-labs/ui/text-field"
11
+ import { showToast } from "@reign-labs/ui/toast"
12
+ import { createMemo, Match, onCleanup, onMount, Switch } from "solid-js"
13
+ import { createStore, produce } from "solid-js/store"
14
+ import { Link } from "@/components/link"
15
+ import { useGlobalSDK } from "@/context/global-sdk"
16
+ import { useGlobalSync } from "@/context/global-sync"
17
+ import { useLanguage } from "@/context/language"
18
+ import { DialogSelectProvider } from "./dialog-select-provider"
19
+
20
+ export function DialogConnectProvider(props: { provider: string }) {
21
+ const dialog = useDialog()
22
+ const globalSync = useGlobalSync()
23
+ const globalSDK = useGlobalSDK()
24
+ const language = useLanguage()
25
+
26
+ const alive = { value: true }
27
+ const timer = { current: undefined as ReturnType<typeof setTimeout> | undefined }
28
+
29
+ onCleanup(() => {
30
+ alive.value = false
31
+ if (timer.current === undefined) return
32
+ clearTimeout(timer.current)
33
+ timer.current = undefined
34
+ })
35
+
36
+ const provider = createMemo(() => globalSync.data.provider.all.find((x) => x.id === props.provider)!)
37
+ const methods = createMemo(
38
+ () =>
39
+ globalSync.data.provider_auth[props.provider] ?? [
40
+ {
41
+ type: "api",
42
+ label: language.t("provider.connect.method.apiKey"),
43
+ },
44
+ ],
45
+ )
46
+ const [store, setStore] = createStore({
47
+ methodIndex: undefined as undefined | number,
48
+ authorization: undefined as undefined | ProviderAuthAuthorization,
49
+ state: "pending" as undefined | "pending" | "complete" | "error" | "prompt",
50
+ error: undefined as string | undefined,
51
+ })
52
+
53
+ type Action =
54
+ | { type: "method.select"; index: number }
55
+ | { type: "method.reset" }
56
+ | { type: "auth.prompt" }
57
+ | { type: "auth.pending" }
58
+ | { type: "auth.complete"; authorization: ProviderAuthAuthorization }
59
+ | { type: "auth.error"; error: string }
60
+
61
+ function dispatch(action: Action) {
62
+ setStore(
63
+ produce((draft) => {
64
+ if (action.type === "method.select") {
65
+ draft.methodIndex = action.index
66
+ draft.authorization = undefined
67
+ draft.state = undefined
68
+ draft.error = undefined
69
+ return
70
+ }
71
+ if (action.type === "method.reset") {
72
+ draft.methodIndex = undefined
73
+ draft.authorization = undefined
74
+ draft.state = undefined
75
+ draft.error = undefined
76
+ return
77
+ }
78
+ if (action.type === "auth.prompt") {
79
+ draft.state = "prompt"
80
+ draft.error = undefined
81
+ return
82
+ }
83
+ if (action.type === "auth.pending") {
84
+ draft.state = "pending"
85
+ draft.error = undefined
86
+ return
87
+ }
88
+ if (action.type === "auth.complete") {
89
+ draft.state = "complete"
90
+ draft.authorization = action.authorization
91
+ draft.error = undefined
92
+ return
93
+ }
94
+ draft.state = "error"
95
+ draft.error = action.error
96
+ }),
97
+ )
98
+ }
99
+
100
+ const method = createMemo(() => (store.methodIndex !== undefined ? methods().at(store.methodIndex!) : undefined))
101
+
102
+ const methodLabel = (value?: { type?: string; label?: string }) => {
103
+ if (!value) return ""
104
+ if (value.type === "api") return language.t("provider.connect.method.apiKey")
105
+ return value.label ?? ""
106
+ }
107
+
108
+ function formatError(value: unknown, fallback: string): string {
109
+ if (value && typeof value === "object" && "data" in value) {
110
+ const data = (value as { data?: { message?: unknown } }).data
111
+ if (typeof data?.message === "string" && data.message) return data.message
112
+ }
113
+ if (value && typeof value === "object" && "error" in value) {
114
+ const nested = formatError((value as { error?: unknown }).error, "")
115
+ if (nested) return nested
116
+ }
117
+ if (value && typeof value === "object" && "message" in value) {
118
+ const message = (value as { message?: unknown }).message
119
+ if (typeof message === "string" && message) return message
120
+ }
121
+ if (value instanceof Error && value.message) return value.message
122
+ if (typeof value === "string" && value) return value
123
+ return fallback
124
+ }
125
+
126
+ async function selectMethod(index: number, inputs?: Record<string, string>) {
127
+ if (timer.current !== undefined) {
128
+ clearTimeout(timer.current)
129
+ timer.current = undefined
130
+ }
131
+
132
+ const method = methods()[index]
133
+ dispatch({ type: "method.select", index })
134
+
135
+ if (method.type === "oauth") {
136
+ if (method.prompts?.length && !inputs) {
137
+ dispatch({ type: "auth.prompt" })
138
+ return
139
+ }
140
+ dispatch({ type: "auth.pending" })
141
+ const start = Date.now()
142
+ await globalSDK.client.provider.oauth
143
+ .authorize(
144
+ {
145
+ providerID: props.provider,
146
+ method: index,
147
+ inputs,
148
+ },
149
+ { throwOnError: true },
150
+ )
151
+ .then((x) => {
152
+ if (!alive.value) return
153
+ const elapsed = Date.now() - start
154
+ const delay = 1000 - elapsed
155
+
156
+ if (delay > 0) {
157
+ if (timer.current !== undefined) clearTimeout(timer.current)
158
+ timer.current = setTimeout(() => {
159
+ timer.current = undefined
160
+ if (!alive.value) return
161
+ dispatch({ type: "auth.complete", authorization: x.data! })
162
+ }, delay)
163
+ return
164
+ }
165
+ dispatch({ type: "auth.complete", authorization: x.data! })
166
+ })
167
+ .catch((e) => {
168
+ if (!alive.value) return
169
+ dispatch({ type: "auth.error", error: formatError(e, language.t("common.requestFailed")) })
170
+ })
171
+ }
172
+ }
173
+
174
+ function OAuthPromptsView() {
175
+ const [formStore, setFormStore] = createStore({
176
+ value: {} as Record<string, string>,
177
+ index: 0,
178
+ })
179
+
180
+ const prompts = createMemo(() => method()?.prompts ?? [])
181
+ const matches = (prompt: NonNullable<ReturnType<typeof prompts>[number]>, value: Record<string, string>) => {
182
+ if (!prompt.when) return true
183
+ const actual = value[prompt.when.key]
184
+ if (actual === undefined) return false
185
+ return prompt.when.op === "eq" ? actual === prompt.when.value : actual !== prompt.when.value
186
+ }
187
+ const current = createMemo(() => {
188
+ const all = prompts()
189
+ const index = all.findIndex((prompt, index) => index >= formStore.index && matches(prompt, formStore.value))
190
+ if (index === -1) return
191
+ return {
192
+ index,
193
+ prompt: all[index],
194
+ }
195
+ })
196
+ const valid = createMemo(() => {
197
+ const item = current()
198
+ if (!item || item.prompt.type !== "text") return false
199
+ const value = formStore.value[item.prompt.key] ?? ""
200
+ return value.trim().length > 0
201
+ })
202
+
203
+ async function next(index: number, value: Record<string, string>) {
204
+ if (store.methodIndex === undefined) return
205
+ const next = prompts().findIndex((prompt, i) => i > index && matches(prompt, value))
206
+ if (next !== -1) {
207
+ setFormStore("index", next)
208
+ return
209
+ }
210
+ await selectMethod(store.methodIndex, value)
211
+ }
212
+
213
+ async function handleSubmit(e: SubmitEvent) {
214
+ e.preventDefault()
215
+ const item = current()
216
+ if (!item || item.prompt.type !== "text") return
217
+ if (!valid()) return
218
+ await next(item.index, formStore.value)
219
+ }
220
+
221
+ const item = () => current()
222
+ const text = createMemo(() => {
223
+ const prompt = item()?.prompt
224
+ if (!prompt || prompt.type !== "text") return
225
+ return prompt
226
+ })
227
+ const select = createMemo(() => {
228
+ const prompt = item()?.prompt
229
+ if (!prompt || prompt.type !== "select") return
230
+ return prompt
231
+ })
232
+
233
+ return (
234
+ <form onSubmit={handleSubmit} class="flex flex-col items-start gap-4">
235
+ <Switch>
236
+ <Match when={item()?.prompt.type === "text"}>
237
+ <TextField
238
+ type="text"
239
+ label={text()?.message ?? ""}
240
+ placeholder={text()?.placeholder}
241
+ value={text() ? (formStore.value[text()!.key] ?? "") : ""}
242
+ onChange={(value) => {
243
+ const prompt = text()
244
+ if (!prompt) return
245
+ setFormStore("value", prompt.key, value)
246
+ }}
247
+ />
248
+ <Button class="w-auto" type="submit" size="large" variant="primary" disabled={!valid()}>
249
+ {language.t("common.continue")}
250
+ </Button>
251
+ </Match>
252
+ <Match when={item()?.prompt.type === "select"}>
253
+ <div class="w-full flex flex-col gap-1.5">
254
+ <div class="text-14-regular text-text-base">{select()?.message}</div>
255
+ <div>
256
+ <List
257
+ items={select()?.options ?? []}
258
+ key={(x) => x.value}
259
+ current={select()?.options.find((x) => x.value === formStore.value[select()!.key])}
260
+ onSelect={(value) => {
261
+ if (!value) return
262
+ const prompt = select()
263
+ if (!prompt) return
264
+ const nextValue = {
265
+ ...formStore.value,
266
+ [prompt.key]: value.value,
267
+ }
268
+ setFormStore("value", prompt.key, value.value)
269
+ void next(item()!.index, nextValue)
270
+ }}
271
+ >
272
+ {(option) => (
273
+ <div class="w-full flex items-center gap-x-2">
274
+ <div class="w-4 h-2 rounded-[1px] bg-input-base shadow-xs-border-base flex items-center justify-center">
275
+ <div class="w-2.5 h-0.5 ml-0 bg-icon-strong-base hidden" data-slot="list-item-extra-icon" />
276
+ </div>
277
+ <span>{option.label}</span>
278
+ <span class="text-14-regular text-text-weak">{option.hint}</span>
279
+ </div>
280
+ )}
281
+ </List>
282
+ </div>
283
+ </div>
284
+ </Match>
285
+ </Switch>
286
+ </form>
287
+ )
288
+ }
289
+
290
+ let listRef: ListRef | undefined
291
+ function handleKey(e: KeyboardEvent) {
292
+ if (e.key === "Enter" && e.target instanceof HTMLInputElement) {
293
+ return
294
+ }
295
+ if (e.key === "Escape") return
296
+ listRef?.onKeyDown(e)
297
+ }
298
+
299
+ onMount(() => {
300
+ if (methods().length === 1) {
301
+ selectMethod(0)
302
+ }
303
+ })
304
+
305
+ async function complete() {
306
+ await globalSDK.client.global.dispose()
307
+ dialog.close()
308
+ showToast({
309
+ variant: "success",
310
+ icon: "circle-check",
311
+ title: language.t("provider.connect.toast.connected.title", { provider: provider().name }),
312
+ description: language.t("provider.connect.toast.connected.description", { provider: provider().name }),
313
+ })
314
+ }
315
+
316
+ function goBack() {
317
+ if (methods().length === 1) {
318
+ dialog.show(() => <DialogSelectProvider />)
319
+ return
320
+ }
321
+ if (store.authorization) {
322
+ dispatch({ type: "method.reset" })
323
+ return
324
+ }
325
+ if (store.methodIndex !== undefined) {
326
+ dispatch({ type: "method.reset" })
327
+ return
328
+ }
329
+ dialog.show(() => <DialogSelectProvider />)
330
+ }
331
+
332
+ function MethodSelection() {
333
+ return (
334
+ <>
335
+ <div class="text-14-regular text-text-base">
336
+ {language.t("provider.connect.selectMethod", { provider: provider().name })}
337
+ </div>
338
+ <div>
339
+ <List
340
+ ref={(ref) => {
341
+ listRef = ref
342
+ }}
343
+ items={methods}
344
+ key={(m) => m?.label}
345
+ onSelect={async (selected, index) => {
346
+ if (!selected) return
347
+ selectMethod(index)
348
+ }}
349
+ >
350
+ {(i) => (
351
+ <div class="w-full flex items-center gap-x-2">
352
+ <div class="w-4 h-2 rounded-[1px] bg-input-base shadow-xs-border-base flex items-center justify-center">
353
+ <div class="w-2.5 h-0.5 ml-0 bg-icon-strong-base hidden" data-slot="list-item-extra-icon" />
354
+ </div>
355
+ <span>{methodLabel(i)}</span>
356
+ </div>
357
+ )}
358
+ </List>
359
+ </div>
360
+ </>
361
+ )
362
+ }
363
+
364
+ function ApiAuthView() {
365
+ const [formStore, setFormStore] = createStore({
366
+ value: "",
367
+ error: undefined as string | undefined,
368
+ })
369
+
370
+ async function handleSubmit(e: SubmitEvent) {
371
+ e.preventDefault()
372
+
373
+ const form = e.currentTarget as HTMLFormElement
374
+ const formData = new FormData(form)
375
+ const apiKey = formData.get("apiKey") as string
376
+
377
+ if (!apiKey?.trim()) {
378
+ setFormStore("error", language.t("provider.connect.apiKey.required"))
379
+ return
380
+ }
381
+
382
+ setFormStore("error", undefined)
383
+ await globalSDK.client.auth.set({
384
+ providerID: props.provider,
385
+ auth: {
386
+ type: "api",
387
+ key: apiKey,
388
+ },
389
+ })
390
+ await complete()
391
+ }
392
+
393
+ return (
394
+ <div class="flex flex-col gap-6">
395
+ <Switch>
396
+ <Match when={provider().id === "opencode"}>
397
+ <div class="flex flex-col gap-4">
398
+ <div class="text-14-regular text-text-base">{language.t("provider.connect.opencodeZen.line1")}</div>
399
+ <div class="text-14-regular text-text-base">{language.t("provider.connect.opencodeZen.line2")}</div>
400
+ <div class="text-14-regular text-text-base">
401
+ {language.t("provider.connect.opencodeZen.visit.prefix")}
402
+ <Link href="https://code.reign-labs.com/zen" tabIndex={-1}>
403
+ {language.t("provider.connect.opencodeZen.visit.link")}
404
+ </Link>
405
+ {language.t("provider.connect.opencodeZen.visit.suffix")}
406
+ </div>
407
+ </div>
408
+ </Match>
409
+ <Match when={true}>
410
+ <div class="text-14-regular text-text-base">
411
+ {language.t("provider.connect.apiKey.description", { provider: provider().name })}
412
+ </div>
413
+ </Match>
414
+ </Switch>
415
+ <form onSubmit={handleSubmit} class="flex flex-col items-start gap-4">
416
+ <TextField
417
+ autofocus
418
+ type="text"
419
+ label={language.t("provider.connect.apiKey.label", { provider: provider().name })}
420
+ placeholder={language.t("provider.connect.apiKey.placeholder")}
421
+ name="apiKey"
422
+ value={formStore.value}
423
+ onChange={(v) => setFormStore("value", v)}
424
+ validationState={formStore.error ? "invalid" : undefined}
425
+ error={formStore.error}
426
+ />
427
+ <Button class="w-auto" type="submit" size="large" variant="primary">
428
+ {language.t("common.continue")}
429
+ </Button>
430
+ </form>
431
+ </div>
432
+ )
433
+ }
434
+
435
+ function OAuthCodeView() {
436
+ const [formStore, setFormStore] = createStore({
437
+ value: "",
438
+ error: undefined as string | undefined,
439
+ })
440
+
441
+ async function handleSubmit(e: SubmitEvent) {
442
+ e.preventDefault()
443
+
444
+ const form = e.currentTarget as HTMLFormElement
445
+ const formData = new FormData(form)
446
+ const code = formData.get("code") as string
447
+
448
+ if (!code?.trim()) {
449
+ setFormStore("error", language.t("provider.connect.oauth.code.required"))
450
+ return
451
+ }
452
+
453
+ setFormStore("error", undefined)
454
+ const result = await globalSDK.client.provider.oauth
455
+ .callback({
456
+ providerID: props.provider,
457
+ method: store.methodIndex,
458
+ code,
459
+ })
460
+ .then((value) => (value.error ? { ok: false as const, error: value.error } : { ok: true as const }))
461
+ .catch((error) => ({ ok: false as const, error }))
462
+ if (result.ok) {
463
+ await complete()
464
+ return
465
+ }
466
+ setFormStore("error", formatError(result.error, language.t("provider.connect.oauth.code.invalid")))
467
+ }
468
+
469
+ return (
470
+ <div class="flex flex-col gap-6">
471
+ <div class="text-14-regular text-text-base">
472
+ {language.t("provider.connect.oauth.code.visit.prefix")}
473
+ <Link href={store.authorization!.url}>{language.t("provider.connect.oauth.code.visit.link")}</Link>
474
+ {language.t("provider.connect.oauth.code.visit.suffix", { provider: provider().name })}
475
+ </div>
476
+ <form onSubmit={handleSubmit} class="flex flex-col items-start gap-4">
477
+ <TextField
478
+ autofocus
479
+ type="text"
480
+ label={language.t("provider.connect.oauth.code.label", { method: method()?.label ?? "" })}
481
+ placeholder={language.t("provider.connect.oauth.code.placeholder")}
482
+ name="code"
483
+ value={formStore.value}
484
+ onChange={(v) => setFormStore("value", v)}
485
+ validationState={formStore.error ? "invalid" : undefined}
486
+ error={formStore.error}
487
+ />
488
+ <Button class="w-auto" type="submit" size="large" variant="primary">
489
+ {language.t("common.continue")}
490
+ </Button>
491
+ </form>
492
+ </div>
493
+ )
494
+ }
495
+
496
+ function OAuthAutoView() {
497
+ const code = createMemo(() => {
498
+ const instructions = store.authorization?.instructions
499
+ if (instructions?.includes(":")) {
500
+ return instructions.split(":")[1]?.trim()
501
+ }
502
+ return instructions
503
+ })
504
+
505
+ onMount(() => {
506
+ void (async () => {
507
+ const result = await globalSDK.client.provider.oauth
508
+ .callback({
509
+ providerID: props.provider,
510
+ method: store.methodIndex,
511
+ })
512
+ .then((value) => (value.error ? { ok: false as const, error: value.error } : { ok: true as const }))
513
+ .catch((error) => ({ ok: false as const, error }))
514
+
515
+ if (!alive.value) return
516
+
517
+ if (!result.ok) {
518
+ const message = formatError(result.error, language.t("common.requestFailed"))
519
+ dispatch({ type: "auth.error", error: message })
520
+ return
521
+ }
522
+
523
+ await complete()
524
+ })()
525
+ })
526
+
527
+ return (
528
+ <div class="flex flex-col gap-6">
529
+ <div class="text-14-regular text-text-base">
530
+ {language.t("provider.connect.oauth.auto.visit.prefix")}
531
+ <Link href={store.authorization!.url}>{language.t("provider.connect.oauth.auto.visit.link")}</Link>
532
+ {language.t("provider.connect.oauth.auto.visit.suffix", { provider: provider().name })}
533
+ </div>
534
+ <TextField
535
+ label={language.t("provider.connect.oauth.auto.confirmationCode")}
536
+ class="font-mono"
537
+ value={code()}
538
+ readOnly
539
+ copyable
540
+ />
541
+ <div class="text-14-regular text-text-base flex items-center gap-4">
542
+ <Spinner />
543
+ <span>{language.t("provider.connect.status.waiting")}</span>
544
+ </div>
545
+ </div>
546
+ )
547
+ }
548
+
549
+ return (
550
+ <Dialog
551
+ title={
552
+ <IconButton
553
+ tabIndex={-1}
554
+ icon="arrow-left"
555
+ variant="ghost"
556
+ onClick={goBack}
557
+ aria-label={language.t("common.goBack")}
558
+ />
559
+ }
560
+ >
561
+ <div class="flex flex-col gap-6 px-2.5 pb-3">
562
+ <div class="px-2.5 flex gap-4 items-center">
563
+ <ProviderIcon id={props.provider} class="size-5 shrink-0 icon-strong-base" />
564
+ <div class="text-16-medium text-text-strong">
565
+ <Switch>
566
+ <Match when={props.provider === "anthropic" && method()?.label?.toLowerCase().includes("max")}>
567
+ {language.t("provider.connect.title.anthropicProMax")}
568
+ </Match>
569
+ <Match when={true}>{language.t("provider.connect.title", { provider: provider().name })}</Match>
570
+ </Switch>
571
+ </div>
572
+ </div>
573
+ <div class="px-2.5 pb-10 flex flex-col gap-6">
574
+ <div onKeyDown={handleKey} tabIndex={0} autofocus={store.methodIndex === undefined ? true : undefined}>
575
+ <Switch>
576
+ <Match when={store.methodIndex === undefined}>
577
+ <MethodSelection />
578
+ </Match>
579
+ <Match when={store.state === "pending"}>
580
+ <div class="text-14-regular text-text-base">
581
+ <div class="flex items-center gap-x-2">
582
+ <Spinner />
583
+ <span>{language.t("provider.connect.status.inProgress")}</span>
584
+ </div>
585
+ </div>
586
+ </Match>
587
+ <Match when={store.state === "prompt"}>
588
+ <OAuthPromptsView />
589
+ </Match>
590
+ <Match when={store.state === "error"}>
591
+ <div class="text-14-regular text-text-base">
592
+ <div class="flex items-center gap-x-2">
593
+ <Icon name="circle-ban-sign" class="text-icon-critical-base" />
594
+ <span>{language.t("provider.connect.status.failed", { error: store.error ?? "" })}</span>
595
+ </div>
596
+ </div>
597
+ </Match>
598
+ <Match when={method()?.type === "api"}>
599
+ <ApiAuthView />
600
+ </Match>
601
+ <Match when={method()?.type === "oauth"}>
602
+ <Switch>
603
+ <Match when={store.authorization?.method === "code"}>
604
+ <OAuthCodeView />
605
+ </Match>
606
+ <Match when={store.authorization?.method === "auto"}>
607
+ <OAuthAutoView />
608
+ </Match>
609
+ </Switch>
610
+ </Match>
611
+ </Switch>
612
+ </div>
613
+ </div>
614
+ </div>
615
+ </Dialog>
616
+ )
617
+ }