Merge pull request #1 from digitalocean-labs/add-serverless-inference-support

Add DO Gradient Serverless Inference Node
This commit is contained in:
Dillon LeDoux 2025-09-08 19:37:58 -05:00 committed by GitHub
commit c714a0cb0f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
19 changed files with 5765 additions and 628 deletions

View file

@ -1,4 +1,7 @@
Copyright 2022 n8n
MIT License
Copyright (c) 2022 n8n GmbH (and contributors)
Copyright (c) 2025 DigitalOcean
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in

View file

@ -1,48 +1,56 @@
![Banner image](https://user-images.githubusercontent.com/10284570/173569848-c624317f-42b1-45a6-ab09-f0ea3c247648.png)
# n8n-nodes-digitalocean-serverless-inference
# n8n-nodes-starter
This is an n8n community node for the [DigitalOcean Gradient™ AI Platform Serverless Inference API](https://gradientai-sdk.digitalocean.com/api/resources/chat/). It provides access to DigitalOcean Gradient™ AI Platform's large language models through n8n workflows.
This repo contains example nodes to help you get started building your own custom integrations for [n8n](https://n8n.io). It includes the node linter and other dependencies.
[n8n](https://n8n.io/) is a [fair-code licensed](https://docs.n8n.io/reference/license/) workflow automation platform.
To make your custom node available to the community, you must create it as an npm package, and [submit it to the npm registry](https://docs.npmjs.com/packages-and-modules/contributing-packages-to-the-registry).
[Installation](#installation)
[Credentials](#credentials)
[Compatibility](#compatibility)
[Resources](#resources)
If you would like your node to be available on n8n cloud you can also [submit your node for verification](https://docs.n8n.io/integrations/creating-nodes/deploy/submit-community-nodes/).
## Installation
## Prerequisites
Follow the [installation guide](https://docs.n8n.io/integrations/community-nodes/installation/) in the n8n community nodes documentation.
You need the following installed on your development machine:
### Quick Installation
* [git](https://git-scm.com/downloads)
* Node.js and npm. Minimum version Node 20. You can find instructions on how to install both using nvm (Node Version Manager) for Linux, Mac, and WSL [here](https://github.com/nvm-sh/nvm). For Windows users, refer to Microsoft's guide to [Install NodeJS on Windows](https://docs.microsoft.com/en-us/windows/dev-environment/javascript/nodejs-on-windows).
* Install n8n with:
```
npm install n8n -g
```
* Recommended: follow n8n's guide to [set up your development environment](https://docs.n8n.io/integrations/creating-nodes/build/node-development-environment/).
1. Go to **Settings > Community Nodes**
2. Select **Install**
3. Enter `@digitalocean/n8n-nodes-digitalocean-serverless-inference` in **Enter npm package name**
4. Agree to the risks of using community nodes
5. Select **Install**
## Using this starter
**Note:** After installation, you need to restart your n8n instance for the new node to be recognized.
These are the basic steps for working with the starter. For detailed guidance on creating and publishing nodes, refer to the [documentation](https://docs.n8n.io/integrations/creating-nodes/).
### Chat Completion
1. [Generate a new repository](https://github.com/n8n-io/n8n-nodes-starter/generate) from this template repository.
2. Clone your new repo:
```
git clone https://github.com/<your organization>/<your-repo-name>.git
```
3. Run `npm i` to install dependencies.
4. Open the project in your editor.
5. Browse the examples in `/nodes` and `/credentials`. Modify the examples, or replace them with your own nodes.
6. Update the `package.json` to match your details.
7. Run `npm run lint` to check for errors or `npm run lintfix` to automatically fix errors when possible.
8. Test your node locally. Refer to [Run your node locally](https://docs.n8n.io/integrations/creating-nodes/test/run-node-locally/) for guidance.
9. Replace this README with documentation for your node. Use the [README_TEMPLATE](README_TEMPLATE.md) to get started.
10. Update the LICENSE file to use your details.
11. [Publish](https://docs.npmjs.com/packages-and-modules/contributing-packages-to-the-registry) your package to npm.
Create chat completions using DigitalOcean Gradient™ AI Serverless Inference LLMs. The node supports:
## More information
- Multiple messages with system, user, and assistant roles
- [Multiple Models](https://docs.digitalocean.com/products/gradient-ai-platform/details/models/)
- Customizable parameters conforming to the [API Specification](https://gradientai-sdk.digitalocean.com/api/resources/chat/subresources/completions/methods/create)
Refer to our [documentation on creating nodes](https://docs.n8n.io/integrations/creating-nodes/) for detailed information on building your own nodes.
## Credentials
To use this node, you need a [Model Access Key](https://docs.digitalocean.com/products/gradient-ai-platform/how-to/use-serverless-inference/#create) from DigitalOcean Gradient™ AI Platform
## Compatibility
- Requires n8n version 1.0.0 or later
- Tested up to n8n version 1.106.3
## Resources
- [n8n community nodes documentation](https://docs.n8n.io/integrations/community-nodes/)
- [DigitalOcean Gradient™ AI Serverless Inference documentation](https://docs.digitalocean.com/products/gradient-ai-platform/how-to/use-serverless-inference/)
## Version History
### 1.0.0
- Initial usable release
## License
[MIT](https://github.com/n8n-io/n8n-nodes-starter/blob/master/LICENSE.md)
[MIT](LICENSE.md)

View file

@ -1,48 +0,0 @@
# n8n-nodes-_node-name_
This is an n8n community node. It lets you use _app/service name_ in your n8n workflows.
_App/service name_ is _one or two sentences describing the service this node integrates with_.
[n8n](https://n8n.io/) is a [fair-code licensed](https://docs.n8n.io/reference/license/) workflow automation platform.
[Installation](#installation)
[Operations](#operations)
[Credentials](#credentials) <!-- delete if no auth needed -->
[Compatibility](#compatibility)
[Usage](#usage) <!-- delete if not using this section -->
[Resources](#resources)
[Version history](#version-history) <!-- delete if not using this section -->
## Installation
Follow the [installation guide](https://docs.n8n.io/integrations/community-nodes/installation/) in the n8n community nodes documentation.
## Operations
_List the operations supported by your node._
## Credentials
_If users need to authenticate with the app/service, provide details here. You should include prerequisites (such as signing up with the service), available authentication methods, and how to set them up._
## Compatibility
_State the minimum n8n version, as well as which versions you test against. You can also include any known version incompatibility issues._
## Usage
_This is an optional section. Use it to help users with any difficult or confusing aspects of the node._
_By the time users are looking for community nodes, they probably already know n8n basics. But if you expect new users, you can link to the [Try it out](https://docs.n8n.io/try-it-out/) documentation to help them get started._
## Resources
* [n8n community nodes documentation](https://docs.n8n.io/integrations/#community-nodes)
* _Link to app/service documentation._
## Version history
_This is another optional section. If your node has multiple versions, include a short description of available versions and what changed, as well as any compatibility impact._

View file

@ -0,0 +1,35 @@
import {
IAuthenticateGeneric,
ICredentialType,
INodeProperties,
} from 'n8n-workflow';
export class DigitalOceanServerlessInference implements ICredentialType {
name = 'digitalOceanServerlessInference';
displayName = 'DigitalOcean Gradient™ AI Platform';
documentationUrl = 'https://docs.digitalocean.com/products/gradient-ai-platform/how-to/use-serverless-inference/';
properties: INodeProperties[] = [
{
displayName: 'Model Access Key',
name: 'key',
type: 'string',
default: '',
typeOptions: {
password: true,
}
},
];
// This allows the credential to be used by other parts of n8n
// stating how this credential is injected as part of the request
// An example is the Http Request node that can make generic calls
// reusing this credential
authenticate: IAuthenticateGeneric = {
type: 'generic',
properties: {
headers: {
Authorization: '={{"Bearer " + $credentials.key}}',
},
},
};
}

View file

@ -1,59 +0,0 @@
import {
IAuthenticateGeneric,
ICredentialTestRequest,
ICredentialType,
INodeProperties,
} from 'n8n-workflow';
export class ExampleCredentialsApi implements ICredentialType {
name = 'exampleCredentialsApi';
displayName = 'Example Credentials API';
documentationUrl = 'https://your-docs-url';
properties: INodeProperties[] = [
// The credentials to get from user and save encrypted.
// Properties can be defined exactly in the same way
// as node properties.
{
displayName: 'User Name',
name: 'username',
type: 'string',
default: '',
},
{
displayName: 'Password',
name: 'password',
type: 'string',
typeOptions: {
password: true,
},
default: '',
},
];
// This credential is currently not used by any node directly
// but the HTTP Request node can use it to make requests.
// The credential is also testable due to the `test` property below
authenticate: IAuthenticateGeneric = {
type: 'generic',
properties: {
auth: {
username: '={{ $credentials.username }}',
password: '={{ $credentials.password }}',
},
qs: {
// Send this as part of the query string
n8n: 'rocks',
},
},
};
// The block below tells how this credential can be tested
test: ICredentialTestRequest = {
request: {
baseURL: 'https://example.com/',
url: '',
},
};
}

View file

@ -1,50 +0,0 @@
import {
IAuthenticateGeneric,
ICredentialTestRequest,
ICredentialType,
INodeProperties,
} from 'n8n-workflow';
export class HttpBinApi implements ICredentialType {
name = 'httpbinApi';
displayName = 'HttpBin API';
documentationUrl = 'https://your-docs-url';
properties: INodeProperties[] = [
{
displayName: 'Token',
name: 'token',
type: 'string',
default: '',
typeOptions: {
password: true,
}
},
{
displayName: 'Domain',
name: 'domain',
type: 'string',
default: 'https://httpbin.org',
},
];
// This allows the credential to be used by other parts of n8n
// stating how this credential is injected as part of the request
// An example is the Http Request node that can make generic calls
// reusing this credential
authenticate: IAuthenticateGeneric = {
type: 'generic',
properties: {
headers: {
Authorization: '={{"Bearer " + $credentials.token}}',
},
},
};
// The block below tells how this credential can be tested
test: ICredentialTestRequest = {
request: {
baseURL: '={{$credentials?.domain}}',
url: '/bearer',
},
};
}

View file

@ -1,77 +0,0 @@
import type {
IExecuteFunctions,
INodeExecutionData,
INodeType,
INodeTypeDescription,
} from 'n8n-workflow';
import { NodeConnectionType, NodeOperationError } from 'n8n-workflow';
export class ExampleNode implements INodeType {
description: INodeTypeDescription = {
displayName: 'Example Node',
name: 'exampleNode',
group: ['transform'],
version: 1,
description: 'Basic Example Node',
defaults: {
name: 'Example Node',
},
inputs: [NodeConnectionType.Main],
outputs: [NodeConnectionType.Main],
usableAsTool: true,
properties: [
// Node properties which the user gets displayed and
// can change on the node.
{
displayName: 'My String',
name: 'myString',
type: 'string',
default: '',
placeholder: 'Placeholder value',
description: 'The description text',
},
],
};
// The function below is responsible for actually doing whatever this node
// is supposed to do. In this case, we're just appending the `myString` property
// with whatever the user has entered.
// You can make async calls and use `await`.
async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
const items = this.getInputData();
let item: INodeExecutionData;
let myString: string;
// Iterates over all input items and add the key "myString" with the
// value the parameter "myString" resolves to.
// (This could be a different value for each item in case it contains an expression)
for (let itemIndex = 0; itemIndex < items.length; itemIndex++) {
try {
myString = this.getNodeParameter('myString', itemIndex, '') as string;
item = items[itemIndex];
item.json.myString = myString;
} catch (error) {
// This node should never fail but we want to showcase how
// to handle errors.
if (this.continueOnFail()) {
items.push({ json: this.getInputData(itemIndex)[0].json, error, pairedItem: itemIndex });
} else {
// Adding `itemIndex` allows other workflows to handle this error
if (error.context) {
// If the error thrown already contains the context property,
// only append the itemIndex
error.context.itemIndex = itemIndex;
throw error;
}
throw new NodeOperationError(this.getNode(), error, {
itemIndex,
});
}
}
}
return [items];
}
}

View file

@ -1,18 +0,0 @@
{
"node": "n8n-nodes-base.httpbin",
"nodeVersion": "1.0",
"codexVersion": "1.0",
"categories": ["Development", "Developer Tools"],
"resources": {
"credentialDocumentation": [
{
"url": "http://httpbin.org/#/Auth/get_bearer"
}
],
"primaryDocumentation": [
{
"url": "http://httpbin.org/"
}
]
}
}

View file

@ -1,63 +0,0 @@
import { INodeType, INodeTypeDescription, NodeConnectionType } from 'n8n-workflow';
import { httpVerbFields, httpVerbOperations } from './HttpVerbDescription';
export class HttpBin implements INodeType {
description: INodeTypeDescription = {
displayName: 'HttpBin',
name: 'httpBin',
icon: { light: 'file:httpbin.svg', dark: 'file:httpbin.svg' },
group: ['transform'],
version: 1,
subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}',
description: 'Interact with HttpBin API',
defaults: {
name: 'HttpBin',
},
inputs: [NodeConnectionType.Main],
outputs: [NodeConnectionType.Main],
usableAsTool: true,
credentials: [
{
name: 'httpbinApi',
required: false,
},
],
requestDefaults: {
baseURL: 'https://httpbin.org',
url: '',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
},
/**
* In the properties array we have two mandatory options objects required
*
* [Resource & Operation]
*
* https://docs.n8n.io/integrations/creating-nodes/code/create-first-node/#resources-and-operations
*
* In our example, the operations are separated into their own file (HTTPVerbDescription.ts)
* to keep this class easy to read.
*
*/
properties: [
{
displayName: 'Resource',
name: 'resource',
type: 'options',
noDataExpression: true,
options: [
{
name: 'HTTP Verb',
value: 'httpVerb',
},
],
default: 'httpVerb',
},
...httpVerbOperations,
...httpVerbFields,
],
};
}

View file

@ -1,250 +0,0 @@
import { INodeProperties } from 'n8n-workflow';
// When the resource `httpVerb` is selected, this `operation` parameter will be shown.
export const httpVerbOperations: INodeProperties[] = [
{
displayName: 'Operation',
name: 'operation',
type: 'options',
noDataExpression: true,
displayOptions: {
show: {
resource: ['httpVerb'],
},
},
options: [
{
name: 'GET',
value: 'get',
description: 'Perform a GET request',
action: 'Perform a GET request',
routing: {
request: {
method: 'GET',
url: '/get',
},
},
},
{
name: 'DELETE',
value: 'delete',
description: 'Perform a DELETE request',
action: 'Perform a DELETE request',
routing: {
request: {
method: 'DELETE',
url: '/delete',
},
},
},
],
default: 'get',
},
];
// Here we define what to show when the `get` operation is selected.
// We do that by adding `operation: ["get"]` to `displayOptions.show`
const getOperation: INodeProperties[] = [
{
displayName: 'Type of Data',
name: 'typeofData',
default: 'queryParameter',
description: 'Select type of data to send [Query Parameters]',
displayOptions: {
show: {
resource: ['httpVerb'],
operation: ['get'],
},
},
type: 'options',
options: [
{
name: 'Query',
value: 'queryParameter',
},
],
required: true,
},
{
displayName: 'Query Parameters',
name: 'arguments',
default: {},
description: "The request's query parameters",
displayOptions: {
show: {
resource: ['httpVerb'],
operation: ['get'],
},
},
options: [
{
name: 'keyvalue',
displayName: 'Key:Value',
values: [
{
displayName: 'Key',
name: 'key',
type: 'string',
default: '',
required: true,
description: 'Key of query parameter',
},
{
displayName: 'Value',
name: 'value',
type: 'string',
default: '',
routing: {
send: {
property: '={{$parent.key}}',
type: 'query',
},
},
required: true,
description: 'Value of query parameter',
},
],
},
],
type: 'fixedCollection',
typeOptions: {
multipleValues: true,
},
},
];
// Here we define what to show when the DELETE Operation is selected.
// We do that by adding `operation: ["delete"]` to `displayOptions.show`
const deleteOperation: INodeProperties[] = [
{
displayName: 'Type of Data',
name: 'typeofData',
default: 'queryParameter',
description: 'Select type of data to send [Query Parameter Arguments, JSON-Body]',
displayOptions: {
show: {
resource: ['httpVerb'],
operation: ['delete'],
},
},
options: [
{
name: 'Query',
value: 'queryParameter',
},
{
name: 'JSON',
value: 'jsonData',
},
],
required: true,
type: 'options',
},
{
displayName: 'Query Parameters',
name: 'arguments',
default: {},
description: "The request's query parameters",
displayOptions: {
show: {
resource: ['httpVerb'],
operation: ['delete'],
typeofData: ['queryParameter'],
},
},
options: [
{
name: 'keyvalue',
displayName: 'Key:Value',
values: [
{
displayName: 'Key',
name: 'key',
type: 'string',
default: '',
required: true,
description: 'Key of query parameter',
},
{
displayName: 'Value',
name: 'value',
type: 'string',
default: '',
routing: {
send: {
property: '={{$parent.key}}',
type: 'query',
},
},
required: true,
description: 'Value of query parameter',
},
],
},
],
type: 'fixedCollection',
typeOptions: {
multipleValues: true,
},
},
{
displayName: 'JSON Object',
name: 'arguments',
default: {},
description: "The request's JSON properties",
displayOptions: {
show: {
resource: ['httpVerb'],
operation: ['delete'],
typeofData: ['jsonData'],
},
},
options: [
{
name: 'keyvalue',
displayName: 'Key:Value',
values: [
{
displayName: 'Key',
name: 'key',
type: 'string',
default: '',
required: true,
description: 'Key of JSON property',
},
{
displayName: 'Value',
name: 'value',
type: 'string',
default: '',
routing: {
send: {
property: '={{$parent.key}}',
type: 'body',
},
},
required: true,
description: 'Value of JSON property',
},
],
},
],
type: 'fixedCollection',
typeOptions: {
multipleValues: true,
},
},
];
export const httpVerbFields: INodeProperties[] = [
/* -------------------------------------------------------------------------- */
/* httpVerb:get */
/* -------------------------------------------------------------------------- */
...getOperation,
/* -------------------------------------------------------------------------- */
/* httpVerb:delete */
/* -------------------------------------------------------------------------- */
...deleteOperation,
];

View file

@ -1,18 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="32px" height="32px" viewBox="0 0 32 32" enable-background="new 0 0 32 32" xml:space="preserve"> <image id="image0" width="32" height="32" x="0" y="0"
href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAQAAADZc7J/AAAABGdBTUEAALGPC/xhBQAAACBjSFJN
AAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAAAmJLR0QA/4ePzL8AAAAHdElN
RQfmBg4UAC/TqOZZAAACA0lEQVRIx5XVv09TURwF8M+jFHDSyRkGFhPAEfyRdDHi5uriXyDoYgKT
MJDWzUT/Ahf/AiOEpajEgCESmpiYmDCxGowDTYE+h76+vte+15Zzk753b7733HNO772PbEw7ECba
genswtEcgl0/PHARV72066YrIDSZ6k8KBym4741r0XsB284TdUX8chn1zrzwJUmw4KFXPqjFE0Y0
u5YKEhpmfLZuy7f2wLKGI8WhDRYdaVhurdTCidmU5P44N+skaaGQH1IfFFrOYMotT932zNgQExve
OfTeT8dtBceO3TFlOyopY7UPxV+/fWyn3Y0xrFhJjZWFXhs12pKdRO9ObGSuyB8Xbd9JjMjDc6HQ
IcrKqAiVe8vyCEJPrGBWxZYqqtZt9RbmHabAvAAVdVUlJTvWshbMt0AYn40OmlchSKOePTyYIMQn
rb8yI8TsDCrRs4od7Jv3KOoPGWKboBqp2LN3FQvdO7EPshSsRSTXrSop2cSiiUGkG/bj2JqaQiHW
4nv50mFcu28j30KQarAnEPhuzvwwGYQ975vx7+JwGXTjTIAzoYlhCArR5d0KkfauqJAVY6+FG5hD
OS6veqyCuSiTAQT/jKmlQtyxIBCoZV28HQvN6LuQvJFC4xjvibfYOZUdUXd9taTWJbOubiIVXmjG
W/fs9qpZcpr6pOe1U0udSf8BR7ef4yxyOskAAAAldEVYdGRhdGU6Y3JlYXRlADIwMjItMDYtMTRU
MTc6MDA6NDcrMDM6MDBfo1sRAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDIyLTA2LTE0VDE3OjAwOjQ3
KzAzOjAwLv7jrQAAAABJRU5ErkJggg==" />
</svg>

Before

Width:  |  Height:  |  Size: 1.5 KiB

View file

@ -0,0 +1,11 @@
<svg width="196" height="196" viewBox="0 0 196 196" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M47.3408 148.115C47.3574 148.023 47.4746 148.494 47.6016 149.161C49.6404 159.873 51.6407 164.16 55.9443 167.039C58.8113 168.957 63.0267 170.284 70.8467 171.731C71.2048 171.798 71.1538 171.814 69.7998 172.078C62.5012 173.499 58.7081 174.733 55.9414 176.587C54.3106 177.68 53.2536 178.736 52.1602 180.366C50.3281 183.097 49.1049 186.808 47.7324 193.798C47.3949 195.517 47.3662 195.613 47.3008 195.262C45.8416 187.417 44.5212 183.229 42.6055 180.369C39.8212 176.212 35.7984 174.254 26.0156 172.293C24.7436 172.038 23.6792 171.809 23.6523 171.803C23.6685 171.799 24.1963 171.677 24.8643 171.553C26.4116 171.266 29.1262 170.673 30.6436 170.291C34.1906 169.398 36.8655 168.325 38.9072 166.975C40.1426 166.158 41.7017 164.606 42.5146 163.385C44.5461 160.332 45.927 155.965 47.3408 148.115ZM0 97.627C0.000131746 33.8237 61.8226 -16.0588 128.758 4.82227C158.042 13.8708 181.283 37.3038 190.58 66.5371C211.497 133.589 161.76 195.305 97.8467 195.305V157.486C138.054 157.486 168.965 117.812 153.626 75.5859C148.048 60.0411 135.497 47.5126 119.926 41.9443C77.6265 26.6316 37.8839 57.721 37.8838 97.627H0ZM77.5518 101.673C77.5736 101.551 77.729 102.173 77.8965 103.053C80.5859 117.183 83.2245 122.838 88.9014 126.636C92.6833 129.166 98.2438 130.917 108.56 132.826C109.032 132.914 108.965 132.935 107.179 133.283C97.5509 135.158 92.547 136.786 88.8975 139.231C86.7464 140.673 85.3525 142.067 83.9102 144.217C81.4935 147.819 79.8798 152.714 78.0693 161.935C77.6242 164.202 77.5862 164.33 77.5 163.866C75.5751 153.518 73.8338 147.993 71.3066 144.22C67.6338 138.736 62.3266 136.153 49.4219 133.566C47.7252 133.226 46.3238 132.936 46.3037 132.92C46.2885 132.905 47.0083 132.756 47.9033 132.59C49.9446 132.211 53.5258 131.43 55.5273 130.927C60.2063 129.749 63.7344 128.333 66.4277 126.552C68.0575 125.474 70.1141 123.427 71.1865 121.815C73.8663 117.788 75.6867 112.027 77.5518 101.673ZM21.5625 121.78C21.5745 121.713 21.6597 122.055 21.752 122.54C23.2335 130.324 24.6872 133.439 27.8145 135.531C29.8977 136.925 32.9605 137.889 38.6426 138.94C38.9031 138.989 38.866 139.001 37.8818 139.192C32.5785 140.225 29.8229 141.123 27.8125 142.47C26.6276 143.264 25.8589 144.031 25.0645 145.215C23.7332 147.199 22.844 149.896 21.8467 154.976C21.6015 156.225 21.5807 156.295 21.5332 156.039C20.4729 150.339 19.514 147.296 18.1221 145.218C16.0989 142.197 13.1751 140.773 6.06641 139.349C5.13025 139.161 4.35709 139.001 4.34863 138.992C4.34871 138.982 4.74259 138.901 5.23047 138.811C6.35498 138.602 8.32724 138.172 9.42969 137.895C12.0071 137.246 13.9509 136.466 15.4346 135.484C16.3322 134.891 17.465 133.764 18.0557 132.876C19.5318 130.658 20.5351 127.484 21.5625 121.78Z" fill="#000C79"/>
<path d="M47.3408 148.115C47.3574 148.023 47.4746 148.494 47.6016 149.161C49.6404 159.873 51.6407 164.16 55.9443 167.039C58.8113 168.957 63.0267 170.284 70.8467 171.731C71.2048 171.798 71.1538 171.814 69.7998 172.078C62.5012 173.499 58.7081 174.733 55.9414 176.587C54.3106 177.68 53.2536 178.736 52.1602 180.366C50.3281 183.097 49.1049 186.808 47.7324 193.798C47.3949 195.517 47.3662 195.613 47.3008 195.262C45.8416 187.417 44.5212 183.229 42.6055 180.369C39.8212 176.212 35.7984 174.254 26.0156 172.293C24.7436 172.038 23.6792 171.809 23.6523 171.803C23.6685 171.799 24.1963 171.677 24.8643 171.553C26.4116 171.266 29.1262 170.673 30.6436 170.291C34.1906 169.398 36.8655 168.325 38.9072 166.975C40.1426 166.158 41.7017 164.606 42.5146 163.385C44.5461 160.332 45.927 155.965 47.3408 148.115ZM0 97.627C0.000131746 33.8237 61.8226 -16.0588 128.758 4.82227C158.042 13.8708 181.283 37.3038 190.58 66.5371C211.497 133.589 161.76 195.305 97.8467 195.305V157.486C138.054 157.486 168.965 117.812 153.626 75.5859C148.048 60.0411 135.497 47.5126 119.926 41.9443C77.6265 26.6316 37.8839 57.721 37.8838 97.627H0ZM77.5518 101.673C77.5736 101.551 77.729 102.173 77.8965 103.053C80.5859 117.183 83.2245 122.838 88.9014 126.636C92.6833 129.166 98.2438 130.917 108.56 132.826C109.032 132.914 108.965 132.935 107.179 133.283C97.5509 135.158 92.547 136.786 88.8975 139.231C86.7464 140.673 85.3525 142.067 83.9102 144.217C81.4935 147.819 79.8798 152.714 78.0693 161.935C77.6242 164.202 77.5862 164.33 77.5 163.866C75.5751 153.518 73.8338 147.993 71.3066 144.22C67.6338 138.736 62.3266 136.153 49.4219 133.566C47.7252 133.226 46.3238 132.936 46.3037 132.92C46.2885 132.905 47.0083 132.756 47.9033 132.59C49.9446 132.211 53.5258 131.43 55.5273 130.927C60.2063 129.749 63.7344 128.333 66.4277 126.552C68.0575 125.474 70.1141 123.427 71.1865 121.815C73.8663 117.788 75.6867 112.027 77.5518 101.673ZM21.5625 121.78C21.5745 121.713 21.6597 122.055 21.752 122.54C23.2335 130.324 24.6872 133.439 27.8145 135.531C29.8977 136.925 32.9605 137.889 38.6426 138.94C38.9031 138.989 38.866 139.001 37.8818 139.192C32.5785 140.225 29.8229 141.123 27.8125 142.47C26.6276 143.264 25.8589 144.031 25.0645 145.215C23.7332 147.199 22.844 149.896 21.8467 154.976C21.6015 156.225 21.5807 156.295 21.5332 156.039C20.4729 150.339 19.514 147.296 18.1221 145.218C16.0989 142.197 13.1751 140.773 6.06641 139.349C5.13025 139.161 4.35709 139.001 4.34863 138.992C4.34871 138.982 4.74259 138.901 5.23047 138.811C6.35498 138.602 8.32724 138.172 9.42969 137.895C12.0071 137.246 13.9509 136.466 15.4346 135.484C16.3322 134.891 17.465 133.764 18.0557 132.876C19.5318 130.658 20.5351 127.484 21.5625 121.78Z" fill="url(#paint0_radial_7847_19772)"/>
<defs>
<radialGradient id="paint0_radial_7847_19772" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(-64.5042 109.279) scale(402.467 106.592)">
<stop stop-color="#FF9FEA"/>
<stop offset="0.601298" stop-color="#0069FF"/>
<stop offset="1" stop-color="#000C79" stop-opacity="0"/>
</radialGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 5.7 KiB

View file

@ -0,0 +1,3 @@
<svg width="196" height="196" viewBox="0 0 196 196" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M47.3408 148.115C47.3574 148.023 47.4746 148.494 47.6016 149.161C49.6404 159.873 51.6407 164.16 55.9443 167.039C58.8113 168.957 63.0267 170.284 70.8467 171.731C71.2048 171.798 71.1538 171.814 69.7998 172.078C62.5012 173.499 58.7081 174.733 55.9414 176.587C54.3106 177.68 53.2536 178.736 52.1602 180.366C50.3281 183.097 49.1049 186.808 47.7324 193.798C47.3949 195.517 47.3662 195.613 47.3008 195.262C45.8416 187.417 44.5212 183.229 42.6055 180.369C39.8212 176.212 35.7984 174.254 26.0156 172.293C24.7436 172.038 23.6792 171.809 23.6523 171.803C23.6685 171.799 24.1963 171.677 24.8643 171.553C26.4116 171.266 29.1262 170.673 30.6436 170.291C34.1906 169.398 36.8655 168.325 38.9072 166.975C40.1426 166.158 41.7017 164.606 42.5146 163.385C44.5461 160.332 45.927 155.965 47.3408 148.115ZM0 97.627C0.000131746 33.8237 61.8226 -16.0588 128.758 4.82227C158.042 13.8708 181.283 37.3038 190.58 66.5371C211.497 133.589 161.76 195.305 97.8467 195.305V157.486C138.054 157.486 168.965 117.812 153.626 75.5859C148.048 60.0411 135.497 47.5126 119.926 41.9443C77.6265 26.6316 37.8839 57.721 37.8838 97.627H0ZM77.5518 101.673C77.5736 101.551 77.729 102.173 77.8965 103.053C80.5859 117.183 83.2245 122.838 88.9014 126.636C92.6833 129.166 98.2438 130.917 108.56 132.826C109.032 132.914 108.965 132.935 107.179 133.283C97.5509 135.158 92.547 136.786 88.8975 139.231C86.7464 140.673 85.3525 142.067 83.9102 144.217C81.4935 147.819 79.8798 152.714 78.0693 161.935C77.6242 164.202 77.5862 164.33 77.5 163.866C75.5751 153.518 73.8338 147.993 71.3066 144.22C67.6339 138.736 62.3266 136.153 49.4219 133.566C47.7252 133.226 46.3238 132.936 46.3037 132.92C46.2885 132.905 47.0083 132.756 47.9033 132.59C49.9446 132.211 53.5258 131.43 55.5273 130.927C60.2063 129.749 63.7344 128.333 66.4277 126.552C68.0575 125.474 70.1141 123.427 71.1865 121.815C73.8663 117.788 75.6867 112.027 77.5518 101.673ZM21.5625 121.78C21.5745 121.713 21.6597 122.055 21.752 122.54C23.2335 130.324 24.6872 133.439 27.8145 135.531C29.8977 136.925 32.9605 137.889 38.6426 138.94C38.9031 138.989 38.866 139.001 37.8818 139.192C32.5785 140.225 29.8229 141.123 27.8125 142.47C26.6276 143.264 25.8589 144.031 25.0645 145.215C23.7332 147.199 22.844 149.896 21.8467 154.976C21.6015 156.225 21.5807 156.295 21.5332 156.039C20.4729 150.339 19.514 147.296 18.1221 145.218C16.0989 142.197 13.1751 140.773 6.06641 139.349C5.13025 139.161 4.35709 139.001 4.34863 138.992C4.34871 138.982 4.74259 138.901 5.23047 138.811C6.35498 138.602 8.32724 138.172 9.42969 137.895C12.0071 137.246 13.9509 136.466 15.4346 135.484C16.3322 134.891 17.465 133.764 18.0557 132.876C19.5318 130.658 20.5351 127.484 21.5625 121.78Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 2.7 KiB

View file

@ -0,0 +1,18 @@
import type {
IExecuteSingleFunctions,
IN8nHttpFullResponse,
INodeExecutionData,
JsonObject,
} from 'n8n-workflow';
import { NodeApiError } from 'n8n-workflow';
export async function sendErrorPostReceive(
this: IExecuteSingleFunctions,
data: INodeExecutionData[],
response: IN8nHttpFullResponse,
): Promise<INodeExecutionData[]> {
if (String(response.statusCode).startsWith('4') || String(response.statusCode).startsWith('5')) {
throw new NodeApiError(this.getNode(), response as unknown as JsonObject);
}
return data;
}

View file

@ -0,0 +1,19 @@
{
"node": "n8n-nodes-base.ServerlessInference",
"nodeVersion": "1.0",
"codexVersion": "1.0",
"categories": ["Utility"],
"resources": {
"credentialDocumentation": [
{
"url": "https://docs.digitalocean.com/products/gradient-ai-platform/how-to/use-serverless-inference/#create"
}
],
"primaryDocumentation": [
{
"url": "https://docs.digitalocean.com/products/gradient-ai-platform/how-to/use-serverless-inference/"
}
]
},
"alias": ["DoServerlessInference"]
}

View file

@ -0,0 +1,55 @@
import { INodeType, INodeTypeDescription, NodeConnectionType } from 'n8n-workflow';
import { textFields, textOperations } from './TextDescription';
const baseURL = 'https://inference.do-ai.run/v1';
export class ServerlessInference implements INodeType {
description: INodeTypeDescription = {
displayName: 'DigitalOcean Gradient™ AI Serverless Inference',
documentationUrl: 'https://gradientai-sdk.digitalocean.com/api/resources/chat/subresources/completions/methods/create',
name: 'digitalOceanGradientServerlessInference',
icon: { light: 'file:DO-Gradient-AI-Agentic-Cloud-logo.svg', dark: 'file:DO-gradient-ai-logo-white.svg' },
group: ['transform'],
version: [1, 1.0],
subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}',
description: 'Consume DigitalOcean Gradient™ AI Serverless Inference',
defaults: {
name: 'DigitalOcean Gradient™ AI Serverless Inference',
},
inputs: [NodeConnectionType.Main],
outputs: [NodeConnectionType.Main],
credentials: [
{
name: 'digitalOceanServerlessInference',
required: true,
},
],
requestDefaults: {
ignoreHttpStatusErrors: true,
baseURL,
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
'User-Agent': 'Gradient/n8n/1.0.0',
},
},
properties: [
{
displayName: 'Resource',
name: 'resource',
type: 'options',
noDataExpression: true,
options: [
{
name: 'Text',
value: 'text',
},
],
default: 'text',
},
...textOperations,
...textFields,
],
};
}

View file

@ -0,0 +1,623 @@
import type { INodeProperties } from 'n8n-workflow';
import { sendErrorPostReceive } from './GenericFunctions';
export const textOperations: INodeProperties[] = [
{
displayName: 'Operation',
name: 'operation',
type: 'options',
noDataExpression: true,
displayOptions: {
show: {
resource: ['text'],
},
},
options: [
{
name: 'Complete',
value: 'complete',
action: 'Create a Text Completion',
description: 'Create one or more completions for a given text',
routing: {
request: {
method: 'POST',
url: '/chat/completions',
},
output: { postReceive: [sendErrorPostReceive] },
},
},
],
default: 'complete',
},
];
const completeOperations: INodeProperties[] = [
{
displayName: 'Model',
name: 'model',
type: 'options',
description:
'The model which will generate the completion. <a href="https://docs.digitalocean.com/products/gradient-ai-platform/details/models/">Learn more</a>',
displayOptions: {
show: {
operation: ['complete'],
resource: ['text'],
},
},
typeOptions: {
loadOptions: {
routing: {
request: {
method: 'GET',
url: '/models',
},
output: {
postReceive: [
{
type: 'rootProperty',
properties: {
property: 'data',
},
},
{
type: 'setKeyValue',
properties: {
name: '={{$responseItem.id}}',
value: '={{$responseItem.id}}',
},
},
{
type: 'sort',
properties: {
key: 'name',
},
},
],
},
},
},
},
routing: {
send: {
type: 'body',
property: 'model',
},
},
default: 'openai-gpt-oss-120b',
},
{
displayName: 'Input Type',
name: 'inputType',
type: 'options',
description: 'Choose how to provide the conversation input',
displayOptions: {
show: {
resource: ['text'],
operation: ['complete'],
},
},
options: [
{
name: 'Simple Prompt',
value: 'prompt',
description: 'Provide a simple text prompt',
},
{
name: 'Chat Messages',
value: 'messages',
description: 'Provide a full conversation with roles',
},
],
default: 'prompt',
},
{
displayName: 'Prompt',
name: 'prompt',
type: 'string',
description: 'The prompt to generate completion(s) for',
placeholder: 'e.g. Say this is a test',
displayOptions: {
show: {
resource: ['text'],
operation: ['complete'],
inputType: ['prompt'],
},
},
default: '',
typeOptions: {
rows: 2,
},
routing: {
send: {
type: 'body',
property: 'messages',
value: '={{[{"role": "user", "content": $parameter.prompt}]}}',
},
},
},
{
displayName: 'Messages',
name: 'messages',
type: 'fixedCollection',
description: 'The messages in the conversation',
displayOptions: {
show: {
resource: ['text'],
operation: ['complete'],
inputType: ['messages'],
},
},
default: {
messagesList: [
{
role: 'user',
content: '',
},
],
},
typeOptions: {
multipleValues: true,
},
options: [
{
name: 'messagesList',
displayName: 'Message',
values: [
{
displayName: 'Role',
name: 'role',
type: 'options',
description: 'The role of the message author',
options: [
{
name: 'System',
value: 'system',
description: 'A system message that sets the behavior of the assistant',
},
{
name: 'User',
value: 'user',
description: 'A message from the user',
},
{
name: 'Assistant',
value: 'assistant',
description: 'A message from the assistant',
},
],
default: 'user',
},
{
displayName: 'Content',
name: 'content',
type: 'string',
description: 'The content of the message',
default: '',
typeOptions: {
rows: 2,
},
},
],
},
],
routing: {
send: {
type: 'body',
property: 'messages',
value: '={{$parameter.messages.messagesList}}',
},
},
},
];
const sharedOperations: INodeProperties[] = [
{
displayName: 'Options',
name: 'options',
placeholder: 'Add option',
description: 'Additional options to add',
type: 'collection',
default: {
maxTokens: 2048,
temperature: 0.7,
},
displayOptions: {
show: {
operation: ['complete'],
resource: ['text'],
},
},
options: [
{
displayName: 'Maximum Number of Tokens',
name: 'maxTokens',
default: 2048,
description:
'The maximum number of tokens to generate in the completion. Most models have a context length of 2048 tokens (except for the newest models, which support 32,768).',
type: 'number',
displayOptions: {
show: {
'/operation': ['complete'],
},
},
typeOptions: {
maxValue: 131072,
minValue: 1,
},
routing: {
send: {
type: 'body',
property: 'max_tokens',
},
},
},
{
displayName: 'Max Completion Tokens',
name: 'maxCompletionTokens',
description:
'The maximum number of tokens that can be generated in the chat completion. This value can be used to control costs for text generated via API.',
type: 'number',
default: undefined,
typeOptions: {
minValue: 1,
},
routing: {
send: {
type: 'body',
property: 'max_completion_tokens',
},
},
},
{
displayName: 'Temperature',
name: 'temperature',
default: 0.7,
typeOptions: { maxValue: 2, minValue: 0, numberPrecision: 2 },
description:
'Controls randomness: Lowering results in less random completions. As the temperature approaches zero, the model will become deterministic and repetitive.',
type: 'number',
routing: {
send: {
type: 'body',
property: 'temperature',
},
},
},
{
displayName: 'Top P',
name: 'topP',
description:
'An alternative to sampling with temperature, called nucleus sampling, where the model considers the results of the tokens with top_p probability mass.',
type: 'number',
default: undefined,
typeOptions: {
maxValue: 1,
minValue: 0,
numberPrecision: 3,
},
routing: {
send: {
type: 'body',
property: 'top_p',
},
},
},
{
displayName: 'Number of Completions',
name: 'n',
description: 'How many chat completion choices to generate for each input message',
type: 'number',
typeOptions: {
minValue: 1,
maxValue: 128,
},
default: 1,
routing: {
send: {
type: 'body',
property: 'n',
},
},
},
{
displayName: 'Stream',
name: 'stream',
description: 'If set, partial message deltas will be sent, like in ChatGPT',
type: 'boolean',
default: false,
routing: {
send: {
type: 'body',
property: 'stream',
},
},
},
{
displayName: 'Stream Options',
name: 'streamOptions',
description: 'Options for streaming response',
type: 'collection',
displayOptions: {
show: {
stream: [true],
},
},
default: {},
options: [
{
displayName: 'Include Usage',
name: 'includeUsage',
description: 'If set, an additional chunk will be streamed before the data: [DONE] message',
type: 'boolean',
default: false,
},
],
routing: {
send: {
type: 'body',
property: 'stream_options',
value: '={{$parameter.streamOptions.includeUsage ? {"include_usage": $parameter.streamOptions.includeUsage} : undefined}}',
},
},
},
{
displayName: 'Stop Sequences',
name: 'stop',
description: 'Up to 4 sequences where the API will stop generating further tokens',
type: 'string',
default: '',
placeholder: 'e.g. \\n, Human:, AI:',
routing: {
send: {
type: 'body',
property: 'stop',
value: '={{$parameter.stop ? $parameter.stop.split(",").map(s => s.trim()) : undefined}}',
},
},
},
{
displayName: 'Presence Penalty',
name: 'presencePenalty',
description:
'Number between -2.0 and 2.0. Positive values penalize new tokens based on whether they appear in the text so far',
type: 'number',
default: undefined,
typeOptions: {
maxValue: 2,
minValue: -2,
numberPrecision: 2,
},
routing: {
send: {
type: 'body',
property: 'presence_penalty',
},
},
},
{
displayName: 'Frequency Penalty',
name: 'frequencyPenalty',
description:
'Number between -2.0 and 2.0. Positive values penalize new tokens based on their existing frequency in the text so far',
type: 'number',
default: undefined,
typeOptions: {
maxValue: 2,
minValue: -2,
numberPrecision: 2,
},
routing: {
send: {
type: 'body',
property: 'frequency_penalty',
},
},
},
{
displayName: 'Logprobs',
name: 'logprobs',
description: 'Whether to return log probabilities of the output tokens',
type: 'boolean',
default: false,
routing: {
send: {
type: 'body',
property: 'logprobs',
},
},
},
{
displayName: 'Top Logprobs',
name: 'topLogprobs',
description: 'An integer between 0 and 20 specifying the number of most likely tokens to return at each token position',
type: 'number',
default: undefined,
displayOptions: {
show: {
logprobs: [true],
},
},
typeOptions: {
minValue: 0,
maxValue: 20,
},
routing: {
send: {
type: 'body',
property: 'top_logprobs',
},
},
},
{
displayName: 'User Identifier',
name: 'user',
description: 'A unique identifier representing your end-user, which can help monitor and detect abuse',
type: 'string',
default: '',
routing: {
send: {
type: 'body',
property: 'user',
},
},
},
{
displayName: 'Logit Bias',
name: 'logitBias',
description: 'Modify the likelihood of specified tokens appearing in the completion (JSON object mapping token IDs to bias values)',
type: 'string',
default: '',
placeholder: '{"50256": -100}',
routing: {
send: {
type: 'body',
property: 'logit_bias',
value: '={{$parameter.logitBias ? JSON.parse($parameter.logitBias) : undefined}}',
},
},
},
{
displayName: 'Metadata',
name: 'metadata',
description: 'Developer-defined metadata to attach to the completion (JSON object)',
type: 'string',
default: '',
placeholder: '{"purpose": "testing"}',
routing: {
send: {
type: 'body',
property: 'metadata',
value: '={{$parameter.metadata ? JSON.parse($parameter.metadata) : undefined}}',
},
},
},
{
displayName: 'Tools',
name: 'tools',
description: 'A list of tools the model may call',
type: 'fixedCollection',
typeOptions: {
multipleValues: true,
},
default: {},
options: [
{
name: 'toolsList',
displayName: 'Tool',
values: [
{
displayName: 'Type',
name: 'type',
type: 'options',
options: [
{
name: 'Function',
value: 'function',
},
],
default: 'function',
},
{
displayName: 'Function Name',
name: 'functionName',
type: 'string',
default: '',
description: 'The name of the function to be called',
},
{
displayName: 'Function Description',
name: 'functionDescription',
type: 'string',
default: '',
description: 'A description of what the function does',
},
{
displayName: 'Function Parameters',
name: 'functionParameters',
type: 'string',
description: 'The parameters the function accepts, described as a JSON Schema object',
placeholder: '{"type": "object", "properties": {"location": {"type": "string"}}}',
default: '',
},
],
},
],
routing: {
send: {
type: 'body',
property: 'tools',
value: '={{$parameter.tools && $parameter.tools.toolsList && $parameter.tools.toolsList.length > 0 ? $parameter.tools.toolsList.map(tool => ({"type": tool.type, "function": {"name": tool.functionName, "description": tool.functionDescription, "parameters": tool.functionParameters ? JSON.parse(tool.functionParameters) : {}}})) : undefined}}',
},
},
},
{
displayName: 'Tool Choice',
name: 'toolChoice',
description: 'Controls which (if any) tool is called by the model',
type: 'options',
options: [
{
name: 'Auto',
value: 'auto',
description: 'The model can pick between generating a message or calling one or more tools',
},
{
name: 'None',
value: 'none',
description: 'The model will not call any tool and instead generates a message',
},
{
name: 'Required',
value: 'required',
description: 'The model must call one or more tools',
},
{
name: 'Function',
value: 'function',
description: 'Specifies a particular tool via {"type": "function", "function": {"name": "my_function"}}',
},
],
default: 'auto',
routing: {
send: {
type: 'body',
property: 'tool_choice',
},
},
},
{
displayName: 'Tool Choice Function Name',
name: 'toolChoiceFunctionName',
description: 'The name of the function to call when tool choice is set to function',
type: 'string',
default: '',
displayOptions: {
show: {
toolChoice: ['function'],
},
},
routing: {
send: {
type: 'body',
property: 'tool_choice',
value: '={{"type": "function", "function": {"name": $parameter.toolChoiceFunctionName}}}',
},
},
},
],
},
];
export const textFields: INodeProperties[] = [
/* -------------------------------------------------------------------------- */
/* text:complete */
/* -------------------------------------------------------------------------- */
...completeOperations,
/* -------------------------------------------------------------------------- */
/* text:ALL */
/* -------------------------------------------------------------------------- */
...sharedOperations,
];

4947
package-lock.json generated Normal file

File diff suppressed because it is too large Load diff

View file

@ -1,19 +1,19 @@
{
"name": "n8n-nodes-<...>",
"version": "0.1.0",
"description": "",
"name": "n8n-node-digitalocean-gradient-serverless-inference",
"version": "1.0.0",
"description": "This is an n8n community node for the DigitalOcean Gradient™ AI Platform Serverless Inference API",
"keywords": [
"n8n-community-node-package"
],
"license": "MIT",
"homepage": "",
"homepage": "https://github.com/digitalocean-labs/n8n-node-gradient-serverless-inference",
"author": {
"name": "",
"email": ""
"name": "DigitalOcean",
"email": "support@digitalocean.com"
},
"repository": {
"type": "git",
"url": "https://github.com/<...>/n8n-nodes-<...>.git"
"url": "https://github.com/digitalocean-labs/n8n-node-gradient-serverless-inference.git"
},
"engines": {
"node": ">=20.15"
@ -33,12 +33,10 @@
"n8n": {
"n8nNodesApiVersion": 1,
"credentials": [
"dist/credentials/ExampleCredentialsApi.credentials.js",
"dist/credentials/HttpBinApi.credentials.js"
"dist/credentials/DigitalOceanServerlessInference.credentials.js"
],
"nodes": [
"dist/nodes/ExampleNode/ExampleNode.node.js",
"dist/nodes/HttpBin/HttpBin.node.js"
"dist/nodes/ServerlessInferenceNode/ServerlessInference.node.js"
]
},
"devDependencies": {