@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
@@ -0,0 +1,290 @@
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 { coerceType } 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
+ headers: '',
34
+ serverId: '',
35
+ toolName: '',
36
+ toolArguments: dedent `
37
+ {
38
+ "key": "value"
39
+ }`,
40
+ toolCallId: '',
41
+ useNameInput: false,
42
+ useVersionInput: false,
43
+ useToolNameInput: true,
44
+ useToolArgumentsInput: true,
45
+ useToolCallIdInput: true,
46
+ },
47
+ };
48
+ return chartNode;
49
+ }
50
+ getInputDefinitions() {
51
+ const inputs = getMCPBaseInputs(this.data);
52
+ if (this.data.useToolNameInput) {
53
+ inputs.push({
54
+ dataType: 'string',
55
+ id: 'toolName',
56
+ title: 'Tool Name',
57
+ });
58
+ }
59
+ if (this.data.useToolArgumentsInput) {
60
+ inputs.push({
61
+ dataType: 'object',
62
+ id: 'toolArguments',
63
+ title: 'Tool Arguments',
64
+ });
65
+ }
66
+ if (this.data.useToolCallIdInput) {
67
+ inputs.push({
68
+ dataType: 'object',
69
+ id: 'toolCallId',
70
+ title: 'Tool ID',
71
+ });
72
+ }
73
+ return inputs;
74
+ }
75
+ getOutputDefinitions() {
76
+ const outputDefinitions = [];
77
+ outputDefinitions.push({
78
+ id: 'response',
79
+ title: 'Response',
80
+ dataType: 'object',
81
+ description: 'Response from the Tool Call',
82
+ });
83
+ outputDefinitions.push({
84
+ id: 'toolCallId',
85
+ title: 'Tool ID',
86
+ dataType: 'string',
87
+ description: 'ID associated with the Tool Call',
88
+ });
89
+ return outputDefinitions;
90
+ }
91
+ async getEditors(context) {
92
+ const editors = [
93
+ {
94
+ type: 'string',
95
+ label: 'Name',
96
+ dataKey: 'name',
97
+ useInputToggleDataKey: 'useNameInput',
98
+ helperMessage: 'The name for the MCP Client',
99
+ },
100
+ {
101
+ type: 'string',
102
+ label: 'Version',
103
+ dataKey: 'version',
104
+ useInputToggleDataKey: 'useVersionInput',
105
+ helperMessage: 'A version for the MCP Client',
106
+ },
107
+ {
108
+ type: 'dropdown',
109
+ label: 'Transport Type',
110
+ dataKey: 'transportType',
111
+ options: [
112
+ { label: 'HTTP', value: 'http' },
113
+ { label: 'STDIO', value: 'stdio' },
114
+ ],
115
+ },
116
+ {
117
+ type: 'string',
118
+ label: 'Tool Name',
119
+ dataKey: 'toolName',
120
+ useInputToggleDataKey: 'useToolNameInput',
121
+ helperMessage: 'The name for the MCP Tool Call',
122
+ },
123
+ {
124
+ type: 'code',
125
+ label: 'Tool Arguments',
126
+ dataKey: 'toolArguments',
127
+ language: 'json',
128
+ useInputToggleDataKey: 'useToolArgumentsInput',
129
+ },
130
+ {
131
+ type: 'string',
132
+ label: 'Tool ID',
133
+ dataKey: 'toolCallId',
134
+ useInputToggleDataKey: 'useToolCallIdInput',
135
+ helperMessage: 'The ID associated with the tool call',
136
+ },
137
+ ];
138
+ if (this.data.transportType === 'http') {
139
+ editors.push({
140
+ type: 'string',
141
+ label: 'Server URL',
142
+ dataKey: 'serverUrl',
143
+ useInputToggleDataKey: 'useServerUrlInput',
144
+ helperMessage: 'The base URL endpoint for the MCP server with `/mcp`',
145
+ }, {
146
+ type: 'code',
147
+ label: 'Headers',
148
+ dataKey: 'headers',
149
+ useInputToggleDataKey: 'useHeadersInput',
150
+ language: 'json',
151
+ });
152
+ }
153
+ else if (this.data.transportType === 'stdio') {
154
+ const serverOptions = await getServerOptions(context);
155
+ editors.push({
156
+ type: 'dropdown',
157
+ label: 'Server ID',
158
+ dataKey: 'serverId',
159
+ helperMessage: getServerHelperMessage(context, serverOptions.length),
160
+ options: serverOptions,
161
+ });
162
+ }
163
+ return editors;
164
+ }
165
+ getBody(context) {
166
+ let base;
167
+ let headers = '';
168
+ if (this.data.transportType === 'http') {
169
+ base = this.data.useServerUrlInput ? '(Using Server URL Input)' : this.data.serverUrl;
170
+ headers = this.data.useHeadersInput
171
+ ? '\nHeaders: (Using Input)'
172
+ : this.data.headers?.trim()
173
+ ? `\nHeaders: ${this.data.headers}`
174
+ : '';
175
+ }
176
+ else {
177
+ base = `Server ID: ${this.data.serverId || '(None)'}`;
178
+ }
179
+ const namePart = `Name: ${this.data.name}`;
180
+ const versionPart = `Version: ${this.data.version}`;
181
+ const parts = [namePart, versionPart, base, headers];
182
+ if (context.executor !== 'nodejs') {
183
+ parts.push('(Requires Node Executor)');
184
+ }
185
+ return parts.join('\n');
186
+ }
187
+ static getUIData() {
188
+ return {
189
+ infoBoxBody: dedent `
190
+ Connects to an MCP (Model Context Protocol) server and gets a tool call response.
191
+ `,
192
+ infoBoxTitle: 'MCP Tool Call Node',
193
+ contextMenuTitle: 'MCP Tool Call',
194
+ group: ['MCP'],
195
+ };
196
+ }
197
+ async process(inputs, context) {
198
+ const name = getInputOrData(this.data, inputs, 'name', 'string');
199
+ const version = getInputOrData(this.data, inputs, 'version', 'string');
200
+ const toolName = getInputOrData(this.data, inputs, 'toolName', 'string');
201
+ const toolCallId = getInputOrData(this.data, inputs, 'toolCallId', 'string');
202
+ let toolArguments;
203
+ if (this.data.useToolArgumentsInput) {
204
+ toolArguments = getInputOrData(this.data, inputs, 'toolArguments', 'object');
205
+ if (toolArguments == null) {
206
+ throw new MCPError(MCPErrorType.INVALID_SCHEMA, 'Cannot parse tool argument with input toggle on');
207
+ }
208
+ }
209
+ else {
210
+ const inputMap = keys(inputs)
211
+ .filter((key) => key.startsWith('input'))
212
+ .reduce((acc, key) => {
213
+ const stringValue = coerceTypeOptional(inputs[key], 'string') ?? '';
214
+ const interpolationKey = key.slice('input-'.length);
215
+ acc[interpolationKey] = stringValue;
216
+ return acc;
217
+ }, {});
218
+ const interpolated = interpolate(this.data.toolArguments ?? '', inputMap);
219
+ toolArguments = JSON.parse(interpolated);
220
+ }
221
+ const toolCall = {
222
+ name: toolName,
223
+ arguments: toolArguments,
224
+ };
225
+ const transportType = getInputOrData(this.data, inputs, 'transportType', 'string');
226
+ let toolResponse = undefined;
227
+ try {
228
+ if (!context.mcpProvider) {
229
+ throw new Error('MCP Provider not found');
230
+ }
231
+ if (transportType === 'http') {
232
+ const serverUrl = getInputOrData(this.data, inputs, 'serverUrl', 'string');
233
+ if (!serverUrl || serverUrl === '') {
234
+ throw new MCPError(MCPErrorType.SERVER_NOT_FOUND, 'No server URL was provided');
235
+ }
236
+ if (!serverUrl.includes('/mcp')) {
237
+ throw new MCPError(MCPErrorType.SERVER_COMMUNICATION_FAILED, 'Include /mcp in your server URL. For example: http://localhost:8080/mcp');
238
+ }
239
+ let headers;
240
+ if (this.data.useHeadersInput) {
241
+ const headersInput = inputs['headers'];
242
+ if (headersInput?.type === 'string') {
243
+ headers = JSON.parse(headersInput.value);
244
+ }
245
+ else if (headersInput?.type === 'object') {
246
+ headers = headersInput.value;
247
+ }
248
+ else {
249
+ headers = coerceType(headersInput, 'object');
250
+ }
251
+ }
252
+ else if (this.data.headers?.trim()) {
253
+ headers = JSON.parse(this.data.headers);
254
+ }
255
+ toolResponse = await context.mcpProvider.httpToolCall({ name, version }, serverUrl, headers, toolCall);
256
+ }
257
+ else if (transportType === 'stdio') {
258
+ const serverId = this.data.serverId ?? '';
259
+ const mcpConfig = await loadMCPConfiguration(context);
260
+ if (!mcpConfig.mcpServers[serverId]) {
261
+ throw new MCPError(MCPErrorType.SERVER_NOT_FOUND, `Server ${serverId} not found in MCP config`);
262
+ }
263
+ const serverConfig = {
264
+ config: mcpConfig.mcpServers[serverId],
265
+ serverId,
266
+ };
267
+ toolResponse = await context.mcpProvider.stdioToolCall({ name, version }, serverConfig, toolCall);
268
+ }
269
+ const output = {};
270
+ output['response'] = {
271
+ type: 'object[]',
272
+ value: toolResponse?.content,
273
+ };
274
+ output['toolCallId'] = {
275
+ type: 'string',
276
+ value: toolCallId,
277
+ };
278
+ return output;
279
+ }
280
+ catch (err) {
281
+ const { message } = getError(err);
282
+ if (context.executor === 'browser') {
283
+ throw new Error('Failed to create Client without a node executor');
284
+ }
285
+ console.log(message);
286
+ throw err;
287
+ }
288
+ }
289
+ }
290
+ 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) {
@@ -159,6 +159,7 @@ export class SubGraphNodeImpl extends NodeImpl {
159
159
  return outputs;
160
160
  }
161
161
  catch (err) {
162
+ console.error(`Error Processing subgraph: ${getError(err).message}`);
162
163
  if (!this.data.useErrorOutput) {
163
164
  throw err;
164
165
  }
@@ -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',
@@ -53,7 +53,7 @@ class ChatAidonNodeImpl extends ChatNodeImpl {
53
53
  }
54
54
  return path;
55
55
  }
56
- async callToolGet(parsedArgs, schemaDetail, path, data) {
56
+ async callToolGet(schemaDetail, path, parsedArgs, data) {
57
57
  const queryParams = new URLSearchParams(parsedArgs.parameters).toString();
58
58
  const fullUrl = schemaDetail.url + path + (queryParams ? "?" + queryParams : "");
59
59
  let headers = {};
@@ -112,8 +112,11 @@ class ChatAidonNodeImpl extends ChatNodeImpl {
112
112
  async process(inputs, context) {
113
113
  //make sure not to include functions if we have no way to run them after.
114
114
  inputs = this.removeInvalidInputs(inputs);
115
+ // const configData = this.data
116
+ // configData.frequencyPenalty = 2;
115
117
  // Call the parent class's process method to do its job
116
118
  let outputs = await super.process(inputs, context);
119
+ //Now check if the LLM wants us to do some tool calling
117
120
  const funcCallOutput = outputs['function-call'] ?? outputs['function-calls'];
118
121
  const funcCalls = funcCallOutput?.type === 'object[]'
119
122
  ? funcCallOutput.value
@@ -135,14 +138,13 @@ class ChatAidonNodeImpl extends ChatNodeImpl {
135
138
  }
136
139
  const schemaDetail = this.convertToolSchemaToSchemaDetail(toolSchema);
137
140
  const path = this.extractPath(schemaDetail, functionCall.name, functionCall.arguments);
138
- // Determine if the request should be in the body or as a query
139
141
  let data = {};
140
- if (schemaDetail.requestInBody) {
141
- // If the type is set to body
142
+ // Determine if the request should be in the body or as a query
143
+ if (schemaDetail.requestInBody) { // If the type is set to body
142
144
  data = await this.callToolPost(schemaDetail, path, functionCall.arguments, data);
143
145
  }
144
146
  else { // If the type is set to query
145
- data = await this.callToolGet(functionCall.arguments, schemaDetail, path, data);
147
+ data = await this.callToolGet(schemaDetail, path, functionCall.arguments, data);
146
148
  }
147
149
  messages['value'].push({
148
150
  type: "function",
@@ -3,6 +3,21 @@ import { chatAidonNode } from './nodes/ChatAidonNode.js';
3
3
  export const aidonPlugin = {
4
4
  id: 'aidon',
5
5
  name: 'Aidon',
6
+ configSpec: {
7
+ aidonURL: {
8
+ type: 'string',
9
+ label: 'Aidon URL',
10
+ description: 'The URL for the Aidon application.',
11
+ helperText: 'Defaults to https://app.aidon.ai. URL for the Aidon application.',
12
+ default: 'https://app.aidon.ai',
13
+ },
14
+ aidonKey: {
15
+ type: 'secret',
16
+ label: 'Aidon API Key',
17
+ description: 'The API Key for the Aidon application.',
18
+ helperText: 'API Key for the Aidon application.',
19
+ },
20
+ },
6
21
  register: (register) => {
7
22
  register(chatAidonNode());
8
23
  },
@@ -80,12 +80,28 @@ export const anthropicModels = {
80
80
  },
81
81
  displayName: 'Claude 3.7 Sonnet',
82
82
  },
83
+ 'claude-sonnet-4-20250514': {
84
+ maxTokens: 200_000,
85
+ cost: {
86
+ prompt: 3e-6,
87
+ completion: 3.75e-6,
88
+ },
89
+ displayName: 'Claude Sonnet 4',
90
+ },
91
+ 'claude-opus-4-20250514': {
92
+ maxTokens: 200_000,
93
+ cost: {
94
+ prompt: 15e-6,
95
+ completion: 18.75e-6,
96
+ },
97
+ displayName: 'Claude Opus 4',
98
+ },
83
99
  };
84
100
  export const anthropicModelOptions = Object.entries(anthropicModels).map(([id, { displayName }]) => ({
85
101
  value: id,
86
102
  label: displayName,
87
103
  }));
88
- export async function* streamChatCompletions({ apiEndpoint, apiKey, signal, ...rest }) {
104
+ export async function* streamChatCompletions({ apiEndpoint, apiKey, signal, additionalHeaders, ...rest }) {
89
105
  const defaultSignal = new AbortController().signal;
90
106
  const response = await fetchEventSource(`${apiEndpoint}/completions`, {
91
107
  method: 'POST',
@@ -94,6 +110,7 @@ export async function* streamChatCompletions({ apiEndpoint, apiKey, signal, ...r
94
110
  'x-api-key': apiKey,
95
111
  'anthropic-version': '2023-06-01',
96
112
  'anthropic-dangerous-direct-browser-access': 'true',
113
+ ...additionalHeaders,
97
114
  },
98
115
  body: JSON.stringify({
99
116
  ...rest,
@@ -125,7 +142,7 @@ export async function* streamChatCompletions({ apiEndpoint, apiKey, signal, ...r
125
142
  throw new AnthropicError(`No chunks received. Response: ${JSON.stringify(responseJson)}`, response, responseJson);
126
143
  }
127
144
  }
128
- export async function callMessageApi({ apiEndpoint, apiKey, signal, tools, beta, ...rest }) {
145
+ export async function callMessageApi({ apiEndpoint, apiKey, signal, tools, beta, additionalHeaders, ...rest }) {
129
146
  const defaultSignal = new AbortController().signal;
130
147
  const response = await fetch(`${apiEndpoint}/messages`, {
131
148
  method: 'POST',
@@ -135,6 +152,7 @@ export async function callMessageApi({ apiEndpoint, apiKey, signal, tools, beta,
135
152
  'anthropic-version': '2023-06-01',
136
153
  'anthropic-dangerous-direct-browser-access': 'true',
137
154
  ...(beta ? { 'anthropic-beta': beta } : {}),
155
+ ...additionalHeaders,
138
156
  },
139
157
  body: JSON.stringify({
140
158
  ...rest,
@@ -149,7 +167,7 @@ export async function callMessageApi({ apiEndpoint, apiKey, signal, tools, beta,
149
167
  }
150
168
  return responseJson;
151
169
  }
152
- export async function* streamMessageApi({ apiEndpoint, apiKey, signal, beta, ...rest }) {
170
+ export async function* streamMessageApi({ apiEndpoint, apiKey, signal, beta, additionalHeaders, ...rest }) {
153
171
  // Use the Messages API for Claude 3 models
154
172
  const defaultSignal = new AbortController().signal;
155
173
  const response = await fetchEventSource(`${apiEndpoint}/messages`, {
@@ -160,6 +178,7 @@ export async function* streamMessageApi({ apiEndpoint, apiKey, signal, beta, ...
160
178
  'anthropic-version': '2023-06-01',
161
179
  'anthropic-dangerous-direct-browser-access': 'true',
162
180
  ...(beta ? { 'anthropic-beta': beta } : {}),
181
+ ...additionalHeaders,
163
182
  },
164
183
  body: JSON.stringify({
165
184
  ...rest,
@@ -12,7 +12,7 @@ import { getScalarTypeOf, isArrayDataValue } from '../../../model/DataValue.js';
12
12
  import { assertNever } from '../../../utils/assertNever.js';
13
13
  import { isNotNull } from '../../../utils/genericUtilFunctions.js';
14
14
  import { uint8ArrayToBase64 } from '../../../utils/base64.js';
15
- import { getInputOrData } from '../../../utils/inputs.js';
15
+ import { getInputOrData, cleanHeaders } from '../../../utils/inputs.js';
16
16
  // Temporary
17
17
  const cache = new Map();
18
18
  export const ChatAnthropicNodeImpl = {
@@ -27,7 +27,7 @@ export const ChatAnthropicNodeImpl = {
27
27
  width: 275,
28
28
  },
29
29
  data: {
30
- model: 'claude-3-7-sonnet-latest',
30
+ model: 'claude-sonnet-4-20250514',
31
31
  useModelInput: false,
32
32
  temperature: 0.5,
33
33
  useTemperatureInput: false,
@@ -120,6 +120,14 @@ export const ChatAnthropicNodeImpl = {
120
120
  coerced: false,
121
121
  });
122
122
  }
123
+ if (data.useHeadersInput) {
124
+ inputs.push({
125
+ dataType: 'object',
126
+ id: 'headers',
127
+ title: 'Headers',
128
+ description: 'Additional headers to send to the API.',
129
+ });
130
+ }
123
131
  return inputs;
124
132
  },
125
133
  getOutputDefinitions(data) {
@@ -267,6 +275,14 @@ export const ChatAnthropicNodeImpl = {
267
275
  useInputToggleDataKey: 'useOverrideModelInput',
268
276
  helperMessage: 'Overrides the AI model used for the chat node to this value.',
269
277
  },
278
+ {
279
+ type: 'keyValuePair',
280
+ label: 'Headers',
281
+ dataKey: 'headers',
282
+ useInputToggleDataKey: 'useHeadersInput',
283
+ keyPlaceholder: 'Header',
284
+ helperMessage: 'Additional headers to send to the API.',
285
+ },
270
286
  ],
271
287
  },
272
288
  ];
@@ -343,6 +359,18 @@ export const ChatAnthropicNodeImpl = {
343
359
  addWarning(output, message);
344
360
  maxTokens = Math.floor((modelInfo.maxTokens - tokenCountEstimate) * 0.95); // reduce max tokens by 5% to be safe, calculation is a little wrong.
345
361
  }
362
+ const headersFromData = (data.headers ?? []).reduce((acc, header) => {
363
+ acc[header.key] = header.value;
364
+ return acc;
365
+ }, {});
366
+ const additionalHeaders = data.useHeadersInput
367
+ ? coerceTypeOptional(inputs['headers'], 'object') ??
368
+ headersFromData
369
+ : headersFromData;
370
+ const allAdditionalHeaders = cleanHeaders({
371
+ ...context.settings.chatNodeHeaders,
372
+ ...additionalHeaders,
373
+ });
346
374
  try {
347
375
  return await retry(async () => {
348
376
  const completionOptions = {
@@ -365,7 +393,7 @@ export const ChatAnthropicNodeImpl = {
365
393
  ? tools.map((tool) => ({ name: tool.name, description: tool.description, input_schema: tool.parameters }))
366
394
  : undefined,
367
395
  };
368
- const useMessageApi = model.startsWith('claude-3');
396
+ const useMessageApi = model.startsWith('claude-3') || model.startsWith('claude-sonnet') || model.startsWith('claude-opus');
369
397
  const cacheKey = JSON.stringify(useMessageApi ? messageOptions : completionOptions);
370
398
  if (data.cache) {
371
399
  const cached = cache.get(cacheKey);
@@ -385,6 +413,7 @@ export const ChatAnthropicNodeImpl = {
385
413
  apiKey: apiKey ?? '',
386
414
  signal: context.signal,
387
415
  beta: 'prompt-caching-2024-07-31',
416
+ additionalHeaders: allAdditionalHeaders,
388
417
  ...messageOptions,
389
418
  });
390
419
  // Process the response chunks and update the output
@@ -516,6 +545,7 @@ export const ChatAnthropicNodeImpl = {
516
545
  apiEndpoint,
517
546
  apiKey: apiKey ?? '',
518
547
  signal: context.signal,
548
+ additionalHeaders: allAdditionalHeaders,
519
549
  ...completionOptions,
520
550
  });
521
551
  // Process the response chunks and update the output