@shipfox/client-agent 5.0.0 → 6.0.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.
- package/.turbo/turbo-build.log +1 -1
- package/CHANGELOG.md +45 -0
- package/dist/components/available-provider-card.d.ts +2 -2
- package/dist/components/available-provider-card.d.ts.map +1 -1
- package/dist/components/available-provider-card.js.map +1 -1
- package/dist/components/available-providers-grid.d.ts +2 -2
- package/dist/components/available-providers-grid.d.ts.map +1 -1
- package/dist/components/available-providers-grid.js +1 -1
- package/dist/components/available-providers-grid.js.map +1 -1
- package/dist/components/change-default-model-form.d.ts +3 -4
- package/dist/components/change-default-model-form.d.ts.map +1 -1
- package/dist/components/change-default-model-form.js +2 -4
- package/dist/components/change-default-model-form.js.map +1 -1
- package/dist/components/custom-model-provider-api-options.d.ts +3 -3
- package/dist/components/custom-model-provider-api-options.d.ts.map +1 -1
- package/dist/components/custom-model-provider-api-options.js.map +1 -1
- package/dist/components/custom-model-provider-form.d.ts +2 -2
- package/dist/components/custom-model-provider-form.d.ts.map +1 -1
- package/dist/components/custom-model-provider-form.js +12 -10
- package/dist/components/custom-model-provider-form.js.map +1 -1
- package/dist/components/custom-model-provider-form.stories.d.ts.map +1 -1
- package/dist/components/custom-model-provider-form.stories.js +11 -11
- package/dist/components/custom-model-provider-form.stories.js.map +1 -1
- package/dist/components/custom-model-provider-payload.d.ts +9 -9
- package/dist/components/custom-model-provider-payload.d.ts.map +1 -1
- package/dist/components/custom-model-provider-payload.js +42 -26
- package/dist/components/custom-model-provider-payload.js.map +1 -1
- package/dist/components/custom-model-provider-slug.d.ts +1 -1
- package/dist/components/custom-model-provider-slug.d.ts.map +1 -1
- package/dist/components/custom-model-provider-slug.js +3 -3
- package/dist/components/custom-model-provider-slug.js.map +1 -1
- package/dist/components/default-model-field.d.ts +2 -2
- package/dist/components/default-model-field.d.ts.map +1 -1
- package/dist/components/default-model-field.js +1 -1
- package/dist/components/default-model-field.js.map +1 -1
- package/dist/components/harness-availability.d.ts +4 -7
- package/dist/components/harness-availability.d.ts.map +1 -1
- package/dist/components/harness-availability.js +2 -11
- package/dist/components/harness-availability.js.map +1 -1
- package/dist/components/harnesses-section.d.ts.map +1 -1
- package/dist/components/harnesses-section.js +4 -6
- package/dist/components/harnesses-section.js.map +1 -1
- package/dist/components/model-provider-usage-modal.d.ts +2 -2
- package/dist/components/model-provider-usage-modal.d.ts.map +1 -1
- package/dist/components/model-provider-usage-modal.js +4 -4
- package/dist/components/model-provider-usage-modal.js.map +1 -1
- package/dist/components/model-provider-usage-modal.stories.d.ts.map +1 -1
- package/dist/components/model-provider-usage-modal.stories.js +14 -16
- package/dist/components/model-provider-usage-modal.stories.js.map +1 -1
- package/dist/components/model-provider-usage-target.d.ts +5 -5
- package/dist/components/model-provider-usage-target.d.ts.map +1 -1
- package/dist/components/model-provider-usage-target.js +10 -8
- package/dist/components/model-provider-usage-target.js.map +1 -1
- package/dist/components/model-providers-section.d.ts.map +1 -1
- package/dist/components/model-providers-section.js +112 -94
- package/dist/components/model-providers-section.js.map +1 -1
- package/dist/components/model-providers-section.stories.js +8 -4
- package/dist/components/model-providers-section.stories.js.map +1 -1
- package/dist/components/test-and-save-form.d.ts +3 -3
- package/dist/components/test-and-save-form.d.ts.map +1 -1
- package/dist/components/test-and-save-form.js +7 -7
- package/dist/components/test-and-save-form.js.map +1 -1
- package/dist/core/harness-policy.d.ts +11 -0
- package/dist/core/harness-policy.d.ts.map +1 -0
- package/dist/core/harness-policy.js +71 -0
- package/dist/core/harness-policy.js.map +1 -0
- package/dist/core/model-provider-management-reducer.d.ts +51 -0
- package/dist/core/model-provider-management-reducer.d.ts.map +1 -0
- package/dist/core/model-provider-management-reducer.js +43 -0
- package/dist/core/model-provider-management-reducer.js.map +1 -0
- package/dist/core/models.d.ts +120 -0
- package/dist/core/models.d.ts.map +1 -0
- package/dist/core/models.js +3 -0
- package/dist/core/models.js.map +1 -0
- package/dist/core/onboarding-reducer.d.ts +36 -0
- package/dist/core/onboarding-reducer.d.ts.map +1 -0
- package/dist/core/onboarding-reducer.js +54 -0
- package/dist/core/onboarding-reducer.js.map +1 -0
- package/dist/core/provider-policy.d.ts +9 -0
- package/dist/core/provider-policy.d.ts.map +1 -0
- package/dist/core/provider-policy.js +37 -0
- package/dist/core/provider-policy.js.map +1 -0
- package/dist/feature.d.ts +7 -0
- package/dist/feature.d.ts.map +1 -1
- package/dist/feature.js +10 -9
- package/dist/feature.js.map +1 -1
- package/dist/hooks/api/model-provider-command-mapper.d.ts +11 -0
- package/dist/hooks/api/model-provider-command-mapper.d.ts.map +1 -0
- package/dist/hooks/api/model-provider-command-mapper.js +144 -0
- package/dist/hooks/api/model-provider-command-mapper.js.map +1 -0
- package/dist/hooks/api/model-provider-mapper.d.ts +11 -0
- package/dist/hooks/api/model-provider-mapper.d.ts.map +1 -0
- package/dist/hooks/api/model-provider-mapper.js +94 -0
- package/dist/hooks/api/model-provider-mapper.js.map +1 -0
- package/dist/hooks/api/model-providers.d.ts +60 -295
- package/dist/hooks/api/model-providers.d.ts.map +1 -1
- package/dist/hooks/api/model-providers.js +51 -37
- package/dist/hooks/api/model-providers.js.map +1 -1
- package/dist/pages/model-provider-onboarding-page.d.ts.map +1 -1
- package/dist/pages/model-provider-onboarding-page.js +57 -45
- package/dist/pages/model-provider-onboarding-page.js.map +1 -1
- package/dist/routes/inputs.d.ts +4 -0
- package/dist/routes/inputs.d.ts.map +1 -0
- package/dist/routes/inputs.js +12 -0
- package/dist/routes/inputs.js.map +1 -0
- package/dist/routes/model-provider.d.ts.map +1 -1
- package/dist/routes/model-provider.js +4 -5
- package/dist/routes/model-provider.js.map +1 -1
- package/dist/tsconfig.test.tsbuildinfo +1 -1
- package/package.json +9 -10
- package/src/components/available-provider-card.tsx +2 -2
- package/src/components/available-providers-grid.tsx +3 -3
- package/src/components/change-default-model-form.tsx +5 -6
- package/src/components/custom-model-provider-api-options.ts +3 -3
- package/src/components/custom-model-provider-form.stories.tsx +13 -13
- package/src/components/custom-model-provider-form.tsx +19 -12
- package/src/components/custom-model-provider-payload.test.ts +22 -22
- package/src/components/custom-model-provider-payload.ts +69 -54
- package/src/components/custom-model-provider-slug.test.ts +1 -1
- package/src/components/custom-model-provider-slug.ts +6 -3
- package/src/components/default-model-field.tsx +5 -5
- package/src/components/harness-availability.test.ts +13 -10
- package/src/components/harness-availability.ts +4 -28
- package/src/components/harnesses-section.tsx +7 -11
- package/src/components/model-provider-usage-modal.stories.tsx +18 -23
- package/src/components/model-provider-usage-modal.test.tsx +35 -7
- package/src/components/model-provider-usage-modal.tsx +12 -16
- package/src/components/model-provider-usage-target.ts +15 -18
- package/src/components/model-providers-section.stories.tsx +4 -1
- package/src/components/model-providers-section.tsx +112 -125
- package/src/components/test-and-save-form.tsx +13 -17
- package/src/core/harness-policy.test.ts +15 -0
- package/src/core/harness-policy.ts +83 -0
- package/src/core/model-provider-management-reducer.test.ts +59 -0
- package/src/core/model-provider-management-reducer.ts +72 -0
- package/src/core/models.ts +143 -0
- package/src/core/onboarding-reducer.test.ts +44 -0
- package/src/core/onboarding-reducer.ts +64 -0
- package/src/core/provider-policy.ts +53 -0
- package/src/feature.ts +12 -10
- package/src/hooks/api/model-provider-command-mapper.ts +122 -0
- package/src/hooks/api/model-provider-mapper.test.ts +27 -0
- package/src/hooks/api/model-provider-mapper.ts +120 -0
- package/src/hooks/api/model-providers.test.ts +15 -15
- package/src/hooks/api/model-providers.ts +146 -81
- package/src/pages/model-provider-onboarding-page.tsx +62 -56
- package/src/routes/inputs.test.ts +11 -0
- package/src/routes/inputs.ts +9 -0
- package/src/routes/model-provider.tsx +4 -3
- package/test/fixtures/model-providers.ts +1 -1
- package/tsconfig.build.tsbuildinfo +1 -1
- package/vercel.json +5 -0
- package/dist/components/provider-search.d.ts +0 -4
- package/dist/components/provider-search.d.ts.map +0 -1
- package/dist/components/provider-search.js +0 -13
- package/dist/components/provider-search.js.map +0 -1
- package/dist/components/supported-model-provider-catalog-entry.d.ts +0 -8
- package/dist/components/supported-model-provider-catalog-entry.d.ts.map +0 -1
- package/dist/components/supported-model-provider-catalog-entry.js +0 -9
- package/dist/components/supported-model-provider-catalog-entry.js.map +0 -1
- package/src/components/provider-search.test.ts +0 -45
- package/src/components/provider-search.ts +0 -18
- package/src/components/supported-model-provider-catalog-entry.ts +0 -19
|
@@ -1,9 +1,3 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
CustomModelProviderConfigDto,
|
|
3
|
-
ModelProviderCatalogEntryDto,
|
|
4
|
-
ModelProviderConfigDto,
|
|
5
|
-
ModelProviderConfigResponseDto,
|
|
6
|
-
} from '@shipfox/api-agent-dto';
|
|
7
1
|
import {QueryLoadError} from '@shipfox/client-ui';
|
|
8
2
|
import {Button, IconButton} from '@shipfox/react-ui/button';
|
|
9
3
|
import {Callout} from '@shipfox/react-ui/callout';
|
|
@@ -28,7 +22,23 @@ import {toast} from '@shipfox/react-ui/toast';
|
|
|
28
22
|
import {Tooltip, TooltipContent, TooltipTrigger} from '@shipfox/react-ui/tooltip';
|
|
29
23
|
import {Header, Text} from '@shipfox/react-ui/typography';
|
|
30
24
|
import {cn} from '@shipfox/react-ui/utils';
|
|
31
|
-
import {useEffect, useMemo, useRef, useState} from 'react';
|
|
25
|
+
import {useEffect, useMemo, useReducer, useRef, useState} from 'react';
|
|
26
|
+
import {
|
|
27
|
+
type ManagementModal,
|
|
28
|
+
managementModalReducer,
|
|
29
|
+
} from '#core/model-provider-management-reducer.js';
|
|
30
|
+
import type {
|
|
31
|
+
BuiltinProviderConfig,
|
|
32
|
+
CustomProviderConfig,
|
|
33
|
+
ProviderCatalogEntry,
|
|
34
|
+
ProviderConfig,
|
|
35
|
+
SupportedProvider,
|
|
36
|
+
} from '#core/models.js';
|
|
37
|
+
import {
|
|
38
|
+
customProviderCardMatchesSearch,
|
|
39
|
+
isSupportedProvider,
|
|
40
|
+
availableProviders as selectAvailableProviders,
|
|
41
|
+
} from '#core/provider-policy.js';
|
|
32
42
|
import {
|
|
33
43
|
useDeleteModelProviderConfigMutation,
|
|
34
44
|
useModelProviderCatalogQuery,
|
|
@@ -46,24 +56,11 @@ import {
|
|
|
46
56
|
usageTargetFromCatalogEntry,
|
|
47
57
|
usageTargetFromCustomConfig,
|
|
48
58
|
} from './model-provider-usage-target.js';
|
|
49
|
-
import {customProviderCardMatchesSearch} from './provider-search.js';
|
|
50
|
-
import {
|
|
51
|
-
isSupportedCatalogEntry,
|
|
52
|
-
type SupportedModelProviderCatalogEntry,
|
|
53
|
-
toSupportedCatalogEntry,
|
|
54
|
-
} from './supported-model-provider-catalog-entry.js';
|
|
55
59
|
import {ModelProviderTestAndSaveForm} from './test-and-save-form.js';
|
|
56
60
|
|
|
57
61
|
const SURFACE_CLASS =
|
|
58
62
|
'overflow-hidden rounded-8 border border-border-neutral-base bg-background-neutral-base';
|
|
59
63
|
|
|
60
|
-
type ModelProviderFormState =
|
|
61
|
-
| {mode: 'configure'; entry: SupportedModelProviderCatalogEntry; config?: undefined}
|
|
62
|
-
| {mode: 'edit'; entry: SupportedModelProviderCatalogEntry; config: ModelProviderConfigDto};
|
|
63
|
-
type CustomModelProviderFormState =
|
|
64
|
-
| {mode: 'create'}
|
|
65
|
-
| {mode: 'edit'; config: CustomModelProviderConfigDto};
|
|
66
|
-
type ModelFormState = {entry: SupportedModelProviderCatalogEntry; config: ModelProviderConfigDto};
|
|
67
64
|
type UsageTarget = {
|
|
68
65
|
target: ModelProviderUsageTarget;
|
|
69
66
|
initialModel: string | null;
|
|
@@ -75,61 +72,45 @@ const USAGE_MODAL_OPEN_DELAY_MS = 250;
|
|
|
75
72
|
export function WorkspaceModelProvidersSection({workspaceId}: {workspaceId: string}) {
|
|
76
73
|
const catalogQuery = useModelProviderCatalogQuery();
|
|
77
74
|
const configsQuery = useModelProviderConfigsQuery(workspaceId);
|
|
78
|
-
const [
|
|
79
|
-
const [customFormState, setCustomFormState] = useState<CustomModelProviderFormState | null>(null);
|
|
80
|
-
const [modelFormState, setModelFormState] = useState<ModelFormState | null>(null);
|
|
81
|
-
const [usageTarget, setUsageTarget] = useState<UsageTarget | null>(null);
|
|
75
|
+
const [modal, dispatchModal] = useReducer(managementModalReducer, {kind: 'closed'});
|
|
82
76
|
const [pendingUsageTarget, setPendingUsageTarget] = useState<UsageTarget | null>(null);
|
|
83
77
|
const configuredProvidersRegionRef = useRef<HTMLElement | null>(null);
|
|
84
78
|
|
|
85
79
|
const providers = catalogQuery.data?.providers ?? [];
|
|
86
80
|
const configs = configsQuery.data?.configs ?? [];
|
|
87
81
|
const configsLoaded = configsQuery.data !== undefined;
|
|
88
|
-
const defaultProviderId = configsQuery.data?.
|
|
82
|
+
const defaultProviderId = configsQuery.data?.defaultProviderId ?? null;
|
|
89
83
|
const providerById = useMemo(
|
|
90
84
|
() =>
|
|
91
|
-
new Map<string,
|
|
92
|
-
providers.map((provider) => [provider.id, provider]),
|
|
93
|
-
),
|
|
85
|
+
new Map<string, ProviderCatalogEntry>(providers.map((provider) => [provider.id, provider])),
|
|
94
86
|
[providers],
|
|
95
87
|
);
|
|
96
|
-
const
|
|
97
|
-
|
|
98
|
-
[configs],
|
|
99
|
-
);
|
|
100
|
-
const availableProviders = configsLoaded
|
|
101
|
-
? providers.filter(
|
|
102
|
-
(provider): provider is SupportedModelProviderCatalogEntry =>
|
|
103
|
-
isSupportedCatalogEntry(provider) && !configuredIds.has(provider.id),
|
|
104
|
-
)
|
|
105
|
-
: [];
|
|
106
|
-
const unsupportedProviders = providers.filter(
|
|
107
|
-
(provider) => provider.support_status === 'unsupported',
|
|
108
|
-
);
|
|
109
|
-
|
|
110
|
-
function closeForm() {
|
|
111
|
-
setFormState(null);
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
function closeModelForm() {
|
|
115
|
-
setModelFormState(null);
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
function closeCustomForm() {
|
|
119
|
-
setCustomFormState(null);
|
|
120
|
-
}
|
|
88
|
+
const availableProviders = configsLoaded ? selectAvailableProviders(providers, configs) : [];
|
|
89
|
+
const unsupportedProviders = providers.filter((provider) => !isSupportedProvider(provider));
|
|
121
90
|
|
|
122
91
|
useEffect(() => {
|
|
123
|
-
if (pendingUsageTarget === null ||
|
|
124
|
-
return undefined;
|
|
92
|
+
if (pendingUsageTarget === null || modal.kind !== 'closed') return undefined;
|
|
125
93
|
|
|
126
94
|
const timer = window.setTimeout(() => {
|
|
127
|
-
|
|
95
|
+
dispatchModal({
|
|
96
|
+
type: 'show-usage',
|
|
97
|
+
providerId: pendingUsageTarget.target.id,
|
|
98
|
+
initialModel: pendingUsageTarget.initialModel,
|
|
99
|
+
restoreFocusToConfiguredProviders: pendingUsageTarget.restoreFocusToConfiguredProviders,
|
|
100
|
+
});
|
|
128
101
|
setPendingUsageTarget(null);
|
|
129
102
|
}, USAGE_MODAL_OPEN_DELAY_MS);
|
|
130
103
|
|
|
131
104
|
return () => window.clearTimeout(timer);
|
|
132
|
-
}, [
|
|
105
|
+
}, [modal.kind, pendingUsageTarget]);
|
|
106
|
+
|
|
107
|
+
const usageTarget = useMemo(() => {
|
|
108
|
+
if (modal.kind !== 'show-usage') return null;
|
|
109
|
+
const config = configs.find((item) => item.providerId === modal.providerId);
|
|
110
|
+
if (config?.kind === 'custom') return usageTargetFromCustomConfig(config);
|
|
111
|
+
const entry = providerById.get(modal.providerId);
|
|
112
|
+
return entry && isSupportedProvider(entry) ? usageTargetFromCatalogEntry(entry) : null;
|
|
113
|
+
}, [configs, modal, providerById]);
|
|
133
114
|
|
|
134
115
|
return (
|
|
135
116
|
<div className="flex min-w-0 flex-col gap-32">
|
|
@@ -169,39 +150,48 @@ export function WorkspaceModelProvidersSection({workspaceId}: {workspaceId: stri
|
|
|
169
150
|
{configs.length > 0 ? (
|
|
170
151
|
<ul className={cn('divide-y divide-border-neutral-base', SURFACE_CLASS)}>
|
|
171
152
|
{configs.map((config) => {
|
|
172
|
-
const
|
|
153
|
+
const catalogEntry = providerById.get(config.providerId);
|
|
154
|
+
const entry =
|
|
155
|
+
catalogEntry && isSupportedProvider(catalogEntry) ? catalogEntry : undefined;
|
|
173
156
|
const builtinConfig = isBuiltinModelProviderConfig(config) ? config : undefined;
|
|
174
157
|
const customConfig = isCustomModelProviderConfig(config) ? config : undefined;
|
|
175
158
|
return (
|
|
176
159
|
<ConfiguredProviderRow
|
|
177
|
-
key={config.
|
|
160
|
+
key={config.providerId}
|
|
178
161
|
workspaceId={workspaceId}
|
|
179
162
|
config={config}
|
|
180
163
|
entry={entry}
|
|
181
|
-
isDefault={config.
|
|
164
|
+
isDefault={config.providerId === defaultProviderId}
|
|
182
165
|
onEdit={() => {
|
|
183
166
|
if (entry && builtinConfig) {
|
|
184
|
-
|
|
167
|
+
dispatchModal({type: 'edit-builtin', provider: entry, config: builtinConfig});
|
|
185
168
|
} else if (customConfig) {
|
|
186
|
-
|
|
169
|
+
dispatchModal({type: 'edit-custom', config: customConfig});
|
|
187
170
|
}
|
|
188
171
|
}}
|
|
189
172
|
onChangeDefaultModel={() => {
|
|
190
|
-
if (entry && builtinConfig)
|
|
173
|
+
if (entry && builtinConfig)
|
|
174
|
+
dispatchModal({
|
|
175
|
+
type: 'change-default-model',
|
|
176
|
+
provider: entry,
|
|
177
|
+
config: builtinConfig,
|
|
178
|
+
});
|
|
191
179
|
}}
|
|
192
180
|
onShowUsage={() => {
|
|
193
181
|
if (entry && builtinConfig) {
|
|
194
182
|
setPendingUsageTarget(null);
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
183
|
+
dispatchModal({
|
|
184
|
+
type: 'show-usage',
|
|
185
|
+
providerId: entry.id,
|
|
186
|
+
initialModel: builtinConfig.defaultModel,
|
|
198
187
|
restoreFocusToConfiguredProviders: false,
|
|
199
188
|
});
|
|
200
189
|
} else if (customConfig) {
|
|
201
190
|
setPendingUsageTarget(null);
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
191
|
+
dispatchModal({
|
|
192
|
+
type: 'show-usage',
|
|
193
|
+
providerId: customConfig.providerId,
|
|
194
|
+
initialModel: customConfig.defaultModel,
|
|
205
195
|
restoreFocusToConfiguredProviders: false,
|
|
206
196
|
});
|
|
207
197
|
}
|
|
@@ -232,9 +222,9 @@ export function WorkspaceModelProvidersSection({workspaceId}: {workspaceId: stri
|
|
|
232
222
|
{configsLoaded ? (
|
|
233
223
|
<AvailableProvidersGrid
|
|
234
224
|
entries={availableProviders}
|
|
235
|
-
onSelect={(entry) =>
|
|
225
|
+
onSelect={(entry) => dispatchModal({type: 'configure-builtin', provider: entry})}
|
|
236
226
|
trailingCard={
|
|
237
|
-
<AddCustomProviderCard onConfigure={() =>
|
|
227
|
+
<AddCustomProviderCard onConfigure={() => dispatchModal({type: 'create-custom'})} />
|
|
238
228
|
}
|
|
239
229
|
trailingCardMatchesSearch={customProviderCardMatchesSearch}
|
|
240
230
|
/>
|
|
@@ -267,7 +257,7 @@ export function WorkspaceModelProvidersSection({workspaceId}: {workspaceId: stri
|
|
|
267
257
|
{entry.label}
|
|
268
258
|
</Text>
|
|
269
259
|
<Text size="sm" className="text-foreground-neutral-muted">
|
|
270
|
-
{entry.
|
|
260
|
+
{entry.unsupportedReason}
|
|
271
261
|
</Text>
|
|
272
262
|
</div>
|
|
273
263
|
</li>
|
|
@@ -276,33 +266,36 @@ export function WorkspaceModelProvidersSection({workspaceId}: {workspaceId: stri
|
|
|
276
266
|
) : null}
|
|
277
267
|
</section>
|
|
278
268
|
|
|
279
|
-
<Modal
|
|
269
|
+
<Modal
|
|
270
|
+
open={modal.kind === 'configure-builtin' || modal.kind === 'edit-builtin'}
|
|
271
|
+
onOpenChange={(open) => (open ? undefined : dispatchModal({type: 'close'}))}
|
|
272
|
+
>
|
|
280
273
|
<ModalContent aria-describedby={undefined}>
|
|
281
|
-
<ModalTitle className="sr-only">{modelProviderFormTitle(
|
|
274
|
+
<ModalTitle className="sr-only">{modelProviderFormTitle(modal)}</ModalTitle>
|
|
282
275
|
<ModalHeader>
|
|
283
276
|
<Text
|
|
284
277
|
size="lg"
|
|
285
278
|
aria-hidden="true"
|
|
286
279
|
className="overflow-ellipsis overflow-hidden whitespace-nowrap"
|
|
287
280
|
>
|
|
288
|
-
{modelProviderFormTitle(
|
|
281
|
+
{modelProviderFormTitle(modal)}
|
|
289
282
|
</Text>
|
|
290
283
|
</ModalHeader>
|
|
291
|
-
{
|
|
284
|
+
{modal.kind === 'configure-builtin' || modal.kind === 'edit-builtin' ? (
|
|
292
285
|
<ModelProviderTestAndSaveForm
|
|
293
286
|
workspaceId={workspaceId}
|
|
294
|
-
entry={
|
|
295
|
-
existingConfig={
|
|
287
|
+
entry={modal.provider}
|
|
288
|
+
existingConfig={modal.kind === 'edit-builtin' ? modal.config : undefined}
|
|
296
289
|
onSaved={(savedDefaultModel) => {
|
|
297
|
-
toast.success(`${
|
|
298
|
-
if (
|
|
290
|
+
toast.success(`${modal.provider.label} saved`);
|
|
291
|
+
if (modal.kind === 'configure-builtin' && configs.length === 0) {
|
|
299
292
|
setPendingUsageTarget({
|
|
300
|
-
target: usageTargetFromCatalogEntry(
|
|
293
|
+
target: usageTargetFromCatalogEntry(modal.provider),
|
|
301
294
|
initialModel: savedDefaultModel,
|
|
302
295
|
restoreFocusToConfiguredProviders: true,
|
|
303
296
|
});
|
|
304
297
|
}
|
|
305
|
-
|
|
298
|
+
dispatchModal({type: 'close'});
|
|
306
299
|
}}
|
|
307
300
|
/>
|
|
308
301
|
) : null}
|
|
@@ -310,8 +303,8 @@ export function WorkspaceModelProvidersSection({workspaceId}: {workspaceId: stri
|
|
|
310
303
|
</Modal>
|
|
311
304
|
|
|
312
305
|
<Modal
|
|
313
|
-
open={
|
|
314
|
-
onOpenChange={(open) => (open ? undefined :
|
|
306
|
+
open={modal.kind === 'change-default-model'}
|
|
307
|
+
onOpenChange={(open) => (open ? undefined : dispatchModal({type: 'close'}))}
|
|
315
308
|
>
|
|
316
309
|
<ModalContent aria-describedby={undefined}>
|
|
317
310
|
<ModalTitle className="sr-only">Change default model</ModalTitle>
|
|
@@ -321,17 +314,18 @@ export function WorkspaceModelProvidersSection({workspaceId}: {workspaceId: stri
|
|
|
321
314
|
aria-hidden="true"
|
|
322
315
|
className="overflow-ellipsis overflow-hidden whitespace-nowrap"
|
|
323
316
|
>
|
|
324
|
-
Change default model for
|
|
317
|
+
Change default model for{' '}
|
|
318
|
+
{modal.kind === 'change-default-model' ? modal.provider.label : ''}
|
|
325
319
|
</Text>
|
|
326
320
|
</ModalHeader>
|
|
327
|
-
{
|
|
321
|
+
{modal.kind === 'change-default-model' ? (
|
|
328
322
|
<ChangeDefaultModelForm
|
|
329
323
|
workspaceId={workspaceId}
|
|
330
|
-
entry={
|
|
331
|
-
config={
|
|
324
|
+
entry={modal.provider}
|
|
325
|
+
config={modal.config}
|
|
332
326
|
onSaved={() => {
|
|
333
|
-
toast.success(`${
|
|
334
|
-
|
|
327
|
+
toast.success(`${modal.provider.label} default model saved`);
|
|
328
|
+
dispatchModal({type: 'close'});
|
|
335
329
|
}}
|
|
336
330
|
/>
|
|
337
331
|
) : null}
|
|
@@ -339,52 +333,50 @@ export function WorkspaceModelProvidersSection({workspaceId}: {workspaceId: stri
|
|
|
339
333
|
</Modal>
|
|
340
334
|
|
|
341
335
|
<ModelProviderUsageModal
|
|
342
|
-
target={usageTarget
|
|
343
|
-
initialModel={
|
|
344
|
-
workspaceDefaultHarnessId={configsQuery.data?.
|
|
345
|
-
open={
|
|
336
|
+
target={usageTarget}
|
|
337
|
+
initialModel={modal.kind === 'show-usage' ? modal.initialModel : null}
|
|
338
|
+
workspaceDefaultHarnessId={configsQuery.data?.defaultHarnessId ?? null}
|
|
339
|
+
open={modal.kind === 'show-usage'}
|
|
346
340
|
closeFocusTarget={
|
|
347
|
-
|
|
341
|
+
modal.kind === 'show-usage' && modal.restoreFocusToConfiguredProviders
|
|
348
342
|
? configuredProvidersRegionRef.current
|
|
349
343
|
: null
|
|
350
344
|
}
|
|
351
345
|
onOpenChange={(open) => {
|
|
352
|
-
if (!open)
|
|
346
|
+
if (!open) dispatchModal({type: 'close'});
|
|
353
347
|
}}
|
|
354
348
|
/>
|
|
355
349
|
|
|
356
350
|
<Modal
|
|
357
|
-
open={
|
|
358
|
-
onOpenChange={(open) => (open ? undefined :
|
|
351
|
+
open={modal.kind === 'create-custom' || modal.kind === 'edit-custom'}
|
|
352
|
+
onOpenChange={(open) => (open ? undefined : dispatchModal({type: 'close'}))}
|
|
359
353
|
>
|
|
360
354
|
<ModalContent
|
|
361
355
|
aria-describedby={undefined}
|
|
362
356
|
className="max-h-[calc(100vh-32px)] max-w-[760px]"
|
|
363
357
|
>
|
|
364
|
-
<ModalTitle className="sr-only">
|
|
365
|
-
{customModelProviderFormTitle(customFormState)}
|
|
366
|
-
</ModalTitle>
|
|
358
|
+
<ModalTitle className="sr-only">{customModelProviderFormTitle(modal)}</ModalTitle>
|
|
367
359
|
<ModalHeader>
|
|
368
360
|
<div className="flex min-w-0 flex-col gap-2">
|
|
369
361
|
<Text size="lg" aria-hidden="true" className="truncate">
|
|
370
|
-
{customModelProviderFormTitle(
|
|
362
|
+
{customModelProviderFormTitle(modal)}
|
|
371
363
|
</Text>
|
|
372
364
|
<Text size="sm" className="text-foreground-neutral-muted">
|
|
373
365
|
Connect an OpenAI-, Anthropic-, or Gemini-compatible endpoint.
|
|
374
366
|
</Text>
|
|
375
367
|
</div>
|
|
376
368
|
</ModalHeader>
|
|
377
|
-
{
|
|
369
|
+
{modal.kind === 'create-custom' || modal.kind === 'edit-custom' ? (
|
|
378
370
|
<CustomModelProviderForm
|
|
379
371
|
workspaceId={workspaceId}
|
|
380
|
-
existingConfig={
|
|
372
|
+
existingConfig={modal.kind === 'edit-custom' ? modal.config : undefined}
|
|
381
373
|
onSaved={() => {
|
|
382
374
|
toast.success(
|
|
383
|
-
|
|
384
|
-
? `${
|
|
375
|
+
modal.kind === 'edit-custom'
|
|
376
|
+
? `${modal.config.displayName} saved`
|
|
385
377
|
: 'Custom provider saved',
|
|
386
378
|
);
|
|
387
|
-
|
|
379
|
+
dispatchModal({type: 'close'});
|
|
388
380
|
}}
|
|
389
381
|
/>
|
|
390
382
|
) : null}
|
|
@@ -394,27 +386,22 @@ export function WorkspaceModelProvidersSection({workspaceId}: {workspaceId: stri
|
|
|
394
386
|
);
|
|
395
387
|
}
|
|
396
388
|
|
|
397
|
-
function modelProviderFormTitle(
|
|
398
|
-
if (
|
|
399
|
-
if (
|
|
400
|
-
return
|
|
389
|
+
function modelProviderFormTitle(modal: ManagementModal): string {
|
|
390
|
+
if (modal.kind === 'edit-builtin') return `Edit credentials for ${modal.provider.label}`;
|
|
391
|
+
if (modal.kind === 'configure-builtin') return `Configure ${modal.provider.label}`;
|
|
392
|
+
return '';
|
|
401
393
|
}
|
|
402
394
|
|
|
403
|
-
function customModelProviderFormTitle(
|
|
404
|
-
if (
|
|
405
|
-
if (formState.mode === 'edit') return `Edit ${formState.config.display_name}`;
|
|
395
|
+
function customModelProviderFormTitle(modal: ManagementModal): string {
|
|
396
|
+
if (modal.kind === 'edit-custom') return `Edit ${modal.config.displayName}`;
|
|
406
397
|
return 'Add custom provider';
|
|
407
398
|
}
|
|
408
399
|
|
|
409
|
-
function isBuiltinModelProviderConfig(
|
|
410
|
-
config: ModelProviderConfigResponseDto,
|
|
411
|
-
): config is ModelProviderConfigDto {
|
|
400
|
+
function isBuiltinModelProviderConfig(config: ProviderConfig): config is BuiltinProviderConfig {
|
|
412
401
|
return config.kind === 'builtin';
|
|
413
402
|
}
|
|
414
403
|
|
|
415
|
-
function isCustomModelProviderConfig(
|
|
416
|
-
config: ModelProviderConfigResponseDto,
|
|
417
|
-
): config is CustomModelProviderConfigDto {
|
|
404
|
+
function isCustomModelProviderConfig(config: ProviderConfig): config is CustomProviderConfig {
|
|
418
405
|
return config.kind === 'custom';
|
|
419
406
|
}
|
|
420
407
|
|
|
@@ -428,8 +415,8 @@ function ConfiguredProviderRow({
|
|
|
428
415
|
onShowUsage,
|
|
429
416
|
}: {
|
|
430
417
|
workspaceId: string;
|
|
431
|
-
config:
|
|
432
|
-
entry:
|
|
418
|
+
config: ProviderConfig;
|
|
419
|
+
entry: SupportedProvider | undefined;
|
|
433
420
|
isDefault: boolean;
|
|
434
421
|
onEdit: () => void;
|
|
435
422
|
onChangeDefaultModel: () => void;
|
|
@@ -441,7 +428,7 @@ function ConfiguredProviderRow({
|
|
|
441
428
|
const [deleteOpen, setDeleteOpen] = useState(false);
|
|
442
429
|
const [deleteError, setDeleteError] = useState<string | undefined>();
|
|
443
430
|
const customConfig = isCustomModelProviderConfig(config) ? config : undefined;
|
|
444
|
-
const label = customConfig?.
|
|
431
|
+
const label = customConfig?.displayName ?? entry?.label ?? config.providerId;
|
|
445
432
|
const canUse = entry !== undefined || customConfig !== undefined;
|
|
446
433
|
const canEdit = entry !== undefined || customConfig !== undefined;
|
|
447
434
|
const isBuiltinConfig = isBuiltinModelProviderConfig(config);
|
|
@@ -451,7 +438,7 @@ function ConfiguredProviderRow({
|
|
|
451
438
|
try {
|
|
452
439
|
await setDefault.mutateAsync({
|
|
453
440
|
workspaceId,
|
|
454
|
-
|
|
441
|
+
providerId: config.providerId,
|
|
455
442
|
});
|
|
456
443
|
toast.success(`${label} is now the default provider`);
|
|
457
444
|
} catch (error) {
|
|
@@ -463,7 +450,7 @@ function ConfiguredProviderRow({
|
|
|
463
450
|
async function handleDelete() {
|
|
464
451
|
setDeleteError(undefined);
|
|
465
452
|
try {
|
|
466
|
-
await deleteConfig.mutateAsync({workspaceId, providerId: config.
|
|
453
|
+
await deleteConfig.mutateAsync({workspaceId, providerId: config.providerId});
|
|
467
454
|
toast.success(`${label} deleted`);
|
|
468
455
|
setDeleteOpen(false);
|
|
469
456
|
} catch (error) {
|
|
@@ -1,8 +1,3 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
ModelProviderCatalogEntryDto,
|
|
3
|
-
ModelProviderConfigDto,
|
|
4
|
-
SupportedModelProviderId,
|
|
5
|
-
} from '@shipfox/api-agent-dto';
|
|
6
1
|
import {Button} from '@shipfox/react-ui/button';
|
|
7
2
|
import {Callout} from '@shipfox/react-ui/callout';
|
|
8
3
|
import {FormField, FormFieldInput, fieldError} from '@shipfox/react-ui/form-field';
|
|
@@ -10,6 +5,7 @@ import {ModalBody, ModalFooter} from '@shipfox/react-ui/modal';
|
|
|
10
5
|
import {Text} from '@shipfox/react-ui/typography';
|
|
11
6
|
import {useForm} from '@tanstack/react-form';
|
|
12
7
|
import {useState} from 'react';
|
|
8
|
+
import type {BuiltinProviderConfig, SupportedProvider} from '#core/models.js';
|
|
13
9
|
import {useUpsertModelProviderConfigMutation} from '#hooks/api/model-providers.js';
|
|
14
10
|
import {
|
|
15
11
|
DefaultModelField,
|
|
@@ -29,8 +25,8 @@ export function ModelProviderTestAndSaveForm({
|
|
|
29
25
|
setAsDefaultOnSave = false,
|
|
30
26
|
}: {
|
|
31
27
|
workspaceId: string;
|
|
32
|
-
entry:
|
|
33
|
-
existingConfig?:
|
|
28
|
+
entry: SupportedProvider;
|
|
29
|
+
existingConfig?: BuiltinProviderConfig | undefined;
|
|
34
30
|
onSaved: (savedDefaultModel: string | null) => void;
|
|
35
31
|
setAsDefaultOnSave?: boolean | undefined;
|
|
36
32
|
}) {
|
|
@@ -41,7 +37,7 @@ export function ModelProviderTestAndSaveForm({
|
|
|
41
37
|
onSubmit: async ({value}) => {
|
|
42
38
|
setFormError(undefined);
|
|
43
39
|
const credentials = Object.fromEntries(
|
|
44
|
-
entry.
|
|
40
|
+
entry.credentialFields.map((credentialField) => [
|
|
45
41
|
credentialField.key,
|
|
46
42
|
value[credentialField.key]?.trim() ?? '',
|
|
47
43
|
]),
|
|
@@ -55,11 +51,11 @@ export function ModelProviderTestAndSaveForm({
|
|
|
55
51
|
try {
|
|
56
52
|
await upsertConfig.mutateAsync({
|
|
57
53
|
workspaceId,
|
|
58
|
-
providerId: entry.id
|
|
59
|
-
|
|
60
|
-
...(defaultModel !== undefined ? {
|
|
54
|
+
providerId: entry.id,
|
|
55
|
+
command: {
|
|
56
|
+
...(defaultModel !== undefined ? {defaultModel} : {}),
|
|
61
57
|
credentials,
|
|
62
|
-
...(setAsDefaultOnSave ? {
|
|
58
|
+
...(setAsDefaultOnSave ? {setAsDefault: true} : {}),
|
|
63
59
|
},
|
|
64
60
|
});
|
|
65
61
|
onSaved(selectedModelForCredentialsPayload(selectedModel));
|
|
@@ -102,7 +98,7 @@ export function ModelProviderTestAndSaveForm({
|
|
|
102
98
|
)}
|
|
103
99
|
</form.Field>
|
|
104
100
|
) : null}
|
|
105
|
-
{entry.
|
|
101
|
+
{entry.credentialFields.map((credentialField) => (
|
|
106
102
|
<form.Field
|
|
107
103
|
key={credentialField.key}
|
|
108
104
|
name={credentialField.key}
|
|
@@ -156,11 +152,11 @@ export function ModelProviderTestAndSaveForm({
|
|
|
156
152
|
}
|
|
157
153
|
|
|
158
154
|
function defaultFormValues(
|
|
159
|
-
entry:
|
|
160
|
-
existingConfig:
|
|
155
|
+
entry: SupportedProvider,
|
|
156
|
+
existingConfig: BuiltinProviderConfig | undefined,
|
|
161
157
|
): Record<string, string> {
|
|
162
158
|
return {
|
|
163
|
-
default_model: defaultModelFormValue(existingConfig?.
|
|
164
|
-
...Object.fromEntries(entry.
|
|
159
|
+
default_model: defaultModelFormValue(existingConfig?.defaultModel),
|
|
160
|
+
...Object.fromEntries(entry.credentialFields.map((field) => [field.key, ''])),
|
|
165
161
|
};
|
|
166
162
|
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import {listHarnessDescriptors} from '@shipfox/api-agent-dto';
|
|
2
|
+
import {listHarnesses} from './harness-policy.js';
|
|
3
|
+
|
|
4
|
+
describe('harness policy', () => {
|
|
5
|
+
test('stays aligned with the shared harness compatibility contract', () => {
|
|
6
|
+
expect(listHarnesses()).toEqual(
|
|
7
|
+
listHarnessDescriptors().map(({id, label, description, supportedProviderIds}) => ({
|
|
8
|
+
id,
|
|
9
|
+
label,
|
|
10
|
+
description,
|
|
11
|
+
supportedProviderIds,
|
|
12
|
+
})),
|
|
13
|
+
);
|
|
14
|
+
});
|
|
15
|
+
});
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import type {HarnessDescriptor, HarnessId, ProviderConfig} from './models.js';
|
|
2
|
+
|
|
3
|
+
export const DEFAULT_HARNESS: HarnessId = 'pi';
|
|
4
|
+
|
|
5
|
+
const harnesses: readonly HarnessDescriptor[] = [
|
|
6
|
+
{
|
|
7
|
+
id: 'pi',
|
|
8
|
+
label: 'pi',
|
|
9
|
+
description: 'Works with 30+ model providers',
|
|
10
|
+
supportedProviderIds: [
|
|
11
|
+
'anthropic',
|
|
12
|
+
'ant-ling',
|
|
13
|
+
'azure-openai-responses',
|
|
14
|
+
'openai',
|
|
15
|
+
'deepseek',
|
|
16
|
+
'nvidia',
|
|
17
|
+
'google',
|
|
18
|
+
'mistral',
|
|
19
|
+
'groq',
|
|
20
|
+
'cerebras',
|
|
21
|
+
'cloudflare-ai-gateway',
|
|
22
|
+
'cloudflare-workers-ai',
|
|
23
|
+
'xai',
|
|
24
|
+
'openrouter',
|
|
25
|
+
'vercel-ai-gateway',
|
|
26
|
+
'zai',
|
|
27
|
+
'zai-coding-cn',
|
|
28
|
+
'opencode',
|
|
29
|
+
'opencode-go',
|
|
30
|
+
'huggingface',
|
|
31
|
+
'fireworks',
|
|
32
|
+
'together',
|
|
33
|
+
'kimi-coding',
|
|
34
|
+
'minimax',
|
|
35
|
+
'minimax-cn',
|
|
36
|
+
'moonshotai',
|
|
37
|
+
'moonshotai-cn',
|
|
38
|
+
'xiaomi',
|
|
39
|
+
'xiaomi-token-plan-cn',
|
|
40
|
+
'xiaomi-token-plan-ams',
|
|
41
|
+
'xiaomi-token-plan-sgp',
|
|
42
|
+
],
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
id: 'claude',
|
|
46
|
+
label: 'Claude',
|
|
47
|
+
description: 'Runs on your Anthropic API key',
|
|
48
|
+
supportedProviderIds: ['anthropic'],
|
|
49
|
+
},
|
|
50
|
+
];
|
|
51
|
+
|
|
52
|
+
export function listHarnesses(): readonly HarnessDescriptor[] {
|
|
53
|
+
return harnesses;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function getHarness(id: HarnessId): HarnessDescriptor {
|
|
57
|
+
const harness = harnesses.find((item) => item.id === id);
|
|
58
|
+
if (harness === undefined) throw new Error(`Unknown harness: ${id}`);
|
|
59
|
+
return harness;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function harnessSupportsProvider(harnessId: HarnessId, providerId: string): boolean {
|
|
63
|
+
return getHarness(harnessId).supportedProviderIds.includes(providerId);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function configSupportsHarness(config: ProviderConfig, harness: HarnessDescriptor): boolean {
|
|
67
|
+
return config.kind === 'custom'
|
|
68
|
+
? harness.id === DEFAULT_HARNESS
|
|
69
|
+
: harness.supportedProviderIds.includes(config.providerId);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function compatibleHarnessIds({
|
|
73
|
+
isCustom,
|
|
74
|
+
providerId,
|
|
75
|
+
}: {
|
|
76
|
+
isCustom: boolean;
|
|
77
|
+
providerId: string;
|
|
78
|
+
}): HarnessId[] {
|
|
79
|
+
if (isCustom) return [DEFAULT_HARNESS];
|
|
80
|
+
return harnesses
|
|
81
|
+
.filter((harness) => harness.supportedProviderIds.includes(providerId))
|
|
82
|
+
.map((harness) => harness.id);
|
|
83
|
+
}
|