From 9a1af251ec1bece4ca9d2d6d9050220c0cc84920 Mon Sep 17 00:00:00 2001 From: Harish Kotra Date: Thu, 6 Mar 2025 12:23:18 -0700 Subject: [PATCH 1/6] Added integration: Use your own Gaia node with XMTP --- integrations/gaia/.env.example | 4 + integrations/gaia/README.md | 149 +++++++++++++++++++++++++++++++++ integrations/gaia/index.ts | 120 ++++++++++++++++++++++++++ integrations/gaia/package.json | 20 +++++ yarn.lock | 11 +++ 5 files changed, 304 insertions(+) create mode 100644 integrations/gaia/.env.example create mode 100644 integrations/gaia/README.md create mode 100644 integrations/gaia/index.ts create mode 100644 integrations/gaia/package.json diff --git a/integrations/gaia/.env.example b/integrations/gaia/.env.example new file mode 100644 index 0000000..58ab873 --- /dev/null +++ b/integrations/gaia/.env.example @@ -0,0 +1,4 @@ +#GAIA Integration +GAIA_NODE_URL= # the URL of the Gaia node (Ex: https://llama8b.gaia.domains/v1) +GAIA_API_KEY= # the API key for the Gaia node (Ex: gaia-xxxxxxxxxx) +GAIA_MODEL_NAME= # the name of model running on your Gaia node (Ex: llama) \ No newline at end of file diff --git a/integrations/gaia/README.md b/integrations/gaia/README.md new file mode 100644 index 0000000..278c7fd --- /dev/null +++ b/integrations/gaia/README.md @@ -0,0 +1,149 @@ +# GPT agent example + +This example uses a [Gaia](https://docs.gaianet.ai) API for AI based responses and [XMTP](https://xmtp.org) for secure messaging. You can test your agent on [xmtp.chat](https://xmtp.chat) or any other XMTP-compatible client. + +Using Gaia, you can also run your own [node](https://docs.gaianet.ai/getting-started/quick-start) and use the OpenAI compatible API in this library. + +## Environment variables + +Add the following keys to a `.env` file: + +```bash +WALLET_KEY= # the private key of the wallet +ENCRYPTION_KEY= # a second random 32 bytes encryption key for local db encryption +GAIA_API_KEY= # Your API key from https://gaianet.ai +GAIA_NODE_URL= # Your custom Gaia node URL or a public node, ex: https://llama8b.gaia.domains/v1 +GAIA_MODEL_NAME= # Model name running in your Gaia node or a public node, ex: llama +``` + +You can generate random keys with the following command: + +```bash +yarn gen:keys +``` + +> [!WARNING] +> Running the `gen:keys` script will overwrite the existing `.env` file. + +## Usage + +```tsx +import { Client, type XmtpEnv } from "@xmtp/node-sdk"; +import OpenAI from "openai"; +import { createSigner, getEncryptionKeyFromHex } from "@/helpers"; + +const { WALLET_KEY, ENCRYPTION_KEY, GAIA_NODE_URL, GAIA_API_KEY, GAIA_MODEL_NAME } = process.env; + +if (!WALLET_KEY) { + throw new Error("WALLET_KEY must be set"); +} + +if (!ENCRYPTION_KEY) { + throw new Error("ENCRYPTION_KEY must be set"); +} + +/* Check if the Gaia API key is set */ +if (!GAIA_API_KEY) { + throw new Error("GAIA_API_KEY must be set"); +} + +/* Check if the Gaia node's base URL is set */ +if (!GAIA_NODE_URL) { + throw new Error("GAIA_NODE_URL must be set"); +} + +/* Check if the the model name for the Gaia node is set */ +if (!GAIA_MODEL_NAME) { + throw new Error("GAIA_MODEL_NAME must be set"); +} + +const signer = createSigner(WALLET_KEY); +const encryptionKey = getEncryptionKeyFromHex(ENCRYPTION_KEY); +const openai = new OpenAI({ + baseURL: GAIA_NODE_URL, + apiKey: GAIA_API_KEY +}); + +/* Set the environment to dev or production */ +const env: XmtpEnv = "dev"; + +async function main() { + console.log(`Creating client on the '${env}' network...`); + const client = await Client.create(signer, encryptionKey, { + env, + }); + + console.log("Syncing conversations..."); + /* Sync the conversations from the network to update the local db */ + await client.conversations.sync(); + + console.log( + `Agent initialized on ${client.accountAddress}\nSend a message on http://xmtp.chat/dm/${client.accountAddress}`, + ); + + console.log("Waiting for messages..."); + const stream = client.conversations.streamAllMessages(); + + for await (const message of await stream) { + /* Ignore messages from the same agent or non-text messages */ + if ( + message?.senderInboxId.toLowerCase() === client.inboxId.toLowerCase() || + message?.contentType?.typeId !== "text" + ) { + continue; + } + + console.log( + `Received message: ${message.content as string} by ${message.senderInboxId}`, + ); + + const conversation = client.conversations.getConversationById( + message.conversationId, + ); + + if (!conversation) { + console.log("Unable to find conversation, skipping"); + continue; + } + + try { + const completion = await openai.chat.completions.create({ + messages: [{ role: "user", content: message.content as string }], + model: GAIA_MODEL_NAME, + }); + + /* Get the AI response */ + const response = + completion.choices[0]?.message?.content || + "I'm not sure how to respond to that."; + + console.log(`Sending AI response: ${response}`); + await conversation.send(response); + } catch (error) { + console.error("Error getting AI response:", error); + await conversation.send( + "Sorry, I encountered an error processing your message.", + ); + } + + console.log("Waiting for messages..."); + } +} + +main().catch(console.error); +``` + +## Run the agent + +```bash +# git clone repo +git clone https://github.com/ephemeraHQ/xmtp-agent-examples.git +# go to the folder +cd xmtp-agent-examples +# install packages +yarn +# generate random keys (optional) +yarn gen:keys +# run the example +yarn examples:gpt +``` diff --git a/integrations/gaia/index.ts b/integrations/gaia/index.ts new file mode 100644 index 0000000..8b43bc4 --- /dev/null +++ b/integrations/gaia/index.ts @@ -0,0 +1,120 @@ +import { Client, type XmtpEnv } from "@xmtp/node-sdk"; +import OpenAI from "openai"; +import { createSigner, getEncryptionKeyFromHex } from "@/helpers"; + +/* Get the wallet key associated to the public key of + * the agent and the encryption key for the local db + * that stores your agent's messages */ +const { WALLET_KEY, ENCRYPTION_KEY, GAIA_NODE_URL, GAIA_API_KEY, GAIA_MODEL_NAME } = process.env; + +/* Check if the environment variables are set */ +if (!WALLET_KEY) { + throw new Error("WALLET_KEY must be set"); +} + +/* Check if the encryption key is set */ +if (!ENCRYPTION_KEY) { + throw new Error("ENCRYPTION_KEY must be set"); +} + +/* Check if the OpenAI API key is set */ +if (!GAIA_API_KEY) { + throw new Error("GAIA_API_KEY must be set"); +} + +/* Check if the Gaia node's base URL is set */ +if (!GAIA_NODE_URL) { + throw new Error("GAIA_NODE_URL must be set"); +} + +/* Check if the the model name for the Gaia node is set */ +if (!GAIA_MODEL_NAME) { + throw new Error("GAIA_MODEL_NAME must be set"); +} + +/* Create the signer using viem and parse the encryption key for the local db */ +const signer = createSigner(WALLET_KEY); +const encryptionKey = getEncryptionKeyFromHex(ENCRYPTION_KEY); + +/* Initialize the OpenAI client */ +const openai = new OpenAI({ + baseURL: GAIA_NODE_URL, + apiKey: GAIA_API_KEY +}); + +/* Set the environment to dev or production */ +const env: XmtpEnv = "dev"; + +/** + * Main function to run the agent + */ +async function main() { + console.log(`Creating client on the '${env}' network...`); + /* Initialize the xmtp client */ + const client = await Client.create(signer, encryptionKey, { + env, + }); + + console.log("Syncing conversations..."); + /* Sync the conversations from the network to update the local db */ + await client.conversations.sync(); + + console.log( + `Agent initialized on ${client.accountAddress}\nSend a message on http://xmtp.chat/dm/${client.accountAddress}`, + ); + + console.log("Waiting for messages..."); + /* Stream all messages from the network */ + const stream = client.conversations.streamAllMessages(); + + for await (const message of await stream) { + /* Ignore messages from the same agent or non-text messages */ + if ( + message?.senderInboxId.toLowerCase() === client.inboxId.toLowerCase() || + message?.contentType?.typeId !== "text" + ) { + continue; + } + + console.log( + `Received message: ${message.content as string} by ${message.senderInboxId}`, + ); + + /* Get the conversation from the local db */ + const conversation = client.conversations.getConversationById( + message.conversationId, + ); + + /* If the conversation is not found, skip the message */ + if (!conversation) { + console.log("Unable to find conversation, skipping"); + continue; + } + + try { + /* Get the AI response */ + const completion = await openai.chat.completions.create({ + messages: [{ role: "user", content: message.content as string }], + model: GAIA_MODEL_NAME, + }); + + /* Get the AI response */ + const response = + completion.choices[0]?.message?.content || + "I'm not sure how to respond to that."; + + console.log(`Sending AI response: ${response}`); + /* Send the AI response to the conversation */ + await conversation.send(response); + } catch (error) { + console.error("Error getting AI response:", error); + await conversation.send( + "Sorry, I encountered an error processing your message.", + ); + } + + console.log("Waiting for messages..."); + } +} + +main().catch(console.error); diff --git a/integrations/gaia/package.json b/integrations/gaia/package.json new file mode 100644 index 0000000..f733b29 --- /dev/null +++ b/integrations/gaia/package.json @@ -0,0 +1,20 @@ +{ + "name": "@integrations/gaia", + "version": "0.0.1", + "private": true, + "type": "module", + "scripts": { + "dev": "tsx --env-file=.env index.ts", + "gen:keys": "tsx ../../scripts/generateKeys.ts" + }, + "dependencies": { + "@xmtp/node-sdk": "0.0.42", + "tsx": "^4.19.2", + "uint8arrays": "^5.1.0", + "viem": "^2.22.17" + }, + "packageManager": "yarn@4.6.0", + "engines": { + "node": ">=20" + } +} diff --git a/yarn.lock b/yarn.lock index c1e12f0..81598df 100644 --- a/yarn.lock +++ b/yarn.lock @@ -808,6 +808,17 @@ __metadata: languageName: node linkType: hard +"@integrations/gaia@workspace:integrations/gaia": + version: 0.0.0-use.local + resolution: "@integrations/gaia@workspace:integrations/gaia" + dependencies: + "@xmtp/node-sdk": "npm:0.0.42" + tsx: "npm:^4.19.2" + uint8arrays: "npm:^5.1.0" + viem: "npm:^2.22.17" + languageName: unknown + linkType: soft + "@integrations/gpt@workspace:integrations/grok": version: 0.0.0-use.local resolution: "@integrations/gpt@workspace:integrations/grok" From f6e55a3d26b9a32bdabee6b04dd08541f38b7093 Mon Sep 17 00:00:00 2001 From: Harish Kotra Date: Sat, 8 Mar 2025 07:21:18 +0530 Subject: [PATCH 2/6] Update README.md Updated details to match with `integrations/gaia` --- integrations/gaia/README.md | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/integrations/gaia/README.md b/integrations/gaia/README.md index 278c7fd..b3bdbca 100644 --- a/integrations/gaia/README.md +++ b/integrations/gaia/README.md @@ -1,4 +1,4 @@ -# GPT agent example +# Gaia agent example This example uses a [Gaia](https://docs.gaianet.ai) API for AI based responses and [XMTP](https://xmtp.org) for secure messaging. You can test your agent on [xmtp.chat](https://xmtp.chat) or any other XMTP-compatible client. @@ -142,8 +142,7 @@ git clone https://github.com/ephemeraHQ/xmtp-agent-examples.git cd xmtp-agent-examples # install packages yarn -# generate random keys (optional) -yarn gen:keys -# run the example -yarn examples:gpt + +cd integrations/gaia +yarn dev ``` From a506e25f51dff39590a27a7a9c463070606f6e1b Mon Sep 17 00:00:00 2001 From: fabri Date: Mon, 10 Mar 2025 20:47:23 -0300 Subject: [PATCH 3/6] update linter --- integrations/gaia/index.ts | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/integrations/gaia/index.ts b/integrations/gaia/index.ts index 8b43bc4..22b97c4 100644 --- a/integrations/gaia/index.ts +++ b/integrations/gaia/index.ts @@ -5,7 +5,13 @@ import { createSigner, getEncryptionKeyFromHex } from "@/helpers"; /* Get the wallet key associated to the public key of * the agent and the encryption key for the local db * that stores your agent's messages */ -const { WALLET_KEY, ENCRYPTION_KEY, GAIA_NODE_URL, GAIA_API_KEY, GAIA_MODEL_NAME } = process.env; +const { + WALLET_KEY, + ENCRYPTION_KEY, + GAIA_NODE_URL, + GAIA_API_KEY, + GAIA_MODEL_NAME, +} = process.env; /* Check if the environment variables are set */ if (!WALLET_KEY) { @@ -37,9 +43,9 @@ const signer = createSigner(WALLET_KEY); const encryptionKey = getEncryptionKeyFromHex(ENCRYPTION_KEY); /* Initialize the OpenAI client */ -const openai = new OpenAI({ - baseURL: GAIA_NODE_URL, - apiKey: GAIA_API_KEY +const openai = new OpenAI({ + baseURL: GAIA_NODE_URL, + apiKey: GAIA_API_KEY, }); /* Set the environment to dev or production */ From 0e23caa9f284373a8888f97d7c372a0ae3240e10 Mon Sep 17 00:00:00 2001 From: fabri Date: Mon, 10 Mar 2025 20:55:32 -0300 Subject: [PATCH 4/6] update pr --- integrations/gaia/index.ts | 2 +- integrations/gaia/package.json | 5 ++++- integrations/gaia/tsconfig.json | 9 +++++++++ 3 files changed, 14 insertions(+), 2 deletions(-) create mode 100644 integrations/gaia/tsconfig.json diff --git a/integrations/gaia/index.ts b/integrations/gaia/index.ts index 22b97c4..d062eaa 100644 --- a/integrations/gaia/index.ts +++ b/integrations/gaia/index.ts @@ -101,7 +101,7 @@ async function main() { /* Get the AI response */ const completion = await openai.chat.completions.create({ messages: [{ role: "user", content: message.content as string }], - model: GAIA_MODEL_NAME, + model: GAIA_MODEL_NAME as string, }); /* Get the AI response */ diff --git a/integrations/gaia/package.json b/integrations/gaia/package.json index f733b29..9fe6a66 100644 --- a/integrations/gaia/package.json +++ b/integrations/gaia/package.json @@ -4,8 +4,11 @@ "private": true, "type": "module", "scripts": { + "build": "cd ../.. && yarn tsc", "dev": "tsx --env-file=.env index.ts", - "gen:keys": "tsx ../../scripts/generateKeys.ts" + "gen:keys": "tsx ../../scripts/generateKeys.ts", + "lint": "cd ../.. && yarn eslint integrations/gaia", + "typecheck": "cd ../.. && yarn typecheck" }, "dependencies": { "@xmtp/node-sdk": "0.0.42", diff --git a/integrations/gaia/tsconfig.json b/integrations/gaia/tsconfig.json new file mode 100644 index 0000000..a13356c --- /dev/null +++ b/integrations/gaia/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "paths": { + "@/helpers": ["./helpers/index.ts"] + } + }, + "include": ["**/*.ts"] +} From 823fd48414fde227ff7031ab286622fe7cd4bb6d Mon Sep 17 00:00:00 2001 From: fabri Date: Tue, 11 Mar 2025 13:35:30 -0300 Subject: [PATCH 5/6] push commits --- integrations/gaia/index.ts | 4 ++-- integrations/gaia/package.json | 7 ++----- integrations/gaia/tsconfig.json | 9 --------- 3 files changed, 4 insertions(+), 16 deletions(-) delete mode 100644 integrations/gaia/tsconfig.json diff --git a/integrations/gaia/index.ts b/integrations/gaia/index.ts index d062eaa..d4d96f0 100644 --- a/integrations/gaia/index.ts +++ b/integrations/gaia/index.ts @@ -87,8 +87,8 @@ async function main() { ); /* Get the conversation from the local db */ - const conversation = client.conversations.getConversationById( - message.conversationId, + const conversation = client.conversations.getDmByInboxId( + message.senderInboxId, ); /* If the conversation is not found, skip the message */ diff --git a/integrations/gaia/package.json b/integrations/gaia/package.json index 9fe6a66..db0dc9e 100644 --- a/integrations/gaia/package.json +++ b/integrations/gaia/package.json @@ -4,14 +4,11 @@ "private": true, "type": "module", "scripts": { - "build": "cd ../.. && yarn tsc", "dev": "tsx --env-file=.env index.ts", - "gen:keys": "tsx ../../scripts/generateKeys.ts", - "lint": "cd ../.. && yarn eslint integrations/gaia", - "typecheck": "cd ../.. && yarn typecheck" + "gen:keys": "tsx ../../scripts/generateKeys.ts" }, "dependencies": { - "@xmtp/node-sdk": "0.0.42", + "@xmtp/node-sdk": "0.0.47", "tsx": "^4.19.2", "uint8arrays": "^5.1.0", "viem": "^2.22.17" diff --git a/integrations/gaia/tsconfig.json b/integrations/gaia/tsconfig.json deleted file mode 100644 index a13356c..0000000 --- a/integrations/gaia/tsconfig.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "extends": "../../tsconfig.json", - "compilerOptions": { - "paths": { - "@/helpers": ["./helpers/index.ts"] - } - }, - "include": ["**/*.ts"] -} From 14e40f6529a66d2a7858e914d0fbfac73ac51141 Mon Sep 17 00:00:00 2001 From: fabri Date: Tue, 11 Mar 2025 13:54:57 -0300 Subject: [PATCH 6/6] yarn lock --- yarn.lock | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/yarn.lock b/yarn.lock index 81598df..19e3357 100644 --- a/yarn.lock +++ b/yarn.lock @@ -812,7 +812,7 @@ __metadata: version: 0.0.0-use.local resolution: "@integrations/gaia@workspace:integrations/gaia" dependencies: - "@xmtp/node-sdk": "npm:0.0.42" + "@xmtp/node-sdk": "npm:0.0.47" tsx: "npm:^4.19.2" uint8arrays: "npm:^5.1.0" viem: "npm:^2.22.17" @@ -1299,6 +1299,13 @@ __metadata: languageName: node linkType: hard +"@xmtp/node-bindings@npm:^0.0.41": + version: 0.0.41 + resolution: "@xmtp/node-bindings@npm:0.0.41" + checksum: 10/809347d36de2b4f205fe46cc3869b1eab7595107fd14b0535457732745d14851121d1e7b0f22a0845deb32e48dfca29a7ad36a12bf9fc5c46b445a308a589c10 + languageName: node + linkType: hard + "@xmtp/node-sdk@npm:0.0.42": version: 0.0.42 resolution: "@xmtp/node-sdk@npm:0.0.42" @@ -1325,6 +1332,19 @@ __metadata: languageName: node linkType: hard +"@xmtp/node-sdk@npm:0.0.47": + version: 0.0.47 + resolution: "@xmtp/node-sdk@npm:0.0.47" + dependencies: + "@xmtp/content-type-group-updated": "npm:^2.0.0" + "@xmtp/content-type-primitives": "npm:^2.0.0" + "@xmtp/content-type-text": "npm:^2.0.0" + "@xmtp/node-bindings": "npm:^0.0.41" + "@xmtp/proto": "npm:^3.72.3" + checksum: 10/bf3b5e59cc5be0b9ec02acd2d7cf59bcae5f478971b5ffc772e6153c8d59515032c95436faf2c741e1de334d9572dc1991098395d9813ec5364f0eb09666a815 + languageName: node + linkType: hard + "@xmtp/proto@npm:^3.72.0, @xmtp/proto@npm:^3.72.3": version: 3.73.0 resolution: "@xmtp/proto@npm:3.73.0"