@shipfox/client-agent 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.storybook/main.ts +1 -0
- package/.storybook/preview.css +4 -0
- package/.storybook/preview.tsx +56 -0
- package/.turbo/turbo-build.log +2 -0
- package/.turbo/turbo-type$colon$emit.log +1 -0
- package/.turbo/turbo-type.log +1 -0
- package/CHANGELOG.md +93 -0
- package/LICENSE +21 -0
- package/dist/components/add-custom-provider-card.d.ts +4 -0
- package/dist/components/add-custom-provider-card.d.ts.map +1 -0
- package/dist/components/add-custom-provider-card.js +41 -0
- package/dist/components/add-custom-provider-card.js.map +1 -0
- package/dist/components/agent-workflow-example.d.ts +11 -0
- package/dist/components/agent-workflow-example.d.ts.map +1 -0
- package/dist/components/agent-workflow-example.js +38 -0
- package/dist/components/agent-workflow-example.js.map +1 -0
- package/dist/components/available-provider-card.d.ts +6 -0
- package/dist/components/available-provider-card.d.ts.map +1 -0
- package/dist/components/available-provider-card.js +41 -0
- package/dist/components/available-provider-card.js.map +1 -0
- package/dist/components/available-providers-grid.d.ts +10 -0
- package/dist/components/available-providers-grid.d.ts.map +1 -0
- package/dist/components/available-providers-grid.js +89 -0
- package/dist/components/available-providers-grid.js.map +1 -0
- package/dist/components/change-default-model-form.d.ts +9 -0
- package/dist/components/change-default-model-form.d.ts.map +1 -0
- package/dist/components/change-default-model-form.js +98 -0
- package/dist/components/change-default-model-form.js.map +1 -0
- package/dist/components/custom-model-provider-api-options.d.ts +7 -0
- package/dist/components/custom-model-provider-api-options.d.ts.map +1 -0
- package/dist/components/custom-model-provider-api-options.js +23 -0
- package/dist/components/custom-model-provider-api-options.js.map +1 -0
- package/dist/components/custom-model-provider-form.d.ts +8 -0
- package/dist/components/custom-model-provider-form.d.ts.map +1 -0
- package/dist/components/custom-model-provider-form.js +667 -0
- package/dist/components/custom-model-provider-form.js.map +1 -0
- package/dist/components/custom-model-provider-form.stories.d.ts +20 -0
- package/dist/components/custom-model-provider-form.stories.d.ts.map +1 -0
- package/dist/components/custom-model-provider-form.stories.js +104 -0
- package/dist/components/custom-model-provider-form.stories.js.map +1 -0
- package/dist/components/custom-model-provider-payload.d.ts +39 -0
- package/dist/components/custom-model-provider-payload.d.ts.map +1 -0
- package/dist/components/custom-model-provider-payload.js +203 -0
- package/dist/components/custom-model-provider-payload.js.map +1 -0
- package/dist/components/custom-model-provider-slug.d.ts +3 -0
- package/dist/components/custom-model-provider-slug.d.ts.map +1 -0
- package/dist/components/custom-model-provider-slug.js +14 -0
- package/dist/components/custom-model-provider-slug.js.map +1 -0
- package/dist/components/default-model-field.d.ts +16 -0
- package/dist/components/default-model-field.d.ts.map +1 -0
- package/dist/components/default-model-field.js +60 -0
- package/dist/components/default-model-field.js.map +1 -0
- package/dist/components/form-errors.d.ts +11 -0
- package/dist/components/form-errors.d.ts.map +1 -0
- package/dist/components/form-errors.js +101 -0
- package/dist/components/form-errors.js.map +1 -0
- package/dist/components/harness-availability.d.ts +8 -0
- package/dist/components/harness-availability.d.ts.map +1 -0
- package/dist/components/harness-availability.js +16 -0
- package/dist/components/harness-availability.js.map +1 -0
- package/dist/components/harnesses-section.d.ts +4 -0
- package/dist/components/harnesses-section.d.ts.map +1 -0
- package/dist/components/harnesses-section.js +249 -0
- package/dist/components/harnesses-section.js.map +1 -0
- package/dist/components/harnesses-section.stories.d.ts +25 -0
- package/dist/components/harnesses-section.stories.d.ts.map +1 -0
- package/dist/components/harnesses-section.stories.js +138 -0
- package/dist/components/harnesses-section.stories.js.map +1 -0
- package/dist/components/model-provider-usage-modal.d.ts +11 -0
- package/dist/components/model-provider-usage-modal.d.ts.map +1 -0
- package/dist/components/model-provider-usage-modal.js +349 -0
- package/dist/components/model-provider-usage-modal.js.map +1 -0
- package/dist/components/model-provider-usage-modal.stories.d.ts +21 -0
- package/dist/components/model-provider-usage-modal.stories.d.ts.map +1 -0
- package/dist/components/model-provider-usage-modal.stories.js +115 -0
- package/dist/components/model-provider-usage-modal.stories.js.map +1 -0
- package/dist/components/model-provider-usage-target.d.ts +14 -0
- package/dist/components/model-provider-usage-target.d.ts.map +1 -0
- package/dist/components/model-provider-usage-target.js +20 -0
- package/dist/components/model-provider-usage-target.js.map +1 -0
- package/dist/components/model-providers-section.d.ts +4 -0
- package/dist/components/model-providers-section.d.ts.map +1 -0
- package/dist/components/model-providers-section.js +668 -0
- package/dist/components/model-providers-section.js.map +1 -0
- package/dist/components/model-providers-section.stories.d.ts +32 -0
- package/dist/components/model-providers-section.stories.d.ts.map +1 -0
- package/dist/components/model-providers-section.stories.js +442 -0
- package/dist/components/model-providers-section.stories.js.map +1 -0
- package/dist/components/provider-search.d.ts +4 -0
- package/dist/components/provider-search.d.ts.map +1 -0
- package/dist/components/provider-search.js +13 -0
- package/dist/components/provider-search.js.map +1 -0
- package/dist/components/supported-model-provider-catalog-entry.d.ts +8 -0
- package/dist/components/supported-model-provider-catalog-entry.d.ts.map +1 -0
- package/dist/components/supported-model-provider-catalog-entry.js +9 -0
- package/dist/components/supported-model-provider-catalog-entry.js.map +1 -0
- package/dist/components/test-and-save-form.d.ts +10 -0
- package/dist/components/test-and-save-form.d.ts.map +1 -0
- package/dist/components/test-and-save-form.js +138 -0
- package/dist/components/test-and-save-form.js.map +1 -0
- package/dist/feature.d.ts +9 -0
- package/dist/feature.d.ts.map +1 -0
- package/dist/feature.js +13 -0
- package/dist/feature.js.map +1 -0
- package/dist/hooks/api/model-providers.d.ts +334 -0
- package/dist/hooks/api/model-providers.d.ts.map +1 -0
- package/dist/hooks/api/model-providers.js +190 -0
- package/dist/hooks/api/model-providers.js.map +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +10 -0
- package/dist/index.js.map +1 -0
- package/dist/pages/model-provider-onboarding-page.d.ts +6 -0
- package/dist/pages/model-provider-onboarding-page.d.ts.map +1 -0
- package/dist/pages/model-provider-onboarding-page.js +295 -0
- package/dist/pages/model-provider-onboarding-page.js.map +1 -0
- package/dist/pages/model-provider-onboarding-page.stories.d.ts +27 -0
- package/dist/pages/model-provider-onboarding-page.stories.d.ts.map +1 -0
- package/dist/pages/model-provider-onboarding-page.stories.js +368 -0
- package/dist/pages/model-provider-onboarding-page.stories.js.map +1 -0
- package/dist/routes/model-provider.d.ts +5 -0
- package/dist/routes/model-provider.d.ts.map +1 -0
- package/dist/routes/model-provider.js +28 -0
- package/dist/routes/model-provider.js.map +1 -0
- package/dist/state/model-provider-onboarding.d.ts +3 -0
- package/dist/state/model-provider-onboarding.d.ts.map +1 -0
- package/dist/state/model-provider-onboarding.js +28 -0
- package/dist/state/model-provider-onboarding.js.map +1 -0
- package/dist/tsconfig.test.tsbuildinfo +1 -0
- package/package.json +114 -0
- package/src/components/add-custom-provider-card.tsx +32 -0
- package/src/components/agent-workflow-example.test.ts +74 -0
- package/src/components/agent-workflow-example.ts +55 -0
- package/src/components/available-provider-card.tsx +39 -0
- package/src/components/available-providers-grid.test.tsx +55 -0
- package/src/components/available-providers-grid.tsx +106 -0
- package/src/components/change-default-model-form.tsx +104 -0
- package/src/components/custom-model-provider-api-options.test.ts +18 -0
- package/src/components/custom-model-provider-api-options.ts +12 -0
- package/src/components/custom-model-provider-form.stories.tsx +91 -0
- package/src/components/custom-model-provider-form.tsx +710 -0
- package/src/components/custom-model-provider-payload.test.ts +170 -0
- package/src/components/custom-model-provider-payload.ts +264 -0
- package/src/components/custom-model-provider-slug.test.ts +40 -0
- package/src/components/custom-model-provider-slug.ts +23 -0
- package/src/components/default-model-field.tsx +85 -0
- package/src/components/form-errors.test.ts +159 -0
- package/src/components/form-errors.ts +102 -0
- package/src/components/harness-availability.test.ts +45 -0
- package/src/components/harness-availability.ts +35 -0
- package/src/components/harnesses-section.stories.tsx +129 -0
- package/src/components/harnesses-section.test.tsx +188 -0
- package/src/components/harnesses-section.tsx +245 -0
- package/src/components/model-provider-usage-modal.stories.tsx +106 -0
- package/src/components/model-provider-usage-modal.test.tsx +210 -0
- package/src/components/model-provider-usage-modal.tsx +326 -0
- package/src/components/model-provider-usage-target.ts +36 -0
- package/src/components/model-providers-section.stories.tsx +345 -0
- package/src/components/model-providers-section.test.tsx +831 -0
- package/src/components/model-providers-section.tsx +645 -0
- package/src/components/provider-search.test.ts +45 -0
- package/src/components/provider-search.ts +18 -0
- package/src/components/supported-model-provider-catalog-entry.ts +19 -0
- package/src/components/test-and-save-form.tsx +166 -0
- package/src/feature.ts +12 -0
- package/src/hooks/api/model-providers.test.ts +167 -0
- package/src/hooks/api/model-providers.ts +281 -0
- package/src/index.ts +8 -0
- package/src/pages/model-provider-onboarding-page.stories.tsx +284 -0
- package/src/pages/model-provider-onboarding-page.test.tsx +368 -0
- package/src/pages/model-provider-onboarding-page.tsx +290 -0
- package/src/routes/model-provider.tsx +20 -0
- package/src/state/model-provider-onboarding.test.ts +55 -0
- package/src/state/model-provider-onboarding.ts +34 -0
- package/test/fixtures/model-providers.ts +111 -0
- package/test/setup.ts +3 -0
- package/tsconfig.build.json +9 -0
- package/tsconfig.build.tsbuildinfo +1 -0
- package/tsconfig.json +3 -0
- package/tsconfig.test.json +8 -0
- package/vercel.json +8 -0
- package/vitest.config.ts +72 -0
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { FormField, useFormField } from '@shipfox/react-ui/form-field';
|
|
3
|
+
import { cn } from '@shipfox/react-ui/utils';
|
|
4
|
+
export const LATEST_MODEL_VALUE = '__latest__';
|
|
5
|
+
export function DefaultModelField({ entry, value, error, children, onChange, onBlur }) {
|
|
6
|
+
return /*#__PURE__*/ _jsxs(FormField, {
|
|
7
|
+
label: "Default model",
|
|
8
|
+
id: `model-provider-${entry.id}-default-model`,
|
|
9
|
+
error: error,
|
|
10
|
+
description: defaultModelDescription(entry, value),
|
|
11
|
+
children: [
|
|
12
|
+
/*#__PURE__*/ _jsxs(FormFieldSelect, {
|
|
13
|
+
value: value,
|
|
14
|
+
onChange: onChange,
|
|
15
|
+
onBlur: onBlur,
|
|
16
|
+
children: [
|
|
17
|
+
/*#__PURE__*/ _jsx("option", {
|
|
18
|
+
value: LATEST_MODEL_VALUE,
|
|
19
|
+
children: "Latest"
|
|
20
|
+
}),
|
|
21
|
+
entry.models.map((model)=>/*#__PURE__*/ _jsx("option", {
|
|
22
|
+
value: model.id,
|
|
23
|
+
children: model.label
|
|
24
|
+
}, model.id))
|
|
25
|
+
]
|
|
26
|
+
}),
|
|
27
|
+
children
|
|
28
|
+
]
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
export function defaultModelFormValue(defaultModel) {
|
|
32
|
+
return defaultModel ?? LATEST_MODEL_VALUE;
|
|
33
|
+
}
|
|
34
|
+
export function selectedModelForCredentialsPayload(selectedModel) {
|
|
35
|
+
return selectedModel === LATEST_MODEL_VALUE ? null : selectedModel.trim();
|
|
36
|
+
}
|
|
37
|
+
export function selectedModelForModelPayload(selectedModel) {
|
|
38
|
+
return selectedModel === LATEST_MODEL_VALUE ? null : selectedModel.trim();
|
|
39
|
+
}
|
|
40
|
+
function defaultModelLabel(entry) {
|
|
41
|
+
const defaultModel = entry.default_model ?? entry.models[0]?.id ?? '';
|
|
42
|
+
return entry.models.find((model)=>model.id === defaultModel)?.label ?? defaultModel;
|
|
43
|
+
}
|
|
44
|
+
function defaultModelDescription(entry, selectedModel) {
|
|
45
|
+
if (selectedModel === LATEST_MODEL_VALUE) {
|
|
46
|
+
return `Latest follows the provider catalog default. Currently resolves to ${defaultModelLabel(entry)}.`;
|
|
47
|
+
}
|
|
48
|
+
if (!selectedModel) return undefined;
|
|
49
|
+
return selectedModel;
|
|
50
|
+
}
|
|
51
|
+
export function FormFieldSelect({ className, ...props }) {
|
|
52
|
+
const wiring = useFormField();
|
|
53
|
+
return /*#__PURE__*/ _jsx("select", {
|
|
54
|
+
className: cn('w-full min-w-0 rounded-6 bg-background-field-base px-8 py-6 text-sm leading-20 text-foreground-neutral-base shadow-button-neutral outline-none transition-[color,box-shadow]', 'hover:bg-background-field-hover focus-visible:shadow-border-interactive-with-active disabled:cursor-not-allowed disabled:bg-background-neutral-disabled disabled:text-foreground-neutral-disabled', 'aria-invalid:shadow-border-error', className),
|
|
55
|
+
...props,
|
|
56
|
+
...wiring
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
//# sourceMappingURL=default-model-field.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/components/default-model-field.tsx"],"sourcesContent":["import type {ModelProviderCatalogEntryDto} from '@shipfox/api-agent-dto';\nimport {FormField, useFormField} from '@shipfox/react-ui/form-field';\nimport {cn} from '@shipfox/react-ui/utils';\nimport type {ComponentProps, ReactNode} from 'react';\n\nexport const LATEST_MODEL_VALUE = '__latest__';\n\nexport function DefaultModelField({\n entry,\n value,\n error,\n children,\n onChange,\n onBlur,\n}: {\n entry: ModelProviderCatalogEntryDto;\n value: string;\n error: string | undefined;\n children?: ReactNode;\n onChange: ComponentProps<'select'>['onChange'];\n onBlur: ComponentProps<'select'>['onBlur'];\n}) {\n return (\n <FormField\n label=\"Default model\"\n id={`model-provider-${entry.id}-default-model`}\n error={error}\n description={defaultModelDescription(entry, value)}\n >\n <FormFieldSelect value={value} onChange={onChange} onBlur={onBlur}>\n <option value={LATEST_MODEL_VALUE}>Latest</option>\n {entry.models.map((model) => (\n <option key={model.id} value={model.id}>\n {model.label}\n </option>\n ))}\n </FormFieldSelect>\n {children}\n </FormField>\n );\n}\n\nexport function defaultModelFormValue(defaultModel: string | null | undefined): string {\n return defaultModel ?? LATEST_MODEL_VALUE;\n}\n\nexport function selectedModelForCredentialsPayload(selectedModel: string): string | null {\n return selectedModel === LATEST_MODEL_VALUE ? null : selectedModel.trim();\n}\n\nexport function selectedModelForModelPayload(selectedModel: string): string | null {\n return selectedModel === LATEST_MODEL_VALUE ? null : selectedModel.trim();\n}\n\nfunction defaultModelLabel(entry: ModelProviderCatalogEntryDto): string {\n const defaultModel = entry.default_model ?? entry.models[0]?.id ?? '';\n return entry.models.find((model) => model.id === defaultModel)?.label ?? defaultModel;\n}\n\nfunction defaultModelDescription(\n entry: ModelProviderCatalogEntryDto,\n selectedModel: string | undefined,\n): string | undefined {\n if (selectedModel === LATEST_MODEL_VALUE) {\n return `Latest follows the provider catalog default. Currently resolves to ${defaultModelLabel(entry)}.`;\n }\n if (!selectedModel) return undefined;\n return selectedModel;\n}\n\nexport function FormFieldSelect({className, ...props}: ComponentProps<'select'>) {\n const wiring = useFormField();\n return (\n <select\n className={cn(\n 'w-full min-w-0 rounded-6 bg-background-field-base px-8 py-6 text-sm leading-20 text-foreground-neutral-base shadow-button-neutral outline-none transition-[color,box-shadow]',\n 'hover:bg-background-field-hover focus-visible:shadow-border-interactive-with-active disabled:cursor-not-allowed disabled:bg-background-neutral-disabled disabled:text-foreground-neutral-disabled',\n 'aria-invalid:shadow-border-error',\n className,\n )}\n {...props}\n {...wiring}\n />\n );\n}\n"],"names":["FormField","useFormField","cn","LATEST_MODEL_VALUE","DefaultModelField","entry","value","error","children","onChange","onBlur","label","id","description","defaultModelDescription","FormFieldSelect","option","models","map","model","defaultModelFormValue","defaultModel","selectedModelForCredentialsPayload","selectedModel","trim","selectedModelForModelPayload","defaultModelLabel","default_model","find","undefined","className","props","wiring","select"],"mappings":";AACA,SAAQA,SAAS,EAAEC,YAAY,QAAO,+BAA+B;AACrE,SAAQC,EAAE,QAAO,0BAA0B;AAG3C,OAAO,MAAMC,qBAAqB,aAAa;AAE/C,OAAO,SAASC,kBAAkB,EAChCC,KAAK,EACLC,KAAK,EACLC,KAAK,EACLC,QAAQ,EACRC,QAAQ,EACRC,MAAM,EAQP;IACC,qBACE,MAACV;QACCW,OAAM;QACNC,IAAI,CAAC,eAAe,EAAEP,MAAMO,EAAE,CAAC,cAAc,CAAC;QAC9CL,OAAOA;QACPM,aAAaC,wBAAwBT,OAAOC;;0BAE5C,MAACS;gBAAgBT,OAAOA;gBAAOG,UAAUA;gBAAUC,QAAQA;;kCACzD,KAACM;wBAAOV,OAAOH;kCAAoB;;oBAClCE,MAAMY,MAAM,CAACC,GAAG,CAAC,CAACC,sBACjB,KAACH;4BAAsBV,OAAOa,MAAMP,EAAE;sCACnCO,MAAMR,KAAK;2BADDQ,MAAMP,EAAE;;;YAKxBJ;;;AAGP;AAEA,OAAO,SAASY,sBAAsBC,YAAuC;IAC3E,OAAOA,gBAAgBlB;AACzB;AAEA,OAAO,SAASmB,mCAAmCC,aAAqB;IACtE,OAAOA,kBAAkBpB,qBAAqB,OAAOoB,cAAcC,IAAI;AACzE;AAEA,OAAO,SAASC,6BAA6BF,aAAqB;IAChE,OAAOA,kBAAkBpB,qBAAqB,OAAOoB,cAAcC,IAAI;AACzE;AAEA,SAASE,kBAAkBrB,KAAmC;IAC5D,MAAMgB,eAAehB,MAAMsB,aAAa,IAAItB,MAAMY,MAAM,CAAC,EAAE,EAAEL,MAAM;IACnE,OAAOP,MAAMY,MAAM,CAACW,IAAI,CAAC,CAACT,QAAUA,MAAMP,EAAE,KAAKS,eAAeV,SAASU;AAC3E;AAEA,SAASP,wBACPT,KAAmC,EACnCkB,aAAiC;IAEjC,IAAIA,kBAAkBpB,oBAAoB;QACxC,OAAO,CAAC,mEAAmE,EAAEuB,kBAAkBrB,OAAO,CAAC,CAAC;IAC1G;IACA,IAAI,CAACkB,eAAe,OAAOM;IAC3B,OAAON;AACT;AAEA,OAAO,SAASR,gBAAgB,EAACe,SAAS,EAAE,GAAGC,OAAgC;IAC7E,MAAMC,SAAS/B;IACf,qBACE,KAACgC;QACCH,WAAW5B,GACT,gLACA,qMACA,oCACA4B;QAED,GAAGC,KAAK;QACR,GAAGC,MAAM;;AAGhB"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export type ModelProviderConfigFormErrorMapping = {
|
|
2
|
+
kind: 'form';
|
|
3
|
+
message: string;
|
|
4
|
+
} | {
|
|
5
|
+
kind: 'field';
|
|
6
|
+
field: 'slug' | 'base_url' | 'default_model';
|
|
7
|
+
message: string;
|
|
8
|
+
};
|
|
9
|
+
export declare function modelProviderConfigErrorToFormError(error: unknown): ModelProviderConfigFormErrorMapping;
|
|
10
|
+
export declare function modelProviderConfigErrorField(error: unknown): ModelProviderConfigFormErrorMapping;
|
|
11
|
+
//# sourceMappingURL=form-errors.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"form-errors.d.ts","sourceRoot":"","sources":["../../src/components/form-errors.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,mCAAmC,GAC3C;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAC,GAC/B;IAAC,IAAI,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,MAAM,GAAG,UAAU,GAAG,eAAe,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAC,CAAC;AAYnF,wBAAgB,mCAAmC,CACjD,KAAK,EAAE,OAAO,GACb,mCAAmC,CAQrC;AAuDD,wBAAgB,6BAA6B,CAAC,KAAK,EAAE,OAAO,GAAG,mCAAmC,CAUjG"}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { ApiError } from '@shipfox/client-api';
|
|
2
|
+
export function modelProviderConfigErrorToFormError(error) {
|
|
3
|
+
if (error instanceof ApiError) {
|
|
4
|
+
return {
|
|
5
|
+
kind: 'form',
|
|
6
|
+
message: apiErrorMessage(error)
|
|
7
|
+
};
|
|
8
|
+
}
|
|
9
|
+
if (error instanceof Error) {
|
|
10
|
+
return {
|
|
11
|
+
kind: 'form',
|
|
12
|
+
message: error.message
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
return {
|
|
16
|
+
kind: 'form',
|
|
17
|
+
message: 'Something went wrong. Try again.'
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
function apiErrorMessage(error) {
|
|
21
|
+
const details = modelProviderErrorDetails(error.details);
|
|
22
|
+
if (error.code === 'provider-validation-failed') {
|
|
23
|
+
return typeof details.message === 'string' ? details.message : error.message;
|
|
24
|
+
}
|
|
25
|
+
if (error.code === 'invalid-credential-fields') {
|
|
26
|
+
const expectedKeys = Array.isArray(details.expected_keys) ? details.expected_keys.filter((key)=>typeof key === 'string') : [];
|
|
27
|
+
if (expectedKeys.length > 0) {
|
|
28
|
+
return `Credentials must include exactly these fields: ${expectedKeys.join(', ')}.`;
|
|
29
|
+
}
|
|
30
|
+
return 'Credentials do not match the fields required by this provider.';
|
|
31
|
+
}
|
|
32
|
+
if (error.code === 'invalid-agent-model') {
|
|
33
|
+
return 'Choose a model from the models list.';
|
|
34
|
+
}
|
|
35
|
+
if (error.code === 'slug-collision') {
|
|
36
|
+
return 'A provider with this id already exists in this workspace.';
|
|
37
|
+
}
|
|
38
|
+
if (error.code === 'egress-denied') {
|
|
39
|
+
const target = typeof details.target === 'string' ? details.target : undefined;
|
|
40
|
+
const reason = typeof details.reason === 'string' ? details.reason : undefined;
|
|
41
|
+
const prefix = target && reason ? `Requests to ${target} are blocked (${reason}). ` : '';
|
|
42
|
+
return `${prefix}On Shipfox Cloud the endpoint must be reachable from the internet. Use a public HTTPS URL for hosted providers.`;
|
|
43
|
+
}
|
|
44
|
+
if (error.code === 'invalid-header-keep') {
|
|
45
|
+
return 'A kept secret header no longer exists - re-enter its value.';
|
|
46
|
+
}
|
|
47
|
+
if (error.code === 'stored-secret-base-url-changed') {
|
|
48
|
+
return 'Re-enter stored secret values before testing against a different base URL.';
|
|
49
|
+
}
|
|
50
|
+
if (error.code === 'not-found') {
|
|
51
|
+
return 'This custom provider no longer exists. Refresh and try again.';
|
|
52
|
+
}
|
|
53
|
+
if (error.code === 'workspace-secret-cap-exceeded') {
|
|
54
|
+
return typeof details.cap === 'number' ? `This workspace has reached its secrets limit of ${details.cap}.` : 'This workspace has reached its secrets limit.';
|
|
55
|
+
}
|
|
56
|
+
if (error.code === 'value-too-large') {
|
|
57
|
+
return 'A key or header value is too large to store.';
|
|
58
|
+
}
|
|
59
|
+
if (error.code === 'provider-unsupported') {
|
|
60
|
+
return 'This provider is not supported for workspace-managed credentials.';
|
|
61
|
+
}
|
|
62
|
+
if (error.code === 'provider-not-configured') {
|
|
63
|
+
return 'Configure this provider before setting it as the default.';
|
|
64
|
+
}
|
|
65
|
+
return error.message;
|
|
66
|
+
}
|
|
67
|
+
export function modelProviderConfigErrorField(error) {
|
|
68
|
+
const mapping = modelProviderConfigErrorToFormError(error);
|
|
69
|
+
if (!(error instanceof ApiError)) return mapping;
|
|
70
|
+
if (error.code === 'slug-collision') return {
|
|
71
|
+
...mapping,
|
|
72
|
+
kind: 'field',
|
|
73
|
+
field: 'slug'
|
|
74
|
+
};
|
|
75
|
+
if (error.code === 'egress-denied') return {
|
|
76
|
+
...mapping,
|
|
77
|
+
kind: 'field',
|
|
78
|
+
field: 'base_url'
|
|
79
|
+
};
|
|
80
|
+
if (error.code === 'invalid-agent-model') return {
|
|
81
|
+
...mapping,
|
|
82
|
+
kind: 'field',
|
|
83
|
+
field: 'default_model'
|
|
84
|
+
};
|
|
85
|
+
if (error.code === 'stored-secret-base-url-changed') return {
|
|
86
|
+
...mapping,
|
|
87
|
+
kind: 'field',
|
|
88
|
+
field: 'base_url'
|
|
89
|
+
};
|
|
90
|
+
return mapping;
|
|
91
|
+
}
|
|
92
|
+
function modelProviderErrorDetails(rawDetails) {
|
|
93
|
+
if (typeof rawDetails !== 'object' || rawDetails === null) return {};
|
|
94
|
+
const payload = rawDetails;
|
|
95
|
+
if (typeof payload.details === 'object' && payload.details !== null) {
|
|
96
|
+
return payload.details;
|
|
97
|
+
}
|
|
98
|
+
return payload;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
//# sourceMappingURL=form-errors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/components/form-errors.ts"],"sourcesContent":["import {ApiError} from '@shipfox/client-api';\n\nexport type ModelProviderConfigFormErrorMapping =\n | {kind: 'form'; message: string}\n | {kind: 'field'; field: 'slug' | 'base_url' | 'default_model'; message: string};\n\ntype ModelProviderErrorDetails = {\n message?: unknown;\n provider_id?: unknown;\n expected_keys?: unknown;\n reason?: unknown;\n target?: unknown;\n cap?: unknown;\n details?: unknown;\n};\n\nexport function modelProviderConfigErrorToFormError(\n error: unknown,\n): ModelProviderConfigFormErrorMapping {\n if (error instanceof ApiError) {\n return {kind: 'form', message: apiErrorMessage(error)};\n }\n if (error instanceof Error) {\n return {kind: 'form', message: error.message};\n }\n return {kind: 'form', message: 'Something went wrong. Try again.'};\n}\n\nfunction apiErrorMessage(error: ApiError): string {\n const details = modelProviderErrorDetails(error.details);\n\n if (error.code === 'provider-validation-failed') {\n return typeof details.message === 'string' ? details.message : error.message;\n }\n if (error.code === 'invalid-credential-fields') {\n const expectedKeys = Array.isArray(details.expected_keys)\n ? details.expected_keys.filter((key): key is string => typeof key === 'string')\n : [];\n if (expectedKeys.length > 0) {\n return `Credentials must include exactly these fields: ${expectedKeys.join(', ')}.`;\n }\n return 'Credentials do not match the fields required by this provider.';\n }\n if (error.code === 'invalid-agent-model') {\n return 'Choose a model from the models list.';\n }\n if (error.code === 'slug-collision') {\n return 'A provider with this id already exists in this workspace.';\n }\n if (error.code === 'egress-denied') {\n const target = typeof details.target === 'string' ? details.target : undefined;\n const reason = typeof details.reason === 'string' ? details.reason : undefined;\n const prefix = target && reason ? `Requests to ${target} are blocked (${reason}). ` : '';\n return `${prefix}On Shipfox Cloud the endpoint must be reachable from the internet. Use a public HTTPS URL for hosted providers.`;\n }\n if (error.code === 'invalid-header-keep') {\n return 'A kept secret header no longer exists - re-enter its value.';\n }\n if (error.code === 'stored-secret-base-url-changed') {\n return 'Re-enter stored secret values before testing against a different base URL.';\n }\n if (error.code === 'not-found') {\n return 'This custom provider no longer exists. Refresh and try again.';\n }\n if (error.code === 'workspace-secret-cap-exceeded') {\n return typeof details.cap === 'number'\n ? `This workspace has reached its secrets limit of ${details.cap}.`\n : 'This workspace has reached its secrets limit.';\n }\n if (error.code === 'value-too-large') {\n return 'A key or header value is too large to store.';\n }\n if (error.code === 'provider-unsupported') {\n return 'This provider is not supported for workspace-managed credentials.';\n }\n if (error.code === 'provider-not-configured') {\n return 'Configure this provider before setting it as the default.';\n }\n return error.message;\n}\n\nexport function modelProviderConfigErrorField(error: unknown): ModelProviderConfigFormErrorMapping {\n const mapping = modelProviderConfigErrorToFormError(error);\n if (!(error instanceof ApiError)) return mapping;\n if (error.code === 'slug-collision') return {...mapping, kind: 'field', field: 'slug'};\n if (error.code === 'egress-denied') return {...mapping, kind: 'field', field: 'base_url'};\n if (error.code === 'invalid-agent-model')\n return {...mapping, kind: 'field', field: 'default_model'};\n if (error.code === 'stored-secret-base-url-changed')\n return {...mapping, kind: 'field', field: 'base_url'};\n return mapping;\n}\n\nfunction modelProviderErrorDetails(rawDetails: unknown): ModelProviderErrorDetails {\n if (typeof rawDetails !== 'object' || rawDetails === null) return {};\n\n const payload = rawDetails as ModelProviderErrorDetails;\n if (typeof payload.details === 'object' && payload.details !== null) {\n return payload.details as ModelProviderErrorDetails;\n }\n return payload;\n}\n"],"names":["ApiError","modelProviderConfigErrorToFormError","error","kind","message","apiErrorMessage","Error","details","modelProviderErrorDetails","code","expectedKeys","Array","isArray","expected_keys","filter","key","length","join","target","undefined","reason","prefix","cap","modelProviderConfigErrorField","mapping","field","rawDetails","payload"],"mappings":"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"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { type Harness, type HarnessDescriptor, type ModelProviderConfigResponseDto } from '@shipfox/api-agent-dto';
|
|
2
|
+
export declare function configSupportsHarness(config: ModelProviderConfigResponseDto, descriptor: HarnessDescriptor): boolean;
|
|
3
|
+
export declare function isHarnessAvailable(descriptor: HarnessDescriptor, configs: ModelProviderConfigResponseDto[]): boolean;
|
|
4
|
+
export declare function compatibleHarnessIds({ isCustom, providerId, }: {
|
|
5
|
+
isCustom: boolean;
|
|
6
|
+
providerId: string;
|
|
7
|
+
}): Harness[];
|
|
8
|
+
//# sourceMappingURL=harness-availability.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"harness-availability.d.ts","sourceRoot":"","sources":["../../src/components/harness-availability.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,OAAO,EACZ,KAAK,iBAAiB,EAEtB,KAAK,8BAA8B,EACpC,MAAM,wBAAwB,CAAC;AAEhC,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,8BAA8B,EACtC,UAAU,EAAE,iBAAiB,GAC5B,OAAO,CAGT;AAED,wBAAgB,kBAAkB,CAChC,UAAU,EAAE,iBAAiB,EAC7B,OAAO,EAAE,8BAA8B,EAAE,GACxC,OAAO,CAET;AAED,wBAAgB,oBAAoB,CAAC,EACnC,QAAQ,EACR,UAAU,GACX,EAAE;IACD,QAAQ,EAAE,OAAO,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;CACpB,GAAG,OAAO,EAAE,CAKZ"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { DEFAULT_HARNESS, listHarnessDescriptors } from '@shipfox/api-agent-dto';
|
|
2
|
+
export function configSupportsHarness(config, descriptor) {
|
|
3
|
+
if (config.kind === 'custom') return descriptor.id === DEFAULT_HARNESS;
|
|
4
|
+
return descriptor.supportedProviderIds.includes(config.provider_id);
|
|
5
|
+
}
|
|
6
|
+
export function isHarnessAvailable(descriptor, configs) {
|
|
7
|
+
return configs.some((config)=>configSupportsHarness(config, descriptor));
|
|
8
|
+
}
|
|
9
|
+
export function compatibleHarnessIds({ isCustom, providerId }) {
|
|
10
|
+
if (isCustom) return [
|
|
11
|
+
DEFAULT_HARNESS
|
|
12
|
+
];
|
|
13
|
+
return listHarnessDescriptors().filter((descriptor)=>descriptor.supportedProviderIds.includes(providerId)).map((descriptor)=>descriptor.id);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
//# sourceMappingURL=harness-availability.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/components/harness-availability.ts"],"sourcesContent":["import {\n DEFAULT_HARNESS,\n type Harness,\n type HarnessDescriptor,\n listHarnessDescriptors,\n type ModelProviderConfigResponseDto,\n} from '@shipfox/api-agent-dto';\n\nexport function configSupportsHarness(\n config: ModelProviderConfigResponseDto,\n descriptor: HarnessDescriptor,\n): boolean {\n if (config.kind === 'custom') return descriptor.id === DEFAULT_HARNESS;\n return descriptor.supportedProviderIds.includes(config.provider_id);\n}\n\nexport function isHarnessAvailable(\n descriptor: HarnessDescriptor,\n configs: ModelProviderConfigResponseDto[],\n): boolean {\n return configs.some((config) => configSupportsHarness(config, descriptor));\n}\n\nexport function compatibleHarnessIds({\n isCustom,\n providerId,\n}: {\n isCustom: boolean;\n providerId: string;\n}): Harness[] {\n if (isCustom) return [DEFAULT_HARNESS];\n return listHarnessDescriptors()\n .filter((descriptor) => descriptor.supportedProviderIds.includes(providerId))\n .map((descriptor) => descriptor.id);\n}\n"],"names":["DEFAULT_HARNESS","listHarnessDescriptors","configSupportsHarness","config","descriptor","kind","id","supportedProviderIds","includes","provider_id","isHarnessAvailable","configs","some","compatibleHarnessIds","isCustom","providerId","filter","map"],"mappings":"AAAA,SACEA,eAAe,EAGfC,sBAAsB,QAEjB,yBAAyB;AAEhC,OAAO,SAASC,sBACdC,MAAsC,EACtCC,UAA6B;IAE7B,IAAID,OAAOE,IAAI,KAAK,UAAU,OAAOD,WAAWE,EAAE,KAAKN;IACvD,OAAOI,WAAWG,oBAAoB,CAACC,QAAQ,CAACL,OAAOM,WAAW;AACpE;AAEA,OAAO,SAASC,mBACdN,UAA6B,EAC7BO,OAAyC;IAEzC,OAAOA,QAAQC,IAAI,CAAC,CAACT,SAAWD,sBAAsBC,QAAQC;AAChE;AAEA,OAAO,SAASS,qBAAqB,EACnCC,QAAQ,EACRC,UAAU,EAIX;IACC,IAAID,UAAU,OAAO;QAACd;KAAgB;IACtC,OAAOC,yBACJe,MAAM,CAAC,CAACZ,aAAeA,WAAWG,oBAAoB,CAACC,QAAQ,CAACO,aAChEE,GAAG,CAAC,CAACb,aAAeA,WAAWE,EAAE;AACtC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"harnesses-section.d.ts","sourceRoot":"","sources":["../../src/components/harnesses-section.tsx"],"names":[],"mappings":"AAgCA,wBAAgB,yBAAyB,CAAC,EAAC,WAAW,EAAC,EAAE;IAAC,WAAW,EAAE,MAAM,CAAA;CAAC,+BA+F7E"}
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { DEFAULT_HARNESS, listHarnessDescriptors } from '@shipfox/api-agent-dto';
|
|
3
|
+
import { QueryLoadError } from '@shipfox/client-ui';
|
|
4
|
+
import { IconButton } from '@shipfox/react-ui/button';
|
|
5
|
+
import { Callout } from '@shipfox/react-ui/callout';
|
|
6
|
+
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from '@shipfox/react-ui/dropdown-menu';
|
|
7
|
+
import { Icon } from '@shipfox/react-ui/icon';
|
|
8
|
+
import { Skeleton } from '@shipfox/react-ui/skeleton';
|
|
9
|
+
import { toast } from '@shipfox/react-ui/toast';
|
|
10
|
+
import { Tooltip, TooltipContent, TooltipTrigger } from '@shipfox/react-ui/tooltip';
|
|
11
|
+
import { Header, Text } from '@shipfox/react-ui/typography';
|
|
12
|
+
import { cn } from '@shipfox/react-ui/utils';
|
|
13
|
+
import { useRef, useState } from 'react';
|
|
14
|
+
import { useModelProviderConfigsQuery, useSetDefaultHarnessMutation } from '#hooks/api/model-providers.js';
|
|
15
|
+
import { modelProviderConfigErrorToFormError } from './form-errors.js';
|
|
16
|
+
import { isHarnessAvailable } from './harness-availability.js';
|
|
17
|
+
const SURFACE_CLASS = 'overflow-hidden rounded-8 border border-border-neutral-base bg-background-neutral-base';
|
|
18
|
+
export function WorkspaceHarnessesSection({ workspaceId }) {
|
|
19
|
+
const configsQuery = useModelProviderConfigsQuery(workspaceId);
|
|
20
|
+
const setDefaultHarness = useSetDefaultHarnessMutation();
|
|
21
|
+
const activeWorkspaceIdRef = useRef(workspaceId);
|
|
22
|
+
const defaultRequestSeqRef = useRef(0);
|
|
23
|
+
const activeDefaultRequestRef = useRef(null);
|
|
24
|
+
const [pendingDefaultHarness, setPendingDefaultHarness] = useState(null);
|
|
25
|
+
const [defaultError, setDefaultError] = useState();
|
|
26
|
+
const configs = configsQuery.data?.configs ?? [];
|
|
27
|
+
const defaultHarnessId = configsQuery.data?.default_harness_id ?? DEFAULT_HARNESS;
|
|
28
|
+
activeWorkspaceIdRef.current = workspaceId;
|
|
29
|
+
function isActiveDefaultRequest(request) {
|
|
30
|
+
const activeRequest = activeDefaultRequestRef.current;
|
|
31
|
+
return activeWorkspaceIdRef.current === request.workspaceId && activeRequest?.workspaceId === request.workspaceId && activeRequest.id === request.id;
|
|
32
|
+
}
|
|
33
|
+
async function handleSetDefault(harness) {
|
|
34
|
+
if (activeDefaultRequestRef.current?.workspaceId === workspaceId) return;
|
|
35
|
+
const request = {
|
|
36
|
+
workspaceId,
|
|
37
|
+
id: defaultRequestSeqRef.current + 1
|
|
38
|
+
};
|
|
39
|
+
defaultRequestSeqRef.current = request.id;
|
|
40
|
+
activeDefaultRequestRef.current = request;
|
|
41
|
+
setPendingDefaultHarness({
|
|
42
|
+
workspaceId,
|
|
43
|
+
harnessId: harness.id
|
|
44
|
+
});
|
|
45
|
+
setDefaultError(undefined);
|
|
46
|
+
try {
|
|
47
|
+
await setDefaultHarness.mutateAsync({
|
|
48
|
+
workspaceId,
|
|
49
|
+
body: {
|
|
50
|
+
harness_id: harness.id
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
if (!isActiveDefaultRequest(request)) return;
|
|
54
|
+
toast.success(`${harness.label} is now the default harness`);
|
|
55
|
+
} catch (error) {
|
|
56
|
+
if (!isActiveDefaultRequest(request)) return;
|
|
57
|
+
const mapped = modelProviderConfigErrorToFormError(error);
|
|
58
|
+
setDefaultError({
|
|
59
|
+
workspaceId,
|
|
60
|
+
harnessId: harness.id,
|
|
61
|
+
message: mapped.message || 'Could not save default harness. Try again.'
|
|
62
|
+
});
|
|
63
|
+
} finally{
|
|
64
|
+
if (isActiveDefaultRequest(request)) {
|
|
65
|
+
activeDefaultRequestRef.current = null;
|
|
66
|
+
setPendingDefaultHarness(null);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return /*#__PURE__*/ _jsxs("section", {
|
|
71
|
+
className: "flex flex-col gap-16",
|
|
72
|
+
"aria-label": "Harnesses",
|
|
73
|
+
children: [
|
|
74
|
+
/*#__PURE__*/ _jsxs("div", {
|
|
75
|
+
className: "flex flex-col gap-4",
|
|
76
|
+
children: [
|
|
77
|
+
/*#__PURE__*/ _jsx(Header, {
|
|
78
|
+
variant: "h3",
|
|
79
|
+
children: "Harnesses"
|
|
80
|
+
}),
|
|
81
|
+
/*#__PURE__*/ _jsx(Text, {
|
|
82
|
+
size: "sm",
|
|
83
|
+
className: "text-foreground-neutral-muted",
|
|
84
|
+
children: "Harnesses available to run agent steps in this workspace."
|
|
85
|
+
})
|
|
86
|
+
]
|
|
87
|
+
}),
|
|
88
|
+
configsQuery.isPending ? /*#__PURE__*/ _jsx(HarnessRowsSkeleton, {}) : null,
|
|
89
|
+
configsQuery.isError && configsQuery.data === undefined ? /*#__PURE__*/ _jsx("div", {
|
|
90
|
+
className: cn(SURFACE_CLASS, 'px-16'),
|
|
91
|
+
children: /*#__PURE__*/ _jsx(QueryLoadError, {
|
|
92
|
+
query: configsQuery,
|
|
93
|
+
subject: "harnesses"
|
|
94
|
+
})
|
|
95
|
+
}) : null,
|
|
96
|
+
configsQuery.data !== undefined ? /*#__PURE__*/ _jsx("ul", {
|
|
97
|
+
className: cn('divide-y divide-border-neutral-base', SURFACE_CLASS),
|
|
98
|
+
children: listHarnessDescriptors().map((harness)=>/*#__PURE__*/ _jsx(HarnessRow, {
|
|
99
|
+
harness: harness,
|
|
100
|
+
isDefault: harness.id === defaultHarnessId,
|
|
101
|
+
isAvailable: isHarnessAvailable(harness, configs),
|
|
102
|
+
isSettingDefault: pendingDefaultHarness?.workspaceId === workspaceId,
|
|
103
|
+
defaultError: defaultError?.workspaceId === workspaceId && defaultError.harnessId === harness.id ? defaultError.message : undefined,
|
|
104
|
+
onSetDefault: handleSetDefault
|
|
105
|
+
}, harness.id))
|
|
106
|
+
}) : null
|
|
107
|
+
]
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
function HarnessRow({ harness, isDefault, isAvailable, isSettingDefault, defaultError, onSetDefault }) {
|
|
111
|
+
const unavailableCopy = harnessUnavailableCopy(isDefault);
|
|
112
|
+
return /*#__PURE__*/ _jsxs("li", {
|
|
113
|
+
className: "flex flex-col gap-10 px-16 py-12 transition-colors hover:bg-background-components-hover",
|
|
114
|
+
children: [
|
|
115
|
+
/*#__PURE__*/ _jsxs("div", {
|
|
116
|
+
className: "flex items-center justify-between gap-12",
|
|
117
|
+
children: [
|
|
118
|
+
/*#__PURE__*/ _jsxs("div", {
|
|
119
|
+
className: "flex min-w-0 items-center gap-8",
|
|
120
|
+
children: [
|
|
121
|
+
isDefault ? /*#__PURE__*/ _jsxs(_Fragment, {
|
|
122
|
+
children: [
|
|
123
|
+
/*#__PURE__*/ _jsxs(Tooltip, {
|
|
124
|
+
children: [
|
|
125
|
+
/*#__PURE__*/ _jsx(TooltipTrigger, {
|
|
126
|
+
asChild: true,
|
|
127
|
+
children: /*#__PURE__*/ _jsx("span", {
|
|
128
|
+
className: "inline-flex size-16 shrink-0 items-center justify-center",
|
|
129
|
+
children: /*#__PURE__*/ _jsx(Icon, {
|
|
130
|
+
name: "starLine",
|
|
131
|
+
className: "size-16 text-foreground-neutral-muted",
|
|
132
|
+
"aria-hidden": true
|
|
133
|
+
})
|
|
134
|
+
})
|
|
135
|
+
}),
|
|
136
|
+
/*#__PURE__*/ _jsx(TooltipContent, {
|
|
137
|
+
children: "Default harness"
|
|
138
|
+
})
|
|
139
|
+
]
|
|
140
|
+
}),
|
|
141
|
+
/*#__PURE__*/ _jsx("span", {
|
|
142
|
+
className: "sr-only",
|
|
143
|
+
children: "Default harness"
|
|
144
|
+
})
|
|
145
|
+
]
|
|
146
|
+
}) : null,
|
|
147
|
+
/*#__PURE__*/ _jsx(Text, {
|
|
148
|
+
size: "md",
|
|
149
|
+
bold: true,
|
|
150
|
+
className: "truncate",
|
|
151
|
+
children: harness.label
|
|
152
|
+
}),
|
|
153
|
+
!isAvailable ? /*#__PURE__*/ _jsxs(_Fragment, {
|
|
154
|
+
children: [
|
|
155
|
+
/*#__PURE__*/ _jsxs(Tooltip, {
|
|
156
|
+
children: [
|
|
157
|
+
/*#__PURE__*/ _jsx(TooltipTrigger, {
|
|
158
|
+
asChild: true,
|
|
159
|
+
children: /*#__PURE__*/ _jsx("span", {
|
|
160
|
+
className: "inline-flex size-16 shrink-0 items-center justify-center",
|
|
161
|
+
children: /*#__PURE__*/ _jsx(Icon, {
|
|
162
|
+
name: "errorWarningLine",
|
|
163
|
+
className: "size-16 text-foreground-warning-base",
|
|
164
|
+
"aria-hidden": true
|
|
165
|
+
})
|
|
166
|
+
})
|
|
167
|
+
}),
|
|
168
|
+
/*#__PURE__*/ _jsx(TooltipContent, {
|
|
169
|
+
children: unavailableCopy
|
|
170
|
+
})
|
|
171
|
+
]
|
|
172
|
+
}),
|
|
173
|
+
/*#__PURE__*/ _jsx("span", {
|
|
174
|
+
className: "sr-only",
|
|
175
|
+
children: unavailableCopy
|
|
176
|
+
})
|
|
177
|
+
]
|
|
178
|
+
}) : null
|
|
179
|
+
]
|
|
180
|
+
}),
|
|
181
|
+
!isDefault ? /*#__PURE__*/ _jsxs(DropdownMenu, {
|
|
182
|
+
children: [
|
|
183
|
+
/*#__PURE__*/ _jsx(DropdownMenuTrigger, {
|
|
184
|
+
asChild: true,
|
|
185
|
+
children: /*#__PURE__*/ _jsx(IconButton, {
|
|
186
|
+
size: "sm",
|
|
187
|
+
variant: "transparent",
|
|
188
|
+
icon: "more2Line",
|
|
189
|
+
disabled: isSettingDefault,
|
|
190
|
+
"aria-label": `Open ${harness.label} harness actions`
|
|
191
|
+
})
|
|
192
|
+
}),
|
|
193
|
+
/*#__PURE__*/ _jsx(DropdownMenuContent, {
|
|
194
|
+
align: "end",
|
|
195
|
+
children: /*#__PURE__*/ _jsx(DropdownMenuItem, {
|
|
196
|
+
icon: "starLine",
|
|
197
|
+
disabled: isSettingDefault || !isAvailable,
|
|
198
|
+
onSelect: ()=>{
|
|
199
|
+
onSetDefault(harness);
|
|
200
|
+
},
|
|
201
|
+
children: "Set as default"
|
|
202
|
+
})
|
|
203
|
+
})
|
|
204
|
+
]
|
|
205
|
+
}) : null
|
|
206
|
+
]
|
|
207
|
+
}),
|
|
208
|
+
defaultError ? /*#__PURE__*/ _jsx(Callout, {
|
|
209
|
+
role: "alert",
|
|
210
|
+
type: "error",
|
|
211
|
+
children: /*#__PURE__*/ _jsx(Text, {
|
|
212
|
+
size: "sm",
|
|
213
|
+
children: defaultError
|
|
214
|
+
})
|
|
215
|
+
}) : null
|
|
216
|
+
]
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
function harnessUnavailableCopy(isDefault) {
|
|
220
|
+
const base = 'Configure a compatible model provider to use this harness.';
|
|
221
|
+
if (!isDefault) return base;
|
|
222
|
+
return `${base} The stored default harness cannot currently run in this workspace.`;
|
|
223
|
+
}
|
|
224
|
+
function HarnessRowsSkeleton() {
|
|
225
|
+
return /*#__PURE__*/ _jsx("ul", {
|
|
226
|
+
role: "status",
|
|
227
|
+
"aria-label": "Loading harnesses",
|
|
228
|
+
className: cn('divide-y divide-border-neutral-base', SURFACE_CLASS),
|
|
229
|
+
children: [
|
|
230
|
+
0,
|
|
231
|
+
1
|
|
232
|
+
].map((row)=>/*#__PURE__*/ _jsxs("li", {
|
|
233
|
+
className: "flex items-center gap-12 px-16 py-12",
|
|
234
|
+
children: [
|
|
235
|
+
/*#__PURE__*/ _jsx(Skeleton, {
|
|
236
|
+
className: "size-16 shrink-0"
|
|
237
|
+
}),
|
|
238
|
+
/*#__PURE__*/ _jsx(Skeleton, {
|
|
239
|
+
className: "h-16 w-120"
|
|
240
|
+
}),
|
|
241
|
+
/*#__PURE__*/ _jsx(Skeleton, {
|
|
242
|
+
className: "ml-auto size-28 shrink-0"
|
|
243
|
+
})
|
|
244
|
+
]
|
|
245
|
+
}, row))
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
//# sourceMappingURL=harnesses-section.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/components/harnesses-section.tsx"],"sourcesContent":["import {\n DEFAULT_HARNESS,\n type Harness,\n type HarnessDescriptor,\n listHarnessDescriptors,\n} from '@shipfox/api-agent-dto';\nimport {QueryLoadError} from '@shipfox/client-ui';\nimport {IconButton} from '@shipfox/react-ui/button';\nimport {Callout} from '@shipfox/react-ui/callout';\nimport {\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuTrigger,\n} from '@shipfox/react-ui/dropdown-menu';\nimport {Icon} from '@shipfox/react-ui/icon';\nimport {Skeleton} from '@shipfox/react-ui/skeleton';\nimport {toast} from '@shipfox/react-ui/toast';\nimport {Tooltip, TooltipContent, TooltipTrigger} from '@shipfox/react-ui/tooltip';\nimport {Header, Text} from '@shipfox/react-ui/typography';\nimport {cn} from '@shipfox/react-ui/utils';\nimport {useRef, useState} from 'react';\nimport {\n useModelProviderConfigsQuery,\n useSetDefaultHarnessMutation,\n} from '#hooks/api/model-providers.js';\nimport {modelProviderConfigErrorToFormError} from './form-errors.js';\nimport {isHarnessAvailable} from './harness-availability.js';\n\nconst SURFACE_CLASS =\n 'overflow-hidden rounded-8 border border-border-neutral-base bg-background-neutral-base';\n\nexport function WorkspaceHarnessesSection({workspaceId}: {workspaceId: string}) {\n const configsQuery = useModelProviderConfigsQuery(workspaceId);\n const setDefaultHarness = useSetDefaultHarnessMutation();\n const activeWorkspaceIdRef = useRef(workspaceId);\n const defaultRequestSeqRef = useRef(0);\n const activeDefaultRequestRef = useRef<{workspaceId: string; id: number} | null>(null);\n const [pendingDefaultHarness, setPendingDefaultHarness] = useState<{\n workspaceId: string;\n harnessId: Harness;\n } | null>(null);\n const [defaultError, setDefaultError] = useState<\n {workspaceId: string; harnessId: Harness; message: string} | undefined\n >();\n const configs = configsQuery.data?.configs ?? [];\n const defaultHarnessId = configsQuery.data?.default_harness_id ?? DEFAULT_HARNESS;\n activeWorkspaceIdRef.current = workspaceId;\n\n function isActiveDefaultRequest(request: {workspaceId: string; id: number}) {\n const activeRequest = activeDefaultRequestRef.current;\n return (\n activeWorkspaceIdRef.current === request.workspaceId &&\n activeRequest?.workspaceId === request.workspaceId &&\n activeRequest.id === request.id\n );\n }\n\n async function handleSetDefault(harness: HarnessDescriptor) {\n if (activeDefaultRequestRef.current?.workspaceId === workspaceId) return;\n\n const request = {workspaceId, id: defaultRequestSeqRef.current + 1};\n defaultRequestSeqRef.current = request.id;\n activeDefaultRequestRef.current = request;\n setPendingDefaultHarness({workspaceId, harnessId: harness.id});\n setDefaultError(undefined);\n try {\n await setDefaultHarness.mutateAsync({\n workspaceId,\n body: {harness_id: harness.id},\n });\n if (!isActiveDefaultRequest(request)) return;\n toast.success(`${harness.label} is now the default harness`);\n } catch (error) {\n if (!isActiveDefaultRequest(request)) return;\n const mapped = modelProviderConfigErrorToFormError(error);\n setDefaultError({\n workspaceId,\n harnessId: harness.id,\n message: mapped.message || 'Could not save default harness. Try again.',\n });\n } finally {\n if (isActiveDefaultRequest(request)) {\n activeDefaultRequestRef.current = null;\n setPendingDefaultHarness(null);\n }\n }\n }\n\n return (\n <section className=\"flex flex-col gap-16\" aria-label=\"Harnesses\">\n <div className=\"flex flex-col gap-4\">\n <Header variant=\"h3\">Harnesses</Header>\n <Text size=\"sm\" className=\"text-foreground-neutral-muted\">\n Harnesses available to run agent steps in this workspace.\n </Text>\n </div>\n\n {configsQuery.isPending ? <HarnessRowsSkeleton /> : null}\n\n {configsQuery.isError && configsQuery.data === undefined ? (\n <div className={cn(SURFACE_CLASS, 'px-16')}>\n <QueryLoadError query={configsQuery} subject=\"harnesses\" />\n </div>\n ) : null}\n\n {configsQuery.data !== undefined ? (\n <ul className={cn('divide-y divide-border-neutral-base', SURFACE_CLASS)}>\n {listHarnessDescriptors().map((harness) => (\n <HarnessRow\n key={harness.id}\n harness={harness}\n isDefault={harness.id === defaultHarnessId}\n isAvailable={isHarnessAvailable(harness, configs)}\n isSettingDefault={pendingDefaultHarness?.workspaceId === workspaceId}\n defaultError={\n defaultError?.workspaceId === workspaceId && defaultError.harnessId === harness.id\n ? defaultError.message\n : undefined\n }\n onSetDefault={handleSetDefault}\n />\n ))}\n </ul>\n ) : null}\n </section>\n );\n}\n\nfunction HarnessRow({\n harness,\n isDefault,\n isAvailable,\n isSettingDefault,\n defaultError,\n onSetDefault,\n}: {\n harness: HarnessDescriptor;\n isDefault: boolean;\n isAvailable: boolean;\n isSettingDefault: boolean;\n defaultError: string | undefined;\n onSetDefault: (harness: HarnessDescriptor) => void;\n}) {\n const unavailableCopy = harnessUnavailableCopy(isDefault);\n\n return (\n <li className=\"flex flex-col gap-10 px-16 py-12 transition-colors hover:bg-background-components-hover\">\n <div className=\"flex items-center justify-between gap-12\">\n <div className=\"flex min-w-0 items-center gap-8\">\n {isDefault ? (\n <>\n <Tooltip>\n <TooltipTrigger asChild>\n <span className=\"inline-flex size-16 shrink-0 items-center justify-center\">\n <Icon\n name=\"starLine\"\n className=\"size-16 text-foreground-neutral-muted\"\n aria-hidden\n />\n </span>\n </TooltipTrigger>\n <TooltipContent>Default harness</TooltipContent>\n </Tooltip>\n <span className=\"sr-only\">Default harness</span>\n </>\n ) : null}\n <Text size=\"md\" bold className=\"truncate\">\n {harness.label}\n </Text>\n {!isAvailable ? (\n <>\n <Tooltip>\n <TooltipTrigger asChild>\n <span className=\"inline-flex size-16 shrink-0 items-center justify-center\">\n <Icon\n name=\"errorWarningLine\"\n className=\"size-16 text-foreground-warning-base\"\n aria-hidden\n />\n </span>\n </TooltipTrigger>\n <TooltipContent>{unavailableCopy}</TooltipContent>\n </Tooltip>\n <span className=\"sr-only\">{unavailableCopy}</span>\n </>\n ) : null}\n </div>\n {!isDefault ? (\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <IconButton\n size=\"sm\"\n variant=\"transparent\"\n icon=\"more2Line\"\n disabled={isSettingDefault}\n aria-label={`Open ${harness.label} harness actions`}\n />\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"end\">\n <DropdownMenuItem\n icon=\"starLine\"\n disabled={isSettingDefault || !isAvailable}\n onSelect={() => {\n onSetDefault(harness);\n }}\n >\n Set as default\n </DropdownMenuItem>\n </DropdownMenuContent>\n </DropdownMenu>\n ) : null}\n </div>\n {defaultError ? (\n <Callout role=\"alert\" type=\"error\">\n <Text size=\"sm\">{defaultError}</Text>\n </Callout>\n ) : null}\n </li>\n );\n}\n\nfunction harnessUnavailableCopy(isDefault: boolean): string {\n const base = 'Configure a compatible model provider to use this harness.';\n if (!isDefault) return base;\n return `${base} The stored default harness cannot currently run in this workspace.`;\n}\n\nfunction HarnessRowsSkeleton() {\n return (\n <ul\n role=\"status\"\n aria-label=\"Loading harnesses\"\n className={cn('divide-y divide-border-neutral-base', SURFACE_CLASS)}\n >\n {[0, 1].map((row) => (\n <li key={row} className=\"flex items-center gap-12 px-16 py-12\">\n <Skeleton className=\"size-16 shrink-0\" />\n <Skeleton className=\"h-16 w-120\" />\n <Skeleton className=\"ml-auto size-28 shrink-0\" />\n </li>\n ))}\n </ul>\n );\n}\n"],"names":["DEFAULT_HARNESS","listHarnessDescriptors","QueryLoadError","IconButton","Callout","DropdownMenu","DropdownMenuContent","DropdownMenuItem","DropdownMenuTrigger","Icon","Skeleton","toast","Tooltip","TooltipContent","TooltipTrigger","Header","Text","cn","useRef","useState","useModelProviderConfigsQuery","useSetDefaultHarnessMutation","modelProviderConfigErrorToFormError","isHarnessAvailable","SURFACE_CLASS","WorkspaceHarnessesSection","workspaceId","configsQuery","setDefaultHarness","activeWorkspaceIdRef","defaultRequestSeqRef","activeDefaultRequestRef","pendingDefaultHarness","setPendingDefaultHarness","defaultError","setDefaultError","configs","data","defaultHarnessId","default_harness_id","current","isActiveDefaultRequest","request","activeRequest","id","handleSetDefault","harness","harnessId","undefined","mutateAsync","body","harness_id","success","label","error","mapped","message","section","className","aria-label","div","variant","size","isPending","HarnessRowsSkeleton","isError","query","subject","ul","map","HarnessRow","isDefault","isAvailable","isSettingDefault","onSetDefault","unavailableCopy","harnessUnavailableCopy","li","asChild","span","name","aria-hidden","bold","icon","disabled","align","onSelect","role","type","base","row"],"mappings":"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"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { StoryObj } from '@storybook/react';
|
|
2
|
+
type Scenario = 'default-pi' | 'default-claude' | 'claude-unavailable' | 'default-claude-unavailable' | 'loading' | 'error';
|
|
3
|
+
interface HarnessesStoryProps {
|
|
4
|
+
scenario: Scenario;
|
|
5
|
+
}
|
|
6
|
+
declare function HarnessesStory({ scenario }: HarnessesStoryProps): import("react").JSX.Element;
|
|
7
|
+
declare const meta: {
|
|
8
|
+
title: string;
|
|
9
|
+
component: typeof HarnessesStory;
|
|
10
|
+
parameters: {
|
|
11
|
+
layout: string;
|
|
12
|
+
};
|
|
13
|
+
args: {
|
|
14
|
+
scenario: "default-pi";
|
|
15
|
+
};
|
|
16
|
+
};
|
|
17
|
+
export default meta;
|
|
18
|
+
type Story = StoryObj<typeof meta>;
|
|
19
|
+
export declare const Playground: Story;
|
|
20
|
+
export declare const DefaultClaude: Story;
|
|
21
|
+
export declare const ClaudeUnavailable: Story;
|
|
22
|
+
export declare const DefaultClaudeUnavailable: Story;
|
|
23
|
+
export declare const Loading: Story;
|
|
24
|
+
export declare const ErrorState: Story;
|
|
25
|
+
//# sourceMappingURL=harnesses-section.stories.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"harnesses-section.stories.d.ts","sourceRoot":"","sources":["../../src/components/harnesses-section.stories.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAO,QAAQ,EAAC,MAAM,kBAAkB,CAAC;AAOrD,KAAK,QAAQ,GACT,YAAY,GACZ,gBAAgB,GAChB,oBAAoB,GACpB,4BAA4B,GAC5B,SAAS,GACT,OAAO,CAAC;AAEZ,UAAU,mBAAmB;IAC3B,QAAQ,EAAE,QAAQ,CAAC;CACpB;AAED,iBAAS,cAAc,CAAC,EAAC,QAAQ,EAAC,EAAE,mBAAmB,+BAmBtD;AAED,QAAA,MAAM,IAAI;;;;;;;;;CAK6B,CAAC;AAExC,eAAe,IAAI,CAAC;AACpB,KAAK,KAAK,GAAG,QAAQ,CAAC,OAAO,IAAI,CAAC,CAAC;AAEnC,eAAO,MAAM,UAAU,EAAE,KAAU,CAAC;AAEpC,eAAO,MAAM,aAAa,EAAE,KAE3B,CAAC;AAEF,eAAO,MAAM,iBAAiB,EAAE,KAE/B,CAAC;AAEF,eAAO,MAAM,wBAAwB,EAAE,KAEtC,CAAC;AAEF,eAAO,MAAM,OAAO,EAAE,KAErB,CAAC;AAEF,eAAO,MAAM,UAAU,EAAE,KAExB,CAAC"}
|