@alpic80/rivet-core 1.24.0-aidon.5 → 1.24.2-aidon.10

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.
Files changed (59) hide show
  1. package/README.md +9 -6
  2. package/dist/cjs/bundle.cjs +1534 -278
  3. package/dist/cjs/bundle.cjs.map +4 -4
  4. package/dist/esm/api/createProcessor.js +2 -0
  5. package/dist/esm/api/streaming.js +48 -2
  6. package/dist/esm/exports.js +1 -0
  7. package/dist/esm/integrations/CodeRunner.js +10 -2
  8. package/dist/esm/integrations/mcp/MCPBase.js +100 -0
  9. package/dist/esm/integrations/mcp/MCPProvider.js +23 -0
  10. package/dist/esm/integrations/mcp/MCPUtils.js +33 -0
  11. package/dist/esm/model/GraphProcessor.js +7 -1
  12. package/dist/esm/model/NodeRegistration.js +0 -1
  13. package/dist/esm/model/Nodes.js +9 -0
  14. package/dist/esm/model/nodes/ChatNodeBase.js +1 -1
  15. package/dist/esm/model/nodes/CodeNode.js +1 -1
  16. package/dist/esm/model/nodes/GetAllDatasetsNode.js +1 -1
  17. package/dist/esm/model/nodes/GraphInputNode.js +2 -0
  18. package/dist/esm/model/nodes/HttpCallNode.js +2 -2
  19. package/dist/esm/model/nodes/MCPDiscoveryNode.js +239 -0
  20. package/dist/esm/model/nodes/MCPGetPromptNode.js +262 -0
  21. package/dist/esm/model/nodes/MCPToolCallNode.js +290 -0
  22. package/dist/esm/model/nodes/ObjectNode.js +42 -21
  23. package/dist/esm/model/nodes/PromptNode.js +1 -1
  24. package/dist/esm/model/nodes/SubGraphNode.js +1 -0
  25. package/dist/esm/model/nodes/TextNode.js +13 -2
  26. package/dist/esm/plugins/aidon/nodes/ChatAidonNode.js +7 -5
  27. package/dist/esm/plugins/aidon/plugin.js +15 -0
  28. package/dist/esm/plugins/anthropic/anthropic.js +22 -3
  29. package/dist/esm/plugins/anthropic/nodes/ChatAnthropicNode.js +33 -3
  30. package/dist/esm/plugins/google/google.js +29 -14
  31. package/dist/esm/plugins/google/nodes/ChatGoogleNode.js +70 -5
  32. package/dist/esm/plugins/huggingface/nodes/ChatHuggingFace.js +4 -2
  33. package/dist/esm/plugins/huggingface/nodes/TextToImageHuggingFace.js +5 -3
  34. package/dist/esm/utils/interpolation.js +155 -17
  35. package/dist/esm/utils/openai.js +24 -0
  36. package/dist/types/api/createProcessor.d.ts +3 -2
  37. package/dist/types/api/streaming.d.ts +7 -1
  38. package/dist/types/exports.d.ts +1 -0
  39. package/dist/types/integrations/CodeRunner.d.ts +4 -3
  40. package/dist/types/integrations/mcp/MCPBase.d.ts +20 -0
  41. package/dist/types/integrations/mcp/MCPProvider.d.ts +153 -0
  42. package/dist/types/integrations/mcp/MCPUtils.d.ts +9 -0
  43. package/dist/types/model/GraphProcessor.d.ts +5 -1
  44. package/dist/types/model/Nodes.d.ts +13 -2
  45. package/dist/types/model/ProcessContext.d.ts +5 -1
  46. package/dist/types/model/Project.d.ts +2 -0
  47. package/dist/types/model/nodes/GetAllDatasetsNode.d.ts +2 -2
  48. package/dist/types/model/nodes/MCPDiscoveryNode.d.ts +9 -0
  49. package/dist/types/model/nodes/MCPGetPromptNode.d.ts +23 -0
  50. package/dist/types/model/nodes/MCPToolCallNode.d.ts +26 -0
  51. package/dist/types/model/nodes/ObjectNode.d.ts +3 -2
  52. package/dist/types/model/nodes/TextNode.d.ts +2 -1
  53. package/dist/types/plugins/anthropic/anthropic.d.ts +21 -3
  54. package/dist/types/plugins/anthropic/nodes/ChatAnthropicNode.d.ts +5 -0
  55. package/dist/types/plugins/google/google.d.ts +12 -2
  56. package/dist/types/plugins/google/nodes/ChatGoogleNode.d.ts +7 -0
  57. package/dist/types/utils/interpolation.d.ts +6 -1
  58. package/dist/types/utils/openai.d.ts +24 -0
  59. package/package.json +7 -7
@@ -1,5 +1,6 @@
1
1
  import {} from '../index.js';
2
2
  import { getProcessorEvents, getProcessorSSEStream, getSingleNodeStream } from './streaming.js';
3
+ // eslint-disable-next-line import/no-cycle -- Necessary cycle
3
4
  import { GraphProcessor } from '../model/GraphProcessor.js';
4
5
  import { deserializeProject } from '../utils/serialization/serialization.js';
5
6
  import { DEFAULT_CHAT_NODE_TIMEOUT } from '../utils/defaults.js';
@@ -89,6 +90,7 @@ export function coreCreateProcessor(project, options) {
89
90
  nativeApi: options.nativeApi,
90
91
  datasetProvider: options.datasetProvider,
91
92
  audioProvider: options.audioProvider,
93
+ mcpProvider: options.mcpProvider,
92
94
  codeRunner: options.codeRunner,
93
95
  projectPath: options.projectPath,
94
96
  projectReferenceLoader: options.projectReferenceLoader,
@@ -9,12 +9,14 @@ function nodeMatches(spec, event) {
9
9
  export async function* getProcessorEvents(processor, spec) {
10
10
  const previousIndexes = new Map();
11
11
  const usages = [];
12
+ let hasDelta = false;
12
13
  for await (const event of processor.events()) {
13
14
  if (event.type === 'partialOutput') {
14
15
  if (spec.partialOutputs === true ||
15
16
  nodeMatches(spec.partialOutputs, event)) {
16
17
  const currentOutput = coerceType(event.outputs['response'], 'string');
17
18
  const delta = currentOutput.slice(previousIndexes.get(event.node.id) ?? 0);
19
+ hasDelta = true;
18
20
  yield {
19
21
  type: 'partialOutput',
20
22
  nodeId: event.node.id,
@@ -76,8 +78,8 @@ export async function* getProcessorEvents(processor, spec) {
76
78
  usages.push(usage);
77
79
  }
78
80
  }
79
- if (spec.nodeFinish === true ||
80
- nodeMatches(spec.nodeFinish, event)) {
81
+ if ((spec.nodeFinish === true || nodeMatches(spec.nodeFinish, event)) &&
82
+ !(spec.removeFinalOutput && hasDelta)) {
81
83
  yield {
82
84
  type: 'nodeFinish',
83
85
  outputs: event.outputs,
@@ -88,6 +90,21 @@ export async function* getProcessorEvents(processor, spec) {
88
90
  }
89
91
  }
90
92
  }
93
+ export const createOnStreamUserEvents = (eventList, handleUserEvent) => {
94
+ if (!eventList?.trim()) {
95
+ return undefined;
96
+ }
97
+ const events = eventList.split(',').map(e => e.trim()).filter(Boolean);
98
+ if (!events.length) {
99
+ return undefined;
100
+ }
101
+ return Object.fromEntries(events.map(event => [
102
+ event,
103
+ async (data) => {
104
+ await handleUserEvent(event, data);
105
+ }
106
+ ]));
107
+ };
91
108
  /**
92
109
  * Creates a ReadableStream for processor events, following the Server-Sent Events protocol.
93
110
  * https://developer.mozilla.org/en-US/docs/Web/API/EventSource
@@ -105,6 +122,22 @@ spec) {
105
122
  }
106
123
  return new ReadableStream({
107
124
  async start(controller) {
125
+ const userEventHandler = async (eventName, data) => {
126
+ const graphEvent = {
127
+ type: 'event',
128
+ graphEvent: {
129
+ name: eventName,
130
+ message: coerceType(data, 'string')
131
+ }
132
+ };
133
+ sendEvent(controller, 'event', graphEvent);
134
+ };
135
+ const streamEvents = createOnStreamUserEvents(spec.userStreamEvents, userEventHandler);
136
+ if (streamEvents) {
137
+ for (const [name, fn] of Object.entries(streamEvents)) {
138
+ processor.onUserEvent(name, fn);
139
+ }
140
+ }
108
141
  try {
109
142
  for await (const event of getProcessorEvents(processor, spec)) {
110
143
  sendEvent(controller, event.type, event);
@@ -132,6 +165,19 @@ export function getSingleNodeStream(processor, arg) {
132
165
  return new ReadableStream({
133
166
  async start(controller) {
134
167
  try {
168
+ const userEventHandler = async (eventName, data) => {
169
+ const payload = {
170
+ name: eventName,
171
+ message: coerceType(data, 'string')
172
+ };
173
+ controller.enqueue(`event: ${JSON.stringify(payload)}\n\n`);
174
+ };
175
+ const streamEvents = createOnStreamUserEvents(spec.userStreamEvents, userEventHandler);
176
+ if (streamEvents) {
177
+ for (const [name, fn] of Object.entries(streamEvents)) {
178
+ processor.onUserEvent(name, fn);
179
+ }
180
+ }
135
181
  for await (const event of getProcessorEvents(processor, spec)) {
136
182
  if (event.type === 'partialOutput') { //nodeIdOrTitle filter managed by spec
137
183
  controller.enqueue(`data: ${JSON.stringify(event.delta)}\n\n`);
@@ -14,6 +14,7 @@ export * from './model/ProcessContext.js';
14
14
  export * from './integrations/integrations.js';
15
15
  import './integrations/enableIntegrations.js';
16
16
  export * from './integrations/VectorDatabase.js';
17
+ export * from './integrations/mcp/MCPProvider.js';
17
18
  export * from './integrations/EmbeddingGenerator.js';
18
19
  export * from './integrations/LLMProvider.js';
19
20
  export * from './recording/ExecutionRecorder.js';
@@ -1,7 +1,7 @@
1
1
  // eslint-disable-next-line import/no-cycle -- There has to be a cycle if we're to import the entirety of Rivet here.
2
2
  import * as Rivet from '../exports.js';
3
3
  export class IsomorphicCodeRunner {
4
- async runCode(code, inputs, options) {
4
+ async runCode(code, inputs, options, graphInputs, contextValues) {
5
5
  const argNames = ['inputs'];
6
6
  const args = [inputs];
7
7
  if (options.includeRequire) {
@@ -22,6 +22,14 @@ export class IsomorphicCodeRunner {
22
22
  argNames.push('Rivet');
23
23
  args.push(Rivet);
24
24
  }
25
+ if (graphInputs) {
26
+ argNames.push('graphInputs');
27
+ args.push(graphInputs);
28
+ }
29
+ if (contextValues) {
30
+ argNames.push('context');
31
+ args.push(contextValues);
32
+ }
25
33
  argNames.push(code);
26
34
  const AsyncFunction = async function () { }.constructor;
27
35
  const codeFunction = new AsyncFunction(...argNames);
@@ -30,7 +38,7 @@ export class IsomorphicCodeRunner {
30
38
  }
31
39
  }
32
40
  export class NotAllowedCodeRunner {
33
- async runCode(_code, _inputs, _options) {
41
+ async runCode(_code, _inputs, _options, _graphInputs, _contextValues) {
34
42
  throw new Error('Dynamic code execution is disabled.');
35
43
  }
36
44
  }
@@ -0,0 +1,100 @@
1
+ import { getServerHelperMessage, getServerOptions } from './MCPUtils.js';
2
+ export const getMCPBaseInputs = (data) => {
3
+ const inputs = [];
4
+ if (data.useNameInput) {
5
+ inputs.push({
6
+ dataType: 'string',
7
+ id: 'name',
8
+ title: 'Name',
9
+ });
10
+ }
11
+ if (data.useVersionInput) {
12
+ inputs.push({
13
+ dataType: 'string',
14
+ id: 'version',
15
+ title: 'Version',
16
+ });
17
+ }
18
+ if (data.transportType === 'http' && data.useServerUrlInput) {
19
+ inputs.push({
20
+ dataType: 'string',
21
+ id: 'serverUrl',
22
+ title: 'Server URL',
23
+ description: 'The endpoint URL for the MCP server',
24
+ });
25
+ }
26
+ if (data.transportType === 'http' && data.useHeadersInput) {
27
+ inputs.push({
28
+ dataType: 'object',
29
+ id: 'headers',
30
+ title: 'Headers',
31
+ });
32
+ }
33
+ return inputs;
34
+ };
35
+ export const getMCPBaseEditors = async (context, data) => {
36
+ const editors = [];
37
+ editors.push([
38
+ {
39
+ type: 'toggle',
40
+ label: 'Output Tools',
41
+ dataKey: 'useToolsOutput',
42
+ helperMessage: 'Toggle on if you want to get a Tools output',
43
+ },
44
+ {
45
+ type: 'toggle',
46
+ label: 'Output Prompts',
47
+ dataKey: 'usePromptsOutput',
48
+ helperMessage: 'Toggle on if you want to get a Prompts output',
49
+ },
50
+ {
51
+ type: 'string',
52
+ label: 'Name',
53
+ dataKey: 'name',
54
+ useInputToggleDataKey: 'useNameInput',
55
+ helperMessage: 'The name for the MCP Client',
56
+ },
57
+ {
58
+ type: 'string',
59
+ label: 'Version',
60
+ dataKey: 'version',
61
+ useInputToggleDataKey: 'useVersionInput',
62
+ helperMessage: 'A version for the MCP Client',
63
+ },
64
+ {
65
+ type: 'dropdown',
66
+ label: 'Transport Type',
67
+ dataKey: 'transportType',
68
+ options: [
69
+ { label: 'HTTP', value: 'http' },
70
+ { label: 'STDIO', value: 'stdio' },
71
+ ],
72
+ },
73
+ ]);
74
+ if (data.transportType === 'http') {
75
+ editors.push({
76
+ type: 'string',
77
+ label: 'Server URL',
78
+ dataKey: 'serverUrl',
79
+ useInputToggleDataKey: 'useServerUrlInput',
80
+ helperMessage: 'The endpoint URL for the MCP server to connect',
81
+ }, {
82
+ type: 'code',
83
+ label: 'Headers',
84
+ dataKey: 'headers',
85
+ useInputToggleDataKey: 'useHeadersInput',
86
+ language: 'json',
87
+ });
88
+ }
89
+ else if (data.transportType === 'stdio') {
90
+ const serverOptions = await getServerOptions(context);
91
+ editors.push({
92
+ type: 'dropdown',
93
+ label: 'Server ID',
94
+ dataKey: 'serverId',
95
+ helperMessage: getServerHelperMessage(context, serverOptions.length),
96
+ options: serverOptions,
97
+ });
98
+ }
99
+ return editors;
100
+ };
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Derived from types here - https://github.com/modelcontextprotocol/typescript-sdk/blob/main/src/types.ts
3
+ */
4
+ /**
5
+ * Errors
6
+ */
7
+ export var MCPErrorType;
8
+ (function (MCPErrorType) {
9
+ MCPErrorType["CONFIG_NOT_FOUND"] = "CONFIG_NOT_FOUND";
10
+ MCPErrorType["SERVER_NOT_FOUND"] = "SERVER_NOT_FOUND";
11
+ MCPErrorType["SERVER_COMMUNICATION_FAILED"] = "SERVER_COMMUNICATION_FAILED";
12
+ MCPErrorType["INVALID_SCHEMA"] = "INVALID_SCHEMA";
13
+ })(MCPErrorType || (MCPErrorType = {}));
14
+ export class MCPError extends Error {
15
+ type;
16
+ details;
17
+ constructor(type, message, details) {
18
+ super(message);
19
+ this.type = type;
20
+ this.details = details;
21
+ this.name = 'Error';
22
+ }
23
+ }
@@ -0,0 +1,33 @@
1
+ import { MCPError, MCPErrorType } from './MCPProvider.js';
2
+ export const loadMCPConfiguration = async (context) => {
3
+ if (context.executor !== 'nodejs') {
4
+ throw new MCPError(MCPErrorType.CONFIG_NOT_FOUND, 'MCP config is not supported in browser environment');
5
+ }
6
+ const mcpConfig = context.project.metadata.mcpServer;
7
+ if (!mcpConfig || mcpConfig.mcpServers == null) {
8
+ throw new MCPError(MCPErrorType.CONFIG_NOT_FOUND, 'MCP configuration not defined in Project tab');
9
+ }
10
+ return mcpConfig;
11
+ };
12
+ export const getServerOptions = async (context) => {
13
+ if (context.executor === 'nodejs' && context.nativeApi) {
14
+ try {
15
+ const config = await loadMCPConfiguration(context);
16
+ return Object.entries(config.mcpServers)
17
+ .filter(([, config]) => !config.disabled)
18
+ .map(([id]) => ({
19
+ label: id,
20
+ value: id,
21
+ }));
22
+ }
23
+ catch { }
24
+ }
25
+ return [];
26
+ };
27
+ export const getServerHelperMessage = (context, optionsLength) => {
28
+ if (optionsLength > 0)
29
+ return 'Select an MCP server from local configuration located in the Project tab';
30
+ if (context.executor !== 'nodejs')
31
+ return 'MCP nodes require Node Executor';
32
+ return 'No MCP servers found in config';
33
+ };
@@ -13,7 +13,6 @@ import { coerceTypeOptional } from '../utils/coerceType.js';
13
13
  import { globalRivetNodeRegistry } from './Nodes.js';
14
14
  import { getPluginConfig } from '../utils/index.js';
15
15
  import { GptTokenizerTokenizer } from '../integrations/GptTokenizerTokenizer.js';
16
- // eslint-disable-next-line import/no-cycle -- There has to be a cycle because CodeRunner needs to import the entirety of Rivet
17
16
  import { IsomorphicCodeRunner } from '../integrations/CodeRunner.js';
18
17
  // CJS compatibility, gets default.default for whatever reason
19
18
  let PQueue = PQueueImport;
@@ -79,6 +78,7 @@ export class GraphProcessor {
79
78
  // @ts-expect-error
80
79
  #nodesNotInCycle = undefined;
81
80
  #nodeAbortControllers = new Map();
81
+ #graphInputNodeValues = {};
82
82
  /** User input nodes that are pending user input. */
83
83
  #pendingUserInputs = undefined;
84
84
  get isRunning() {
@@ -499,6 +499,7 @@ export class GraphProcessor {
499
499
  this.#abortSuccessfully = false;
500
500
  this.#nodeAbortControllers = new Map();
501
501
  this.#loadedProjects = {};
502
+ this.#graphInputNodeValues = {};
502
503
  }
503
504
  /** Main function for running a graph. Runs a graph and returns the outputs from the output nodes of the graph. */
504
505
  async processGraph(
@@ -512,6 +513,7 @@ export class GraphProcessor {
512
513
  if (this.#running) {
513
514
  throw new Error('Cannot process graph while already processing');
514
515
  }
516
+ console.info(`Process graph called. Context:${context}, Inputs: ${JSON.stringify(inputs)}, Context Values: ${JSON.stringify(contextValues)}`);
515
517
  this.#initProcessState();
516
518
  this.#context = context;
517
519
  this.#graphInputs = inputs;
@@ -522,8 +524,11 @@ export class GraphProcessor {
522
524
  this.#emitter.emit('error', { error });
523
525
  });
524
526
  }
527
+ console.info(`Process graph calling loadProjectReferences`);
525
528
  await this.#loadProjectReferences();
529
+ console.info(`Process graph called loadProjectReferences`);
526
530
  this.#preprocessGraph();
531
+ console.info(`Process graph called preprocessGraph`);
527
532
  if (!this.#isSubProcessor) {
528
533
  await this.#emitter.emit('start', {
529
534
  contextValues: this.#contextValues,
@@ -1227,6 +1232,7 @@ export class GraphProcessor {
1227
1232
  });
1228
1233
  return results;
1229
1234
  },
1235
+ graphInputNodeValues: this.#graphInputNodeValues,
1230
1236
  };
1231
1237
  await this.#waitUntilUnpaused();
1232
1238
  const results = await instance.process(inputValues, context);
@@ -101,7 +101,6 @@ export class NodeRegistration {
101
101
  if (!ImplClass) {
102
102
  throw new Error(`Unknown node type: ${type}`);
103
103
  }
104
- // eslint-disable-next-line new-cap
105
104
  const impl = new ImplClass.impl(node, ImplClass.pluginImpl);
106
105
  if (!impl) {
107
106
  throw new Error(`Unknown node type: ${type}`);
@@ -162,6 +162,12 @@ import { toTreeNode } from './nodes/ToTreeNode.js';
162
162
  export * from './nodes/ToTreeNode.js';
163
163
  import { loopUntilNode } from './nodes/LoopUntilNode.js';
164
164
  export * from './nodes/LoopUntilNode.js';
165
+ import { mcpDiscoveryNode } from './nodes/MCPDiscoveryNode.js';
166
+ export * from './nodes/MCPDiscoveryNode.js';
167
+ import { mcpToolCallNode } from './nodes/MCPToolCallNode.js';
168
+ export * from './nodes/MCPToolCallNode.js';
169
+ import { mcpGetPromptNode } from './nodes/MCPGetPromptNode.js';
170
+ export * from './nodes/MCPGetPromptNode.js';
165
171
  import { referencedGraphAliasNode } from './nodes/ReferencedGraphAliasNode.js';
166
172
  export * from './nodes/ReferencedGraphAliasNode.js';
167
173
  export const registerBuiltInNodes = (registry) => {
@@ -247,6 +253,9 @@ export const registerBuiltInNodes = (registry) => {
247
253
  .register(cronNode)
248
254
  .register(toTreeNode)
249
255
  .register(loopUntilNode)
256
+ .register(mcpDiscoveryNode)
257
+ .register(mcpToolCallNode)
258
+ .register(mcpGetPromptNode)
250
259
  .register(referencedGraphAliasNode);
251
260
  };
252
261
  let globalRivetNodeRegistry = registerBuiltInNodes(new NodeRegistration());
@@ -755,7 +755,7 @@ export const ChatNodeBase = {
755
755
  type: 'function',
756
756
  }));
757
757
  const { messages } = getChatNodeMessages(inputs);
758
- const isReasoningModel = finalModel.startsWith('o1') || finalModel.startsWith('o3');
758
+ const isReasoningModel = finalModel.startsWith('o1') || finalModel.startsWith('o3') || finalModel.startsWith('o4');
759
759
  const completionMessages = await Promise.all(messages.map((message) => chatMessageToOpenAIChatCompletionMessage(message, { isReasoningModel })));
760
760
  let { maxTokens } = data;
761
761
  const openaiModel = {
@@ -155,7 +155,7 @@ export class CodeNodeImpl extends NodeImpl {
155
155
  includeRivet: this.data.allowRivet ?? false,
156
156
  includeProcess: this.data.allowProcess ?? false,
157
157
  includeConsole: this.data.allowConsole ?? false,
158
- });
158
+ }, context.graphInputNodeValues, context.contextValues);
159
159
  if (outputs == null || typeof outputs !== 'object' || ('then' in outputs && typeof outputs.then === 'function')) {
160
160
  throw new Error('Code node must return an object with output values.');
161
161
  }
@@ -36,7 +36,7 @@ export class GetAllDatasetsNodeImpl extends NodeImpl {
36
36
  getEditors() {
37
37
  return [];
38
38
  }
39
- async process(context) {
39
+ async process(_inputs, context) {
40
40
  const { datasetProvider } = context;
41
41
  if (datasetProvider == null) {
42
42
  throw new Error('datasetProvider is required');
@@ -124,6 +124,8 @@ export class GraphInputNodeImpl extends NodeImpl {
124
124
  type: this.data.dataType,
125
125
  value: inputValue,
126
126
  };
127
+ // Store the resolved value in the context for access by other nodes
128
+ context.graphInputNodeValues[this.data.id] = value;
127
129
  return { ['data']: value };
128
130
  }
129
131
  }
@@ -141,7 +141,7 @@ export class HttpCallNodeImpl extends NodeImpl {
141
141
  return dedent `
142
142
  ${this.data.useMethodInput ? '(Method Using Input)' : this.data.method} ${this.data.useUrlInput ? '(URL Using Input)' : this.data.url} ${this.data.useHeadersInput
143
143
  ? '\nHeaders: (Using Input)'
144
- : this.data.headers.trim()
144
+ : this.data.headers?.trim()
145
145
  ? `\nHeaders: ${this.data.headers}`
146
146
  : ''}${this.data.useBodyInput ? '\nBody: (Using Input)' : this.data.body.trim() ? `\nBody: ${this.data.body}` : ''}${this.data.errorOnNon200 ? '\nError on non-200' : ''}
147
147
  `;
@@ -179,7 +179,7 @@ export class HttpCallNodeImpl extends NodeImpl {
179
179
  headers = coerceType(headersInput, 'object');
180
180
  }
181
181
  }
182
- else if (this.data.headers.trim()) {
182
+ else if (this.data.headers?.trim()) {
183
183
  headers = JSON.parse(this.data.headers);
184
184
  }
185
185
  let body;