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

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 (53) hide show
  1. package/README.md +9 -6
  2. package/dist/cjs/bundle.cjs +1382 -262
  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 +27 -0
  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 +87 -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 +3 -0
  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/MCPDiscoveryNode.js +210 -0
  19. package/dist/esm/model/nodes/MCPGetPromptNode.js +233 -0
  20. package/dist/esm/model/nodes/MCPToolCallNode.js +261 -0
  21. package/dist/esm/model/nodes/ObjectNode.js +42 -21
  22. package/dist/esm/model/nodes/PromptNode.js +1 -1
  23. package/dist/esm/model/nodes/TextNode.js +13 -2
  24. package/dist/esm/plugins/anthropic/anthropic.js +22 -3
  25. package/dist/esm/plugins/anthropic/nodes/ChatAnthropicNode.js +33 -3
  26. package/dist/esm/plugins/google/google.js +29 -14
  27. package/dist/esm/plugins/google/nodes/ChatGoogleNode.js +70 -5
  28. package/dist/esm/utils/interpolation.js +155 -17
  29. package/dist/esm/utils/openai.js +24 -0
  30. package/dist/types/api/createProcessor.d.ts +3 -2
  31. package/dist/types/api/streaming.d.ts +8 -1
  32. package/dist/types/exports.d.ts +1 -0
  33. package/dist/types/integrations/CodeRunner.d.ts +4 -3
  34. package/dist/types/integrations/mcp/MCPBase.d.ts +18 -0
  35. package/dist/types/integrations/mcp/MCPProvider.d.ts +153 -0
  36. package/dist/types/integrations/mcp/MCPUtils.d.ts +9 -0
  37. package/dist/types/model/GraphProcessor.d.ts +1 -1
  38. package/dist/types/model/Nodes.d.ts +13 -2
  39. package/dist/types/model/ProcessContext.d.ts +5 -1
  40. package/dist/types/model/Project.d.ts +2 -0
  41. package/dist/types/model/nodes/GetAllDatasetsNode.d.ts +2 -2
  42. package/dist/types/model/nodes/MCPDiscoveryNode.d.ts +9 -0
  43. package/dist/types/model/nodes/MCPGetPromptNode.d.ts +23 -0
  44. package/dist/types/model/nodes/MCPToolCallNode.d.ts +26 -0
  45. package/dist/types/model/nodes/ObjectNode.d.ts +3 -2
  46. package/dist/types/model/nodes/TextNode.d.ts +2 -1
  47. package/dist/types/plugins/anthropic/anthropic.d.ts +21 -3
  48. package/dist/types/plugins/anthropic/nodes/ChatAnthropicNode.d.ts +5 -0
  49. package/dist/types/plugins/google/google.d.ts +12 -2
  50. package/dist/types/plugins/google/nodes/ChatGoogleNode.d.ts +7 -0
  51. package/dist/types/utils/interpolation.d.ts +6 -1
  52. package/dist/types/utils/openai.d.ts +24 -0
  53. package/package.json +3 -3
@@ -0,0 +1,233 @@
1
+ import {} from '../NodeBase.js';
2
+ import { nanoid } from 'nanoid/non-secure';
3
+ import { NodeImpl } from '../NodeImpl.js';
4
+ import { nodeDefinition } from '../NodeDefinition.js';
5
+ import {} from '../GraphProcessor.js';
6
+ import {} from '../../index.js';
7
+ import { MCPError, MCPErrorType } from '../../integrations/mcp/MCPProvider.js';
8
+ import { coerceTypeOptional } from '../../utils/coerceType.js';
9
+ import { dedent, getInputOrData } from '../../utils/index.js';
10
+ import { getError } from '../../utils/errors.js';
11
+ import { getMCPBaseInputs } from '../../integrations/mcp/MCPBase.js';
12
+ import { getServerHelperMessage, getServerOptions, loadMCPConfiguration } from '../../integrations/mcp/MCPUtils.js';
13
+ import { keys } from '../../utils/typeSafety.js';
14
+ import { interpolate } from '../../utils/interpolation.js';
15
+ export class MCPGetPromptNodeImpl extends NodeImpl {
16
+ static create() {
17
+ const chartNode = {
18
+ type: 'mcpGetPrompt',
19
+ title: 'MCP Get Prompt',
20
+ id: nanoid(),
21
+ visualData: {
22
+ x: 0,
23
+ y: 0,
24
+ width: 250,
25
+ },
26
+ data: {
27
+ name: 'mcp-get-prompt-client',
28
+ version: '1.0.0',
29
+ transportType: 'stdio',
30
+ serverUrl: 'http://localhost:8080/mcp',
31
+ serverId: '',
32
+ promptName: '',
33
+ promptArguments: dedent `
34
+ {
35
+ "key": "value"
36
+ }`,
37
+ useNameInput: false,
38
+ useVersionInput: false,
39
+ usePromptNameInput: false,
40
+ usePromptArgumentsInput: false,
41
+ },
42
+ };
43
+ return chartNode;
44
+ }
45
+ getInputDefinitions() {
46
+ const inputs = getMCPBaseInputs(this.data);
47
+ if (this.data.usePromptNameInput) {
48
+ inputs.push({
49
+ dataType: 'string',
50
+ id: 'promptName',
51
+ title: 'Prompt Name',
52
+ });
53
+ }
54
+ if (this.data.usePromptArgumentsInput) {
55
+ inputs.push({
56
+ dataType: 'object',
57
+ id: 'promptArguments',
58
+ title: 'Prompt Arguments',
59
+ });
60
+ }
61
+ return inputs;
62
+ }
63
+ getOutputDefinitions() {
64
+ const outputDefinitions = [];
65
+ outputDefinitions.push({
66
+ id: 'prompt',
67
+ title: 'Prompt',
68
+ dataType: 'object',
69
+ description: 'Prompt response result',
70
+ });
71
+ return outputDefinitions;
72
+ }
73
+ async getEditors(context) {
74
+ const editors = [
75
+ {
76
+ type: 'string',
77
+ label: 'Name',
78
+ dataKey: 'name',
79
+ useInputToggleDataKey: 'useNameInput',
80
+ helperMessage: 'The name for the MCP Client',
81
+ },
82
+ {
83
+ type: 'string',
84
+ label: 'Version',
85
+ dataKey: 'version',
86
+ useInputToggleDataKey: 'useVersionInput',
87
+ helperMessage: 'A version for the MCP Client',
88
+ },
89
+ {
90
+ type: 'dropdown',
91
+ label: 'Transport Type',
92
+ dataKey: 'transportType',
93
+ options: [
94
+ { label: 'HTTP', value: 'http' },
95
+ { label: 'STDIO', value: 'stdio' },
96
+ ],
97
+ },
98
+ {
99
+ type: 'string',
100
+ label: 'Prompt Name',
101
+ dataKey: 'promptName',
102
+ useInputToggleDataKey: 'usePromptNameInput',
103
+ helperMessage: 'The name for the MCP prompt',
104
+ },
105
+ {
106
+ type: 'code',
107
+ label: 'Prompt Arguments',
108
+ dataKey: 'promptArguments',
109
+ useInputToggleDataKey: 'usePromptArgumentsInput',
110
+ language: 'json',
111
+ helperMessage: 'Arguments to provide the prompt',
112
+ },
113
+ ];
114
+ if (this.data.transportType === 'http') {
115
+ editors.push({
116
+ type: 'string',
117
+ label: 'Server URL',
118
+ dataKey: 'serverUrl',
119
+ useInputToggleDataKey: 'useServerUrlInput',
120
+ helperMessage: 'The endpoint URL for the MCP server to connect',
121
+ });
122
+ }
123
+ else if (this.data.transportType === 'stdio') {
124
+ const serverOptions = await getServerOptions(context);
125
+ editors.push({
126
+ type: 'dropdown',
127
+ label: 'Server ID',
128
+ dataKey: 'serverId',
129
+ helperMessage: getServerHelperMessage(context, serverOptions.length),
130
+ options: serverOptions,
131
+ });
132
+ }
133
+ return editors;
134
+ }
135
+ getBody(context) {
136
+ let base;
137
+ if (this.data.transportType === 'http') {
138
+ base = this.data.useServerUrlInput ? '(Using Server URL Input)' : this.data.serverUrl;
139
+ }
140
+ else {
141
+ base = `Server ID: ${this.data.serverId || '(None)'}`;
142
+ }
143
+ const namePart = `Name: ${this.data.name}`;
144
+ const versionPart = `Version: ${this.data.version}`;
145
+ const parts = [namePart, versionPart, base];
146
+ if (context.executor !== 'nodejs') {
147
+ parts.push('(Requires Node Executor)');
148
+ }
149
+ return parts.join('\n');
150
+ }
151
+ static getUIData() {
152
+ return {
153
+ infoBoxBody: dedent `
154
+ Connects to an MCP (Model Context Protocol) server and gets a prompt response.
155
+ `,
156
+ infoBoxTitle: 'MCP Get Prompt Node',
157
+ contextMenuTitle: 'MCP Get Prompt',
158
+ group: ['MCP'],
159
+ };
160
+ }
161
+ async process(inputs, context) {
162
+ const name = getInputOrData(this.data, inputs, 'name', 'string');
163
+ const version = getInputOrData(this.data, inputs, 'version', 'string');
164
+ const promptName = getInputOrData(this.data, inputs, 'promptName', 'string');
165
+ let promptArguments;
166
+ if (this.data.usePromptArgumentsInput) {
167
+ promptArguments = getInputOrData(this.data, inputs, 'promptArguments', 'object');
168
+ if (promptArguments == null) {
169
+ throw new MCPError(MCPErrorType.INVALID_SCHEMA, 'Cannot parse tool argument with input toggle on');
170
+ }
171
+ }
172
+ else {
173
+ const inputMap = keys(inputs)
174
+ .filter((key) => key.startsWith('input'))
175
+ .reduce((acc, key) => {
176
+ const stringValue = coerceTypeOptional(inputs[key], 'string') ?? '';
177
+ const interpolationKey = key.slice('input-'.length);
178
+ acc[interpolationKey] = stringValue;
179
+ return acc;
180
+ }, {});
181
+ const interpolated = interpolate(this.data.promptArguments ?? '', inputMap);
182
+ promptArguments = JSON.parse(interpolated);
183
+ }
184
+ const getPromptRequest = {
185
+ name: promptName,
186
+ arguments: promptArguments,
187
+ };
188
+ const transportType = getInputOrData(this.data, inputs, 'transportType', 'string');
189
+ let getPromptResponse = undefined;
190
+ try {
191
+ if (!context.mcpProvider) {
192
+ throw new Error('MCP Provider not found');
193
+ }
194
+ if (transportType === 'http') {
195
+ const serverUrl = getInputOrData(this.data, inputs, 'serverUrl', 'string');
196
+ if (!serverUrl || serverUrl === '') {
197
+ throw new MCPError(MCPErrorType.SERVER_NOT_FOUND, 'No server URL was provided');
198
+ }
199
+ if (!serverUrl.includes('/mcp')) {
200
+ throw new MCPError(MCPErrorType.SERVER_COMMUNICATION_FAILED, 'Include /mcp in your server URL. For example: http://localhost:8080/mcp');
201
+ }
202
+ getPromptResponse = await context.mcpProvider.getHTTPrompt({ name, version }, serverUrl, getPromptRequest);
203
+ }
204
+ else if (transportType === 'stdio') {
205
+ const serverId = this.data.serverId ?? '';
206
+ const mcpConfig = await loadMCPConfiguration(context);
207
+ if (!mcpConfig.mcpServers[serverId]) {
208
+ throw new MCPError(MCPErrorType.SERVER_NOT_FOUND, `Server ${serverId} not found in MCP config`);
209
+ }
210
+ const serverConfig = {
211
+ config: mcpConfig.mcpServers[serverId],
212
+ serverId,
213
+ };
214
+ getPromptResponse = await context.mcpProvider.getStdioPrompt({ name, version }, serverConfig, getPromptRequest);
215
+ }
216
+ const output = {};
217
+ output['response'] = {
218
+ type: 'object',
219
+ value: getPromptResponse,
220
+ };
221
+ return output;
222
+ }
223
+ catch (err) {
224
+ const { message } = getError(err);
225
+ if (context.executor === 'browser') {
226
+ throw new Error('Failed to create Client without a node executor');
227
+ }
228
+ console.log(message);
229
+ throw err;
230
+ }
231
+ }
232
+ }
233
+ export const mcpGetPromptNode = nodeDefinition(MCPGetPromptNodeImpl, 'MCP Get Prompt');
@@ -0,0 +1,261 @@
1
+ import {} from '../NodeBase.js';
2
+ import { nanoid } from 'nanoid/non-secure';
3
+ import { NodeImpl } from '../NodeImpl.js';
4
+ import { nodeDefinition } from '../NodeDefinition.js';
5
+ import {} from '../GraphProcessor.js';
6
+ import {} from '../../index.js';
7
+ import { MCPError, MCPErrorType } from '../../integrations/mcp/MCPProvider.js';
8
+ import { coerceTypeOptional } from '../../utils/coerceType.js';
9
+ import { getInputOrData } from '../../utils/index.js';
10
+ import { getError } from '../../utils/errors.js';
11
+ import { getMCPBaseInputs } from '../../integrations/mcp/MCPBase.js';
12
+ import { getServerHelperMessage, getServerOptions, loadMCPConfiguration } from '../../integrations/mcp/MCPUtils.js';
13
+ import { dedent } from 'ts-dedent';
14
+ import {} from '../EditorDefinition.js';
15
+ import { interpolate } from '../../utils/interpolation.js';
16
+ import { keys } from '../../utils/typeSafety.js';
17
+ export class MCPToolCallNodeImpl extends NodeImpl {
18
+ static create() {
19
+ const chartNode = {
20
+ type: 'mcpToolCall',
21
+ title: 'MCP Tool Call',
22
+ id: nanoid(),
23
+ visualData: {
24
+ x: 0,
25
+ y: 0,
26
+ width: 250,
27
+ },
28
+ data: {
29
+ name: 'mcp-tool-call-client',
30
+ version: '1.0.0',
31
+ transportType: 'stdio',
32
+ serverUrl: 'http://localhost:8080/mcp',
33
+ serverId: '',
34
+ toolName: '',
35
+ toolArguments: dedent `
36
+ {
37
+ "key": "value"
38
+ }`,
39
+ toolCallId: '',
40
+ useNameInput: false,
41
+ useVersionInput: false,
42
+ useToolNameInput: true,
43
+ useToolArgumentsInput: true,
44
+ useToolCallIdInput: true,
45
+ },
46
+ };
47
+ return chartNode;
48
+ }
49
+ getInputDefinitions() {
50
+ const inputs = getMCPBaseInputs(this.data);
51
+ if (this.data.useToolNameInput) {
52
+ inputs.push({
53
+ dataType: 'string',
54
+ id: 'toolName',
55
+ title: 'Tool Name',
56
+ });
57
+ }
58
+ if (this.data.useToolArgumentsInput) {
59
+ inputs.push({
60
+ dataType: 'object',
61
+ id: 'toolArguments',
62
+ title: 'Tool Arguments',
63
+ });
64
+ }
65
+ if (this.data.useToolCallIdInput) {
66
+ inputs.push({
67
+ dataType: 'object',
68
+ id: 'toolCallId',
69
+ title: 'Tool ID',
70
+ });
71
+ }
72
+ return inputs;
73
+ }
74
+ getOutputDefinitions() {
75
+ const outputDefinitions = [];
76
+ outputDefinitions.push({
77
+ id: 'response',
78
+ title: 'Response',
79
+ dataType: 'object',
80
+ description: 'Response from the Tool Call',
81
+ });
82
+ outputDefinitions.push({
83
+ id: 'toolCallId',
84
+ title: 'Tool ID',
85
+ dataType: 'string',
86
+ description: 'ID associated with the Tool Call',
87
+ });
88
+ return outputDefinitions;
89
+ }
90
+ async getEditors(context) {
91
+ const editors = [
92
+ {
93
+ type: 'string',
94
+ label: 'Name',
95
+ dataKey: 'name',
96
+ useInputToggleDataKey: 'useNameInput',
97
+ helperMessage: 'The name for the MCP Client',
98
+ },
99
+ {
100
+ type: 'string',
101
+ label: 'Version',
102
+ dataKey: 'version',
103
+ useInputToggleDataKey: 'useVersionInput',
104
+ helperMessage: 'A version for the MCP Client',
105
+ },
106
+ {
107
+ type: 'dropdown',
108
+ label: 'Transport Type',
109
+ dataKey: 'transportType',
110
+ options: [
111
+ { label: 'HTTP', value: 'http' },
112
+ { label: 'STDIO', value: 'stdio' },
113
+ ],
114
+ },
115
+ {
116
+ type: 'string',
117
+ label: 'Tool Name',
118
+ dataKey: 'toolName',
119
+ useInputToggleDataKey: 'useToolNameInput',
120
+ helperMessage: 'The name for the MCP Tool Call',
121
+ },
122
+ {
123
+ type: 'code',
124
+ label: 'Tool Arguments',
125
+ dataKey: 'toolArguments',
126
+ language: 'json',
127
+ useInputToggleDataKey: 'useToolArgumentsInput',
128
+ },
129
+ {
130
+ type: 'string',
131
+ label: 'Tool ID',
132
+ dataKey: 'toolCallId',
133
+ useInputToggleDataKey: 'useToolCallIdInput',
134
+ helperMessage: 'The name for the MCP Tool Call',
135
+ },
136
+ ];
137
+ if (this.data.transportType === 'http') {
138
+ editors.push({
139
+ type: 'string',
140
+ label: 'Server URL',
141
+ dataKey: 'serverUrl',
142
+ useInputToggleDataKey: 'useServerUrlInput',
143
+ helperMessage: 'The endpoint URL for the MCP server to connect',
144
+ });
145
+ }
146
+ else if (this.data.transportType === 'stdio') {
147
+ const serverOptions = await getServerOptions(context);
148
+ editors.push({
149
+ type: 'dropdown',
150
+ label: 'Server ID',
151
+ dataKey: 'serverId',
152
+ helperMessage: getServerHelperMessage(context, serverOptions.length),
153
+ options: serverOptions,
154
+ });
155
+ }
156
+ return editors;
157
+ }
158
+ getBody(context) {
159
+ let base;
160
+ if (this.data.transportType === 'http') {
161
+ base = this.data.useServerUrlInput ? '(Using Server URL Input)' : this.data.serverUrl;
162
+ }
163
+ else {
164
+ base = `Server ID: ${this.data.serverId || '(None)'}`;
165
+ }
166
+ const namePart = `Name: ${this.data.name}`;
167
+ const versionPart = `Version: ${this.data.version}`;
168
+ const parts = [namePart, versionPart, base];
169
+ if (context.executor !== 'nodejs') {
170
+ parts.push('(Requires Node Executor)');
171
+ }
172
+ return parts.join('\n');
173
+ }
174
+ static getUIData() {
175
+ return {
176
+ infoBoxBody: dedent `
177
+ Connects to an MCP (Model Context Protocol) server and gets a tool call response.
178
+ `,
179
+ infoBoxTitle: 'MCP Tool Call Node',
180
+ contextMenuTitle: 'MCP Tool Call',
181
+ group: ['MCP'],
182
+ };
183
+ }
184
+ async process(inputs, context) {
185
+ const name = getInputOrData(this.data, inputs, 'name', 'string');
186
+ const version = getInputOrData(this.data, inputs, 'version', 'string');
187
+ const toolName = getInputOrData(this.data, inputs, 'toolName', 'string');
188
+ const toolCallId = getInputOrData(this.data, inputs, 'toolCallId', 'string');
189
+ let toolArguments;
190
+ if (this.data.useToolArgumentsInput) {
191
+ toolArguments = getInputOrData(this.data, inputs, 'toolArguments', 'object');
192
+ if (toolArguments == null) {
193
+ throw new MCPError(MCPErrorType.INVALID_SCHEMA, 'Cannot parse tool argument with input toggle on');
194
+ }
195
+ }
196
+ else {
197
+ const inputMap = keys(inputs)
198
+ .filter((key) => key.startsWith('input'))
199
+ .reduce((acc, key) => {
200
+ const stringValue = coerceTypeOptional(inputs[key], 'string') ?? '';
201
+ const interpolationKey = key.slice('input-'.length);
202
+ acc[interpolationKey] = stringValue;
203
+ return acc;
204
+ }, {});
205
+ const interpolated = interpolate(this.data.toolArguments ?? '', inputMap);
206
+ toolArguments = JSON.parse(interpolated);
207
+ }
208
+ const toolCall = {
209
+ name: toolName,
210
+ arguments: toolArguments,
211
+ };
212
+ const transportType = getInputOrData(this.data, inputs, 'transportType', 'string');
213
+ let toolResponse = undefined;
214
+ try {
215
+ if (!context.mcpProvider) {
216
+ throw new Error('MCP Provider not found');
217
+ }
218
+ if (transportType === 'http') {
219
+ const serverUrl = getInputOrData(this.data, inputs, 'serverUrl', 'string');
220
+ if (!serverUrl || serverUrl === '') {
221
+ throw new MCPError(MCPErrorType.SERVER_NOT_FOUND, 'No server URL was provided');
222
+ }
223
+ if (!serverUrl.includes('/mcp')) {
224
+ throw new MCPError(MCPErrorType.SERVER_COMMUNICATION_FAILED, 'Include /mcp in your server URL. For example: http://localhost:8080/mcp');
225
+ }
226
+ toolResponse = await context.mcpProvider.httpToolCall({ name, version }, serverUrl, toolCall);
227
+ }
228
+ else if (transportType === 'stdio') {
229
+ const serverId = this.data.serverId ?? '';
230
+ const mcpConfig = await loadMCPConfiguration(context);
231
+ if (!mcpConfig.mcpServers[serverId]) {
232
+ throw new MCPError(MCPErrorType.SERVER_NOT_FOUND, `Server ${serverId} not found in MCP config`);
233
+ }
234
+ const serverConfig = {
235
+ config: mcpConfig.mcpServers[serverId],
236
+ serverId,
237
+ };
238
+ toolResponse = await context.mcpProvider.stdioToolCall({ name, version }, serverConfig, toolCall);
239
+ }
240
+ const output = {};
241
+ output['response'] = {
242
+ type: 'object[]',
243
+ value: toolResponse?.content,
244
+ };
245
+ output['toolCallId'] = {
246
+ type: 'string',
247
+ value: toolCallId,
248
+ };
249
+ return output;
250
+ }
251
+ catch (err) {
252
+ const { message } = getError(err);
253
+ if (context.executor === 'browser') {
254
+ throw new Error('Failed to create Client without a node executor');
255
+ }
256
+ console.log(message);
257
+ throw err;
258
+ }
259
+ }
260
+ }
261
+ export const mcpToolCallNode = nodeDefinition(MCPToolCallNodeImpl, 'MCP Tool Call');
@@ -5,6 +5,7 @@ import { nodeDefinition } from '../NodeDefinition.js';
5
5
  import {} from '../DataValue.js';
