@qredence/fleet 0.6.0 → 0.6.1

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 (127) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/dist/web/client/assets/action-swap-roll-COzQ86wm.js +1 -0
  3. package/dist/web/client/assets/agent-activity-svETLf_5.js +1 -0
  4. package/dist/web/client/assets/agent-disclosure-DAMuuGcM.js +1 -0
  5. package/dist/web/client/assets/analytics-stub-B6zGMuSV.js +1 -0
  6. package/dist/web/client/assets/{artifacts-panel-CJ75gQub.js → artifacts-panel-HVjqBMyx.js} +1 -1
  7. package/dist/web/client/assets/button-C5wFDY5_.js +1 -0
  8. package/dist/web/client/assets/card-DA3Vnqay.js +1 -0
  9. package/dist/web/client/assets/chat-BhYseKd6.js +52 -0
  10. package/dist/web/client/assets/{chat-command-palette-CkISHfKE.js → chat-command-palette-LILDO_vG.js} +1 -1
  11. package/dist/web/client/assets/{citations-C_tPOJE_.js → citations-BPv8HIpf.js} +2 -2
  12. package/dist/web/client/assets/command-PhicLCoO.js +1 -0
  13. package/dist/web/client/assets/dialog-pBGUjAQ7.js +1 -0
  14. package/dist/web/client/assets/{fleet-message-queue-BCno4XP-.js → fleet-message-queue-fbYYwMj0.js} +1 -1
  15. package/dist/web/client/assets/{fleet-pi-tool-renderer-BEDIO3al.js → fleet-pi-tool-renderer-BRgNnmaJ.js} +28 -28
  16. package/dist/web/client/assets/fleet-reasoning-panel-CjA2dwUJ.js +1 -0
  17. package/dist/web/client/assets/{fork-picker-dialog-ByBOGVsa.js → fork-picker-dialog-B7NNpVmW.js} +1 -1
  18. package/dist/web/client/assets/globals-CuIiqOTg.css +2 -0
  19. package/dist/web/client/assets/horizontal-resize-Cp3iHMH7.js +1 -0
  20. package/dist/web/client/assets/index-CSGplyr_.js +12 -0
  21. package/dist/web/client/assets/{input-0wwjWjKq.js → input-PcdubeKn.js} +1 -1
  22. package/dist/web/client/assets/input-group-C2HJEZvl.js +1 -0
  23. package/dist/web/client/assets/ipython-tool-CyuMOxz1.js +3 -0
  24. package/dist/web/client/assets/{markdown-code-CuQcR3UO.js → markdown-code-xNyjdZ4g.js} +1 -1
  25. package/dist/web/client/assets/{markdown-DInPmRUG.js → markdown-lvexwooo.js} +2 -2
  26. package/dist/web/client/assets/{model-selector-list-Ch05B2nZ.js → model-selector-list-CoXX358G.js} +1 -1
  27. package/dist/web/client/assets/{notify-DgMKgZZW.js → notify-DxL0VD7Z.js} +1 -1
  28. package/dist/web/client/assets/openui-renderer-TLNe5e_Q.js +378 -0
  29. package/dist/web/client/assets/prompt-input-fwP8TsgQ.js +1 -0
  30. package/dist/web/client/assets/{prompt-suggestions-Cc--lSNR.js → prompt-suggestions-BlGXmWS7.js} +1 -1
  31. package/dist/web/client/assets/provider-catalog-DYMFdbKc.js +1 -0
  32. package/dist/web/client/assets/provider-label-B4q5mI65.js +1 -0
  33. package/dist/web/client/assets/{repl-panel-DsPX04Xr.js → repl-panel-C5om8MGU.js} +1 -1
  34. package/dist/web/client/assets/{right-panel-registry-Dp8yX-oL.js → right-panel-registry-BBlxLxnx.js} +2 -2
  35. package/dist/web/client/assets/routes-XGsAIqBJ.js +16 -0
  36. package/dist/web/client/assets/settings-dialog-BSBasR3o.js +2 -0
  37. package/dist/web/client/assets/{subagent-chat-panel-Yujplxpb.js → subagent-chat-panel-oeLDPDAK.js} +1 -1
  38. package/dist/web/client/assets/{subagent-transcript-CnI-mqN0.js → subagent-transcript-DaVHjNUH.js} +1 -1
  39. package/dist/web/client/assets/{subagents-panel-CjXEdimj.js → subagents-panel-D_aVgSCi.js} +1 -1
  40. package/dist/web/client/assets/surfaces-BhAXz9So.js +1 -0
  41. package/dist/web/client/assets/{switch-9rTeg6J6.js → switch-WhXeJNNY.js} +1 -1
  42. package/dist/web/client/assets/{toast-Bt9p2OL4.js → toast-D3c9XeGe.js} +1 -1
  43. package/dist/web/client/assets/{tool-result-DfnPN0FQ.js → tool-result-Cki8WUNc.js} +3 -3
  44. package/dist/web/client/assets/virtualized-turn-list-CdMa-ydT.js +6 -0
  45. package/dist/web/client/assets/workspace-panel-77bDbprs.js +1 -0
  46. package/dist/web/client/bundle-budget.json +5 -5
  47. package/dist/web/launcher.mjs +10 -1
  48. package/dist/web/server/{_tanstack-start-manifest_v-DS_CSIFQ-WB3VTKF3.js → _tanstack-start-manifest_v-0L7fqnH4-KMVGUFE4.js} +24 -24
  49. package/dist/web/server/{agent-activity-CS2rbF_C-A3R26CYH.js → agent-activity-BuEE6iVw-CDZLK4D2.js} +4 -4
  50. package/dist/web/server/{artifacts-panel-CaKrX2XC-R37I4BHF.js → artifacts-panel-D3ySO--V-XSL53CXL.js} +18 -18
  51. package/dist/web/server/{chat-command-palette-7gbqEnm9-6XICQZN7.js → chat-command-palette-kAbXhWVg-TJWXH2WW.js} +9 -9
  52. package/dist/web/server/{chunk-CNXJN5HN.js → chunk-2DDWXTVG.js} +5 -5
  53. package/dist/web/server/{chunk-TWYHHG5N.js → chunk-2JHTYWD5.js} +15 -15
  54. package/dist/web/server/{chunk-EAE2QXRK.js → chunk-3TIMGWWI.js} +1 -2
  55. package/dist/web/server/{chunk-AGFRJQZ7.js → chunk-4WBFCB4O.js} +257 -257
  56. package/dist/web/server/{chunk-P6UMR32I.js → chunk-5UCAJBAH.js} +4 -4
  57. package/dist/web/server/{chunk-DZ2IC3FH.js → chunk-D27LRYVT.js} +42 -42
  58. package/dist/web/server/{chunk-YXXRP3NS.js → chunk-DLUZFOG3.js} +5 -5
  59. package/dist/web/server/{chunk-K3BXQG3N.js → chunk-EBG3XFGQ.js} +60 -60
  60. package/dist/web/server/{chunk-2ATMSG7U.js → chunk-EJEPDVK6.js} +3 -3
  61. package/dist/web/server/{chunk-IJPCV6DH.js → chunk-FYDQK6UX.js} +1 -1
  62. package/dist/web/server/{chunk-WHBVQJ4K.js → chunk-HF7DG7PC.js} +73 -73
  63. package/dist/web/server/{chunk-WYL2DW5R.js → chunk-JTCCQZLN.js} +2 -2
  64. package/dist/web/server/{chunk-UAPNEWCA.js → chunk-KP2PNDGL.js} +6 -6
  65. package/dist/web/server/{chunk-7SR6WDM2.js → chunk-MNBGB2SP.js} +2 -2
  66. package/dist/web/server/{chunk-HSJMLO36.js → chunk-MNPHMZU7.js} +3 -3
  67. package/dist/web/server/{chunk-JQDLJ7KI.js → chunk-NGCQJ33Y.js} +1134 -1134
  68. package/dist/web/server/{chunk-7SD2ZSEA.js → chunk-NIDBN7BQ.js} +1 -1
  69. package/dist/web/server/{chunk-ZJFA4F5X.js → chunk-NIZBFVJF.js} +2 -2
  70. package/dist/web/server/{chunk-PIOFZUOU.js → chunk-NJ4QEZHK.js} +3 -4
  71. package/dist/web/server/{chunk-WM4PUT6M.js → chunk-NNKJTKOQ.js} +234 -234
  72. package/dist/web/server/{chunk-N6NOTURM.js → chunk-NWDI3OWB.js} +4 -4
  73. package/dist/web/server/{chunk-KELEYPVK.js → chunk-O3HDELZ2.js} +74826 -74825
  74. package/dist/web/server/{chunk-YNUWKMNQ.js → chunk-PLYZ7P2Y.js} +1 -152
  75. package/dist/web/server/{chunk-LXJD6IGW.js → chunk-QFRCJWQQ.js} +7 -7
  76. package/dist/web/server/{chunk-NXJ32VFR.js → chunk-QIULBRTU.js} +13 -13
  77. package/dist/web/server/{chunk-DI7XRK2H.js → chunk-RIP3PBWT.js} +3 -3
  78. package/dist/web/server/{chunk-YKVGQBZA.js → chunk-S5LFXXDY.js} +625 -622
  79. package/dist/web/server/{chunk-XLNCWZMZ.js → chunk-UCUFC553.js} +3 -1
  80. package/dist/web/server/{chunk-KOUEJ3NU.js → chunk-UL2725KQ.js} +3 -3
  81. package/dist/web/server/{chunk-HFQTI7AX.js → chunk-VPKX7QRU.js} +3 -3
  82. package/dist/web/server/{chunk-LZ4Q5KUJ.js → chunk-WNCAL4HN.js} +133 -133
  83. package/dist/web/server/{chunk-C5X6G26O.js → chunk-XIB6XNUX.js} +436 -436
  84. package/dist/web/server/{chunk-EH4BZ6QM.js → chunk-YGYOBX7G.js} +2 -2
  85. package/dist/web/server/{chunk-BBBS5AS7.js → chunk-ZIFSJ3HQ.js} +626 -626
  86. package/dist/web/server/{fleet-message-queue-BS-cZ56r-GLZXUKDF.js → fleet-message-queue-D_w8lSeY-T3HB7HVQ.js} +5 -5
  87. package/dist/web/server/{fleet-pi-tool-renderer-umKunT3K-O4GBH35V.js → fleet-pi-tool-renderer-qwc-qZFq-4U556N5Q.js} +12 -12
  88. package/dist/web/server/{fleet-reasoning-panel-BxC2COxi-GPLMQSTT.js → fleet-reasoning-panel-DG_vuvv0-NCZWEEIT.js} +26 -26
  89. package/dist/web/server/{fleet-subagent-list-CpSYzCIq-JDH2LHSS.js → fleet-subagent-list-BGb0PjhF-2X67FMUV.js} +1 -1
  90. package/dist/web/server/{fleet-tool-timeline-IB36_PB2-XJENTO6I.js → fleet-tool-timeline-D6yM4ZMz-ZSFVRRRI.js} +1 -1
  91. package/dist/web/server/{fork-picker-dialog-DlINz9UQ-UWIXFDJX.js → fork-picker-dialog-Ds6Tgtu3-DIBWN7MV.js} +5 -5
  92. package/dist/web/server/{model-selector-list-jBTZc2Mu-OXJOS5NA.js → model-selector-list-DxeGZoD2-P6S6FSJH.js} +9 -9
  93. package/dist/web/server/{openui-renderer-Biyyjhjn-SJOEKPTJ.js → openui-renderer-MKFv6LlH-MDRSVFEY.js} +11 -11
  94. package/dist/web/server/{prompt-suggestions-D4AEEY7K-RZSP7PWV.js → prompt-suggestions-BOItmGnD-YQA7RHLG.js} +2 -2
  95. package/dist/web/server/{repl-panel-D5KsoJik-BQKO53JO.js → repl-panel-CuyF8wt5-CHDT2Q3N.js} +2 -2
  96. package/dist/web/server/{router-DWALoX_M-I5CBL7FQ.js → router-Dg72SESP-PCWKLFRG.js} +16 -16
  97. package/dist/web/server/{routes-BBVirYfL-5WXMOT7M.js → routes-BGrVtPYF-SURPAJN6.js} +6432 -6413
  98. package/dist/web/server/server.js +2 -2
  99. package/dist/web/server/{settings-dialog-DndPbM-v-RFNXXEAN.js → settings-dialog-CK0FJlTg-HZRBAXDZ.js} +1392 -1402
  100. package/dist/web/server/{subagent-chat-panel-C0RpnXav-J7VYO7QY.js → subagent-chat-panel-gzamnTgK-PQ2TLLJF.js} +21 -21
  101. package/dist/web/server/{subagents-panel-jDqIn9Nh-YEV6QLUY.js → subagents-panel-Berjd7HJ-EI5ZKC6V.js} +20 -20
  102. package/dist/web/server/{workspace-panel-CX_Vo6As-EWO6AMPI.js → workspace-panel-CBBa_3UO-B4PPFPMA.js} +45 -20
  103. package/package.json +1 -1
  104. package/dist/web/client/assets/action-swap-roll-BB8DFMuf.js +0 -1
  105. package/dist/web/client/assets/agent-activity-CwMDU7aP.js +0 -1
  106. package/dist/web/client/assets/agent-disclosure-B8SnhQXL.js +0 -1
  107. package/dist/web/client/assets/analytics-stub-Cas1He0w.js +0 -1
  108. package/dist/web/client/assets/button-BR2WWrFo.js +0 -1
  109. package/dist/web/client/assets/card-BKl76a3R.js +0 -1
  110. package/dist/web/client/assets/chat-Ci2cEQrR.js +0 -52
  111. package/dist/web/client/assets/command-_WjnervL.js +0 -1
  112. package/dist/web/client/assets/dialog-BEZuZhzk.js +0 -1
  113. package/dist/web/client/assets/fleet-reasoning-panel-CbkO7u-v.js +0 -1
  114. package/dist/web/client/assets/globals-B_I1Eq1u.css +0 -2
  115. package/dist/web/client/assets/horizontal-resize-CwoDojST.js +0 -1
  116. package/dist/web/client/assets/index-DJ1fFbQu.js +0 -12
  117. package/dist/web/client/assets/input-group-Tb5TYbzi.js +0 -1
  118. package/dist/web/client/assets/ipython-tool-ldJ9F1Cw.js +0 -3
  119. package/dist/web/client/assets/openui-renderer-Bf60nxNi.js +0 -378
  120. package/dist/web/client/assets/prompt-input-qPIXZ-Rb.js +0 -1
  121. package/dist/web/client/assets/provider-catalog-D8G66gwc.js +0 -1
  122. package/dist/web/client/assets/provider-label-DDLRnZyC.js +0 -1
  123. package/dist/web/client/assets/routes-DpgMJZ5n.js +0 -16
  124. package/dist/web/client/assets/settings-dialog-YJkvt5EB.js +0 -2
  125. package/dist/web/client/assets/surfaces-DI-U-_2a.js +0 -1
  126. package/dist/web/client/assets/virtualized-turn-list--Vynre4D.js +0 -6
  127. package/dist/web/client/assets/workspace-panel-CPPIznT9.js +0 -1
@@ -24,7 +24,7 @@ import {
24
24
  useHoverInteractionSharedState,
25
25
  useHoverReferenceInteraction,
26
26
  usePopupHandleStore
27
- } from "./chunk-TWYHHG5N.js";
27
+ } from "./chunk-2JHTYWD5.js";
28
28
  import {
29
29
  UI_PREFERENCES_KEY,
30
30
  isModelPatternEnabled,
@@ -56,11 +56,11 @@ import {
56
56
  } from "./chunk-OKL5ZRVB.js";
57
57
  import {
58
58
  notify
59
- } from "./chunk-WYL2DW5R.js";
59
+ } from "./chunk-JTCCQZLN.js";
60
60
  import {
61
61
  FloatingPortalLite,
62
62
  Info
63
- } from "./chunk-KOUEJ3NU.js";
63
+ } from "./chunk-UL2725KQ.js";
64
64
  import "./chunk-UCA4WN66.js";
65
65
  import {
66
66
  Eye
@@ -84,7 +84,7 @@ import {
84
84
  InputGroupButton,
85
85
  InputGroupInput,
86
86
  Textarea
87
- } from "./chunk-PIOFZUOU.js";
87
+ } from "./chunk-NJ4QEZHK.js";
88
88
  import {
89
89
  Search
90
90
  } from "./chunk-JIULUYHT.js";
@@ -105,7 +105,7 @@ import {
105
105
  FieldGroup,
106
106
  FieldLabel,
107
107
  Switch
108
- } from "./chunk-UAPNEWCA.js";
108
+ } from "./chunk-KP2PNDGL.js";
109
109
  import {
110
110
  Select,
111
111
  clamp,
@@ -117,7 +117,7 @@ import {
117
117
  useDirection,
118
118
  usePositioner,
119
119
  useRender
120
- } from "./chunk-WM4PUT6M.js";
120
+ } from "./chunk-NNKJTKOQ.js";
121
121
  import {
122
122
  isCustomProviderId,
123
123
  isNamedOccInstanceId,
@@ -126,7 +126,7 @@ import {
126
126
  import "./chunk-WWJF4RO2.js";
127
127
  import {
128
128
  Input
129
- } from "./chunk-P6UMR32I.js";
129
+ } from "./chunk-5UCAJBAH.js";
130
130
  import {
131
131
  Dialog,
132
132
  DialogBackdrop,
@@ -160,7 +160,7 @@ import {
160
160
  usePopupRootStore,
161
161
  useSurface,
162
162
  useTriggerDataForwarding
163
- } from "./chunk-AGFRJQZ7.js";
163
+ } from "./chunk-4WBFCB4O.js";
164
164
  import {
165
165
  Button,
166
166
  POPUP_COLLISION_AVOIDANCE,
@@ -181,7 +181,7 @@ import {
181
181
  ownerDocument,
182
182
  useTimeout,
183
183
  webkit
184
- } from "./chunk-LZ4Q5KUJ.js";
184
+ } from "./chunk-WNCAL4HN.js";
185
185
  import {
186
186
  Check
187
187
  } from "./chunk-4QD4PCVP.js";
@@ -189,7 +189,7 @@ import "./chunk-S3PQ6HKH.js";
189
189
  import {
190
190
  useBaseUiId,
191
191
  useValueAsRef
192
- } from "./chunk-7SR6WDM2.js";
192
+ } from "./chunk-MNBGB2SP.js";
193
193
  import {
194
194
  X
195
195
  } from "./chunk-JBLHIEVG.js";
@@ -212,7 +212,7 @@ import {
212
212
  useOpenChangeComplete,
213
213
  useRenderElement,
214
214
  useStableCallback
215
- } from "./chunk-C5X6G26O.js";
215
+ } from "./chunk-XIB6XNUX.js";
216
216
  import {
217
217
  Plug
218
218
  } from "./chunk-AHTO6GDS.js";
@@ -228,7 +228,7 @@ import "./chunk-JDCCC7A7.js";
228
228
  import "./chunk-L547YHC4.js";
229
229
  import "./chunk-MLKGABMK.js";
230
230
 
231
- // web/app/dist/server/assets/settings-dialog-DndPbM-v.js
231
+ // web/app/dist/server/assets/settings-dialog-CK0FJlTg.js
232
232
  import * as React$1 from "react";
233
233
  import { useCallback as useCallback2, useEffect as useEffect2, useId, useMemo as useMemo2, useRef as useRef2, useState as useState2 } from "react";
234
234
  import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
@@ -722,6 +722,46 @@ function useFocus(context, props = {}) {
722
722
  trigger: reference
723
723
  } : {}, [enabled, reference]);
724
724
  }
725
+ function Breadcrumb({ className, ...props }) {
726
+ return /* @__PURE__ */ jsx("nav", {
727
+ "aria-label": "breadcrumb",
728
+ "data-slot": "breadcrumb",
729
+ className: cn(className),
730
+ ...props
731
+ });
732
+ }
733
+ function BreadcrumbList({ className, ...props }) {
734
+ return /* @__PURE__ */ jsx("ol", {
735
+ "data-slot": "breadcrumb-list",
736
+ className: cn("flex flex-wrap items-center gap-1.5 text-sm wrap-break-word text-muted-foreground", className),
737
+ ...props
738
+ });
739
+ }
740
+ function BreadcrumbItem({ className, ...props }) {
741
+ return /* @__PURE__ */ jsx("li", {
742
+ "data-slot": "breadcrumb-item",
743
+ className: cn("inline-flex items-center gap-1", className),
744
+ ...props
745
+ });
746
+ }
747
+ function BreadcrumbPage({ className, ...props }) {
748
+ return /* @__PURE__ */ jsx("span", {
749
+ "data-slot": "breadcrumb-page",
750
+ "aria-current": "page",
751
+ className: cn("font-normal text-foreground", className),
752
+ ...props
753
+ });
754
+ }
755
+ function BreadcrumbSeparator({ children, className, ...props }) {
756
+ return /* @__PURE__ */ jsx("li", {
757
+ "data-slot": "breadcrumb-separator",
758
+ role: "presentation",
759
+ "aria-hidden": "true",
760
+ className: cn("[&>svg]:size-3.5", className),
761
+ ...props,
762
+ children: children ?? /* @__PURE__ */ jsx(ChevronRight, {})
763
+ });
764
+ }
725
765
  var ScrollAreaRootContext = /* @__PURE__ */ React$1.createContext(void 0);
