@shipfox/client-agent 22.0.2 → 22.0.3
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/preview.tsx +2 -2
- package/.turbo/turbo-build.log +1 -1
- package/CHANGELOG.md +19 -0
- package/dist/components/form-errors.d.ts.map +1 -1
- package/dist/components/form-errors.js +3 -0
- package/dist/components/form-errors.js.map +1 -1
- package/dist/components/harness-availability.d.ts +2 -2
- package/dist/components/harness-availability.d.ts.map +1 -1
- package/dist/components/harness-availability.js +4 -3
- 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 -2
- package/dist/components/harnesses-section.js.map +1 -1
- package/dist/components/model-provider-usage-modal.d.ts.map +1 -1
- package/dist/components/model-provider-usage-modal.js +23 -5
- package/dist/components/model-provider-usage-modal.js.map +1 -1
- package/dist/components/model-provider-usage-target.d.ts +6 -6
- package/dist/components/model-provider-usage-target.d.ts.map +1 -1
- package/dist/components/model-provider-usage-target.js +3 -1
- 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 +277 -170
- package/dist/components/model-providers-section.js.map +1 -1
- package/dist/components/model-providers-section.stories.d.ts +2 -1
- package/dist/components/model-providers-section.stories.d.ts.map +1 -1
- package/dist/components/model-providers-section.stories.js +45 -1
- package/dist/components/model-providers-section.stories.js.map +1 -1
- package/dist/core/harness-policy.d.ts +5 -2
- package/dist/core/harness-policy.d.ts.map +1 -1
- package/dist/core/harness-policy.js +7 -1
- package/dist/core/harness-policy.js.map +1 -1
- package/dist/core/models.d.ts +3 -0
- package/dist/core/models.d.ts.map +1 -1
- package/dist/core/models.js.map +1 -1
- package/dist/core/provider-policy.d.ts +3 -1
- package/dist/core/provider-policy.d.ts.map +1 -1
- package/dist/core/provider-policy.js +7 -0
- package/dist/core/provider-policy.js.map +1 -1
- package/dist/hooks/api/model-provider-mapper.d.ts.map +1 -1
- package/dist/hooks/api/model-provider-mapper.js +5 -1
- package/dist/hooks/api/model-provider-mapper.js.map +1 -1
- package/dist/pages/model-provider-onboarding-page.d.ts +1 -1
- package/dist/pages/model-provider-onboarding-page.d.ts.map +1 -1
- package/dist/pages/model-provider-onboarding-page.js +58 -44
- package/dist/pages/model-provider-onboarding-page.js.map +1 -1
- package/dist/pages/model-provider-onboarding-page.stories.d.ts.map +1 -1
- package/dist/pages/model-provider-onboarding-page.stories.js +5 -17
- package/dist/pages/model-provider-onboarding-page.stories.js.map +1 -1
- package/dist/tsconfig.test.tsbuildinfo +1 -1
- package/package.json +5 -5
- package/src/components/form-errors.test.ts +9 -0
- package/src/components/form-errors.ts +3 -0
- package/src/components/harness-availability.test.ts +49 -2
- package/src/components/harness-availability.ts +18 -3
- package/src/components/harnesses-section.test.tsx +51 -9
- package/src/components/harnesses-section.tsx +4 -1
- package/src/components/model-provider-usage-modal.test.tsx +23 -0
- package/src/components/model-provider-usage-modal.tsx +32 -6
- package/src/components/model-provider-usage-target.ts +9 -3
- package/src/components/model-providers-section.stories.tsx +33 -1
- package/src/components/model-providers-section.test.tsx +41 -0
- package/src/components/model-providers-section.tsx +264 -155
- package/src/core/harness-policy.ts +21 -1
- package/src/core/models.ts +3 -0
- package/src/core/provider-policy.ts +12 -0
- package/src/hooks/api/model-provider-mapper.test.ts +18 -0
- package/src/hooks/api/model-provider-mapper.ts +6 -1
- package/src/hooks/api/model-providers.test.ts +11 -0
- package/src/pages/model-provider-onboarding-page.stories.tsx +10 -14
- package/src/pages/model-provider-onboarding-page.test.tsx +133 -24
- package/src/pages/model-provider-onboarding-page.tsx +47 -34
- package/test/fixtures/model-providers.ts +22 -1
- package/tsconfig.build.tsbuildinfo +1 -1
|
@@ -21,7 +21,7 @@ import {Panel, PanelBody, PanelRow} from '@shipfox/react-ui/panel';
|
|
|
21
21
|
import {Skeleton} from '@shipfox/react-ui/skeleton';
|
|
22
22
|
import {toast} from '@shipfox/react-ui/toast';
|
|
23
23
|
import {Tooltip, TooltipContent, TooltipTrigger} from '@shipfox/react-ui/tooltip';
|
|
24
|
-
import {Header, Text} from '@shipfox/react-ui/typography';
|
|
24
|
+
import {Code, Header, Text} from '@shipfox/react-ui/typography';
|
|
25
25
|
import {useEffect, useMemo, useReducer, useRef, useState} from 'react';
|
|
26
26
|
import {
|
|
27
27
|
type ManagementModal,
|
|
@@ -36,7 +36,9 @@ import type {
|
|
|
36
36
|
} from '#core/models.js';
|
|
37
37
|
import {
|
|
38
38
|
customProviderCardMatchesSearch,
|
|
39
|
+
isManagedOnlyCatalog,
|
|
39
40
|
isSupportedProvider,
|
|
41
|
+
managedProviderFromCatalog,
|
|
40
42
|
availableProviders as selectAvailableProviders,
|
|
41
43
|
} from '#core/provider-policy.js';
|
|
42
44
|
import {
|
|
@@ -67,6 +69,78 @@ type UsageTarget = {
|
|
|
67
69
|
|
|
68
70
|
const USAGE_MODAL_OPEN_DELAY_MS = 250;
|
|
69
71
|
|
|
72
|
+
function ManagedProviderSection({
|
|
73
|
+
provider,
|
|
74
|
+
onShowUsage,
|
|
75
|
+
}: {
|
|
76
|
+
provider: SupportedProvider | undefined;
|
|
77
|
+
onShowUsage: () => void;
|
|
78
|
+
}) {
|
|
79
|
+
return (
|
|
80
|
+
<section className="flex flex-col gap-group" aria-label="Managed provider">
|
|
81
|
+
<div className="flex flex-col gap-tight">
|
|
82
|
+
<Header variant="h3">Managed provider</Header>
|
|
83
|
+
</div>
|
|
84
|
+
|
|
85
|
+
{provider ? (
|
|
86
|
+
<Panel>
|
|
87
|
+
<PanelBody className="gap-group p-panel">
|
|
88
|
+
<div className="flex min-w-0 flex-col gap-tight">
|
|
89
|
+
<Text size="md" bold>
|
|
90
|
+
{provider.label}
|
|
91
|
+
</Text>
|
|
92
|
+
<Text size="sm" className="text-foreground-neutral-muted">
|
|
93
|
+
Managed by this instance. No workspace credentials are required.
|
|
94
|
+
</Text>
|
|
95
|
+
</div>
|
|
96
|
+
|
|
97
|
+
<div className="flex flex-col gap-inline">
|
|
98
|
+
<Text size="sm" bold>
|
|
99
|
+
Available models ({provider.models.length})
|
|
100
|
+
</Text>
|
|
101
|
+
<ul
|
|
102
|
+
aria-label={`${provider.label} models`}
|
|
103
|
+
className="rounded-8 border border-border-neutral-base"
|
|
104
|
+
>
|
|
105
|
+
{provider.models.map((model) => (
|
|
106
|
+
<li
|
|
107
|
+
key={model.id}
|
|
108
|
+
className="flex min-w-0 items-center justify-between gap-inline border-b border-border-neutral-base px-row py-row last:border-b-0"
|
|
109
|
+
>
|
|
110
|
+
<Text as="span" size="sm" bold className="min-w-0 truncate">
|
|
111
|
+
{model.label}
|
|
112
|
+
</Text>
|
|
113
|
+
<Code
|
|
114
|
+
as="span"
|
|
115
|
+
variant="label"
|
|
116
|
+
className="min-w-0 truncate text-foreground-neutral-muted"
|
|
117
|
+
>
|
|
118
|
+
{model.id}
|
|
119
|
+
</Code>
|
|
120
|
+
</li>
|
|
121
|
+
))}
|
|
122
|
+
</ul>
|
|
123
|
+
</div>
|
|
124
|
+
|
|
125
|
+
<Button type="button" size="sm" variant="secondary" onClick={onShowUsage}>
|
|
126
|
+
Use in a workflow
|
|
127
|
+
</Button>
|
|
128
|
+
</PanelBody>
|
|
129
|
+
</Panel>
|
|
130
|
+
) : (
|
|
131
|
+
<Panel>
|
|
132
|
+
<EmptyState
|
|
133
|
+
icon="errorWarningLine"
|
|
134
|
+
title="Managed provider unavailable"
|
|
135
|
+
description="This instance is configured without workspace providers, but no managed provider was returned."
|
|
136
|
+
variant="panel"
|
|
137
|
+
/>
|
|
138
|
+
</Panel>
|
|
139
|
+
)}
|
|
140
|
+
</section>
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
|
|
70
144
|
export function WorkspaceModelProvidersSection({workspaceId}: {workspaceId: string}) {
|
|
71
145
|
const catalogQuery = useModelProviderCatalogQuery();
|
|
72
146
|
const configsQuery = useModelProviderConfigsQuery(workspaceId);
|
|
@@ -78,6 +152,8 @@ export function WorkspaceModelProvidersSection({workspaceId}: {workspaceId: stri
|
|
|
78
152
|
const configs = configsQuery.data?.configs ?? [];
|
|
79
153
|
const configsLoaded = configsQuery.data !== undefined;
|
|
80
154
|
const defaultProviderId = configsQuery.data?.defaultProviderId ?? null;
|
|
155
|
+
const managedOnly = isManagedOnlyCatalog(catalogQuery.data);
|
|
156
|
+
const managedProvider = managedProviderFromCatalog(catalogQuery.data);
|
|
81
157
|
const providerById = useMemo(
|
|
82
158
|
() =>
|
|
83
159
|
new Map<string, ProviderCatalogEntry>(providers.map((provider) => [provider.id, provider])),
|
|
@@ -107,174 +183,207 @@ export function WorkspaceModelProvidersSection({workspaceId}: {workspaceId: stri
|
|
|
107
183
|
const config = configs.find((item) => item.providerId === modal.providerId);
|
|
108
184
|
if (config?.kind === 'custom') return usageTargetFromCustomConfig(config);
|
|
109
185
|
const entry = providerById.get(modal.providerId);
|
|
110
|
-
return entry && isSupportedProvider(entry)
|
|
111
|
-
|
|
186
|
+
return entry && isSupportedProvider(entry)
|
|
187
|
+
? usageTargetFromCatalogEntry(entry, {isManaged: managedOnly})
|
|
188
|
+
: null;
|
|
189
|
+
}, [configs, managedOnly, modal, providerById]);
|
|
112
190
|
|
|
113
191
|
return (
|
|
114
192
|
<div className="flex min-w-0 flex-col gap-region">
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
193
|
+
{managedOnly ? (
|
|
194
|
+
<ManagedProviderSection
|
|
195
|
+
provider={managedProvider}
|
|
196
|
+
onShowUsage={() => {
|
|
197
|
+
if (!managedProvider) return;
|
|
198
|
+
dispatchModal({
|
|
199
|
+
type: 'show-usage',
|
|
200
|
+
providerId: managedProvider.id,
|
|
201
|
+
initialModel: managedProvider.defaultModel,
|
|
202
|
+
restoreFocusToConfiguredProviders: false,
|
|
203
|
+
});
|
|
204
|
+
}}
|
|
205
|
+
/>
|
|
206
|
+
) : (
|
|
207
|
+
<>
|
|
208
|
+
<section
|
|
209
|
+
ref={configuredProvidersRegionRef}
|
|
210
|
+
className="flex flex-col gap-group outline-none"
|
|
211
|
+
aria-label="Configured providers"
|
|
212
|
+
tabIndex={-1}
|
|
213
|
+
>
|
|
214
|
+
<div className="flex flex-col gap-tight">
|
|
215
|
+
<Header variant="h3">Configured providers</Header>
|
|
216
|
+
</div>
|
|
124
217
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
218
|
+
{configsQuery.isPending ? (
|
|
219
|
+
<ModelProviderRowsSkeleton label="Loading configured providers" />
|
|
220
|
+
) : null}
|
|
128
221
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
222
|
+
{configsQuery.isError && configsQuery.data === undefined ? (
|
|
223
|
+
<Panel>
|
|
224
|
+
<QueryLoadError
|
|
225
|
+
query={configsQuery}
|
|
226
|
+
subject="model provider configs"
|
|
227
|
+
variant="panel"
|
|
228
|
+
/>
|
|
229
|
+
</Panel>
|
|
230
|
+
) : null}
|
|
134
231
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
232
|
+
{configsQuery.data !== undefined && configs.length === 0 ? (
|
|
233
|
+
<Panel>
|
|
234
|
+
<EmptyState
|
|
235
|
+
icon="key2Line"
|
|
236
|
+
title="No providers configured"
|
|
237
|
+
description="Configure a provider below to run agent steps with workspace-managed credentials."
|
|
238
|
+
variant="panel"
|
|
239
|
+
/>
|
|
240
|
+
</Panel>
|
|
241
|
+
) : null}
|
|
145
242
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
243
|
+
{configs.length > 0 ? (
|
|
244
|
+
<Panel>
|
|
245
|
+
<PanelBody asChild>
|
|
246
|
+
<ul>
|
|
247
|
+
{configs.map((config) => {
|
|
248
|
+
const catalogEntry = providerById.get(config.providerId);
|
|
249
|
+
const entry =
|
|
250
|
+
catalogEntry && isSupportedProvider(catalogEntry)
|
|
251
|
+
? catalogEntry
|
|
252
|
+
: undefined;
|
|
253
|
+
const builtinConfig = isBuiltinModelProviderConfig(config)
|
|
254
|
+
? config
|
|
255
|
+
: undefined;
|
|
256
|
+
const customConfig = isCustomModelProviderConfig(config) ? config : undefined;
|
|
257
|
+
return (
|
|
258
|
+
<ConfiguredProviderRow
|
|
259
|
+
key={config.providerId}
|
|
260
|
+
workspaceId={workspaceId}
|
|
261
|
+
config={config}
|
|
262
|
+
entry={entry}
|
|
263
|
+
isDefault={config.providerId === defaultProviderId}
|
|
264
|
+
onEdit={() => {
|
|
265
|
+
if (entry && builtinConfig) {
|
|
266
|
+
dispatchModal({
|
|
267
|
+
type: 'edit-builtin',
|
|
268
|
+
provider: entry,
|
|
269
|
+
config: builtinConfig,
|
|
270
|
+
});
|
|
271
|
+
} else if (customConfig) {
|
|
272
|
+
dispatchModal({type: 'edit-custom', config: customConfig});
|
|
273
|
+
}
|
|
274
|
+
}}
|
|
275
|
+
onChangeDefaultModel={() => {
|
|
276
|
+
if (entry && builtinConfig)
|
|
277
|
+
dispatchModal({
|
|
278
|
+
type: 'change-default-model',
|
|
279
|
+
provider: entry,
|
|
280
|
+
config: builtinConfig,
|
|
281
|
+
});
|
|
282
|
+
}}
|
|
283
|
+
onShowUsage={() => {
|
|
284
|
+
if (entry && builtinConfig) {
|
|
285
|
+
setPendingUsageTarget(null);
|
|
286
|
+
dispatchModal({
|
|
287
|
+
type: 'show-usage',
|
|
288
|
+
providerId: entry.id,
|
|
289
|
+
initialModel: builtinConfig.defaultModel,
|
|
290
|
+
restoreFocusToConfiguredProviders: false,
|
|
291
|
+
});
|
|
292
|
+
} else if (customConfig) {
|
|
293
|
+
setPendingUsageTarget(null);
|
|
294
|
+
dispatchModal({
|
|
295
|
+
type: 'show-usage',
|
|
296
|
+
providerId: customConfig.providerId,
|
|
297
|
+
initialModel: customConfig.defaultModel,
|
|
298
|
+
restoreFocusToConfiguredProviders: false,
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
}}
|
|
302
|
+
/>
|
|
303
|
+
);
|
|
304
|
+
})}
|
|
305
|
+
</ul>
|
|
306
|
+
</PanelBody>
|
|
307
|
+
</Panel>
|
|
308
|
+
) : null}
|
|
309
|
+
</section>
|
|
209
310
|
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
311
|
+
<section className="flex flex-col gap-group" aria-label="Available providers">
|
|
312
|
+
<div className="flex flex-col gap-tight">
|
|
313
|
+
<Header variant="h3">Available providers</Header>
|
|
314
|
+
</div>
|
|
214
315
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
316
|
+
{catalogQuery.isPending || configsQuery.isPending ? (
|
|
317
|
+
<ModelProviderGridSkeleton label="Loading available providers" />
|
|
318
|
+
) : null}
|
|
218
319
|
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
320
|
+
{catalogQuery.isError && catalogQuery.data === undefined ? (
|
|
321
|
+
<Panel>
|
|
322
|
+
<QueryLoadError
|
|
323
|
+
query={catalogQuery}
|
|
324
|
+
subject="model provider catalog"
|
|
325
|
+
variant="panel"
|
|
326
|
+
/>
|
|
327
|
+
</Panel>
|
|
328
|
+
) : null}
|
|
224
329
|
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
330
|
+
{configsLoaded ? (
|
|
331
|
+
<AvailableProvidersGrid
|
|
332
|
+
entries={availableProviders}
|
|
333
|
+
onSelect={(entry) => dispatchModal({type: 'configure-builtin', provider: entry})}
|
|
334
|
+
trailingCard={
|
|
335
|
+
<AddCustomProviderCard
|
|
336
|
+
onConfigure={() => dispatchModal({type: 'create-custom'})}
|
|
337
|
+
/>
|
|
338
|
+
}
|
|
339
|
+
trailingCardMatchesSearch={customProviderCardMatchesSearch}
|
|
340
|
+
/>
|
|
341
|
+
) : null}
|
|
342
|
+
</section>
|
|
236
343
|
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
344
|
+
<section className="flex flex-col gap-group" aria-label="Unsupported providers">
|
|
345
|
+
<div className="flex flex-col gap-tight">
|
|
346
|
+
<Header variant="h3">Unsupported providers</Header>
|
|
347
|
+
</div>
|
|
241
348
|
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
349
|
+
{catalogQuery.isPending ? (
|
|
350
|
+
<ModelProviderRowsSkeleton label="Loading unsupported providers" />
|
|
351
|
+
) : null}
|
|
245
352
|
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
353
|
+
{unsupportedProviders.length > 0 ? (
|
|
354
|
+
<Panel>
|
|
355
|
+
<PanelBody asChild>
|
|
356
|
+
<ul>
|
|
357
|
+
{unsupportedProviders.map((entry) => (
|
|
358
|
+
<PanelRow
|
|
359
|
+
asChild
|
|
360
|
+
className="items-start justify-start gap-cluster opacity-70 hover:bg-background-neutral-base"
|
|
361
|
+
key={entry.id}
|
|
362
|
+
>
|
|
363
|
+
<li>
|
|
364
|
+
<Icon
|
|
365
|
+
name="forbid2Line"
|
|
366
|
+
className="mt-[2px] size-18 shrink-0 text-foreground-neutral-muted"
|
|
367
|
+
aria-hidden
|
|
368
|
+
/>
|
|
369
|
+
<div className="flex min-w-0 flex-1 flex-col gap-tight">
|
|
370
|
+
<Text size="md" bold className="truncate">
|
|
371
|
+
{entry.label}
|
|
372
|
+
</Text>
|
|
373
|
+
<Text size="sm" className="text-foreground-neutral-muted">
|
|
374
|
+
{entry.unsupportedReason}
|
|
375
|
+
</Text>
|
|
376
|
+
</div>
|
|
377
|
+
</li>
|
|
378
|
+
</PanelRow>
|
|
379
|
+
))}
|
|
380
|
+
</ul>
|
|
381
|
+
</PanelBody>
|
|
382
|
+
</Panel>
|
|
383
|
+
) : null}
|
|
384
|
+
</section>
|
|
385
|
+
</>
|
|
386
|
+
)}
|
|
278
387
|
|
|
279
388
|
<Modal
|
|
280
389
|
open={modal.kind === 'configure-builtin' || modal.kind === 'edit-builtin'}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {HarnessDescriptor, HarnessId, ProviderConfig} from './models.js';
|
|
1
|
+
import type {AgentModel, HarnessDescriptor, HarnessId, ProviderConfig} from './models.js';
|
|
2
2
|
|
|
3
3
|
export const DEFAULT_HARNESS: HarnessId = 'pi';
|
|
4
4
|
|
|
@@ -71,14 +71,34 @@ export function configSupportsHarness(config: ProviderConfig, harness: HarnessDe
|
|
|
71
71
|
: harness.supportedProviderIds.includes(config.providerId);
|
|
72
72
|
}
|
|
73
73
|
|
|
74
|
+
export function managedModelSupportsHarness(
|
|
75
|
+
harnessId: HarnessId,
|
|
76
|
+
model: Pick<AgentModel, 'api'>,
|
|
77
|
+
): boolean {
|
|
78
|
+
return harnessId === 'pi' || model.api === undefined || model.api === 'anthropic-messages';
|
|
79
|
+
}
|
|
80
|
+
|
|
74
81
|
export function compatibleHarnessIds({
|
|
75
82
|
isCustom,
|
|
83
|
+
isManaged = false,
|
|
84
|
+
models,
|
|
76
85
|
providerId,
|
|
77
86
|
}: {
|
|
78
87
|
isCustom: boolean;
|
|
88
|
+
isManaged?: boolean;
|
|
89
|
+
models?: readonly Pick<AgentModel, 'api'>[];
|
|
79
90
|
providerId: string;
|
|
80
91
|
}): HarnessId[] {
|
|
81
92
|
if (isCustom) return [DEFAULT_HARNESS];
|
|
93
|
+
if (isManaged) {
|
|
94
|
+
return harnesses
|
|
95
|
+
.filter(
|
|
96
|
+
(harness) =>
|
|
97
|
+
models === undefined ||
|
|
98
|
+
models.some((model) => managedModelSupportsHarness(harness.id, model)),
|
|
99
|
+
)
|
|
100
|
+
.map((harness) => harness.id);
|
|
101
|
+
}
|
|
82
102
|
return harnesses
|
|
83
103
|
.filter((harness) => harness.supportedProviderIds.includes(providerId))
|
|
84
104
|
.map((harness) => harness.id);
|
package/src/core/models.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export type HarnessId = 'pi' | 'claude';
|
|
2
|
+
export type WorkspaceProvidersPolicy = 'enabled' | 'disabled';
|
|
2
3
|
export type ProviderApi =
|
|
3
4
|
| 'openai-completions'
|
|
4
5
|
| 'openai-responses'
|
|
@@ -15,6 +16,7 @@ export interface HarnessDescriptor {
|
|
|
15
16
|
export interface AgentModel {
|
|
16
17
|
readonly id: string;
|
|
17
18
|
readonly label: string;
|
|
19
|
+
readonly api?: ProviderApi | undefined;
|
|
18
20
|
readonly contextWindow?: number | undefined;
|
|
19
21
|
readonly maxOutputTokens?: number | undefined;
|
|
20
22
|
readonly inputImage?: boolean | undefined;
|
|
@@ -76,6 +78,7 @@ export type ProviderConfig = BuiltinProviderConfig | CustomProviderConfig;
|
|
|
76
78
|
|
|
77
79
|
export interface ProviderCatalog {
|
|
78
80
|
readonly providers: readonly ProviderCatalogEntry[];
|
|
81
|
+
readonly workspaceProviders: WorkspaceProvidersPolicy;
|
|
79
82
|
}
|
|
80
83
|
|
|
81
84
|
export interface ProviderConfiguration {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type {
|
|
2
2
|
HarnessDescriptor,
|
|
3
|
+
ProviderCatalog,
|
|
3
4
|
ProviderCatalogEntry,
|
|
4
5
|
ProviderConfig,
|
|
5
6
|
SupportedProvider,
|
|
@@ -13,6 +14,17 @@ export function supportsProvider(harness: HarnessDescriptor, providerId: string)
|
|
|
13
14
|
return harness.supportedProviderIds.includes(providerId);
|
|
14
15
|
}
|
|
15
16
|
|
|
17
|
+
export function isManagedOnlyCatalog(catalog: ProviderCatalog | undefined): boolean {
|
|
18
|
+
return catalog?.workspaceProviders === 'disabled';
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function managedProviderFromCatalog(
|
|
22
|
+
catalog: ProviderCatalog | undefined,
|
|
23
|
+
): SupportedProvider | undefined {
|
|
24
|
+
if (!isManagedOnlyCatalog(catalog)) return undefined;
|
|
25
|
+
return catalog?.providers.find(isSupportedProvider);
|
|
26
|
+
}
|
|
27
|
+
|
|
16
28
|
export function availableProviders(
|
|
17
29
|
catalog: readonly ProviderCatalogEntry[],
|
|
18
30
|
configs: readonly ProviderConfig[],
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import {
|
|
2
|
+
managedModelProviderEntry,
|
|
2
3
|
modelProviderCatalogResponse,
|
|
3
4
|
modelProviderConfigsResponse,
|
|
4
5
|
modelProviderEntry,
|
|
@@ -17,6 +18,23 @@ test('maps provider catalog entries before they reach the client domain', () =>
|
|
|
17
18
|
]);
|
|
18
19
|
});
|
|
19
20
|
|
|
21
|
+
test('maps the managed-only workspace policy and managed provider models', () => {
|
|
22
|
+
const catalog = toProviderCatalog(
|
|
23
|
+
modelProviderCatalogResponse([managedModelProviderEntry()], 'disabled'),
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
expect(catalog.workspaceProviders).toBe('disabled');
|
|
27
|
+
expect(catalog.providers[0]).toEqual(
|
|
28
|
+
expect.objectContaining({
|
|
29
|
+
id: 'shipfox',
|
|
30
|
+
models: [
|
|
31
|
+
{id: 'claude-opus-4-8', label: 'Claude Opus 4.8', api: 'anthropic-messages'},
|
|
32
|
+
{id: 'gpt-5.5-pro', label: 'GPT-5.5 Pro', api: 'openai-responses'},
|
|
33
|
+
],
|
|
34
|
+
}),
|
|
35
|
+
);
|
|
36
|
+
});
|
|
37
|
+
|
|
20
38
|
test('maps configuration response defaults and provider config fields', () => {
|
|
21
39
|
const configuration = toProviderConfiguration(modelProviderConfigsResponse());
|
|
22
40
|
|
|
@@ -21,7 +21,10 @@ import type {
|
|
|
21
21
|
} from '#core/models.js';
|
|
22
22
|
|
|
23
23
|
export function toProviderCatalog(response: ModelProviderCatalogResponseDto): ProviderCatalog {
|
|
24
|
-
return {
|
|
24
|
+
return {
|
|
25
|
+
providers: response.providers.map(toProviderCatalogEntry),
|
|
26
|
+
workspaceProviders: response.workspace_providers ?? 'enabled',
|
|
27
|
+
};
|
|
25
28
|
}
|
|
26
29
|
|
|
27
30
|
export function toProviderCatalogEntry(entry: ModelProviderCatalogEntryDto): ProviderCatalogEntry {
|
|
@@ -104,6 +107,7 @@ export function toCustomProviderConfig(config: CustomModelProviderConfigDto): Cu
|
|
|
104
107
|
function toAgentModel(model: {
|
|
105
108
|
id: string;
|
|
106
109
|
label: string;
|
|
110
|
+
api?: AgentModel['api'];
|
|
107
111
|
context_window?: number | undefined;
|
|
108
112
|
max_output_tokens?: number | undefined;
|
|
109
113
|
input_image?: boolean | undefined;
|
|
@@ -112,6 +116,7 @@ function toAgentModel(model: {
|
|
|
112
116
|
return {
|
|
113
117
|
id: model.id,
|
|
114
118
|
label: model.label,
|
|
119
|
+
...(model.api === undefined ? {} : {api: model.api}),
|
|
115
120
|
...(model.context_window === undefined ? {} : {contextWindow: model.context_window}),
|
|
116
121
|
...(model.max_output_tokens === undefined ? {} : {maxOutputTokens: model.max_output_tokens}),
|
|
117
122
|
...(model.input_image === undefined ? {} : {inputImage: model.input_image}),
|
|
@@ -40,6 +40,17 @@ describe('model provider transport', () => {
|
|
|
40
40
|
expect(request.method).toBe('GET');
|
|
41
41
|
});
|
|
42
42
|
|
|
43
|
+
test('preserves the managed-only workspace policy from the catalog', async () => {
|
|
44
|
+
const fetchImpl = vi
|
|
45
|
+
.fn()
|
|
46
|
+
.mockResolvedValue(jsonResponse(modelProviderCatalogResponse(undefined, 'disabled')));
|
|
47
|
+
configureApiClient({fetchImpl});
|
|
48
|
+
|
|
49
|
+
const result = await getModelProviderCatalog();
|
|
50
|
+
|
|
51
|
+
expect(result.workspaceProviders).toBe('disabled');
|
|
52
|
+
});
|
|
53
|
+
|
|
43
54
|
test('fetches workspace model provider configs', async () => {
|
|
44
55
|
const fetchImpl = vi.fn().mockResolvedValue(jsonResponse(modelProviderConfigsResponse()));
|
|
45
56
|
configureApiClient({fetchImpl});
|