6
6
  import { dedent } from 'ts-dedent';
7
7
  import {} from '../EditorDefinition.js';
8
+ import { resolveExpressionRawValue, unwrapPotentialDataValue } from '../../utils/interpolation.js';
8
9
  const DEFAULT_JSON_TEMPLATE = `{
9
10
  "key": "{{input}}"
10
11
  }`;
@@ -26,17 +27,23 @@ export class ObjectNodeImpl extends NodeImpl {
26
27
  return chartNode;
27
28
  }
28
29
  getInputDefinitions() {
29
- // Extract inputs from text, everything like {{input}}
30
- const inputNames = [...new Set(this.chartNode.data.jsonTemplate.match(/\{\{([^}]+)\}\}/g))];
31
- return (inputNames?.map((inputName) => {
30
+ // Provide default empty string for jsonTemplate if undefined
31
+ const jsonTemplate = this.chartNode.data.jsonTemplate ?? '';
32
+ const matches = jsonTemplate.match(/\{\{([^}]+?)\}\}/g); // matches is string[] | null
33
+ const allTokens = matches ?? []; // allTokens is now explicitly string[]
34
+ const inputTokens = allTokens
35
+ // id and title should not have the {{ and }}
36
+ .map((token) => token.slice(2, -2).trim())
37
+ .filter((tokenContent) => !tokenContent.startsWith('@graphInputs.') && !tokenContent.startsWith('@context.'))
38
+ .filter((token) => token !== '');
39
+ return [...new Set(inputTokens)].map((inputName) => {
32
40
  return {
33
- // id and title should not have the {{ and }}
34
- id: inputName.slice(2, -2),
35
- title: inputName.slice(2, -2),
41
+ id: inputName,
42
+ title: inputName,
36
43
  dataType: 'any',
37
44
  required: false,
38
45
  };
39
- }) ?? []);
46
+ });
40
47
  }
