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) { if (protocol === 'graphql') return '$.request.variables'; if (protocol === 'grpc') return '$.request.message'; if (protocol === 'soap') return '$.request.body'; if (protocol === 'websocket') return '$.request.body'; return '$.request.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 normalizeOutputSource(path) { if (!path) return '$.response.body'; if (path.indexOf('$.') === 0) return path; return '$.' + path.replace(/^\$\./, ''); } function buildMappingSet(rawValue, mode) { var value = rawValue || {}; var root = mappingRootForProtocol(wizardProtocol); var rules = []; Object.keys(value).forEach(function(target) { var source = value[target]; if (mode === 'input') { rules.push({ source: normalizeInputSource(source), target: root + '.' + target, }); return; } rules.push({ source: normalizeOutputSource(source), target: '$.output.' + target, }); }); 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, }; if (wizardProtocol === 'grpc') { var useTls = false; if (value.protocol_options && value.protocol_options.grpc) { useTls = !!value.protocol_options.grpc.use_tls; } else if (value.tls) { useTls = !!value.tls.verify; } config.protocol_options = { grpc: { use_tls: useTls } }; } else if (wizardProtocol === 'websocket') { var websocketProtocolOptions = value.protocol_options && value.protocol_options.websocket ? value.protocol_options.websocket : {}; var heartbeatInterval = websocketProtocolOptions.heartbeat_interval_ms != null ? Number(websocketProtocolOptions.heartbeat_interval_ms) : numericValueOrNull('websocket-heartbeat-interval-ms'); var reconnectAttempts = websocketProtocolOptions.reconnect_max_attempts != null ? Number(websocketProtocolOptions.reconnect_max_attempts) : numericValueOrNull('websocket-reconnect-max-attempts'); var reconnectBackoff = websocketProtocolOptions.reconnect_backoff_ms != null ? Number(websocketProtocolOptions.reconnect_backoff_ms) : numericValueOrNull('websocket-reconnect-backoff-ms'); var runtimeOptions = {}; if (Number.isFinite(heartbeatInterval) && heartbeatInterval > 0) { runtimeOptions.heartbeat_interval_ms = heartbeatInterval; } if (Number.isFinite(reconnectAttempts) && reconnectAttempts >= 0) { runtimeOptions.reconnect_max_attempts = reconnectAttempts; } if (Number.isFinite(reconnectBackoff) && reconnectBackoff >= 0) { runtimeOptions.reconnect_backoff_ms = reconnectBackoff; } if (Object.keys(runtimeOptions).length) { config.protocol_options = { websocket: runtimeOptions }; } } config.streaming = collectStreamingConfig(); 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 && wizardProtocol === 'soap') { customUrl = textValue('soap-endpoint-override'); } 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 selectedGraphqlOperationType() { var active = document.querySelector('.gql-type-card.active'); return active ? active.dataset.gqlType : 'query'; } function graphqlTopLevelField(queryText) { var match = queryText.replace(/#[^\n]*/g, '').match(/\{\s*([A-Za-z_][A-Za-z0-9_]*)/); return match ? match[1] : null; } function graphqlOperationName(queryText) { var match = queryText.match(/\b(query|mutation)\s+([A-Za-z_][A-Za-z0-9_]*)/); return match ? match[2] : textValue('tool-name'); } function parseGrpcRoute(route) { var normalized = route.replace(/^\/+/, ''); var parts = normalized.split('/'); if (parts.length !== 2) { return { package: '', service: '', method: '', }; } var servicePart = parts[0]; var method = parts[1]; var serviceSegments = servicePart.split('.'); var service = serviceSegments.pop() || ''; var packageName = serviceSegments.join('.'); return { package: packageName, service: service, method: method, }; } function parseHeaderMap(text) { if (!text || !text.trim()) return {}; var value = parseStructuredText(text); return value && typeof value === 'object' ? value : {}; } function numericValueOrNull(id) { var value = textValue(id); if (!value) return null; var parsed = Number(value); return Number.isFinite(parsed) && parsed > 0 ? parsed : null; } function stringValueOrNull(id) { var value = textValue(id); return value ? value : null; } function textareaLines(id) { var value = textValue(id); if (!value) return []; return value .split(/\r?\n/) .map(function(line) { return line.trim(); }) .filter(Boolean); } function buildTarget() { var upstream = currentUpstream(); if (!upstream || !upstream.url) { throw new Error(tKey('wizard.error.select_upstream')); } var pathTemplate = textValue('endpoint-path') || '/'; if (wizardProtocol === 'rest') { 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), }; } if (wizardProtocol === 'graphql') { var queryTemplate = textValue('gql-query-editor'); var topField = graphqlTopLevelField(queryTemplate); return { kind: 'graphql', endpoint: joinUrl(upstream.url, pathTemplate), operation_type: selectedGraphqlOperationType(), operation_name: graphqlOperationName(queryTemplate), query_template: queryTemplate, response_path: topField ? '$.response.body.data.' + topField : '$.response.body.data', }; } if (wizardProtocol === 'soap') { var endpointOverride = textValue('soap-endpoint-override') || upstream.url; var existingWsdlRef = wizardCurrentVersion && wizardCurrentVersion.target && wizardCurrentVersion.target.kind === 'soap' ? wizardCurrentVersion.target.wsdl_ref : 'sample_wsdl_pending'; return { kind: 'soap', wsdl_ref: existingWsdlRef, service_name: textValue('soap-service-name') || 'Service', port_name: textValue('soap-port-name') || 'Port', operation_name: textValue('soap-operation-name') || 'Operation', endpoint_override: endpointOverride || null, soap_version: textValue('soap-version') || 'soap_11', soap_action: textValue('soap-action') || null, binding_style: textValue('soap-binding-style') || 'document_literal', headers: [], fault_contract: null, metadata: { input_part_names: [], output_part_names: [], namespaces: [] }, }; } if (wizardProtocol === 'websocket') { return { kind: 'websocket', url: joinUrl(upstream.url, textValue('websocket-path')), subprotocols: textareaLines('websocket-subprotocols'), subscribe_message_template: stringValueOrNull('websocket-subscribe-template') ? parseStructuredText(textValue('websocket-subscribe-template')) : null, unsubscribe_message_template: stringValueOrNull('websocket-unsubscribe-template') ? parseStructuredText(textValue('websocket-unsubscribe-template')) : null, static_headers: parseHeaderMap(upstream.staticHeaders), }; } var route = textValue('grpc-manual-route') || (document.getElementById('grpc-path-value') ? document.getElementById('grpc-path-value').textContent.trim() : ''); var descriptorRef = wizardCurrentVersion && wizardCurrentVersion.target && wizardCurrentVersion.target.kind === 'grpc' ? wizardCurrentVersion.target.descriptor_ref : 'desc_pending'; var descriptorSetB64 = wizardCurrentVersion && wizardCurrentVersion.target && wizardCurrentVersion.target.kind === 'grpc' ? (wizardCurrentVersion.target.descriptor_set_b64 || '') : ''; var parsedRoute = parseGrpcRoute(route); return { kind: 'grpc', server_addr: upstream.url, package: parsedRoute.package, service: parsedRoute.service, method: parsedRoute.method, descriptor_ref: descriptorRef, descriptor_set_b64: descriptorSetB64, }; } 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 mappingSetToEditorValue(mappingSet, mode, protocol) { var rules = mappingSet && mappingSet.rules ? mappingSet.rules : []; var object = {}; rules.forEach(function(rule) { if (mode === 'input') { var key = rule.target.replace(mappingRootForProtocol(protocol) + '.', ''); object[key] = rule.source.replace('$.mcp.', '$.input.'); return; } var outputKey = rule.target.replace('$.output.', ''); object[outputKey] = 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; } if (config.protocol_options && config.protocol_options.grpc) { value.tls = { verify: !!config.protocol_options.grpc.use_tls }; } if (config.protocol_options && config.protocol_options.websocket) { value.protocol_options = value.protocol_options || {}; value.protocol_options.websocket = {}; if (config.protocol_options.websocket.heartbeat_interval_ms != null) { value.protocol_options.websocket.heartbeat_interval_ms = config.protocol_options.websocket.heartbeat_interval_ms; } if (config.protocol_options.websocket.reconnect_max_attempts != null) { value.protocol_options.websocket.reconnect_max_attempts = config.protocol_options.websocket.reconnect_max_attempts; } if (config.protocol_options.websocket.reconnect_backoff_ms != null) { value.protocol_options.websocket.reconnect_backoff_ms = config.protocol_options.websocket.reconnect_backoff_ms; } if (!Object.keys(value.protocol_options.websocket).length) { delete value.protocol_options.websocket; } if (!Object.keys(value.protocol_options).length) { delete value.protocol_options; } } return window.jsyaml ? window.jsyaml.dump(value, { lineWidth: -1 }) : JSON.stringify(value, null, 2); } 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; setEditModePresentation(); selectProtocol(detail.protocol); var target = versionDocument.target || {}; if (target.kind === 'rest') { prefillUpstream(target.base_url, target.static_headers, versionDocument.execution_config ? versionDocument.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); }); } else if (target.kind === 'graphql') { var endpoint = target.endpoint || ''; var parsedEndpoint = new URL(endpoint, window.location.origin); prefillUpstream(parsedEndpoint.origin, {}, versionDocument.execution_config ? versionDocument.execution_config.auth_profile_ref : null); setValue('endpoint-path', parsedEndpoint.pathname + parsedEndpoint.search); setValue('gql-query-editor', target.query_template); document.querySelectorAll('.gql-type-card').forEach(function(card) { card.classList.toggle('active', card.dataset.gqlType === target.operation_type); }); } else if (target.kind === 'grpc') { prefillUpstream(target.server_addr, {}, versionDocument.execution_config ? versionDocument.execution_config.auth_profile_ref : null); var route = '/' + (target.package ? target.package + '.' : '') + target.service + '/' + target.method; setValue('grpc-manual-route', route); setValue('grpc-manual-req-type', ''); setValue('grpc-manual-res-type', ''); grpcManualRouteInput(route); grpcManualTypeInput(); } else if (target.kind === 'soap') { prefillUpstream( target.endpoint_override || '', {}, versionDocument.execution_config ? versionDocument.execution_config.auth_profile_ref : null ); setValue('soap-service-name', target.service_name || ''); setValue('soap-port-name', target.port_name || ''); setValue('soap-operation-name', target.operation_name || ''); setValue('soap-endpoint-override', target.endpoint_override || ''); setValue('soap-action', target.soap_action || ''); setValue('soap-version', target.soap_version || 'soap_11'); setValue('soap-binding-style', target.binding_style || 'document_literal'); } else if (target.kind === 'websocket') { prefillUpstream( target.url, target.static_headers || {}, versionDocument.execution_config ? versionDocument.execution_config.auth_profile_ref : null ); setValue('websocket-path', ''); setValue('websocket-subprotocols', (target.subprotocols || []).join('\n')); setValue( 'websocket-subscribe-template', target.subscribe_message_template ? JSON.stringify(target.subscribe_message_template, null, 2) : '' ); setValue( 'websocket-unsubscribe-template', target.unsubscribe_message_template ? JSON.stringify(target.unsubscribe_message_template, null, 2) : '' ); var websocketOptions = versionDocument.execution_config && versionDocument.execution_config.protocol_options && versionDocument.execution_config.protocol_options.websocket ? versionDocument.execution_config.protocol_options.websocket : null; setValue('websocket-heartbeat-interval-ms', websocketOptions && websocketOptions.heartbeat_interval_ms ? websocketOptions.heartbeat_interval_ms : ''); setValue('websocket-reconnect-max-attempts', websocketOptions && websocketOptions.reconnect_max_attempts != null ? websocketOptions.reconnect_max_attempts : ''); setValue('websocket-reconnect-backoff-ms', websocketOptions && websocketOptions.reconnect_backoff_ms ? websocketOptions.reconnect_backoff_ms : ''); } setValue('tool-name', detail.name); setValue('tool-display-name', detail.display_name); setValue('tool-title', versionDocument.tool_description ? versionDocument.tool_description.title : detail.display_name); setValue('tool-description', versionDocument.tool_description ? versionDocument.tool_description.description : ''); setValue('tool-input-schema', JSON.stringify(crankSchemaToJsonSchema(versionDocument.input_schema), null, 2)); setValue('tool-output-schema', JSON.stringify(crankSchemaToJsonSchema(versionDocument.output_schema), null, 2)); setValue('tool-input-mapping', mappingSetToEditorValue(versionDocument.input_mapping, 'input', detail.protocol)); setValue('tool-output-mapping', mappingSetToEditorValue(versionDocument.output_mapping, 'output', detail.protocol)); setValue('tool-exec-config', executionConfigToEditorValue(versionDocument.execution_config)); applyStreamingConfig(versionDocument.execution_config ? versionDocument.execution_config.streaming : null); var toolNameInput = document.getElementById('tool-name'); if (toolNameInput) toolNameInput.disabled = true; updateWizardProtocolVisibility(); }