Skip to content

Commit 32e5b13

Browse files
feat: add AWS SNS tool for publishing messages to SNS topics (#5049)
1 parent db4de45 commit 32e5b13

File tree

4 files changed

+38969
-38003
lines changed

4 files changed

+38969
-38003
lines changed
Lines changed: 209 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,209 @@
1+
import { Tool } from '@langchain/core/tools'
2+
import { ICommonObject, INode, INodeData, INodeOptionsValue, INodeParams } from '../../../src/Interface'
3+
import { getBaseClasses, getCredentialData, getCredentialParam } from '../../../src/utils'
4+
import { SNSClient, ListTopicsCommand, PublishCommand } from '@aws-sdk/client-sns'
5+
6+
class AWSSNSTool extends Tool {
7+
name = 'aws_sns_publish'
8+
description = 'Publishes a message to an AWS SNS topic'
9+
private snsClient: SNSClient
10+
private topicArn: string
11+
12+
constructor(snsClient: SNSClient, topicArn: string) {
13+
super()
14+
this.snsClient = snsClient
15+
this.topicArn = topicArn
16+
}
17+
18+
async _call(message: string): Promise<string> {
19+
try {
20+
const command = new PublishCommand({
21+
TopicArn: this.topicArn,
22+
Message: message
23+
})
24+
25+
const response = await this.snsClient.send(command)
26+
return `Successfully published message to SNS topic. MessageId: ${response.MessageId}`
27+
} catch (error) {
28+
return `Failed to publish message to SNS: ${error}`
29+
}
30+
}
31+
}
32+
33+
class AWSSNS_Tools implements INode {
34+
label: string
35+
name: string
36+
version: number
37+
type: string
38+
icon: string
39+
category: string
40+
description: string
41+
baseClasses: string[]
42+
credential: INodeParams
43+
inputs: INodeParams[]
44+
45+
constructor() {
46+
this.label = 'AWS SNS'
47+
this.name = 'awsSNS'
48+
this.version = 1.0
49+
this.type = 'AWSSNS'
50+
this.icon = 'awssns.svg'
51+
this.category = 'Tools'
52+
this.description = 'Publish messages to AWS SNS topics'
53+
this.baseClasses = [this.type, ...getBaseClasses(AWSSNSTool)]
54+
this.credential = {
55+
label: 'AWS Credentials',
56+
name: 'credential',
57+
type: 'credential',
58+
credentialNames: ['awsApi']
59+
}
60+
this.inputs = [
61+
{
62+
label: 'AWS Region',
63+
name: 'region',
64+
type: 'options',
65+
options: [
66+
{ label: 'US East (N. Virginia) - us-east-1', name: 'us-east-1' },
67+
{ label: 'US East (Ohio) - us-east-2', name: 'us-east-2' },
68+
{ label: 'US West (N. California) - us-west-1', name: 'us-west-1' },
69+
{ label: 'US West (Oregon) - us-west-2', name: 'us-west-2' },
70+
{ label: 'Africa (Cape Town) - af-south-1', name: 'af-south-1' },
71+
{ label: 'Asia Pacific (Hong Kong) - ap-east-1', name: 'ap-east-1' },
72+
{ label: 'Asia Pacific (Mumbai) - ap-south-1', name: 'ap-south-1' },
73+
{ label: 'Asia Pacific (Osaka) - ap-northeast-3', name: 'ap-northeast-3' },
74+
{ label: 'Asia Pacific (Seoul) - ap-northeast-2', name: 'ap-northeast-2' },
75+
{ label: 'Asia Pacific (Singapore) - ap-southeast-1', name: 'ap-southeast-1' },
76+
{ label: 'Asia Pacific (Sydney) - ap-southeast-2', name: 'ap-southeast-2' },
77+
{ label: 'Asia Pacific (Tokyo) - ap-northeast-1', name: 'ap-northeast-1' },
78+
{ label: 'Canada (Central) - ca-central-1', name: 'ca-central-1' },
79+
{ label: 'Europe (Frankfurt) - eu-central-1', name: 'eu-central-1' },
80+
{ label: 'Europe (Ireland) - eu-west-1', name: 'eu-west-1' },
81+
{ label: 'Europe (London) - eu-west-2', name: 'eu-west-2' },
82+
{ label: 'Europe (Milan) - eu-south-1', name: 'eu-south-1' },
83+
{ label: 'Europe (Paris) - eu-west-3', name: 'eu-west-3' },
84+
{ label: 'Europe (Stockholm) - eu-north-1', name: 'eu-north-1' },
85+
{ label: 'Middle East (Bahrain) - me-south-1', name: 'me-south-1' },
86+
{ label: 'South America (São Paulo) - sa-east-1', name: 'sa-east-1' }
87+
],
88+
default: 'us-east-1',
89+
description: 'AWS Region where your SNS topics are located'
90+
},
91+
{
92+
label: 'SNS Topic',
93+
name: 'topicArn',
94+
type: 'asyncOptions',
95+
loadMethod: 'listTopics',
96+
description: 'Select the SNS topic to publish to',
97+
refresh: true
98+
}
99+
]
100+
}
101+
102+
//@ts-ignore
103+
loadMethods = {
104+
listTopics: async (nodeData: INodeData, options?: ICommonObject): Promise<INodeOptionsValue[]> => {
105+
try {
106+
const credentialData = await getCredentialData(nodeData.credential ?? '', options ?? {})
107+
108+
const accessKeyId = getCredentialParam('awsKey', credentialData, nodeData)
109+
const secretAccessKey = getCredentialParam('awsSecret', credentialData, nodeData)
110+
const sessionToken = getCredentialParam('awsSession', credentialData, nodeData)
111+
112+
const region = (nodeData.inputs?.region as string) || 'us-east-1'
113+
114+
if (!accessKeyId || !secretAccessKey) {
115+
return [
116+
{
117+
label: 'AWS Credentials Required',
118+
name: 'placeholder',
119+
description: 'Enter AWS Access Key ID and Secret Access Key'
120+
}
121+
]
122+
}
123+
124+
const credentials: any = {
125+
accessKeyId: accessKeyId,
126+
secretAccessKey: secretAccessKey
127+
}
128+
129+
if (sessionToken) {
130+
credentials.sessionToken = sessionToken
131+
}
132+
133+
const snsClient = new SNSClient({
134+
region: region,
135+
credentials: credentials
136+
})
137+
138+
const command = new ListTopicsCommand({})
139+
const response = await snsClient.send(command)
140+
141+
if (!response.Topics || response.Topics.length === 0) {
142+
return [
143+
{
144+
label: 'No topics found',
145+
name: 'placeholder',
146+
description: 'No SNS topics found in this region'
147+
}
148+
]
149+
}
150+
151+
return response.Topics.map((topic) => {
152+
const topicArn = topic.TopicArn || ''
153+
const topicName = topicArn.split(':').pop() || topicArn
154+
return {
155+
label: topicName,
156+
name: topicArn,
157+
description: topicArn
158+
}
159+
})
160+
} catch (error) {
161+
console.error('Error loading SNS topics:', error)
162+
return [
163+
{
164+
label: 'Error Loading Topics',
165+
name: 'error',
166+
description: `Failed to load topics: ${error}`
167+
}
168+
]
169+
}
170+
}
171+
}
172+
173+
async init(nodeData: INodeData, _: string, options: ICommonObject): Promise<any> {
174+
const credentialData = await getCredentialData(nodeData.credential ?? '', options)
175+
176+
const accessKeyId = getCredentialParam('awsKey', credentialData, nodeData)
177+
const secretAccessKey = getCredentialParam('awsSecret', credentialData, nodeData)
178+
const sessionToken = getCredentialParam('awsSession', credentialData, nodeData)
179+
180+
const region = (nodeData.inputs?.region as string) || 'us-east-1'
181+
const topicArn = nodeData.inputs?.topicArn as string
182+
183+
if (!accessKeyId || !secretAccessKey) {
184+
throw new Error('AWS Access Key ID and Secret Access Key are required')
185+
}
186+
187+
if (!topicArn) {
188+
throw new Error('SNS Topic ARN is required')
189+
}
190+
191+
const credentials: any = {
192+
accessKeyId: accessKeyId,
193+
secretAccessKey: secretAccessKey
194+
}
195+
196+
if (sessionToken) {
197+
credentials.sessionToken = sessionToken
198+
}
199+
200+
const snsClient = new SNSClient({
201+
region: region,
202+
credentials: credentials
203+
})
204+
205+
return new AWSSNSTool(snsClient, topicArn)
206+
}
207+
}
208+
209+
module.exports = { nodeClass: AWSSNS_Tools }
Lines changed: 1 addition & 0 deletions
Loading

packages/components/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
"@aws-sdk/client-dynamodb": "^3.360.0",
2525
"@aws-sdk/client-s3": "^3.844.0",
2626
"@aws-sdk/client-secrets-manager": "^3.699.0",
27+
"@aws-sdk/client-sns": "^3.699.0",
2728
"@datastax/astra-db-ts": "1.5.0",
2829
"@dqbd/tiktoken": "^1.0.21",
2930
"@e2b/code-interpreter": "^1.5.1",

0 commit comments

Comments
 (0)