@shipfox/client-agent 5.0.0 → 6.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +1 -1
- package/CHANGELOG.md +57 -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/state/model-provider-onboarding.d.ts.map +1 -1
- package/dist/state/model-provider-onboarding.js +9 -18
- package/dist/state/model-provider-onboarding.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/src/state/model-provider-onboarding.test.ts +4 -4
- package/src/state/model-provider-onboarding.ts +13 -23
- 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,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {MODEL_PROVIDER_SLUG_PATTERN} from '@shipfox/api-agent-dto';
|
|
2
2
|
|
|
3
3
|
const MAX_SLUG_LENGTH = 40;
|
|
4
4
|
const SLUG_FORMAT_ERROR =
|
|
@@ -15,9 +15,12 @@ export function deriveCustomModelProviderSlug(displayName: string): string {
|
|
|
15
15
|
.replace(/-+$/g, '');
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
-
export function customModelProviderSlugError(
|
|
18
|
+
export function customModelProviderSlugError(
|
|
19
|
+
slug: string,
|
|
20
|
+
reservedProviderIds: readonly string[] = [],
|
|
21
|
+
): string | undefined {
|
|
19
22
|
const trimmed = slug.trim();
|
|
20
23
|
if (!MODEL_PROVIDER_SLUG_PATTERN.test(trimmed)) return SLUG_FORMAT_ERROR;
|
|
21
|
-
if (
|
|
24
|
+
if (reservedProviderIds.includes(trimmed)) return 'This id is reserved for a built-in provider.';
|
|
22
25
|
return undefined;
|
|
23
26
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import type {ModelProviderCatalogEntryDto} from '@shipfox/api-agent-dto';
|
|
2
1
|
import {FormField, useFormField} from '@shipfox/react-ui/form-field';
|
|
3
2
|
import {cn} from '@shipfox/react-ui/utils';
|
|
4
3
|
import type {ComponentProps, ReactNode} from 'react';
|
|
4
|
+
import type {SupportedProvider} from '#core/models.js';
|
|
5
5
|
|
|
6
6
|
export const LATEST_MODEL_VALUE = '__latest__';
|
|
7
7
|
|
|
@@ -13,7 +13,7 @@ export function DefaultModelField({
|
|
|
13
13
|
onChange,
|
|
14
14
|
onBlur,
|
|
15
15
|
}: {
|
|
16
|
-
entry:
|
|
16
|
+
entry: SupportedProvider;
|
|
17
17
|
value: string;
|
|
18
18
|
error: string | undefined;
|
|
19
19
|
children?: ReactNode;
|
|
@@ -52,13 +52,13 @@ export function selectedModelForModelPayload(selectedModel: string): string | nu
|
|
|
52
52
|
return selectedModel === LATEST_MODEL_VALUE ? null : selectedModel.trim();
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
-
function defaultModelLabel(entry:
|
|
56
|
-
const defaultModel = entry.
|
|
55
|
+
function defaultModelLabel(entry: SupportedProvider): string {
|
|
56
|
+
const defaultModel = entry.defaultModel ?? entry.models[0]?.id ?? '';
|
|
57
57
|
return entry.models.find((model) => model.id === defaultModel)?.label ?? defaultModel;
|
|
58
58
|
}
|
|
59
59
|
|
|
60
60
|
function defaultModelDescription(
|
|
61
|
-
entry:
|
|
61
|
+
entry: SupportedProvider,
|
|
62
62
|
selectedModel: string | undefined,
|
|
63
63
|
): string | undefined {
|
|
64
64
|
if (selectedModel === LATEST_MODEL_VALUE) {
|
|
@@ -1,28 +1,31 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {getHarness} from '#core/harness-policy.js';
|
|
2
|
+
import {toProviderConfig} from '#hooks/api/model-provider-mapper.js';
|
|
2
3
|
import {customModelProviderConfig, modelProviderConfig} from '#test/fixtures/model-providers.js';
|
|
3
4
|
import {compatibleHarnessIds, isHarnessAvailable} from './harness-availability.js';
|
|
4
5
|
|
|
5
6
|
describe('harness availability', () => {
|
|
6
7
|
test('marks pi available with any builtin provider config', () => {
|
|
7
|
-
const result = isHarnessAvailable(
|
|
8
|
-
modelProviderConfig({provider_id: 'openai'}),
|
|
8
|
+
const result = isHarnessAvailable(getHarness('pi'), [
|
|
9
|
+
toProviderConfig(modelProviderConfig({provider_id: 'openai'})),
|
|
9
10
|
]);
|
|
10
11
|
|
|
11
12
|
expect(result).toBe(true);
|
|
12
13
|
});
|
|
13
14
|
|
|
14
15
|
test('marks pi available with only a custom provider config', () => {
|
|
15
|
-
const result = isHarnessAvailable(
|
|
16
|
+
const result = isHarnessAvailable(getHarness('pi'), [
|
|
17
|
+
toProviderConfig(customModelProviderConfig()),
|
|
18
|
+
]);
|
|
16
19
|
|
|
17
20
|
expect(result).toBe(true);
|
|
18
21
|
});
|
|
19
22
|
|
|
20
23
|
test('marks claude available only when Anthropic is configured', () => {
|
|
21
|
-
const openai = isHarnessAvailable(
|
|
22
|
-
modelProviderConfig({provider_id: 'openai'}),
|
|
24
|
+
const openai = isHarnessAvailable(getHarness('claude'), [
|
|
25
|
+
toProviderConfig(modelProviderConfig({provider_id: 'openai'})),
|
|
23
26
|
]);
|
|
24
|
-
const anthropic = isHarnessAvailable(
|
|
25
|
-
modelProviderConfig({provider_id: 'anthropic'}),
|
|
27
|
+
const anthropic = isHarnessAvailable(getHarness('claude'), [
|
|
28
|
+
toProviderConfig(modelProviderConfig({provider_id: 'anthropic'})),
|
|
26
29
|
]);
|
|
27
30
|
|
|
28
31
|
expect(openai).toBe(false);
|
|
@@ -30,8 +33,8 @@ describe('harness availability', () => {
|
|
|
30
33
|
});
|
|
31
34
|
|
|
32
35
|
test('marks both harnesses unavailable when no providers are configured', () => {
|
|
33
|
-
expect(isHarnessAvailable(
|
|
34
|
-
expect(isHarnessAvailable(
|
|
36
|
+
expect(isHarnessAvailable(getHarness('pi'), [])).toBe(false);
|
|
37
|
+
expect(isHarnessAvailable(getHarness('claude'), [])).toBe(false);
|
|
35
38
|
});
|
|
36
39
|
|
|
37
40
|
test('returns compatible harnesses for builtin and custom providers', () => {
|
|
@@ -1,35 +1,11 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
type Harness,
|
|
4
|
-
type HarnessDescriptor,
|
|
5
|
-
listHarnessDescriptors,
|
|
6
|
-
type ModelProviderConfigResponseDto,
|
|
7
|
-
} from '@shipfox/api-agent-dto';
|
|
8
|
-
|
|
9
|
-
export function configSupportsHarness(
|
|
10
|
-
config: ModelProviderConfigResponseDto,
|
|
11
|
-
descriptor: HarnessDescriptor,
|
|
12
|
-
): boolean {
|
|
13
|
-
if (config.kind === 'custom') return descriptor.id === DEFAULT_HARNESS;
|
|
14
|
-
return descriptor.supportedProviderIds.includes(config.provider_id);
|
|
15
|
-
}
|
|
1
|
+
import {compatibleHarnessIds, configSupportsHarness} from '#core/harness-policy.js';
|
|
2
|
+
import type {HarnessDescriptor, ProviderConfig} from '#core/models.js';
|
|
16
3
|
|
|
17
4
|
export function isHarnessAvailable(
|
|
18
5
|
descriptor: HarnessDescriptor,
|
|
19
|
-
configs:
|
|
6
|
+
configs: readonly ProviderConfig[],
|
|
20
7
|
): boolean {
|
|
21
8
|
return configs.some((config) => configSupportsHarness(config, descriptor));
|
|
22
9
|
}
|
|
23
10
|
|
|
24
|
-
export
|
|
25
|
-
isCustom,
|
|
26
|
-
providerId,
|
|
27
|
-
}: {
|
|
28
|
-
isCustom: boolean;
|
|
29
|
-
providerId: string;
|
|
30
|
-
}): Harness[] {
|
|
31
|
-
if (isCustom) return [DEFAULT_HARNESS];
|
|
32
|
-
return listHarnessDescriptors()
|
|
33
|
-
.filter((descriptor) => descriptor.supportedProviderIds.includes(providerId))
|
|
34
|
-
.map((descriptor) => descriptor.id);
|
|
35
|
-
}
|
|
11
|
+
export {compatibleHarnessIds};
|
|
@@ -1,9 +1,3 @@
|
|
|
1
|
-
import {
|
|
2
|
-
DEFAULT_HARNESS,
|
|
3
|
-
type Harness,
|
|
4
|
-
type HarnessDescriptor,
|
|
5
|
-
listHarnessDescriptors,
|
|
6
|
-
} from '@shipfox/api-agent-dto';
|
|
7
1
|
import {QueryLoadError} from '@shipfox/client-ui';
|
|
8
2
|
import {IconButton} from '@shipfox/react-ui/button';
|
|
9
3
|
import {Callout} from '@shipfox/react-ui/callout';
|
|
@@ -20,6 +14,8 @@ import {Tooltip, TooltipContent, TooltipTrigger} from '@shipfox/react-ui/tooltip
|
|
|
20
14
|
import {Header, Text} from '@shipfox/react-ui/typography';
|
|
21
15
|
import {cn} from '@shipfox/react-ui/utils';
|
|
22
16
|
import {useRef, useState} from 'react';
|
|
17
|
+
import {DEFAULT_HARNESS, listHarnesses} from '#core/harness-policy.js';
|
|
18
|
+
import type {HarnessDescriptor, HarnessId} from '#core/models.js';
|
|
23
19
|
import {
|
|
24
20
|
useModelProviderConfigsQuery,
|
|
25
21
|
useSetDefaultHarnessMutation,
|
|
@@ -38,13 +34,13 @@ export function WorkspaceHarnessesSection({workspaceId}: {workspaceId: string})
|
|
|
38
34
|
const activeDefaultRequestRef = useRef<{workspaceId: string; id: number} | null>(null);
|
|
39
35
|
const [pendingDefaultHarness, setPendingDefaultHarness] = useState<{
|
|
40
36
|
workspaceId: string;
|
|
41
|
-
harnessId:
|
|
37
|
+
harnessId: HarnessId;
|
|
42
38
|
} | null>(null);
|
|
43
39
|
const [defaultError, setDefaultError] = useState<
|
|
44
|
-
{workspaceId: string; harnessId:
|
|
40
|
+
{workspaceId: string; harnessId: HarnessId; message: string} | undefined
|
|
45
41
|
>();
|
|
46
42
|
const configs = configsQuery.data?.configs ?? [];
|
|
47
|
-
const defaultHarnessId = configsQuery.data?.
|
|
43
|
+
const defaultHarnessId = configsQuery.data?.defaultHarnessId ?? DEFAULT_HARNESS;
|
|
48
44
|
activeWorkspaceIdRef.current = workspaceId;
|
|
49
45
|
|
|
50
46
|
function isActiveDefaultRequest(request: {workspaceId: string; id: number}) {
|
|
@@ -67,7 +63,7 @@ export function WorkspaceHarnessesSection({workspaceId}: {workspaceId: string})
|
|
|
67
63
|
try {
|
|
68
64
|
await setDefaultHarness.mutateAsync({
|
|
69
65
|
workspaceId,
|
|
70
|
-
|
|
66
|
+
harnessId: harness.id,
|
|
71
67
|
});
|
|
72
68
|
if (!isActiveDefaultRequest(request)) return;
|
|
73
69
|
toast.success(`${harness.label} is now the default harness`);
|
|
@@ -106,7 +102,7 @@ export function WorkspaceHarnessesSection({workspaceId}: {workspaceId: string})
|
|
|
106
102
|
|
|
107
103
|
{configsQuery.data !== undefined ? (
|
|
108
104
|
<ul className={cn('divide-y divide-border-neutral-base', SURFACE_CLASS)}>
|
|
109
|
-
{
|
|
105
|
+
{listHarnesses().map((harness) => (
|
|
110
106
|
<HarnessRow
|
|
111
107
|
key={harness.id}
|
|
112
108
|
harness={harness}
|
|
@@ -1,9 +1,6 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
CustomModelProviderConfigDto,
|
|
3
|
-
ModelProviderCatalogEntryDto,
|
|
4
|
-
} from '@shipfox/api-agent-dto';
|
|
5
1
|
import type {Meta, StoryObj} from '@storybook/react';
|
|
6
2
|
import {useState} from 'react';
|
|
3
|
+
import type {CustomProviderConfig, SupportedProvider} from '#core/models.js';
|
|
7
4
|
import {ModelProviderUsageModal} from './model-provider-usage-modal.js';
|
|
8
5
|
import {
|
|
9
6
|
usageTargetFromCatalogEntry,
|
|
@@ -26,7 +23,7 @@ function ModelProviderUsageModalStory({variant}: ModelProviderUsageModalStoryPro
|
|
|
26
23
|
<div className="min-h-screen bg-background-neutral-background p-24">
|
|
27
24
|
<ModelProviderUsageModal
|
|
28
25
|
target={target}
|
|
29
|
-
initialModel={target.
|
|
26
|
+
initialModel={target.defaultModel}
|
|
30
27
|
workspaceDefaultHarnessId="claude"
|
|
31
28
|
open={open}
|
|
32
29
|
onOpenChange={setOpen}
|
|
@@ -55,14 +52,13 @@ export const CustomProvider: Story = {
|
|
|
55
52
|
args: {variant: 'custom'},
|
|
56
53
|
};
|
|
57
54
|
|
|
58
|
-
function anthropicEntry():
|
|
55
|
+
function anthropicEntry(): SupportedProvider {
|
|
59
56
|
return {
|
|
57
|
+
kind: 'supported',
|
|
60
58
|
id: 'anthropic',
|
|
61
59
|
label: 'Anthropic',
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
credential_fields: [{key: 'api_key', label: 'API key', secret: true}],
|
|
65
|
-
unsupported_reason: null,
|
|
60
|
+
defaultModel: 'claude-opus-4-8',
|
|
61
|
+
credentialFields: [{key: 'api_key', label: 'API key', secret: true}],
|
|
66
62
|
models: [
|
|
67
63
|
{id: 'claude-opus-4-8', label: 'Claude Opus 4.8'},
|
|
68
64
|
{id: 'claude-sonnet-4-8', label: 'Claude Sonnet 4.8'},
|
|
@@ -71,14 +67,13 @@ function anthropicEntry(): ModelProviderCatalogEntryDto {
|
|
|
71
67
|
};
|
|
72
68
|
}
|
|
73
69
|
|
|
74
|
-
function longListEntry():
|
|
70
|
+
function longListEntry(): SupportedProvider {
|
|
75
71
|
return {
|
|
72
|
+
kind: 'supported',
|
|
76
73
|
id: 'cloudflare-workers-ai',
|
|
77
74
|
label: 'Cloudflare Workers AI',
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
credential_fields: [{key: 'api_token', label: 'API token', secret: true}],
|
|
81
|
-
unsupported_reason: null,
|
|
75
|
+
defaultModel: '@cf/moonshotai/kimi-k2.7-code',
|
|
76
|
+
credentialFields: [{key: 'api_token', label: 'API token', secret: true}],
|
|
82
77
|
models: Array.from({length: 56}, (_, index) => ({
|
|
83
78
|
id:
|
|
84
79
|
index === 0
|
|
@@ -89,18 +84,18 @@ function longListEntry(): ModelProviderCatalogEntryDto {
|
|
|
89
84
|
};
|
|
90
85
|
}
|
|
91
86
|
|
|
92
|
-
function customProviderConfig():
|
|
87
|
+
function customProviderConfig(): CustomProviderConfig {
|
|
93
88
|
return {
|
|
94
89
|
kind: 'custom',
|
|
95
|
-
|
|
96
|
-
|
|
90
|
+
providerId: 'local-vllm',
|
|
91
|
+
displayName: 'Local vLLM',
|
|
97
92
|
api: 'openai-completions',
|
|
98
|
-
|
|
93
|
+
baseUrl: 'http://localhost:8000/v1',
|
|
99
94
|
headers: [],
|
|
100
|
-
|
|
95
|
+
secretHeaderNames: [],
|
|
101
96
|
models: [{id: 'llama-3.1', label: 'Llama 3.1'}],
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
97
|
+
defaultModel: 'llama-3.1',
|
|
98
|
+
createdAt: '2026-05-08T00:00:00.000Z',
|
|
99
|
+
updatedAt: '2026-05-08T00:00:00.000Z',
|
|
105
100
|
};
|
|
106
101
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {fireEvent, render, screen, waitFor, within} from '@testing-library/react';
|
|
2
2
|
import userEvent from '@testing-library/user-event';
|
|
3
|
-
import {
|
|
3
|
+
import type {CustomProviderConfig, SupportedProvider} from '#core/models.js';
|
|
4
4
|
import {ModelProviderUsageModal} from './model-provider-usage-modal.js';
|
|
5
5
|
import {
|
|
6
6
|
usageTargetFromCatalogEntry,
|
|
@@ -12,8 +12,8 @@ const KIMI_MODEL_ROW_NAME = 'Copy Kimi K2.7 Code model id @cf/moonshotai/kimi-k2
|
|
|
12
12
|
|
|
13
13
|
function renderUsageModal() {
|
|
14
14
|
const onOpenChange = vi.fn();
|
|
15
|
-
const entry =
|
|
16
|
-
|
|
15
|
+
const entry = supportedProvider({
|
|
16
|
+
defaultModel: 'claude-opus-4-8',
|
|
17
17
|
models: [
|
|
18
18
|
{id: 'claude-opus-4-8', label: 'Claude Opus 4.8'},
|
|
19
19
|
{id: '@cf/moonshotai/kimi-k2.7-code', label: 'Kimi K2.7 Code'},
|
|
@@ -79,7 +79,7 @@ describe('ModelProviderUsageModal', () => {
|
|
|
79
79
|
|
|
80
80
|
test('uses the workspace default harness when it is compatible', async () => {
|
|
81
81
|
const onOpenChange = vi.fn();
|
|
82
|
-
const entry =
|
|
82
|
+
const entry = supportedProvider();
|
|
83
83
|
|
|
84
84
|
render(
|
|
85
85
|
<ModelProviderUsageModal
|
|
@@ -100,8 +100,8 @@ describe('ModelProviderUsageModal', () => {
|
|
|
100
100
|
|
|
101
101
|
test('clamps the selected harness when the target changes', async () => {
|
|
102
102
|
const onOpenChange = vi.fn();
|
|
103
|
-
const anthropic =
|
|
104
|
-
const openai =
|
|
103
|
+
const anthropic = supportedProvider();
|
|
104
|
+
const openai = supportedProvider({
|
|
105
105
|
id: 'openai',
|
|
106
106
|
label: 'OpenAI',
|
|
107
107
|
models: [{id: 'gpt-5.5-pro', label: 'GPT-5.5 Pro'}],
|
|
@@ -144,7 +144,7 @@ describe('ModelProviderUsageModal', () => {
|
|
|
144
144
|
|
|
145
145
|
render(
|
|
146
146
|
<ModelProviderUsageModal
|
|
147
|
-
target={usageTargetFromCustomConfig(
|
|
147
|
+
target={usageTargetFromCustomConfig(customProviderConfig())}
|
|
148
148
|
initialModel="custom-model"
|
|
149
149
|
workspaceDefaultHarnessId="claude"
|
|
150
150
|
open
|
|
@@ -208,3 +208,31 @@ describe('ModelProviderUsageModal', () => {
|
|
|
208
208
|
);
|
|
209
209
|
});
|
|
210
210
|
});
|
|
211
|
+
|
|
212
|
+
function supportedProvider(overrides: Partial<SupportedProvider> = {}): SupportedProvider {
|
|
213
|
+
return {
|
|
214
|
+
kind: 'supported',
|
|
215
|
+
id: 'anthropic',
|
|
216
|
+
label: 'Anthropic',
|
|
217
|
+
defaultModel: 'claude-opus-4-8',
|
|
218
|
+
credentialFields: [{key: 'api_key', label: 'API key', secret: true}],
|
|
219
|
+
models: [{id: 'claude-opus-4-8', label: 'Claude Opus 4.8'}],
|
|
220
|
+
...overrides,
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function customProviderConfig(): CustomProviderConfig {
|
|
225
|
+
return {
|
|
226
|
+
kind: 'custom',
|
|
227
|
+
providerId: 'openai-compatible',
|
|
228
|
+
displayName: 'OpenAI Compatible',
|
|
229
|
+
api: 'openai-completions',
|
|
230
|
+
baseUrl: 'https://llm.example.test/v1',
|
|
231
|
+
headers: [],
|
|
232
|
+
secretHeaderNames: [],
|
|
233
|
+
models: [{id: 'custom-model', label: 'Custom Model'}],
|
|
234
|
+
defaultModel: 'custom-model',
|
|
235
|
+
createdAt: '2026-05-08T00:00:00.000Z',
|
|
236
|
+
updatedAt: '2026-05-08T00:00:00.000Z',
|
|
237
|
+
};
|
|
238
|
+
}
|
|
@@ -1,9 +1,3 @@
|
|
|
1
|
-
import {
|
|
2
|
-
DEFAULT_HARNESS,
|
|
3
|
-
getHarnessDescriptor,
|
|
4
|
-
type Harness,
|
|
5
|
-
listHarnessDescriptors,
|
|
6
|
-
} from '@shipfox/api-agent-dto';
|
|
7
1
|
import {Button} from '@shipfox/react-ui/button';
|
|
8
2
|
import {
|
|
9
3
|
CodeBlock,
|
|
@@ -28,6 +22,8 @@ import {
|
|
|
28
22
|
import {Tooltip, TooltipContent, TooltipTrigger} from '@shipfox/react-ui/tooltip';
|
|
29
23
|
import {Code, Text} from '@shipfox/react-ui/typography';
|
|
30
24
|
import {useEffect, useMemo, useRef, useState} from 'react';
|
|
25
|
+
import {DEFAULT_HARNESS, getHarness, listHarnesses} from '#core/harness-policy.js';
|
|
26
|
+
import type {HarnessId} from '#core/models.js';
|
|
31
27
|
import {buildAgentWorkflowExample} from './agent-workflow-example.js';
|
|
32
28
|
import {compatibleHarnessIds} from './harness-availability.js';
|
|
33
29
|
import type {ModelProviderUsageTarget} from './model-provider-usage-target.js';
|
|
@@ -49,14 +45,14 @@ export function ModelProviderUsageModal({
|
|
|
49
45
|
open: boolean;
|
|
50
46
|
onOpenChange: (open: boolean) => void;
|
|
51
47
|
closeFocusTarget?: HTMLElement | null | undefined;
|
|
52
|
-
workspaceDefaultHarnessId?:
|
|
48
|
+
workspaceDefaultHarnessId?: HarnessId | null | undefined;
|
|
53
49
|
}) {
|
|
54
50
|
const [selectedModel, setSelectedModel] = useState('');
|
|
55
|
-
const [selectedHarness, setSelectedHarness] = useState<
|
|
51
|
+
const [selectedHarness, setSelectedHarness] = useState<HarnessId>(DEFAULT_HARNESS);
|
|
56
52
|
|
|
57
53
|
useEffect(() => {
|
|
58
54
|
if (!target) return;
|
|
59
|
-
setSelectedModel(initialModel ?? target.
|
|
55
|
+
setSelectedModel(initialModel ?? target.defaultModel ?? target.models[0]?.id ?? '');
|
|
60
56
|
}, [target, initialModel]);
|
|
61
57
|
|
|
62
58
|
useEffect(() => {
|
|
@@ -72,14 +68,14 @@ export function ModelProviderUsageModal({
|
|
|
72
68
|
() =>
|
|
73
69
|
target
|
|
74
70
|
? compatibleHarnessIds({isCustom: target.isCustom, providerId: target.id})
|
|
75
|
-
: ([] as
|
|
71
|
+
: ([] as HarnessId[]),
|
|
76
72
|
[target],
|
|
77
73
|
);
|
|
78
74
|
const harnessOptions = useMemo(
|
|
79
75
|
() =>
|
|
80
76
|
compatibleIds.map((harnessId) => ({
|
|
81
77
|
value: harnessId,
|
|
82
|
-
label:
|
|
78
|
+
label: getHarness(harnessId).label,
|
|
83
79
|
})),
|
|
84
80
|
[compatibleIds],
|
|
85
81
|
);
|
|
@@ -239,9 +235,9 @@ export function ModelProviderUsageModal({
|
|
|
239
235
|
}
|
|
240
236
|
|
|
241
237
|
function selectInitialHarness(
|
|
242
|
-
compatibleIds:
|
|
243
|
-
workspaceDefaultHarnessId:
|
|
244
|
-
):
|
|
238
|
+
compatibleIds: HarnessId[],
|
|
239
|
+
workspaceDefaultHarnessId: HarnessId | null | undefined,
|
|
240
|
+
): HarnessId {
|
|
245
241
|
if (workspaceDefaultHarnessId && compatibleIds.includes(workspaceDefaultHarnessId)) {
|
|
246
242
|
return workspaceDefaultHarnessId;
|
|
247
243
|
}
|
|
@@ -249,8 +245,8 @@ function selectInitialHarness(
|
|
|
249
245
|
return compatibleIds[0] ?? DEFAULT_HARNESS;
|
|
250
246
|
}
|
|
251
247
|
|
|
252
|
-
function isHarness(value: string): value is
|
|
253
|
-
return
|
|
248
|
+
function isHarness(value: string): value is HarnessId {
|
|
249
|
+
return listHarnesses().some((descriptor) => descriptor.id === value);
|
|
254
250
|
}
|
|
255
251
|
|
|
256
252
|
function ModelProviderModelRow({label, id}: {label: string; id: string}) {
|
|
@@ -1,36 +1,33 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
CustomModelProviderConfigDto,
|
|
3
|
-
ModelProviderCatalogEntryDto,
|
|
4
|
-
} from '@shipfox/api-agent-dto';
|
|
1
|
+
import type {CustomProviderConfig, SupportedProvider} from '#core/models.js';
|
|
5
2
|
|
|
6
3
|
export interface ModelProviderUsageTarget {
|
|
7
4
|
id: string;
|
|
8
5
|
label: string;
|
|
9
6
|
isCustom: boolean;
|
|
10
|
-
models:
|
|
11
|
-
|
|
7
|
+
models: ReadonlyArray<{id: string; label: string}>;
|
|
8
|
+
defaultModel: string | null;
|
|
12
9
|
}
|
|
13
10
|
|
|
14
|
-
export function usageTargetFromCatalogEntry(
|
|
15
|
-
entry
|
|
16
|
-
): ModelProviderUsageTarget {
|
|
11
|
+
export function usageTargetFromCatalogEntry(entry: SupportedProvider): ModelProviderUsageTarget {
|
|
12
|
+
const provider = entry;
|
|
17
13
|
return {
|
|
18
|
-
id:
|
|
19
|
-
label:
|
|
14
|
+
id: provider.id,
|
|
15
|
+
label: provider.label,
|
|
20
16
|
isCustom: false,
|
|
21
|
-
models:
|
|
22
|
-
|
|
17
|
+
models: provider.models,
|
|
18
|
+
defaultModel: provider.defaultModel,
|
|
23
19
|
};
|
|
24
20
|
}
|
|
25
21
|
|
|
26
22
|
export function usageTargetFromCustomConfig(
|
|
27
|
-
config:
|
|
23
|
+
config: CustomProviderConfig,
|
|
28
24
|
): ModelProviderUsageTarget {
|
|
25
|
+
const provider = config;
|
|
29
26
|
return {
|
|
30
|
-
id:
|
|
31
|
-
label:
|
|
27
|
+
id: provider.providerId,
|
|
28
|
+
label: provider.displayName,
|
|
32
29
|
isCustom: true,
|
|
33
|
-
models:
|
|
34
|
-
|
|
30
|
+
models: provider.models,
|
|
31
|
+
defaultModel: provider.defaultModel,
|
|
35
32
|
};
|
|
36
33
|
}
|
|
@@ -255,7 +255,7 @@ function catalogForScenario(scenario: Scenario): ModelProviderCatalogEntryDto[]
|
|
|
255
255
|
|
|
256
256
|
function configsForScenario(scenario: Scenario) {
|
|
257
257
|
if (scenario === 'empty-configured' || scenario === 'configs-error') {
|
|
258
|
-
return {configs: [], default_provider_id: null};
|
|
258
|
+
return {configs: [], default_provider_id: null, default_harness_id: null};
|
|
259
259
|
}
|
|
260
260
|
if (scenario === 'all-configured') {
|
|
261
261
|
return {
|
|
@@ -265,17 +265,20 @@ function configsForScenario(scenario: Scenario) {
|
|
|
265
265
|
}),
|
|
266
266
|
),
|
|
267
267
|
default_provider_id: 'anthropic',
|
|
268
|
+
default_harness_id: null,
|
|
268
269
|
};
|
|
269
270
|
}
|
|
270
271
|
if (scenario === 'custom-configured') {
|
|
271
272
|
return {
|
|
272
273
|
configs: [providerConfig({provider_id: 'anthropic'}), customProviderConfig()],
|
|
273
274
|
default_provider_id: 'local-vllm',
|
|
275
|
+
default_harness_id: null,
|
|
274
276
|
};
|
|
275
277
|
}
|
|
276
278
|
return {
|
|
277
279
|
configs: [providerConfig({provider_id: 'anthropic'})],
|
|
278
280
|
default_provider_id: 'anthropic',
|
|
281
|
+
default_harness_id: null,
|
|
279
282
|
};
|
|
280
283
|
}
|
|
281
284
|
|