Skip to content

Feature/add composio tool #3722

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Dec 19, 2024
Merged
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/ComposioApi.credential.ts
Original file line number Diff line number Diff line change
@@ -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 }
225 changes: 225 additions & 0 deletions packages/components/nodes/tools/Composio/Composio.ts
Original file line number Diff line number Diff line change
@@ -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 }
11 changes: 11 additions & 0 deletions packages/components/nodes/tools/Composio/composio.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions packages/components/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
1 change: 1 addition & 0 deletions packages/components/src/Interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ export interface INodeParams {
hint?: Record<string, string>
tabIdentifier?: string
tabs?: Array<INodeParams>
refresh?: boolean
}

export interface INodeExecutionData {
Expand Down
14 changes: 12 additions & 2 deletions packages/ui/src/views/canvas/NodeInputHandler.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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'

Expand Down Expand Up @@ -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}
Expand All @@ -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>
</>
)}
Expand Down
Loading
Loading