@morscherlab/mint-sdk 1.1.0-beta.5 → 1.1.0-beta.7
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/README.md +18 -4
- package/dist/{SettingsModal-DDaq294M.js → SettingsModal-Dpg-X1OM.js} +3 -3
- package/dist/{SettingsModal-DDaq294M.js.map → SettingsModal-Dpg-X1OM.js.map} +1 -1
- package/dist/__tests__/types/GeneratedJsonSchema.test.d.ts +1 -0
- package/dist/components/PluginWorkspaceView.props.d.ts +1 -1
- package/dist/components/index.js +3 -3
- package/dist/{components-t0iBwn5R.js → components-Ci_BTx00.js} +10 -11
- package/dist/components-Ci_BTx00.js.map +1 -0
- package/dist/composables/index.d.ts +1 -1
- package/dist/composables/index.js +5 -5
- package/dist/composables/useGeneratedAnalysis.d.ts +1 -1
- package/dist/composables/usePluginClient.d.ts +11 -5
- package/dist/{composables-CqjA4Gth.js → composables-C0VEJ73O.js} +3 -3
- package/dist/{composables-CqjA4Gth.js.map → composables-C0VEJ73O.js.map} +1 -1
- package/dist/index.js +7 -7
- package/dist/install.js +3 -3
- package/dist/styles.css +11 -11
- package/dist/templates/index.js +2 -2
- package/dist/{templates-BdArs8LL.js → templates-Cdj93S9C.js} +2 -2
- package/dist/{templates-BdArs8LL.js.map → templates-Cdj93S9C.js.map} +1 -1
- package/dist/types/generated-ui.d.ts +4 -0
- package/dist/{useControlSchema-BAqu3kqE.js → useControlSchema-B_qgU4rW.js} +16 -14
- package/dist/useControlSchema-B_qgU4rW.js.map +1 -0
- package/dist/{useFormBuilder-DvO-TrfX.js → useFormBuilder-D1G5J5Z5.js} +2 -2
- package/dist/{useFormBuilder-DvO-TrfX.js.map → useFormBuilder-D1G5J5Z5.js.map} +1 -1
- package/dist/{useProtocolTemplates-BuY_MlBP.js → useProtocolTemplates-DeaOmMaw.js} +3 -3
- package/dist/{useProtocolTemplates-BuY_MlBP.js.map → useProtocolTemplates-DeaOmMaw.js.map} +1 -1
- package/package.json +1 -1
- package/src/__tests__/components/GeneratedPluginView.test.ts +10 -0
- package/src/__tests__/components/PluginWorkspaceView.test.ts +68 -14
- package/src/__tests__/composables/useGeneratedAnalysis.test.ts +2 -0
- package/src/__tests__/composables/usePluginClient.test.ts +36 -1
- package/src/__tests__/types/GeneratedJsonSchema.test.ts +17 -0
- package/src/components/GeneratedPluginView.vue +1 -1
- package/src/components/PluginWorkspaceView.props.ts +1 -1
- package/src/components/PluginWorkspaceView.vue +1 -5
- package/src/composables/index.ts +1 -0
- package/src/composables/useGeneratedAnalysis.ts +30 -12
- package/src/composables/usePluginClient.ts +12 -5
- package/src/types/generated-ui.ts +4 -0
- package/dist/components-t0iBwn5R.js.map +0 -1
- package/dist/useControlSchema-BAqu3kqE.js.map +0 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@morscherlab/mint-sdk",
|
|
3
|
-
"version": "1.1.0-beta.
|
|
3
|
+
"version": "1.1.0-beta.7",
|
|
4
4
|
"description": "MINT Platform SDK — Vue 3 components, composables, and types for plugin development. MINT = Mass-spec INtegrated Toolkit.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -190,6 +190,16 @@ describe('GeneratedPluginView', () => {
|
|
|
190
190
|
expect(wrapper.find('[aria-label="Job resource requirements"]').text()).toBe('Host service')
|
|
191
191
|
})
|
|
192
192
|
|
|
193
|
+
it('describes process CPU declarations as an upper bound', async () => {
|
|
194
|
+
const wrapper = mount(GeneratedPluginView, {
|
|
195
|
+
props: { baseUrl: '/api/demo' },
|
|
196
|
+
global: { plugins: [createPinia()], stubs: { 'router-link': true, Teleport: true } },
|
|
197
|
+
})
|
|
198
|
+
await flushPromises()
|
|
199
|
+
|
|
200
|
+
expect(wrapper.find('[aria-label="Job resource requirements"]').text()).toBe('Up to 2 CPU')
|
|
201
|
+
})
|
|
202
|
+
|
|
193
203
|
it('should pass the host transport to the generated analysis composable', async () => {
|
|
194
204
|
const transport = vi.fn()
|
|
195
205
|
mount(GeneratedPluginView, {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { flushPromises, mount } from '@vue/test-utils'
|
|
2
|
+
import type { VueWrapper } from '@vue/test-utils'
|
|
2
3
|
import { createPinia } from 'pinia'
|
|
3
4
|
import { computed, h, nextTick, ref } from 'vue'
|
|
4
5
|
import { describe, expect, it, vi } from 'vitest'
|
|
@@ -10,7 +11,7 @@ import ExperimentSelectorModal from '../../components/ExperimentSelectorModal.vu
|
|
|
10
11
|
import PluginWorkspaceView from '../../components/PluginWorkspaceView.vue'
|
|
11
12
|
import { defineDoseDesignControlModel } from '../../composables/useControlSchema'
|
|
12
13
|
import { usePlatformContext } from '../../composables/usePlatformContext'
|
|
13
|
-
import type { PillNavItem, SidebarToolSection } from '../../types'
|
|
14
|
+
import type { PageSelectorItemInput, PillNavItem, SidebarToolSection } from '../../types'
|
|
14
15
|
|
|
15
16
|
vi.mock('../../composables/usePlatformContext', () => ({
|
|
16
17
|
usePlatformContext: vi.fn(() => ({
|
|
@@ -79,6 +80,44 @@ const globalOptions = {
|
|
|
79
80
|
},
|
|
80
81
|
}
|
|
81
82
|
|
|
83
|
+
interface PageSelectorWorkspaceProps {
|
|
84
|
+
pageSelector?: PageSelectorItemInput[]
|
|
85
|
+
currentPageSelectorId?: string
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function mountIntegratedPageSelectorWorkspace(
|
|
89
|
+
props: PageSelectorWorkspaceProps = {},
|
|
90
|
+
): VueWrapper {
|
|
91
|
+
vi.mocked(usePlatformContext).mockReturnValueOnce({
|
|
92
|
+
isIntegrated: computed(() => true),
|
|
93
|
+
context: ref({ isIntegrated: true, theme: 'system' }),
|
|
94
|
+
plugin: computed(() => ({
|
|
95
|
+
id: 'workspace-plugin',
|
|
96
|
+
name: 'Workspace Plugin',
|
|
97
|
+
version: '1.0.0',
|
|
98
|
+
route_prefix: '/workspace-plugin',
|
|
99
|
+
api_prefix: '/api/workspace-plugin',
|
|
100
|
+
nav_items: [
|
|
101
|
+
{ path: '/platform-one', label: 'Platform One' },
|
|
102
|
+
{ path: '/platform-two', label: 'Platform Two' },
|
|
103
|
+
],
|
|
104
|
+
})),
|
|
105
|
+
user: computed(() => undefined),
|
|
106
|
+
theme: computed(() => 'system' as const),
|
|
107
|
+
features: computed(() => undefined),
|
|
108
|
+
navigate: vi.fn(),
|
|
109
|
+
notify: vi.fn(),
|
|
110
|
+
sendToPlatform: vi.fn(),
|
|
111
|
+
})
|
|
112
|
+
return mount(PluginWorkspaceView, {
|
|
113
|
+
props: {
|
|
114
|
+
title: 'Workspace',
|
|
115
|
+
...props,
|
|
116
|
+
},
|
|
117
|
+
global: globalOptions,
|
|
118
|
+
})
|
|
119
|
+
}
|
|
120
|
+
|
|
82
121
|
describe('PluginWorkspaceView', () => {
|
|
83
122
|
it('renders the standard AppLayout, AppTopBar, and AppSidebar plugin shell', () => {
|
|
84
123
|
const wrapper = mount(PluginWorkspaceView, {
|
|
@@ -146,22 +185,37 @@ describe('PluginWorkspaceView', () => {
|
|
|
146
185
|
expect(wrapper.emitted('pill-select')).toEqual([[{ id: 'results', label: 'Results' }]])
|
|
147
186
|
})
|
|
148
187
|
|
|
149
|
-
it('
|
|
150
|
-
const wrapper =
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
188
|
+
it('should use platform pages when pageSelector is omitted', () => {
|
|
189
|
+
const wrapper = mountIntegratedPageSelectorWorkspace()
|
|
190
|
+
|
|
191
|
+
expect(wrapper.find('.mint-page-selector').exists()).toBe(true)
|
|
192
|
+
})
|
|
193
|
+
|
|
194
|
+
it('should suppress platform pages when pageSelector is explicitly empty', () => {
|
|
195
|
+
const wrapper = mountIntegratedPageSelectorWorkspace({ pageSelector: [] })
|
|
196
|
+
|
|
197
|
+
expect(wrapper.find('.mint-page-selector').exists()).toBe(false)
|
|
198
|
+
})
|
|
199
|
+
|
|
200
|
+
it('should hide one explicit page without falling back to platform pages', () => {
|
|
201
|
+
const wrapper = mountIntegratedPageSelectorWorkspace({
|
|
202
|
+
pageSelector: [{ id: 'downloads', label: 'Downloads', to: '/' }],
|
|
203
|
+
currentPageSelectorId: 'downloads',
|
|
160
204
|
})
|
|
161
205
|
|
|
162
|
-
expect(wrapper.findComponent(AppTopBar).props('pageSelector')).toBeUndefined()
|
|
163
206
|
expect(wrapper.find('.mint-page-selector').exists()).toBe(false)
|
|
164
|
-
|
|
207
|
+
})
|
|
208
|
+
|
|
209
|
+
it('should render multiple explicit pages ahead of platform pages', () => {
|
|
210
|
+
const wrapper = mountIntegratedPageSelectorWorkspace({
|
|
211
|
+
pageSelector: [
|
|
212
|
+
{ id: 'overview', label: 'Overview', to: '/' },
|
|
213
|
+
{ id: 'reports', label: 'Reports', to: '/reports' },
|
|
214
|
+
],
|
|
215
|
+
currentPageSelectorId: 'overview',
|
|
216
|
+
})
|
|
217
|
+
|
|
218
|
+
expect(wrapper.get('.mint-page-selector__label').text()).toBe('Overview')
|
|
165
219
|
})
|
|
166
220
|
|
|
167
221
|
it('passes compact control models to the sidebar for generated forms', () => {
|
|
@@ -407,6 +407,7 @@ describe('useGeneratedAnalysis', () => {
|
|
|
407
407
|
uploadUrl: upload?.[0],
|
|
408
408
|
uploadMethod: upload?.[1]?.method,
|
|
409
409
|
uploadSessionHeader: new Headers(upload?.[1]?.headers).get('X-Mint-Job-Session'),
|
|
410
|
+
uploadDefinitionHeader: new Headers(upload?.[1]?.headers).get('X-Mint-Job-Definition'),
|
|
410
411
|
sessionId: form.get('session_id'),
|
|
411
412
|
relativePaths: form.getAll('relative_paths'),
|
|
412
413
|
submittedSource: submit.inputs.source,
|
|
@@ -414,6 +415,7 @@ describe('useGeneratedAnalysis', () => {
|
|
|
414
415
|
uploadUrl: '/api/demo/jobs/uploads',
|
|
415
416
|
uploadMethod: 'POST',
|
|
416
417
|
uploadSessionHeader: 'session-1',
|
|
418
|
+
uploadDefinitionHeader: 'run',
|
|
417
419
|
sessionId: 'session-1',
|
|
418
420
|
relativePaths: ['data.csv'],
|
|
419
421
|
submittedSource: { token: 'upload-token' },
|
|
@@ -22,12 +22,37 @@ import { AuthenticationRequiredError } from '../../composables/useApi'
|
|
|
22
22
|
import { useAuthStore } from '../../stores/auth'
|
|
23
23
|
|
|
24
24
|
const contract: PluginContract = {
|
|
25
|
-
schemaVersion:
|
|
25
|
+
schemaVersion: 2,
|
|
26
26
|
plugin: {
|
|
27
27
|
routesPrefix: '/drp',
|
|
28
28
|
apiPrefix: '/api/drp',
|
|
29
29
|
type: 'analysis',
|
|
30
30
|
},
|
|
31
|
+
settings: {
|
|
32
|
+
modelName: 'PluginSettings',
|
|
33
|
+
valueSchema: {
|
|
34
|
+
type: 'object',
|
|
35
|
+
properties: {
|
|
36
|
+
threshold: { type: 'number', default: 0.05 },
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
formSchema: {
|
|
40
|
+
groups: [
|
|
41
|
+
{
|
|
42
|
+
id: 'settings',
|
|
43
|
+
label: 'Settings',
|
|
44
|
+
fields: [
|
|
45
|
+
{
|
|
46
|
+
name: 'threshold',
|
|
47
|
+
label: 'Threshold',
|
|
48
|
+
type: 'number',
|
|
49
|
+
defaultValue: 0.05,
|
|
50
|
+
},
|
|
51
|
+
],
|
|
52
|
+
},
|
|
53
|
+
],
|
|
54
|
+
},
|
|
55
|
+
},
|
|
31
56
|
endpoints: [],
|
|
32
57
|
hash: 'sha256:test',
|
|
33
58
|
}
|
|
@@ -818,6 +843,16 @@ describe('usePluginClient', () => {
|
|
|
818
843
|
})
|
|
819
844
|
})
|
|
820
845
|
|
|
846
|
+
it('should preserve the contract v2 form schema in settingsConfig', () => {
|
|
847
|
+
const settings = usePluginSettings({
|
|
848
|
+
apiBaseUrl: '/api/drp',
|
|
849
|
+
loadOnMount: false,
|
|
850
|
+
schema: contract.settings.formSchema,
|
|
851
|
+
})
|
|
852
|
+
|
|
853
|
+
expect(settings.settingsConfig.value.schema).toBe(contract.settings.formSchema)
|
|
854
|
+
})
|
|
855
|
+
|
|
821
856
|
it('should use the platform facade for an explicit different plugin name', async () => {
|
|
822
857
|
;(window as unknown as { __MINT_PLATFORM__?: unknown }).__MINT_PLATFORM__ = {
|
|
823
858
|
isIntegrated: true,
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest'
|
|
2
|
+
|
|
3
|
+
import type { GeneratedJsonSchema } from '../../types/generated-ui'
|
|
4
|
+
|
|
5
|
+
describe('GeneratedJsonSchema', () => {
|
|
6
|
+
it('should accept array size constraints emitted by Pydantic', () => {
|
|
7
|
+
const schema: GeneratedJsonSchema = { maxItems: 100 }
|
|
8
|
+
|
|
9
|
+
expect(schema.maxItems).toBe(100)
|
|
10
|
+
})
|
|
11
|
+
|
|
12
|
+
it('should accept write-only fields emitted by Pydantic', () => {
|
|
13
|
+
const schema: GeneratedJsonSchema = { writeOnly: true }
|
|
14
|
+
|
|
15
|
+
expect(schema.writeOnly).toBe(true)
|
|
16
|
+
})
|
|
17
|
+
})
|
|
@@ -116,7 +116,7 @@ function resourceSummary(definition: GeneratedJobDefinition): string {
|
|
|
116
116
|
if (definition.lifecycle === 'staged' || definition.profile?.kind === 'staged') {
|
|
117
117
|
return 'Staged upload'
|
|
118
118
|
}
|
|
119
|
-
return
|
|
119
|
+
return `Up to ${definition.cpu} CPU`
|
|
120
120
|
}
|
|
121
121
|
|
|
122
122
|
onMounted(() => {
|
|
@@ -34,7 +34,7 @@ export interface PluginWorkspaceViewProps {
|
|
|
34
34
|
homePath?: string
|
|
35
35
|
/** Show the default MINT logo when no icon/logo slot is provided. */
|
|
36
36
|
showLogo?: boolean
|
|
37
|
-
/** Preferred route-level page selector entries. */
|
|
37
|
+
/** Preferred route-level page selector entries. Pass [] to suppress integrated platform fallback. */
|
|
38
38
|
pageSelector?: PageSelectorItemInput[]
|
|
39
39
|
/** Active page selector id. Defaults to activeView. */
|
|
40
40
|
currentPageSelectorId?: string
|
|
@@ -208,10 +208,6 @@ const {
|
|
|
208
208
|
emitPillSelect: (item) => emit('pill-select', item),
|
|
209
209
|
})
|
|
210
210
|
|
|
211
|
-
const visiblePageSelector = computed(() =>
|
|
212
|
-
(props.pageSelector?.length ?? 0) > 1 ? props.pageSelector : undefined,
|
|
213
|
-
)
|
|
214
|
-
|
|
215
211
|
const hasSidebarSurface = computed(() => {
|
|
216
212
|
return hasPluginWorkspaceSidebarSurface({
|
|
217
213
|
showSidebar: props.showSidebar,
|
|
@@ -274,7 +270,7 @@ function handleFormCancel(sectionId: string) {
|
|
|
274
270
|
:variant="topBarVariant"
|
|
275
271
|
:home-path="homePath"
|
|
276
272
|
:show-logo="showLogo"
|
|
277
|
-
:page-selector="
|
|
273
|
+
:page-selector="pageSelector"
|
|
278
274
|
:current-page-selector-id="resolvedCurrentPageSelectorId"
|
|
279
275
|
:plugin-switcher="pluginSwitcher"
|
|
280
276
|
:pill-nav="resolvedPillNav"
|
package/src/composables/index.ts
CHANGED
|
@@ -75,7 +75,10 @@ export interface UsePluginJobsReturn extends PluginJobCenterSource {
|
|
|
75
75
|
status: ShallowRef<'idle' | 'initializing' | 'ready' | 'paused' | 'error' | 'disposed'>
|
|
76
76
|
initialize: () => Promise<void>
|
|
77
77
|
ready: () => Promise<void>
|
|
78
|
-
uploadPath: (
|
|
78
|
+
uploadPath: (
|
|
79
|
+
input: PluginJobPathInput,
|
|
80
|
+
definitionId?: string,
|
|
81
|
+
) => Promise<PluginJobPathUpload>
|
|
79
82
|
uploadStagedPart: <Metadata>(
|
|
80
83
|
input: PluginJobStagedPartInput<Metadata>,
|
|
81
84
|
) => Promise<PluginJobStagedPartUploadResult<Metadata>>
|
|
@@ -279,7 +282,7 @@ function createPluginJobs(
|
|
|
279
282
|
jobInput = await submitToSession(
|
|
280
283
|
session.value.id,
|
|
281
284
|
clientRequestId,
|
|
282
|
-
await stagePathInputs(inputs, session.value.id),
|
|
285
|
+
await stagePathInputs(inputs, session.value.id, definitionId),
|
|
283
286
|
)
|
|
284
287
|
} catch (cause) {
|
|
285
288
|
if (!isSessionNotFoundError(cause)) throw cause
|
|
@@ -287,7 +290,7 @@ function createPluginJobs(
|
|
|
287
290
|
jobInput = await submitToSession(
|
|
288
291
|
session.value.id,
|
|
289
292
|
clientRequestId,
|
|
290
|
-
await stagePathInputs(inputs, session.value.id),
|
|
293
|
+
await stagePathInputs(inputs, session.value.id, definitionId),
|
|
291
294
|
)
|
|
292
295
|
}
|
|
293
296
|
const job = upsertJob(jobInput, definitionId)
|
|
@@ -317,9 +320,12 @@ function createPluginJobs(
|
|
|
317
320
|
}
|
|
318
321
|
}
|
|
319
322
|
|
|
320
|
-
async function uploadPath(
|
|
323
|
+
async function uploadPath(
|
|
324
|
+
input: PluginJobPathInput,
|
|
325
|
+
definitionId?: string,
|
|
326
|
+
): Promise<PluginJobPathUpload> {
|
|
321
327
|
if (!session.value) throw new Error('Generated analysis session is not initialized')
|
|
322
|
-
return uploadPathForSession(input, session.value.id)
|
|
328
|
+
return uploadPathForSession(input, session.value.id, definitionId)
|
|
323
329
|
}
|
|
324
330
|
|
|
325
331
|
async function uploadStagedPart<Metadata>(
|
|
@@ -375,6 +381,7 @@ function createPluginJobs(
|
|
|
375
381
|
async function uploadPathForSession(
|
|
376
382
|
input: PluginJobPathInput,
|
|
377
383
|
sessionId: string,
|
|
384
|
+
definitionId?: string,
|
|
378
385
|
): Promise<PluginJobPathUpload> {
|
|
379
386
|
if (!input.files.length || input.files.length !== input.relativePaths.length) {
|
|
380
387
|
throw new Error('Path upload requires one relative path for each file')
|
|
@@ -386,40 +393,51 @@ function createPluginJobs(
|
|
|
386
393
|
body.append('files', input.files[index]!)
|
|
387
394
|
body.append('relative_paths', input.relativePaths[index]!)
|
|
388
395
|
}
|
|
396
|
+
const headers: Record<string, string> = {
|
|
397
|
+
'X-Mint-Job-Session': sessionId,
|
|
398
|
+
}
|
|
399
|
+
if (definitionId) headers['X-Mint-Job-Definition'] = definitionId
|
|
389
400
|
return request<PluginJobPathUpload>('/jobs/uploads', {
|
|
390
401
|
method: 'POST',
|
|
391
402
|
body,
|
|
392
403
|
signal: input.signal,
|
|
393
|
-
headers
|
|
404
|
+
headers,
|
|
394
405
|
})
|
|
395
406
|
}
|
|
396
407
|
|
|
397
408
|
async function stagePathInputs(
|
|
398
409
|
inputs: Record<string, unknown>,
|
|
399
410
|
sessionId: string,
|
|
411
|
+
definitionId: string,
|
|
400
412
|
): Promise<Record<string, unknown>> {
|
|
401
|
-
return await stagePathValue(inputs, sessionId) as Record<string, unknown>
|
|
413
|
+
return await stagePathValue(inputs, sessionId, definitionId) as Record<string, unknown>
|
|
402
414
|
}
|
|
403
415
|
|
|
404
|
-
async function stagePathValue(
|
|
416
|
+
async function stagePathValue(
|
|
417
|
+
value: unknown,
|
|
418
|
+
sessionId: string,
|
|
419
|
+
definitionId: string,
|
|
420
|
+
): Promise<unknown> {
|
|
405
421
|
if (typeof File !== 'undefined' && value instanceof File) {
|
|
406
422
|
const upload = await uploadPathForSession({
|
|
407
423
|
kind: 'path',
|
|
408
424
|
files: [value],
|
|
409
425
|
relativePaths: [value.name],
|
|
410
|
-
}, sessionId)
|
|
426
|
+
}, sessionId, definitionId)
|
|
411
427
|
return { token: upload.token }
|
|
412
428
|
}
|
|
413
429
|
if (isPluginJobPathInput(value)) {
|
|
414
|
-
const upload = await uploadPathForSession(value, sessionId)
|
|
430
|
+
const upload = await uploadPathForSession(value, sessionId, definitionId)
|
|
415
431
|
return { token: upload.token }
|
|
416
432
|
}
|
|
417
433
|
if (Array.isArray(value)) {
|
|
418
|
-
return await Promise.all(value.map(
|
|
434
|
+
return await Promise.all(value.map(
|
|
435
|
+
item => stagePathValue(item, sessionId, definitionId),
|
|
436
|
+
))
|
|
419
437
|
}
|
|
420
438
|
if (value && typeof value === 'object') {
|
|
421
439
|
const entries = await Promise.all(Object.entries(value).map(async ([key, child]) => (
|
|
422
|
-
[key, await stagePathValue(child, sessionId)] as const
|
|
440
|
+
[key, await stagePathValue(child, sessionId, definitionId)] as const
|
|
423
441
|
)))
|
|
424
442
|
return Object.fromEntries(entries)
|
|
425
443
|
}
|
|
@@ -45,6 +45,7 @@ export type PluginHttpMethod = 'get' | 'post' | 'put' | 'patch' | 'delete'
|
|
|
45
45
|
|
|
46
46
|
export interface PluginEndpointContract {
|
|
47
47
|
name: string
|
|
48
|
+
operationId?: string
|
|
48
49
|
method: PluginHttpMethod
|
|
49
50
|
path: string
|
|
50
51
|
requiresAuth?: boolean
|
|
@@ -75,8 +76,16 @@ export interface PluginNavItemContract {
|
|
|
75
76
|
requiresFeature?: string
|
|
76
77
|
}
|
|
77
78
|
|
|
79
|
+
export interface PluginContractSettings {
|
|
80
|
+
modelName: string | null
|
|
81
|
+
/** Pydantic JSON Schema used to generate typed settings values. */
|
|
82
|
+
valueSchema: Record<string, unknown> | null
|
|
83
|
+
/** SDK SettingsModal schema used to render the settings form. */
|
|
84
|
+
formSchema: SettingsModalSchema | null
|
|
85
|
+
}
|
|
86
|
+
|
|
78
87
|
export interface PluginContract {
|
|
79
|
-
schemaVersion:
|
|
88
|
+
schemaVersion: 2
|
|
80
89
|
plugin: {
|
|
81
90
|
name?: string
|
|
82
91
|
packageName?: string
|
|
@@ -92,11 +101,9 @@ export interface PluginContract {
|
|
|
92
101
|
analysisResultReaders?: string[]
|
|
93
102
|
capabilities?: Record<string, unknown>
|
|
94
103
|
}
|
|
95
|
-
settings
|
|
96
|
-
modelName?: string | null
|
|
97
|
-
schema?: SettingsModalSchema | null
|
|
98
|
-
}
|
|
104
|
+
settings: PluginContractSettings
|
|
99
105
|
endpoints: PluginEndpointContract[]
|
|
106
|
+
schemas?: Record<string, Record<string, unknown>>
|
|
100
107
|
jobs?: GeneratedJobDefinition[]
|
|
101
108
|
hash: string
|
|
102
109
|
}
|
|
@@ -12,6 +12,8 @@ export interface GeneratedJsonSchema {
|
|
|
12
12
|
maximum?: number
|
|
13
13
|
minLength?: number
|
|
14
14
|
maxLength?: number
|
|
15
|
+
maxItems?: number
|
|
16
|
+
writeOnly?: boolean
|
|
15
17
|
properties?: Record<string, GeneratedJsonSchema>
|
|
16
18
|
additionalProperties?: boolean | GeneratedJsonSchema
|
|
17
19
|
required?: string[]
|
|
@@ -102,6 +104,8 @@ export interface GeneratedJobDefinition {
|
|
|
102
104
|
title: string
|
|
103
105
|
description: string
|
|
104
106
|
cpu: number
|
|
107
|
+
/** Whether integrated submission requires a platform or current-plugin administrator. */
|
|
108
|
+
requires_admin?: boolean
|
|
105
109
|
/** Defaults to process for manifests generated before lifecycle profiles. */
|
|
106
110
|
lifecycle?: GeneratedJobLifecycle
|
|
107
111
|
/** Defaults to `{ kind: 'process' }` for older manifests. */
|