Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions packages/components/credentials/AGIoneApi.credential.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { INodeCredential, INodeParams } from '../src/Interface'

class AGIoneApi implements INodeCredential {
label: string
name: string
version: number
inputs: INodeParams[]

constructor() {
this.label = 'AGIone API'
this.name = 'agioneApi'
this.version = 1.0
this.inputs = [
{
label: 'AGIone Auth Token',
name: 'agioneApiKey',
type: 'password'
}
]
}
}

module.exports = { credClass: AGIoneApi }
198 changes: 198 additions & 0 deletions packages/components/nodes/chatmodels/ChatAGIone/ChatAGIone.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
import { BaseCache } from '@langchain/core/caches'
import { ChatOpenAI, ChatOpenAIFields } from '@langchain/openai'
import { ICommonObject, INode, INodeData, INodeParams } from '../../../src/Interface'
import { getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils'

class ChatAGIone_ChatModels implements INode {
readonly baseURL: string = 'https://agione.pro/hyperone/xapi/api/v1'
label: string
name: string
version: number
type: string
icon: string
category: string
description: string
baseClasses: string[]
credential: INodeParams
inputs: INodeParams[]

constructor() {
this.label = 'AGIone'
this.name = 'chatAGIone'
this.version = 1.0
this.type = 'ChatAGIone'
this.icon = 'agione.svg'
this.category = 'Chat Models'
this.description = 'Wrapper around AGIone models that use the OpenAI-compatible Chat endpoint'
this.baseClasses = [this.type, ...getBaseClasses(ChatOpenAI)]
this.credential = {
label: 'Connect Credential',
name: 'credential',
type: 'credential',
credentialNames: ['agioneApi']
}
this.inputs = [
{
label: 'Cache',
name: 'cache',
type: 'BaseCache',
optional: true
},
{
label: 'Model Name',
name: 'modelName',
type: 'string',
default: 'openai/GPT-5.5/c6fbe',
description:
'Enter the model name (e.g., openai/GPT-5.5/c6fbe, anthropic/Claude-opus-4.7/a4d5d, deepseek/deepseek-v3.2/0000n)'
},
{
label: 'Temperature',
name: 'temperature',
type: 'number',
step: 0.1,
default: 0.7,
optional: true
},
{
label: 'Streaming',
name: 'streaming',
type: 'boolean',
default: true,
optional: true,
additionalParams: true
},
{
label: 'Max Tokens',
name: 'maxTokens',
type: 'number',
step: 1,
optional: true,
additionalParams: true
},
{
label: 'Top Probability',
name: 'topP',
type: 'number',
step: 0.1,
optional: true,
additionalParams: true
},
{
label: 'Frequency Penalty',
name: 'frequencyPenalty',
type: 'number',
step: 0.1,
optional: true,
additionalParams: true
},
{
label: 'Presence Penalty',
name: 'presencePenalty',
type: 'number',
step: 0.1,
optional: true,
additionalParams: true
},
{
label: 'Base Options',
name: 'baseOptions',
type: 'json',
optional: true,
additionalParams: true,
description: 'Additional options to pass to the AGIone client. This should be a JSON object.'
}
]
}

async init(nodeData: INodeData, _: string, options: ICommonObject): Promise<any> {
const temperature = nodeData.inputs?.temperature as string
const modelName = nodeData.inputs?.modelName as string
const maxTokens = nodeData.inputs?.maxTokens as string
const topP = nodeData.inputs?.topP as string
const frequencyPenalty = nodeData.inputs?.frequencyPenalty as string
const presencePenalty = nodeData.inputs?.presencePenalty as string
const streaming = nodeData.inputs?.streaming as boolean
const baseOptions = nodeData.inputs?.baseOptions

if (nodeData.inputs?.credentialId) {
nodeData.credential = nodeData.inputs?.credentialId
}
const credentialData = await getCredentialData(nodeData.credential ?? '', options)
const agioneApiKey = getCredentialParam('agioneApiKey', credentialData, nodeData)

if (!agioneApiKey || agioneApiKey.trim() === '') {
throw new Error(
'AGIone Auth Token is missing or empty. Please provide a valid AGIone Auth Token in the credential configuration.'
)
}

if (!modelName || modelName.trim() === '') {
throw new Error('Model Name is required. Please enter a valid AGIone model name (e.g., openai/GPT-5.5/c6fbe).')
}

const cache = nodeData.inputs?.cache as BaseCache

const parseOptionalFloat = (value: unknown): number | undefined => {
if (value === undefined || value === null || value === '') return undefined
const parsedValue = typeof value === 'number' ? value : parseFloat(value as string)
return Number.isNaN(parsedValue) ? undefined : parsedValue
}

const parseOptionalInteger = (value: unknown): number | undefined => {
if (value === undefined || value === null || value === '') return undefined
const parsedValue = typeof value === 'number' ? value : parseInt(value as string, 10)
return Number.isNaN(parsedValue) ? undefined : parsedValue
}

const obj: ChatOpenAIFields = {
modelName,
openAIApiKey: agioneApiKey,
apiKey: agioneApiKey,
streaming: streaming ?? true
}

const parsedTemperature = parseOptionalFloat(temperature)
const parsedMaxTokens = parseOptionalInteger(maxTokens)
const parsedTopP = parseOptionalFloat(topP)
const parsedFrequencyPenalty = parseOptionalFloat(frequencyPenalty)
const parsedPresencePenalty = parseOptionalFloat(presencePenalty)

if (parsedTemperature !== undefined) obj.temperature = parsedTemperature
if (parsedMaxTokens !== undefined) obj.maxTokens = parsedMaxTokens
if (parsedTopP !== undefined) obj.topP = parsedTopP
if (parsedFrequencyPenalty !== undefined) obj.frequencyPenalty = parsedFrequencyPenalty
if (parsedPresencePenalty !== undefined) obj.presencePenalty = parsedPresencePenalty
if (cache) obj.cache = cache
Comment on lines +148 to +166
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Parsing optional numeric inputs directly with parseFloat or parseInt without checking if they are defined or empty can result in NaN values being passed to the ChatOpenAI constructor, which can cause runtime errors or API validation failures. Additionally, using simple truthiness checks like if (frequencyPenalty) will incorrectly ignore valid 0 values. Using a nullish check (!= null) ensures that 0 is correctly preserved while safely ignoring empty or undefined values.

        const obj: ChatOpenAIFields = {
            modelName,
            openAIApiKey: agioneApiKey,
            apiKey: agioneApiKey,
            streaming: streaming ?? true
        }

        if (temperature != null && temperature !== '') {
            obj.temperature = parseFloat(temperature)
        }
        if (maxTokens != null && maxTokens !== '') {
            obj.maxTokens = parseInt(maxTokens, 10)
        }
        if (topP != null && topP !== '') {
            obj.topP = parseFloat(topP)
        }
        if (frequencyPenalty != null && frequencyPenalty !== '') {
            obj.frequencyPenalty = parseFloat(frequencyPenalty)
        }
        if (presencePenalty != null && presencePenalty !== '') {
            obj.presencePenalty = parseFloat(presencePenalty)
        }
        if (cache) obj.cache = cache
References
  1. In JavaScript/TypeScript, use loose equality (== null) as a standard idiom for a 'nullish' check that covers both null and undefined.


let parsedBaseOptions: Record<string, unknown> | undefined = undefined

if (baseOptions) {
try {
const parsedOptions = typeof baseOptions === 'object' ? baseOptions : JSON.parse(baseOptions)
if (parsedOptions && typeof parsedOptions === 'object' && !Array.isArray(parsedOptions)) {
parsedBaseOptions = parsedOptions as Record<string, unknown>
if ('baseURL' in parsedBaseOptions) {
console.warn("The 'baseURL' parameter is not allowed when using the ChatAGIone node.")
parsedBaseOptions.baseURL = undefined
}
} else if (parsedOptions !== null) {
throw new Error('BaseOptions must be a JSON object')
}
} catch (exception) {
throw new Error('Invalid JSON in the BaseOptions: ' + exception)
}
}

const model = new ChatOpenAI({
...obj,
configuration: {
baseURL: this.baseURL,
...parsedBaseOptions
}
})
return model
}
}

module.exports = { nodeClass: ChatAGIone_ChatModels }
4 changes: 4 additions & 0 deletions packages/components/nodes/chatmodels/ChatAGIone/agione.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.