@shipfox/client-agent 0.2.0

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 (182) hide show
  1. package/.storybook/main.ts +1 -0
  2. package/.storybook/preview.css +4 -0
  3. package/.storybook/preview.tsx +56 -0
  4. package/.turbo/turbo-build.log +2 -0
  5. package/.turbo/turbo-type$colon$emit.log +1 -0
  6. package/.turbo/turbo-type.log +1 -0
  7. package/CHANGELOG.md +93 -0
  8. package/LICENSE +21 -0
  9. package/dist/components/add-custom-provider-card.d.ts +4 -0
  10. package/dist/components/add-custom-provider-card.d.ts.map +1 -0
  11. package/dist/components/add-custom-provider-card.js +41 -0
  12. package/dist/components/add-custom-provider-card.js.map +1 -0
  13. package/dist/components/agent-workflow-example.d.ts +11 -0
  14. package/dist/components/agent-workflow-example.d.ts.map +1 -0
  15. package/dist/components/agent-workflow-example.js +38 -0
  16. package/dist/components/agent-workflow-example.js.map +1 -0
  17. package/dist/components/available-provider-card.d.ts +6 -0
  18. package/dist/components/available-provider-card.d.ts.map +1 -0
  19. package/dist/components/available-provider-card.js +41 -0
  20. package/dist/components/available-provider-card.js.map +1 -0
  21. package/dist/components/available-providers-grid.d.ts +10 -0
  22. package/dist/components/available-providers-grid.d.ts.map +1 -0
  23. package/dist/components/available-providers-grid.js +89 -0
  24. package/dist/components/available-providers-grid.js.map +1 -0
  25. package/dist/components/change-default-model-form.d.ts +9 -0
  26. package/dist/components/change-default-model-form.d.ts.map +1 -0
  27. package/dist/components/change-default-model-form.js +98 -0
  28. package/dist/components/change-default-model-form.js.map +1 -0
  29. package/dist/components/custom-model-provider-api-options.d.ts +7 -0
  30. package/dist/components/custom-model-provider-api-options.d.ts.map +1 -0
  31. package/dist/components/custom-model-provider-api-options.js +23 -0
  32. package/dist/components/custom-model-provider-api-options.js.map +1 -0
  33. package/dist/components/custom-model-provider-form.d.ts +8 -0
  34. package/dist/components/custom-model-provider-form.d.ts.map +1 -0
  35. package/dist/components/custom-model-provider-form.js +667 -0
  36. package/dist/components/custom-model-provider-form.js.map +1 -0
  37. package/dist/components/custom-model-provider-form.stories.d.ts +20 -0
  38. package/dist/components/custom-model-provider-form.stories.d.ts.map +1 -0
  39. package/dist/components/custom-model-provider-form.stories.js +104 -0
  40. package/dist/components/custom-model-provider-form.stories.js.map +1 -0
  41. package/dist/components/custom-model-provider-payload.d.ts +39 -0
  42. package/dist/components/custom-model-provider-payload.d.ts.map +1 -0
  43. package/dist/components/custom-model-provider-payload.js +203 -0
  44. package/dist/components/custom-model-provider-payload.js.map +1 -0
  45. package/dist/components/custom-model-provider-slug.d.ts +3 -0
  46. package/dist/components/custom-model-provider-slug.d.ts.map +1 -0
  47. package/dist/components/custom-model-provider-slug.js +14 -0
  48. package/dist/components/custom-model-provider-slug.js.map +1 -0
  49. package/dist/components/default-model-field.d.ts +16 -0
  50. package/dist/components/default-model-field.d.ts.map +1 -0
  51. package/dist/components/default-model-field.js +60 -0
  52. package/dist/components/default-model-field.js.map +1 -0
  53. package/dist/components/form-errors.d.ts +11 -0
  54. package/dist/components/form-errors.d.ts.map +1 -0
  55. package/dist/components/form-errors.js +101 -0
  56. package/dist/components/form-errors.js.map +1 -0
  57. package/dist/components/harness-availability.d.ts +8 -0
  58. package/dist/components/harness-availability.d.ts.map +1 -0
  59. package/dist/components/harness-availability.js +16 -0
  60. package/dist/components/harness-availability.js.map +1 -0
  61. package/dist/components/harnesses-section.d.ts +4 -0
  62. package/dist/components/harnesses-section.d.ts.map +1 -0
  63. package/dist/components/harnesses-section.js +249 -0
  64. package/dist/components/harnesses-section.js.map +1 -0
  65. package/dist/components/harnesses-section.stories.d.ts +25 -0
  66. package/dist/components/harnesses-section.stories.d.ts.map +1 -0
  67. package/dist/components/harnesses-section.stories.js +138 -0
  68. package/dist/components/harnesses-section.stories.js.map +1 -0
  69. package/dist/components/model-provider-usage-modal.d.ts +11 -0
  70. package/dist/components/model-provider-usage-modal.d.ts.map +1 -0
  71. package/dist/components/model-provider-usage-modal.js +349 -0
  72. package/dist/components/model-provider-usage-modal.js.map +1 -0
  73. package/dist/components/model-provider-usage-modal.stories.d.ts +21 -0
  74. package/dist/components/model-provider-usage-modal.stories.d.ts.map +1 -0
  75. package/dist/components/model-provider-usage-modal.stories.js +115 -0
  76. package/dist/components/model-provider-usage-modal.stories.js.map +1 -0
  77. package/dist/components/model-provider-usage-target.d.ts +14 -0
  78. package/dist/components/model-provider-usage-target.d.ts.map +1 -0
  79. package/dist/components/model-provider-usage-target.js +20 -0
  80. package/dist/components/model-provider-usage-target.js.map +1 -0
  81. package/dist/components/model-providers-section.d.ts +4 -0
  82. package/dist/components/model-providers-section.d.ts.map +1 -0
  83. package/dist/components/model-providers-section.js +668 -0
  84. package/dist/components/model-providers-section.js.map +1 -0
  85. package/dist/components/model-providers-section.stories.d.ts +32 -0
  86. package/dist/components/model-providers-section.stories.d.ts.map +1 -0
  87. package/dist/components/model-providers-section.stories.js +442 -0
  88. package/dist/components/model-providers-section.stories.js.map +1 -0
  89. package/dist/components/provider-search.d.ts +4 -0
  90. package/dist/components/provider-search.d.ts.map +1 -0
  91. package/dist/components/provider-search.js +13 -0
  92. package/dist/components/provider-search.js.map +1 -0
  93. package/dist/components/supported-model-provider-catalog-entry.d.ts +8 -0
  94. package/dist/components/supported-model-provider-catalog-entry.d.ts.map +1 -0
  95. package/dist/components/supported-model-provider-catalog-entry.js +9 -0
  96. package/dist/components/supported-model-provider-catalog-entry.js.map +1 -0
  97. package/dist/components/test-and-save-form.d.ts +10 -0
  98. package/dist/components/test-and-save-form.d.ts.map +1 -0
  99. package/dist/components/test-and-save-form.js +138 -0
  100. package/dist/components/test-and-save-form.js.map +1 -0
  101. package/dist/feature.d.ts +9 -0
  102. package/dist/feature.d.ts.map +1 -0
  103. package/dist/feature.js +13 -0
  104. package/dist/feature.js.map +1 -0
  105. package/dist/hooks/api/model-providers.d.ts +334 -0
  106. package/dist/hooks/api/model-providers.d.ts.map +1 -0
  107. package/dist/hooks/api/model-providers.js +190 -0
  108. package/dist/hooks/api/model-providers.js.map +1 -0
  109. package/dist/index.d.ts +9 -0
  110. package/dist/index.d.ts.map +1 -0
  111. package/dist/index.js +10 -0
  112. package/dist/index.js.map +1 -0
  113. package/dist/pages/model-provider-onboarding-page.d.ts +6 -0
  114. package/dist/pages/model-provider-onboarding-page.d.ts.map +1 -0
  115. package/dist/pages/model-provider-onboarding-page.js +295 -0
  116. package/dist/pages/model-provider-onboarding-page.js.map +1 -0
  117. package/dist/pages/model-provider-onboarding-page.stories.d.ts +27 -0
  118. package/dist/pages/model-provider-onboarding-page.stories.d.ts.map +1 -0
  119. package/dist/pages/model-provider-onboarding-page.stories.js +368 -0
  120. package/dist/pages/model-provider-onboarding-page.stories.js.map +1 -0
  121. package/dist/routes/model-provider.d.ts +5 -0
  122. package/dist/routes/model-provider.d.ts.map +1 -0
  123. package/dist/routes/model-provider.js +28 -0
  124. package/dist/routes/model-provider.js.map +1 -0
  125. package/dist/state/model-provider-onboarding.d.ts +3 -0
  126. package/dist/state/model-provider-onboarding.d.ts.map +1 -0
  127. package/dist/state/model-provider-onboarding.js +28 -0
  128. package/dist/state/model-provider-onboarding.js.map +1 -0
  129. package/dist/tsconfig.test.tsbuildinfo +1 -0
  130. package/package.json +114 -0
  131. package/src/components/add-custom-provider-card.tsx +32 -0
  132. package/src/components/agent-workflow-example.test.ts +74 -0
  133. package/src/components/agent-workflow-example.ts +55 -0
  134. package/src/components/available-provider-card.tsx +39 -0
  135. package/src/components/available-providers-grid.test.tsx +55 -0
  136. package/src/components/available-providers-grid.tsx +106 -0
  137. package/src/components/change-default-model-form.tsx +104 -0
  138. package/src/components/custom-model-provider-api-options.test.ts +18 -0
  139. package/src/components/custom-model-provider-api-options.ts +12 -0
  140. package/src/components/custom-model-provider-form.stories.tsx +91 -0
  141. package/src/components/custom-model-provider-form.tsx +710 -0
  142. package/src/components/custom-model-provider-payload.test.ts +170 -0
  143. package/src/components/custom-model-provider-payload.ts +264 -0
  144. package/src/components/custom-model-provider-slug.test.ts +40 -0
  145. package/src/components/custom-model-provider-slug.ts +23 -0
  146. package/src/components/default-model-field.tsx +85 -0
  147. package/src/components/form-errors.test.ts +159 -0
  148. package/src/components/form-errors.ts +102 -0
  149. package/src/components/harness-availability.test.ts +45 -0
  150. package/src/components/harness-availability.ts +35 -0
  151. package/src/components/harnesses-section.stories.tsx +129 -0
  152. package/src/components/harnesses-section.test.tsx +188 -0
  153. package/src/components/harnesses-section.tsx +245 -0
  154. package/src/components/model-provider-usage-modal.stories.tsx +106 -0
  155. package/src/components/model-provider-usage-modal.test.tsx +210 -0
  156. package/src/components/model-provider-usage-modal.tsx +326 -0
  157. package/src/components/model-provider-usage-target.ts +36 -0
  158. package/src/components/model-providers-section.stories.tsx +345 -0
  159. package/src/components/model-providers-section.test.tsx +831 -0
  160. package/src/components/model-providers-section.tsx +645 -0
  161. package/src/components/provider-search.test.ts +45 -0
  162. package/src/components/provider-search.ts +18 -0
  163. package/src/components/supported-model-provider-catalog-entry.ts +19 -0
  164. package/src/components/test-and-save-form.tsx +166 -0
  165. package/src/feature.ts +12 -0
  166. package/src/hooks/api/model-providers.test.ts +167 -0
  167. package/src/hooks/api/model-providers.ts +281 -0
  168. package/src/index.ts +8 -0
  169. package/src/pages/model-provider-onboarding-page.stories.tsx +284 -0
  170. package/src/pages/model-provider-onboarding-page.test.tsx +368 -0
  171. package/src/pages/model-provider-onboarding-page.tsx +290 -0
  172. package/src/routes/model-provider.tsx +20 -0
  173. package/src/state/model-provider-onboarding.test.ts +55 -0
  174. package/src/state/model-provider-onboarding.ts +34 -0
  175. package/test/fixtures/model-providers.ts +111 -0
  176. package/test/setup.ts +3 -0
  177. package/tsconfig.build.json +9 -0
  178. package/tsconfig.build.tsbuildinfo +1 -0
  179. package/tsconfig.json +3 -0
  180. package/tsconfig.test.json +8 -0
  181. package/vercel.json +8 -0
  182. package/vitest.config.ts +72 -0
@@ -0,0 +1,668 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { QueryLoadError } from '@shipfox/client-ui';
3
+ import { Button, IconButton } from '@shipfox/react-ui/button';
4
+ import { Callout } from '@shipfox/react-ui/callout';
5
+ import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from '@shipfox/react-ui/dropdown-menu';
6
+ import { EmptyState } from '@shipfox/react-ui/empty-state';
7
+ import { Icon } from '@shipfox/react-ui/icon';
8
+ import { Modal, ModalBody, ModalContent, ModalFooter, ModalHeader, ModalTitle } from '@shipfox/react-ui/modal';
9
+ import { Skeleton } from '@shipfox/react-ui/skeleton';
10
+ import { toast } from '@shipfox/react-ui/toast';
11
+ import { Tooltip, TooltipContent, TooltipTrigger } from '@shipfox/react-ui/tooltip';
12
+ import { Header, Text } from '@shipfox/react-ui/typography';
13
+ import { cn } from '@shipfox/react-ui/utils';
14
+ import { useEffect, useMemo, useRef, useState } from 'react';
15
+ import { useDeleteModelProviderConfigMutation, useModelProviderCatalogQuery, useModelProviderConfigsQuery, useSetDefaultModelProviderMutation } from '#hooks/api/model-providers.js';
16
+ import { AddCustomProviderCard } from './add-custom-provider-card.js';
17
+ import { AvailableProvidersGrid, PROVIDER_GRID_CLASS } from './available-providers-grid.js';
18
+ import { ChangeDefaultModelForm } from './change-default-model-form.js';
19
+ import { CustomModelProviderForm } from './custom-model-provider-form.js';
20
+ import { modelProviderConfigErrorToFormError } from './form-errors.js';
21
+ import { ModelProviderUsageModal } from './model-provider-usage-modal.js';
22
+ import { usageTargetFromCatalogEntry, usageTargetFromCustomConfig } from './model-provider-usage-target.js';
23
+ import { customProviderCardMatchesSearch } from './provider-search.js';
24
+ import { isSupportedCatalogEntry, toSupportedCatalogEntry } from './supported-model-provider-catalog-entry.js';
25
+ import { ModelProviderTestAndSaveForm } from './test-and-save-form.js';
26
+ const SURFACE_CLASS = 'overflow-hidden rounded-8 border border-border-neutral-base bg-background-neutral-base';
27
+ const USAGE_MODAL_OPEN_DELAY_MS = 250;
28
+ export function WorkspaceModelProvidersSection({ workspaceId }) {
29
+ const catalogQuery = useModelProviderCatalogQuery();
30
+ const configsQuery = useModelProviderConfigsQuery(workspaceId);
31
+ const [formState, setFormState] = useState(null);
32
+ const [customFormState, setCustomFormState] = useState(null);
33
+ const [modelFormState, setModelFormState] = useState(null);
34
+ const [usageTarget, setUsageTarget] = useState(null);
35
+ const [pendingUsageTarget, setPendingUsageTarget] = useState(null);
36
+ const configuredProvidersRegionRef = useRef(null);
37
+ const providers = catalogQuery.data?.providers ?? [];
38
+ const configs = configsQuery.data?.configs ?? [];
39
+ const configsLoaded = configsQuery.data !== undefined;
40
+ const defaultProviderId = configsQuery.data?.default_provider_id ?? null;
41
+ const providerById = useMemo(()=>new Map(providers.map((provider)=>[
42
+ provider.id,
43
+ provider
44
+ ])), [
45
+ providers
46
+ ]);
47
+ const configuredIds = useMemo(()=>new Set(configs.map((config)=>config.provider_id)), [
48
+ configs
49
+ ]);
50
+ const availableProviders = configsLoaded ? providers.filter((provider)=>isSupportedCatalogEntry(provider) && !configuredIds.has(provider.id)) : [];
51
+ const unsupportedProviders = providers.filter((provider)=>provider.support_status === 'unsupported');
52
+ function closeForm() {
53
+ setFormState(null);
54
+ }
55
+ function closeModelForm() {
56
+ setModelFormState(null);
57
+ }
58
+ function closeCustomForm() {
59
+ setCustomFormState(null);
60
+ }
61
+ useEffect(()=>{
62
+ if (pendingUsageTarget === null || formState !== null || customFormState !== null) return undefined;
63
+ const timer = window.setTimeout(()=>{
64
+ setUsageTarget(pendingUsageTarget);
65
+ setPendingUsageTarget(null);
66
+ }, USAGE_MODAL_OPEN_DELAY_MS);
67
+ return ()=>window.clearTimeout(timer);
68
+ }, [
69
+ customFormState,
70
+ formState,
71
+ pendingUsageTarget
72
+ ]);
73
+ return /*#__PURE__*/ _jsxs("div", {
74
+ className: "flex min-w-0 flex-col gap-32",
75
+ children: [
76
+ /*#__PURE__*/ _jsxs("section", {
77
+ ref: configuredProvidersRegionRef,
78
+ className: "flex flex-col gap-16 outline-none",
79
+ "aria-label": "Configured providers",
80
+ tabIndex: -1,
81
+ children: [
82
+ /*#__PURE__*/ _jsxs("div", {
83
+ className: "flex flex-col gap-4",
84
+ children: [
85
+ /*#__PURE__*/ _jsx(Header, {
86
+ variant: "h3",
87
+ children: "Configured providers"
88
+ }),
89
+ /*#__PURE__*/ _jsx(Text, {
90
+ size: "sm",
91
+ className: "text-foreground-neutral-muted",
92
+ children: "Workspace credentials available to agent steps."
93
+ })
94
+ ]
95
+ }),
96
+ configsQuery.isPending ? /*#__PURE__*/ _jsx(ModelProviderRowsSkeleton, {
97
+ label: "Loading configured providers"
98
+ }) : null,
99
+ configsQuery.isError && configsQuery.data === undefined ? /*#__PURE__*/ _jsx("div", {
100
+ className: cn(SURFACE_CLASS, 'px-16'),
101
+ children: /*#__PURE__*/ _jsx(QueryLoadError, {
102
+ query: configsQuery,
103
+ subject: "model provider configs"
104
+ })
105
+ }) : null,
106
+ configsQuery.data !== undefined && configs.length === 0 ? /*#__PURE__*/ _jsx("div", {
107
+ className: cn(SURFACE_CLASS, 'px-16'),
108
+ children: /*#__PURE__*/ _jsx(EmptyState, {
109
+ icon: "key2Line",
110
+ title: "No providers configured",
111
+ description: "Configure a provider below to run agent steps with workspace-managed credentials."
112
+ })
113
+ }) : null,
114
+ configs.length > 0 ? /*#__PURE__*/ _jsx("ul", {
115
+ className: cn('divide-y divide-border-neutral-base', SURFACE_CLASS),
116
+ children: configs.map((config)=>{
117
+ const entry = toSupportedCatalogEntry(providerById.get(config.provider_id));
118
+ const builtinConfig = isBuiltinModelProviderConfig(config) ? config : undefined;
119
+ const customConfig = isCustomModelProviderConfig(config) ? config : undefined;
120
+ return /*#__PURE__*/ _jsx(ConfiguredProviderRow, {
121
+ workspaceId: workspaceId,
122
+ config: config,
123
+ entry: entry,
124
+ isDefault: config.provider_id === defaultProviderId,
125
+ onEdit: ()=>{
126
+ if (entry && builtinConfig) {
127
+ setFormState({
128
+ mode: 'edit',
129
+ entry,
130
+ config: builtinConfig
131
+ });
132
+ } else if (customConfig) {
133
+ setCustomFormState({
134
+ mode: 'edit',
135
+ config: customConfig
136
+ });
137
+ }
138
+ },
139
+ onChangeDefaultModel: ()=>{
140
+ if (entry && builtinConfig) setModelFormState({
141
+ entry,
142
+ config: builtinConfig
143
+ });
144
+ },
145
+ onShowUsage: ()=>{
146
+ if (entry && builtinConfig) {
147
+ setPendingUsageTarget(null);
148
+ setUsageTarget({
149
+ target: usageTargetFromCatalogEntry(entry),
150
+ initialModel: builtinConfig.default_model,
151
+ restoreFocusToConfiguredProviders: false
152
+ });
153
+ } else if (customConfig) {
154
+ setPendingUsageTarget(null);
155
+ setUsageTarget({
156
+ target: usageTargetFromCustomConfig(customConfig),
157
+ initialModel: customConfig.default_model,
158
+ restoreFocusToConfiguredProviders: false
159
+ });
160
+ }
161
+ }
162
+ }, config.provider_id);
163
+ })
164
+ }) : null
165
+ ]
166
+ }),
167
+ /*#__PURE__*/ _jsxs("section", {
168
+ className: "flex flex-col gap-16",
169
+ "aria-label": "Available providers",
170
+ children: [
171
+ /*#__PURE__*/ _jsxs("div", {
172
+ className: "flex flex-col gap-4",
173
+ children: [
174
+ /*#__PURE__*/ _jsx(Header, {
175
+ variant: "h3",
176
+ children: "Available providers"
177
+ }),
178
+ /*#__PURE__*/ _jsx(Text, {
179
+ size: "sm",
180
+ className: "text-foreground-neutral-muted",
181
+ children: "Providers that can be configured for agent steps in this workspace."
182
+ })
183
+ ]
184
+ }),
185
+ catalogQuery.isPending || configsQuery.isPending ? /*#__PURE__*/ _jsx(ModelProviderGridSkeleton, {}) : null,
186
+ catalogQuery.isError && catalogQuery.data === undefined ? /*#__PURE__*/ _jsx("div", {
187
+ className: cn(SURFACE_CLASS, 'px-16'),
188
+ children: /*#__PURE__*/ _jsx(QueryLoadError, {
189
+ query: catalogQuery,
190
+ subject: "model provider catalog"
191
+ })
192
+ }) : null,
193
+ configsLoaded ? /*#__PURE__*/ _jsx(AvailableProvidersGrid, {
194
+ entries: availableProviders,
195
+ onSelect: (entry)=>setFormState({
196
+ mode: 'configure',
197
+ entry
198
+ }),
199
+ trailingCard: /*#__PURE__*/ _jsx(AddCustomProviderCard, {
200
+ onConfigure: ()=>setCustomFormState({
201
+ mode: 'create'
202
+ })
203
+ }),
204
+ trailingCardMatchesSearch: customProviderCardMatchesSearch
205
+ }) : null
206
+ ]
207
+ }),
208
+ /*#__PURE__*/ _jsxs("section", {
209
+ className: "flex flex-col gap-16",
210
+ "aria-label": "Unsupported providers",
211
+ children: [
212
+ /*#__PURE__*/ _jsxs("div", {
213
+ className: "flex flex-col gap-4",
214
+ children: [
215
+ /*#__PURE__*/ _jsx(Header, {
216
+ variant: "h3",
217
+ children: "Unsupported providers"
218
+ }),
219
+ /*#__PURE__*/ _jsx(Text, {
220
+ size: "sm",
221
+ className: "text-foreground-neutral-muted",
222
+ children: "Providers that cannot be configured in this workspace yet."
223
+ })
224
+ ]
225
+ }),
226
+ catalogQuery.isPending ? /*#__PURE__*/ _jsx(ModelProviderRowsSkeleton, {
227
+ label: "Loading unsupported providers"
228
+ }) : null,
229
+ unsupportedProviders.length > 0 ? /*#__PURE__*/ _jsx("ul", {
230
+ className: cn('divide-y divide-border-neutral-base', SURFACE_CLASS),
231
+ children: unsupportedProviders.map((entry)=>/*#__PURE__*/ _jsxs("li", {
232
+ className: "flex items-start gap-12 px-16 py-12 opacity-70",
233
+ children: [
234
+ /*#__PURE__*/ _jsx(Icon, {
235
+ name: "forbid2Line",
236
+ className: "mt-2 size-18 shrink-0 text-foreground-neutral-muted",
237
+ "aria-hidden": true
238
+ }),
239
+ /*#__PURE__*/ _jsxs("div", {
240
+ className: "flex min-w-0 flex-1 flex-col gap-2",
241
+ children: [
242
+ /*#__PURE__*/ _jsx(Text, {
243
+ size: "md",
244
+ bold: true,
245
+ className: "truncate",
246
+ children: entry.label
247
+ }),
248
+ /*#__PURE__*/ _jsx(Text, {
249
+ size: "sm",
250
+ className: "text-foreground-neutral-muted",
251
+ children: entry.unsupported_reason
252
+ })
253
+ ]
254
+ })
255
+ ]
256
+ }, entry.id))
257
+ }) : null
258
+ ]
259
+ }),
260
+ /*#__PURE__*/ _jsx(Modal, {
261
+ open: formState !== null,
262
+ onOpenChange: (open)=>open ? undefined : closeForm(),
263
+ children: /*#__PURE__*/ _jsxs(ModalContent, {
264
+ "aria-describedby": undefined,
265
+ children: [
266
+ /*#__PURE__*/ _jsx(ModalTitle, {
267
+ className: "sr-only",
268
+ children: modelProviderFormTitle(formState)
269
+ }),
270
+ /*#__PURE__*/ _jsx(ModalHeader, {
271
+ children: /*#__PURE__*/ _jsx(Text, {
272
+ size: "lg",
273
+ "aria-hidden": "true",
274
+ className: "overflow-ellipsis overflow-hidden whitespace-nowrap",
275
+ children: modelProviderFormTitle(formState)
276
+ })
277
+ }),
278
+ formState ? /*#__PURE__*/ _jsx(ModelProviderTestAndSaveForm, {
279
+ workspaceId: workspaceId,
280
+ entry: formState.entry,
281
+ existingConfig: formState.config,
282
+ onSaved: (savedDefaultModel)=>{
283
+ toast.success(`${formState.entry.label} saved`);
284
+ if (formState.mode === 'configure' && configs.length === 0) {
285
+ setPendingUsageTarget({
286
+ target: usageTargetFromCatalogEntry(formState.entry),
287
+ initialModel: savedDefaultModel,
288
+ restoreFocusToConfiguredProviders: true
289
+ });
290
+ }
291
+ closeForm();
292
+ }
293
+ }) : null
294
+ ]
295
+ })
296
+ }),
297
+ /*#__PURE__*/ _jsx(Modal, {
298
+ open: modelFormState !== null,
299
+ onOpenChange: (open)=>open ? undefined : closeModelForm(),
300
+ children: /*#__PURE__*/ _jsxs(ModalContent, {
301
+ "aria-describedby": undefined,
302
+ children: [
303
+ /*#__PURE__*/ _jsx(ModalTitle, {
304
+ className: "sr-only",
305
+ children: "Change default model"
306
+ }),
307
+ /*#__PURE__*/ _jsx(ModalHeader, {
308
+ children: /*#__PURE__*/ _jsxs(Text, {
309
+ size: "lg",
310
+ "aria-hidden": "true",
311
+ className: "overflow-ellipsis overflow-hidden whitespace-nowrap",
312
+ children: [
313
+ "Change default model for ",
314
+ modelFormState?.entry.label
315
+ ]
316
+ })
317
+ }),
318
+ modelFormState ? /*#__PURE__*/ _jsx(ChangeDefaultModelForm, {
319
+ workspaceId: workspaceId,
320
+ entry: modelFormState.entry,
321
+ config: modelFormState.config,
322
+ onSaved: ()=>{
323
+ toast.success(`${modelFormState.entry.label} default model saved`);
324
+ closeModelForm();
325
+ }
326
+ }) : null
327
+ ]
328
+ })
329
+ }),
330
+ /*#__PURE__*/ _jsx(ModelProviderUsageModal, {
331
+ target: usageTarget?.target ?? null,
332
+ initialModel: usageTarget?.initialModel ?? null,
333
+ workspaceDefaultHarnessId: configsQuery.data?.default_harness_id ?? null,
334
+ open: usageTarget !== null,
335
+ closeFocusTarget: usageTarget?.restoreFocusToConfiguredProviders ? configuredProvidersRegionRef.current : null,
336
+ onOpenChange: (open)=>{
337
+ if (!open) setUsageTarget(null);
338
+ }
339
+ }),
340
+ /*#__PURE__*/ _jsx(Modal, {
341
+ open: customFormState !== null,
342
+ onOpenChange: (open)=>open ? undefined : closeCustomForm(),
343
+ children: /*#__PURE__*/ _jsxs(ModalContent, {
344
+ "aria-describedby": undefined,
345
+ className: "max-h-[calc(100vh-32px)] max-w-[760px]",
346
+ children: [
347
+ /*#__PURE__*/ _jsx(ModalTitle, {
348
+ className: "sr-only",
349
+ children: customModelProviderFormTitle(customFormState)
350
+ }),
351
+ /*#__PURE__*/ _jsx(ModalHeader, {
352
+ children: /*#__PURE__*/ _jsxs("div", {
353
+ className: "flex min-w-0 flex-col gap-2",
354
+ children: [
355
+ /*#__PURE__*/ _jsx(Text, {
356
+ size: "lg",
357
+ "aria-hidden": "true",
358
+ className: "truncate",
359
+ children: customModelProviderFormTitle(customFormState)
360
+ }),
361
+ /*#__PURE__*/ _jsx(Text, {
362
+ size: "sm",
363
+ className: "text-foreground-neutral-muted",
364
+ children: "Connect an OpenAI-, Anthropic-, or Gemini-compatible endpoint."
365
+ })
366
+ ]
367
+ })
368
+ }),
369
+ customFormState ? /*#__PURE__*/ _jsx(CustomModelProviderForm, {
370
+ workspaceId: workspaceId,
371
+ existingConfig: customFormState.mode === 'edit' ? customFormState.config : undefined,
372
+ onSaved: ()=>{
373
+ toast.success(customFormState.mode === 'edit' ? `${customFormState.config.display_name} saved` : 'Custom provider saved');
374
+ closeCustomForm();
375
+ }
376
+ }) : null
377
+ ]
378
+ })
379
+ })
380
+ ]
381
+ });
382
+ }
383
+ function modelProviderFormTitle(formState) {
384
+ if (formState === null) return '';
385
+ if (formState.mode === 'edit') return `Edit credentials for ${formState.entry.label}`;
386
+ return `Configure ${formState.entry.label}`;
387
+ }
388
+ function customModelProviderFormTitle(formState) {
389
+ if (formState === null) return '';
390
+ if (formState.mode === 'edit') return `Edit ${formState.config.display_name}`;
391
+ return 'Add custom provider';
392
+ }
393
+ function isBuiltinModelProviderConfig(config) {
394
+ return config.kind === 'builtin';
395
+ }
396
+ function isCustomModelProviderConfig(config) {
397
+ return config.kind === 'custom';
398
+ }
399
+ function ConfiguredProviderRow({ workspaceId, config, entry, isDefault, onEdit, onChangeDefaultModel, onShowUsage }) {
400
+ const setDefault = useSetDefaultModelProviderMutation();
401
+ const deleteConfig = useDeleteModelProviderConfigMutation();
402
+ const [defaultError, setDefaultError] = useState();
403
+ const [deleteOpen, setDeleteOpen] = useState(false);
404
+ const [deleteError, setDeleteError] = useState();
405
+ const customConfig = isCustomModelProviderConfig(config) ? config : undefined;
406
+ const label = customConfig?.display_name ?? entry?.label ?? config.provider_id;
407
+ const canUse = entry !== undefined || customConfig !== undefined;
408
+ const canEdit = entry !== undefined || customConfig !== undefined;
409
+ const isBuiltinConfig = isBuiltinModelProviderConfig(config);
410
+ async function handleSetDefault() {
411
+ setDefaultError(undefined);
412
+ try {
413
+ await setDefault.mutateAsync({
414
+ workspaceId,
415
+ body: {
416
+ provider_id: config.provider_id
417
+ }
418
+ });
419
+ toast.success(`${label} is now the default provider`);
420
+ } catch (error) {
421
+ const mapped = modelProviderConfigErrorToFormError(error);
422
+ setDefaultError(mapped.message);
423
+ }
424
+ }
425
+ async function handleDelete() {
426
+ setDeleteError(undefined);
427
+ try {
428
+ await deleteConfig.mutateAsync({
429
+ workspaceId,
430
+ providerId: config.provider_id
431
+ });
432
+ toast.success(`${label} deleted`);
433
+ setDeleteOpen(false);
434
+ } catch (error) {
435
+ const mapped = modelProviderConfigErrorToFormError(error);
436
+ setDeleteError(mapped.message);
437
+ }
438
+ }
439
+ function handleDeleteOpenChange(nextOpen) {
440
+ setDeleteOpen(nextOpen);
441
+ if (nextOpen) {
442
+ setDeleteError(undefined);
443
+ deleteConfig.reset();
444
+ }
445
+ }
446
+ return /*#__PURE__*/ _jsxs("li", {
447
+ className: "flex flex-col gap-10 px-16 py-12 transition-colors hover:bg-background-components-hover",
448
+ children: [
449
+ /*#__PURE__*/ _jsxs("div", {
450
+ className: "flex items-center justify-between gap-12",
451
+ children: [
452
+ /*#__PURE__*/ _jsxs("div", {
453
+ className: "flex min-w-0 items-center gap-8",
454
+ children: [
455
+ isDefault ? /*#__PURE__*/ _jsxs(_Fragment, {
456
+ children: [
457
+ /*#__PURE__*/ _jsxs(Tooltip, {
458
+ children: [
459
+ /*#__PURE__*/ _jsx(TooltipTrigger, {
460
+ asChild: true,
461
+ children: /*#__PURE__*/ _jsx("span", {
462
+ className: "inline-flex size-16 shrink-0 items-center justify-center",
463
+ children: /*#__PURE__*/ _jsx(Icon, {
464
+ name: "starLine",
465
+ className: "size-16 text-foreground-neutral-muted",
466
+ "aria-hidden": true
467
+ })
468
+ })
469
+ }),
470
+ /*#__PURE__*/ _jsx(TooltipContent, {
471
+ children: "Default provider"
472
+ })
473
+ ]
474
+ }),
475
+ /*#__PURE__*/ _jsx("span", {
476
+ className: "sr-only",
477
+ children: "Default provider"
478
+ })
479
+ ]
480
+ }) : null,
481
+ /*#__PURE__*/ _jsx(Text, {
482
+ size: "md",
483
+ bold: true,
484
+ className: "truncate",
485
+ children: label
486
+ })
487
+ ]
488
+ }),
489
+ /*#__PURE__*/ _jsxs(DropdownMenu, {
490
+ children: [
491
+ /*#__PURE__*/ _jsx(DropdownMenuTrigger, {
492
+ asChild: true,
493
+ children: /*#__PURE__*/ _jsx(IconButton, {
494
+ size: "sm",
495
+ variant: "transparent",
496
+ icon: "more2Line",
497
+ "aria-label": `Open ${label} provider actions`
498
+ })
499
+ }),
500
+ /*#__PURE__*/ _jsxs(DropdownMenuContent, {
501
+ align: "end",
502
+ children: [
503
+ !isDefault ? /*#__PURE__*/ _jsx(DropdownMenuItem, {
504
+ icon: "starLine",
505
+ disabled: setDefault.isPending || !entry && !customConfig,
506
+ onSelect: ()=>{
507
+ void handleSetDefault();
508
+ },
509
+ children: "Set as default"
510
+ }) : null,
511
+ isBuiltinConfig ? /*#__PURE__*/ _jsx(DropdownMenuItem, {
512
+ icon: "settings3Line",
513
+ disabled: !entry,
514
+ onSelect: onChangeDefaultModel,
515
+ children: "Change default model"
516
+ }) : null,
517
+ /*#__PURE__*/ _jsx(DropdownMenuItem, {
518
+ icon: "bookOpenLine",
519
+ disabled: !canUse,
520
+ onSelect: onShowUsage,
521
+ children: "View workflow example"
522
+ }),
523
+ /*#__PURE__*/ _jsx(DropdownMenuItem, {
524
+ icon: "editLine",
525
+ disabled: !canEdit,
526
+ onSelect: onEdit,
527
+ children: customConfig ? 'Edit' : 'Edit credentials'
528
+ }),
529
+ /*#__PURE__*/ _jsx(DropdownMenuItem, {
530
+ icon: "deleteBinLine",
531
+ onSelect: ()=>{
532
+ setDeleteOpen(true);
533
+ },
534
+ children: "Delete"
535
+ })
536
+ ]
537
+ })
538
+ ]
539
+ })
540
+ ]
541
+ }),
542
+ defaultError ? /*#__PURE__*/ _jsx(Callout, {
543
+ role: "alert",
544
+ type: "error",
545
+ children: /*#__PURE__*/ _jsx(Text, {
546
+ size: "sm",
547
+ children: defaultError
548
+ })
549
+ }) : null,
550
+ /*#__PURE__*/ _jsx(DeleteModelProviderDialog, {
551
+ open: deleteOpen,
552
+ onOpenChange: handleDeleteOpenChange,
553
+ label: label,
554
+ errorMessage: deleteError,
555
+ isLoading: deleteConfig.isPending,
556
+ onDelete: handleDelete
557
+ })
558
+ ]
559
+ });
560
+ }
561
+ function DeleteModelProviderDialog({ open, onOpenChange, label, errorMessage, isLoading, onDelete }) {
562
+ return /*#__PURE__*/ _jsx(Modal, {
563
+ open: open,
564
+ onOpenChange: onOpenChange,
565
+ children: /*#__PURE__*/ _jsxs(ModalContent, {
566
+ "aria-describedby": undefined,
567
+ className: "max-w-[420px]",
568
+ children: [
569
+ /*#__PURE__*/ _jsx(ModalTitle, {
570
+ className: "sr-only",
571
+ children: "Delete model provider"
572
+ }),
573
+ /*#__PURE__*/ _jsx(ModalHeader, {
574
+ title: "Delete model provider"
575
+ }),
576
+ /*#__PURE__*/ _jsxs(ModalBody, {
577
+ className: "gap-16",
578
+ children: [
579
+ /*#__PURE__*/ _jsxs(Text, {
580
+ size: "sm",
581
+ className: "text-foreground-neutral-muted",
582
+ children: [
583
+ "Delete ",
584
+ label,
585
+ " credentials from this workspace? Agent jobs cannot use this provider until it is configured again."
586
+ ]
587
+ }),
588
+ errorMessage ? /*#__PURE__*/ _jsx(Callout, {
589
+ role: "alert",
590
+ type: "error",
591
+ children: /*#__PURE__*/ _jsx(Text, {
592
+ size: "sm",
593
+ children: errorMessage
594
+ })
595
+ }) : null
596
+ ]
597
+ }),
598
+ /*#__PURE__*/ _jsxs(ModalFooter, {
599
+ children: [
600
+ /*#__PURE__*/ _jsx(Button, {
601
+ size: "sm",
602
+ variant: "secondary",
603
+ onClick: ()=>onOpenChange(false),
604
+ children: "Cancel"
605
+ }),
606
+ /*#__PURE__*/ _jsx(Button, {
607
+ size: "sm",
608
+ variant: "danger",
609
+ isLoading: isLoading,
610
+ onClick: onDelete,
611
+ children: "Delete"
612
+ })
613
+ ]
614
+ })
615
+ ]
616
+ })
617
+ });
618
+ }
619
+ function ModelProviderRowsSkeleton({ label }) {
620
+ return /*#__PURE__*/ _jsx("ul", {
621
+ role: "status",
622
+ "aria-label": label,
623
+ className: cn('divide-y divide-border-neutral-base', SURFACE_CLASS),
624
+ children: [
625
+ 0,
626
+ 1,
627
+ 2
628
+ ].map((row)=>/*#__PURE__*/ _jsxs("li", {
629
+ className: "flex items-center gap-12 px-16 py-12",
630
+ children: [
631
+ /*#__PURE__*/ _jsx(Skeleton, {
632
+ className: "size-32 shrink-0"
633
+ }),
634
+ /*#__PURE__*/ _jsxs("div", {
635
+ className: "flex min-w-0 flex-1 flex-col gap-6",
636
+ children: [
637
+ /*#__PURE__*/ _jsx(Skeleton, {
638
+ className: "h-16 w-120"
639
+ }),
640
+ /*#__PURE__*/ _jsx(Skeleton, {
641
+ className: "h-14 w-180"
642
+ })
643
+ ]
644
+ }),
645
+ /*#__PURE__*/ _jsx(Skeleton, {
646
+ className: "h-28 w-96 shrink-0"
647
+ })
648
+ ]
649
+ }, row))
650
+ });
651
+ }
652
+ function ModelProviderGridSkeleton() {
653
+ return /*#__PURE__*/ _jsx("div", {
654
+ role: "status",
655
+ "aria-label": "Loading available providers",
656
+ className: PROVIDER_GRID_CLASS,
657
+ children: [
658
+ 0,
659
+ 1,
660
+ 2,
661
+ 3
662
+ ].map((card)=>/*#__PURE__*/ _jsx(Skeleton, {
663
+ className: "h-136 w-full"
664
+ }, card))
665
+ });
666
+ }
667
+
668
+ //# sourceMappingURL=model-providers-section.js.map