507 lines
18 KiB
JavaScript
507 lines
18 KiB
JavaScript
function inferSchemaKind(typeName) {
|
|
if (typeName === 'object') return 'object';
|
|
if (typeName === 'array') return 'array';
|
|
if (typeName === 'string') return 'string';
|
|
if (typeName === 'integer') return 'integer';
|
|
if (typeName === 'number') return 'number';
|
|
if (typeName === 'boolean') return 'boolean';
|
|
if (typeName === 'null') return 'null';
|
|
return 'string';
|
|
}
|
|
|
|
function convertJsonSchemaToCrankSchema(node, requiredNames) {
|
|
var schemaNode = node || {};
|
|
var typeName = Array.isArray(schemaNode.type)
|
|
? schemaNode.type.find(function(value) { return value !== 'null'; }) || 'string'
|
|
: schemaNode.type || (schemaNode.properties ? 'object' : 'string');
|
|
var nullable = Array.isArray(schemaNode.type) && schemaNode.type.indexOf('null') >= 0;
|
|
var kind = schemaNode.enum ? 'enum' : inferSchemaKind(typeName);
|
|
var schema = {
|
|
type: kind,
|
|
description: schemaNode.description || null,
|
|
required: false,
|
|
nullable: nullable,
|
|
default_value: Object.prototype.hasOwnProperty.call(schemaNode, 'default') ? schemaNode.default : null,
|
|
fields: {},
|
|
items: null,
|
|
enum_values: schemaNode.enum || [],
|
|
variants: [],
|
|
};
|
|
|
|
if (kind === 'object') {
|
|
var requiredSet = new Set(schemaNode.required || requiredNames || []);
|
|
Object.keys(schemaNode.properties || {}).forEach(function(fieldName) {
|
|
var field = convertJsonSchemaToCrankSchema(schemaNode.properties[fieldName], []);
|
|
field.required = requiredSet.has(fieldName);
|
|
schema.fields[fieldName] = field;
|
|
});
|
|
}
|
|
|
|
if (kind === 'array' && schemaNode.items) {
|
|
schema.items = convertJsonSchemaToCrankSchema(schemaNode.items, []);
|
|
}
|
|
|
|
return schema;
|
|
}
|
|
|
|
function mappingRootForProtocol(protocol) {
|
|
return '$.request.body';
|
|
}
|
|
|
|
var REQUEST_MAPPING_TARGET_PREFIXES = [
|
|
'path',
|
|
'query',
|
|
'headers',
|
|
'body',
|
|
];
|
|
|
|
function normalizeInputSource(path) {
|
|
if (!path) return '$.mcp';
|
|
if (path === '$') return '$.mcp';
|
|
if (path.indexOf('$.mcp') === 0) return path;
|
|
if (path.indexOf('$.input') === 0) return '$.mcp' + path.slice('$.input'.length);
|
|
return '$.mcp.' + path.replace(/^\$\./, '');
|
|
}
|
|
|
|
function normalizeInputTarget(target) {
|
|
if (!target) return mappingRootForProtocol(wizardProtocol);
|
|
if (target.indexOf('$.request.') === 0) return target;
|
|
if (target.indexOf('request.') === 0) return '$.' + target;
|
|
|
|
var normalized = target.replace(/^\$\./, '');
|
|
var prefix = normalized.split('.')[0];
|
|
if (REQUEST_MAPPING_TARGET_PREFIXES.indexOf(prefix) >= 0) {
|
|
return '$.request.' + normalized;
|
|
}
|
|
|
|
return mappingRootForProtocol(wizardProtocol) + '.' + normalized;
|
|
}
|
|
|
|
function normalizeOutputSource(path) {
|
|
if (!path) return '$.response.body';
|
|
if (path.indexOf('$.') === 0) return path;
|
|
return '$.' + path.replace(/^\$\./, '');
|
|
}
|
|
|
|
function normalizeOutputTarget(target) {
|
|
if (!target) return '$.output';
|
|
if (target.indexOf('$.output') === 0) return target;
|
|
if (target.indexOf('output') === 0) return '$.' + target;
|
|
return '$.output.' + target.replace(/^\$\./, '');
|
|
}
|
|
|
|
function currentOperationSnapshot() {
|
|
return wizardCurrentVersion && wizardCurrentVersion.snapshot
|
|
? wizardCurrentVersion.snapshot
|
|
: wizardCurrentVersion;
|
|
}
|
|
|
|
function existingMappingRuleByTarget(mode, target) {
|
|
var snapshot = currentOperationSnapshot();
|
|
var mapping = mode === 'input'
|
|
? snapshot && snapshot.input_mapping
|
|
: snapshot && snapshot.output_mapping;
|
|
var rules = mapping && Array.isArray(mapping.rules) ? mapping.rules : [];
|
|
return rules.find(function(rule) {
|
|
return rule.target === target;
|
|
}) || null;
|
|
}
|
|
|
|
function preserveMappingRuleMetadata(rule, existingRule) {
|
|
if (!existingRule) {
|
|
rule.required = true;
|
|
return rule;
|
|
}
|
|
|
|
rule.required = existingRule.required !== undefined ? existingRule.required : true;
|
|
['default_value', 'transform', 'condition', 'notes'].forEach(function(field) {
|
|
if (existingRule[field] !== undefined && existingRule[field] !== null) {
|
|
rule[field] = existingRule[field];
|
|
}
|
|
});
|
|
return rule;
|
|
}
|
|
|
|
function mappingEntrySource(entry) {
|
|
if (entry && typeof entry === 'object' && !Array.isArray(entry)) {
|
|
return entry.source;
|
|
}
|
|
return entry;
|
|
}
|
|
|
|
function normalizeTransformKind(value) {
|
|
if (!value) return null;
|
|
var kind = value && typeof value === 'object' ? value.kind : value;
|
|
kind = String(kind || '').trim();
|
|
var allowed = [
|
|
'identity',
|
|
'to_string',
|
|
'to_number',
|
|
'to_boolean',
|
|
'join',
|
|
'split',
|
|
'wrap_array',
|
|
'unwrap_singleton',
|
|
];
|
|
return allowed.indexOf(kind) >= 0 ? kind : null;
|
|
}
|
|
|
|
function applyMappingEntryMetadata(rule, entry, existingRule) {
|
|
preserveMappingRuleMetadata(rule, existingRule);
|
|
|
|
if (!entry || typeof entry !== 'object' || Array.isArray(entry)) {
|
|
return rule;
|
|
}
|
|
|
|
if (Object.prototype.hasOwnProperty.call(entry, 'required')) {
|
|
rule.required = entry.required !== false;
|
|
}
|
|
if (Object.prototype.hasOwnProperty.call(entry, 'default_value')) {
|
|
rule.default_value = entry.default_value;
|
|
}
|
|
if (entry.transform !== undefined && entry.transform !== null) {
|
|
var transformKind = normalizeTransformKind(entry.transform);
|
|
if (transformKind) rule.transform = { kind: transformKind };
|
|
}
|
|
['condition', 'notes'].forEach(function(field) {
|
|
if (entry[field] !== undefined && entry[field] !== null) {
|
|
rule[field] = entry[field];
|
|
}
|
|
});
|
|
return rule;
|
|
}
|
|
|
|
function buildMappingSet(rawValue, mode) {
|
|
var value = rawValue || {};
|
|
var rules = [];
|
|
|
|
Object.keys(value).forEach(function(target) {
|
|
var entry = value[target];
|
|
var source = mappingEntrySource(entry);
|
|
if (mode === 'input') {
|
|
var inputTarget = normalizeInputTarget(target);
|
|
rules.push(applyMappingEntryMetadata({
|
|
source: normalizeInputSource(source),
|
|
target: inputTarget,
|
|
}, entry, existingMappingRuleByTarget(mode, inputTarget)));
|
|
return;
|
|
}
|
|
|
|
var outputTarget = normalizeOutputTarget(target);
|
|
rules.push(applyMappingEntryMetadata({
|
|
source: normalizeOutputSource(source),
|
|
target: outputTarget,
|
|
}, entry, existingMappingRuleByTarget(mode, outputTarget)));
|
|
});
|
|
|
|
return { rules: rules };
|
|
}
|
|
|
|
function parseExecutionConfig(text) {
|
|
var value = parseStructuredText(text);
|
|
var retry = value.retry || {};
|
|
var headers = value.headers && typeof value.headers === 'object' ? value.headers : {};
|
|
var selectedUpstream = currentUpstream();
|
|
if (!selectedUpstream && wizardMode !== 'edit') {
|
|
throw new Error(tKey('wizard.error.save_upstream_first'));
|
|
}
|
|
var authProfileRef = selectedUpstream && selectedUpstream.authProfileId
|
|
? selectedUpstream.authProfileId
|
|
: (value.auth && value.auth.profile ? value.auth.profile : null);
|
|
var config = {
|
|
timeout_ms: Number(value.timeout_ms || 10000),
|
|
retry_policy: retry && retry.max_attempts ? { max_attempts: Number(retry.max_attempts) } : null,
|
|
auth_profile_ref: authProfileRef,
|
|
headers: headers,
|
|
protocol_options: null,
|
|
streaming: null,
|
|
};
|
|
|
|
return config;
|
|
}
|
|
|
|
function currentUpstream() {
|
|
var selected = upstreams.find(function(item) { return item.id === selectedUpstreamId; });
|
|
if (selected) return selected;
|
|
|
|
var customName = textValue('new-upstream-name');
|
|
var customUrl = textValue('new-upstream-url');
|
|
if (!customUrl) return null;
|
|
|
|
var authMode = textValue('new-upstream-auth-mode') || 'none';
|
|
var authProfileId = authMode === 'existing' ? textValue('new-upstream-auth-profile') : null;
|
|
var authProfile = authProfileById(authProfileId);
|
|
return {
|
|
id: 'custom',
|
|
name: customName || 'custom-upstream',
|
|
url: customUrl,
|
|
staticHeaders: textValue('new-upstream-static-headers') || '{\n}',
|
|
authMode: authMode,
|
|
authProfileId: authProfileId || null,
|
|
authProfileName: authProfile ? authProfile.name : '',
|
|
authKind: authProfile ? authProfile.kind : null,
|
|
};
|
|
}
|
|
|
|
function joinUrl(baseUrl, path) {
|
|
if (!baseUrl) return path || '';
|
|
if (!path) return baseUrl;
|
|
return baseUrl.replace(/\/+$/, '') + '/' + path.replace(/^\/+/, '');
|
|
}
|
|
|
|
function parseHeaderMap(text) {
|
|
if (!text || !text.trim()) return {};
|
|
var value = parseStructuredText(text);
|
|
return value && typeof value === 'object' ? value : {};
|
|
}
|
|
|
|
function buildTarget() {
|
|
var upstream = currentUpstream();
|
|
if (!upstream || !upstream.url) {
|
|
throw new Error(tKey('wizard.error.select_upstream'));
|
|
}
|
|
|
|
var pathTemplate = textValue('endpoint-path') || '/';
|
|
var activeMethod = document.querySelector('.method-card.active');
|
|
return {
|
|
kind: 'rest',
|
|
base_url: upstream.url,
|
|
method: activeMethod ? activeMethod.dataset.method : 'POST',
|
|
path_template: pathTemplate,
|
|
static_headers: parseHeaderMap(upstream.staticHeaders),
|
|
};
|
|
}
|
|
|
|
function crankSchemaToJsonSchema(schema) {
|
|
var result = {};
|
|
var typeName = schema.type === 'enum' ? 'string' : schema.type;
|
|
result.type = schema.nullable ? [typeName, 'null'] : typeName;
|
|
if (schema.description) result.description = schema.description;
|
|
if (schema.default_value !== null && schema.default_value !== undefined) result.default = schema.default_value;
|
|
if (schema.enum_values && schema.enum_values.length) result.enum = schema.enum_values.slice();
|
|
if (schema.type === 'object') {
|
|
result.type = schema.nullable ? ['object', 'null'] : 'object';
|
|
result.properties = {};
|
|
var required = [];
|
|
Object.keys(schema.fields || {}).forEach(function(fieldName) {
|
|
var field = schema.fields[fieldName];
|
|
result.properties[fieldName] = crankSchemaToJsonSchema(field);
|
|
if (field.required) required.push(fieldName);
|
|
});
|
|
if (required.length) result.required = required;
|
|
result.additionalProperties = false;
|
|
}
|
|
if (schema.type === 'array' && schema.items) {
|
|
result.items = crankSchemaToJsonSchema(schema.items);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
function exampleValueForSchema(schema) {
|
|
if (!schema) return {};
|
|
|
|
if (schema.default_value !== null && schema.default_value !== undefined) {
|
|
return schema.default_value;
|
|
}
|
|
|
|
if (schema.enum_values && schema.enum_values.length) {
|
|
return schema.enum_values[0];
|
|
}
|
|
|
|
if (schema.type === 'object') {
|
|
var object = {};
|
|
Object.keys(schema.fields || {}).forEach(function(fieldName) {
|
|
object[fieldName] = exampleValueForSchema(schema.fields[fieldName]);
|
|
});
|
|
return object;
|
|
}
|
|
|
|
if (schema.type === 'array') {
|
|
return [exampleValueForSchema(schema.items)];
|
|
}
|
|
|
|
if (schema.type === 'integer') return 1;
|
|
if (schema.type === 'number') return 1.23;
|
|
if (schema.type === 'boolean') return true;
|
|
if (schema.type === 'null') return null;
|
|
return 'string';
|
|
}
|
|
|
|
function firstToolExampleValue(toolDescription, fieldName) {
|
|
var examples = toolDescription && Array.isArray(toolDescription.examples)
|
|
? toolDescription.examples
|
|
: [];
|
|
if (!examples.length) return null;
|
|
|
|
var value = examples[0] && examples[0][fieldName];
|
|
return value === undefined ? null : value;
|
|
}
|
|
|
|
function prefillWizardSamples(snapshot) {
|
|
var state = snapshot.wizard_state || {};
|
|
var inputExample = firstToolExampleValue(snapshot.tool_description, 'input');
|
|
var inputSample = state.input_sample !== undefined && state.input_sample !== null
|
|
? state.input_sample
|
|
: inputExample !== null ? inputExample : exampleValueForSchema(snapshot.input_schema);
|
|
var outputSample = state.output_sample !== undefined && state.output_sample !== null
|
|
? state.output_sample
|
|
: exampleValueForSchema(snapshot.output_schema);
|
|
var testInput = state.test_input !== undefined && state.test_input !== null
|
|
? state.test_input
|
|
: inputSample;
|
|
|
|
setValue('wizard-input-sample', JSON.stringify(inputSample, null, 2));
|
|
setValue('wizard-test-input', JSON.stringify(testInput, null, 2));
|
|
setValue('wizard-output-sample', JSON.stringify(outputSample, null, 2));
|
|
}
|
|
|
|
function mappingSetToEditorValue(mappingSet, mode, protocol) {
|
|
var rules = mappingSet && mappingSet.rules ? mappingSet.rules : [];
|
|
var object = {};
|
|
rules.forEach(function(rule) {
|
|
if (mode === 'input') {
|
|
var key = rule.target.indexOf(mappingRootForProtocol(protocol) + '.') === 0
|
|
? rule.target.replace(mappingRootForProtocol(protocol) + '.', '')
|
|
: rule.target.replace('$.request.', '');
|
|
var inputSource = rule.source.replace('$.mcp.', '$.input.');
|
|
var inputEntry = { source: inputSource };
|
|
var hasMetadata = false;
|
|
if (rule.required === false) {
|
|
inputEntry.required = false;
|
|
hasMetadata = true;
|
|
}
|
|
if (rule.default_value !== undefined && rule.default_value !== null) {
|
|
inputEntry.default_value = rule.default_value;
|
|
hasMetadata = true;
|
|
}
|
|
if (rule.transform && rule.transform.kind) {
|
|
inputEntry.transform = rule.transform.kind;
|
|
hasMetadata = true;
|
|
}
|
|
['condition', 'notes'].forEach(function(field) {
|
|
if (rule[field] !== undefined && rule[field] !== null) {
|
|
inputEntry[field] = rule[field];
|
|
hasMetadata = true;
|
|
}
|
|
});
|
|
object[key] = hasMetadata ? inputEntry : inputSource;
|
|
return;
|
|
}
|
|
var outputKey = rule.target.replace('$.output.', '');
|
|
var outputEntry = { source: rule.source };
|
|
var outputHasMetadata = false;
|
|
if (rule.required === false) {
|
|
outputEntry.required = false;
|
|
outputHasMetadata = true;
|
|
}
|
|
if (rule.default_value !== undefined && rule.default_value !== null) {
|
|
outputEntry.default_value = rule.default_value;
|
|
outputHasMetadata = true;
|
|
}
|
|
if (rule.transform && rule.transform.kind) {
|
|
outputEntry.transform = rule.transform.kind;
|
|
outputHasMetadata = true;
|
|
}
|
|
['condition', 'notes'].forEach(function(field) {
|
|
if (rule[field] !== undefined && rule[field] !== null) {
|
|
outputEntry[field] = rule[field];
|
|
outputHasMetadata = true;
|
|
}
|
|
});
|
|
object[outputKey] = outputHasMetadata ? outputEntry : rule.source;
|
|
});
|
|
return window.jsyaml ? window.jsyaml.dump(object, { lineWidth: -1 }) : JSON.stringify(object, null, 2);
|
|
}
|
|
|
|
function executionConfigToEditorValue(config) {
|
|
var value = {
|
|
timeout_ms: config.timeout_ms,
|
|
};
|
|
if (config.retry_policy) {
|
|
value.retry = { max_attempts: config.retry_policy.max_attempts };
|
|
}
|
|
if (config.auth_profile_ref) {
|
|
value.auth = { profile: config.auth_profile_ref };
|
|
}
|
|
if (config.headers && Object.keys(config.headers).length) {
|
|
value.headers = config.headers;
|
|
}
|
|
return window.jsyaml ? window.jsyaml.dump(value, { lineWidth: -1 }) : JSON.stringify(value, null, 2);
|
|
}
|
|
|
|
function setApprovalPolicyFromSnapshot(config) {
|
|
if (!window.CrankWizardLive || typeof window.CrankWizardLive.setApprovalPolicyEditor !== 'function') {
|
|
return;
|
|
}
|
|
window.CrankWizardLive.setApprovalPolicyEditor(config && config.approval_policy ? config.approval_policy : null);
|
|
}
|
|
|
|
function operationSnapshot(versionDocument) {
|
|
if (!versionDocument) return {};
|
|
return versionDocument.snapshot || versionDocument;
|
|
}
|
|
|
|
function prefillUpstream(baseUrl, headers, authProfileRef) {
|
|
var known = upstreams.find(function(item) {
|
|
return item.url === baseUrl && (item.authProfileId || null) === (authProfileRef || null);
|
|
});
|
|
if (known) {
|
|
pickUpstream(known.id);
|
|
return;
|
|
}
|
|
|
|
selectedUpstreamId = null;
|
|
var trigger = document.getElementById('upstream-new-trigger');
|
|
var form = document.getElementById('upstream-new-form');
|
|
if (trigger) trigger.classList.add('active');
|
|
if (form) form.hidden = false;
|
|
setValue('new-upstream-name', 'imported-upstream');
|
|
setValue('new-upstream-url', baseUrl);
|
|
setValue('new-upstream-static-headers', headers && Object.keys(headers).length ? JSON.stringify(headers, null, 2) : '{\n}');
|
|
setValue('new-upstream-auth-mode', authProfileRef ? 'existing' : 'none');
|
|
setValue('new-upstream-auth-profile', authProfileRef || '');
|
|
updateUpstreamAuthUi();
|
|
|
|
var valueEl = document.getElementById('upstream-combobox-value');
|
|
if (valueEl) {
|
|
valueEl.innerHTML = '';
|
|
valueEl.appendChild(buildUpstreamTriggerContent('imported-upstream', baseUrl));
|
|
}
|
|
}
|
|
|
|
function prefillWizardFromEdit(detail, versionDocument) {
|
|
if (!detail || !versionDocument) return;
|
|
|
|
var snapshot = operationSnapshot(versionDocument);
|
|
setEditModePresentation();
|
|
selectProtocol(snapshot.protocol || detail.protocol);
|
|
|
|
var target = snapshot.target || {};
|
|
if (target.kind === 'rest') {
|
|
prefillUpstream(target.base_url, target.static_headers, snapshot.execution_config ? snapshot.execution_config.auth_profile_ref : null);
|
|
setValue('endpoint-path', target.path_template);
|
|
document.querySelectorAll('.method-card').forEach(function(card) {
|
|
card.classList.toggle('active', card.dataset.method === target.method);
|
|
});
|
|
}
|
|
|
|
setValue('tool-name', snapshot.name || detail.name);
|
|
setValue('tool-display-name', snapshot.display_name || detail.display_name);
|
|
setValue('tool-title', snapshot.tool_description ? snapshot.tool_description.title : (snapshot.display_name || detail.display_name));
|
|
setValue('tool-description', snapshot.tool_description ? snapshot.tool_description.description : '');
|
|
setValue('tool-input-schema', JSON.stringify(crankSchemaToJsonSchema(snapshot.input_schema), null, 2));
|
|
setValue('tool-output-schema', JSON.stringify(crankSchemaToJsonSchema(snapshot.output_schema), null, 2));
|
|
setValue('tool-input-mapping', mappingSetToEditorValue(snapshot.input_mapping, 'input', snapshot.protocol || detail.protocol));
|
|
setValue('tool-output-mapping', mappingSetToEditorValue(snapshot.output_mapping, 'output', snapshot.protocol || detail.protocol));
|
|
setValue('tool-exec-config', executionConfigToEditorValue(snapshot.execution_config || {}));
|
|
setApprovalPolicyFromSnapshot(snapshot.execution_config || {});
|
|
prefillWizardSamples(snapshot);
|
|
if (window.CrankWizardMapping && typeof window.CrankWizardMapping.renderFromEditors === 'function') {
|
|
window.CrankWizardMapping.renderFromEditors();
|
|
}
|
|
|
|
var toolNameInput = document.getElementById('tool-name');
|
|
if (toolNameInput) toolNameInput.disabled = true;
|
|
updateWizardProtocolVisibility();
|
|
}
|