41
48
  getOutputDefinitions() {
42
49
  return [
@@ -77,10 +84,22 @@ export class ObjectNodeImpl extends NodeImpl {
77
84
  group: ['Objects'],
78
85
  };
79
86
  }
80
- interpolate(baseString, values) {
81
- return baseString.replace(/("?)\{\{([^}]+)\}\}("?)/g, (_m, openQuote, key, _closeQuote) => {
87
+ interpolate(baseString, values, graphInputNodeValues, contextValues) {
88
+ return baseString.replace(/("?)\{\{([^}]+?)\}\}("?)/g, (_m, openQuote, key, _closeQuote) => {
82
89
  const isQuoted = Boolean(openQuote);
83
- const value = values[key];
90
+ const trimmedKey = key.trim(); // Use trimmedKey for lookups
91
+ let value;
92
+ const graphInputPrefix = '@graphInputs.';
93
+ const contextPrefix = '@context.';
94
+ if (trimmedKey.startsWith(graphInputPrefix) && graphInputNodeValues) {
95
+ value = resolveExpressionRawValue(graphInputNodeValues, trimmedKey.substring(graphInputPrefix.length), 'graphInputs');
96
+ }
97
+ else if (trimmedKey.startsWith(contextPrefix) && contextValues) {
98
+ value = resolveExpressionRawValue(contextValues, trimmedKey.substring(contextPrefix.length), 'context');
99
+ }
100
+ else {
101
+ value = values[trimmedKey]; // Original logic for non-@ variables
102
+ }
84
103
  if (value == null) {
85
104
  return 'null';
86
105
  }
@@ -96,23 +115,25 @@ export class ObjectNodeImpl extends NodeImpl {
96
115
  return JSON.stringify(value);
97
116
  });
98
117
  }
99
- async process(inputs) {
118
+ async process(inputs, context) {
100
119
  const inputMap = Object.keys(inputs).reduce((acc, key) => {
101
- acc[key] = inputs[key]?.value;
120
+ acc[key] = unwrapPotentialDataValue(inputs[key]);
102
121
  return acc;
103
122
  }, {});
104
- const outputValue = JSON.parse(this.interpolate(this.chartNode.data.jsonTemplate, inputMap));
105
- if (Array.isArray(outputValue)) {
106
- return {
107
- output: {
108
- type: 'object[]',
109
- value: outputValue,
110
- },
111
- };
123
+ const interpolatedString = this.interpolate(this.chartNode.data.jsonTemplate, inputMap, context.graphInputNodeValues, // Pass graph inputs
124
+ context.contextValues // Pass context values
125
+ );
126
+ let outputValue;
127
+ try {
128
+ outputValue = JSON.parse(interpolatedString);
129
+ }
130
+ catch (err) {
131
+ throw new Error(`Failed to parse JSON template: ${err.message}`);
112
132
  }
133
+ const outputType = Array.isArray(outputValue) ? 'object[]' : 'object';
113
134
  return {
114
135
  output: {
115
- type: 'object',
136
+ type: outputType,
116
137
  value: outputValue,
117
138
  },
118
139
  };
@@ -177,7 +177,7 @@ export class PromptNodeImpl extends NodeImpl {
177
177
  }
178
178
  async process(inputs, context) {
179
179
  const inputMap = mapValues(inputs, (input) => coerceType(input, 'string'));
180
- const outputValue = interpolate(this.chartNode.data.promptText, inputMap);
180
+ const outputValue = interpolate(this.chartNode.data.promptText, inputMap, context.graphInputNodeValues, context.contextValues);
181
181
  const type = getInputOrData(this.data, inputs, 'type', 'string');
182
182
  const isCacheBreakpoint = getInputOrData(this.data, inputs, 'isCacheBreakpoint', 'boolean');
183
183
  if (['assistant', 'system', 'user', 'function'].includes(type) === false) {
@@ -20,6 +20,7 @@ export class TextNodeImpl extends NodeImpl {
20
20
  },
21
21
  data: {
22
22
  text: '{{input}}',
23
+ normalizeLineEndings: true, // Default to true for better compatibility
23
24
  },
24
25
  };
25
26
  return chartNode;
@@ -61,6 +62,12 @@ export class TextNodeImpl extends NodeImpl {
61
62
  language: 'prompt-interpolation-markdown',
62
63
  theme: 'prompt-interpolation',
63
64
  },
65
+ {
66
+ type: 'toggle',
67
+ label: 'Normalize Line Endings',
68
+ dataKey: 'normalizeLineEndings',
69
+ helperMessage: 'Normalize line endings to use only LF (\\n) instead of CRLF (\\r\\n).',
70
+ },
64
71
  ];
65
72
  }
66
73
  getBody() {
@@ -72,13 +79,17 @@ export class TextNodeImpl extends NodeImpl {
72
79
  text: truncated,
73
80
  };
74
81
  }
75
- async process(inputs) {
82
+ async process(inputs, context) {
76
83
  const inputMap = Object.keys(inputs).reduce((acc, key) => {
77
84
  const stringValue = coerceTypeOptional(inputs[key], 'string') ?? '';
78
85
  acc[key] = stringValue;
79
86
  return acc;
80
87
  }, {});
81
- const outputValue = interpolate(this.chartNode.data.text, inputMap);
88
+ let outputValue = interpolate(this.chartNode.data.text, inputMap, context.graphInputNodeValues, // Pass graph inputs
89
+ context.contextValues);
90
+ if (this.chartNode.data.normalizeLineEndings) {
91
+ outputValue = outputValue.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
92
+ }
82
93
  return {
83
94
  output: {
84
95
  type: 'string',