726
766
  function useScrollAreaRootContext() {
727
767
  const context = React$1.useContext(ScrollAreaRootContext);
@@ -1414,46 +1454,6 @@ function ScrollBar({ className, orientation = "vertical", ...props }) {
1414
1454
  })
1415
1455
  });
1416
1456
  }
1417
- function Breadcrumb({ className, ...props }) {
1418
- return /* @__PURE__ */ jsx("nav", {
1419
- "aria-label": "breadcrumb",
1420
- "data-slot": "breadcrumb",
1421
- className: cn(className),
1422
- ...props
1423
- });
1424
- }
1425
- function BreadcrumbList({ className, ...props }) {
1426
- return /* @__PURE__ */ jsx("ol", {
1427
- "data-slot": "breadcrumb-list",
1428
- className: cn("flex flex-wrap items-center gap-1.5 text-sm wrap-break-word text-muted-foreground", className),
1429
- ...props
1430
- });
1431
- }
1432
- function BreadcrumbItem({ className, ...props }) {
1433
- return /* @__PURE__ */ jsx("li", {
1434
- "data-slot": "breadcrumb-item",
1435
- className: cn("inline-flex items-center gap-1", className),
1436
- ...props
1437
- });
1438
- }
1439
- function BreadcrumbPage({ className, ...props }) {
1440
- return /* @__PURE__ */ jsx("span", {
1441
- "data-slot": "breadcrumb-page",
1442
- "aria-current": "page",
1443
- className: cn("font-normal text-foreground", className),
1444
- ...props
1445
- });
1446
- }
1447
- function BreadcrumbSeparator({ children, className, ...props }) {
1448
- return /* @__PURE__ */ jsx("li", {
1449
- "data-slot": "breadcrumb-separator",
1450
- role: "presentation",
1451
- "aria-hidden": "true",
1452
- className: cn("[&>svg]:size-3.5", className),
1453
- ...props,
1454
- children: children ?? /* @__PURE__ */ jsx(ChevronRight, {})
1455
- });
1456
- }
1457
1457
  var MOBILE_BREAKPOINT = 768;
1458
1458
  function useIsMobile() {
1459
1459
  const [isMobile, setIsMobile] = React$1.useState(void 0);
@@ -2253,42 +2253,6 @@ function SidebarMenuButton({ render, isActive = false, variant = "default", size
2253
2253
  ...tooltip
2254
2254
  })] });
2255
2255
  }
