@esheet/adapters 0.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/README.md +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +5 -0
- package/dist/lib/mcp.d.ts +128 -0
- package/dist/lib/mcp.d.ts.map +1 -0
- package/dist/lib/mcp.js +408 -0
- package/dist/lib/surveyjs-converter.d.ts +120 -0
- package/dist/lib/surveyjs-converter.d.ts.map +1 -0
- package/dist/lib/surveyjs-converter.js +698 -0
- package/package.json +39 -0
package/README.md
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# Adapter Pakcage
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export { convertSurveyJS, convertSurveyJSToESheet, importFromSurveyJS, exportToSurveyJS, SURVEYJS_SYSTEM_PROMPT, } from './lib/surveyjs-converter.js';
|
|
2
|
+
export { importFromMcp, exportToMcp, type McpElicitationSchema, type McpElicitationRequest, type McpProperty, type McpStringProp, type McpNumberProp, type McpBooleanProp, type McpArrayProp, type McpConstOption, } from './lib/mcp.js';
|
|
3
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAIA,OAAO,EACL,eAAe,EACf,uBAAuB,EACvB,kBAAkB,EAClB,gBAAgB,EAChB,sBAAsB,GACvB,MAAM,6BAA6B,CAAC;AACrC,OAAO,EACL,aAAa,EACb,WAAW,EACX,KAAK,oBAAoB,EACzB,KAAK,qBAAqB,EAC1B,KAAK,WAAW,EAChB,KAAK,aAAa,EAClB,KAAK,aAAa,EAClB,KAAK,cAAc,EACnB,KAAK,YAAY,EACjB,KAAK,cAAc,GACpB,MAAM,cAAc,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
// =============================================================================
|
|
2
|
+
// @esheet/adapters - Convert external form schemas to eSheet FormDefinition
|
|
3
|
+
// =============================================================================
|
|
4
|
+
export { convertSurveyJS, convertSurveyJSToESheet, importFromSurveyJS, exportToSurveyJS, SURVEYJS_SYSTEM_PROMPT, } from './lib/surveyjs-converter.js';
|
|
5
|
+
export { importFromMcp, exportToMcp, } from './lib/mcp.js';
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import type { FormDefinition } from '@esheet/core';
|
|
2
|
+
/** A literal const/title pair used in single- and multi-select enums. */
|
|
3
|
+
export interface McpConstOption {
|
|
4
|
+
const: string;
|
|
5
|
+
title: string;
|
|
6
|
+
}
|
|
7
|
+
/** String property — plain text, formatted text, or single-select enum. */
|
|
8
|
+
export interface McpStringProp {
|
|
9
|
+
type: 'string';
|
|
10
|
+
title?: string;
|
|
11
|
+
description?: string;
|
|
12
|
+
minLength?: number;
|
|
13
|
+
maxLength?: number;
|
|
14
|
+
pattern?: string;
|
|
15
|
+
format?: 'email' | 'uri' | 'date' | 'date-time';
|
|
16
|
+
/** Simple named enum (parallel array to `enum`). Spec 2025-11-25. */
|
|
17
|
+
enum?: string[];
|
|
18
|
+
enumNames?: string[];
|
|
19
|
+
oneOf?: McpConstOption[];
|
|
20
|
+
default?: string;
|
|
21
|
+
}
|
|
22
|
+
/** Numeric property. */
|
|
23
|
+
export interface McpNumberProp {
|
|
24
|
+
type: 'number' | 'integer';
|
|
25
|
+
title?: string;
|
|
26
|
+
description?: string;
|
|
27
|
+
minimum?: number;
|
|
28
|
+
maximum?: number;
|
|
29
|
+
default?: number;
|
|
30
|
+
}
|
|
31
|
+
/** Boolean property. */
|
|
32
|
+
export interface McpBooleanProp {
|
|
33
|
+
type: 'boolean';
|
|
34
|
+
title?: string;
|
|
35
|
+
description?: string;
|
|
36
|
+
default?: boolean;
|
|
37
|
+
}
|
|
38
|
+
/** Multi-select enum property. */
|
|
39
|
+
export interface McpArrayProp {
|
|
40
|
+
type: 'array';
|
|
41
|
+
title?: string;
|
|
42
|
+
description?: string;
|
|
43
|
+
minItems?: number;
|
|
44
|
+
maxItems?: number;
|
|
45
|
+
uniqueItems?: boolean;
|
|
46
|
+
/** Spec 2025-11-25 wraps enum inside `{ type: "string", enum: [...] }` or uses `anyOf`. */
|
|
47
|
+
items: {
|
|
48
|
+
type?: string;
|
|
49
|
+
enum: string[];
|
|
50
|
+
} | {
|
|
51
|
+
anyOf: McpConstOption[];
|
|
52
|
+
};
|
|
53
|
+
default?: string[];
|
|
54
|
+
}
|
|
55
|
+
export type McpProperty = McpStringProp | McpNumberProp | McpBooleanProp | McpArrayProp;
|
|
56
|
+
/** The `requestedSchema` object in an MCP form-mode elicitation request. */
|
|
57
|
+
export interface McpElicitationSchema {
|
|
58
|
+
type: 'object';
|
|
59
|
+
title?: string;
|
|
60
|
+
properties: Record<string, McpProperty>;
|
|
61
|
+
required?: string[];
|
|
62
|
+
}
|
|
63
|
+
/** Full MCP JSON-RPC 2.0 elicitation/create request envelope (spec 2025-11-25). */
|
|
64
|
+
export interface McpElicitationRequest {
|
|
65
|
+
jsonrpc: '2.0';
|
|
66
|
+
id: string | number;
|
|
67
|
+
method: 'elicitation/create';
|
|
68
|
+
params: {
|
|
69
|
+
/** Form mode (default when omitted). */
|
|
70
|
+
mode?: 'form';
|
|
71
|
+
message: string;
|
|
72
|
+
requestedSchema: McpElicitationSchema;
|
|
73
|
+
} | {
|
|
74
|
+
/** URL mode — out-of-band interaction, no requestedSchema. */
|
|
75
|
+
mode: 'url';
|
|
76
|
+
message: string;
|
|
77
|
+
url: string;
|
|
78
|
+
elicitationId: string;
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Convert an MCP elicitation `requestedSchema` into an eSheet `FormDefinition`.
|
|
83
|
+
*
|
|
84
|
+
* Field IDs are taken directly from the property key names so that a round-trip
|
|
85
|
+
* export preserves the original MCP property names.
|
|
86
|
+
*
|
|
87
|
+
* Field types are mapped as follows:
|
|
88
|
+
* - `string` (plain) → `text` (+ `inputType` from `format`)
|
|
89
|
+
* - `string` with `enum`/`enumNames` → `radio`
|
|
90
|
+
* - `string` with `oneOf` → `radio` (options carry display titles)
|
|
91
|
+
* - `number` / `integer` → `text` + `inputType: 'number'`
|
|
92
|
+
* - `boolean` → `boolean`
|
|
93
|
+
* - `array` with enum items → `check` (multi-select)
|
|
94
|
+
*
|
|
95
|
+
* All MCP constraints (`default`, `minLength`, `maxLength`, `pattern`,
|
|
96
|
+
* `minimum`, `maximum`, `minItems`, `maxItems`) are preserved in each
|
|
97
|
+
* field's `_sourceData` so they survive a round-trip export.
|
|
98
|
+
*/
|
|
99
|
+
export declare function importFromMcp(schema: McpElicitationSchema, options?: {
|
|
100
|
+
formId?: string;
|
|
101
|
+
title?: string;
|
|
102
|
+
description?: string;
|
|
103
|
+
mcpId?: string | number;
|
|
104
|
+
mcpMessage?: string;
|
|
105
|
+
/** Top-level envelope `meta` field (non-standard, preserved for round-trip). */
|
|
106
|
+
mcpMeta?: unknown;
|
|
107
|
+
}): FormDefinition;
|
|
108
|
+
/**
|
|
109
|
+
* Convert an eSheet `FormDefinition` into an MCP elicitation request (form mode).
|
|
110
|
+
*
|
|
111
|
+
* Section containers are flattened — their leaf fields are promoted to the
|
|
112
|
+
* top-level properties object. Field types with no MCP equivalent (matrix,
|
|
113
|
+
* ranking, image, html, signature, diagram, display, multitext) are silently
|
|
114
|
+
* skipped.
|
|
115
|
+
*
|
|
116
|
+
* All MCP constraints stored in `_sourceData` during import are restored so
|
|
117
|
+
* the output is a lossless round-trip of the original schema.
|
|
118
|
+
*
|
|
119
|
+
* Field types map as follows:
|
|
120
|
+
* - `text` / `longtext` → `string` (+ `format` from `inputType`)
|
|
121
|
+
* - `boolean` → `boolean`
|
|
122
|
+
* - `radio` / `dropdown` → `string` enum (with or without titles)
|
|
123
|
+
* - `check` / `multiselectdropdown` → `array` enum (with or without titles)
|
|
124
|
+
* - `rating` → `integer` (min=1, max=option count)
|
|
125
|
+
* - `slider` → `number`
|
|
126
|
+
*/
|
|
127
|
+
export declare function exportToMcp(definition: FormDefinition): McpElicitationRequest;
|
|
128
|
+
//# sourceMappingURL=mcp.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mcp.d.ts","sourceRoot":"","sources":["../../src/lib/mcp.ts"],"names":[],"mappings":"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"}
|
package/dist/lib/mcp.js
ADDED
|
@@ -0,0 +1,408 @@
|
|
|
1
|
+
import { generateOptionId } from '@esheet/core';
|
|
2
|
+
// ---------------------------------------------------------------------------
|
|
3
|
+
// Import: MCP elicitation schema → FormDefinition
|
|
4
|
+
// ---------------------------------------------------------------------------
|
|
5
|
+
/**
|
|
6
|
+
* Convert an MCP elicitation `requestedSchema` into an eSheet `FormDefinition`.
|
|
7
|
+
*
|
|
8
|
+
* Field IDs are taken directly from the property key names so that a round-trip
|
|
9
|
+
* export preserves the original MCP property names.
|
|
10
|
+
*
|
|
11
|
+
* Field types are mapped as follows:
|
|
12
|
+
* - `string` (plain) → `text` (+ `inputType` from `format`)
|
|
13
|
+
* - `string` with `enum`/`enumNames` → `radio`
|
|
14
|
+
* - `string` with `oneOf` → `radio` (options carry display titles)
|
|
15
|
+
* - `number` / `integer` → `text` + `inputType: 'number'`
|
|
16
|
+
* - `boolean` → `boolean`
|
|
17
|
+
* - `array` with enum items → `check` (multi-select)
|
|
18
|
+
*
|
|
19
|
+
* All MCP constraints (`default`, `minLength`, `maxLength`, `pattern`,
|
|
20
|
+
* `minimum`, `maximum`, `minItems`, `maxItems`) are preserved in each
|
|
21
|
+
* field's `_sourceData` so they survive a round-trip export.
|
|
22
|
+
*/
|
|
23
|
+
export function importFromMcp(schema, options) {
|
|
24
|
+
const existingIds = new Set();
|
|
25
|
+
const requiredKeys = new Set(schema.required ?? []);
|
|
26
|
+
const fields = Object.entries(schema.properties ?? {}).map(([key, prop]) => mcpPropToField(key, prop, requiredKeys.has(key), existingIds));
|
|
27
|
+
// Preserve MCP envelope + schema metadata for lossless round-trip export.
|
|
28
|
+
const mcpMeta = {};
|
|
29
|
+
if (options?.mcpId !== undefined)
|
|
30
|
+
mcpMeta['mcpId'] = options.mcpId;
|
|
31
|
+
if (options?.mcpMessage !== undefined)
|
|
32
|
+
mcpMeta['mcpMessage'] = options.mcpMessage;
|
|
33
|
+
if (schema.title !== undefined)
|
|
34
|
+
mcpMeta['schemaTitle'] = schema.title;
|
|
35
|
+
if (options?.mcpMeta !== undefined)
|
|
36
|
+
mcpMeta['meta'] = options.mcpMeta;
|
|
37
|
+
const hasMcpMeta = Object.keys(mcpMeta).length > 0;
|
|
38
|
+
return {
|
|
39
|
+
id: options?.formId ?? 'mcp-form',
|
|
40
|
+
...(options?.title !== undefined ? { title: options.title } : {}),
|
|
41
|
+
...(options?.description !== undefined
|
|
42
|
+
? { description: options.description }
|
|
43
|
+
: {}),
|
|
44
|
+
...(hasMcpMeta ? { _sourceData: mcpMeta } : {}),
|
|
45
|
+
fields,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
function mcpPropToField(key, prop, isRequired, existingIds) {
|
|
49
|
+
const question = prop.title;
|
|
50
|
+
// Collect all preservable MCP metadata into _sourceData.
|
|
51
|
+
const meta = {};
|
|
52
|
+
if (prop.description !== undefined)
|
|
53
|
+
meta.description = prop.description;
|
|
54
|
+
const shared = {
|
|
55
|
+
id: key,
|
|
56
|
+
...(question !== undefined ? { question } : {}),
|
|
57
|
+
...(isRequired ? { required: true } : {}),
|
|
58
|
+
};
|
|
59
|
+
if (prop.type === 'object') {
|
|
60
|
+
// Nested objects are outside the MCP flat-form spec — import as longtext
|
|
61
|
+
// so the user can inspect/paste the JSON value. Full definition preserved
|
|
62
|
+
// in _sourceData so export restores it verbatim.
|
|
63
|
+
return {
|
|
64
|
+
...shared,
|
|
65
|
+
_sourceData: { ...meta, mcpPropDefinition: prop },
|
|
66
|
+
fieldType: 'longtext',
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
if (prop.type === 'array') {
|
|
70
|
+
if (prop.default !== undefined)
|
|
71
|
+
meta.default = prop.default;
|
|
72
|
+
if (prop.minItems !== undefined)
|
|
73
|
+
meta.minItems = prop.minItems;
|
|
74
|
+
if (prop.maxItems !== undefined)
|
|
75
|
+
meta.maxItems = prop.maxItems;
|
|
76
|
+
if (prop.uniqueItems !== undefined)
|
|
77
|
+
meta.uniqueItems = prop.uniqueItems;
|
|
78
|
+
const options = mcpItemsToOptions(prop.items, existingIds);
|
|
79
|
+
// Array of objects (no enum/anyOf) is outside spec — fall back to longtext,
|
|
80
|
+
// full definition preserved for verbatim export.
|
|
81
|
+
if (options === null) {
|
|
82
|
+
return {
|
|
83
|
+
...shared,
|
|
84
|
+
_sourceData: { ...meta, mcpPropDefinition: prop },
|
|
85
|
+
fieldType: 'longtext',
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
return {
|
|
89
|
+
...shared,
|
|
90
|
+
...(Object.keys(meta).length > 0 ? { _sourceData: meta } : {}),
|
|
91
|
+
fieldType: 'check',
|
|
92
|
+
options,
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
if (prop.type === 'boolean') {
|
|
96
|
+
if (prop.default !== undefined)
|
|
97
|
+
meta.default = prop.default;
|
|
98
|
+
return {
|
|
99
|
+
...shared,
|
|
100
|
+
...(Object.keys(meta).length > 0 ? { _sourceData: meta } : {}),
|
|
101
|
+
fieldType: 'boolean',
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
if (prop.type === 'number' || prop.type === 'integer') {
|
|
105
|
+
meta.mcpType = prop.type;
|
|
106
|
+
if (prop.default !== undefined)
|
|
107
|
+
meta.default = prop.default;
|
|
108
|
+
if (prop.minimum !== undefined)
|
|
109
|
+
meta.minimum = prop.minimum;
|
|
110
|
+
if (prop.maximum !== undefined)
|
|
111
|
+
meta.maximum = prop.maximum;
|
|
112
|
+
return {
|
|
113
|
+
...shared,
|
|
114
|
+
...(Object.keys(meta).length > 0 ? { _sourceData: meta } : {}),
|
|
115
|
+
fieldType: 'text',
|
|
116
|
+
inputType: 'number',
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
// prop.type === 'string'
|
|
120
|
+
const strProp = prop;
|
|
121
|
+
if (strProp.default !== undefined)
|
|
122
|
+
meta.default = strProp.default;
|
|
123
|
+
if (strProp.minLength !== undefined)
|
|
124
|
+
meta.minLength = strProp.minLength;
|
|
125
|
+
if (strProp.maxLength !== undefined)
|
|
126
|
+
meta.maxLength = strProp.maxLength;
|
|
127
|
+
if (strProp.pattern !== undefined)
|
|
128
|
+
meta.pattern = strProp.pattern;
|
|
129
|
+
const metaSpread = Object.keys(meta).length > 0 ? { _sourceData: meta } : {};
|
|
130
|
+
if (strProp.oneOf) {
|
|
131
|
+
return {
|
|
132
|
+
...shared,
|
|
133
|
+
...metaSpread,
|
|
134
|
+
fieldType: 'radio',
|
|
135
|
+
options: strProp.oneOf.map(({ const: v, title: t }) => makeOption(v, existingIds, t)),
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
if (strProp.enum) {
|
|
139
|
+
// `enumNames` (parallel array) provides display titles — spec 2025-11-25.
|
|
140
|
+
const names = strProp.enumNames;
|
|
141
|
+
return {
|
|
142
|
+
...shared,
|
|
143
|
+
...metaSpread,
|
|
144
|
+
fieldType: 'radio',
|
|
145
|
+
options: strProp.enum.map((v, i) => makeOption(v, existingIds, names?.[i])),
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
// plain string
|
|
149
|
+
return {
|
|
150
|
+
...shared,
|
|
151
|
+
...metaSpread,
|
|
152
|
+
fieldType: 'text',
|
|
153
|
+
...(strProp.format !== undefined
|
|
154
|
+
? { inputType: mcpFormatToInputType(strProp.format) }
|
|
155
|
+
: {}),
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Returns null when items is an array-of-objects (outside MCP flat-form spec).
|
|
160
|
+
* Returns an empty array for a valid but empty enum.
|
|
161
|
+
*/
|
|
162
|
+
function mcpItemsToOptions(items, existingIds) {
|
|
163
|
+
if ('anyOf' in items) {
|
|
164
|
+
return items.anyOf.map(({ const: v, title: t }) => makeOption(v, existingIds, t));
|
|
165
|
+
}
|
|
166
|
+
if (!Array.isArray(items.enum)) {
|
|
167
|
+
// items is an object schema (array-of-objects) — not a flat enum.
|
|
168
|
+
return null;
|
|
169
|
+
}
|
|
170
|
+
return items.enum.map((v) => makeOption(v, existingIds));
|
|
171
|
+
}
|
|
172
|
+
function makeOption(value, existingIds, text) {
|
|
173
|
+
const id = generateOptionId(existingIds);
|
|
174
|
+
existingIds.add(id);
|
|
175
|
+
return text !== undefined ? { id, value, text } : { id, value };
|
|
176
|
+
}
|
|
177
|
+
function mcpFormatToInputType(format) {
|
|
178
|
+
const map = {
|
|
179
|
+
email: 'email',
|
|
180
|
+
uri: 'url',
|
|
181
|
+
date: 'date',
|
|
182
|
+
'date-time': 'datetime-local',
|
|
183
|
+
};
|
|
184
|
+
return format !== undefined ? map[format] : undefined;
|
|
185
|
+
}
|
|
186
|
+
// ---------------------------------------------------------------------------
|
|
187
|
+
// Export: FormDefinition → MCP elicitation schema
|
|
188
|
+
// ---------------------------------------------------------------------------
|
|
189
|
+
/**
|
|
190
|
+
* Convert an eSheet `FormDefinition` into an MCP elicitation request (form mode).
|
|
191
|
+
*
|
|
192
|
+
* Section containers are flattened — their leaf fields are promoted to the
|
|
193
|
+
* top-level properties object. Field types with no MCP equivalent (matrix,
|
|
194
|
+
* ranking, image, html, signature, diagram, display, multitext) are silently
|
|
195
|
+
* skipped.
|
|
196
|
+
*
|
|
197
|
+
* All MCP constraints stored in `_sourceData` during import are restored so
|
|
198
|
+
* the output is a lossless round-trip of the original schema.
|
|
199
|
+
*
|
|
200
|
+
* Field types map as follows:
|
|
201
|
+
* - `text` / `longtext` → `string` (+ `format` from `inputType`)
|
|
202
|
+
* - `boolean` → `boolean`
|
|
203
|
+
* - `radio` / `dropdown` → `string` enum (with or without titles)
|
|
204
|
+
* - `check` / `multiselectdropdown` → `array` enum (with or without titles)
|
|
205
|
+
* - `rating` → `integer` (min=1, max=option count)
|
|
206
|
+
* - `slider` → `number`
|
|
207
|
+
*/
|
|
208
|
+
export function exportToMcp(definition) {
|
|
209
|
+
const properties = {};
|
|
210
|
+
const required = [];
|
|
211
|
+
for (const field of collectLeafFields(definition.fields)) {
|
|
212
|
+
const prop = fieldToMcpProp(field);
|
|
213
|
+
if (prop === null)
|
|
214
|
+
continue;
|
|
215
|
+
properties[field.id] = prop;
|
|
216
|
+
if (field.required)
|
|
217
|
+
required.push(field.id);
|
|
218
|
+
}
|
|
219
|
+
const src = definition._sourceData;
|
|
220
|
+
const requestedSchema = { type: 'object', properties };
|
|
221
|
+
if (src?.schemaTitle !== undefined)
|
|
222
|
+
requestedSchema.title = src.schemaTitle;
|
|
223
|
+
if (required.length > 0)
|
|
224
|
+
requestedSchema.required = required;
|
|
225
|
+
const envelopeId = src?.mcpId ?? definition.id;
|
|
226
|
+
const message = src?.mcpMessage ?? definition.description ?? definition.title ?? '';
|
|
227
|
+
const envelope = {
|
|
228
|
+
jsonrpc: '2.0',
|
|
229
|
+
id: envelopeId,
|
|
230
|
+
method: 'elicitation/create',
|
|
231
|
+
params: { mode: 'form', message, requestedSchema },
|
|
232
|
+
};
|
|
233
|
+
// Restore non-standard top-level meta if it was preserved during import.
|
|
234
|
+
if (src?.meta !== undefined) {
|
|
235
|
+
envelope['meta'] = src.meta;
|
|
236
|
+
}
|
|
237
|
+
return envelope;
|
|
238
|
+
}
|
|
239
|
+
/** Recursively flatten sections to their answerable leaf fields. */
|
|
240
|
+
function collectLeafFields(fields) {
|
|
241
|
+
const result = [];
|
|
242
|
+
for (const f of fields) {
|
|
243
|
+
if (f.fieldType === 'section' && f.fields) {
|
|
244
|
+
result.push(...collectLeafFields(f.fields));
|
|
245
|
+
}
|
|
246
|
+
else {
|
|
247
|
+
result.push(f);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
return result;
|
|
251
|
+
}
|
|
252
|
+
function fieldToMcpProp(field) {
|
|
253
|
+
const title = field.question;
|
|
254
|
+
const meta = field._sourceData;
|
|
255
|
+
const description = meta?.description;
|
|
256
|
+
switch (field.fieldType) {
|
|
257
|
+
case 'text':
|
|
258
|
+
case 'longtext': {
|
|
259
|
+
// If this field was imported from a non-flat MCP type (object or
|
|
260
|
+
// array-of-objects), restore the original property definition verbatim.
|
|
261
|
+
if (meta?.mcpPropDefinition !== undefined) {
|
|
262
|
+
return meta.mcpPropDefinition;
|
|
263
|
+
}
|
|
264
|
+
// Restore number/integer fields that were imported as text+inputType:number.
|
|
265
|
+
if (meta?.mcpType !== undefined) {
|
|
266
|
+
return {
|
|
267
|
+
type: meta.mcpType,
|
|
268
|
+
...(title !== undefined ? { title } : {}),
|
|
269
|
+
...(description !== undefined ? { description } : {}),
|
|
270
|
+
...(meta.minimum !== undefined ? { minimum: meta.minimum } : {}),
|
|
271
|
+
...(meta.maximum !== undefined ? { maximum: meta.maximum } : {}),
|
|
272
|
+
...(meta.default !== undefined
|
|
273
|
+
? { default: meta.default }
|
|
274
|
+
: {}),
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
const format = inputTypeToMcpFormat(field.inputType);
|
|
278
|
+
return {
|
|
279
|
+
type: 'string',
|
|
280
|
+
...(title !== undefined ? { title } : {}),
|
|
281
|
+
...(description !== undefined ? { description } : {}),
|
|
282
|
+
...(meta?.minLength !== undefined ? { minLength: meta.minLength } : {}),
|
|
283
|
+
...(meta?.maxLength !== undefined ? { maxLength: meta.maxLength } : {}),
|
|
284
|
+
...(meta?.pattern !== undefined ? { pattern: meta.pattern } : {}),
|
|
285
|
+
...(format !== undefined ? { format } : {}),
|
|
286
|
+
...(meta?.default !== undefined
|
|
287
|
+
? { default: meta.default }
|
|
288
|
+
: {}),
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
case 'boolean':
|
|
292
|
+
return {
|
|
293
|
+
type: 'boolean',
|
|
294
|
+
...(title !== undefined ? { title } : {}),
|
|
295
|
+
...(description !== undefined ? { description } : {}),
|
|
296
|
+
...(meta?.default !== undefined
|
|
297
|
+
? { default: meta.default }
|
|
298
|
+
: {}),
|
|
299
|
+
};
|
|
300
|
+
case 'radio':
|
|
301
|
+
case 'dropdown': {
|
|
302
|
+
const options = field.options ?? [];
|
|
303
|
+
if (options.length === 0) {
|
|
304
|
+
return {
|
|
305
|
+
type: 'string',
|
|
306
|
+
...(title !== undefined ? { title } : {}),
|
|
307
|
+
...(description !== undefined ? { description } : {}),
|
|
308
|
+
...(meta?.default !== undefined
|
|
309
|
+
? { default: meta.default }
|
|
310
|
+
: {}),
|
|
311
|
+
};
|
|
312
|
+
}
|
|
313
|
+
const hasText = options.some((o) => o.text !== undefined);
|
|
314
|
+
if (hasText) {
|
|
315
|
+
return {
|
|
316
|
+
type: 'string',
|
|
317
|
+
...(title !== undefined ? { title } : {}),
|
|
318
|
+
...(description !== undefined ? { description } : {}),
|
|
319
|
+
oneOf: options.map((o) => ({
|
|
320
|
+
const: o.value,
|
|
321
|
+
title: o.text ?? o.value,
|
|
322
|
+
})),
|
|
323
|
+
...(meta?.default !== undefined
|
|
324
|
+
? { default: meta.default }
|
|
325
|
+
: {}),
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
return {
|
|
329
|
+
type: 'string',
|
|
330
|
+
...(title !== undefined ? { title } : {}),
|
|
331
|
+
...(description !== undefined ? { description } : {}),
|
|
332
|
+
enum: options.map((o) => o.value),
|
|
333
|
+
...(meta?.default !== undefined
|
|
334
|
+
? { default: meta.default }
|
|
335
|
+
: {}),
|
|
336
|
+
};
|
|
337
|
+
}
|
|
338
|
+
case 'check':
|
|
339
|
+
case 'multiselectdropdown': {
|
|
340
|
+
const options = field.options ?? [];
|
|
341
|
+
const hasText = options.some((o) => o.text !== undefined);
|
|
342
|
+
const items = hasText
|
|
343
|
+
? {
|
|
344
|
+
anyOf: options.map((o) => ({
|
|
345
|
+
const: o.value,
|
|
346
|
+
title: o.text ?? o.value,
|
|
347
|
+
})),
|
|
348
|
+
}
|
|
349
|
+
: { type: 'string', enum: options.map((o) => o.value) };
|
|
350
|
+
return {
|
|
351
|
+
type: 'array',
|
|
352
|
+
...(title !== undefined ? { title } : {}),
|
|
353
|
+
...(description !== undefined ? { description } : {}),
|
|
354
|
+
...(meta?.minItems !== undefined ? { minItems: meta.minItems } : {}),
|
|
355
|
+
...(meta?.maxItems !== undefined ? { maxItems: meta.maxItems } : {}),
|
|
356
|
+
...(meta?.uniqueItems !== undefined
|
|
357
|
+
? { uniqueItems: meta.uniqueItems }
|
|
358
|
+
: {}),
|
|
359
|
+
items,
|
|
360
|
+
...(meta?.default !== undefined
|
|
361
|
+
? { default: meta.default }
|
|
362
|
+
: {}),
|
|
363
|
+
};
|
|
364
|
+
}
|
|
365
|
+
case 'rating': {
|
|
366
|
+
const max = field.options?.length;
|
|
367
|
+
return {
|
|
368
|
+
type: 'integer',
|
|
369
|
+
minimum: 1,
|
|
370
|
+
...(max !== undefined ? { maximum: max } : {}),
|
|
371
|
+
...(title !== undefined ? { title } : {}),
|
|
372
|
+
...(description !== undefined ? { description } : {}),
|
|
373
|
+
...(meta?.default !== undefined
|
|
374
|
+
? { default: meta.default }
|
|
375
|
+
: {}),
|
|
376
|
+
};
|
|
377
|
+
}
|
|
378
|
+
case 'slider': {
|
|
379
|
+
// Restore number vs integer distinction from original import.
|
|
380
|
+
const numType = meta?.mcpType ?? 'number';
|
|
381
|
+
return {
|
|
382
|
+
type: numType,
|
|
383
|
+
...(title !== undefined ? { title } : {}),
|
|
384
|
+
...(description !== undefined ? { description } : {}),
|
|
385
|
+
...(meta?.minimum !== undefined ? { minimum: meta.minimum } : {}),
|
|
386
|
+
...(meta?.maximum !== undefined ? { maximum: meta.maximum } : {}),
|
|
387
|
+
...(meta?.default !== undefined
|
|
388
|
+
? { default: meta.default }
|
|
389
|
+
: {}),
|
|
390
|
+
};
|
|
391
|
+
}
|
|
392
|
+
default:
|
|
393
|
+
// ranking, multitext, singlematrix, multimatrix, image, html,
|
|
394
|
+
// signature, diagram, display → no MCP equivalent
|
|
395
|
+
return null;
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
function inputTypeToMcpFormat(inputType) {
|
|
399
|
+
if (inputType === undefined)
|
|
400
|
+
return undefined;
|
|
401
|
+
const map = {
|
|
402
|
+
email: 'email',
|
|
403
|
+
url: 'uri',
|
|
404
|
+
date: 'date',
|
|
405
|
+
'datetime-local': 'date-time',
|
|
406
|
+
};
|
|
407
|
+
return map[inputType];
|
|
408
|
+
}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Convert SurveyJS schema to eSheet FormDefinition.
|
|
3
|
+
*
|
|
4
|
+
* SurveyJS is well-known with lots of training data, so AI models
|
|
5
|
+
* generate better SurveyJS output. We leverage this by having AI
|
|
6
|
+
* generate SurveyJS, then converting to eSheet format.
|
|
7
|
+
*/
|
|
8
|
+
import type { FormDefinition } from '@esheet/core';
|
|
9
|
+
interface SurveyJSChoice {
|
|
10
|
+
value: string;
|
|
11
|
+
text?: string;
|
|
12
|
+
score?: number;
|
|
13
|
+
}
|
|
14
|
+
interface SurveyJSMatrixItem {
|
|
15
|
+
value: string;
|
|
16
|
+
text?: string;
|
|
17
|
+
score?: number;
|
|
18
|
+
}
|
|
19
|
+
interface SurveyJSMultipleTextItem {
|
|
20
|
+
name: string;
|
|
21
|
+
title?: string;
|
|
22
|
+
isRequired?: boolean;
|
|
23
|
+
inputType?: string;
|
|
24
|
+
placeholder?: string;
|
|
25
|
+
}
|
|
26
|
+
interface SurveyJSElement {
|
|
27
|
+
type: string;
|
|
28
|
+
name: string;
|
|
29
|
+
title?: string;
|
|
30
|
+
description?: string;
|
|
31
|
+
isRequired?: boolean;
|
|
32
|
+
inputType?: string;
|
|
33
|
+
choices?: (string | SurveyJSChoice)[];
|
|
34
|
+
rows?: (string | SurveyJSMatrixItem)[];
|
|
35
|
+
columns?: (string | SurveyJSMatrixItem)[];
|
|
36
|
+
visibleIf?: string;
|
|
37
|
+
enableIf?: string;
|
|
38
|
+
requiredIf?: string;
|
|
39
|
+
elements?: SurveyJSElement[];
|
|
40
|
+
html?: string;
|
|
41
|
+
imageLink?: string;
|
|
42
|
+
altText?: string;
|
|
43
|
+
expression?: string;
|
|
44
|
+
placeholder?: string;
|
|
45
|
+
defaultValue?: unknown;
|
|
46
|
+
defaultValueExpression?: string;
|
|
47
|
+
min?: number | string;
|
|
48
|
+
max?: number | string;
|
|
49
|
+
step?: number;
|
|
50
|
+
rateMin?: number;
|
|
51
|
+
rateMax?: number;
|
|
52
|
+
rateStep?: number;
|
|
53
|
+
minRateDescription?: string;
|
|
54
|
+
maxRateDescription?: string;
|
|
55
|
+
showOtherItem?: boolean;
|
|
56
|
+
otherText?: string;
|
|
57
|
+
showNoneItem?: boolean;
|
|
58
|
+
noneText?: string;
|
|
59
|
+
showSelectAllItem?: boolean;
|
|
60
|
+
selectAllText?: string;
|
|
61
|
+
storeOthersAsComment?: boolean;
|
|
62
|
+
colCount?: number;
|
|
63
|
+
allowMultiple?: boolean;
|
|
64
|
+
acceptedTypes?: string;
|
|
65
|
+
items?: SurveyJSMultipleTextItem[];
|
|
66
|
+
}
|
|
67
|
+
interface SurveyJSPage {
|
|
68
|
+
name?: string;
|
|
69
|
+
title?: string;
|
|
70
|
+
elements?: SurveyJSElement[];
|
|
71
|
+
}
|
|
72
|
+
interface SurveyJSSchemaMeta {
|
|
73
|
+
locale?: string;
|
|
74
|
+
logo?: string;
|
|
75
|
+
logoPosition?: string;
|
|
76
|
+
showProgressBar?: string;
|
|
77
|
+
progressBarType?: string;
|
|
78
|
+
completedHtml?: string;
|
|
79
|
+
showQuestionNumbers?: string | boolean;
|
|
80
|
+
questionTitleLocation?: string;
|
|
81
|
+
calculatedValues?: unknown[];
|
|
82
|
+
triggers?: unknown[];
|
|
83
|
+
}
|
|
84
|
+
interface SurveyJSSchema extends SurveyJSSchemaMeta {
|
|
85
|
+
title?: string;
|
|
86
|
+
description?: string;
|
|
87
|
+
pages?: SurveyJSPage[];
|
|
88
|
+
elements?: SurveyJSElement[];
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Convert a SurveyJS schema to eSheet FormDefinition.
|
|
92
|
+
*/
|
|
93
|
+
export declare function convertSurveyJSToESheet(survey: SurveyJSSchema): FormDefinition;
|
|
94
|
+
/**
|
|
95
|
+
* System prompt for generating SurveyJS format.
|
|
96
|
+
* AI models have better training data for SurveyJS.
|
|
97
|
+
*/
|
|
98
|
+
export declare const SURVEYJS_SYSTEM_PROMPT = "You are an expert form designer. Output ONLY a valid SurveyJS JSON schema \u2014 no markdown, no explanation, no code blocks, raw JSON only. STRUCTURE: {\"title\":\"...\",\"pages\":[{\"name\":\"page1\",\"title\":\"...\",\"elements\":[...]}]} FIELD TYPES \u2014 always pick most specific: short text\u2192text (add inputType:\"email\"/\"tel\"/\"date\"/\"number\"/\"url\"), long text\u2192comment, single choice\u2192radiogroup (NOT text), multiple choice\u2192checkbox (NOT text), long list single\u2192dropdown, long list multi\u2192tagbox, yes/no\u2192boolean (NOT radiogroup), 1-N scale\u2192rating (set rateMin/rateMax/minRateDescription/maxRateDescription), ordered preference\u2192ranking, grid single answer\u2192matrix (rows=items columns=scale labels), grid multi answer\u2192matrixdropdown, signature\u2192signaturepad (NEVER use text), file upload\u2192file, image pick\u2192imagepicker, multiple short inputs\u2192multipletext. RULES: use pages only to group related fields \u2014 never use panel type; every page must have a title; add \"isRequired\":true on important fields; use \"visibleIf\" for follow-up questions: \"{field} = 'value'\"; choices format: [{\"value\":\"id\",\"text\":\"Label\"}]. For scored surveys: use numeric strings as column values (\"0\",\"1\",\"2\",\"3\"), add calculatedValues at top level. OUTPUT: raw JSON only, no prose.";
|
|
99
|
+
/**
|
|
100
|
+
* Convert an eSheet FormDefinition back to a SurveyJS schema.
|
|
101
|
+
*
|
|
102
|
+
* Preserves original SurveyJS element names, types, and conditional expressions
|
|
103
|
+
* stored in `_sourceData` during import, enabling a lossless round-trip.
|
|
104
|
+
* Fields without `_sourceData` are reverse-mapped from eSheet types.
|
|
105
|
+
*
|
|
106
|
+
* Section fields become pages; non-section fields are placed in a single page.
|
|
107
|
+
*/
|
|
108
|
+
export declare function exportToSurveyJS(form: FormDefinition): SurveyJSSchema;
|
|
109
|
+
/**
|
|
110
|
+
* Convert a SurveyJS schema to eSheet FormDefinition.
|
|
111
|
+
* Primary named export matching the importFromMcp / exportToMcp naming convention.
|
|
112
|
+
*/
|
|
113
|
+
export declare const importFromSurveyJS: typeof convertSurveyJSToESheet;
|
|
114
|
+
/**
|
|
115
|
+
* Convert a SurveyJS schema to eSheet FormDefinition.
|
|
116
|
+
* Alias for importFromSurveyJS / convertSurveyJSToESheet.
|
|
117
|
+
*/
|
|
118
|
+
export declare const convertSurveyJS: typeof convertSurveyJSToESheet;
|
|
119
|
+
export {};
|
|
120
|
+
//# sourceMappingURL=surveyjs-converter.d.ts.map
|