Skip to main content
Glama
vmcontext.ts4.46 kB
// SPDX-FileCopyrightText: Copyright Orangebot, Inc. and Medplum contributors // SPDX-License-Identifier: Apache-2.0 import { ContentType, createReference, Hl7Message, MedplumClient, normalizeErrorString, normalizeOperationOutcome, } from '@medplum/core'; import type { Binary, Reference } from '@medplum/fhirtypes'; import fetch from 'node-fetch'; import { createRequire } from 'node:module'; import vm from 'node:vm'; import { getConfig } from '../config/loader'; import { getSystemRepo } from '../fhir/repo'; import { getBinaryStorage } from '../storage/loader'; import { MockConsole } from '../util/console'; import { readStreamToString } from '../util/streams'; import type { BotExecutionContext, BotExecutionResult } from './types'; export const DEFAULT_VM_CONTEXT_TIMEOUT = 10000; /** * Executes a Bot on the server in a separate Node.js VM. * @param request - The bot request. * @returns The bot execution result. */ export async function runInVmContext(request: BotExecutionContext): Promise<BotExecutionResult> { const { bot, input, contentType, traceId, headers } = request; const config = getConfig(); if (!config.vmContextBotsEnabled) { return { success: false, logResult: 'VM Context bots not enabled on this server' }; } const codeUrl = bot.executableCode?.url; if (!codeUrl) { return { success: false, logResult: 'No executable code' }; } if (!codeUrl.startsWith('Binary/')) { return { success: false, logResult: 'Executable code is not a Binary' }; } const systemRepo = getSystemRepo(); const binary = await systemRepo.readReference<Binary>({ reference: codeUrl } as Reference<Binary>); const stream = await getBinaryStorage().readBinary(binary); const code = await readStreamToString(stream); const botConsole = new MockConsole(); const sandbox = { console: botConsole, fetch, require: createRequire(typeof __filename !== 'undefined' ? __filename : import.meta.url), ContentType, Hl7Message, MedplumClient, TextDecoder, TextEncoder, URL, URLSearchParams, event: { bot: createReference(bot), baseUrl: config.vmContextBaseUrl ?? config.baseUrl, accessToken: request.accessToken, requester: request.requester, input: input instanceof Hl7Message ? input.toString() : input, contentType, secrets: request.secrets, traceId, headers, defaultHeaders: request.defaultHeaders, }, }; const options: vm.RunningScriptOptions = { timeout: bot.timeout ? bot.timeout * 1000 : DEFAULT_VM_CONTEXT_TIMEOUT, }; // Wrap code in an async block for top-level await support const wrappedCode = ` const exports = {}; const module = {exports}; // Start user code ${code} // End user code (async () => { const { bot, baseUrl, accessToken, requester, contentType, secrets, traceId, headers, defaultHeaders } = event; const medplum = new MedplumClient({ baseUrl, defaultHeaders, fetch: function(url, options = {}) { options.headers ||= {}; options.headers['X-Trace-Id'] = traceId; options.headers['traceparent'] = traceId; return fetch(url, options); }, }); medplum.setAccessToken(accessToken); try { let input = event.input; if (contentType === ContentType.HL7_V2 && input) { input = Hl7Message.parse(input); } let result = await exports.handler(medplum, { bot, requester, input, contentType, secrets, traceId, headers }); if (contentType === ContentType.HL7_V2 && result) { result = result.toString(); } return result; } catch (err) { if (err instanceof Error) { console.log("Unhandled error: " + err.message + "\\n" + err.stack); } else if (typeof err === "object") { console.log("Unhandled error: " + JSON.stringify(err, undefined, 2)); } else { console.log("Unhandled error: " + err); } throw err; } })(); `; // Return the result of the code execution try { const returnValue = (await vm.runInNewContext(wrappedCode, sandbox, options)) as any; return { success: true, logResult: botConsole.toString(), returnValue, }; } catch (err) { botConsole.log('Error', normalizeErrorString(err)); return { success: false, logResult: botConsole.toString(), returnValue: normalizeOperationOutcome(err), }; } }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/medplum/medplum'

If you have feedback or need assistance with the MCP directory API, please join our Discord server