2256
- var THEME_OPTIONS = [
2257
- {
2258
- value: "system",
2259
- label: "System",
2260
- icon: Monitor
2261
- },
2262
- {
2263
- value: "light",
2264
- label: "Light",
2265
- icon: Sun
2266
- },
2267
- {
2268
- value: "dark",
2269
- label: "Dark",
2270
- icon: Moon
2271
- }
2272
- ];
2273
- function PersonalizationSection({ onThemePreferenceChange, themePreference }) {
2274
- return /* @__PURE__ */ jsxs(Field, {
2275
- orientation: "horizontal",
2276
- className: "items-center justify-between gap-6 rounded-xl border p-4",
2277
- children: [/* @__PURE__ */ jsxs("div", {
2278
- className: "min-w-0",
2279
- children: [/* @__PURE__ */ jsx(FieldLabel, { children: "Theme" }), /* @__PURE__ */ jsx(FieldDescription, { children: "Match the system theme or pick light or dark." })]
2280
- }), /* @__PURE__ */ jsx("div", {
2281
- className: "shrink-0",
2282
- children: /* @__PURE__ */ jsx(Select, {
2283
- "aria-label": "Theme",
2284
- className: "w-[160px]",
2285
- onValueChange: (value) => onThemePreferenceChange(value),
2286
- options: [...THEME_OPTIONS],
2287
- value: themePreference
2288
- })
2289
- })]
2290
- });
2291
- }
2292
2256
  function RowSurface({ className, interactive, padding, tone, ...props }) {
2293
2257
  return /* @__PURE__ */ jsx("div", {
2294
2258
  className: cn(fleetPiRowSurface({
@@ -2589,14 +2553,10 @@ function ProviderOAuthSignIn({ onConfigured, onOAuthLogin, provider }) {
2589
2553
  }),
2590
2554
  userCode ? /* @__PURE__ */ jsxs("p", {
2591
2555
  className: "text-sm",
2592
- children: [
2593
- "Enter this code:",
2594
- " ",
2595
- /* @__PURE__ */ jsx("span", {
2596
- className: "font-mono text-foreground",
2597
- children: userCode
2598
- })
2599
- ]
2556
+ children: ["Enter this code: ", /* @__PURE__ */ jsx("span", {
2557
+ className: "font-mono text-foreground",
2558
+ children: userCode
2559
+ })]
2600
2560
  }) : null,
2601
2561
  instructions && !userCode ? /* @__PURE__ */ jsx("p", {
2602
2562
  className: "text-xs text-pretty text-muted-foreground",
@@ -2980,1374 +2940,1299 @@ function McpConnectionsSection({ connections, isLoading, isPending, onOAuth, onR
2980
2940
  ]
2981
2941
  });
2982
2942
  }
2983
- function isFieldInvalid(value, attemptedSave, required = true) {
2984
- return attemptedSave && required && value.trim().length === 0;
2943
+ function customModelKey(provider, model) {
2944
+ return `${provider}/${model}`;
2985
2945
  }
2986
- function SecretCredentialField({ attemptedSave, label, onChange, onToggleVisibility, placeholder, required = true, showPassword, value }) {
2987
- const controlId = useId();
2988
- const invalid = isFieldInvalid(value, attemptedSave, required);
2989
- return /* @__PURE__ */ jsxs(Field, {
2990
- "data-invalid": invalid || void 0,
2991
- children: [/* @__PURE__ */ jsx(FieldLabel, {
2992
- htmlFor: controlId,
2993
- children: label
2994
- }), /* @__PURE__ */ jsxs(InputGroup, { children: [
2995
- /* @__PURE__ */ jsx(InputGroupAddon, {
2996
- align: "inline-start",
2997
- children: /* @__PURE__ */ jsx(Lock, {})
2998
- }),
2999
- /* @__PURE__ */ jsx(InputGroupInput, {
3000
- id: controlId,
3001
- type: showPassword ? "text" : "password",
3002
- placeholder,
3003
- value,
3004
- onChange: (event) => onChange(event.target.value),
3005
- "aria-label": label,
3006
- "aria-invalid": invalid || void 0
3007
- }),
3008
- /* @__PURE__ */ jsx(InputGroupAddon, {
3009
- align: "inline-end",
3010
- children: /* @__PURE__ */ jsx(InputGroupButton, {
3011
- size: "icon-xs",
3012
- className: cn(HIT_AREA_EXPAND_CLASS, "transition-[background-color,transform] duration-150 active:scale-[0.96]"),
3013
- "aria-label": showPassword ? "Hide value" : "Show value",
3014
- onClick: onToggleVisibility,
3015
- children: showPassword ? /* @__PURE__ */ jsx(EyeOff, {}) : /* @__PURE__ */ jsx(Eye, {})
3016
- })
3017
- })
3018
- ] })]
3019
- });
2946
+ function isModelEnabled(model, patterns) {
2947
+ return isModelPatternEnabled({
2948
+ id: model.id,
2949
+ name: model.name,
2950
+ provider: model.provider,
2951
+ modelId: model.modelId
2952
+ }, patterns);
3020
2953
  }
3021
- function TextCredentialField({ attemptedSave, icon: Icon, inputType = "text", label, onChange, placeholder, required = true, value }) {
3022
- const controlId = useId();
3023
- const invalid = isFieldInvalid(value, attemptedSave, required);
3024
- return /* @__PURE__ */ jsxs(Field, {
3025
- "data-invalid": invalid || void 0,
3026
- children: [/* @__PURE__ */ jsx(FieldLabel, {
3027
- htmlFor: controlId,
3028
- children: label
3029
- }), /* @__PURE__ */ jsxs(InputGroup, { children: [/* @__PURE__ */ jsx(InputGroupAddon, {
3030
- align: "inline-start",
3031
- children: /* @__PURE__ */ jsx(Icon, {})
3032
- }), /* @__PURE__ */ jsx(InputGroupInput, {
3033
- id: controlId,
3034
- type: inputType,
3035
- placeholder,
3036
- value,
3037
- onChange: (event) => onChange(event.target.value),
3038
- "aria-label": label,
3039
- "aria-invalid": invalid || void 0
3040
- })] })]
3041
- });
2954
+ function nextEnabledModelPatterns({ currentPatterns, enabled, model, models }) {
2955
+ if (currentPatterns === void 0 && enabled) return void 0;
2956
+ const current = currentPatterns ?? [];
2957
+ const knownModelPatterns = new Set(models.map((item) => modelPatternFor(item)));
2958
+ const activeKnown = /* @__PURE__ */ new Set();
2959
+ for (const item of models) if (isModelEnabled(item, currentPatterns)) activeKnown.add(modelPatternFor(item));
2960
+ if (enabled) activeKnown.add(modelPatternFor(model));
2961
+ else activeKnown.delete(modelPatternFor(model));
2962
+ return [...current.filter((pattern) => {
2963
+ if (knownModelPatterns.has(pattern)) return false;
2964
+ return enabled || !modelMatchesPattern(model, pattern);
2965
+ }), ...activeKnown];
3042
2966
  }
3043
- var OPENAI_CHAT_BASE_URL_PLACEHOLDER = "https://opencode.ai/zen/v1";
3044
- var OPENAI_CHAT_MODEL_PLACEHOLDER = "qwen35-122b-a10b";
3045
- var OPENAI_CHAT_NAME_PLACEHOLDER = "e.g. OpenCode Zen, Nebius, Groq\u2026";
3046
- var CUSTOM_MODELS_PLACEHOLDER = "model-a, model-b (comma-separated)";
3047
- var CUSTOM_PROVIDER_API_OPTIONS = [
3048
- {
3049
- value: "openai-completions",
3050
- label: "OpenAI Chat Completions"
3051
- },
3052
- {
3053
- value: "openai-responses",
3054
- label: "OpenAI Responses"
3055
- },
3056
- {
3057
- value: "anthropic-messages",
3058
- label: "Anthropic Messages"
3059
- },
3060
- {
3061
- value: "google-genai",
3062
- label: "Google Generative AI"
3063
- }
3064
- ];
3065
- function ProviderCredentialFields({ attemptedSave, apiKey, api, baseUrl, modelId, models, displayName, isCustom, onApiKeyChange, onApiChange, onBaseUrlChange, onModelIdChange, onModelsChange, onDisplayNameChange, onTogglePassword, openAiChat, placeholder, showPassword }) {
3066
- const showApiSelector = isCustom && onApiChange;
3067
- const showMultiModels = isCustom && onModelsChange;
3068
- return /* @__PURE__ */ jsxs(FieldGroup, {
3069
- className: "gap-2",
3070
- children: [
3071
- (openAiChat || isCustom) && onDisplayNameChange ? /* @__PURE__ */ jsx(TextCredentialField, {
3072
- attemptedSave,
3073
- icon: Tag,
3074
- label: "Provider name",
3075
- placeholder: OPENAI_CHAT_NAME_PLACEHOLDER,
3076
- value: displayName ?? "",
3077
- onChange: onDisplayNameChange
3078
- }) : null,
3079
- showApiSelector ? /* @__PURE__ */ jsx(ApiSelector, {
3080
- value: api ?? "openai-completions",
3081
- onChange: onApiChange
3082
- }) : null,
3083
- /* @__PURE__ */ jsx(SecretCredentialField, {
3084
- attemptedSave,
3085
- label: "API key",
3086
- placeholder,
3087
- value: apiKey,
3088
- showPassword,
3089
- onChange: onApiKeyChange,
3090
- onToggleVisibility: onTogglePassword
3091
- }),
3092
- openAiChat || isCustom ? /* @__PURE__ */ jsx(TextCredentialField, {
3093
- attemptedSave,
3094
- icon: Globe,
3095
- inputType: "url",
3096
- label: "Base URL",
3097
- placeholder: OPENAI_CHAT_BASE_URL_PLACEHOLDER,
3098
- value: baseUrl,
3099
- onChange: onBaseUrlChange
3100
- }) : null,
3101
- showMultiModels ? /* @__PURE__ */ jsx(TextCredentialField, {
3102
- attemptedSave,
3103
- icon: Layers,
3104
- label: "Model ids",
3105
- placeholder: CUSTOM_MODELS_PLACEHOLDER,
3106
- value: models ?? "",
3107
- onChange: onModelsChange
3108
- }) : null,
3109
- openAiChat && !showMultiModels ? /* @__PURE__ */ jsx(TextCredentialField, {
3110
- attemptedSave,
3111
- icon: Box,
3112
- label: "Model name",
3113
- placeholder: OPENAI_CHAT_MODEL_PLACEHOLDER,
3114
- value: modelId,
3115
- onChange: onModelIdChange
3116
- }) : null
3117
- ]
3118
- });
3119
- }
3120
- function ApiSelector({ value, onChange }) {
3121
- return /* @__PURE__ */ jsxs("div", {
3122
- className: "flex flex-col gap-1",
3123
- children: [/* @__PURE__ */ jsx("span", {
3124
- className: "text-xs font-medium text-muted-foreground",
3125
- children: "API family"
3126
- }), /* @__PURE__ */ jsx(Select, {
3127
- "aria-label": "API family",
3128
- value,
3129
- options: CUSTOM_PROVIDER_API_OPTIONS,
3130
- placeholder: "Select API family",
3131
- onValueChange: (next) => onChange(next)
3132
- })]
3133
- });
3134
- }
3135
- function isOAuthProvider(provider) {
3136
- return provider.authType === "oauth";
3137
- }
3138
- function supportsOAuth(provider) {
3139
- return provider.supportsOAuth === true;
2967
+ function modelPatternFor(model) {
2968
+ return modelPattern(model.provider, model.modelId);
3140
2969
  }
3141
- var CUSTOM_PROVIDER_PICKER_ROW = {
3142
- id: "custom",
3143
- name: "Custom provider",
3144
- isConfigured: false,
3145
- envVarName: "CUSTOM_PROVIDER_API_KEY",
3146
- displayName: "Custom provider"
3147
- };
3148
- function parseCustomProviderModels(raw) {
3149
- return raw.split(",").map((entry) => entry.trim()).filter((entry) => entry.length > 0);
2970
+ function searchableModelText(model) {
2971
+ return [
2972
+ model.name,
2973
+ model.modelId,
2974
+ model.provider,
2975
+ model.id
2976
+ ].join(" ").toLowerCase();
3150
2977
  }
3151
- function useProviderCredentialsController({ onRemoveProvider, onUpdateProvider, providers }) {
3152
- const [editingProvider, setEditingProvider] = useState2(null);
3153
- const [editingIsNewOccInstance, setEditingIsNewOccInstance] = useState2(false);
3154
- const [apiKey, setApiKey] = useState2("");
3155
- const [baseUrl, setBaseUrl] = useState2("");
3156
- const [modelId, setModelId] = useState2("");
3157
- const [models, setModels] = useState2("");
3158
- const [api, setApi] = useState2("openai-completions");
3159
- const [displayName, setDisplayName] = useState2("");
3160
- const [showPassword, setShowPassword] = useState2(false);
3161
- const [attemptedSave, setAttemptedSave] = useState2(false);
3162
- const [searchQuery, setSearchQuery] = useState2("");
3163
- const [addPickerOpen, setAddPickerOpen] = useState2(false);
3164
- const [addPickerQuery, setAddPickerQuery] = useState2("");
3165
- const [confirmRemoveProvider, setConfirmRemoveProvider] = useState2(null);
3166
- const credentialProviders = useMemo2(() => {
3167
- return [...providers.filter((provider) => provider.id !== CUSTOM_PROVIDER_PICKER_ROW.id), CUSTOM_PROVIDER_PICKER_ROW];
3168
- }, [providers]);
3169
- const activeProviders = useMemo2(() => credentialProviders.filter((provider) => provider.isConfigured), [credentialProviders]);
3170
- const unconfiguredProviders = useMemo2(() => credentialProviders.filter((provider) => !provider.isConfigured || provider.id === "openai-chat-completions" && provider.providerFamily === void 0), [credentialProviders]);
3171
- const filteredActiveProviders = useMemo2(() => {
3172
- const query = searchQuery.trim().toLowerCase();
3173
- if (!query) return activeProviders;
3174
- return activeProviders.filter((provider) => providerMatchesQuery(provider, query));
3175
- }, [activeProviders, searchQuery]);
3176
- const filteredPickerAvailable = useMemo2(() => {
3177
- const query = addPickerQuery.trim().toLowerCase();
3178
- return unconfiguredProviders.filter((provider) => providerMatchesQuery(provider, query));
3179
- }, [addPickerQuery, unconfiguredProviders]);
3180
- const filteredPickerConfigured = useMemo2(() => {
3181
- const query = addPickerQuery.trim().toLowerCase();
3182
- return activeProviders.filter((provider) => providerMatchesQuery(provider, query));
3183
- }, [activeProviders, addPickerQuery]);
3184
- const pickerHasResults = filteredPickerAvailable.length > 0 || filteredPickerConfigured.length > 0;
3185
- const editingUnconfiguredProvider = editingProvider && !activeProviders.some((provider) => provider.id === editingProvider) ? credentialProviders.find((entry) => entry.id === editingProvider) ?? null : null;
3186
- const resetEditor = () => {
3187
- setApiKey("");
3188
- setBaseUrl("");
3189
- setModelId("");
3190
- setModels("");
3191
- setApi("openai-completions");
3192
- setDisplayName("");
3193
- setShowPassword(false);
3194
- setAttemptedSave(false);
3195
- };
3196
- const closeEditor = () => {
3197
- setEditingProvider(null);
3198
- setEditingIsNewOccInstance(false);
3199
- resetEditor();
3200
- };
3201
- const openEditor = (providerId, opts) => {
3202
- setAddPickerOpen(false);
3203
- setAddPickerQuery("");
3204
- setEditingProvider(providerId);
3205
- setEditingIsNewOccInstance(opts?.newOccInstance === true);
3206
- resetEditor();
3207
- const existing = providers.find((provider) => provider.id === providerId);
3208
- setBaseUrl(existing?.baseUrl ?? "");
3209
- setDisplayName(existing?.displayName ?? (isCustomProviderId(providerId) ? existing?.name ?? "" : ""));
3210
- setApi(existing?.api ?? "openai-completions");
3211
- setModels((existing?.modelIds ?? []).join(", "));
3212
- setModelId(existing?.modelIds?.[0] ?? "");
3213
- };
3214
- const openAddPicker = () => {
3215
- closeEditor();
3216
- setAddPickerQuery("");
3217
- setAddPickerOpen(true);
3218
- };
3219
- const closeAddPicker = () => {
3220
- setAddPickerOpen(false);
3221
- setAddPickerQuery("");
3222
- };
3223
- const selectProviderFromPicker = (providerId) => {
3224
- openEditor(providerId, { newOccInstance: isOccProviderId(providerId) });
3225
- };
3226
- const handleSave = async (providerId) => {
3227
- if (!onUpdateProvider) return;
3228
- setAttemptedSave(true);
3229
- const isCustomEditor = providerId === "custom" || isCustomProviderId(providerId);
3230
- const openAiChat = isOccProviderId(providerId);
3231
- if (isCustomEditor) {
3232
- const modelList = parseCustomProviderModels(models);
3233
- if (!apiKey.trim() || !baseUrl.trim() || !displayName.trim()) return;
3234
- if (modelList.length === 0) return;
3235
- try {
3236
- await onUpdateProvider({
3237
- providerId,
3238
- apiKey: apiKey.trim(),
3239
- baseUrl: baseUrl.trim(),
3240
- displayName: displayName.trim(),
3241
- api,
3242
- models: modelList,
3243
- ...providerId === "custom" ? { createOccInstance: true } : {}
3244
- });
3245
- notify.success("Provider credentials applied to your active sessions.");
3246
- closeEditor();
3247
- closeAddPicker();
3248
- } catch (error) {
3249
- notify.error(error instanceof Error ? error.message : "Failed to update provider");
3250
- }
3251
- return;
3252
- }
3253
- const isNamedOcc = openAiChat && isNamedOccInstanceId(providerId);
3254
- const isOccLike = openAiChat && (isNamedOcc || editingIsNewOccInstance);
3255
- if (!apiKey.trim()) return;
3256
- if (openAiChat && (!baseUrl.trim() || !modelId.trim())) return;
3257
- if (isOccLike && !displayName.trim()) return;
3258
- try {
3259
- await onUpdateProvider({
3260
- providerId,
3261
- apiKey: apiKey.trim(),
3262
- ...openAiChat ? {
3263
- baseUrl: baseUrl.trim(),
3264
- modelId: modelId.trim(),
3265
- ...isOccLike ? { displayName: displayName.trim() } : {},
3266
- ...editingIsNewOccInstance ? { createOccInstance: true } : {}
3267
- } : {}
3268
- });
3269
- notify.success("Provider credentials applied to your active sessions.");
3270
- closeEditor();
3271
- closeAddPicker();
3272
- } catch (error) {
3273
- notify.error(error instanceof Error ? error.message : "Failed to update provider");
2978
+ function AddModelsDialog({ configuredProviderIds, discoverableProviderIds, discoveringProviderId, enabledModels, modelOptions, onAddModels, onDiscoverProvider, onOpenChange, open, providerLabel = formatProviderLabel }) {
2979
+ const [filter, setFilter] = useState2("");
2980
+ const [selectedKeys, setSelectedKeys] = useState2(/* @__PURE__ */ new Set());
2981
+ const normalizedFilter = filter.trim().toLowerCase();
2982
+ const candidateModels = useMemo2(() => {
2983
+ return modelOptions.filter((model) => {
2984
+ if (!configuredProviderIds.has(model.provider)) return false;
2985
+ if (isModelEnabled(model, enabledModels)) return false;
2986
+ return !normalizedFilter || searchableModelText(model).includes(normalizedFilter);
2987
+ });
2988
+ }, [
2989
+ configuredProviderIds,
2990
+ enabledModels,
2991
+ modelOptions,
2992
+ normalizedFilter
2993
+ ]);
2994
+ const groupedCandidates = useMemo2(() => {
2995
+ const groups = /* @__PURE__ */ new Map();
2996
+ for (const model of candidateModels) {
2997
+ const existing = groups.get(model.provider);
2998
+ if (existing) existing.push(model);
2999
+ else groups.set(model.provider, [model]);
3274
3000
  }
3001
+ return [...groups.entries()];
3002
+ }, [candidateModels]);
3003
+ const discoverableProviders = useMemo2(() => {
3004
+ const ids = discoverableProviderIds ?? configuredProviderIds;
3005
+ return Array.from(ids).filter((id) => configuredProviderIds.has(id)).sort((a, b) => providerLabel(a).localeCompare(providerLabel(b)));
3006
+ }, [
3007
+ configuredProviderIds,
3008
+ discoverableProviderIds,
3009
+ providerLabel
3010
+ ]);
3011
+ const activeCountByProvider = useMemo2(() => {
3012
+ const counts = /* @__PURE__ */ new Map();
3013
+ for (const model of modelOptions) if (isModelEnabled(model, enabledModels)) counts.set(model.provider, (counts.get(model.provider) ?? 0) + 1);
3014
+ return counts;
3015
+ }, [enabledModels, modelOptions]);
3016
+ const toggleSelected = (model) => {
3017
+ setSelectedKeys((current) => {
3018
+ const next = new Set(current);
3019
+ if (next.has(model.id)) next.delete(model.id);
3020
+ else next.add(model.id);
3021
+ return next;
3022
+ });
3275
3023
  };
3276
- const handleRemove = async (provider) => {
3277
- if (!onRemoveProvider) return;
3278
- try {
3279
- await onRemoveProvider({ providerId: provider.id });
3280
- if (editingProvider === provider.id) closeEditor();
3281
- notify.success(`${provider.name} removed from your configured providers.`);
3282
- setConfirmRemoveProvider(null);
3283
- } catch (error) {
3284
- notify.error(error instanceof Error ? error.message : "Failed to remove provider");
3285
- }
3024
+ const confirmAdd = () => {
3025
+ const selected = candidateModels.filter((model) => selectedKeys.has(model.id));
3026
+ if (selected.length > 0) onAddModels(selected);
3027
+ onOpenChange(false);
3286
3028
  };
3287
- return {
3288
- activeProviders,
3289
- addPickerOpen,
3290
- addPickerQuery,
3291
- api,
3292
- apiKey,
3293
- attemptedSave,
3294
- baseUrl,
3295
- canSave: useMemo2(() => {
3296
- if (apiKey.trim().length === 0) return false;
3297
- if (!editingProvider) return true;
3298
- if (editingProvider === "custom" || isCustomProviderId(editingProvider)) {
3299
- if (baseUrl.trim().length === 0) return false;
3300
- if (parseCustomProviderModels(models).length === 0) return false;
3301
- return displayName.trim().length > 0;
3302
- }
3303
- if (!isOccProviderId(editingProvider)) return true;
3304
- if (baseUrl.trim().length === 0 || modelId.trim().length === 0) return false;
3305
- if (isNamedOccInstanceId(editingProvider) || editingIsNewOccInstance) return displayName.trim().length > 0;
3306
- return true;
3307
- }, [
3308
- apiKey,
3309
- baseUrl,
3310
- modelId,
3311
- models,
3312
- displayName,
3313
- editingProvider,
3314
- editingIsNewOccInstance
3315
- ]),
3316
- closeAddPicker,
3317
- closeEditor,
3318
- confirmRemoveProvider,
3319
- credentialProviders,
3320
- displayName,
3321
- editingIsNewOccInstance,
3322
- editingProvider,
3323
- editingUnconfiguredProvider,
3324
- filteredActiveProviders,
3325
- filteredPickerAvailable,
3326
- filteredPickerConfigured,
3327
- handleRemove,
3328
- handleSave,
3329
- modelId,
3330
- models,
3331
- openAddPicker,
3332
- openEditor,
3333
- pickerHasResults,
3334
- resetEditor,
3335
- searchQuery,
3336
- selectProviderFromPicker,
3337
- setAddPickerOpen,
3338
- setAddPickerQuery,
3339
- setApi,
3340
- setApiKey,
3341
- setAttemptedSave,
3342
- setBaseUrl,
3343
- setConfirmRemoveProvider,
3344
- setDisplayName,
3345
- setEditingIsNewOccInstance,
3346
- setEditingProvider,
3347
- setModelId,
3348
- setModels,
3349
- setSearchQuery,
3350
- setShowPassword,
3351
- showPassword,
3352
- unconfiguredProviders
3353
- };
3354
- }
3355
- function providerMatchesQuery(provider, query) {
3356
- if (!query) return true;
3357
- const keywords = supportsOAuth(provider) ? isOAuthProvider(provider) ? "oauth sign-in login" : "api key oauth sign-in login" : provider.id === "custom" || isCustomProviderId(provider.id) ? "custom provider endpoint openai anthropic google compatible" : isOccProviderId(provider.id) ? "api key base url model name chat completions" : "";
3358
- return [
3359
- provider.name,
3360
- provider.envVarName,
3361
- provider.id,
3362
- keywords
3363
- ].join(" ").toLowerCase().includes(query);
3364
- }
3365
- function AddProviderPickerPanel({ addPickerQuery, credentialProvidersLength, filteredPickerAvailable, filteredPickerConfigured, pickerHasResults, onClose, onQueryChange, onSelect }) {
3366
- return /* @__PURE__ */ jsxs("div", {
3367
- className: "flex flex-col gap-3",
3368
- children: [
3369
- /* @__PURE__ */ jsxs("div", {
3370
- className: "flex items-center gap-2",
3371
- children: [/* @__PURE__ */ jsx(Button, {
3372
- type: "button",
3373
- variant: "ghost",
3374
- size: "icon-sm",
3375
- "aria-label": "Back to providers",
3376
- onClick: onClose,
3377
- children: /* @__PURE__ */ jsx(ArrowLeft, {})
3378
- }), /* @__PURE__ */ jsxs("div", {
3379
- className: "min-w-0 flex-1",
3380
- children: [/* @__PURE__ */ jsx("p", {
3381
- className: "text-sm font-medium",
3382
- children: "Add provider"
3383
- }), /* @__PURE__ */ jsx("p", {
3384
- className: "text-xs text-pretty text-muted-foreground",
3385
- children: "Choose a provider to store encrypted credentials for this account."
3029
+ return /* @__PURE__ */ jsx(Dialog, {
3030
+ open,
3031
+ onOpenChange,
3032
+ children: /* @__PURE__ */ jsxs(DialogContent, {
3033
+ className: "max-w-lg",
3034
+ children: [
3035
+ /* @__PURE__ */ jsxs(DialogHeader, { children: [/* @__PURE__ */ jsx(DialogTitle, { children: "Add models" }), /* @__PURE__ */ jsxs(DialogDescription, { children: [
3036
+ "Pick models discovered from your configured providers. For OpenAI Chat Completions, refresh runs",
3037
+ " ",
3038
+ /* @__PURE__ */ jsxs("span", {
3039
+ className: "font-medium",
3040
+ children: ["GET ", "{baseUrl}/models"]
3041
+ }),
3042
+ "."
3043
+ ] })] }),
3044
+ /* @__PURE__ */ jsx("div", {
3045
+ className: "flex flex-wrap gap-1.5",
3046
+ children: discoverableProviders.map((providerId) => {
3047
+ const discovering = discoveringProviderId === providerId;
3048
+ const activeCount = activeCountByProvider.get(providerId) ?? 0;
3049
+ return /* @__PURE__ */ jsxs(Button, {
3050
+ type: "button",
3051
+ variant: "outline",
3052
+ size: "sm",
3053
+ disabled: discoveringProviderId !== null,
3054
+ onClick: () => {
3055
+ onDiscoverProvider(providerId);
3056
+ },
3057
+ children: [
3058
+ discovering ? /* @__PURE__ */ jsx(Spinner, { "data-icon": "inline-start" }) : /* @__PURE__ */ jsx(RefreshCw, { "data-icon": "inline-start" }),
3059
+ providerLabel(providerId),
3060
+ activeCount > 0 ? /* @__PURE__ */ jsxs("span", {
3061
+ className: "tabular-nums text-muted-foreground",
3062
+ children: [
3063
+ "\xB7 ",
3064
+ activeCount,
3065
+ " active"
3066
+ ]
3067
+ }) : null
3068
+ ]
3069
+ }, providerId);
3070
+ })
3071
+ }),
3072
+ /* @__PURE__ */ jsxs(InputGroup, { children: [/* @__PURE__ */ jsx(InputGroupAddon, {
3073
+ align: "inline-start",
3074
+ children: /* @__PURE__ */ jsx(Search, {})
3075
+ }), /* @__PURE__ */ jsx(InputGroupInput, {
3076
+ "aria-label": "Search discovered models",
3077
+ value: filter,
3078
+ onChange: (event) => setFilter(event.target.value),
3079
+ placeholder: "Search discovered models\u2026"
3080
+ })] }),
3081
+ /* @__PURE__ */ jsx("div", {
3082
+ className: "max-h-72 overflow-y-auto",
3083
+ children: groupedCandidates.length === 0 ? /* @__PURE__ */ jsx("p", {
3084
+ className: "py-6 text-center text-xs text-pretty text-muted-foreground",
3085
+ children: candidateModels.length === 0 && !normalizedFilter ? "All discovered models are already in your list, or none are available yet." : "No models match your search."
3086
+ }) : /* @__PURE__ */ jsx("div", {
3087
+ className: "flex flex-col gap-3",
3088
+ children: groupedCandidates.map(([provider, models]) => /* @__PURE__ */ jsxs("div", {
3089
+ className: "flex flex-col gap-1",
3090
+ children: [/* @__PURE__ */ jsxs("div", {
3091
+ className: "px-2 text-[0.6875rem] font-medium tracking-wide text-muted-foreground uppercase",
3092
+ children: [providerLabel(provider), (activeCountByProvider.get(provider) ?? 0) > 0 ? /* @__PURE__ */ jsxs("span", {
3093
+ className: "ml-1 font-normal normal-case tabular-nums",
3094
+ children: [
3095
+ "\xB7 ",
3096
+ activeCountByProvider.get(provider),
3097
+ " active"
3098
+ ]
3099
+ }) : null]
3100
+ }), models.map((model) => {
3101
+ const checked = selectedKeys.has(model.id);
3102
+ return /* @__PURE__ */ jsxs("button", {
3103
+ type: "button",
3104
+ className: cn("flex w-full items-center gap-3 rounded-lg px-2 py-2 text-left transition-[background-color,transform] duration-150", "hover:bg-muted/60 focus-visible:ring-2 focus-visible:ring-ring focus-visible:outline-none active:scale-[0.96]", checked && "bg-muted/70"),
3105
+ "aria-pressed": checked,
3106
+ onClick: () => toggleSelected(model),
3107
+ children: [
3108
+ /* @__PURE__ */ jsx("div", {
3109
+ className: "flex size-8 shrink-0 items-center justify-center rounded-[4px] border border-border/40 bg-background/60",
3110
+ children: /* @__PURE__ */ jsx(ProviderBrandIcon, {
3111
+ provider: model.provider,
3112
+ className: "text-foreground/70"
3113
+ })
3114
+ }),
3115
+ /* @__PURE__ */ jsxs("div", {
3116
+ className: "min-w-0 flex-1",
3117
+ children: [/* @__PURE__ */ jsx("div", {
3118
+ className: "truncate text-sm font-medium",
3119
+ children: model.name
3120
+ }), /* @__PURE__ */ jsx("div", {
3121
+ className: "truncate text-xs text-muted-foreground",
3122
+ children: model.modelId
3123
+ })]
3124
+ }),
3125
+ /* @__PURE__ */ jsx("span", {
3126
+ className: cn("size-4 rounded border border-border/60", checked && "border-foreground bg-foreground"),
3127
+ "aria-hidden": true
3128
+ })
3129
+ ]
3130
+ }, model.id);
3131
+ })]
3132
+ }, provider))
3133
+ })
3134
+ }),
3135
+ /* @__PURE__ */ jsxs("div", {
3136
+ className: "flex items-center justify-end gap-1.5",
3137
+ children: [/* @__PURE__ */ jsx(Button, {
3138
+ type: "button",
3139
+ variant: "ghost",
3140
+ size: "sm",
3141
+ onClick: () => onOpenChange(false),
3142
+ children: "Cancel"
3143
+ }), /* @__PURE__ */ jsxs(Button, {
3144
+ type: "button",
3145
+ size: "sm",
3146
+ disabled: selectedKeys.size === 0,
3147
+ onClick: confirmAdd,
3148
+ children: ["Add ", selectedKeys.size > 0 ? /* @__PURE__ */ jsxs("span", {
3149
+ className: "tabular-nums",
3150
+ children: [
3151
+ "(",
3152
+ selectedKeys.size,
3153
+ ")"
3154
+ ]
3155
+ }) : null]
3386
3156
  })]
3387
- })]
3388
- }),
3389
- /* @__PURE__ */ jsxs(InputGroup, { children: [/* @__PURE__ */ jsx(InputGroupAddon, {
3390
- align: "inline-start",
3391
- children: /* @__PURE__ */ jsx(Search, {})
3392
- }), /* @__PURE__ */ jsx(InputGroupInput, {
3393
- type: "text",
3394
- placeholder: "Search providers\u2026",
3395
- value: addPickerQuery,
3396
- onChange: (event) => onQueryChange(event.target.value),
3397
- "aria-label": "Search providers to add"
3398
- })] }),
3399
- /* @__PURE__ */ jsx("div", {
3400
- className: "max-h-[min(24rem,50vh)] overflow-y-auto",
3401
- children: !pickerHasResults ? /* @__PURE__ */ jsx("p", {
3402
- className: "py-6 text-center text-xs text-pretty text-muted-foreground",
3403
- children: credentialProvidersLength === 0 ? "No providers available." : "No matching providers found."
3404
- }) : /* @__PURE__ */ jsxs("div", {
3405
- className: "flex flex-col gap-3",
3406
- children: [filteredPickerAvailable.length > 0 ? /* @__PURE__ */ jsx("div", {
3407
- className: "flex flex-col gap-1",
3408
- children: filteredPickerAvailable.map((provider) => /* @__PURE__ */ jsx(ProviderPickerRow, {
3409
- provider,
3410
- configured: false,
3411
- onSelect: () => onSelect(provider.id)
3412
- }, provider.id))
3413
- }) : null, filteredPickerConfigured.length > 0 ? /* @__PURE__ */ jsxs("div", {
3414
- className: "flex flex-col gap-1",
3415
- children: [filteredPickerAvailable.length > 0 ? /* @__PURE__ */ jsx("p", {
3416
- className: "px-2 pt-1 text-[0.6875rem] font-medium tracking-wide text-muted-foreground uppercase",
3417
- children: "Already configured"
3418
- }) : null, filteredPickerConfigured.map((provider) => /* @__PURE__ */ jsx(ProviderPickerRow, {
3419
- provider,
3420
- configured: true,
3421
- onSelect: () => onSelect(provider.id)
3422
- }, provider.id))]
3423
- }) : null]
3424
3157
  })
3425
- })
3426
- ]
3158
+ ]
3159
+ })
3427
3160
  });
3428
3161
  }
3429
- function AddProviderOAuthPanel({ onBack, onConfigured, onOAuthLogin, provider }) {
3430
- return /* @__PURE__ */ jsxs("div", {
3431
- className: "flex flex-col gap-3",
3432
- children: [/* @__PURE__ */ jsxs("div", {
3433
- className: "flex items-center gap-2",
3434
- children: [/* @__PURE__ */ jsx(Button, {
3435
- type: "button",
3436
- variant: "ghost",
3437
- size: "icon-sm",
3438
- "aria-label": "Back to provider list",
3439
- onClick: onBack,
3440
- children: /* @__PURE__ */ jsx(ArrowLeft, {})
3441
- }), /* @__PURE__ */ jsxs("div", {
3442
- className: "min-w-0 flex-1",
3443
- children: [/* @__PURE__ */ jsxs("p", {
3444
- className: "text-sm font-medium",
3445
- children: ["Configure ", provider.name]
3446
- }), /* @__PURE__ */ jsx("p", {
3447
- className: "text-xs text-pretty text-muted-foreground",
3448
- children: "OAuth sign-in. Credentials are stored in auth.json for this account."
3449
- })]
3450
- })]
3451
- }), /* @__PURE__ */ jsx(ProviderOAuthSignIn, {
3452
- provider,
3453
- onConfigured,
3454
- onOAuthLogin
3455
- }, provider.id)]
3456
- });
3457
- }
3458
- function AddProviderEditorPanel({ provider, api, apiKey, baseUrl, modelId, models, displayName, showPassword, attemptedSave, isPending, canSave, onApiKeyChange, onApiChange, onBaseUrlChange, onModelIdChange, onModelsChange, onDisplayNameChange, onTogglePassword, onBack, onCancel, onConfigured, onOAuthLogin, onSave }) {
3459
- const isCustomTemplate = provider.id === "custom" || isCustomProviderId(provider.id);
3460
- const meta = PROVIDER_METADATA[provider.id] ?? (isCustomTemplate ? PROVIDER_METADATA["custom"] : {
3461
- placeholder: "Enter credentials\u2026",
3462
- help: "Stored securely in your local environment overrides."
3463
- });
3464
- const openAiChat = isOccProviderId(provider.id) && !isCustomTemplate;
3465
- const oauthAvailable = supportsOAuth(provider);
3466
- return /* @__PURE__ */ jsxs("div", {
3467
- className: "flex flex-col gap-3",
3162
+ function ModelDefaultsSection({ draft, discoveringProviderId, modelDirty, modelFilter, modelOptions, onAddModels, onDiscoverProvider, onModelFilterChange, onRemoveModel, onSetDefaultModel, onRevert, onSave, providers, saving, settingsLoading }) {
3163
+ const [addOpen, setAddOpen] = useState2(false);
3164
+ const disabled2 = !draft || settingsLoading;
3165
+ const normalizedFilter = modelFilter.trim().toLowerCase();
3166
+ const configuredProviderIds = useMemo2(() => {
3167
+ const ids = /* @__PURE__ */ new Set();
3168
+ for (const provider of providers) if (provider.isConfigured) ids.add(provider.id);
3169
+ for (const model of modelOptions) if (isModelEnabled(model, draft?.enabledModels)) ids.add(model.provider);
3170
+ return ids;
3171
+ }, [
3172
+ draft?.enabledModels,
3173
+ modelOptions,
3174
+ providers
3175
+ ]);
3176
+ const discoverableProviderIds = useMemo2(() => {
3177
+ const ids = /* @__PURE__ */ new Set();
3178
+ for (const provider of providers) if (provider.discoverable === true && configuredProviderIds.has(provider.id)) ids.add(provider.id);
3179
+ return ids;
3180
+ }, [configuredProviderIds, providers]);
3181
+ const isDefaultModel = useMemo2(() => {
3182
+ return (model) => !!draft?.defaultProvider && !!draft?.defaultModel && model.provider === draft.defaultProvider && model.modelId === draft.defaultModel;
3183
+ }, [draft?.defaultModel, draft?.defaultProvider]);
3184
+ const providerLabel = useMemo2(() => {
3185
+ const labels = /* @__PURE__ */ new Map();
3186
+ for (const provider of providers) if (provider.displayName || provider.name) labels.set(provider.id, provider.displayName ?? provider.name ?? provider.id);
3187
+ return (providerId) => labels.get(providerId) ?? formatProviderLabel(providerId);
3188
+ }, [providers]);
3189
+ const listedModels = useMemo2(() => {
3190
+ return modelOptions.filter((model) => {
3191
+ if (!isModelEnabled(model, draft?.enabledModels)) return false;
3192
+ if (!normalizedFilter) return true;
3193
+ return [
3194
+ model.name,
3195
+ model.modelId,
3196
+ model.provider,
3197
+ model.id
3198
+ ].join(" ").toLowerCase().includes(normalizedFilter);
3199
+ });
3200
+ }, [
3201
+ draft?.enabledModels,
3202
+ modelOptions,
3203
+ normalizedFilter
3204
+ ]);
3205
+ const groupedListed = useMemo2(() => {
3206
+ const groups = /* @__PURE__ */ new Map();
3207
+ for (const model of listedModels) {
3208
+ const existing = groups.get(model.provider);
3209
+ if (existing) existing.push(model);
3210
+ else groups.set(model.provider, [model]);
3211
+ }
3212
+ return [...groups.entries()];
3213
+ }, [listedModels]);
3214
+ const emptyMessage = (() => {
3215
+ if (modelOptions.length === 0) return "No models discovered yet. Configure a provider, then add models.";
3216
+ if (listedModels.length === 0 && !normalizedFilter) return "Add models from your configured providers to make them available in chat.";
3217
+ if (listedModels.length === 0) return "No models match your search.";
3218
+ return null;
3219
+ })();
3220
+ const openAddDialog = () => {
3221
+ setAddOpen(true);
3222
+ };
3223
+ return /* @__PURE__ */ jsxs(SettingsPane, {
3224
+ title: "LLM Models",
3225
+ description: /* @__PURE__ */ jsxs(Fragment$1, { children: [
3226
+ "Models available in chat. Discover from configured providers, add the ones you want, and remove any you do not need \u2014 changes save automatically. ",
3227
+ /* @__PURE__ */ jsx("span", {
3228
+ className: "tabular-nums",
3229
+ children: listedModels.length
3230
+ }),
3231
+ " in list",
3232
+ modelOptions.length > 0 ? /* @__PURE__ */ jsxs(Fragment$1, { children: [
3233
+ " ",
3234
+ "\xB7 ",
3235
+ /* @__PURE__ */ jsx("span", {
3236
+ className: "tabular-nums",
3237
+ children: modelOptions.length
3238
+ }),
3239
+ " discovered"
3240
+ ] }) : null,
3241
+ "."
3242
+ ] }),
3243
+ actions: /* @__PURE__ */ jsx(SettingsCommitActions, {
3244
+ dirty: modelDirty,
3245
+ disabled: disabled2,
3246
+ onRevert,
3247
+ onSave,
3248
+ saving
3249
+ }),
3468
3250
  children: [
3469
3251
  /* @__PURE__ */ jsxs("div", {
3470
3252
  className: "flex items-center gap-2",
3471
- children: [/* @__PURE__ */ jsx(Button, {
3472
- type: "button",
3473
- variant: "ghost",
3474
- size: "icon-sm",
3475
- "aria-label": "Back to provider list",
3476
- onClick: onBack,
3477
- children: /* @__PURE__ */ jsx(ArrowLeft, {})
3478
- }), /* @__PURE__ */ jsxs("div", {
3479
- className: "min-w-0 flex-1",
3480
- children: [/* @__PURE__ */ jsxs("p", {
3481
- className: "text-sm font-medium",
3482
- children: ["Configure ", provider.name]
3483
- }), /* @__PURE__ */ jsx("p", {
3484
- className: "text-xs text-pretty text-muted-foreground",
3485
- children: isCustomTemplate ? "Name this custom provider, pick its API family, then enter its API key, https base URL, and model ids." : openAiChat ? "Name this OpenAI-compatible provider, then enter its API key, base URL, and model name." : `Stored as ${provider.envVarName} for this account.`
3253
+ children: [/* @__PURE__ */ jsxs(InputGroup, {
3254
+ className: "flex-1",
3255
+ children: [/* @__PURE__ */ jsx(InputGroupAddon, {
3256
+ align: "inline-start",
3257
+ children: /* @__PURE__ */ jsx(Search, {})
3258
+ }), /* @__PURE__ */ jsx(InputGroupInput, {
3259
+ "aria-label": "Search listed models",
3260
+ value: modelFilter,
3261
+ onChange: (event) => onModelFilterChange(event.target.value),
3262
+ placeholder: "Search listed models\u2026",
3263
+ disabled: disabled2 || listedModels.length === 0
3486
3264
  })]
3487
- })]
3488
- }),
3489
- /* @__PURE__ */ jsx(ProviderCredentialFields, {
3490
- attemptedSave,
3491
- api,
3492
- apiKey,
3493
- baseUrl,
3494
- modelId,
3495
- models,
3496
- displayName,
3497
- isCustom: isCustomTemplate,
3498
- openAiChat,
3499
- placeholder: meta.placeholder,
3500
- showPassword,
3501
- onApiKeyChange,
3502
- onApiChange,
3503
- onBaseUrlChange,
3504
- onModelIdChange,
3505
- onModelsChange,
3506
- onDisplayNameChange,
3507
- onTogglePassword
3508
- }),
3509
- /* @__PURE__ */ jsxs(Alert, {
3510
- className: "px-3 py-2",
3511
- children: [/* @__PURE__ */ jsx(Info, {}), /* @__PURE__ */ jsx(AlertDescription, {
3512
- className: "text-xs text-pretty",
3513
- children: meta.help
3514
- })]
3515
- }),
3516
- /* @__PURE__ */ jsxs("div", {
3517
- className: "flex items-center justify-end gap-1.5",
3518
- children: [/* @__PURE__ */ jsx(Button, {
3519
- type: "button",
3520
- variant: "ghost",
3521
- size: "sm",
3522
- onClick: onCancel,
3523
- children: "Cancel"
3524
3265
  }), /* @__PURE__ */ jsxs(Button, {
3525
3266
  type: "button",
3267
+ variant: "outline",
3526
3268
  size: "sm",
3527
- disabled: isPending || !canSave,
3528
- onClick: onSave,
3529
- children: [isPending ? /* @__PURE__ */ jsx(Spinner, { "data-icon": "inline-start" }) : null, "Save"]
3269
+ disabled: disabled2,
3270
+ onClick: openAddDialog,
3271
+ children: [/* @__PURE__ */ jsx(Plus, { "data-icon": "inline-start" }), "Add models"]
3530
3272
  })]
3531
3273
  }),
3532
- oauthAvailable ? /* @__PURE__ */ jsx(ProviderOAuthSignIn, {
3533
- provider,
3534
- onConfigured: onConfigured ?? onCancel,
3535
- onOAuthLogin
3536
- }, `${provider.id}-oauth`) : null
3274
+ /* @__PURE__ */ jsx("div", {
3275
+ className: "flex flex-col gap-3",
3276
+ "data-testid": "runtime-models-list",
3277
+ children: emptyMessage ? /* @__PURE__ */ jsxs("div", {
3278
+ className: "flex flex-col items-center gap-3 py-8",
3279
+ children: [/* @__PURE__ */ jsx("p", {
3280
+ className: "px-1 text-center text-xs text-pretty text-muted-foreground",
3281
+ children: emptyMessage
3282
+ }), !normalizedFilter ? /* @__PURE__ */ jsxs(Button, {
3283
+ type: "button",
3284
+ variant: "outline",
3285
+ size: "sm",
3286
+ disabled: disabled2,
3287
+ onClick: openAddDialog,
3288
+ children: [/* @__PURE__ */ jsx(Plus, { "data-icon": "inline-start" }), "Add models"]
3289
+ }) : null]
3290
+ }) : groupedListed.map(([provider, models]) => /* @__PURE__ */ jsxs("div", {
3291
+ className: "flex flex-col gap-1.5",
3292
+ children: [/* @__PURE__ */ jsx("div", {
3293
+ className: "px-1 text-xs font-medium text-muted-foreground",
3294
+ children: providerLabel(provider)
3295
+ }), models.map((model) => /* @__PURE__ */ jsx(ItemRow, {
3296
+ icon: /* @__PURE__ */ jsx(ProviderBrandIcon, { provider: model.provider }),
3297
+ title: model.name,
3298
+ subtitle: `${providerLabel(model.provider)} \xB7 ${model.modelId}`,
3299
+ trailing: /* @__PURE__ */ jsxs("div", {
3300
+ className: "flex items-center",
3301
+ children: [isDefaultModel(model) ? /* @__PURE__ */ jsx("span", {
3302
+ className: "px-2 text-xs font-medium text-muted-foreground",
3303
+ children: "Default"
3304
+ }) : onSetDefaultModel ? /* @__PURE__ */ jsxs(Button, {
3305
+ type: "button",
3306
+ variant: "ghost",
3307
+ size: "sm",
3308
+ className: cn(HIT_AREA_EXPAND_DENSE_CLASS, "h-7 px-2 text-xs text-muted-foreground transition-[background-color,color,transform] duration-150 active:scale-[0.96]"),
3309
+ disabled: disabled2,
3310
+ "aria-label": `Set ${model.name} as default`,
3311
+ onClick: () => onSetDefaultModel(model),
3312
+ children: [/* @__PURE__ */ jsx(Star, { "data-icon": "inline-start" }), "Set default"]
3313
+ }) : null, /* @__PURE__ */ jsxs(Button, {
3314
+ type: "button",
3315
+ variant: "ghost",
3316
+ size: "sm",
3317
+ className: cn(HIT_AREA_EXPAND_DENSE_CLASS, "h-7 px-2 text-xs text-muted-foreground transition-[background-color,color,transform] duration-150 hover:text-destructive active:scale-[0.96]"),
3318
+ disabled: disabled2,
3319
+ "aria-label": `Remove ${model.name}`,
3320
+ onClick: () => onRemoveModel(model),
3321
+ children: [/* @__PURE__ */ jsx(Trash2, { "data-icon": "inline-start" }), "Remove"]
3322
+ })]
3323
+ })
3324
+ }, model.id))]
3325
+ }, provider))
3326
+ }),
3327
+ /* @__PURE__ */ jsx(AddModelsDialog, {
3328
+ open: addOpen,
3329
+ configuredProviderIds,
3330
+ discoverableProviderIds,
3331
+ discoveringProviderId,
3332
+ enabledModels: draft?.enabledModels,
3333
+ modelOptions,
3334
+ onAddModels,
3335
+ onDiscoverProvider,
3336
+ onOpenChange: setAddOpen,
3337
+ providerLabel
3338
+ })
3537
3339
  ]
3538
3340
  });
3539
3341
  }
3540
- function RemoveProviderConfirmDialog({ confirmRemoveProvider, isPending, onClose, onConfirm }) {
3541
- return /* @__PURE__ */ jsx(AlertDialog, {
3542
- open: confirmRemoveProvider !== null,
3543
- onOpenChange: (open) => {
3544
- if (!open) onClose();
3545
- },
3546
- children: /* @__PURE__ */ jsxs(AlertDialogContent, { children: [/* @__PURE__ */ jsxs("div", {
3547
- className: "flex flex-col gap-2",
3548
- children: [/* @__PURE__ */ jsxs(AlertDialogTitle, { children: [
3549
- "Remove ",
3550
- confirmRemoveProvider?.name ?? "provider",
3551
- "?"
3552
- ] }), /* @__PURE__ */ jsx(AlertDialogDescription, { children: "This clears stored credentials for this provider from your workspace settings. Active sessions will stop using it immediately." })]
3553
- }), /* @__PURE__ */ jsxs(AlertDialogFooter, { children: [/* @__PURE__ */ jsx(AlertDialogCancel, {
3554
- disabled: isPending,
3555
- children: "Cancel"
3556
- }), /* @__PURE__ */ jsxs(AlertDialogAction, {
3557
- disabled: isPending || !confirmRemoveProvider,
3558
- onClick: () => {
3559
- if (confirmRemoveProvider) onConfirm(confirmRemoveProvider);
3560
- },
3561
- children: [isPending ? /* @__PURE__ */ jsx(Spinner, { "data-icon": "inline-start" }) : null, "Remove provider"]
3562
- })] })] })
3563
- });
3564
- }
3565
- function ProviderPickerRow({ configured, onSelect, provider }) {
3566
- const openAiChat = isOccProviderId(provider.id);
3567
- const isCustomTemplate = provider.id === "custom" || isCustomProviderId(provider.id);
3568
- const oauthOnly = isOAuthProvider(provider);
3569
- const oauthAvailable = supportsOAuth(provider);
3570
- return /* @__PURE__ */ jsxs("button", {
3571
- type: "button",
3572
- className: cn("flex w-full items-center gap-3 rounded-lg px-2 py-2 text-left transition-[background-color,transform] duration-150", "hover:bg-muted/60 focus-visible:ring-2 focus-visible:ring-ring focus-visible:outline-none active:scale-[0.96]"),
3573
- onClick: onSelect,
3574
- children: [/* @__PURE__ */ jsx("div", {
3575
- className: "flex size-8 shrink-0 items-center justify-center rounded-[4px] border border-border/40 bg-background/60",
3576
- children: /* @__PURE__ */ jsx(ProviderBrandIcon, {
3577
- provider: isCustomTemplate ? "custom" : provider.id,
3578
- api: provider.api,
3579
- className: "text-foreground/70"
3342
+ var THEME_OPTIONS = [
3343
+ {
3344
+ value: "system",
3345
+ label: "System",
3346
+ icon: Monitor
3347
+ },
3348
+ {
3349
+ value: "light",
3350
+ label: "Light",
3351
+ icon: Sun
3352
+ },
3353
+ {
3354
+ value: "dark",
3355
+ label: "Dark",
3356
+ icon: Moon
3357
+ }
3358
+ ];
3359
+ function PersonalizationSection({ onThemePreferenceChange, themePreference }) {
3360
+ return /* @__PURE__ */ jsxs(Field, {
3361
+ orientation: "horizontal",
3362
+ className: "items-center justify-between gap-6 rounded-xl border p-4",
3363
+ children: [/* @__PURE__ */ jsxs("div", {
3364
+ className: "min-w-0",
3365
+ children: [/* @__PURE__ */ jsx(FieldLabel, { children: "Theme" }), /* @__PURE__ */ jsx(FieldDescription, { children: "Match the system theme or pick light or dark." })]
3366
+ }), /* @__PURE__ */ jsx("div", {
3367
+ className: "shrink-0",
3368
+ children: /* @__PURE__ */ jsx(Select, {
3369
+ "aria-label": "Theme",
3370
+ className: "w-[160px]",
3371
+ onValueChange: (value) => onThemePreferenceChange(value),
3372
+ options: [...THEME_OPTIONS],
3373
+ value: themePreference
3580
3374
  })
3581
- }), /* @__PURE__ */ jsxs("div", {
3582
- className: "min-w-0 flex-1",
3583
- children: [/* @__PURE__ */ jsx("div", {
3584
- className: "truncate text-sm font-medium",
3585
- children: provider.name
3586
- }), /* @__PURE__ */ jsxs("div", {
3587
- className: "truncate text-xs text-muted-foreground",
3588
- children: [oauthOnly ? "OAuth sign-in" : oauthAvailable ? "API key or OAuth" : isCustomTemplate ? "API family + key + https base URL + models" : openAiChat ? "API key + base URL + model name" : provider.envVarName, configured ? " \xB7 Update" : ""]
3589
- })]
3590
3375
  })]
3591
3376
  });
3592
3377
  }
3593
- function ActiveProviderList({ providers, editingProvider, form, operation, actions, onEdit, onCancelEdit, onSave, onRemove, onOAuthLogin, onConfigured }) {
3594
- const { api, apiKey, baseUrl, modelId, models, displayName, showPassword, attemptedSave, canSave } = form;
3595
- const { isPending, canRemove } = operation;
3596
- const { onApiKeyChange, onApiChange, onBaseUrlChange, onModelIdChange, onModelsChange, onDisplayNameChange, onTogglePassword } = actions;
3597
- return /* @__PURE__ */ jsx("div", {
3598
- className: "flex flex-col gap-1.5",
3599
- children: providers.map((provider) => {
3600
- const isEditing = editingProvider === provider.id;
3601
- const isCustom = isCustomProviderId(provider.id);
3602
- const meta = PROVIDER_METADATA[provider.id] ?? PROVIDER_METADATA[isCustom ? "custom" : "openai-chat-completions"];
3603
- const openAiChat = isOccProviderId(provider.id);
3604
- const oauthOnly = isOAuthProvider(provider);
3605
- const oauthAvailable = supportsOAuth(provider);
3606
- return /* @__PURE__ */ jsxs("div", {
3607
- className: "flex flex-col",
3608
- children: [/* @__PURE__ */ jsx(ItemRow, {
3609
- interactive: false,
3610
- icon: /* @__PURE__ */ jsx(ProviderBrandIcon, {
3611
- provider: provider.id,
3612
- api: provider.api,
3613
- className: "text-foreground/80"
3614
- }),
3615
- title: provider.name,
3616
- subtitle: oauthOnly ? "OAuth sign-in" : oauthAvailable ? "API key or OAuth" : isCustom ? "Custom provider \xB7 API key + base URL + models" : openAiChat ? "OpenAI-compatible \xB7 API key + base URL + model name" : provider.envVarName,
3617
- trailing: /* @__PURE__ */ jsxs("div", {
3618
- className: "flex items-center gap-0.5",
3619
- children: [/* @__PURE__ */ jsxs(Button, {
3620
- type: "button",
3621
- variant: "ghost",
3622
- size: "sm",
3623
- className: cn(HIT_AREA_EXPAND_DENSE_CLASS, "h-7 px-2 text-xs text-muted-foreground transition-[background-color,color,transform] duration-150 hover:text-destructive active:scale-[0.96]"),
3624
- disabled: isPending || !canRemove,
3625
- "aria-label": `Remove ${provider.name}`,
3626
- onClick: () => onRemove(provider),
3627
- children: [/* @__PURE__ */ jsx(Trash2, { "data-icon": "inline-start" }), "Remove"]
3628
- }), /* @__PURE__ */ jsxs(Button, {
3629
- type: "button",
3630
- variant: "ghost",
3631
- size: "sm",
3632
- className: cn(HIT_AREA_EXPAND_DENSE_CLASS, "h-7 px-2 text-xs transition-[background-color,transform] duration-150 active:scale-[0.96]"),
3633
- disabled: isPending,
3634
- onClick: () => {
3635
- if (isEditing) {
3636
- onCancelEdit();
3637
- return;
3638
- }
3639
- onEdit(provider.id);
3640
- },
3641
- children: [!isEditing ? /* @__PURE__ */ jsx(Pencil, { "data-icon": "inline-start" }) : null, isEditing ? "Cancel" : "Update"]
3642
- })]
3643
- })
3644
- }), isEditing ? /* @__PURE__ */ jsx(RowSurface, {
3645
- tone: "inset",
3646
- padding: "md",
3647
- className: "flex flex-col gap-2 border-t border-border/30",
3648
- children: oauthOnly ? /* @__PURE__ */ jsx(ProviderOAuthSignIn, {
3649
- provider,
3650
- onConfigured,
3651
- onOAuthLogin
3652
- }, provider.id) : /* @__PURE__ */ jsxs(Fragment$1, { children: [
3653
- /* @__PURE__ */ jsx(ProviderCredentialFields, {
3654
- attemptedSave,
3655
- api: provider.api ?? api,
3656
- apiKey,
3657
- baseUrl,
3658
- modelId,
3659
- models,
3660
- displayName,
3661
- isCustom,
3662
- openAiChat,
3663
- placeholder: meta.placeholder,
3664
- showPassword,
3665
- onApiKeyChange,
3666
- onApiChange,
3667
- onBaseUrlChange,
3668
- onModelIdChange,
3669
- onModelsChange,
3670
- onDisplayNameChange: isCustom || provider.providerFamily !== void 0 ? onDisplayNameChange : void 0,
3671
- onTogglePassword
3672
- }),
3673
- /* @__PURE__ */ jsxs(Alert, {
3674
- className: "px-3 py-2",
3675
- children: [/* @__PURE__ */ jsx(Info, {}), /* @__PURE__ */ jsx(AlertDescription, {
3676
- className: "text-xs text-pretty",
3677
- children: meta.help
3678
- })]
3679
- }),
3680
- /* @__PURE__ */ jsx("div", {
3681
- className: "flex items-center justify-end",
3682
- children: /* @__PURE__ */ jsxs(Button, {
3683
- type: "button",
3684
- size: "sm",
3685
- disabled: isPending || !canSave,
3686
- onClick: () => onSave(provider.id),
3687
- children: [isPending ? /* @__PURE__ */ jsx(Spinner, { "data-icon": "inline-start" }) : null, "Save"]
3688
- })
3689
- }),
3690
- oauthAvailable ? /* @__PURE__ */ jsx(ProviderOAuthSignIn, {
3691
- provider,
3692
- onConfigured,
3693
- onOAuthLogin
3694
- }, `${provider.id}-oauth`) : null
3695
- ] })
3696
- }) : null]
3697
- }, provider.id);
3698
- })
3378
+ function isFieldInvalid(value, attemptedSave, required = true) {
3379
+ return attemptedSave && required && value.trim().length === 0;
3380
+ }
3381
+ function SecretCredentialField({ attemptedSave, label, onChange, onToggleVisibility, placeholder, required = true, showPassword, value }) {
3382
+ const controlId = useId();
3383
+ const invalid = isFieldInvalid(value, attemptedSave, required);
3384
+ return /* @__PURE__ */ jsxs(Field, {
3385
+ "data-invalid": invalid || void 0,
3386
+ children: [/* @__PURE__ */ jsx(FieldLabel, {
3387
+ htmlFor: controlId,
3388
+ children: label
3389
+ }), /* @__PURE__ */ jsxs(InputGroup, { children: [
3390
+ /* @__PURE__ */ jsx(InputGroupAddon, {
3391
+ align: "inline-start",
3392
+ children: /* @__PURE__ */ jsx(Lock, {})
3393
+ }),
3394
+ /* @__PURE__ */ jsx(InputGroupInput, {
3395
+ id: controlId,
3396
+ type: showPassword ? "text" : "password",
3397
+ placeholder,
3398
+ value,
3399
+ onChange: (event) => onChange(event.target.value),
3400
+ "aria-label": label,
3401
+ "aria-invalid": invalid || void 0
3402
+ }),
3403
+ /* @__PURE__ */ jsx(InputGroupAddon, {
3404
+ align: "inline-end",
3405
+ children: /* @__PURE__ */ jsx(InputGroupButton, {
3406
+ size: "icon-xs",
3407
+ className: cn(HIT_AREA_EXPAND_CLASS, "transition-[background-color,transform] duration-150 active:scale-[0.96]"),
3408
+ "aria-label": showPassword ? "Hide value" : "Show value",
3409
+ onClick: onToggleVisibility,
3410
+ children: showPassword ? /* @__PURE__ */ jsx(EyeOff, {}) : /* @__PURE__ */ jsx(Eye, {})
3411
+ })
3412
+ })
3413
+ ] })]
3699
3414
  });
3700
3415
  }
3701
- function ProviderCredentialsSection({ isLoading, isPending, onOAuthLogin, onRemoveProvider, onUpdateProvider, providers }) {
3702
- const { activeProviders, addPickerOpen, addPickerQuery, api, apiKey, attemptedSave, baseUrl, canSave, closeAddPicker, closeEditor, confirmRemoveProvider, credentialProviders, displayName, editingProvider, editingUnconfiguredProvider, filteredActiveProviders, filteredPickerAvailable, filteredPickerConfigured, handleRemove, handleSave, modelId, models, openAddPicker, openEditor, pickerHasResults, searchQuery, selectProviderFromPicker, setAddPickerQuery, setApi, setApiKey, setBaseUrl, setConfirmRemoveProvider, setDisplayName, setModelId, setModels, setSearchQuery, setShowPassword, showPassword } = useProviderCredentialsController({
3703
- onRemoveProvider,
3704
- onUpdateProvider,
3705
- providers
3706
- });
3707
- const form = {
3708
- api,
3709
- apiKey,
3710
- baseUrl,
3711
- modelId,
3712
- models,
3713
- displayName,
3714
- showPassword,
3715
- attemptedSave,
3716
- canSave
3717
- };
3718
- const operation = {
3719
- isPending,
3720
- canRemove: Boolean(onRemoveProvider)
3721
- };
3722
- const actions = {
3723
- onApiKeyChange: setApiKey,
3724
- onApiChange: setApi,
3725
- onBaseUrlChange: setBaseUrl,
3726
- onModelIdChange: setModelId,
3727
- onModelsChange: setModels,
3728
- onDisplayNameChange: setDisplayName,
3729
- onTogglePassword: () => setShowPassword((current) => !current)
3730
- };
3731
- return /* @__PURE__ */ jsxs(SettingsPane, {
3732
- title: "Providers",
3733
- description: "Locally, API keys are stored in `.env.local`. When deployed, they are stored encrypted in your account.",
3734
- children: [addPickerOpen ? /* @__PURE__ */ jsx(AddProviderPickerPanel, {
3735
- addPickerQuery,
3736
- credentialProvidersLength: credentialProviders.length,
3737
- filteredPickerAvailable,
3738
- filteredPickerConfigured,
3739
- pickerHasResults,
3740
- onClose: closeAddPicker,
3741
- onQueryChange: setAddPickerQuery,
3742
- onSelect: selectProviderFromPicker
3743
- }) : editingUnconfiguredProvider && isOAuthProvider(editingUnconfiguredProvider) ? /* @__PURE__ */ jsx(AddProviderOAuthPanel, {
3744
- provider: editingUnconfiguredProvider,
3745
- onBack: openAddPicker,
3746
- onConfigured: closeEditor,
3747
- onOAuthLogin
3748
- }, editingUnconfiguredProvider.id) : editingUnconfiguredProvider ? /* @__PURE__ */ jsx(AddProviderEditorPanel, {
3749
- provider: editingUnconfiguredProvider,
3750
- api,
3751
- apiKey,
3752
- baseUrl,
3753
- modelId,
3754
- models,
3755
- displayName,
3756
- showPassword,
3757
- attemptedSave,
3758
- isPending,
3759
- canSave,
3760
- onApiKeyChange: setApiKey,
3761
- onApiChange: setApi,
3762
- onBaseUrlChange: setBaseUrl,
3763
- onModelIdChange: setModelId,
3764
- onModelsChange: setModels,
3765
- onDisplayNameChange: setDisplayName,
3766
- onTogglePassword: () => setShowPassword((current) => !current),
3767
- onBack: openAddPicker,
3768
- onCancel: closeEditor,
3769
- onConfigured: closeEditor,
3770
- onOAuthLogin,
3771
- onSave: () => {
3772
- if (editingProvider) handleSave(editingProvider);
3773
- }
3774
- }) : /* @__PURE__ */ jsxs("div", {
3775
- className: "flex flex-col gap-3",
3776
- children: [/* @__PURE__ */ jsxs("div", {
3777
- className: "flex items-center gap-2",
3778
- children: [/* @__PURE__ */ jsxs(InputGroup, {
3779
- className: "flex-1",
3780
- children: [/* @__PURE__ */ jsx(InputGroupAddon, {
3781
- align: "inline-start",
3782
- children: /* @__PURE__ */ jsx(Search, {})
3783
- }), /* @__PURE__ */ jsx(InputGroupInput, {
3784
- type: "text",
3785
- placeholder: "Search credentials\u2026",
3786
- value: searchQuery,
3787
- onChange: (event) => setSearchQuery(event.target.value),
3788
- "aria-label": "Search credentials",
3789
- disabled: isLoading || activeProviders.length === 0
3790
- })]
3791
- }), /* @__PURE__ */ jsxs(Button, {
3792
- type: "button",
3793
- variant: "outline",
3794
- size: "sm",
3795
- disabled: isLoading || credentialProviders.length === 0,
3796
- onClick: openAddPicker,
3797
- children: [/* @__PURE__ */ jsx(Plus, { "data-icon": "inline-start" }), "Add provider"]
3798
- })]
3799
- }), isLoading ? /* @__PURE__ */ jsxs("div", {
3800
- className: "flex items-center justify-center gap-2 py-6 text-xs text-muted-foreground",
3801
- children: [/* @__PURE__ */ jsx(Spinner, { className: "size-3" }), /* @__PURE__ */ jsx("span", { children: "Loading\u2026" })]
3802
- }) : activeProviders.length === 0 ? /* @__PURE__ */ jsxs("div", {
3803
- className: "flex flex-col items-center gap-3 py-8",
3804
- children: [/* @__PURE__ */ jsx("p", {
3805
- className: "text-center text-xs text-pretty text-muted-foreground",
3806
- children: "No providers configured"
3807
- }), /* @__PURE__ */ jsxs(Button, {
3808
- type: "button",
3809
- variant: "outline",
3810
- size: "sm",
3811
- disabled: credentialProviders.length === 0,
3812
- onClick: openAddPicker,
3813
- children: [/* @__PURE__ */ jsx(Plus, { "data-icon": "inline-start" }), "Add provider"]
3814
- })]
3815
- }) : filteredActiveProviders.length === 0 ? /* @__PURE__ */ jsx("p", {
3816
- className: "py-6 text-center text-xs text-pretty text-muted-foreground",
3817
- children: "No matching providers found."
3818
- }) : /* @__PURE__ */ jsx(ActiveProviderList, {
3819
- providers: filteredActiveProviders,
3820
- editingProvider,
3821
- form,
3822
- operation,
3823
- actions,
3824
- onEdit: openEditor,
3825
- onCancelEdit: closeEditor,
3826
- onSave: (providerId) => void handleSave(providerId),
3827
- onRemove: setConfirmRemoveProvider,
3828
- onOAuthLogin,
3829
- onConfigured: closeEditor
3830
- })]
3831
- }), /* @__PURE__ */ jsx(RemoveProviderConfirmDialog, {
3832
- confirmRemoveProvider,
3833
- isPending,
3834
- onClose: () => setConfirmRemoveProvider(null),
3835
- onConfirm: (provider) => {
3836
- handleRemove(provider);
3837
- }
3838
- })]
3416
+ function TextCredentialField({ attemptedSave, icon: Icon, inputType = "text", label, onChange, placeholder, required = true, value }) {
3417
+ const controlId = useId();
3418
+ const invalid = isFieldInvalid(value, attemptedSave, required);
3419
+ return /* @__PURE__ */ jsxs(Field, {
3420
+ "data-invalid": invalid || void 0,
3421
+ children: [/* @__PURE__ */ jsx(FieldLabel, {
3422
+ htmlFor: controlId,
3423
+ children: label
3424
+ }), /* @__PURE__ */ jsxs(InputGroup, { children: [/* @__PURE__ */ jsx(InputGroupAddon, {
3425
+ align: "inline-start",
3426
+ children: /* @__PURE__ */ jsx(Icon, {})
3427
+ }), /* @__PURE__ */ jsx(InputGroupInput, {
3428
+ id: controlId,
3429
+ type: inputType,
3430
+ placeholder,
3431
+ value,
3432
+ onChange: (event) => onChange(event.target.value),
3433
+ "aria-label": label,
3434
+ "aria-invalid": invalid || void 0
3435
+ })] })]
3839
3436
  });
3840
3437
  }
3841
- function customModelKey(provider, model) {
3842
- return `${provider}/${model}`;
3438
+ var OPENAI_CHAT_BASE_URL_PLACEHOLDER = "https://opencode.ai/zen/v1";
3439
+ var OPENAI_CHAT_MODEL_PLACEHOLDER = "qwen35-122b-a10b";
3440
+ var OPENAI_CHAT_NAME_PLACEHOLDER = "e.g. OpenCode Zen, Nebius, Groq\u2026";
3441
+ var CUSTOM_MODELS_PLACEHOLDER = "model-a, model-b (comma-separated)";
3442
+ var CUSTOM_PROVIDER_API_OPTIONS = [
3443
+ {
3444
+ value: "openai-completions",
3445
+ label: "OpenAI Chat Completions"
3446
+ },
3447
+ {
3448
+ value: "openai-responses",
3449
+ label: "OpenAI Responses"
3450
+ },
3451
+ {
3452
+ value: "anthropic-messages",
3453
+ label: "Anthropic Messages"
3454
+ },
3455
+ {
3456
+ value: "google-genai",
3457
+ label: "Google Generative AI"
3458
+ }
3459
+ ];
3460
+ function ProviderCredentialFields({ attemptedSave, apiKey, api, baseUrl, modelId, models, displayName, isCustom, onApiKeyChange, onApiChange, onBaseUrlChange, onModelIdChange, onModelsChange, onDisplayNameChange, onTogglePassword, openAiChat, placeholder, showPassword }) {
3461
+ const showApiSelector = isCustom && onApiChange;
3462
+ const showMultiModels = isCustom && onModelsChange;
3463
+ return /* @__PURE__ */ jsxs(FieldGroup, {
3464
+ className: "gap-2",
3465
+ children: [
3466
+ (openAiChat || isCustom) && onDisplayNameChange ? /* @__PURE__ */ jsx(TextCredentialField, {
3467
+ attemptedSave,
3468
+ icon: Tag,
3469
+ label: "Provider name",
3470
+ placeholder: OPENAI_CHAT_NAME_PLACEHOLDER,
3471
+ value: displayName ?? "",
3472
+ onChange: onDisplayNameChange
3473
+ }) : null,
3474
+ showApiSelector ? /* @__PURE__ */ jsx(ApiSelector, {
3475
+ value: api ?? "openai-completions",
3476
+ onChange: onApiChange
3477
+ }) : null,
3478
+ /* @__PURE__ */ jsx(SecretCredentialField, {
3479
+ attemptedSave,
3480
+ label: "API key",
3481
+ placeholder,
3482
+ value: apiKey,
3483
+ showPassword,
3484
+ onChange: onApiKeyChange,
3485
+ onToggleVisibility: onTogglePassword
3486
+ }),
3487
+ openAiChat || isCustom ? /* @__PURE__ */ jsx(TextCredentialField, {
3488
+ attemptedSave,
3489
+ icon: Globe,
3490
+ inputType: "url",
3491
+ label: "Base URL",
3492
+ placeholder: OPENAI_CHAT_BASE_URL_PLACEHOLDER,
3493
+ value: baseUrl,
3494
+ onChange: onBaseUrlChange
3495
+ }) : null,
3496
+ showMultiModels ? /* @__PURE__ */ jsx(TextCredentialField, {
3497
+ attemptedSave,
3498
+ icon: Layers,
3499
+ label: "Model ids",
3500
+ placeholder: CUSTOM_MODELS_PLACEHOLDER,
3501
+ value: models ?? "",
3502
+ onChange: onModelsChange
3503
+ }) : null,
3504
+ openAiChat && !showMultiModels ? /* @__PURE__ */ jsx(TextCredentialField, {
3505
+ attemptedSave,
3506
+ icon: Box,
3507
+ label: "Model name",
3508
+ placeholder: OPENAI_CHAT_MODEL_PLACEHOLDER,
3509
+ value: modelId,
3510
+ onChange: onModelIdChange
3511
+ }) : null
3512
+ ]
3513
+ });
3843
3514
  }
3844
- function isModelEnabled(model, patterns) {
3845
- return isModelPatternEnabled({
3846
- id: model.id,
3847
- name: model.name,
3848
- provider: model.provider,
3849
- modelId: model.modelId
3850
- }, patterns);
3515
+ function ApiSelector({ value, onChange }) {
3516
+ return /* @__PURE__ */ jsxs("div", {
3517
+ className: "flex flex-col gap-1",
3518
+ children: [/* @__PURE__ */ jsx("span", {
3519
+ className: "text-xs font-medium text-muted-foreground",
3520
+ children: "API family"
3521
+ }), /* @__PURE__ */ jsx(Select, {
3522
+ "aria-label": "API family",
3523
+ value,
3524
+ options: CUSTOM_PROVIDER_API_OPTIONS,
3525
+ placeholder: "Select API family",
3526
+ onValueChange: (next) => onChange(next)
3527
+ })]
3528
+ });
3851
3529
  }
3852
- function nextEnabledModelPatterns({ currentPatterns, enabled, model, models }) {
3853
- if (currentPatterns === void 0 && enabled) return void 0;
3854
- const current = currentPatterns ?? [];
3855
- const knownModelPatterns = new Set(models.map((item) => modelPatternFor(item)));
3856
- const activeKnown = /* @__PURE__ */ new Set();
3857
- for (const item of models) if (isModelEnabled(item, currentPatterns)) activeKnown.add(modelPatternFor(item));
3858
- if (enabled) activeKnown.add(modelPatternFor(model));
3859
- else activeKnown.delete(modelPatternFor(model));
3860
- return [...current.filter((pattern) => {
3861
- if (knownModelPatterns.has(pattern)) return false;
3862
- return enabled || !modelMatchesPattern(model, pattern);
3863
- }), ...activeKnown];
3530
+ function isOAuthProvider(provider) {
3531
+ return provider.authType === "oauth";
3864
3532
  }
3865
- function modelPatternFor(model) {
3866
- return modelPattern(model.provider, model.modelId);
3533
+ function supportsOAuth(provider) {
3534
+ return provider.supportsOAuth === true;
3867
3535
  }
3868
- function searchableModelText(model) {
3869
- return [
3870
- model.name,
3871
- model.modelId,
3872
- model.provider,
3873
- model.id
3874
- ].join(" ").toLowerCase();
3536
+ var CUSTOM_PROVIDER_PICKER_ROW = {
3537
+ id: "custom",
3538
+ name: "Custom provider",
3539
+ isConfigured: false,
3540
+ envVarName: "CUSTOM_PROVIDER_API_KEY",
3541
+ displayName: "Custom provider"
3542
+ };
3543
+ function parseCustomProviderModels(raw) {
3544
+ return raw.split(",").map((entry) => entry.trim()).filter((entry) => entry.length > 0);
3875
3545
  }
3876
- function AddModelsDialog({ configuredProviderIds, discoverableProviderIds, discoveringProviderId, enabledModels, modelOptions, onAddModels, onDiscoverProvider, onOpenChange, open, providerLabel = formatProviderLabel }) {
3877
- const [filter, setFilter] = useState2("");
3878
- const [selectedKeys, setSelectedKeys] = useState2(/* @__PURE__ */ new Set());
3879
- const normalizedFilter = filter.trim().toLowerCase();
3880
- const candidateModels = useMemo2(() => {
3881
- return modelOptions.filter((model) => {
3882
- if (!configuredProviderIds.has(model.provider)) return false;
3883
- if (isModelEnabled(model, enabledModels)) return false;
3884
- return !normalizedFilter || searchableModelText(model).includes(normalizedFilter);
3885
- });
3886
- }, [
3887
- configuredProviderIds,
3888
- enabledModels,
3889
- modelOptions,
3890
- normalizedFilter
3891
- ]);
3892
- const groupedCandidates = useMemo2(() => {
3893
- const groups = /* @__PURE__ */ new Map();
3894
- for (const model of candidateModels) {
3895
- const existing = groups.get(model.provider);
3896
- if (existing) existing.push(model);
3897
- else groups.set(model.provider, [model]);
3898
- }
3899
- return [...groups.entries()];
3900
- }, [candidateModels]);
3901
- const discoverableProviders = useMemo2(() => {
3902
- const ids = discoverableProviderIds ?? configuredProviderIds;
3903
- return Array.from(ids).filter((id) => configuredProviderIds.has(id)).sort((a, b) => providerLabel(a).localeCompare(providerLabel(b)));
3904
- }, [
3905
- configuredProviderIds,
3906
- discoverableProviderIds,
3907
- providerLabel
3908
- ]);
3909
- const activeCountByProvider = useMemo2(() => {
3910
- const counts = /* @__PURE__ */ new Map();
3911
- for (const model of modelOptions) if (isModelEnabled(model, enabledModels)) counts.set(model.provider, (counts.get(model.provider) ?? 0) + 1);
3912
- return counts;
3913
- }, [enabledModels, modelOptions]);
3914
- const toggleSelected = (model) => {
3915
- setSelectedKeys((current) => {
3916
- const next = new Set(current);
3917
- if (next.has(model.id)) next.delete(model.id);
3918
- else next.add(model.id);
3919
- return next;
3920
- });
3546
+ function useProviderCredentialsController({ onRemoveProvider, onUpdateProvider, providers }) {
3547
+ const [editingProvider, setEditingProvider] = useState2(null);
3548
+ const [editingIsNewOccInstance, setEditingIsNewOccInstance] = useState2(false);
3549
+ const [apiKey, setApiKey] = useState2("");
3550
+ const [baseUrl, setBaseUrl] = useState2("");
3551
+ const [modelId, setModelId] = useState2("");
3552
+ const [models, setModels] = useState2("");
3553
+ const [api, setApi] = useState2("openai-completions");
3554
+ const [displayName, setDisplayName] = useState2("");
3555
+ const [showPassword, setShowPassword] = useState2(false);
3556
+ const [attemptedSave, setAttemptedSave] = useState2(false);
3557
+ const [searchQuery, setSearchQuery] = useState2("");
3558
+ const [addPickerOpen, setAddPickerOpen] = useState2(false);
3559
+ const [addPickerQuery, setAddPickerQuery] = useState2("");
3560
+ const [confirmRemoveProvider, setConfirmRemoveProvider] = useState2(null);
3561
+ const credentialProviders = useMemo2(() => {
3562
+ return [...providers.filter((provider) => provider.id !== CUSTOM_PROVIDER_PICKER_ROW.id), CUSTOM_PROVIDER_PICKER_ROW];
3563
+ }, [providers]);
3564
+ const activeProviders = useMemo2(() => credentialProviders.filter((provider) => provider.isConfigured), [credentialProviders]);
3565
+ const unconfiguredProviders = useMemo2(() => credentialProviders.filter((provider) => !provider.isConfigured || provider.id === "openai-chat-completions" && provider.providerFamily === void 0), [credentialProviders]);
3566
+ const filteredActiveProviders = useMemo2(() => {
3567
+ const query = searchQuery.trim().toLowerCase();
3568
+ if (!query) return activeProviders;
3569
+ return activeProviders.filter((provider) => providerMatchesQuery(provider, query));
3570
+ }, [activeProviders, searchQuery]);
3571
+ const filteredPickerAvailable = useMemo2(() => {
3572
+ const query = addPickerQuery.trim().toLowerCase();
3573
+ return unconfiguredProviders.filter((provider) => providerMatchesQuery(provider, query));
3574
+ }, [addPickerQuery, unconfiguredProviders]);
3575
+ const filteredPickerConfigured = useMemo2(() => {
3576
+ const query = addPickerQuery.trim().toLowerCase();
3577
+ return activeProviders.filter((provider) => providerMatchesQuery(provider, query));
3578
+ }, [activeProviders, addPickerQuery]);
3579
+ const pickerHasResults = filteredPickerAvailable.length > 0 || filteredPickerConfigured.length > 0;
3580
+ const editingUnconfiguredProvider = editingProvider && !activeProviders.some((provider) => provider.id === editingProvider) ? credentialProviders.find((entry) => entry.id === editingProvider) ?? null : null;
3581
+ const resetEditor = () => {
3582
+ setApiKey("");
3583
+ setBaseUrl("");
3584
+ setModelId("");
3585
+ setModels("");
3586
+ setApi("openai-completions");
3587
+ setDisplayName("");
3588
+ setShowPassword(false);
3589
+ setAttemptedSave(false);
3921
3590
  };
3922
- const confirmAdd = () => {
3923
- const selected = candidateModels.filter((model) => selectedKeys.has(model.id));
3924
- if (selected.length > 0) onAddModels(selected);
3925
- onOpenChange(false);
3591
+ const closeEditor = () => {
3592
+ setEditingProvider(null);
3593
+ setEditingIsNewOccInstance(false);
3594
+ resetEditor();
3926
3595
  };
3927
- return /* @__PURE__ */ jsx(Dialog, {
3928
- open,
3929
- onOpenChange,
3930
- children: /* @__PURE__ */ jsxs(DialogContent, {
3931
- className: "max-w-lg",
3932
- children: [
3933
- /* @__PURE__ */ jsxs(DialogHeader, { children: [/* @__PURE__ */ jsx(DialogTitle, { children: "Add models" }), /* @__PURE__ */ jsxs(DialogDescription, { children: [
3934
- "Pick models discovered from your configured providers. For OpenAI Chat Completions, refresh runs",
3935
- " ",
3936
- /* @__PURE__ */ jsxs("span", {
3937
- className: "font-medium",
3938
- children: ["GET ", "{baseUrl}/models"]
3939
- }),
3940
- "."
3941
- ] })] }),
3942
- /* @__PURE__ */ jsx("div", {
3943
- className: "flex flex-wrap gap-1.5",
3944
- children: discoverableProviders.map((providerId) => {
3945
- const discovering = discoveringProviderId === providerId;
3946
- const activeCount = activeCountByProvider.get(providerId) ?? 0;
3947
- return /* @__PURE__ */ jsxs(Button, {
3948
- type: "button",
3949
- variant: "outline",
3950
- size: "sm",
3951
- disabled: discoveringProviderId !== null,
3952
- onClick: () => {
3953
- onDiscoverProvider(providerId);
3954
- },
3955
- children: [
3956
- discovering ? /* @__PURE__ */ jsx(Spinner, { "data-icon": "inline-start" }) : /* @__PURE__ */ jsx(RefreshCw, { "data-icon": "inline-start" }),
3957
- providerLabel(providerId),
3958
- activeCount > 0 ? /* @__PURE__ */ jsxs("span", {
3959
- className: "tabular-nums text-muted-foreground",
3960
- children: [
3961
- "\xB7 ",
3962
- activeCount,
3963
- " active"
3964
- ]
3965
- }) : null
3966
- ]
3967
- }, providerId);
3968
- })
3969
- }),
3970
- /* @__PURE__ */ jsxs(InputGroup, { children: [/* @__PURE__ */ jsx(InputGroupAddon, {
3971
- align: "inline-start",
3972
- children: /* @__PURE__ */ jsx(Search, {})
3973
- }), /* @__PURE__ */ jsx(InputGroupInput, {
3974
- "aria-label": "Search discovered models",
3975
- value: filter,
3976
- onChange: (event) => setFilter(event.target.value),
3977
- placeholder: "Search discovered models\u2026"
3978
- })] }),
3979
- /* @__PURE__ */ jsx("div", {
3980
- className: "max-h-72 overflow-y-auto",
3981
- children: groupedCandidates.length === 0 ? /* @__PURE__ */ jsx("p", {
3982
- className: "py-6 text-center text-xs text-pretty text-muted-foreground",
3983
- children: candidateModels.length === 0 && !normalizedFilter ? "All discovered models are already in your list, or none are available yet." : "No models match your search."
3984
- }) : /* @__PURE__ */ jsx("div", {
3985
- className: "flex flex-col gap-3",
3986
- children: groupedCandidates.map(([provider, models]) => /* @__PURE__ */ jsxs("div", {
3987
- className: "flex flex-col gap-1",
3988
- children: [/* @__PURE__ */ jsxs("div", {
3989
- className: "px-2 text-[0.6875rem] font-medium tracking-wide text-muted-foreground uppercase",
3990
- children: [providerLabel(provider), (activeCountByProvider.get(provider) ?? 0) > 0 ? /* @__PURE__ */ jsxs("span", {
3991
- className: "ml-1 font-normal normal-case tabular-nums",
3992
- children: [
3993
- "\xB7 ",
3994
- activeCountByProvider.get(provider),
3995
- " active"
3996
- ]
3997
- }) : null]
3998
- }), models.map((model) => {
3999
- const checked = selectedKeys.has(model.id);
4000
- return /* @__PURE__ */ jsxs("button", {
4001
- type: "button",
4002
- className: cn("flex w-full items-center gap-3 rounded-lg px-2 py-2 text-left transition-[background-color,transform] duration-150", "hover:bg-muted/60 focus-visible:ring-2 focus-visible:ring-ring focus-visible:outline-none active:scale-[0.96]", checked && "bg-muted/70"),
4003
- "aria-pressed": checked,
4004
- onClick: () => toggleSelected(model),
4005
- children: [
4006
- /* @__PURE__ */ jsx("div", {
4007
- className: "flex size-8 shrink-0 items-center justify-center rounded-[4px] border border-border/40 bg-background/60",
4008
- children: /* @__PURE__ */ jsx(ProviderBrandIcon, {
4009
- provider: model.provider,
4010
- className: "text-foreground/70"
4011
- })
4012
- }),
4013
- /* @__PURE__ */ jsxs("div", {
4014
- className: "min-w-0 flex-1",
4015
- children: [/* @__PURE__ */ jsx("div", {
4016
- className: "truncate text-sm font-medium",
4017
- children: model.name
4018
- }), /* @__PURE__ */ jsx("div", {
4019
- className: "truncate text-xs text-muted-foreground",
4020
- children: model.modelId
4021
- })]
4022
- }),
4023
- /* @__PURE__ */ jsx("span", {
4024
- className: cn("size-4 rounded border border-border/60", checked && "border-foreground bg-foreground"),
4025
- "aria-hidden": true
4026
- })
4027
- ]
4028
- }, model.id);
4029
- })]
4030
- }, provider))
4031
- })
4032
- }),
4033
- /* @__PURE__ */ jsxs("div", {
4034
- className: "flex items-center justify-end gap-1.5",
4035
- children: [/* @__PURE__ */ jsx(Button, {
4036
- type: "button",
4037
- variant: "ghost",
4038
- size: "sm",
4039
- onClick: () => onOpenChange(false),
4040
- children: "Cancel"
4041
- }), /* @__PURE__ */ jsxs(Button, {
4042
- type: "button",
4043
- size: "sm",
4044
- disabled: selectedKeys.size === 0,
4045
- onClick: confirmAdd,
4046
- children: [
4047
- "Add",
4048
- " ",
4049
- selectedKeys.size > 0 ? /* @__PURE__ */ jsxs("span", {
4050
- className: "tabular-nums",
4051
- children: [
4052
- "(",
4053
- selectedKeys.size,
4054
- ")"
4055
- ]
4056
- }) : null
4057
- ]
4058
- })]
4059
- })
4060
- ]
4061
- })
4062
- });
4063
- }
4064
- function ModelDefaultsSection({ draft, discoveringProviderId, modelDirty, modelFilter, modelOptions, onAddModels, onDiscoverProvider, onModelFilterChange, onRemoveModel, onSetDefaultModel, onRevert, onSave, providers, saving, settingsLoading }) {
4065
- const [addOpen, setAddOpen] = useState2(false);
4066
- const disabled2 = !draft || settingsLoading;
4067
- const normalizedFilter = modelFilter.trim().toLowerCase();
4068
- const configuredProviderIds = useMemo2(() => {
4069
- const ids = /* @__PURE__ */ new Set();
4070
- for (const provider of providers) if (provider.isConfigured) ids.add(provider.id);
4071
- for (const model of modelOptions) if (isModelEnabled(model, draft?.enabledModels)) ids.add(model.provider);
4072
- return ids;
4073
- }, [
4074
- draft?.enabledModels,
4075
- modelOptions,
4076
- providers
4077
- ]);
4078
- const discoverableProviderIds = useMemo2(() => {
4079
- const ids = /* @__PURE__ */ new Set();
4080
- for (const provider of providers) if (provider.discoverable === true && configuredProviderIds.has(provider.id)) ids.add(provider.id);
4081
- return ids;
4082
- }, [configuredProviderIds, providers]);
4083
- const isDefaultModel = useMemo2(() => {
4084
- return (model) => !!draft?.defaultProvider && !!draft?.defaultModel && model.provider === draft.defaultProvider && model.modelId === draft.defaultModel;
4085
- }, [draft?.defaultModel, draft?.defaultProvider]);
4086
- const providerLabel = useMemo2(() => {
4087
- const labels = /* @__PURE__ */ new Map();
4088
- for (const provider of providers) if (provider.displayName || provider.name) labels.set(provider.id, provider.displayName ?? provider.name ?? provider.id);
4089
- return (providerId) => labels.get(providerId) ?? formatProviderLabel(providerId);
4090
- }, [providers]);
4091
- const listedModels = useMemo2(() => {
4092
- return modelOptions.filter((model) => {
4093
- if (!isModelEnabled(model, draft?.enabledModels)) return false;
4094
- if (!normalizedFilter) return true;
4095
- return [
4096
- model.name,
4097
- model.modelId,
4098
- model.provider,
4099
- model.id
4100
- ].join(" ").toLowerCase().includes(normalizedFilter);
4101
- });
4102
- }, [
4103
- draft?.enabledModels,
4104
- modelOptions,
4105
- normalizedFilter
4106
- ]);
4107
- const groupedListed = useMemo2(() => {
4108
- const groups = /* @__PURE__ */ new Map();
4109
- for (const model of listedModels) {
4110
- const existing = groups.get(model.provider);
4111
- if (existing) existing.push(model);
4112
- else groups.set(model.provider, [model]);
3596
+ const openEditor = (providerId, opts) => {
3597
+ setAddPickerOpen(false);
3598
+ setAddPickerQuery("");
3599
+ setEditingProvider(providerId);
3600
+ setEditingIsNewOccInstance(opts?.newOccInstance === true);
3601
+ resetEditor();
3602
+ const existing = providers.find((provider) => provider.id === providerId);
3603
+ setBaseUrl(existing?.baseUrl ?? "");
3604
+ setDisplayName(existing?.displayName ?? (isCustomProviderId(providerId) ? existing?.name ?? "" : ""));
3605
+ setApi(existing?.api ?? "openai-completions");
3606
+ setModels((existing?.modelIds ?? []).join(", "));
3607
+ setModelId(existing?.modelIds?.[0] ?? "");
3608
+ };
3609
+ const openAddPicker = () => {
3610
+ closeEditor();
3611
+ setAddPickerQuery("");
3612
+ setAddPickerOpen(true);
3613
+ };
3614
+ const closeAddPicker = () => {
3615
+ setAddPickerOpen(false);
3616
+ setAddPickerQuery("");
3617
+ };
3618
+ const selectProviderFromPicker = (providerId) => {
3619
+ openEditor(providerId, { newOccInstance: isOccProviderId(providerId) });
3620
+ };
3621
+ const handleSave = async (providerId) => {
3622
+ if (!onUpdateProvider) return;
3623
+ setAttemptedSave(true);
3624
+ const isCustomEditor = providerId === "custom" || isCustomProviderId(providerId);
3625
+ const openAiChat = isOccProviderId(providerId);
3626
+ if (isCustomEditor) {
3627
+ const modelList = parseCustomProviderModels(models);
3628
+ if (!apiKey.trim() || !baseUrl.trim() || !displayName.trim()) return;
3629
+ if (modelList.length === 0) return;
3630
+ try {
3631
+ await onUpdateProvider({
3632
+ providerId,
3633
+ apiKey: apiKey.trim(),
3634
+ baseUrl: baseUrl.trim(),
3635
+ displayName: displayName.trim(),
3636
+ api,
3637
+ models: modelList,
3638
+ ...providerId === "custom" ? { createOccInstance: true } : {}
3639
+ });
3640
+ notify.success("Provider credentials applied to your active sessions.");
3641
+ closeEditor();
3642
+ closeAddPicker();
3643
+ } catch (error) {
3644
+ notify.error(error instanceof Error ? error.message : "Failed to update provider");
3645
+ }
3646
+ return;
3647
+ }
3648
+ const isNamedOcc = openAiChat && isNamedOccInstanceId(providerId);
3649
+ const isOccLike = openAiChat && (isNamedOcc || editingIsNewOccInstance);
3650
+ if (!apiKey.trim()) return;
3651
+ if (openAiChat && (!baseUrl.trim() || !modelId.trim())) return;
3652
+ if (isOccLike && !displayName.trim()) return;
3653
+ try {
3654
+ await onUpdateProvider({
3655
+ providerId,
3656
+ apiKey: apiKey.trim(),
3657
+ ...openAiChat ? {
3658
+ baseUrl: baseUrl.trim(),
3659
+ modelId: modelId.trim(),
3660
+ ...isOccLike ? { displayName: displayName.trim() } : {},
3661
+ ...editingIsNewOccInstance ? { createOccInstance: true } : {}
3662
+ } : {}
3663
+ });
3664
+ notify.success("Provider credentials applied to your active sessions.");
3665
+ closeEditor();
3666
+ closeAddPicker();
3667
+ } catch (error) {
3668
+ notify.error(error instanceof Error ? error.message : "Failed to update provider");
4113
3669
  }
4114
- return [...groups.entries()];
4115
- }, [listedModels]);
4116
- const emptyMessage = (() => {
4117
- if (modelOptions.length === 0) return "No models discovered yet. Configure a provider, then add models.";
4118
- if (listedModels.length === 0 && !normalizedFilter) return "Add models from your configured providers to make them available in chat.";
4119
- if (listedModels.length === 0) return "No models match your search.";
4120
- return null;
4121
- })();
4122
- const openAddDialog = () => {
4123
- setAddOpen(true);
4124
3670
  };
4125
- return /* @__PURE__ */ jsxs(SettingsPane, {
4126
- title: "LLM Models",
4127
- description: /* @__PURE__ */ jsxs(Fragment$1, { children: [
4128
- "Models available in chat. Discover from configured providers, add the ones you want, and remove any you do not need \u2014 changes save automatically.",
4129
- " ",
4130
- /* @__PURE__ */ jsx("span", {
4131
- className: "tabular-nums",
4132
- children: listedModels.length
4133
- }),
4134
- " in list",
4135
- modelOptions.length > 0 ? /* @__PURE__ */ jsxs(Fragment$1, { children: [
4136
- " ",
4137
- "\xB7 ",
4138
- /* @__PURE__ */ jsx("span", {
4139
- className: "tabular-nums",
4140
- children: modelOptions.length
4141
- }),
4142
- " ",
4143
- "discovered"
4144
- ] }) : null,
4145
- "."
4146
- ] }),
4147
- actions: /* @__PURE__ */ jsx(SettingsCommitActions, {
4148
- dirty: modelDirty,
4149
- disabled: disabled2,
4150
- onRevert,
4151
- onSave,
4152
- saving
4153
- }),
3671
+ const handleRemove = async (provider) => {
3672
+ if (!onRemoveProvider) return;
3673
+ try {
3674
+ await onRemoveProvider({ providerId: provider.id });
3675
+ if (editingProvider === provider.id) closeEditor();
3676
+ notify.success(`${provider.name} removed from your configured providers.`);
3677
+ setConfirmRemoveProvider(null);
3678
+ } catch (error) {
3679
+ notify.error(error instanceof Error ? error.message : "Failed to remove provider");
3680
+ }
3681
+ };
3682
+ return {
3683
+ activeProviders,
3684
+ addPickerOpen,
3685
+ addPickerQuery,
3686
+ api,
3687
+ apiKey,
3688
+ attemptedSave,
3689
+ baseUrl,
3690
+ canSave: useMemo2(() => {
3691
+ if (apiKey.trim().length === 0) return false;
3692
+ if (!editingProvider) return true;
3693
+ if (editingProvider === "custom" || isCustomProviderId(editingProvider)) {
3694
+ if (baseUrl.trim().length === 0) return false;
3695
+ if (parseCustomProviderModels(models).length === 0) return false;
3696
+ return displayName.trim().length > 0;
3697
+ }
3698
+ if (!isOccProviderId(editingProvider)) return true;
3699
+ if (baseUrl.trim().length === 0 || modelId.trim().length === 0) return false;
3700
+ if (isNamedOccInstanceId(editingProvider) || editingIsNewOccInstance) return displayName.trim().length > 0;
3701
+ return true;
3702
+ }, [
3703
+ apiKey,
3704
+ baseUrl,
3705
+ modelId,
3706
+ models,
3707
+ displayName,
3708
+ editingProvider,
3709
+ editingIsNewOccInstance
3710
+ ]),
3711
+ closeAddPicker,
3712
+ closeEditor,
3713
+ confirmRemoveProvider,
3714
+ credentialProviders,
3715
+ displayName,
3716
+ editingIsNewOccInstance,
3717
+ editingProvider,
3718
+ editingUnconfiguredProvider,
3719
+ filteredActiveProviders,
3720
+ filteredPickerAvailable,
3721
+ filteredPickerConfigured,
3722
+ handleRemove,
3723
+ handleSave,
3724
+ modelId,
3725
+ models,
3726
+ openAddPicker,
3727
+ openEditor,
3728
+ pickerHasResults,
3729
+ resetEditor,
3730
+ searchQuery,
3731
+ selectProviderFromPicker,
3732
+ setAddPickerOpen,
3733
+ setAddPickerQuery,
3734
+ setApi,
3735
+ setApiKey,
3736
+ setAttemptedSave,
3737
+ setBaseUrl,
3738
+ setConfirmRemoveProvider,
3739
+ setDisplayName,
3740
+ setEditingIsNewOccInstance,
3741
+ setEditingProvider,
3742
+ setModelId,
3743
+ setModels,
3744
+ setSearchQuery,
3745
+ setShowPassword,
3746
+ showPassword,
3747
+ unconfiguredProviders
3748
+ };
3749
+ }
3750
+ function providerMatchesQuery(provider, query) {
3751
+ if (!query) return true;
3752
+ const keywords = supportsOAuth(provider) ? isOAuthProvider(provider) ? "oauth sign-in login" : "api key oauth sign-in login" : provider.id === "custom" || isCustomProviderId(provider.id) ? "custom provider endpoint openai anthropic google compatible" : isOccProviderId(provider.id) ? "api key base url model name chat completions" : "";
3753
+ return [
3754
+ provider.name,
3755
+ provider.envVarName,
3756
+ provider.id,
3757
+ keywords
3758
+ ].join(" ").toLowerCase().includes(query);
3759
+ }
3760
+ function AddProviderPickerPanel({ addPickerQuery, credentialProvidersLength, filteredPickerAvailable, filteredPickerConfigured, pickerHasResults, onClose, onQueryChange, onSelect }) {
3761
+ return /* @__PURE__ */ jsxs("div", {
3762
+ className: "flex flex-col gap-3",
4154
3763
  children: [
4155
3764
  /* @__PURE__ */ jsxs("div", {
4156
3765
  className: "flex items-center gap-2",
4157
- children: [/* @__PURE__ */ jsxs(InputGroup, {
4158
- className: "flex-1",
4159
- children: [/* @__PURE__ */ jsx(InputGroupAddon, {
4160
- align: "inline-start",
4161
- children: /* @__PURE__ */ jsx(Search, {})
4162
- }), /* @__PURE__ */ jsx(InputGroupInput, {
4163
- "aria-label": "Search listed models",
4164
- value: modelFilter,
4165
- onChange: (event) => onModelFilterChange(event.target.value),
4166
- placeholder: "Search listed models\u2026",
4167
- disabled: disabled2 || listedModels.length === 0
4168
- })]
4169
- }), /* @__PURE__ */ jsxs(Button, {
3766
+ children: [/* @__PURE__ */ jsx(Button, {
4170
3767
  type: "button",
4171
- variant: "outline",
4172
- size: "sm",
4173
- disabled: disabled2,
4174
- onClick: openAddDialog,
4175
- children: [/* @__PURE__ */ jsx(Plus, { "data-icon": "inline-start" }), "Add models"]
3768
+ variant: "ghost",
3769
+ size: "icon-sm",
3770
+ "aria-label": "Back to providers",
3771
+ onClick: onClose,
3772
+ children: /* @__PURE__ */ jsx(ArrowLeft, {})
3773
+ }), /* @__PURE__ */ jsxs("div", {
3774
+ className: "min-w-0 flex-1",
3775
+ children: [/* @__PURE__ */ jsx("p", {
3776
+ className: "text-sm font-medium",
3777
+ children: "Add provider"
3778
+ }), /* @__PURE__ */ jsx("p", {
3779
+ className: "text-xs text-pretty text-muted-foreground",
3780
+ children: "Choose a provider to store encrypted credentials for this account."
3781
+ })]
4176
3782
  })]
4177
3783
  }),
3784
+ /* @__PURE__ */ jsxs(InputGroup, { children: [/* @__PURE__ */ jsx(InputGroupAddon, {
3785
+ align: "inline-start",
3786
+ children: /* @__PURE__ */ jsx(Search, {})
3787
+ }), /* @__PURE__ */ jsx(InputGroupInput, {
3788
+ type: "text",
3789
+ placeholder: "Search providers\u2026",
3790
+ value: addPickerQuery,
3791
+ onChange: (event) => onQueryChange(event.target.value),
3792
+ "aria-label": "Search providers to add"
3793
+ })] }),
4178
3794
  /* @__PURE__ */ jsx("div", {
4179
- className: "flex flex-col gap-3",
4180
- "data-testid": "runtime-models-list",
4181
- children: emptyMessage ? /* @__PURE__ */ jsxs("div", {
4182
- className: "flex flex-col items-center gap-3 py-8",
4183
- children: [/* @__PURE__ */ jsx("p", {
4184
- className: "px-1 text-center text-xs text-pretty text-muted-foreground",
4185
- children: emptyMessage
4186
- }), !normalizedFilter ? /* @__PURE__ */ jsxs(Button, {
4187
- type: "button",
4188
- variant: "outline",
4189
- size: "sm",
4190
- disabled: disabled2,
4191
- onClick: openAddDialog,
4192
- children: [/* @__PURE__ */ jsx(Plus, { "data-icon": "inline-start" }), "Add models"]
3795
+ className: "max-h-[min(24rem,50vh)] overflow-y-auto",
3796
+ children: !pickerHasResults ? /* @__PURE__ */ jsx("p", {
3797
+ className: "py-6 text-center text-xs text-pretty text-muted-foreground",
3798
+ children: credentialProvidersLength === 0 ? "No providers available." : "No matching providers found."
3799
+ }) : /* @__PURE__ */ jsxs("div", {
3800
+ className: "flex flex-col gap-3",
3801
+ children: [filteredPickerAvailable.length > 0 ? /* @__PURE__ */ jsx("div", {
3802
+ className: "flex flex-col gap-1",
3803
+ children: filteredPickerAvailable.map((provider) => /* @__PURE__ */ jsx(ProviderPickerRow, {
3804
+ provider,
3805
+ configured: false,
3806
+ onSelect: () => onSelect(provider.id)
3807
+ }, provider.id))
3808
+ }) : null, filteredPickerConfigured.length > 0 ? /* @__PURE__ */ jsxs("div", {
3809
+ className: "flex flex-col gap-1",
3810
+ children: [filteredPickerAvailable.length > 0 ? /* @__PURE__ */ jsx("p", {
3811
+ className: "px-2 pt-1 text-[0.6875rem] font-medium tracking-wide text-muted-foreground uppercase",
3812
+ children: "Already configured"
3813
+ }) : null, filteredPickerConfigured.map((provider) => /* @__PURE__ */ jsx(ProviderPickerRow, {
3814
+ provider,
3815
+ configured: true,
3816
+ onSelect: () => onSelect(provider.id)
3817
+ }, provider.id))]
4193
3818
  }) : null]
4194
- }) : groupedListed.map(([provider, models]) => /* @__PURE__ */ jsxs("div", {
4195
- className: "flex flex-col gap-1.5",
4196
- children: [/* @__PURE__ */ jsx("div", {
4197
- className: "px-1 text-xs font-medium text-muted-foreground",
4198
- children: providerLabel(provider)
4199
- }), models.map((model) => /* @__PURE__ */ jsx(ItemRow, {
4200
- icon: /* @__PURE__ */ jsx(ProviderBrandIcon, { provider: model.provider }),
4201
- title: model.name,
4202
- subtitle: `${providerLabel(model.provider)} \xB7 ${model.modelId}`,
4203
- trailing: /* @__PURE__ */ jsxs("div", {
4204
- className: "flex items-center",
4205
- children: [isDefaultModel(model) ? /* @__PURE__ */ jsx("span", {
4206
- className: "px-2 text-xs font-medium text-muted-foreground",
4207
- children: "Default"
4208
- }) : onSetDefaultModel ? /* @__PURE__ */ jsxs(Button, {
4209
- type: "button",
4210
- variant: "ghost",
4211
- size: "sm",
4212
- className: cn(HIT_AREA_EXPAND_DENSE_CLASS, "h-7 px-2 text-xs text-muted-foreground transition-[background-color,color,transform] duration-150 active:scale-[0.96]"),
4213
- disabled: disabled2,
4214
- "aria-label": `Set ${model.name} as default`,
4215
- onClick: () => onSetDefaultModel(model),
4216
- children: [/* @__PURE__ */ jsx(Star, { "data-icon": "inline-start" }), "Set default"]
4217
- }) : null, /* @__PURE__ */ jsxs(Button, {
4218
- type: "button",
4219
- variant: "ghost",
4220
- size: "sm",
4221
- className: cn(HIT_AREA_EXPAND_DENSE_CLASS, "h-7 px-2 text-xs text-muted-foreground transition-[background-color,color,transform] duration-150 hover:text-destructive active:scale-[0.96]"),
4222
- disabled: disabled2,
4223
- "aria-label": `Remove ${model.name}`,
4224
- onClick: () => onRemoveModel(model),
4225
- children: [/* @__PURE__ */ jsx(Trash2, { "data-icon": "inline-start" }), "Remove"]
4226
- })]
4227
- })
4228
- }, model.id))]
4229
- }, provider))
4230
- }),
4231
- /* @__PURE__ */ jsx(AddModelsDialog, {
4232
- open: addOpen,
4233
- configuredProviderIds,
4234
- discoverableProviderIds,
4235
- discoveringProviderId,
4236
- enabledModels: draft?.enabledModels,
4237
- modelOptions,
4238
- onAddModels,
4239
- onDiscoverProvider,
4240
- onOpenChange: setAddOpen,
4241
- providerLabel
3819
+ })
4242
3820
  })
4243
3821
  ]
4244
3822
  });
4245
3823
  }
4246
- var TRANSPORT_OPTIONS = [
4247
- {
4248
- value: "auto",
4249
- label: "Auto"
4250
- },
4251
- {
4252
- value: "sse",
4253
- label: "SSE"
4254
- },
4255
- {
4256
- value: "websocket",
4257
- label: "WebSocket"
4258
- }
4259
- ];
4260
- var DELIVERY_OPTIONS = [{
4261
- value: "all",
4262
- label: "All"
4263
- }, {
4264
- value: "one-at-a-time",
4265
- label: "One at a time"
4266
- }];
4267
- function HarnessRuntimeSection({ draft, updateDraft }) {
4268
- const disabled2 = !draft;
3824
+ function AddProviderOAuthPanel({ onBack, onConfigured, onOAuthLogin, provider }) {
4269
3825
  return /* @__PURE__ */ jsxs("div", {
4270
- className: "flex flex-col gap-2",
3826
+ className: "flex flex-col gap-3",
3827
+ children: [/* @__PURE__ */ jsxs("div", {
3828
+ className: "flex items-center gap-2",
3829
+ children: [/* @__PURE__ */ jsx(Button, {
3830
+ type: "button",
3831
+ variant: "ghost",
3832
+ size: "icon-sm",
3833
+ "aria-label": "Back to provider list",
3834
+ onClick: onBack,
3835
+ children: /* @__PURE__ */ jsx(ArrowLeft, {})
3836
+ }), /* @__PURE__ */ jsxs("div", {
3837
+ className: "min-w-0 flex-1",
3838
+ children: [/* @__PURE__ */ jsxs("p", {
3839
+ className: "text-sm font-medium",
3840
+ children: ["Configure ", provider.name]
3841
+ }), /* @__PURE__ */ jsx("p", {
3842
+ className: "text-xs text-pretty text-muted-foreground",
3843
+ children: "OAuth sign-in. Credentials are stored in auth.json for this account."
3844
+ })]
3845
+ })]
3846
+ }), /* @__PURE__ */ jsx(ProviderOAuthSignIn, {
3847
+ provider,
3848
+ onConfigured,
3849
+ onOAuthLogin
3850
+ }, provider.id)]
3851
+ });
3852
+ }
3853
+ function AddProviderEditorPanel({ provider, api, apiKey, baseUrl, modelId, models, displayName, showPassword, attemptedSave, isPending, canSave, onApiKeyChange, onApiChange, onBaseUrlChange, onModelIdChange, onModelsChange, onDisplayNameChange, onTogglePassword, onBack, onCancel, onConfigured, onOAuthLogin, onSave }) {
3854
+ const isCustomTemplate = provider.id === "custom" || isCustomProviderId(provider.id);
3855
+ const meta = PROVIDER_METADATA[provider.id] ?? (isCustomTemplate ? PROVIDER_METADATA["custom"] : {
3856
+ placeholder: "Enter credentials\u2026",
3857
+ help: "Stored securely in your local environment overrides."
3858
+ });
3859
+ const openAiChat = isOccProviderId(provider.id) && !isCustomTemplate;
3860
+ const oauthAvailable = supportsOAuth(provider);
3861
+ return /* @__PURE__ */ jsxs("div", {
3862
+ className: "flex flex-col gap-3",
4271
3863
  children: [
4272
- /* @__PURE__ */ jsx(ItemRow, {
4273
- title: "Compaction",
4274
- subtitle: "Summarize old turns when context fills up",
4275
- trailing: /* @__PURE__ */ jsx(Switch, {
4276
- "aria-label": "Compaction",
4277
- checked: draft?.compaction.enabled ?? false,
4278
- disabled: disabled2,
4279
- onCheckedChange: (enabled) => updateDraft((current) => ({
4280
- ...current,
4281
- compaction: {
4282
- ...current.compaction,
4283
- enabled
4284
- }
4285
- }))
4286
- })
3864
+ /* @__PURE__ */ jsxs("div", {
3865
+ className: "flex items-center gap-2",
3866
+ children: [/* @__PURE__ */ jsx(Button, {
3867
+ type: "button",
3868
+ variant: "ghost",
3869
+ size: "icon-sm",
3870
+ "aria-label": "Back to provider list",
3871
+ onClick: onBack,
3872
+ children: /* @__PURE__ */ jsx(ArrowLeft, {})
3873
+ }), /* @__PURE__ */ jsxs("div", {
3874
+ className: "min-w-0 flex-1",
3875
+ children: [/* @__PURE__ */ jsxs("p", {
3876
+ className: "text-sm font-medium",
3877
+ children: ["Configure ", provider.name]
3878
+ }), /* @__PURE__ */ jsx("p", {
3879
+ className: "text-xs text-pretty text-muted-foreground",
3880
+ children: isCustomTemplate ? "Name this custom provider, pick its API family, then enter its API key, https base URL, and model ids." : openAiChat ? "Name this OpenAI-compatible provider, then enter its API key, base URL, and model name." : `Stored as ${provider.envVarName} for this account.`
3881
+ })]
3882
+ })]
4287
3883
  }),
4288
- /* @__PURE__ */ jsx(ItemRow, {
4289
- title: "Auto-retry",
4290
- subtitle: "Retry failed provider requests",
4291
- trailing: /* @__PURE__ */ jsx(Switch, {
4292
- "aria-label": "Auto-retry",
4293
- checked: draft?.retry.enabled ?? false,
4294
- disabled: disabled2,
4295
- onCheckedChange: (enabled) => updateDraft((current) => ({
4296
- ...current,
4297
- retry: {
4298
- ...current.retry,
4299
- enabled
4300
- }
4301
- }))
4302
- })
3884
+ /* @__PURE__ */ jsx(ProviderCredentialFields, {
3885
+ attemptedSave,
3886
+ api,
3887
+ apiKey,
3888
+ baseUrl,
3889
+ modelId,
3890
+ models,
3891
+ displayName,
3892
+ isCustom: isCustomTemplate,
3893
+ openAiChat,
3894
+ placeholder: meta.placeholder,
3895
+ showPassword,
3896
+ onApiKeyChange,
3897
+ onApiChange,
3898
+ onBaseUrlChange,
3899
+ onModelIdChange,
3900
+ onModelsChange,
3901
+ onDisplayNameChange,
3902
+ onTogglePassword
4303
3903
  }),
4304
- /* @__PURE__ */ jsx(ItemRow, {
4305
- title: "Transport",
4306
- trailing: /* @__PURE__ */ jsx(Select, {
4307
- "aria-label": "Transport",
4308
- className: "w-[160px]",
4309
- disabled: disabled2,
4310
- onValueChange: (value) => updateDraft((current) => ({
4311
- ...current,
4312
- transport: value
4313
- })),
4314
- options: [...TRANSPORT_OPTIONS],
4315
- value: draft?.transport ?? "auto"
4316
- })
3904
+ /* @__PURE__ */ jsxs(Alert, {
3905
+ className: "px-3 py-2",
3906
+ children: [/* @__PURE__ */ jsx(Info, {}), /* @__PURE__ */ jsx(AlertDescription, {
3907
+ className: "text-xs text-pretty",
3908
+ children: meta.help
3909
+ })]
4317
3910
  }),
4318
- /* @__PURE__ */ jsx(ItemRow, {
4319
- title: "Steering",
4320
- subtitle: "How queued steers are delivered mid-turn",
4321
- trailing: /* @__PURE__ */ jsx(Select, {
4322
- "aria-label": "Steering",
4323
- className: "w-[160px]",
4324
- disabled: disabled2,
4325
- onValueChange: (value) => updateDraft((current) => ({
4326
- ...current,
4327
- steeringMode: value
4328
- })),
4329
- options: [...DELIVERY_OPTIONS],
4330
- value: draft?.steeringMode ?? "all"
4331
- })
3911
+ /* @__PURE__ */ jsxs("div", {
3912
+ className: "flex items-center justify-end gap-1.5",
3913
+ children: [/* @__PURE__ */ jsx(Button, {
3914
+ type: "button",
3915
+ variant: "ghost",
3916
+ size: "sm",
3917
+ onClick: onCancel,
3918
+ children: "Cancel"
3919
+ }), /* @__PURE__ */ jsxs(Button, {
3920
+ type: "button",
3921
+ size: "sm",
3922
+ disabled: isPending || !canSave,
3923
+ onClick: onSave,
3924
+ children: [isPending ? /* @__PURE__ */ jsx(Spinner, { "data-icon": "inline-start" }) : null, "Save"]
3925
+ })]
4332
3926
  }),
4333
- /* @__PURE__ */ jsx(ItemRow, {
4334
- title: "Follow-up",
4335
- subtitle: "How queued follow-ups run after the turn",
4336
- trailing: /* @__PURE__ */ jsx(Select, {
4337
- "aria-label": "Follow-up",
4338
- className: "w-[160px]",
4339
- disabled: disabled2,
4340
- onValueChange: (value) => updateDraft((current) => ({
4341
- ...current,
4342
- followUpMode: value
4343
- })),
4344
- options: [...DELIVERY_OPTIONS],
4345
- value: draft?.followUpMode ?? "all"
4346
- })
4347
- })
3927
+ oauthAvailable ? /* @__PURE__ */ jsx(ProviderOAuthSignIn, {
3928
+ provider,
3929
+ onConfigured: onConfigured ?? onCancel,
3930
+ onOAuthLogin
3931
+ }, `${provider.id}-oauth`) : null
4348
3932
  ]
4349
3933
  });
4350
3934
  }
3935
+ function RemoveProviderConfirmDialog({ confirmRemoveProvider, isPending, onClose, onConfirm }) {
3936
+ return /* @__PURE__ */ jsx(AlertDialog, {
3937
+ open: confirmRemoveProvider !== null,
3938
+ onOpenChange: (open) => {
3939
+ if (!open) onClose();
3940
+ },
3941
+ children: /* @__PURE__ */ jsxs(AlertDialogContent, { children: [/* @__PURE__ */ jsxs("div", {
3942
+ className: "flex flex-col gap-2",
3943
+ children: [/* @__PURE__ */ jsxs(AlertDialogTitle, { children: [
3944
+ "Remove ",
3945
+ confirmRemoveProvider?.name ?? "provider",
3946
+ "?"
3947
+ ] }), /* @__PURE__ */ jsx(AlertDialogDescription, { children: "This clears stored credentials for this provider from your workspace settings. Active sessions will stop using it immediately." })]
3948
+ }), /* @__PURE__ */ jsxs(AlertDialogFooter, { children: [/* @__PURE__ */ jsx(AlertDialogCancel, {
3949
+ disabled: isPending,
3950
+ children: "Cancel"
3951
+ }), /* @__PURE__ */ jsxs(AlertDialogAction, {
3952
+ disabled: isPending || !confirmRemoveProvider,
3953
+ onClick: () => {
3954
+ if (confirmRemoveProvider) onConfirm(confirmRemoveProvider);
3955
+ },
3956
+ children: [isPending ? /* @__PURE__ */ jsx(Spinner, { "data-icon": "inline-start" }) : null, "Remove provider"]
3957
+ })] })] })
3958
+ });
3959
+ }
3960
+ function ProviderPickerRow({ configured, onSelect, provider }) {
3961
+ const openAiChat = isOccProviderId(provider.id);
3962
+ const isCustomTemplate = provider.id === "custom" || isCustomProviderId(provider.id);
3963
+ const oauthOnly = isOAuthProvider(provider);
3964
+ const oauthAvailable = supportsOAuth(provider);
3965
+ return /* @__PURE__ */ jsxs("button", {
3966
+ type: "button",
3967
+ className: cn("flex w-full items-center gap-3 rounded-lg px-2 py-2 text-left transition-[background-color,transform] duration-150", "hover:bg-muted/60 focus-visible:ring-2 focus-visible:ring-ring focus-visible:outline-none active:scale-[0.96]"),
3968
+ onClick: onSelect,
3969
+ children: [/* @__PURE__ */ jsx("div", {
3970
+ className: "flex size-8 shrink-0 items-center justify-center rounded-[4px] border border-border/40 bg-background/60",
3971
+ children: /* @__PURE__ */ jsx(ProviderBrandIcon, {
3972
+ provider: isCustomTemplate ? "custom" : provider.id,
3973
+ api: provider.api,
3974
+ className: "text-foreground/70"
3975
+ })
3976
+ }), /* @__PURE__ */ jsxs("div", {
3977
+ className: "min-w-0 flex-1",
3978
+ children: [/* @__PURE__ */ jsx("div", {
3979
+ className: "truncate text-sm font-medium",
3980
+ children: provider.name
3981
+ }), /* @__PURE__ */ jsxs("div", {
3982
+ className: "truncate text-xs text-muted-foreground",
3983
+ children: [oauthOnly ? "OAuth sign-in" : oauthAvailable ? "API key or OAuth" : isCustomTemplate ? "API family + key + https base URL + models" : openAiChat ? "API key + base URL + model name" : provider.envVarName, configured ? " \xB7 Update" : ""]
3984
+ })]
3985
+ })]
3986
+ });
3987
+ }
3988
+ function ActiveProviderList({ providers, editingProvider, form, operation, actions, onEdit, onCancelEdit, onSave, onRemove, onOAuthLogin, onConfigured }) {
3989
+ const { api, apiKey, baseUrl, modelId, models, displayName, showPassword, attemptedSave, canSave } = form;
3990
+ const { isPending, canRemove } = operation;
3991
+ const { onApiKeyChange, onApiChange, onBaseUrlChange, onModelIdChange, onModelsChange, onDisplayNameChange, onTogglePassword } = actions;
3992
+ return /* @__PURE__ */ jsx("div", {
3993
+ className: "flex flex-col gap-1.5",
3994
+ children: providers.map((provider) => {
3995
+ const isEditing = editingProvider === provider.id;
3996
+ const isCustom = isCustomProviderId(provider.id);
3997
+ const meta = PROVIDER_METADATA[provider.id] ?? PROVIDER_METADATA[isCustom ? "custom" : "openai-chat-completions"];
3998
+ const openAiChat = isOccProviderId(provider.id);
3999
+ const oauthOnly = isOAuthProvider(provider);
4000
+ const oauthAvailable = supportsOAuth(provider);
4001
+ return /* @__PURE__ */ jsxs("div", {
4002
+ className: "flex flex-col",
4003
+ children: [/* @__PURE__ */ jsx(ItemRow, {
4004
+ interactive: false,
4005
+ icon: /* @__PURE__ */ jsx(ProviderBrandIcon, {
4006
+ provider: provider.id,
4007
+ api: provider.api,
4008
+ className: "text-foreground/80"
4009
+ }),
4010
+ title: provider.name,
4011
+ subtitle: oauthOnly ? "OAuth sign-in" : oauthAvailable ? "API key or OAuth" : isCustom ? "Custom provider \xB7 API key + base URL + models" : openAiChat ? "OpenAI-compatible \xB7 API key + base URL + model name" : provider.envVarName,
4012
+ trailing: /* @__PURE__ */ jsxs("div", {
4013
+ className: "flex items-center gap-0.5",
4014
+ children: [/* @__PURE__ */ jsxs(Button, {
4015
+ type: "button",
4016
+ variant: "ghost",
4017
+ size: "sm",
4018
+ className: cn(HIT_AREA_EXPAND_DENSE_CLASS, "h-7 px-2 text-xs text-muted-foreground transition-[background-color,color,transform] duration-150 hover:text-destructive active:scale-[0.96]"),
4019
+ disabled: isPending || !canRemove,
4020
+ "aria-label": `Remove ${provider.name}`,
4021
+ onClick: () => onRemove(provider),
4022
+ children: [/* @__PURE__ */ jsx(Trash2, { "data-icon": "inline-start" }), "Remove"]
4023
+ }), /* @__PURE__ */ jsxs(Button, {
4024
+ type: "button",
4025
+ variant: "ghost",
4026
+ size: "sm",
4027
+ className: cn(HIT_AREA_EXPAND_DENSE_CLASS, "h-7 px-2 text-xs transition-[background-color,transform] duration-150 active:scale-[0.96]"),
4028
+ disabled: isPending,
4029
+ onClick: () => {
4030
+ if (isEditing) {
4031
+ onCancelEdit();
4032
+ return;
4033
+ }
4034
+ onEdit(provider.id);
4035
+ },
4036
+ children: [!isEditing ? /* @__PURE__ */ jsx(Pencil, { "data-icon": "inline-start" }) : null, isEditing ? "Cancel" : "Update"]
4037
+ })]
4038
+ })
4039
+ }), isEditing ? /* @__PURE__ */ jsx(RowSurface, {
4040
+ tone: "inset",
4041
+ padding: "md",
4042
+ className: "flex flex-col gap-2 border-t border-border/30",
4043
+ children: oauthOnly ? /* @__PURE__ */ jsx(ProviderOAuthSignIn, {
4044
+ provider,
4045
+ onConfigured,
4046
+ onOAuthLogin
4047
+ }, provider.id) : /* @__PURE__ */ jsxs(Fragment$1, { children: [
4048
+ /* @__PURE__ */ jsx(ProviderCredentialFields, {
4049
+ attemptedSave,
4050
+ api: provider.api ?? api,
4051
+ apiKey,
4052
+ baseUrl,
4053
+ modelId,
4054
+ models,
4055
+ displayName,
4056
+ isCustom,
4057
+ openAiChat,
4058
+ placeholder: meta.placeholder,
4059
+ showPassword,
4060
+ onApiKeyChange,
4061
+ onApiChange,
4062
+ onBaseUrlChange,
4063
+ onModelIdChange,
4064
+ onModelsChange,
4065
+ onDisplayNameChange: isCustom || provider.providerFamily !== void 0 ? onDisplayNameChange : void 0,
4066
+ onTogglePassword
4067
+ }),
4068
+ /* @__PURE__ */ jsxs(Alert, {
4069
+ className: "px-3 py-2",
4070
+ children: [/* @__PURE__ */ jsx(Info, {}), /* @__PURE__ */ jsx(AlertDescription, {
4071
+ className: "text-xs text-pretty",
4072
+ children: meta.help
4073
+ })]
4074
+ }),
4075
+ /* @__PURE__ */ jsx("div", {
4076
+ className: "flex items-center justify-end",
4077
+ children: /* @__PURE__ */ jsxs(Button, {
4078
+ type: "button",
4079
+ size: "sm",
4080
+ disabled: isPending || !canSave,
4081
+ onClick: () => onSave(provider.id),
4082
+ children: [isPending ? /* @__PURE__ */ jsx(Spinner, { "data-icon": "inline-start" }) : null, "Save"]
4083
+ })
4084
+ }),
4085
+ oauthAvailable ? /* @__PURE__ */ jsx(ProviderOAuthSignIn, {
4086
+ provider,
4087
+ onConfigured,
4088
+ onOAuthLogin
4089
+ }, `${provider.id}-oauth`) : null
4090
+ ] })
4091
+ }) : null]
4092
+ }, provider.id);
4093
+ })
4094
+ });
4095
+ }
4096
+ function ProviderCredentialsSection({ isLoading, isPending, onOAuthLogin, onRemoveProvider, onUpdateProvider, providers }) {
4097
+ const { activeProviders, addPickerOpen, addPickerQuery, api, apiKey, attemptedSave, baseUrl, canSave, closeAddPicker, closeEditor, confirmRemoveProvider, credentialProviders, displayName, editingProvider, editingUnconfiguredProvider, filteredActiveProviders, filteredPickerAvailable, filteredPickerConfigured, handleRemove, handleSave, modelId, models, openAddPicker, openEditor, pickerHasResults, searchQuery, selectProviderFromPicker, setAddPickerQuery, setApi, setApiKey, setBaseUrl, setConfirmRemoveProvider, setDisplayName, setModelId, setModels, setSearchQuery, setShowPassword, showPassword } = useProviderCredentialsController({
4098
+ onRemoveProvider,
4099
+ onUpdateProvider,
4100
+ providers
4101
+ });
4102
+ const form = {
4103
+ api,
4104
+ apiKey,
4105
+ baseUrl,
4106
+ modelId,
4107
+ models,
4108
+ displayName,
4109
+ showPassword,
4110
+ attemptedSave,
4111
+ canSave
4112
+ };
4113
+ const operation = {
4114
+ isPending,
4115
+ canRemove: Boolean(onRemoveProvider)
4116
+ };
4117
+ const actions = {
4118
+ onApiKeyChange: setApiKey,
4119
+ onApiChange: setApi,
4120
+ onBaseUrlChange: setBaseUrl,
4121
+ onModelIdChange: setModelId,
4122
+ onModelsChange: setModels,
4123
+ onDisplayNameChange: setDisplayName,
4124
+ onTogglePassword: () => setShowPassword((current) => !current)
4125
+ };
4126
+ return /* @__PURE__ */ jsxs(SettingsPane, {
4127
+ title: "Providers",
4128
+ description: "Locally, API keys are stored in `.env.local`. When deployed, they are stored encrypted in your account.",
4129
+ children: [addPickerOpen ? /* @__PURE__ */ jsx(AddProviderPickerPanel, {
4130
+ addPickerQuery,
4131
+ credentialProvidersLength: credentialProviders.length,
4132
+ filteredPickerAvailable,
4133
+ filteredPickerConfigured,
4134
+ pickerHasResults,
4135
+ onClose: closeAddPicker,
4136
+ onQueryChange: setAddPickerQuery,
4137
+ onSelect: selectProviderFromPicker
4138
+ }) : editingUnconfiguredProvider && isOAuthProvider(editingUnconfiguredProvider) ? /* @__PURE__ */ jsx(AddProviderOAuthPanel, {
4139
+ provider: editingUnconfiguredProvider,
4140
+ onBack: openAddPicker,
4141
+ onConfigured: closeEditor,
4142
+ onOAuthLogin
4143
+ }, editingUnconfiguredProvider.id) : editingUnconfiguredProvider ? /* @__PURE__ */ jsx(AddProviderEditorPanel, {
4144
+ provider: editingUnconfiguredProvider,
4145
+ api,
4146
+ apiKey,
4147
+ baseUrl,
4148
+ modelId,
4149
+ models,
4150
+ displayName,
4151
+ showPassword,
4152
+ attemptedSave,
4153
+ isPending,
4154
+ canSave,
4155
+ onApiKeyChange: setApiKey,
4156
+ onApiChange: setApi,
4157
+ onBaseUrlChange: setBaseUrl,
4158
+ onModelIdChange: setModelId,
4159
+ onModelsChange: setModels,
4160
+ onDisplayNameChange: setDisplayName,
4161
+ onTogglePassword: () => setShowPassword((current) => !current),
4162
+ onBack: openAddPicker,
4163
+ onCancel: closeEditor,
4164
+ onConfigured: closeEditor,
4165
+ onOAuthLogin,
4166
+ onSave: () => {
4167
+ if (editingProvider) handleSave(editingProvider);
4168
+ }
4169
+ }) : /* @__PURE__ */ jsxs("div", {
4170
+ className: "flex flex-col gap-3",
4171
+ children: [/* @__PURE__ */ jsxs("div", {
4172
+ className: "flex items-center gap-2",
4173
+ children: [/* @__PURE__ */ jsxs(InputGroup, {
4174
+ className: "flex-1",
4175
+ children: [/* @__PURE__ */ jsx(InputGroupAddon, {
4176
+ align: "inline-start",
4177
+ children: /* @__PURE__ */ jsx(Search, {})
4178
+ }), /* @__PURE__ */ jsx(InputGroupInput, {
4179
+ type: "text",
4180
+ placeholder: "Search credentials\u2026",
4181
+ value: searchQuery,
4182
+ onChange: (event) => setSearchQuery(event.target.value),
4183
+ "aria-label": "Search credentials",
4184
+ disabled: isLoading || activeProviders.length === 0
4185
+ })]
4186
+ }), /* @__PURE__ */ jsxs(Button, {
4187
+ type: "button",
4188
+ variant: "outline",
4189
+ size: "sm",
4190
+ disabled: isLoading || credentialProviders.length === 0,
4191
+ onClick: openAddPicker,
4192
+ children: [/* @__PURE__ */ jsx(Plus, { "data-icon": "inline-start" }), "Add provider"]
4193
+ })]
4194
+ }), isLoading ? /* @__PURE__ */ jsxs("div", {
4195
+ className: "flex items-center justify-center gap-2 py-6 text-xs text-muted-foreground",
4196
+ children: [/* @__PURE__ */ jsx(Spinner, { className: "size-3" }), /* @__PURE__ */ jsx("span", { children: "Loading\u2026" })]
4197
+ }) : activeProviders.length === 0 ? /* @__PURE__ */ jsxs("div", {
4198
+ className: "flex flex-col items-center gap-3 py-8",
4199
+ children: [/* @__PURE__ */ jsx("p", {
4200
+ className: "text-center text-xs text-pretty text-muted-foreground",
4201
+ children: "No providers configured"
4202
+ }), /* @__PURE__ */ jsxs(Button, {
4203
+ type: "button",
4204
+ variant: "outline",
4205
+ size: "sm",
4206
+ disabled: credentialProviders.length === 0,
4207
+ onClick: openAddPicker,
4208
+ children: [/* @__PURE__ */ jsx(Plus, { "data-icon": "inline-start" }), "Add provider"]
4209
+ })]
4210
+ }) : filteredActiveProviders.length === 0 ? /* @__PURE__ */ jsx("p", {
4211
+ className: "py-6 text-center text-xs text-pretty text-muted-foreground",
4212
+ children: "No matching providers found."
4213
+ }) : /* @__PURE__ */ jsx(ActiveProviderList, {
4214
+ providers: filteredActiveProviders,
4215
+ editingProvider,
4216
+ form,
4217
+ operation,
4218
+ actions,
4219
+ onEdit: openEditor,
4220
+ onCancelEdit: closeEditor,
4221
+ onSave: (providerId) => void handleSave(providerId),
4222
+ onRemove: setConfirmRemoveProvider,
4223
+ onOAuthLogin,
4224
+ onConfigured: closeEditor
4225
+ })]
4226
+ }), /* @__PURE__ */ jsx(RemoveProviderConfirmDialog, {
4227
+ confirmRemoveProvider,
4228
+ isPending,
4229
+ onClose: () => setConfirmRemoveProvider(null),
4230
+ onConfirm: (provider) => {
4231
+ handleRemove(provider);
4232
+ }
4233
+ })]
4234
+ });
4235
+ }
4351
4236
  var PROVENANCE_SOURCES = /* @__PURE__ */ new Set([
4352
4237
  "auto",
4353
4238
  "global",
@@ -4464,6 +4349,111 @@ function CatalogValueList({ addLabel, catalog, onChange, placeholder, values })
4464
4349
  })] })]
4465
4350
  });
4466
4351
  }
4352
+ var TRANSPORT_OPTIONS = [
4353
+ {
4354
+ value: "auto",
4355
+ label: "Auto"
4356
+ },
4357
+ {
4358
+ value: "sse",
4359
+ label: "SSE"
4360
+ },
4361
+ {
4362
+ value: "websocket",
4363
+ label: "WebSocket"
4364
+ }
4365
+ ];
4366
+ var DELIVERY_OPTIONS = [{
4367
+ value: "all",
4368
+ label: "All"
4369
+ }, {
4370
+ value: "one-at-a-time",
4371
+ label: "One at a time"
4372
+ }];
4373
+ function HarnessRuntimeSection({ draft, updateDraft }) {
4374
+ const disabled2 = !draft;
4375
+ return /* @__PURE__ */ jsxs("div", {
4376
+ className: "flex flex-col gap-2",
4377
+ children: [
4378
+ /* @__PURE__ */ jsx(ItemRow, {
4379
+ title: "Compaction",
4380
+ subtitle: "Summarize old turns when context fills up",
4381
+ trailing: /* @__PURE__ */ jsx(Switch, {
4382
+ "aria-label": "Compaction",
4383
+ checked: draft?.compaction.enabled ?? false,
4384
+ disabled: disabled2,
4385
+ onCheckedChange: (enabled) => updateDraft((current) => ({
4386
+ ...current,
4387
+ compaction: {
4388
+ ...current.compaction,
4389
+ enabled
4390
+ }
4391
+ }))
4392
+ })
4393
+ }),
4394
+ /* @__PURE__ */ jsx(ItemRow, {
4395
+ title: "Auto-retry",
4396
+ subtitle: "Retry failed provider requests",
4397
+ trailing: /* @__PURE__ */ jsx(Switch, {
4398
+ "aria-label": "Auto-retry",
4399
+ checked: draft?.retry.enabled ?? false,
4400
+ disabled: disabled2,
4401
+ onCheckedChange: (enabled) => updateDraft((current) => ({
4402
+ ...current,
4403
+ retry: {
4404
+ ...current.retry,
4405
+ enabled
4406
+ }
4407
+ }))
4408
+ })
4409
+ }),
4410
+ /* @__PURE__ */ jsx(ItemRow, {
4411
+ title: "Transport",
4412
+ trailing: /* @__PURE__ */ jsx(Select, {
4413
+ "aria-label": "Transport",
4414
+ className: "w-[160px]",
4415
+ disabled: disabled2,
4416
+ onValueChange: (value) => updateDraft((current) => ({
4417
+ ...current,
4418
+ transport: value
4419
+ })),
4420
+ options: [...TRANSPORT_OPTIONS],
4421
+ value: draft?.transport ?? "auto"
4422
+ })
4423
+ }),
4424
+ /* @__PURE__ */ jsx(ItemRow, {
4425
+ title: "Steering",
4426
+ subtitle: "How queued steers are delivered mid-turn",
4427
+ trailing: /* @__PURE__ */ jsx(Select, {
4428
+ "aria-label": "Steering",
4429
+ className: "w-[160px]",
4430
+ disabled: disabled2,
4431
+ onValueChange: (value) => updateDraft((current) => ({
4432
+ ...current,
4433
+ steeringMode: value
4434
+ })),
4435
+ options: [...DELIVERY_OPTIONS],
4436
+ value: draft?.steeringMode ?? "all"
4437
+ })
4438
+ }),
4439
+ /* @__PURE__ */ jsx(ItemRow, {
4440
+ title: "Follow-up",
4441
+ subtitle: "How queued follow-ups run after the turn",
4442
+ trailing: /* @__PURE__ */ jsx(Select, {
4443
+ "aria-label": "Follow-up",
4444
+ className: "w-[160px]",
4445
+ disabled: disabled2,
4446
+ onValueChange: (value) => updateDraft((current) => ({
4447
+ ...current,
4448
+ followUpMode: value
4449
+ })),
4450
+ options: [...DELIVERY_OPTIONS],
4451
+ value: draft?.followUpMode ?? "all"
4452
+ })
4453
+ })
4454
+ ]
4455
+ });
4456
+ }
4467
4457
  function ResourcesSection({ draft, onEnableSkillCommandsChange, onExtensionsChange, onPackageRowsChange, onPromptsChange, onRevert, onSave, onSkillsChange, onThemesChange, packageError, packageRows, resourceDirty, resources, resourceSummary, saving, scope = "harness", settingsLoading, updateDraft }) {
4468
4458
  const disabled2 = !draft || settingsLoading || !!packageError;
4469
4459
  const catalogSubtitle = resourceSummary.total === 0 ? "Empty" : resourceSummary.reloadRequired > 0 ? `${resourceSummary.total} \xB7 ${resourceSummary.reloadRequired} reload` : `${resourceSummary.total}`;