@elementor/editor-canvas 4.3.0-990 → 4.3.0-992

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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@elementor/editor-canvas",
3
3
  "description": "Elementor Editor Canvas",
4
- "version": "4.3.0-990",
4
+ "version": "4.3.0-992",
5
5
  "private": false,
6
6
  "author": "Elementor Team",
7
7
  "homepage": "https://elementor.com/",
@@ -37,26 +37,26 @@
37
37
  "react-dom": "^18.3.1"
38
38
  },
39
39
  "dependencies": {
40
- "@elementor/editor": "4.3.0-990",
41
- "@elementor/editor-controls": "4.3.0-990",
42
- "@elementor/editor-documents": "4.3.0-990",
43
- "@elementor/editor-elements": "4.3.0-990",
44
- "@elementor/editor-interactions": "4.3.0-990",
45
- "@elementor/editor-mcp": "4.3.0-990",
46
- "@elementor/editor-notifications": "4.3.0-990",
47
- "@elementor/editor-props": "4.3.0-990",
48
- "@elementor/editor-responsive": "4.3.0-990",
49
- "@elementor/editor-styles": "4.3.0-990",
50
- "@elementor/editor-styles-repository": "4.3.0-990",
51
- "@elementor/editor-ui": "4.3.0-990",
52
- "@elementor/editor-v1-adapters": "4.3.0-990",
53
- "@elementor/events": "4.3.0-990",
54
- "@elementor/http-client": "4.3.0-990",
55
- "@elementor/schema": "4.3.0-990",
56
- "@elementor/twing": "4.3.0-990",
40
+ "@elementor/editor": "4.3.0-992",
41
+ "@elementor/editor-controls": "4.3.0-992",
42
+ "@elementor/editor-documents": "4.3.0-992",
43
+ "@elementor/editor-elements": "4.3.0-992",
44
+ "@elementor/editor-interactions": "4.3.0-992",
45
+ "@elementor/editor-mcp": "4.3.0-992",
46
+ "@elementor/editor-notifications": "4.3.0-992",
47
+ "@elementor/editor-props": "4.3.0-992",
48
+ "@elementor/editor-responsive": "4.3.0-992",
49
+ "@elementor/editor-styles": "4.3.0-992",
50
+ "@elementor/editor-styles-repository": "4.3.0-992",
51
+ "@elementor/editor-ui": "4.3.0-992",
52
+ "@elementor/editor-v1-adapters": "4.3.0-992",
53
+ "@elementor/events": "4.3.0-992",
54
+ "@elementor/http-client": "4.3.0-992",
55
+ "@elementor/schema": "4.3.0-992",
56
+ "@elementor/twing": "4.3.0-992",
57
57
  "@elementor/ui": "1.37.5",
58
- "@elementor/utils": "4.3.0-990",
59
- "@elementor/wp-media": "4.3.0-990",
58
+ "@elementor/utils": "4.3.0-992",
59
+ "@elementor/wp-media": "4.3.0-992",
60
60
  "@floating-ui/react": "^0.27.5",
61
61
  "@wordpress/i18n": "^5.13.0",
62
62
  "dompurify": "^3.2.6"
@@ -0,0 +1,76 @@
1
+ import { httpService } from '@elementor/http-client';
2
+
3
+ import {
4
+ AVAILABLE_WIDGETS_URI,
5
+ AVAILABLE_WIDGETS_URI_V4,
6
+ initAvailableWidgetsResource,
7
+ } from '../available-widgets-resource';
8
+
9
+ jest.mock( '@elementor/http-client', () => ( {
10
+ httpService: jest.fn(),
11
+ } ) );
12
+
13
+ const mockedHttpService = httpService as jest.MockedFunction< typeof httpService >;
14
+
15
+ type ResourceHandler = () => Promise< { contents: { text: string }[] } >;
16
+
17
+ const captureHandlers = () => {
18
+ const resource = jest.fn();
19
+ initAvailableWidgetsResource( { resource } as never );
20
+ return {
21
+ v4Handler: resource.mock.calls[ 0 ][ 3 ] as ResourceHandler,
22
+ allHandler: resource.mock.calls[ 1 ][ 3 ] as ResourceHandler,
23
+ };
24
+ };
25
+
26
+ describe( 'available-widgets-resource', () => {
27
+ beforeEach( () => {
28
+ jest.clearAllMocks();
29
+ } );
30
+
31
+ it( 'fetches all widgets via list-widgets without a version filter', async () => {
32
+ // Arrange
33
+ const post = jest.fn().mockResolvedValue( {
34
+ data: {
35
+ data: [
36
+ { type: 'e-heading', version: 'v4' },
37
+ { type: 'legacy-icon', version: 'v3' },
38
+ ],
39
+ },
40
+ } );
41
+ mockedHttpService.mockReturnValue( { post } as never );
42
+ const { allHandler } = captureHandlers();
43
+
44
+ // Act
45
+ const result = await allHandler();
46
+
47
+ // Assert
48
+ expect( post ).toHaveBeenCalledWith( 'elementor/v1/mcp-proxy', { tool: 'list-widgets', input: {} } );
49
+ expect( JSON.parse( result.contents[ 0 ].text ) ).toEqual( [
50
+ { type: 'e-heading', version: 'v4' },
51
+ { type: 'legacy-icon', version: 'v3' },
52
+ ] );
53
+ } );
54
+
55
+ it( 'fetches only v4 widgets via list-widgets with a version filter', async () => {
56
+ // Arrange
57
+ const post = jest.fn().mockResolvedValue( { data: { data: [ { type: 'e-heading', version: 'v4' } ] } } );
58
+ mockedHttpService.mockReturnValue( { post } as never );
59
+ const { v4Handler } = captureHandlers();
60
+
61
+ // Act
62
+ const result = await v4Handler();
63
+
64
+ // Assert
65
+ expect( post ).toHaveBeenCalledWith( 'elementor/v1/mcp-proxy', {
66
+ tool: 'list-widgets',
67
+ input: { version: 'v4' },
68
+ } );
69
+ expect( JSON.parse( result.contents[ 0 ].text ) ).toEqual( [ { type: 'e-heading', version: 'v4' } ] );
70
+ } );
71
+
72
+ it( 'exposes the expected resource URIs', () => {
73
+ expect( AVAILABLE_WIDGETS_URI ).toBe( 'elementor://context/available-widgets' );
74
+ expect( AVAILABLE_WIDGETS_URI_V4 ).toBe( 'elementor://context/available-widgets/v4' );
75
+ } );
76
+ } );
@@ -1,95 +1,98 @@
1
- import { type V1ElementConfig } from '@elementor/editor-elements';
1
+ import { httpService } from '@elementor/http-client';
2
2
 
3
- import { buildLlmGuidance, enrichPropertiesWithBaseSettingsHints, mergeInstructions } from '../build-llm-guidance';
3
+ jest.mock( '@elementor/http-client', () => ( {
4
+ httpService: jest.fn(),
5
+ } ) );
4
6
 
5
- const mockEmailBaseSettings = {
6
- email: {
7
- $$type: 'emails',
8
- value: {
9
- to: {
10
- $$type: 'string-array',
11
- value: [ { $$type: 'string', value: 'admin@example.com' } ],
12
- },
13
- from: { $$type: 'string', value: 'email@example.com' },
14
- message: { $$type: 'string', value: '[all-fields]' },
15
- },
16
- },
17
- };
7
+ jest.mock( '@elementor/editor-mcp', () => {
8
+ class MockResourceTemplate {
9
+ callbacks: { list?: () => Promise< { resources: unknown[] } > };
10
+ constructor( _uriTemplate: string, callbacks: { list?: () => Promise< { resources: unknown[] } > } ) {
11
+ this.callbacks = callbacks;
12
+ }
13
+ }
14
+ return { ResourceTemplate: MockResourceTemplate };
15
+ } );
18
16
 
19
- const mockFormWidgetData = {
20
- title: 'Form',
21
- controls: {},
22
- elType: 'widget',
23
- meta: { is_container: true },
24
- base_settings: mockEmailBaseSettings,
25
- atomic_props_schema: {
26
- email: { kind: 'object', key: 'emails' },
27
- 'form-name': { kind: 'string', key: 'string' },
28
- },
29
- } as unknown as V1ElementConfig;
17
+ import { initWidgetsSchemaResource, WIDGET_SCHEMA_URI } from '../widgets-schema-resource';
30
18
 
31
- describe( 'build-llm-guidance', () => {
32
- it( 'mergeInstructions combines existing and additional instructions', () => {
33
- expect( mergeInstructions( 'First.', 'Second.' ) ).toBe( 'First. Second.' );
34
- expect( mergeInstructions( undefined, 'Only.' ) ).toBe( 'Only.' );
35
- } );
19
+ const mockedHttpService = httpService as jest.MockedFunction< typeof httpService >;
20
+
21
+ type ResourceTemplateHandler = (
22
+ uri: URL,
23
+ variables: Record< string, string >
24
+ ) => Promise< { contents: { text: string }[] } >;
25
+ type ResourceTemplateLike = { callbacks: { list?: () => Promise< { resources: unknown[] } > } };
36
26
 
37
- it( 'buildLlmGuidance exposes default_settings for widgets with base_settings', () => {
38
- const guidance = buildLlmGuidance( mockFormWidgetData, 'e-form', {} );
27
+ const captureHandlers = () => {
28
+ const resource = jest.fn();
29
+ initWidgetsSchemaResource( { resource } as never );
30
+ const call = resource.mock.calls[ 0 ];
31
+ const template = call[ 1 ] as ResourceTemplateLike;
32
+ return {
33
+ list: template.callbacks.list as () => Promise< { resources: unknown[] } >,
34
+ readHandler: call[ 3 ] as ResourceTemplateHandler,
35
+ };
36
+ };
39
37
 
40
- expect( guidance.default_settings ).toEqual( mockEmailBaseSettings );
41
- expect( guidance.instructions ).toContain( 'Omit them from elementConfig unless the user explicitly asks' );
42
- expect( guidance.can_have_children ).toBe( true );
38
+ describe( 'widgets-schema-resource', () => {
39
+ beforeEach( () => {
40
+ jest.clearAllMocks();
43
41
  } );
44
42
 
45
- it( 'buildLlmGuidance merges style and settings instructions when both exist', () => {
46
- const widgetData = {
47
- ...mockFormWidgetData,
48
- base_styles: {
49
- 'e-form-base': {
50
- variants: [
51
- {
52
- props: {
53
- display: { $$type: 'string', value: 'flex' },
54
- },
55
- },
56
- ],
57
- },
43
+ it( 'lists widget types fetched from the server via the list-widgets tool', async () => {
44
+ // Arrange
45
+ const post = jest.fn().mockResolvedValue( {
46
+ data: {
47
+ data: [
48
+ { type: 'e-heading', version: 'v4' },
49
+ { type: 'e-button', version: 'v4' },
50
+ ],
58
51
  },
59
- } as unknown as V1ElementConfig;
52
+ } );
53
+ mockedHttpService.mockReturnValue( { post } as never );
54
+ const { list } = captureHandlers();
60
55
 
61
- const guidance = buildLlmGuidance( widgetData, 'e-form', {} );
56
+ // Act
57
+ const result = await list();
62
58
 
63
- expect( guidance.default_styles ).toEqual( { display: { $$type: 'string', value: 'flex' } } );
64
- expect( guidance.default_settings ).toEqual( mockEmailBaseSettings );
65
- expect( guidance.instructions ).toContain( 'default styles' );
66
- expect( guidance.instructions ).toContain( 'default settings' );
59
+ // Assert
60
+ expect( post ).toHaveBeenCalledWith( 'elementor/v1/mcp-proxy', { tool: 'list-widgets', input: {} } );
61
+ expect( result.resources ).toEqual( [
62
+ { uri: 'elementor://widgets/schema/e-heading', name: 'Widget schema for e-heading' },
63
+ { uri: 'elementor://widgets/schema/e-button', name: 'Widget schema for e-button' },
64
+ ] );
67
65
  } );
68
66
 
69
- it( 'enrichPropertiesWithBaseSettingsHints adds omit guidance to base setting props', () => {
70
- const enriched = enrichPropertiesWithBaseSettingsHints(
71
- {
72
- email: { type: 'object' },
73
- 'form-name': { type: 'object' },
74
- },
75
- [ 'email' ]
76
- );
67
+ it( 'reads a single widget schema via the get-widget-schema tool', async () => {
68
+ // Arrange
69
+ const post = jest.fn().mockResolvedValue( {
70
+ data: { data: { type: 'object', properties: { text: { mocked: true } } } },
71
+ } );
72
+ mockedHttpService.mockReturnValue( { post } as never );
73
+ const { readHandler } = captureHandlers();
74
+ const uri = new URL( 'elementor://widgets/schema/e-heading' );
75
+
76
+ // Act
77
+ const result = await readHandler( uri, { widgetType: 'e-heading' } );
77
78
 
78
- expect( enriched.email.description ).toContain( 'llm_guidance.default_settings' );
79
- expect( enriched[ 'form-name' ].description ).toBeUndefined();
79
+ // Assert
80
+ expect( post ).toHaveBeenCalledWith( 'elementor/v1/mcp-proxy', {
81
+ tool: 'get-widget-schema',
82
+ input: { widget_type: 'e-heading' },
83
+ } );
84
+ expect( JSON.parse( result.contents[ 0 ].text ) ).toEqual( {
85
+ type: 'object',
86
+ properties: { text: { mocked: true } },
87
+ } );
80
88
  } );
81
- } );
82
89
 
83
- describe( 'widgets-schema-resource base_settings integration', () => {
84
- it( 'keeps base setting props in schema while enriching descriptions', () => {
85
- const properties = enrichPropertiesWithBaseSettingsHints(
86
- {
87
- email: { type: 'object', properties: {} },
88
- },
89
- Object.keys( mockEmailBaseSettings )
90
- );
90
+ it( 'throws when no widget type variable is provided', async () => {
91
+ // Arrange
92
+ const { readHandler } = captureHandlers();
93
+ const uri = new URL( WIDGET_SCHEMA_URI.replace( '{widgetType}', '' ) );
91
94
 
92
- expect( properties ).toHaveProperty( 'email' );
93
- expect( properties.email.description ).toContain( 'omit unless user explicitly requests' );
95
+ // Act & Assert
96
+ await expect( readHandler( uri, {} ) ).rejects.toThrow( 'No widget type provided.' );
94
97
  } );
95
98
  } );
@@ -1,37 +1,42 @@
1
1
  import { type MCPRegistryEntry } from '@elementor/editor-mcp';
2
- import { v1ReadyEvent } from '@elementor/editor-v1-adapters';
2
+ import { type HttpResponse, httpService } from '@elementor/http-client';
3
3
 
4
- import { type AvailableWidget, getAvailableWidgets } from '../utils/element-data-util';
4
+ const MCP_PROXY_URL = 'elementor/v1/mcp-proxy';
5
5
 
6
6
  export const AVAILABLE_WIDGETS_URI = 'elementor://context/available-widgets';
7
7
  export const AVAILABLE_WIDGETS_URI_V4 = 'elementor://context/available-widgets/v4';
8
8
 
9
- export const initAvailableWidgetsResource = ( reg: MCPRegistryEntry ) => {
10
- const { resource, sendResourceUpdated } = reg;
9
+ type WidgetSummary = {
10
+ type: string;
11
+ version: 'v3' | 'v4';
12
+ description?: string;
13
+ };
11
14
 
12
- const buildContents = ( uri: string, filterFunction: ( x: AvailableWidget ) => boolean = () => true ) => {
13
- const widgets = getAvailableWidgets().filter( filterFunction );
14
- return {
15
- contents: [
16
- {
17
- uri,
18
- mimeType: 'application/json',
19
- text: JSON.stringify( widgets, null, 2 ),
20
- },
21
- ],
22
- };
23
- };
15
+ const fetchWidgets = async ( version?: WidgetSummary[ 'version' ] ): Promise< WidgetSummary[] > => {
16
+ const { data } = await httpService().post< HttpResponse< WidgetSummary[] > >( MCP_PROXY_URL, {
17
+ tool: 'list-widgets',
18
+ input: version ? { version } : {},
19
+ } );
24
20
 
25
- const notifyResourcesUpdated = () => {
26
- sendResourceUpdated( {
27
- uri: AVAILABLE_WIDGETS_URI,
28
- ...buildContents( AVAILABLE_WIDGETS_URI ),
29
- } );
30
- sendResourceUpdated( {
31
- uri: AVAILABLE_WIDGETS_URI_V4,
32
- ...buildContents( AVAILABLE_WIDGETS_URI_V4, ( w: AvailableWidget ) => w.version === 'v4' ),
33
- } );
21
+ return data.data ?? [];
22
+ };
23
+
24
+ const buildContents = async ( uri: string, version?: WidgetSummary[ 'version' ] ) => {
25
+ const widgets = await fetchWidgets( version );
26
+
27
+ return {
28
+ contents: [
29
+ {
30
+ uri,
31
+ mimeType: 'application/json',
32
+ text: JSON.stringify( widgets, null, 2 ),
33
+ },
34
+ ],
34
35
  };
36
+ };
37
+
38
+ export const initAvailableWidgetsResource = ( reg: MCPRegistryEntry ) => {
39
+ const { resource } = reg;
35
40
 
36
41
  resource(
37
42
  'available-widgets-v4',
@@ -39,7 +44,7 @@ export const initAvailableWidgetsResource = ( reg: MCPRegistryEntry ) => {
39
44
  {
40
45
  description: 'All registered v4 version widgets',
41
46
  },
42
- async () => buildContents( AVAILABLE_WIDGETS_URI_V4, ( w ) => w.version === 'v4' )
47
+ async () => buildContents( AVAILABLE_WIDGETS_URI_V4, 'v4' )
43
48
  );
44
49
 
45
50
  resource(
@@ -50,18 +55,4 @@ export const initAvailableWidgetsResource = ( reg: MCPRegistryEntry ) => {
50
55
  },
51
56
  async () => buildContents( AVAILABLE_WIDGETS_URI )
52
57
  );
53
-
54
- const eventName = v1ReadyEvent().name;
55
-
56
- const onV1Ready = () => {
57
- const widgets = getAvailableWidgets();
58
- if ( widgets.length === 0 ) {
59
- return;
60
- }
61
- window.removeEventListener( eventName, onV1Ready );
62
- notifyResourcesUpdated();
63
- };
64
-
65
- window.addEventListener( eventName, onV1Ready );
66
- onV1Ready();
67
58
  };
@@ -1,58 +1,5 @@
1
- import { getWidgetsCache } from '@elementor/editor-elements';
2
1
  import { type MCPRegistryEntry, ResourceTemplate } from '@elementor/editor-mcp';
3
- import {
4
- type ArrayPropType,
5
- type ObjectPropType,
6
- type PropType,
7
- Schema,
8
- type TransformablePropType,
9
- type UnionPropType,
10
- } from '@elementor/editor-props';
11
-
12
- import { hasV3Controls, isWidgetAvailableForLLM } from '../utils/element-data-util';
13
- import { buildLlmGuidance, enrichPropertiesWithBaseSettingsHints } from './build-llm-guidance';
14
-
15
- const V3_LAYOUT_CONTROL_TYPES = new Set( [ 'section', 'tab', 'tabs' ] );
16
-
17
- type V3ControlMetadataEntry = {
18
- default?: unknown;
19
- type?: string;
20
- options?: unknown;
21
- };
22
-
23
- function extractV3ControlsMetadata( controls: unknown ): Record< string, V3ControlMetadataEntry > {
24
- if ( ! hasV3Controls( controls ) ) {
25
- return {};
26
- }
27
- const result: Record< string, V3ControlMetadataEntry > = {};
28
- for ( const [ controlKey, raw ] of Object.entries( controls as Record< string, unknown > ) ) {
29
- if ( ! raw || typeof raw !== 'object' ) {
30
- continue;
31
- }
32
- const control = raw as Record< string, unknown >;
33
- const controlType = typeof control.type === 'string' ? control.type : undefined;
34
- if ( controlType && V3_LAYOUT_CONTROL_TYPES.has( controlType ) ) {
35
- continue;
36
- }
37
- const entry: V3ControlMetadataEntry = {};
38
- if ( Object.prototype.hasOwnProperty.call( control, 'default' ) ) {
39
- entry.default = control.default;
40
- }
41
- if ( controlType ) {
42
- entry.type = controlType;
43
- }
44
- if ( Object.prototype.hasOwnProperty.call( control, 'options' ) && control.options !== undefined ) {
45
- const options = control.options;
46
- if ( options && typeof options === 'object' && ! Array.isArray( options ) ) {
47
- entry.options = Object.keys( options as Record< string, unknown > );
48
- } else {
49
- entry.options = options;
50
- }
51
- }
52
- result[ controlKey ] = entry;
53
- }
54
- return result;
55
- }
2
+ import { type HttpResponse, httpService } from '@elementor/http-client';
56
3
 
57
4
  export const CANVAS_SERVER_NAME = 'editor-canvas';
58
5
 
@@ -62,20 +9,43 @@ export const STYLE_SCHEMA_URI = 'elementor://styles/schema/{category}';
62
9
  export const BEST_PRACTICES_URI = 'elementor://style/best-practices';
63
10
  export const BEST_PRACTICES_FULL_URI = `${ CANVAS_SERVER_NAME }_${ BEST_PRACTICES_URI }`;
64
11
 
12
+ const MCP_PROXY_URL = 'elementor/v1/mcp-proxy';
13
+
14
+ type WidgetSummary = {
15
+ type: string;
16
+ version: 'v3' | 'v4';
17
+ description?: string;
18
+ };
19
+
20
+ const listWidgetTypes = async (): Promise< string[] > => {
21
+ const { data } = await httpService().post< HttpResponse< WidgetSummary[] > >( MCP_PROXY_URL, {
22
+ tool: 'list-widgets',
23
+ input: {},
24
+ } );
25
+
26
+ return ( data.data ?? [] ).map( ( widget ) => widget.type );
27
+ };
28
+
29
+ const fetchWidgetSchema = async ( widgetType: string ): Promise< Record< string, unknown > > => {
30
+ const { data } = await httpService().post< HttpResponse< Record< string, unknown > > >( MCP_PROXY_URL, {
31
+ tool: 'get-widget-schema',
32
+ input: { widget_type: widgetType },
33
+ } );
34
+
35
+ return data.data ?? {};
36
+ };
37
+
65
38
  export const initWidgetsSchemaResource = ( reg: MCPRegistryEntry ) => {
66
39
  const { resource } = reg;
67
40
 
68
41
  resource(
69
42
  'widget-schema-by-type',
70
43
  new ResourceTemplate( WIDGET_SCHEMA_URI, {
71
- list: () => {
72
- const cache = getWidgetsCache() || {};
73
- const availableWidgets = Object.keys( cache ).filter( ( widgetType ) =>
74
- isWidgetAvailableForLLM( cache[ widgetType ] )
75
- );
44
+ list: async () => {
45
+ const widgetTypes = await listWidgetTypes();
76
46
 
77
47
  return {
78
- resources: availableWidgets.map( ( widgetType ) => ( {
48
+ resources: widgetTypes.map( ( widgetType ) => ( {
79
49
  uri: `elementor://widgets/schema/${ widgetType }`,
80
50
  name: 'Widget schema for ' + widgetType,
81
51
  } ) ),
@@ -88,107 +58,22 @@ export const initWidgetsSchemaResource = ( reg: MCPRegistryEntry ) => {
88
58
  async ( uri, variables ) => {
89
59
  const widgetType =
90
60
  typeof variables.widgetType === 'string' ? variables.widgetType : variables.widgetType?.[ 0 ];
91
- const widgetData = getWidgetsCache()?.[ widgetType ];
92
- if ( ! widgetData ) {
93
- throw new Error( `No prop schema found for element type: ${ widgetType }` );
94
- }
95
- const propSchema = widgetData.atomic_props_schema;
96
- if ( ! propSchema ) {
97
- if ( ! hasV3Controls( widgetData.controls ) ) {
98
- throw new Error( `No prop schema found for element type: ${ widgetType }` );
99
- }
100
- const controlMetadata = extractV3ControlsMetadata( widgetData.controls );
101
- return {
102
- contents: [
103
- {
104
- uri: uri.toString(),
105
- mimeType: 'application/json',
106
- text: JSON.stringify( {
107
- widget_version: 'v3',
108
- message:
109
- 'This widget exists in the editor but has no atomic props schema (V4). Use control_metadata as non-authoritative hints from legacy controls.',
110
- fields_note: 'All settings are optional; there is no JSON schema for this widget type.',
111
- properties: controlMetadata,
112
- } ),
113
- },
114
- ],
115
- };
116
- }
117
- const baseSettingsKeys = Object.keys( widgetData?.base_settings ?? {} );
118
61
 
119
- const asJson = enrichPropertiesWithBaseSettingsHints(
120
- Object.fromEntries(
121
- Object.entries( propSchema )
122
- .filter( ( [ key, propType ] ) => Schema.isPropKeyConfigurable( key, propType as PropType ) )
123
- .map( ( [ key, propType ] ) => [ key, Schema.propTypeToJsonSchema( propType ) ] )
124
- ),
125
- baseSettingsKeys
126
- );
127
-
128
- const description =
129
- typeof widgetData?.meta?.description === 'string' ? widgetData.meta.description : undefined;
62
+ if ( ! widgetType ) {
63
+ throw new Error( 'No widget type provided.' );
64
+ }
130
65
 
131
- const allWidgets = getWidgetsCache() || {};
132
- const llmGuidance = buildLlmGuidance( widgetData, widgetType, allWidgets );
66
+ const schema = await fetchWidgetSchema( widgetType );
133
67
 
134
68
  return {
135
69
  contents: [
136
70
  {
137
71
  uri: uri.toString(),
138
72
  mimeType: 'application/json',
139
- text: JSON.stringify( {
140
- type: 'object',
141
- properties: asJson,
142
- description,
143
- llm_guidance: llmGuidance,
144
- } ),
73
+ text: JSON.stringify( schema ),
145
74
  },
146
75
  ],
147
76
  };
148
77
  }
149
78
  );
150
79
  };
151
-
152
- function cleanupPropSchema( propSchema: Record< string, PropType > ): Record< string, PropType > {
153
- const result: Record< string, Partial< PropType > > = {};
154
- Object.keys( propSchema ).forEach( ( propName ) => {
155
- result[ propName ] = cleanupPropType( propSchema[ propName ] );
156
- } );
157
- return result as Record< string, PropType >;
158
- }
159
- function cleanupPropType( propType: PropType & { key?: string } ): Partial< PropType > {
160
- const result: Partial< PropType > = {};
161
- Object.keys( propType ).forEach( ( property ) => {
162
- switch ( property ) {
163
- case 'key':
164
- case 'kind':
165
- ( result as Record< string, unknown > )[ property ] = propType[ property ];
166
- break;
167
- case 'meta':
168
- case 'settings':
169
- {
170
- if ( Object.keys( propType[ property ] || {} ).length > 0 ) {
171
- ( result as Record< string, unknown > )[ property ] = propType[ property ];
172
- }
173
- }
174
- break;
175
- }
176
- } );
177
- if ( result.kind === 'plain' ) {
178
- Object.defineProperty( result, 'kind', { value: 'string' } );
179
- } else if ( result.kind === 'array' ) {
180
- result.item_prop_type = cleanupPropType( ( propType as ArrayPropType ).item_prop_type ) as PropType;
181
- } else if ( result.kind === 'object' ) {
182
- const shape = ( propType as ObjectPropType ).shape as Record< string, PropType >;
183
- const cleanedShape = cleanupPropSchema( shape );
184
- result.shape = cleanedShape;
185
- } else if ( result.kind === 'union' ) {
186
- const propTypes = ( propType as UnionPropType ).prop_types;
187
- const cleanedPropTypes: Record< string, Partial< PropType > > = {};
188
- Object.keys( propTypes ).forEach( ( key ) => {
189
- cleanedPropTypes[ key ] = cleanupPropType( propTypes[ key ] );
190
- } );
191
- result.prop_types = cleanedPropTypes as Record< string, TransformablePropType >;
192
- }
193
- return result;
194
- }