Feature/add composio tool (#3722)
* feat: add composio tool * fix: improve error handling & field description * update composio tools for refresh and sorting --------- Co-authored-by: Henry <hzj94@hotmail.com>
This commit is contained in:
parent
d6b35465e5
commit
b29523d093
|
|
@ -0,0 +1,23 @@
|
|||
import { INodeParams, INodeCredential } from '../src/Interface'
|
||||
|
||||
class ComposioApi implements INodeCredential {
|
||||
label: string
|
||||
name: string
|
||||
version: number
|
||||
inputs: INodeParams[]
|
||||
|
||||
constructor() {
|
||||
this.label = 'Composio API'
|
||||
this.name = 'composioApi'
|
||||
this.version = 1.0
|
||||
this.inputs = [
|
||||
{
|
||||
label: 'Composio API Key',
|
||||
name: 'composioApi',
|
||||
type: 'password'
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { credClass: ComposioApi }
|
||||
|
|
@ -0,0 +1,225 @@
|
|||
import { Tool } from '@langchain/core/tools'
|
||||
import { ICommonObject, INode, INodeData, INodeOptionsValue, INodeParams } from '../../../src/Interface'
|
||||
import { getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils'
|
||||
import { LangchainToolSet } from 'composio-core'
|
||||
|
||||
class ComposioTool extends Tool {
|
||||
name = 'composio'
|
||||
description = 'Tool for interacting with Composio applications and performing actions'
|
||||
toolset: any
|
||||
appName: string
|
||||
actions: string[]
|
||||
|
||||
constructor(toolset: any, appName: string, actions: string[]) {
|
||||
super()
|
||||
this.toolset = toolset
|
||||
this.appName = appName
|
||||
this.actions = actions
|
||||
}
|
||||
|
||||
async _call(input: string): Promise<string> {
|
||||
try {
|
||||
return `Executed action on ${this.appName} with input: ${input}`
|
||||
} catch (error) {
|
||||
return 'Failed to execute action'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class Composio_Tools implements INode {
|
||||
label: string
|
||||
name: string
|
||||
version: number
|
||||
type: string
|
||||
icon: string
|
||||
category: string
|
||||
description: string
|
||||
baseClasses: string[]
|
||||
credential: INodeParams
|
||||
inputs: INodeParams[]
|
||||
|
||||
constructor() {
|
||||
this.label = 'Composio'
|
||||
this.name = 'composio'
|
||||
this.version = 1.0
|
||||
this.type = 'Composio'
|
||||
this.icon = 'composio.svg'
|
||||
this.category = 'Tools'
|
||||
this.description = 'Toolset with over 250+ Apps for building AI-powered applications'
|
||||
this.baseClasses = [this.type, ...getBaseClasses(ComposioTool)]
|
||||
this.credential = {
|
||||
label: 'Connect Credential',
|
||||
name: 'credential',
|
||||
type: 'credential',
|
||||
credentialNames: ['composioApi']
|
||||
}
|
||||
this.inputs = [
|
||||
{
|
||||
label: 'App Name',
|
||||
name: 'appName',
|
||||
type: 'asyncOptions',
|
||||
loadMethod: 'listApps',
|
||||
description: 'Select the app to connect with',
|
||||
refresh: true
|
||||
},
|
||||
{
|
||||
label: 'Auth Status',
|
||||
name: 'authStatus',
|
||||
type: 'asyncOptions',
|
||||
loadMethod: 'authStatus',
|
||||
placeholder: 'Connection status will appear here',
|
||||
refresh: true
|
||||
},
|
||||
{
|
||||
label: 'Actions to Use',
|
||||
name: 'actions',
|
||||
type: 'asyncOptions',
|
||||
loadMethod: 'listActions',
|
||||
description: 'Select the actions you want to use',
|
||||
refresh: true
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
//@ts-ignore
|
||||
loadMethods = {
|
||||
listApps: async (nodeData: INodeData, options?: ICommonObject): Promise<INodeOptionsValue[]> => {
|
||||
try {
|
||||
const credentialData = await getCredentialData(nodeData.credential ?? '', options ?? {})
|
||||
const composioApiKey = getCredentialParam('composioApi', credentialData, nodeData)
|
||||
|
||||
if (!composioApiKey) {
|
||||
return [
|
||||
{
|
||||
label: 'API Key Required',
|
||||
name: 'placeholder',
|
||||
description: 'Enter Composio API key in the credential field'
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
const toolset = new LangchainToolSet({ apiKey: composioApiKey })
|
||||
const apps = await toolset.client.apps.list()
|
||||
apps.sort((a: any, b: any) => a.name.localeCompare(b.name))
|
||||
|
||||
return apps.map(({ name, ...rest }) => ({
|
||||
label: name.toUpperCase(),
|
||||
name: name,
|
||||
description: rest.description || name
|
||||
}))
|
||||
} catch (error) {
|
||||
console.error('Error loading apps:', error)
|
||||
return [
|
||||
{
|
||||
label: 'Error Loading Apps',
|
||||
name: 'error',
|
||||
description: 'Failed to load apps. Please check your API key and try again'
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
listActions: async (nodeData: INodeData, options?: ICommonObject): Promise<INodeOptionsValue[]> => {
|
||||
try {
|
||||
const credentialData = await getCredentialData(nodeData.credential ?? '', options ?? {})
|
||||
const composioApiKey = getCredentialParam('composioApi', credentialData, nodeData)
|
||||
const appName = nodeData.inputs?.appName as string
|
||||
|
||||
if (!composioApiKey) {
|
||||
return [
|
||||
{
|
||||
label: 'API Key Required',
|
||||
name: 'placeholder',
|
||||
description: 'Enter Composio API key in the credential field'
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
if (!appName) {
|
||||
return [
|
||||
{
|
||||
label: 'Select an App first',
|
||||
name: 'placeholder',
|
||||
description: 'Select an app from the dropdown to view available actions'
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
const toolset = new LangchainToolSet({ apiKey: composioApiKey })
|
||||
const actions = await toolset.getTools({ apps: [appName] })
|
||||
actions.sort((a: any, b: any) => a.name.localeCompare(b.name))
|
||||
|
||||
return actions.map(({ name, ...rest }) => ({
|
||||
label: name.toUpperCase(),
|
||||
name: name,
|
||||
description: rest.description || name
|
||||
}))
|
||||
} catch (error) {
|
||||
console.error('Error loading actions:', error)
|
||||
return [
|
||||
{
|
||||
label: 'Error Loading Actions',
|
||||
name: 'error',
|
||||
description: 'Failed to load actions. Please check your API key and try again'
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
authStatus: async (nodeData: INodeData, options?: ICommonObject): Promise<INodeOptionsValue[]> => {
|
||||
const credentialData = await getCredentialData(nodeData.credential ?? '', options ?? {})
|
||||
const composioApiKey = getCredentialParam('composioApi', credentialData, nodeData)
|
||||
const appName = nodeData.inputs?.appName as string
|
||||
|
||||
if (!composioApiKey) {
|
||||
return [
|
||||
{
|
||||
label: 'API Key Required',
|
||||
name: 'placeholder',
|
||||
description: 'Enter Composio API key in the credential field'
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
if (!appName) {
|
||||
return [
|
||||
{
|
||||
label: 'Select an App first',
|
||||
name: 'placeholder',
|
||||
description: 'Select an app from the dropdown to view available actions'
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
const toolset = new LangchainToolSet({ apiKey: composioApiKey })
|
||||
const authStatus = await toolset.client.getEntity('default').getConnection({ app: appName.toLowerCase() })
|
||||
return [
|
||||
{
|
||||
label: authStatus ? 'Connected' : 'Not Connected',
|
||||
name: authStatus ? 'Connected' : 'Not Connected',
|
||||
description: authStatus ? 'Selected app has an active connection' : 'Please connect the app on app.composio.dev'
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
async init(nodeData: INodeData, _: string, options: ICommonObject): Promise<any> {
|
||||
if (!nodeData.inputs) nodeData.inputs = {}
|
||||
|
||||
const credentialData = await getCredentialData(nodeData.credential ?? '', options)
|
||||
const composioApiKey = getCredentialParam('composioApi', credentialData, nodeData)
|
||||
|
||||
if (!composioApiKey) {
|
||||
nodeData.inputs = {
|
||||
appName: undefined,
|
||||
authStatus: '',
|
||||
actions: []
|
||||
}
|
||||
throw new Error('API Key Required')
|
||||
}
|
||||
|
||||
const toolset = new LangchainToolSet({ apiKey: composioApiKey })
|
||||
const tools = await toolset.getTools({ actions: [nodeData.inputs?.actions as string] })
|
||||
return tools
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { nodeClass: Composio_Tools }
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
<svg width="247" height="247" viewBox="0 0 247 247" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect x="1.25506" y="1.25506" width="244.791" height="244.791" rx="77.2891" fill="url(#paint0_linear_4808_63)"/>
|
||||
<rect x="1.25506" y="1.25506" width="244.791" height="244.791" rx="77.2891" stroke="#660DCA" stroke-width="1.48988"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M139.462 60.7396C140.348 61.231 141.043 62.0051 141.438 62.9382C141.833 63.8712 141.903 64.9096 141.639 65.8876L129.593 110.051H173.543C174.427 110.05 175.292 110.308 176.032 110.793C176.771 111.278 177.353 111.968 177.705 112.779C178.057 113.591 178.164 114.487 178.013 115.358C177.862 116.23 177.46 117.038 176.855 117.683L113.355 185.717C112.66 186.449 111.741 186.929 110.743 187.08C109.746 187.232 108.726 187.047 107.845 186.555C106.964 186.063 106.272 185.291 105.878 184.361C105.484 183.432 105.412 182.398 105.671 181.423L117.709 137.268H73.7589C72.8745 137.269 72.0093 137.011 71.2697 136.526C70.5302 136.041 69.9485 135.35 69.5964 134.539C69.2444 133.728 69.1373 132.832 69.2883 131.96C69.4393 131.089 69.8419 130.281 70.4464 129.635L133.947 61.6019C134.639 60.8647 135.557 60.3797 136.556 60.2235C137.555 60.0673 138.578 60.2489 139.462 60.7396Z" fill="white"/>
|
||||
<defs>
|
||||
<linearGradient id="paint0_linear_4808_63" x1="123.651" y1="2" x2="186.945" y2="245.888" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0.67169" stop-color="#8A22FF"/>
|
||||
<stop offset="1" stop-color="#7F28E0"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
|
|
@ -74,6 +74,7 @@
|
|||
"cheerio": "^1.0.0-rc.12",
|
||||
"chromadb": "^1.5.11",
|
||||
"cohere-ai": "^7.7.5",
|
||||
"composio-core": "^0.4.7",
|
||||
"couchbase": "4.4.1",
|
||||
"crypto-js": "^4.1.1",
|
||||
"css-what": "^6.1.0",
|
||||
|
|
|
|||
|
|
@ -93,6 +93,7 @@ export interface INodeParams {
|
|||
hint?: Record<string, string>
|
||||
tabIdentifier?: string
|
||||
tabs?: Array<INodeParams>
|
||||
refresh?: boolean
|
||||
}
|
||||
|
||||
export interface INodeExecutionData {
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ import { Popper, Box, Typography, Tooltip, IconButton, Button, TextField } from
|
|||
import { useGridApiContext } from '@mui/x-data-grid'
|
||||
import IconAutoFixHigh from '@mui/icons-material/AutoFixHigh'
|
||||
import { tooltipClasses } from '@mui/material/Tooltip'
|
||||
import { IconArrowsMaximize, IconEdit, IconAlertTriangle, IconBulb } from '@tabler/icons-react'
|
||||
import { IconArrowsMaximize, IconEdit, IconAlertTriangle, IconBulb, IconRefresh } from '@tabler/icons-react'
|
||||
import { Tabs } from '@mui/base/Tabs'
|
||||
import Autocomplete, { autocompleteClasses } from '@mui/material/Autocomplete'
|
||||
|
||||
|
|
@ -738,7 +738,7 @@ const NodeInputHandler = ({
|
|||
{inputParam.type === 'asyncOptions' && (
|
||||
<>
|
||||
{data.inputParams.length === 1 && <div style={{ marginTop: 10 }} />}
|
||||
<div key={reloadTimestamp} style={{ display: 'flex', flexDirection: 'row' }}>
|
||||
<div key={reloadTimestamp} style={{ display: 'flex', flexDirection: 'row', alignContent: 'center' }}>
|
||||
<AsyncDropdown
|
||||
disabled={disabled}
|
||||
name={inputParam.name}
|
||||
|
|
@ -758,6 +758,16 @@ const NodeInputHandler = ({
|
|||
<IconEdit />
|
||||
</IconButton>
|
||||
)}
|
||||
{inputParam.refresh && (
|
||||
<IconButton
|
||||
title='Refresh'
|
||||
color='primary'
|
||||
size='small'
|
||||
onClick={() => setReloadTimestamp(Date.now().toString())}
|
||||
>
|
||||
<IconRefresh />
|
||||
</IconButton>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
|
|
|||
68051
pnpm-lock.yaml
68051
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue