@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
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import {type ManagementModal, managementModalReducer} from './model-provider-management-reducer.js';
|
|
2
|
+
import type {BuiltinProviderConfig, CustomProviderConfig, SupportedProvider} from './models.js';
|
|
3
|
+
|
|
4
|
+
const provider: SupportedProvider = {
|
|
5
|
+
kind: 'supported',
|
|
6
|
+
id: 'anthropic',
|
|
7
|
+
label: 'Anthropic',
|
|
8
|
+
defaultModel: 'claude-sonnet-4-5',
|
|
9
|
+
credentialFields: [],
|
|
10
|
+
models: [],
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
const builtinConfig: BuiltinProviderConfig = {
|
|
14
|
+
kind: 'builtin',
|
|
15
|
+
providerId: 'anthropic',
|
|
16
|
+
defaultModel: null,
|
|
17
|
+
createdAt: '2026-01-01T00:00:00.000Z',
|
|
18
|
+
updatedAt: '2026-01-01T00:00:00.000Z',
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const customConfig: CustomProviderConfig = {
|
|
22
|
+
kind: 'custom',
|
|
23
|
+
providerId: 'local',
|
|
24
|
+
displayName: 'Local',
|
|
25
|
+
api: 'openai-completions',
|
|
26
|
+
baseUrl: 'https://example.test/v1',
|
|
27
|
+
headers: [],
|
|
28
|
+
secretHeaderNames: [],
|
|
29
|
+
models: [],
|
|
30
|
+
defaultModel: null,
|
|
31
|
+
createdAt: '2026-01-01T00:00:00.000Z',
|
|
32
|
+
updatedAt: '2026-01-01T00:00:00.000Z',
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
describe('managementModalReducer', () => {
|
|
36
|
+
test('always replaces the current modal with one valid mode', () => {
|
|
37
|
+
const initial: ManagementModal = {kind: 'configure-builtin', provider};
|
|
38
|
+
|
|
39
|
+
const state = managementModalReducer(initial, {type: 'edit-custom', config: customConfig});
|
|
40
|
+
|
|
41
|
+
expect(state).toEqual({kind: 'edit-custom', config: customConfig});
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
test.each([
|
|
45
|
+
{type: 'close'} as const,
|
|
46
|
+
{type: 'configure-builtin', provider} as const,
|
|
47
|
+
{type: 'edit-builtin', provider, config: builtinConfig} as const,
|
|
48
|
+
{type: 'change-default-model', provider, config: builtinConfig} as const,
|
|
49
|
+
{type: 'create-custom'} as const,
|
|
50
|
+
{type: 'edit-custom', config: customConfig} as const,
|
|
51
|
+
{type: 'show-usage', providerId: 'anthropic', initialModel: null} as const,
|
|
52
|
+
])('accepts $type from every existing mode', (action) => {
|
|
53
|
+
const existing: ManagementModal = {kind: 'show-usage', providerId: 'local', initialModel: null};
|
|
54
|
+
|
|
55
|
+
const state = managementModalReducer(existing, action);
|
|
56
|
+
|
|
57
|
+
expect(state.kind).not.toBeUndefined();
|
|
58
|
+
});
|
|
59
|
+
});
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import type {BuiltinProviderConfig, CustomProviderConfig, SupportedProvider} from './models.js';
|
|
2
|
+
|
|
3
|
+
export type ManagementModal =
|
|
4
|
+
| {readonly kind: 'closed'}
|
|
5
|
+
| {readonly kind: 'configure-builtin'; readonly provider: SupportedProvider}
|
|
6
|
+
| {
|
|
7
|
+
readonly kind: 'edit-builtin';
|
|
8
|
+
readonly provider: SupportedProvider;
|
|
9
|
+
readonly config: BuiltinProviderConfig;
|
|
10
|
+
}
|
|
11
|
+
| {
|
|
12
|
+
readonly kind: 'change-default-model';
|
|
13
|
+
readonly provider: SupportedProvider;
|
|
14
|
+
readonly config: BuiltinProviderConfig;
|
|
15
|
+
}
|
|
16
|
+
| {readonly kind: 'create-custom'}
|
|
17
|
+
| {readonly kind: 'edit-custom'; readonly config: CustomProviderConfig}
|
|
18
|
+
| {
|
|
19
|
+
readonly kind: 'show-usage';
|
|
20
|
+
readonly providerId: string;
|
|
21
|
+
readonly initialModel: string | null;
|
|
22
|
+
readonly restoreFocusToConfiguredProviders?: boolean | undefined;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export type ManagementModalAction =
|
|
26
|
+
| {readonly type: 'close'}
|
|
27
|
+
| {readonly type: 'configure-builtin'; readonly provider: SupportedProvider}
|
|
28
|
+
| {
|
|
29
|
+
readonly type: 'edit-builtin';
|
|
30
|
+
readonly provider: SupportedProvider;
|
|
31
|
+
readonly config: BuiltinProviderConfig;
|
|
32
|
+
}
|
|
33
|
+
| {
|
|
34
|
+
readonly type: 'change-default-model';
|
|
35
|
+
readonly provider: SupportedProvider;
|
|
36
|
+
readonly config: BuiltinProviderConfig;
|
|
37
|
+
}
|
|
38
|
+
| {readonly type: 'create-custom'}
|
|
39
|
+
| {readonly type: 'edit-custom'; readonly config: CustomProviderConfig}
|
|
40
|
+
| {
|
|
41
|
+
readonly type: 'show-usage';
|
|
42
|
+
readonly providerId: string;
|
|
43
|
+
readonly initialModel: string | null;
|
|
44
|
+
readonly restoreFocusToConfiguredProviders?: boolean | undefined;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
export function managementModalReducer(
|
|
48
|
+
_state: ManagementModal,
|
|
49
|
+
action: ManagementModalAction,
|
|
50
|
+
): ManagementModal {
|
|
51
|
+
switch (action.type) {
|
|
52
|
+
case 'close':
|
|
53
|
+
return {kind: 'closed'};
|
|
54
|
+
case 'configure-builtin':
|
|
55
|
+
return {kind: 'configure-builtin', provider: action.provider};
|
|
56
|
+
case 'edit-builtin':
|
|
57
|
+
return {kind: 'edit-builtin', provider: action.provider, config: action.config};
|
|
58
|
+
case 'change-default-model':
|
|
59
|
+
return {kind: 'change-default-model', provider: action.provider, config: action.config};
|
|
60
|
+
case 'create-custom':
|
|
61
|
+
return {kind: 'create-custom'};
|
|
62
|
+
case 'edit-custom':
|
|
63
|
+
return {kind: 'edit-custom', config: action.config};
|
|
64
|
+
case 'show-usage':
|
|
65
|
+
return {
|
|
66
|
+
kind: 'show-usage',
|
|
67
|
+
providerId: action.providerId,
|
|
68
|
+
initialModel: action.initialModel,
|
|
69
|
+
restoreFocusToConfiguredProviders: action.restoreFocusToConfiguredProviders ?? false,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
export type HarnessId = 'pi' | 'claude';
|
|
2
|
+
export type ProviderApi =
|
|
3
|
+
| 'openai-completions'
|
|
4
|
+
| 'openai-responses'
|
|
5
|
+
| 'anthropic-messages'
|
|
6
|
+
| 'google-generative-ai';
|
|
7
|
+
|
|
8
|
+
export interface HarnessDescriptor {
|
|
9
|
+
readonly id: HarnessId;
|
|
10
|
+
readonly label: string;
|
|
11
|
+
readonly description: string;
|
|
12
|
+
readonly supportedProviderIds: readonly string[];
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface AgentModel {
|
|
16
|
+
readonly id: string;
|
|
17
|
+
readonly label: string;
|
|
18
|
+
readonly contextWindow?: number | undefined;
|
|
19
|
+
readonly maxOutputTokens?: number | undefined;
|
|
20
|
+
readonly inputImage?: boolean | undefined;
|
|
21
|
+
readonly reasoning?: boolean | undefined;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface CredentialField {
|
|
25
|
+
readonly key: string;
|
|
26
|
+
readonly label: string;
|
|
27
|
+
readonly secret: boolean;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface SupportedProvider {
|
|
31
|
+
readonly kind: 'supported';
|
|
32
|
+
readonly id: string;
|
|
33
|
+
readonly label: string;
|
|
34
|
+
readonly defaultModel: string | null;
|
|
35
|
+
readonly credentialFields: readonly CredentialField[];
|
|
36
|
+
readonly models: readonly AgentModel[];
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface UnsupportedProvider {
|
|
40
|
+
readonly kind: 'unsupported';
|
|
41
|
+
readonly id: string;
|
|
42
|
+
readonly label: string;
|
|
43
|
+
readonly unsupportedReason: string;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export type ProviderCatalogEntry = SupportedProvider | UnsupportedProvider;
|
|
47
|
+
|
|
48
|
+
export interface BuiltinProviderConfig {
|
|
49
|
+
readonly kind: 'builtin';
|
|
50
|
+
readonly providerId: string;
|
|
51
|
+
readonly defaultModel: string | null;
|
|
52
|
+
readonly createdAt: string;
|
|
53
|
+
readonly updatedAt: string;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export interface ProviderHeader {
|
|
57
|
+
readonly name: string;
|
|
58
|
+
readonly value: string;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export interface CustomProviderConfig {
|
|
62
|
+
readonly kind: 'custom';
|
|
63
|
+
readonly providerId: string;
|
|
64
|
+
readonly displayName: string;
|
|
65
|
+
readonly api: ProviderApi;
|
|
66
|
+
readonly baseUrl: string;
|
|
67
|
+
readonly headers: readonly ProviderHeader[];
|
|
68
|
+
readonly secretHeaderNames: readonly string[];
|
|
69
|
+
readonly models: readonly AgentModel[];
|
|
70
|
+
readonly defaultModel: string | null;
|
|
71
|
+
readonly createdAt: string;
|
|
72
|
+
readonly updatedAt: string;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export type ProviderConfig = BuiltinProviderConfig | CustomProviderConfig;
|
|
76
|
+
|
|
77
|
+
export interface ProviderCatalog {
|
|
78
|
+
readonly providers: readonly ProviderCatalogEntry[];
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export interface ProviderConfiguration {
|
|
82
|
+
readonly configs: readonly ProviderConfig[];
|
|
83
|
+
readonly defaultHarnessId: HarnessId | null;
|
|
84
|
+
readonly defaultProviderId: string | null;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export interface DefaultModelProviderSelection {
|
|
88
|
+
readonly defaultProviderId: string | null;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export interface DefaultHarnessSelection {
|
|
92
|
+
readonly defaultHarnessId: HarnessId;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export interface ProviderCredentialsCommand {
|
|
96
|
+
defaultModel?: string | null | undefined;
|
|
97
|
+
credentials: Record<string, string>;
|
|
98
|
+
setAsDefault?: boolean | undefined;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export interface CustomProviderHeaderCommand {
|
|
102
|
+
name: string;
|
|
103
|
+
value?: string | undefined;
|
|
104
|
+
secret: boolean;
|
|
105
|
+
keep?: boolean | undefined;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export interface CustomProviderModelCommand {
|
|
109
|
+
id: string;
|
|
110
|
+
label: string;
|
|
111
|
+
contextWindow?: number | undefined;
|
|
112
|
+
maxOutputTokens?: number | undefined;
|
|
113
|
+
inputImage?: boolean | undefined;
|
|
114
|
+
reasoning?: boolean | undefined;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export interface CreateCustomProviderCommand {
|
|
118
|
+
slug: string;
|
|
119
|
+
displayName: string;
|
|
120
|
+
api: ProviderApi;
|
|
121
|
+
baseUrl: string;
|
|
122
|
+
apiKey?: string | undefined;
|
|
123
|
+
headers?: CustomProviderHeaderCommand[] | undefined;
|
|
124
|
+
models: CustomProviderModelCommand[];
|
|
125
|
+
defaultModel?: string | null | undefined;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export interface UpdateCustomProviderCommand {
|
|
129
|
+
displayName?: string | undefined;
|
|
130
|
+
api?: ProviderApi | undefined;
|
|
131
|
+
baseUrl?: string | undefined;
|
|
132
|
+
apiKey?: string | undefined;
|
|
133
|
+
headers?: CustomProviderHeaderCommand[] | undefined;
|
|
134
|
+
models?: CustomProviderModelCommand[] | undefined;
|
|
135
|
+
defaultModel?: string | null | undefined;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export interface DiscoverProviderModelsCommand {
|
|
139
|
+
api?: ProviderApi | undefined;
|
|
140
|
+
baseUrl?: string | undefined;
|
|
141
|
+
apiKey?: string | undefined;
|
|
142
|
+
headers?: CustomProviderHeaderCommand[] | undefined;
|
|
143
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type {SupportedProvider} from './models.js';
|
|
2
|
+
import {initialOnboardingState, onboardingReducer} from './onboarding-reducer.js';
|
|
3
|
+
|
|
4
|
+
const provider: SupportedProvider = {
|
|
5
|
+
kind: 'supported',
|
|
6
|
+
id: 'anthropic',
|
|
7
|
+
label: 'Anthropic',
|
|
8
|
+
defaultModel: 'claude',
|
|
9
|
+
credentialFields: [],
|
|
10
|
+
models: [],
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
test('cannot select a provider before selecting a harness', () => {
|
|
14
|
+
expect(onboardingReducer(initialOnboardingState, {type: 'provider-selected', provider})).toBe(
|
|
15
|
+
initialOnboardingState,
|
|
16
|
+
);
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
test('back steps back one screen at a time, keeping earlier selections', () => {
|
|
20
|
+
const choosingProvider = onboardingReducer(initialOnboardingState, {
|
|
21
|
+
type: 'harness-selected',
|
|
22
|
+
harnessId: 'claude',
|
|
23
|
+
});
|
|
24
|
+
const configuring = onboardingReducer(choosingProvider, {type: 'provider-selected', provider});
|
|
25
|
+
const saving = onboardingReducer(configuring, {type: 'provider-saved'});
|
|
26
|
+
|
|
27
|
+
expect(onboardingReducer(saving, {type: 'back'})).toEqual(configuring);
|
|
28
|
+
expect(onboardingReducer(configuring, {type: 'back'})).toEqual(choosingProvider);
|
|
29
|
+
expect(onboardingReducer(choosingProvider, {type: 'back'})).toBe(initialOnboardingState);
|
|
30
|
+
expect(onboardingReducer(initialOnboardingState, {type: 'back'})).toBe(initialOnboardingState);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
test('keeps the selected provider while a default-harness request fails', () => {
|
|
34
|
+
const choosingProvider = onboardingReducer(initialOnboardingState, {
|
|
35
|
+
type: 'harness-selected',
|
|
36
|
+
harnessId: 'claude',
|
|
37
|
+
});
|
|
38
|
+
const configuring = onboardingReducer(choosingProvider, {type: 'provider-selected', provider});
|
|
39
|
+
const saving = onboardingReducer(configuring, {type: 'provider-saved'});
|
|
40
|
+
|
|
41
|
+
expect(
|
|
42
|
+
onboardingReducer(saving, {type: 'default-harness-failed', message: 'Try again.'}),
|
|
43
|
+
).toEqual({...saving, error: 'Try again.'});
|
|
44
|
+
});
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import type {HarnessId, SupportedProvider} from './models.js';
|
|
2
|
+
|
|
3
|
+
export type OnboardingState =
|
|
4
|
+
| {readonly step: 'choose-harness'}
|
|
5
|
+
| {readonly step: 'choose-provider'; readonly harnessId: HarnessId}
|
|
6
|
+
| {
|
|
7
|
+
readonly step: 'configure-provider';
|
|
8
|
+
readonly harnessId: HarnessId;
|
|
9
|
+
readonly provider: SupportedProvider;
|
|
10
|
+
readonly error?: string | undefined;
|
|
11
|
+
}
|
|
12
|
+
| {
|
|
13
|
+
readonly step: 'saving-default-harness';
|
|
14
|
+
readonly harnessId: HarnessId;
|
|
15
|
+
readonly provider: SupportedProvider;
|
|
16
|
+
readonly error?: string | undefined;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export type OnboardingAction =
|
|
20
|
+
| {readonly type: 'harness-selected'; readonly harnessId: HarnessId}
|
|
21
|
+
| {readonly type: 'back'}
|
|
22
|
+
| {readonly type: 'provider-selected'; readonly provider: SupportedProvider}
|
|
23
|
+
| {readonly type: 'provider-saved'}
|
|
24
|
+
| {readonly type: 'default-harness-failed'; readonly message: string}
|
|
25
|
+
| {readonly type: 'default-harness-saved'};
|
|
26
|
+
|
|
27
|
+
export const initialOnboardingState: OnboardingState = {step: 'choose-harness'};
|
|
28
|
+
|
|
29
|
+
function goBack(state: OnboardingState): OnboardingState {
|
|
30
|
+
switch (state.step) {
|
|
31
|
+
case 'choose-harness':
|
|
32
|
+
return state;
|
|
33
|
+
case 'choose-provider':
|
|
34
|
+
return initialOnboardingState;
|
|
35
|
+
case 'configure-provider':
|
|
36
|
+
return {step: 'choose-provider', harnessId: state.harnessId};
|
|
37
|
+
case 'saving-default-harness':
|
|
38
|
+
return {step: 'configure-provider', harnessId: state.harnessId, provider: state.provider};
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function onboardingReducer(
|
|
43
|
+
state: OnboardingState,
|
|
44
|
+
action: OnboardingAction,
|
|
45
|
+
): OnboardingState {
|
|
46
|
+
switch (action.type) {
|
|
47
|
+
case 'harness-selected':
|
|
48
|
+
return {step: 'choose-provider', harnessId: action.harnessId};
|
|
49
|
+
case 'back':
|
|
50
|
+
return goBack(state);
|
|
51
|
+
case 'provider-selected':
|
|
52
|
+
return state.step === 'choose-provider'
|
|
53
|
+
? {step: 'configure-provider', harnessId: state.harnessId, provider: action.provider}
|
|
54
|
+
: state;
|
|
55
|
+
case 'provider-saved':
|
|
56
|
+
return state.step === 'configure-provider'
|
|
57
|
+
? {step: 'saving-default-harness', harnessId: state.harnessId, provider: state.provider}
|
|
58
|
+
: state;
|
|
59
|
+
case 'default-harness-failed':
|
|
60
|
+
return state.step === 'saving-default-harness' ? {...state, error: action.message} : state;
|
|
61
|
+
case 'default-harness-saved':
|
|
62
|
+
return initialOnboardingState;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
HarnessDescriptor,
|
|
3
|
+
ProviderCatalogEntry,
|
|
4
|
+
ProviderConfig,
|
|
5
|
+
SupportedProvider,
|
|
6
|
+
} from './models.js';
|
|
7
|
+
|
|
8
|
+
export function isSupportedProvider(entry: ProviderCatalogEntry): entry is SupportedProvider {
|
|
9
|
+
return entry.kind === 'supported';
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function supportsProvider(harness: HarnessDescriptor, providerId: string): boolean {
|
|
13
|
+
return harness.supportedProviderIds.includes(providerId);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function availableProviders(
|
|
17
|
+
catalog: readonly ProviderCatalogEntry[],
|
|
18
|
+
configs: readonly ProviderConfig[],
|
|
19
|
+
): SupportedProvider[] {
|
|
20
|
+
const configuredIds = new Set(configs.map((config) => config.providerId));
|
|
21
|
+
return catalog.filter(isSupportedProvider).filter((provider) => !configuredIds.has(provider.id));
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function providerMatchesSearch(entry: SupportedProvider, query: string): boolean {
|
|
25
|
+
const needle = query.trim().toLocaleLowerCase();
|
|
26
|
+
if (!needle) return true;
|
|
27
|
+
const terms = [
|
|
28
|
+
entry.id,
|
|
29
|
+
entry.label,
|
|
30
|
+
...entry.models.flatMap((model) => [model.id, model.label]),
|
|
31
|
+
];
|
|
32
|
+
return terms.some((term) => term.toLocaleLowerCase().includes(needle));
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function customProviderCardMatchesSearch(query: string): boolean {
|
|
36
|
+
const needle = query.trim().toLocaleLowerCase();
|
|
37
|
+
if (!needle) return true;
|
|
38
|
+
return 'custom provider openai anthropic google endpoint https public'.includes(needle);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function deriveProviderSlug(displayName: string): string {
|
|
42
|
+
return displayName
|
|
43
|
+
.trim()
|
|
44
|
+
.toLocaleLowerCase()
|
|
45
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
46
|
+
.replace(/^-+|-+$/g, '');
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const PROVIDER_SLUG_RE = /^[a-z][a-z0-9-]{1,62}$/;
|
|
50
|
+
|
|
51
|
+
export function isProviderSlugValid(slug: string): boolean {
|
|
52
|
+
return PROVIDER_SLUG_RE.test(slug);
|
|
53
|
+
}
|
package/src/feature.ts
CHANGED
|
@@ -1,4 +1,14 @@
|
|
|
1
|
-
import {defineClientFeature} from '@shipfox/client-shell';
|
|
1
|
+
import {defineClientFeature, type SettingsSectionEntry} from '@shipfox/client-shell';
|
|
2
|
+
|
|
3
|
+
export const agentSettingsSections = [
|
|
4
|
+
{
|
|
5
|
+
id: 'settings.agents',
|
|
6
|
+
pathSegment: 'agents',
|
|
7
|
+
label: 'Agents',
|
|
8
|
+
icon: 'robot2Line',
|
|
9
|
+
order: 400,
|
|
10
|
+
},
|
|
11
|
+
] as const satisfies readonly SettingsSectionEntry[];
|
|
2
12
|
|
|
3
13
|
export const agentFeature = defineClientFeature({
|
|
4
14
|
id: 'shipfox.agent',
|
|
@@ -8,5 +18,11 @@ export const agentFeature = defineClientFeature({
|
|
|
8
18
|
parent: 'workspaceLayout',
|
|
9
19
|
impl: '@shipfox/client-agent/routes/model-provider',
|
|
10
20
|
},
|
|
21
|
+
{
|
|
22
|
+
path: '/workspaces/$wid/settings/agents',
|
|
23
|
+
parent: 'workspaceSettings',
|
|
24
|
+
impl: '@shipfox/client-agent/routes/agents-settings',
|
|
25
|
+
},
|
|
11
26
|
],
|
|
27
|
+
settingsSections: agentSettingsSections,
|
|
12
28
|
});
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
CreateCustomModelProviderBodyDto,
|
|
3
|
+
DiscoverCustomModelProviderModelsBodyDto,
|
|
4
|
+
DiscoverCustomModelProviderModelsBySlugBodyDto,
|
|
5
|
+
SetDefaultHarnessBodyDto,
|
|
6
|
+
SetDefaultModelProviderBodyDto,
|
|
7
|
+
UpdateCustomModelProviderBodyDto,
|
|
8
|
+
UpdateModelProviderConfigBodyDto,
|
|
9
|
+
UpdateModelProviderDefaultModelBodyDto,
|
|
10
|
+
} from '@shipfox/api-agent-dto';
|
|
11
|
+
import type {
|
|
12
|
+
CreateCustomProviderCommand,
|
|
13
|
+
CustomProviderHeaderCommand,
|
|
14
|
+
CustomProviderModelCommand,
|
|
15
|
+
DiscoverProviderModelsCommand,
|
|
16
|
+
ProviderCredentialsCommand,
|
|
17
|
+
UpdateCustomProviderCommand,
|
|
18
|
+
} from '#core/models.js';
|
|
19
|
+
|
|
20
|
+
export function toProviderCredentialsBody(
|
|
21
|
+
command: ProviderCredentialsCommand,
|
|
22
|
+
): UpdateModelProviderConfigBodyDto {
|
|
23
|
+
return {
|
|
24
|
+
...(command.defaultModel === undefined ? {} : {default_model: command.defaultModel}),
|
|
25
|
+
credentials: command.credentials,
|
|
26
|
+
...(command.setAsDefault ? {set_as_default: true} : {}),
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function toDefaultModelBody(
|
|
31
|
+
defaultModel: string | null,
|
|
32
|
+
): UpdateModelProviderDefaultModelBodyDto {
|
|
33
|
+
return {default_model: defaultModel};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function toDefaultProviderBody(providerId: string): SetDefaultModelProviderBodyDto {
|
|
37
|
+
return {provider_id: providerId};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function toDefaultHarnessBody(harnessId: 'pi' | 'claude'): SetDefaultHarnessBodyDto {
|
|
41
|
+
return {harness_id: harnessId};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function toCreateCustomProviderBody(
|
|
45
|
+
command: CreateCustomProviderCommand,
|
|
46
|
+
): CreateCustomModelProviderBodyDto {
|
|
47
|
+
return {
|
|
48
|
+
slug: command.slug,
|
|
49
|
+
display_name: command.displayName,
|
|
50
|
+
api: command.api,
|
|
51
|
+
base_url: command.baseUrl,
|
|
52
|
+
...(command.apiKey ? {api_key: command.apiKey} : {}),
|
|
53
|
+
...(command.headers?.length ? {headers: command.headers.map(toCreateHeader)} : {}),
|
|
54
|
+
models: command.models.map(toModel),
|
|
55
|
+
...(command.defaultModel ? {default_model: command.defaultModel} : {}),
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function toUpdateCustomProviderBody(
|
|
60
|
+
command: UpdateCustomProviderCommand,
|
|
61
|
+
): UpdateCustomModelProviderBodyDto {
|
|
62
|
+
return {
|
|
63
|
+
...(command.displayName === undefined ? {} : {display_name: command.displayName}),
|
|
64
|
+
...(command.api === undefined ? {} : {api: command.api}),
|
|
65
|
+
...(command.baseUrl === undefined ? {} : {base_url: command.baseUrl}),
|
|
66
|
+
...(command.apiKey === undefined ? {} : {api_key: command.apiKey}),
|
|
67
|
+
...(command.headers === undefined ? {} : {headers: command.headers.map(toUpdateHeader)}),
|
|
68
|
+
...(command.models === undefined ? {} : {models: command.models.map(toModel)}),
|
|
69
|
+
...(command.defaultModel === undefined ? {} : {default_model: command.defaultModel}),
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function toDiscoverModelsBody(
|
|
74
|
+
command: DiscoverProviderModelsCommand,
|
|
75
|
+
): DiscoverCustomModelProviderModelsBodyDto {
|
|
76
|
+
if (command.api === undefined || command.baseUrl === undefined) {
|
|
77
|
+
throw new Error('Model discovery requires an API and base URL.');
|
|
78
|
+
}
|
|
79
|
+
return {
|
|
80
|
+
api: command.api,
|
|
81
|
+
base_url: command.baseUrl,
|
|
82
|
+
...(command.apiKey ? {api_key: command.apiKey} : {}),
|
|
83
|
+
...(command.headers?.length
|
|
84
|
+
? {headers: command.headers.flatMap(({name, value}) => (value ? [{name, value}] : []))}
|
|
85
|
+
: {}),
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function toDiscoverModelsBySlugBody(
|
|
90
|
+
command: DiscoverProviderModelsCommand,
|
|
91
|
+
): DiscoverCustomModelProviderModelsBySlugBodyDto {
|
|
92
|
+
return {
|
|
93
|
+
...(command.api === undefined ? {} : {api: command.api}),
|
|
94
|
+
...(command.baseUrl === undefined ? {} : {base_url: command.baseUrl}),
|
|
95
|
+
...(command.apiKey === undefined ? {} : {api_key: command.apiKey}),
|
|
96
|
+
...(command.headers === undefined ? {} : {headers: command.headers.map(toUpdateHeader)}),
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function toCreateHeader(header: CustomProviderHeaderCommand) {
|
|
101
|
+
return {name: header.name, value: header.value ?? '', secret: header.secret};
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function toUpdateHeader(header: CustomProviderHeaderCommand) {
|
|
105
|
+
return {
|
|
106
|
+
name: header.name,
|
|
107
|
+
...(header.value === undefined ? {} : {value: header.value}),
|
|
108
|
+
secret: header.secret,
|
|
109
|
+
...(header.keep ? {keep: true} : {}),
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function toModel(model: CustomProviderModelCommand) {
|
|
114
|
+
return {
|
|
115
|
+
id: model.id,
|
|
116
|
+
label: model.label,
|
|
117
|
+
...(model.contextWindow === undefined ? {} : {context_window: model.contextWindow}),
|
|
118
|
+
...(model.maxOutputTokens === undefined ? {} : {max_output_tokens: model.maxOutputTokens}),
|
|
119
|
+
...(model.inputImage ? {input_image: true} : {}),
|
|
120
|
+
...(model.reasoning ? {reasoning: true} : {}),
|
|
121
|
+
};
|
|
122
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import {
|
|
2
|
+
modelProviderCatalogResponse,
|
|
3
|
+
modelProviderConfigsResponse,
|
|
4
|
+
modelProviderEntry,
|
|
5
|
+
} from '#test/fixtures/model-providers.js';
|
|
6
|
+
import {toProviderCatalog, toProviderConfiguration} from './model-provider-mapper.js';
|
|
7
|
+
|
|
8
|
+
test('maps provider catalog entries before they reach the client domain', () => {
|
|
9
|
+
const catalog = toProviderCatalog(modelProviderCatalogResponse([modelProviderEntry()]));
|
|
10
|
+
|
|
11
|
+
expect(catalog.providers).toEqual([
|
|
12
|
+
expect.objectContaining({
|
|
13
|
+
kind: 'supported',
|
|
14
|
+
defaultModel: 'claude-opus-4-8',
|
|
15
|
+
credentialFields: [{key: 'api_key', label: 'API key', secret: true}],
|
|
16
|
+
}),
|
|
17
|
+
]);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
test('maps configuration response defaults and provider config fields', () => {
|
|
21
|
+
const configuration = toProviderConfiguration(modelProviderConfigsResponse());
|
|
22
|
+
|
|
23
|
+
expect(configuration.defaultProviderId).toBe('anthropic');
|
|
24
|
+
expect(configuration.configs[0]).toEqual(
|
|
25
|
+
expect.objectContaining({providerId: 'anthropic', defaultModel: null}),
|
|
26
|
+
);
|
|
27
|
+
});
|