import { MINDSPACE_SERVER_ADAPTER_BINDINGS, MINDSPACE_SERVER_ADAPTER_BINDING_KEYS, } from './mindspace-server-adapter-contract.mjs'; function trimTrailingSlash(value) { return String(value ?? '').trim().replace(/\/+$/, ''); } function resolveRemoteAdapterEndpoint(endpoint) { const base = trimTrailingSlash(endpoint); return base || ''; } function normalizeOperationBasePath(value) { const raw = String(value ?? '').trim(); if (!raw) return '/mindspace/v1/adapter'; return `/${raw.replace(/^\/+/, '').replace(/\/+$/, '')}`; } function buildRemoteOperationUrl(endpoint, bindingKey, method, operationBasePath = '/mindspace/v1/adapter') { const base = resolveRemoteAdapterEndpoint(endpoint); if (!base) { throw new Error('MindSpace remote server adapter requires MINDSPACE_REMOTE_BASE_URL'); } const normalizedBasePath = normalizeOperationBasePath(operationBasePath); return `${base}${normalizedBasePath}/${encodeURIComponent(bindingKey)}/${encodeURIComponent(method)}`; } function throwRemoteOperationError(response, bindingKey, method, bodyText, payload) { const error = new Error( payload?.message ?? `MindSpace remote adapter ${bindingKey}.${method}() failed with ${response.status}${bodyText ? `: ${bodyText}` : ''}`, ); if (payload?.code) error.code = payload.code; if (payload?.details !== undefined) error.details = payload.details; throw error; } async function parseRemoteOperationResponse(response, bindingKey, method) { const bodyText = await response.text().catch(() => ''); let payload = null; if (bodyText) { try { payload = JSON.parse(bodyText); } catch { payload = null; } } if (!response.ok) { throwRemoteOperationError(response, bindingKey, method, bodyText, payload); } if (response.status === 204) return null; if (!bodyText) return null; return payload ?? JSON.parse(bodyText); } async function invokeRemoteOperation({ endpoint, operationBasePath, authToken, timeoutMs, bindingKey, method, args, fetchFn, }) { const controller = typeof AbortController === 'function' ? new AbortController() : null; const timeoutId = controller && Number.isFinite(timeoutMs) && timeoutMs > 0 ? setTimeout(() => controller.abort(new Error('MindSpace remote adapter request timed out')), timeoutMs) : null; try { const response = await fetchFn(buildRemoteOperationUrl(endpoint, bindingKey, method, operationBasePath), { method: 'POST', headers: { 'Content-Type': 'application/json', ...(authToken ? { Authorization: `Bearer ${authToken}` } : {}), }, body: JSON.stringify({ args, }), signal: controller?.signal, }); return parseRemoteOperationResponse(response, bindingKey, method); } finally { if (timeoutId) clearTimeout(timeoutId); } } function createRemoteBindingProxy({ endpoint, bindingKey, fetchFn, authToken, timeoutMs, operationBasePath, }) { const methods = MINDSPACE_SERVER_ADAPTER_BINDINGS[bindingKey] ?? []; const methodSet = new Set(methods); const proxyTarget = {}; for (const method of methods) { proxyTarget[method] = async (...args) => invokeRemoteOperation({ endpoint, operationBasePath, authToken, timeoutMs, bindingKey, method, args, fetchFn, }); } return new Proxy(proxyTarget, { get(target, prop) { if (prop === Symbol.toStringTag) return 'MindSpaceRemoteBindingProxy'; if (typeof prop === 'string' && !methodSet.has(prop)) { throw new Error(`MindSpace remote adapter binding "${bindingKey}" does not expose ${bindingKey}.${prop}()`); } return target[prop]; }, }); } export function createMindSpaceRemoteServerAdapter({ logger = console, endpoint = '', authToken = '', operationBasePath = '/mindspace/v1/adapter', timeoutMs = 15_000, fetchFn = globalThis.fetch?.bind(globalThis), } = {}) { const endpointUrl = resolveRemoteAdapterEndpoint(endpoint); const normalizedOperationBasePath = normalizeOperationBasePath(operationBasePath); const invoke = fetchFn ? fetchFn : async () => { throw new Error('MindSpace remote server adapter requires a fetch implementation'); }; const bindings = Object.fromEntries( MINDSPACE_SERVER_ADAPTER_BINDING_KEYS.map((bindingKey) => [ bindingKey, createRemoteBindingProxy({ endpoint: endpointUrl, bindingKey, fetchFn: invoke, authToken, timeoutMs, operationBasePath: normalizedOperationBasePath, }), ]), ); return { kind: 'remote', implementationStatus: 'scaffold', endpoint: endpointUrl, operationBasePath: normalizedOperationBasePath, timeoutMs, storageRoot: null, publicBaseUrl: null, storageAdapter: null, ...bindings, assertReady() { if (!endpointUrl) { throw new Error('MindSpace remote server adapter requires MINDSPACE_REMOTE_BASE_URL'); } return true; }, startBackgroundJobs() { logger.log?.( `MindSpace remote server adapter enabled${endpointUrl ? ` (${endpointUrl})` : ''}; background jobs stay on the remote runtime`, ); return { publicationCleanup: null, agentWorker: null, workspaceMaintenance: null, }; }, }; } export const mindspaceRemoteServerAdapterInternals = { buildRemoteOperationUrl, invokeRemoteOperation, normalizeOperationBasePath, };