@shipfox/client-agent 4.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 +59 -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 +18 -0
- package/dist/feature.d.ts.map +1 -1
- package/dist/feature.js +16 -1
- 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 +64 -294
- package/dist/hooks/api/model-providers.d.ts.map +1 -1
- package/dist/hooks/api/model-providers.js +65 -48
- 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/agents-settings.d.ts +5 -0
- package/dist/routes/agents-settings.d.ts.map +1 -0
- package/dist/routes/agents-settings.js +21 -0
- package/dist/routes/agents-settings.js.map +1 -0
- 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 +17 -1
- 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 +175 -87
- package/src/pages/model-provider-onboarding-page.tsx +62 -56
- package/src/routes/agents-settings.tsx +14 -0
- 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
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shipfox/client-agent",
|
|
3
3
|
"license": "MIT",
|
|
4
|
-
"version": "
|
|
4
|
+
"version": "6.0.1",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
7
7
|
"url": "git+https://github.com/ShipfoxHQ/shipfox.git",
|
|
@@ -10,10 +10,6 @@
|
|
|
10
10
|
"type": "module",
|
|
11
11
|
"main": "dist/index.js",
|
|
12
12
|
"types": "dist/index.d.ts",
|
|
13
|
-
"imports": {
|
|
14
|
-
"#*": "./dist/*",
|
|
15
|
-
"#test/*": "./test/*"
|
|
16
|
-
},
|
|
17
13
|
"exports": {
|
|
18
14
|
".": {
|
|
19
15
|
"types": "./dist/index.d.ts",
|
|
@@ -32,11 +28,11 @@
|
|
|
32
28
|
"@swc/helpers": "^0.5.17",
|
|
33
29
|
"@tanstack/react-form": "^1.32.0",
|
|
34
30
|
"@tanstack/react-query": "^5.101.0",
|
|
35
|
-
"@shipfox/api-agent-dto": "
|
|
36
|
-
"@shipfox/client-api": "
|
|
37
|
-
"@shipfox/client-shell": "
|
|
38
|
-
"@shipfox/
|
|
39
|
-
"@shipfox/
|
|
31
|
+
"@shipfox/api-agent-dto": "9.0.1",
|
|
32
|
+
"@shipfox/client-api": "6.0.1",
|
|
33
|
+
"@shipfox/client-shell": "6.0.1",
|
|
34
|
+
"@shipfox/react-ui": "0.3.6",
|
|
35
|
+
"@shipfox/client-ui": "6.0.1"
|
|
40
36
|
},
|
|
41
37
|
"peerDependencies": {
|
|
42
38
|
"@tanstack/react-query": "^5.101.0",
|
|
@@ -44,6 +40,9 @@
|
|
|
44
40
|
"react": "^19.0.0",
|
|
45
41
|
"react-dom": "^19.0.0"
|
|
46
42
|
},
|
|
43
|
+
"imports": {
|
|
44
|
+
"#*": "./dist/*"
|
|
45
|
+
},
|
|
47
46
|
"scripts": {
|
|
48
47
|
"build": "shipfox-swc",
|
|
49
48
|
"check": "shipfox-biome-check",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import type {ModelProviderCatalogEntryDto} from '@shipfox/api-agent-dto';
|
|
2
1
|
import {Icon} from '@shipfox/react-ui/icon';
|
|
3
2
|
import {Text} from '@shipfox/react-ui/typography';
|
|
4
3
|
import {cn} from '@shipfox/react-ui/utils';
|
|
4
|
+
import type {SupportedProvider} from '#core/models.js';
|
|
5
5
|
|
|
6
6
|
const SURFACE_CLASS =
|
|
7
7
|
'overflow-hidden rounded-8 border border-border-neutral-base bg-background-neutral-base';
|
|
@@ -10,7 +10,7 @@ export function AvailableProviderCard({
|
|
|
10
10
|
entry,
|
|
11
11
|
onConfigure,
|
|
12
12
|
}: {
|
|
13
|
-
entry:
|
|
13
|
+
entry: SupportedProvider;
|
|
14
14
|
onConfigure: () => void;
|
|
15
15
|
}) {
|
|
16
16
|
return (
|
|
@@ -1,19 +1,19 @@
|
|
|
1
|
-
import type {ModelProviderCatalogEntryDto} from '@shipfox/api-agent-dto';
|
|
2
1
|
import {Button} from '@shipfox/react-ui/button';
|
|
3
2
|
import {EmptyState} from '@shipfox/react-ui/empty-state';
|
|
4
3
|
import {Icon} from '@shipfox/react-ui/icon';
|
|
5
4
|
import {Input} from '@shipfox/react-ui/input';
|
|
6
5
|
import type {ReactNode} from 'react';
|
|
7
6
|
import {useMemo, useRef, useState} from 'react';
|
|
7
|
+
import type {SupportedProvider} from '#core/models.js';
|
|
8
|
+
import {providerMatchesSearch} from '#core/provider-policy.js';
|
|
8
9
|
import {AvailableProviderCard} from './available-provider-card.js';
|
|
9
|
-
import {providerMatchesSearch} from './provider-search.js';
|
|
10
10
|
|
|
11
11
|
export const PROVIDER_GRID_CLASS = 'grid grid-cols-2 gap-12 max-[760px]:grid-cols-1';
|
|
12
12
|
|
|
13
13
|
const SEARCH_VISIBILITY_THRESHOLD = 8;
|
|
14
14
|
const MAX_ECHOED_QUERY_LENGTH = 40;
|
|
15
15
|
|
|
16
|
-
export function AvailableProvidersGrid<TEntry extends
|
|
16
|
+
export function AvailableProvidersGrid<TEntry extends SupportedProvider>({
|
|
17
17
|
entries,
|
|
18
18
|
onSelect,
|
|
19
19
|
trailingCard,
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import type {ModelProviderConfigDto} from '@shipfox/api-agent-dto';
|
|
2
1
|
import {Button} from '@shipfox/react-ui/button';
|
|
3
2
|
import {Callout} from '@shipfox/react-ui/callout';
|
|
4
3
|
import {fieldError} from '@shipfox/react-ui/form-field';
|
|
@@ -6,6 +5,7 @@ import {ModalBody, ModalFooter} from '@shipfox/react-ui/modal';
|
|
|
6
5
|
import {Text} from '@shipfox/react-ui/typography';
|
|
7
6
|
import {useForm} from '@tanstack/react-form';
|
|
8
7
|
import {useState} from 'react';
|
|
8
|
+
import type {BuiltinProviderConfig, SupportedProvider} from '#core/models.js';
|
|
9
9
|
import {useUpdateModelProviderDefaultModelMutation} from '#hooks/api/model-providers.js';
|
|
10
10
|
import {
|
|
11
11
|
DefaultModelField,
|
|
@@ -13,7 +13,6 @@ import {
|
|
|
13
13
|
selectedModelForModelPayload,
|
|
14
14
|
} from './default-model-field.js';
|
|
15
15
|
import {modelProviderConfigErrorToFormError} from './form-errors.js';
|
|
16
|
-
import type {SupportedModelProviderCatalogEntry} from './supported-model-provider-catalog-entry.js';
|
|
17
16
|
|
|
18
17
|
const CHANGE_DEFAULT_MODEL_FORM_ID = 'model-provider-change-default-model-form';
|
|
19
18
|
|
|
@@ -24,21 +23,21 @@ export function ChangeDefaultModelForm({
|
|
|
24
23
|
onSaved,
|
|
25
24
|
}: {
|
|
26
25
|
workspaceId: string;
|
|
27
|
-
entry:
|
|
28
|
-
config:
|
|
26
|
+
entry: SupportedProvider;
|
|
27
|
+
config: BuiltinProviderConfig;
|
|
29
28
|
onSaved: () => void;
|
|
30
29
|
}) {
|
|
31
30
|
const updateDefaultModel = useUpdateModelProviderDefaultModelMutation();
|
|
32
31
|
const [formError, setFormError] = useState<string | undefined>();
|
|
33
32
|
const form = useForm({
|
|
34
|
-
defaultValues: {default_model: defaultModelFormValue(config.
|
|
33
|
+
defaultValues: {default_model: defaultModelFormValue(config.defaultModel)},
|
|
35
34
|
onSubmit: async ({value}) => {
|
|
36
35
|
setFormError(undefined);
|
|
37
36
|
try {
|
|
38
37
|
await updateDefaultModel.mutateAsync({
|
|
39
38
|
workspaceId,
|
|
40
39
|
providerId: entry.id,
|
|
41
|
-
|
|
40
|
+
defaultModel: selectedModelForModelPayload(value.default_model),
|
|
42
41
|
});
|
|
43
42
|
onSaved();
|
|
44
43
|
} catch (error) {
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type {ProviderApi} from '#core/models.js';
|
|
2
2
|
|
|
3
|
-
export const MODEL_PROVIDER_API_OPTIONS: Array<{value:
|
|
3
|
+
export const MODEL_PROVIDER_API_OPTIONS: Array<{value: ProviderApi; label: string}> = [
|
|
4
4
|
{value: 'openai-completions', label: 'OpenAI Chat Completions'},
|
|
5
5
|
{value: 'openai-responses', label: 'OpenAI Responses'},
|
|
6
6
|
{value: 'anthropic-messages', label: 'Anthropic Messages'},
|
|
7
7
|
{value: 'google-generative-ai', label: 'Google Generative AI'},
|
|
8
8
|
];
|
|
9
9
|
|
|
10
|
-
export function modelProviderApiLabel(api:
|
|
10
|
+
export function modelProviderApiLabel(api: ProviderApi): string {
|
|
11
11
|
return MODEL_PROVIDER_API_OPTIONS.find((option) => option.value === api)?.label ?? api;
|
|
12
12
|
}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import type {CustomModelProviderConfigDto} from '@shipfox/api-agent-dto';
|
|
2
1
|
import {configureApiClient} from '@shipfox/client-api';
|
|
3
2
|
import {Modal, ModalContent, ModalHeader, ModalTitle} from '@shipfox/react-ui/modal';
|
|
4
3
|
import type {Meta, StoryObj} from '@storybook/react';
|
|
5
4
|
import {QueryClient, QueryClientProvider} from '@tanstack/react-query';
|
|
6
5
|
import {useMemo, useState} from 'react';
|
|
6
|
+
import type {CustomProviderConfig} from '#core/models.js';
|
|
7
7
|
import {CustomModelProviderForm} from './custom-model-provider-form.js';
|
|
8
8
|
|
|
9
9
|
interface CustomModelProviderFormStoryProps {
|
|
@@ -61,31 +61,31 @@ export const EditStoredSecrets: Story = {
|
|
|
61
61
|
args: {mode: 'edit'},
|
|
62
62
|
};
|
|
63
63
|
|
|
64
|
-
function customConfig():
|
|
64
|
+
function customConfig(): CustomProviderConfig {
|
|
65
65
|
return {
|
|
66
66
|
kind: 'custom',
|
|
67
|
-
|
|
68
|
-
|
|
67
|
+
providerId: 'local-vllm',
|
|
68
|
+
displayName: 'Local vLLM',
|
|
69
69
|
api: 'openai-completions',
|
|
70
|
-
|
|
70
|
+
baseUrl: 'http://localhost:8000/v1',
|
|
71
71
|
headers: [{name: 'x-region', value: 'us'}],
|
|
72
|
-
|
|
72
|
+
secretHeaderNames: ['authorization'],
|
|
73
73
|
models: [
|
|
74
74
|
{
|
|
75
75
|
id: 'llama-3.1',
|
|
76
76
|
label: 'Llama 3.1',
|
|
77
|
-
|
|
78
|
-
|
|
77
|
+
contextWindow: 128000,
|
|
78
|
+
maxOutputTokens: 16384,
|
|
79
79
|
},
|
|
80
80
|
{
|
|
81
81
|
id: 'qwen-coder',
|
|
82
82
|
label: 'Qwen Coder',
|
|
83
|
-
|
|
84
|
-
|
|
83
|
+
contextWindow: 32768,
|
|
84
|
+
maxOutputTokens: 8192,
|
|
85
85
|
},
|
|
86
86
|
],
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
87
|
+
defaultModel: 'llama-3.1',
|
|
88
|
+
createdAt: '2026-05-08T00:00:00.000Z',
|
|
89
|
+
updatedAt: '2026-05-08T00:00:00.000Z',
|
|
90
90
|
};
|
|
91
91
|
}
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import type {CustomModelProviderConfigDto, ModelProviderApi} from '@shipfox/api-agent-dto';
|
|
2
1
|
import {Badge} from '@shipfox/react-ui/badge';
|
|
3
2
|
import {Button, IconButton} from '@shipfox/react-ui/button';
|
|
4
3
|
import {Callout} from '@shipfox/react-ui/callout';
|
|
@@ -10,10 +9,12 @@ import {Text} from '@shipfox/react-ui/typography';
|
|
|
10
9
|
import {useForm} from '@tanstack/react-form';
|
|
11
10
|
import type {ReactNode, RefObject} from 'react';
|
|
12
11
|
import {useEffect, useRef, useState} from 'react';
|
|
12
|
+
import type {CustomProviderConfig, ProviderApi} from '#core/models.js';
|
|
13
13
|
import {
|
|
14
14
|
useCreateCustomModelProviderMutation,
|
|
15
15
|
useDiscoverCustomModelProviderModelsBySlugMutation,
|
|
16
16
|
useDiscoverCustomModelProviderModelsMutation,
|
|
17
|
+
useModelProviderCatalogQuery,
|
|
17
18
|
useUpdateCustomModelProviderMutation,
|
|
18
19
|
} from '#hooks/api/model-providers.js';
|
|
19
20
|
import {MODEL_PROVIDER_API_OPTIONS} from './custom-model-provider-api-options.js';
|
|
@@ -44,12 +45,14 @@ export function CustomModelProviderForm({
|
|
|
44
45
|
onSaved,
|
|
45
46
|
}: {
|
|
46
47
|
workspaceId: string;
|
|
47
|
-
existingConfig?:
|
|
48
|
+
existingConfig?: CustomProviderConfig | undefined;
|
|
48
49
|
onSaved: () => void;
|
|
49
50
|
}) {
|
|
50
51
|
const createMutation = useCreateCustomModelProviderMutation();
|
|
51
52
|
const updateMutation = useUpdateCustomModelProviderMutation();
|
|
52
53
|
const discoverMutation = useDiscoverCustomModelProviderModelsMutation();
|
|
54
|
+
const catalogQuery = useModelProviderCatalogQuery();
|
|
55
|
+
const reservedProviderIds = catalogQuery.data?.providers.map((provider) => provider.id) ?? [];
|
|
53
56
|
const discoverBySlugMutation = useDiscoverCustomModelProviderModelsBySlugMutation();
|
|
54
57
|
const [slugEdited, setSlugEdited] = useState(existingConfig !== undefined);
|
|
55
58
|
const [formError, setFormError] = useState<string | undefined>();
|
|
@@ -67,13 +70,13 @@ export function CustomModelProviderForm({
|
|
|
67
70
|
if (existingConfig) {
|
|
68
71
|
await updateMutation.mutateAsync({
|
|
69
72
|
workspaceId,
|
|
70
|
-
providerId: existingConfig.
|
|
71
|
-
|
|
73
|
+
providerId: existingConfig.providerId,
|
|
74
|
+
command: buildUpdateCustomModelProviderBody(existingConfig, value),
|
|
72
75
|
});
|
|
73
76
|
} else {
|
|
74
77
|
await createMutation.mutateAsync({
|
|
75
78
|
workspaceId,
|
|
76
|
-
|
|
79
|
+
command: buildCreateCustomModelProviderBody(value),
|
|
77
80
|
});
|
|
78
81
|
}
|
|
79
82
|
onSaved();
|
|
@@ -101,16 +104,16 @@ export function CustomModelProviderForm({
|
|
|
101
104
|
const response = existingConfig
|
|
102
105
|
? await discoverBySlugMutation.mutateAsync({
|
|
103
106
|
workspaceId,
|
|
104
|
-
providerId: existingConfig.
|
|
105
|
-
|
|
107
|
+
providerId: existingConfig.providerId,
|
|
108
|
+
command: buildDiscoverModelsBySlugBody(existingConfig, values),
|
|
106
109
|
})
|
|
107
110
|
: await discoverMutation.mutateAsync({
|
|
108
111
|
workspaceId,
|
|
109
|
-
|
|
112
|
+
command: buildDiscoverModelsBody(values),
|
|
110
113
|
});
|
|
111
114
|
if (!mountedRef.current) return;
|
|
112
115
|
const existingIds = new Set(values.models.map((model) => model.id.trim()).filter(Boolean));
|
|
113
|
-
const discovered = response.
|
|
116
|
+
const discovered = response.filter((model) => !existingIds.has(model.id));
|
|
114
117
|
form.setFieldValue('models', [
|
|
115
118
|
...values.models,
|
|
116
119
|
...discovered.map((model) => ({
|
|
@@ -200,9 +203,13 @@ export function CustomModelProviderForm({
|
|
|
200
203
|
name="slug"
|
|
201
204
|
validators={{
|
|
202
205
|
onBlur: ({value}) =>
|
|
203
|
-
existingConfig
|
|
206
|
+
existingConfig
|
|
207
|
+
? undefined
|
|
208
|
+
: customModelProviderSlugError(value, reservedProviderIds),
|
|
204
209
|
onSubmit: ({value}) =>
|
|
205
|
-
existingConfig
|
|
210
|
+
existingConfig
|
|
211
|
+
? undefined
|
|
212
|
+
: customModelProviderSlugError(value, reservedProviderIds),
|
|
206
213
|
}}
|
|
207
214
|
>
|
|
208
215
|
{(field) => (
|
|
@@ -234,7 +241,7 @@ export function CustomModelProviderForm({
|
|
|
234
241
|
<FormFieldSelect
|
|
235
242
|
value={field.state.value}
|
|
236
243
|
onChange={(event) =>
|
|
237
|
-
field.handleChange(event.target.value as
|
|
244
|
+
field.handleChange(event.target.value as ProviderApi)
|
|
238
245
|
}
|
|
239
246
|
onBlur={field.handleBlur}
|
|
240
247
|
>
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type {CustomProviderConfig} from '#core/models.js';
|
|
2
2
|
import {
|
|
3
3
|
buildCreateCustomModelProviderBody,
|
|
4
4
|
buildDiscoverModelsBody,
|
|
@@ -61,21 +61,21 @@ describe('custom model provider payload mappers', () => {
|
|
|
61
61
|
|
|
62
62
|
expect(body).toEqual({
|
|
63
63
|
slug: 'local-vllm',
|
|
64
|
-
|
|
64
|
+
displayName: 'Local vLLM',
|
|
65
65
|
api: 'openai-completions',
|
|
66
|
-
|
|
67
|
-
|
|
66
|
+
baseUrl: 'http://localhost:8000/v1',
|
|
67
|
+
apiKey: 'sk-local',
|
|
68
68
|
headers: [{name: 'authorization', value: 'Bearer token', secret: true}],
|
|
69
69
|
models: [
|
|
70
70
|
{
|
|
71
71
|
id: 'llama-3.1',
|
|
72
72
|
label: 'Llama 3.1',
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
73
|
+
contextWindow: 128000,
|
|
74
|
+
maxOutputTokens: 16384,
|
|
75
|
+
inputImage: true,
|
|
76
76
|
},
|
|
77
77
|
],
|
|
78
|
-
|
|
78
|
+
defaultModel: 'llama-3.1',
|
|
79
79
|
});
|
|
80
80
|
});
|
|
81
81
|
|
|
@@ -131,11 +131,11 @@ describe('custom model provider payload mappers', () => {
|
|
|
131
131
|
|
|
132
132
|
expect(body).toEqual({
|
|
133
133
|
api: 'openai-responses',
|
|
134
|
-
|
|
135
|
-
|
|
134
|
+
baseUrl: 'https://llm.example.test/v1',
|
|
135
|
+
apiKey: 'sk-new',
|
|
136
136
|
headers: [
|
|
137
|
-
{name: 'x-region', value: 'us'},
|
|
138
|
-
{name: 'authorization', value: 'Bearer new'},
|
|
137
|
+
{name: 'x-region', value: 'us', secret: false},
|
|
138
|
+
{name: 'authorization', value: 'Bearer new', secret: false},
|
|
139
139
|
],
|
|
140
140
|
});
|
|
141
141
|
});
|
|
@@ -146,25 +146,25 @@ describe('custom model provider payload mappers', () => {
|
|
|
146
146
|
});
|
|
147
147
|
});
|
|
148
148
|
|
|
149
|
-
function customConfig():
|
|
149
|
+
function customConfig(): CustomProviderConfig {
|
|
150
150
|
return {
|
|
151
151
|
kind: 'custom',
|
|
152
|
-
|
|
153
|
-
|
|
152
|
+
providerId: 'local-vllm',
|
|
153
|
+
displayName: 'Local vLLM',
|
|
154
154
|
api: 'openai-responses',
|
|
155
|
-
|
|
155
|
+
baseUrl: 'https://llm.example.test/v1',
|
|
156
156
|
headers: [{name: 'x-region', value: 'us'}],
|
|
157
|
-
|
|
157
|
+
secretHeaderNames: ['authorization'],
|
|
158
158
|
models: [
|
|
159
159
|
{
|
|
160
160
|
id: 'llama-3.1',
|
|
161
161
|
label: 'Llama 3.1',
|
|
162
|
-
|
|
163
|
-
|
|
162
|
+
contextWindow: 128000,
|
|
163
|
+
maxOutputTokens: 16384,
|
|
164
164
|
},
|
|
165
165
|
],
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
166
|
+
defaultModel: 'llama-3.1',
|
|
167
|
+
createdAt: '2026-05-08T00:00:00.000Z',
|
|
168
|
+
updatedAt: '2026-05-08T00:00:00.000Z',
|
|
169
169
|
};
|
|
170
170
|
}
|
|
@@ -1,15 +1,11 @@
|
|
|
1
1
|
import type {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
} from '@shipfox/api-agent-dto';
|
|
10
|
-
|
|
11
|
-
type CreateHeaderRequest = NonNullable<CreateCustomModelProviderBodyDto['headers']>[number];
|
|
12
|
-
type UpdateHeaderRequest = NonNullable<UpdateCustomModelProviderBodyDto['headers']>[number];
|
|
2
|
+
AgentModel,
|
|
3
|
+
CreateCustomProviderCommand,
|
|
4
|
+
CustomProviderConfig,
|
|
5
|
+
DiscoverProviderModelsCommand,
|
|
6
|
+
ProviderApi,
|
|
7
|
+
UpdateCustomProviderCommand,
|
|
8
|
+
} from '#core/models.js';
|
|
13
9
|
|
|
14
10
|
export interface CustomModelProviderHeaderFormValue {
|
|
15
11
|
client_id: string;
|
|
@@ -33,7 +29,7 @@ export interface CustomModelProviderModelFormValue {
|
|
|
33
29
|
export interface CustomModelProviderFormValues {
|
|
34
30
|
slug: string;
|
|
35
31
|
display_name: string;
|
|
36
|
-
api:
|
|
32
|
+
api: ProviderApi;
|
|
37
33
|
base_url: string;
|
|
38
34
|
api_key: string;
|
|
39
35
|
headers: CustomModelProviderHeaderFormValue[];
|
|
@@ -65,13 +61,13 @@ export function createCustomModelProviderFormValues(): CustomModelProviderFormVa
|
|
|
65
61
|
}
|
|
66
62
|
|
|
67
63
|
export function editCustomModelProviderFormValues(
|
|
68
|
-
config:
|
|
64
|
+
config: CustomProviderConfig,
|
|
69
65
|
): CustomModelProviderFormValues {
|
|
70
66
|
return {
|
|
71
|
-
slug: config.
|
|
72
|
-
display_name: config.
|
|
67
|
+
slug: config.providerId,
|
|
68
|
+
display_name: config.displayName,
|
|
73
69
|
api: config.api,
|
|
74
|
-
base_url: config.
|
|
70
|
+
base_url: config.baseUrl,
|
|
75
71
|
api_key: '',
|
|
76
72
|
headers: [
|
|
77
73
|
...config.headers.map((header) => ({
|
|
@@ -82,7 +78,7 @@ export function editCustomModelProviderFormValues(
|
|
|
82
78
|
hasStoredValue: false,
|
|
83
79
|
storedName: header.name,
|
|
84
80
|
})),
|
|
85
|
-
...config.
|
|
81
|
+
...config.secretHeaderNames.map((name) => ({
|
|
86
82
|
client_id: createFormRowId(),
|
|
87
83
|
name,
|
|
88
84
|
value: '',
|
|
@@ -92,45 +88,45 @@ export function editCustomModelProviderFormValues(
|
|
|
92
88
|
})),
|
|
93
89
|
],
|
|
94
90
|
models: config.models.map(modelFormValue),
|
|
95
|
-
default_model: config.
|
|
91
|
+
default_model: config.defaultModel ?? '',
|
|
96
92
|
};
|
|
97
93
|
}
|
|
98
94
|
|
|
99
95
|
export function buildCreateCustomModelProviderBody(
|
|
100
96
|
values: CustomModelProviderFormValues,
|
|
101
|
-
):
|
|
97
|
+
): CreateCustomProviderCommand {
|
|
102
98
|
return {
|
|
103
99
|
slug: values.slug.trim(),
|
|
104
|
-
|
|
100
|
+
displayName: values.display_name.trim(),
|
|
105
101
|
api: values.api,
|
|
106
|
-
|
|
107
|
-
...(values.api_key.trim() ? {
|
|
102
|
+
baseUrl: values.base_url.trim(),
|
|
103
|
+
...(values.api_key.trim() ? {apiKey: values.api_key.trim()} : {}),
|
|
108
104
|
...(requestHeaders(values.headers).length > 0 ? {headers: requestHeaders(values.headers)} : {}),
|
|
109
105
|
models: requestModels(values.models),
|
|
110
|
-
...(values.default_model.trim() ? {
|
|
106
|
+
...(values.default_model.trim() ? {defaultModel: values.default_model.trim()} : {}),
|
|
111
107
|
};
|
|
112
108
|
}
|
|
113
109
|
|
|
114
110
|
export function buildUpdateCustomModelProviderBody(
|
|
115
|
-
config:
|
|
111
|
+
config: CustomProviderConfig,
|
|
116
112
|
values: CustomModelProviderFormValues,
|
|
117
|
-
):
|
|
118
|
-
const body:
|
|
119
|
-
if (values.display_name.trim() !== config.
|
|
120
|
-
body.
|
|
113
|
+
): UpdateCustomProviderCommand {
|
|
114
|
+
const body: UpdateCustomProviderCommand = {};
|
|
115
|
+
if (values.display_name.trim() !== config.displayName)
|
|
116
|
+
body.displayName = values.display_name.trim();
|
|
121
117
|
if (values.api !== config.api) body.api = values.api;
|
|
122
|
-
if (values.base_url.trim() !== config.
|
|
123
|
-
if (values.api_key.trim()) body.
|
|
118
|
+
if (values.base_url.trim() !== config.baseUrl) body.baseUrl = values.base_url.trim();
|
|
119
|
+
if (values.api_key.trim()) body.apiKey = values.api_key.trim();
|
|
124
120
|
if (customHeadersDirty(config, values.headers)) body.headers = updateHeaders(values.headers);
|
|
125
121
|
if (customModelsDirty(config, values.models)) body.models = requestModels(values.models);
|
|
126
122
|
|
|
127
123
|
const nextDefault = values.default_model.trim() || null;
|
|
128
|
-
if (nextDefault !== config.
|
|
124
|
+
if (nextDefault !== config.defaultModel) body.defaultModel = nextDefault;
|
|
129
125
|
return body;
|
|
130
126
|
}
|
|
131
127
|
|
|
132
128
|
export function customHeadersDirty(
|
|
133
|
-
config:
|
|
129
|
+
config: CustomProviderConfig,
|
|
134
130
|
headers: CustomModelProviderHeaderFormValue[],
|
|
135
131
|
): boolean {
|
|
136
132
|
return (
|
|
@@ -141,7 +137,7 @@ export function customHeadersDirty(
|
|
|
141
137
|
value: header.value,
|
|
142
138
|
secret: false,
|
|
143
139
|
})),
|
|
144
|
-
...config.
|
|
140
|
+
...config.secretHeaderNames.map((name) => ({
|
|
145
141
|
name: normalizeHeaderName(name),
|
|
146
142
|
secret: true,
|
|
147
143
|
keep: true,
|
|
@@ -151,24 +147,37 @@ export function customHeadersDirty(
|
|
|
151
147
|
}
|
|
152
148
|
|
|
153
149
|
export function customModelsDirty(
|
|
154
|
-
config:
|
|
150
|
+
config: CustomProviderConfig,
|
|
155
151
|
models: CustomModelProviderModelFormValue[],
|
|
156
152
|
): boolean {
|
|
157
|
-
return
|
|
153
|
+
return (
|
|
154
|
+
JSON.stringify(requestModels(models)) !==
|
|
155
|
+
JSON.stringify(
|
|
156
|
+
config.models.map((model) => ({
|
|
157
|
+
id: model.id,
|
|
158
|
+
label: model.label,
|
|
159
|
+
...(model.contextWindow === undefined ? {} : {contextWindow: model.contextWindow}),
|
|
160
|
+
...(model.maxOutputTokens === undefined ? {} : {maxOutputTokens: model.maxOutputTokens}),
|
|
161
|
+
...(model.inputImage ? {inputImage: true} : {}),
|
|
162
|
+
...(model.reasoning ? {reasoning: true} : {}),
|
|
163
|
+
})),
|
|
164
|
+
)
|
|
165
|
+
);
|
|
158
166
|
}
|
|
159
167
|
|
|
160
168
|
export function buildDiscoverModelsBody(
|
|
161
169
|
values: CustomModelProviderFormValues,
|
|
162
|
-
):
|
|
170
|
+
): DiscoverProviderModelsCommand {
|
|
163
171
|
return {
|
|
164
172
|
api: values.api,
|
|
165
|
-
|
|
166
|
-
...(values.api_key.trim() ? {
|
|
173
|
+
baseUrl: values.base_url.trim(),
|
|
174
|
+
...(values.api_key.trim() ? {apiKey: values.api_key.trim()} : {}),
|
|
167
175
|
...(requestHeaders(values.headers).length > 0
|
|
168
176
|
? {
|
|
169
177
|
headers: requestHeaders(values.headers).map(({name, value}) => ({
|
|
170
178
|
name,
|
|
171
179
|
value,
|
|
180
|
+
secret: false,
|
|
172
181
|
})),
|
|
173
182
|
}
|
|
174
183
|
: {}),
|
|
@@ -176,13 +185,13 @@ export function buildDiscoverModelsBody(
|
|
|
176
185
|
}
|
|
177
186
|
|
|
178
187
|
export function buildDiscoverModelsBySlugBody(
|
|
179
|
-
config:
|
|
188
|
+
config: CustomProviderConfig,
|
|
180
189
|
values: CustomModelProviderFormValues,
|
|
181
|
-
):
|
|
182
|
-
const body:
|
|
190
|
+
): DiscoverProviderModelsCommand {
|
|
191
|
+
const body: DiscoverProviderModelsCommand = {};
|
|
183
192
|
if (values.api !== config.api) body.api = values.api;
|
|
184
|
-
if (values.base_url.trim() !== config.
|
|
185
|
-
if (values.api_key.trim()) body.
|
|
193
|
+
if (values.base_url.trim() !== config.baseUrl) body.baseUrl = values.base_url.trim();
|
|
194
|
+
if (values.api_key.trim()) body.apiKey = values.api_key.trim();
|
|
186
195
|
body.headers = updateHeaders(values.headers);
|
|
187
196
|
return body;
|
|
188
197
|
}
|
|
@@ -200,8 +209,10 @@ export function createFormRowId(): string {
|
|
|
200
209
|
return crypto.randomUUID();
|
|
201
210
|
}
|
|
202
211
|
|
|
203
|
-
function updateHeaders(
|
|
204
|
-
|
|
212
|
+
function updateHeaders(
|
|
213
|
+
headers: CustomModelProviderHeaderFormValue[],
|
|
214
|
+
): NonNullable<UpdateCustomProviderCommand['headers']> {
|
|
215
|
+
const result: NonNullable<UpdateCustomProviderCommand['headers']> = [];
|
|
205
216
|
for (const header of headers) {
|
|
206
217
|
const name = normalizeHeaderName(header.name);
|
|
207
218
|
if (!name) continue;
|
|
@@ -219,7 +230,9 @@ function updateHeaders(headers: CustomModelProviderHeaderFormValue[]): UpdateHea
|
|
|
219
230
|
return result;
|
|
220
231
|
}
|
|
221
232
|
|
|
222
|
-
function requestHeaders(
|
|
233
|
+
function requestHeaders(
|
|
234
|
+
headers: CustomModelProviderHeaderFormValue[],
|
|
235
|
+
): NonNullable<CreateCustomProviderCommand['headers']> {
|
|
223
236
|
return updateHeaders(headers).flatMap((header) =>
|
|
224
237
|
'value' in header && header.value
|
|
225
238
|
? [{name: header.name, value: header.value, secret: header.secret}]
|
|
@@ -227,7 +240,9 @@ function requestHeaders(headers: CustomModelProviderHeaderFormValue[]): CreateHe
|
|
|
227
240
|
);
|
|
228
241
|
}
|
|
229
242
|
|
|
230
|
-
function requestModels(
|
|
243
|
+
function requestModels(
|
|
244
|
+
models: CustomModelProviderModelFormValue[],
|
|
245
|
+
): CreateCustomProviderCommand['models'] {
|
|
231
246
|
return models.flatMap((model) => {
|
|
232
247
|
const id = model.id.trim();
|
|
233
248
|
const label = model.label.trim();
|
|
@@ -236,25 +251,25 @@ function requestModels(models: CustomModelProviderModelFormValue[]): CustomAgent
|
|
|
236
251
|
{
|
|
237
252
|
id,
|
|
238
253
|
label,
|
|
239
|
-
...(model.context_window.trim() ? {
|
|
254
|
+
...(model.context_window.trim() ? {contextWindow: Number(model.context_window)} : {}),
|
|
240
255
|
...(model.max_output_tokens.trim()
|
|
241
|
-
? {
|
|
256
|
+
? {maxOutputTokens: Number(model.max_output_tokens)}
|
|
242
257
|
: {}),
|
|
243
|
-
...(model.input_image ? {
|
|
258
|
+
...(model.input_image ? {inputImage: true} : {}),
|
|
244
259
|
...(model.reasoning ? {reasoning: true} : {}),
|
|
245
260
|
},
|
|
246
261
|
];
|
|
247
262
|
});
|
|
248
263
|
}
|
|
249
264
|
|
|
250
|
-
function modelFormValue(model:
|
|
265
|
+
function modelFormValue(model: AgentModel): CustomModelProviderModelFormValue {
|
|
251
266
|
return {
|
|
252
267
|
client_id: createFormRowId(),
|
|
253
268
|
id: model.id,
|
|
254
269
|
label: model.label,
|
|
255
|
-
context_window: model.
|
|
256
|
-
max_output_tokens: model.
|
|
257
|
-
input_image: model.
|
|
270
|
+
context_window: model.contextWindow?.toString() ?? '',
|
|
271
|
+
max_output_tokens: model.maxOutputTokens?.toString() ?? '',
|
|
272
|
+
input_image: model.inputImage ?? false,
|
|
258
273
|
reasoning: model.reasoning ?? false,
|
|
259
274
|
};
|
|
260
275
|
}
|
|
@@ -25,7 +25,7 @@ describe('customModelProviderSlugError', () => {
|
|
|
25
25
|
});
|
|
26
26
|
|
|
27
27
|
test('rejects reserved built-in provider ids', () => {
|
|
28
|
-
const result = customModelProviderSlugError('anthropic');
|
|
28
|
+
const result = customModelProviderSlugError('anthropic', ['anthropic']);
|
|
29
29
|
|
|
30
30
|
expect(result).toBe('This id is reserved for a built-in provider.');
|
|
31
31
|
});
|