diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 767e2332..a0c74c5c 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -48,22 +48,23 @@ jobs: NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} - name: Copy files run: yarn copy - - name: Create release pull request - if: github.event_name == 'push' - uses: changesets/action@v1 - with: - title: "release: version packages" - commit: "release: version packages" - publish: yarn publish - setupGitUser: true - createGithubReleases: true - bump: "patch" - env: - GITHUB_TOKEN: ${{ secrets.PERSONAL_ACCESS_TOKEN }} - NPM_TOKEN: ${{ secrets.NPM_TOKEN }} - continue-on-error: true - - name: Publish to npm - if: github.event_name == 'push' - run: yarn publish --access public - env: - NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + + ## - name: Create release pull request + ## if: github.event_name == 'push' + ## uses: changesets/action@v1 + ## with: + ## title: "release: version packages" + ## commit: "release: version packages" + ## publish: yarn publish + ## setupGitUser: true + ## createGithubReleases: true + ## bump: "patch" +## env: +## GITHUB_TOKEN: ${{ secrets.PERSONAL_ACCESS_TOKEN }} +## NPM_TOKEN: ${{ secrets.NPM_TOKEN }} +## continue-on-error: true +## - name: Publish to npm +## if: github.event_name == 'push' +## run: yarn publish --access public +## env: +## NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} diff --git a/examples/group/src/lib/openai.ts b/examples/group/src/lib/openai.ts index f0a6c73d..7e288626 100644 --- a/examples/group/src/lib/openai.ts +++ b/examples/group/src/lib/openai.ts @@ -24,7 +24,14 @@ export async function textGeneration(userPrompt: string, systemPrompt: string) { role: "assistant", content: reply || "No response from OpenAI.", }); - return { reply: reply as string, history: messages }; + const cleanedReply = reply + ?.replace(/(\*\*|__)(.*?)\1/g, "$2") + ?.replace(/\[([^\]]+)\]\(([^)]+)\)/g, "$2") + ?.replace(/^#+\s*(.*)$/gm, "$1") + ?.replace(/`([^`]+)`/g, "$1") + ?.replace(/^`|`$/g, ""); + + return { reply: cleanedReply as string, history: messages }; } catch (error) { console.error("Failed to fetch from OpenAI:", error); throw error; diff --git a/package.json b/package.json index 269a43a4..dbbc55e2 100644 --- a/package.json +++ b/package.json @@ -13,13 +13,14 @@ "changeset": "yarn copy && changeset add --type patch", "clean": "turbo run clean && rm -rf node_modules && rm -rf .turbo && yarn cache clean", "copy": "cd packages/create-message-kit && yarn copy && cd .. && cd ..", + "dev": "cd packages/message-kit && yarn build:watch && bun link", "dev:docs": "cd packages/docs && yarn dev", "dev:gm": "cd examples/gm && yarn dev", "dev:group": "cd examples/group && yarn dev", "dev:one-to-one": "cd examples/one-to-one && yarn dev", "format": "turbo run format", "format:check": "turbo run format:check", - "publish": "turbo run build --filter='./packages/*' --filter='!./packages/docs' && changeset publish", + "publish": "turbo run build --filter='./packages/*' --filter='!./packages/docs' && yarn copy && changeset publish", "test": "FORCE_COLOR=1 turbo run test --force", "typecheck": "FORCE_COLOR=1 turbo run typecheck" }, diff --git a/packages/docs/components/CustomHomePage.tsx b/packages/docs/components/CustomHomePage.tsx index 0e025614..39e175d9 100644 --- a/packages/docs/components/CustomHomePage.tsx +++ b/packages/docs/components/CustomHomePage.tsx @@ -1,4 +1,4 @@ -import React from 'react' +import React from "react"; const Root: React.FC<{ children: React.ReactNode }> = ({ children }) => (
{children}
@@ -7,10 +7,11 @@ const Root: React.FC<{ children: React.ReactNode }> = ({ children }) => ( const Headline: React.FC<{ children: React.ReactNode }> = ({ children }) => { return (

- {React.Children.map(children, child => { - if (React.isValidElement(child) && child.type === 'p') { + {React.Children.map(children, (child) => { + if (React.isValidElement(child) && child.type === "p") { return React.cloneElement(child, { - className: `custom-homepage-headline-text ${child.props.className || ''}`.trim() + className: + `custom-homepage-headline-text ${child.props.className || ""}`.trim(), }); } return {child}; @@ -22,10 +23,11 @@ const Headline: React.FC<{ children: React.ReactNode }> = ({ children }) => { const Subhead: React.FC<{ children: React.ReactNode }> = ({ children }) => { return (
- {React.Children.map(children, child => { - if (React.isValidElement(child) && child.type === 'p') { + {React.Children.map(children, (child) => { + if (React.isValidElement(child) && child.type === "p") { return React.cloneElement(child, { - className: `custom-homepage-subhead-text ${child.props.className || ''}`.trim() + className: + `custom-homepage-subhead-text ${child.props.className || ""}`.trim(), }); } return {child}; @@ -39,39 +41,60 @@ const TileGrid: React.FC<{ children: React.ReactNode }> = ({ children }) => ( ); interface TileProps { - href: string; - title: string; - description: string; - icon?: string; - isExternal?: boolean; - } - - const ExternalLinkIcon = () => ( - - - - - - ); - - const Tile: React.FC = ({ href, title, description, icon, isExternal }) => ( - - {icon && {icon}} -

{title}

-

{description}

- {isExternal && } -
- ); - + href: string; + title: string; + description: string; + icon?: string; + isExternal?: boolean; +} + +const ExternalLinkIcon = () => ( + + + + + +); + +const Tile: React.FC = ({ + href, + title, + description, + icon, + isExternal, +}) => ( + + {icon && {icon}} +

{title}

+

{description}

+ {isExternal && ( + + )} +
+); + export const CustomHomePage = { Root, Headline, Subhead, TileGrid, Tile, -}; \ No newline at end of file +}; diff --git a/packages/docs/pages/deployment.mdx b/packages/docs/pages/deployment.mdx index 4845ba3d..f1a690e8 100644 --- a/packages/docs/pages/deployment.mdx +++ b/packages/docs/pages/deployment.mdx @@ -40,3 +40,9 @@ REDIS_CONNECTION_STRING= # the connection string for the Redis database 4. **Environment Variables**: Set up environment variables in Railway. ![](/img/railway/5.gif) + +5. **Cache**: The cache is stored in the `.cache` folder where the conversation history is stored. If deleted or restarted, the bot will not have access to previous messages. + +![](/img/railway/volume.png) + +- Railway allows to attach a volume to the container to preserve the cache between deployments diff --git a/packages/docs/pages/directory.mdx b/packages/docs/pages/directory.mdx index 477d1502..1c5f47d1 100644 --- a/packages/docs/pages/directory.mdx +++ b/packages/docs/pages/directory.mdx @@ -26,6 +26,12 @@ These are some open source examples of mini-apps built with MessageKit. - **How to use**: [Send a DM](https://converse.xyz/dm/wordlebot.eth) to `wordlebot.eth` in Converse. - **Source Code**: [Bot](https://github.com/ephemerahq/wordle-bot) +#### 💱 Swapbot + +- **Description**: Swap tokens through messaging. +- **How to use**: [Send a DM](https://converse.xyz/dm/swapbot.converse.xyz) to `swapbot.converse.xyz` in Converse. +- **Source Code**: [Bot](https://github.com/fabriguespe/swap-bot) + ### 🎫 Invite Bot - **Description**: Invite bot for Events linked to a Notion db. diff --git a/packages/docs/pages/index.mdx b/packages/docs/pages/index.mdx index 534c2846..2c930bbb 100644 --- a/packages/docs/pages/index.mdx +++ b/packages/docs/pages/index.mdx @@ -1,20 +1,21 @@ import { HomePage } from "vocs/components"; -import { CustomHomePage } from '../components/CustomHomePage' +import { CustomHomePage } from "../components/CustomHomePage"; - - Build with MessageKit - + + {typeof window !== 'undefined' ? "Build with MessageKit" : ""} + An open SDK for building messaging mini-apps that run in apps built with [XMTP](https://docs.xmtp.org) -
:::code-group @@ -29,7 +30,9 @@ yarn create message-kit ```bash [npm] npm init message-kit ``` + ::: +
-
\ No newline at end of file + diff --git a/packages/docs/pages/use-cases/group/agents.mdx b/packages/docs/pages/use-cases/group/agents.mdx index 03b458c6..aba094c7 100644 --- a/packages/docs/pages/use-cases/group/agents.mdx +++ b/packages/docs/pages/use-cases/group/agents.mdx @@ -103,7 +103,14 @@ export async function textGeneration(userPrompt: string, systemPrompt: string) { role: "assistant", content: reply || "No response from OpenAI.", }); - return { reply: reply as string, history: messages }; + const cleanedReply = reply + ?.replace(/(\*\*|__)(.*?)\1/g, "$2") + ?.replace(/\[([^\]]+)\]\(([^)]+)\)/g, "$2") + ?.replace(/^#+\s*(.*)$/gm, "$1") + ?.replace(/`([^`]+)`/g, "$1") + ?.replace(/^`|`$/g, ""); + + return { reply: cleanedReply as string, history: messages }; } catch (error) { console.error("Failed to fetch from OpenAI:", error); throw error; diff --git a/packages/docs/pages/use-cases/group/index.mdx b/packages/docs/pages/use-cases/group/index.mdx index 9f064741..032a3dc9 100644 --- a/packages/docs/pages/use-cases/group/index.mdx +++ b/packages/docs/pages/use-cases/group/index.mdx @@ -303,6 +303,7 @@ const commandHandlers: CommandHandlers = { "- 💧 faucetbot.eth : Delivers Faucet funds to devs on Testnet\n\n\n" + "- 🛍️ thegeneralstore.eth : Simple ecommerce storefront for hackathon goods\n\n\n" + "- 📅 wordlebot.eth : Play daily to the WORDLE game through messaging.\n\n\n" + + "- 💱 swapbot.converse.xyz : Swap tokens through messaging.\n\n\n" + "- 🪨 urltomint.eth : Turn a Zora url into a mint frame.\n\n\n" + "To learn how to build your own app, visit MessageKit: https://message-kit.vercel.app/\n\n" + "To publish your app, visit Directory: https://message-kit.vercel.app/directory\n\n" + diff --git a/packages/docs/pages/use-cases/group/payments.mdx b/packages/docs/pages/use-cases/group/payments.mdx index 708f6024..e3ddfef9 100644 --- a/packages/docs/pages/use-cases/group/payments.mdx +++ b/packages/docs/pages/use-cases/group/payments.mdx @@ -90,41 +90,36 @@ const openai = new OpenAI({ }); export async function textGeneration(userPrompt: string, systemPrompt: string) { - let messages = [ - { - role: "system", - content: systemPrompt, - }, - ]; + let messages = []; + messages.push({ + role: "system", + content: systemPrompt, + }); + messages.push({ + role: "user", + content: userPrompt, + }); try { - if (userPrompt.toLowerCase() === "stop") { - // Reset the conversation state - messages = [ - { - role: "system", - content: systemPrompt, - }, - ]; - } else { - // Add the user's message to the conversation history - messages.push({ - role: "user", - content: userPrompt, - }); - } + const response = await openai.chat.completions.create({ model: "gpt-4o", messages: messages as any, }); const reply = response.choices[0].message.content; - // Add the assistant's reply to the conversation history messages.push({ role: "assistant", content: reply || "No response from OpenAI.", }); - return { reply: reply as string, history: messages }; + const cleanedReply = reply + ?.replace(/(\*\*|__)(.*?)\1/g, "$2") + ?.replace(/\[([^\]]+)\]\(([^)]+)\)/g, "$2") + ?.replace(/^#+\s*(.*)$/gm, "$1") + ?.replace(/`([^`]+)`/g, "$1") + ?.replace(/^`|`$/g, ""); + + return { reply: cleanedReply as string, history: messages }; } catch (error) { console.error("Failed to fetch from OpenAI:", error); throw error; diff --git a/packages/docs/public/hero.jpg b/packages/docs/public/hero.jpg new file mode 100644 index 00000000..fce147d1 Binary files /dev/null and b/packages/docs/public/hero.jpg differ diff --git a/packages/docs/public/img/railway/volume.png b/packages/docs/public/img/railway/volume.png new file mode 100644 index 00000000..e3163f5b Binary files /dev/null and b/packages/docs/public/img/railway/volume.png differ diff --git a/packages/docs/styles.css b/packages/docs/styles.css index 48a49b07..9cc1fc9f 100644 --- a/packages/docs/styles.css +++ b/packages/docs/styles.css @@ -12,7 +12,7 @@ .custom-homepage-headline { margin-bottom: 1rem; - text-align: left; + text-align: center; max-width: 800px; margin-left: auto; margin-right: auto; @@ -41,7 +41,7 @@ .custom-homepage-subhead { margin-bottom: 1rem; - text-align: left; + text-align: center; max-width: 800px; margin-left: auto; margin-right: auto; @@ -99,11 +99,21 @@ } :root { - --tile-external-icon-color: rgba(76, 76, 76, 0.7); /* Default color for light mode */ + --tile-external-icon-color: rgba( + 76, + 76, + 76, + 0.7 + ); /* Default color for light mode */ } .dark { - --tile-external-icon-color: rgba(207, 207, 207, 0.7); /* Use the text color in dark mode */ + --tile-external-icon-color: rgba( + 207, + 207, + 207, + 0.7 + ); /* Use the text color in dark mode */ } .custom-homepage-tile-external-icon { @@ -113,13 +123,17 @@ width: 0.75rem; height: 0.75rem; opacity: 0.7; - transition: opacity 0.3s ease, filter 0.3s ease; - filter: invert(0%) sepia(0%) saturate(0%) hue-rotate(0deg) brightness(0%) contrast(100%); + transition: + opacity 0.3s ease, + filter 0.3s ease; + filter: invert(0%) sepia(0%) saturate(0%) hue-rotate(0deg) brightness(0%) + contrast(100%); display: block; /* Add this line */ } .dark .custom-homepage-tile-external-icon { - filter: invert(100%) sepia(0%) saturate(0%) hue-rotate(0deg) brightness(100%) contrast(100%); + filter: invert(100%) sepia(0%) saturate(0%) hue-rotate(0deg) brightness(100%) + contrast(100%); } .custom-homepage-tile-title { @@ -137,7 +151,12 @@ /* Dark mode adjustments */ .dark .custom-homepage-tile { - background-color: rgba(255, 255, 255, 0.01); /* Slightly lighter than the background */ + background-color: rgba( + 255, + 255, + 255, + 0.01 + ); /* Slightly lighter than the background */ border-color: rgba(255, 255, 255, 0.1); box-shadow: 0 1px 3px rgba(255, 255, 255, 0.05); } @@ -147,4 +166,10 @@ background-color: rgba(255, 255, 255, 0.01); /* Slightly lighter on hover */ box-shadow: 0 4px 12px rgba(255, 255, 255, 0.02); border-color: var(--vocs-color-primary); -} \ No newline at end of file +} + +.custom-homepage-tile-grid-container .vocs_Tabs_list { + display: flex; + justify-content: center; + outline: none; /* Keep existing style */ +} diff --git a/packages/docs/vocs.config.tsx b/packages/docs/vocs.config.tsx index 6d71bd88..39cfbea3 100644 --- a/packages/docs/vocs.config.tsx +++ b/packages/docs/vocs.config.tsx @@ -13,6 +13,10 @@ export default defineConfig({ ); }, + ogImageUrl: { + "/": "/hero.jpg", + "/docs": "/hero.jpg", + }, title: "MessageKit", iconUrl: "/messagekit-logo.png", rootDir: ".", diff --git a/packages/message-kit/package.json b/packages/message-kit/package.json index 872580aa..d7d8f212 100644 --- a/packages/message-kit/package.json +++ b/packages/message-kit/package.json @@ -1,6 +1,6 @@ { "name": "@xmtp/message-kit", - "version": "0.0.19", + "version": "0.0.24-beta.0", "license": "MIT", "type": "module", "exports": { diff --git a/packages/message-kit/rollup.config.js b/packages/message-kit/rollup.config.js index 769f31d6..588a7cd5 100644 --- a/packages/message-kit/rollup.config.js +++ b/packages/message-kit/rollup.config.js @@ -13,8 +13,10 @@ const external = [ "ethers", "@xmtp/proto", "@xmtp/grpc-api-client", + "path", "viem", "viem/accounts", + "fs/promises", "fs", "viem/chains", "dotenv/config", diff --git a/packages/message-kit/src/helpers/openai.ts b/packages/message-kit/src/helpers/openai.ts index f0a6c73d..7e288626 100644 --- a/packages/message-kit/src/helpers/openai.ts +++ b/packages/message-kit/src/helpers/openai.ts @@ -24,7 +24,14 @@ export async function textGeneration(userPrompt: string, systemPrompt: string) { role: "assistant", content: reply || "No response from OpenAI.", }); - return { reply: reply as string, history: messages }; + const cleanedReply = reply + ?.replace(/(\*\*|__)(.*?)\1/g, "$2") + ?.replace(/\[([^\]]+)\]\(([^)]+)\)/g, "$2") + ?.replace(/^#+\s*(.*)$/gm, "$1") + ?.replace(/`([^`]+)`/g, "$1") + ?.replace(/^`|`$/g, ""); + + return { reply: cleanedReply as string, history: messages }; } catch (error) { console.error("Failed to fetch from OpenAI:", error); throw error; diff --git a/packages/message-kit/src/helpers/types.ts b/packages/message-kit/src/helpers/types.ts index 12c4a70e..a7db85bf 100644 --- a/packages/message-kit/src/helpers/types.ts +++ b/packages/message-kit/src/helpers/types.ts @@ -7,6 +7,7 @@ export type MessageAbstracted = { id: string; sent: Date; content: any; + version: string; sender: { inboxId: string; username: string; diff --git a/packages/message-kit/src/lib/handlerContext.ts b/packages/message-kit/src/lib/handlerContext.ts index b548e20f..efeb2ca7 100644 --- a/packages/message-kit/src/lib/handlerContext.ts +++ b/packages/message-kit/src/lib/handlerContext.ts @@ -4,6 +4,8 @@ import { Client as ClientV2, Conversation as ConversationV2, } from "@xmtp/xmtp-js"; +import fs from "fs/promises"; + import type { Reaction } from "@xmtp/content-type-reaction"; import { populateUsernames } from "../helpers/usernames.js"; import { ContentTypeText } from "@xmtp/content-type-text"; @@ -35,7 +37,6 @@ export default class HandlerContext { members?: User[]; commandHandlers?: CommandHandlers; getMessageById!: (id: string) => DecodedMessage | null; - private constructor( conversation: Conversation | ConversationV2, { client, v2client }: { client: Client; v2client: ClientV2 }, @@ -123,18 +124,9 @@ export default class HandlerContext { sender: sender, typeId: message.contentType.typeId, sent: sentAt, + version: version as string, }; - if (process?.env?.MSG_LOG) { - //trim spaces from text - let content = - typeof message?.content === "string" - ? message?.content - : message?.contentType.typeId; - - console.log("content", content, senderAddress); - } - return context; } else { context.message = { @@ -148,34 +140,71 @@ export default class HandlerContext { }, typeId: "new_" + (context.isGroup ? "group" : "conversation"), sent: conversation.createdAt, + version: version as string, }; } - return context; } - async getReplyChain(reference: string): Promise<{ - messageChain: string; - receiverFromChain: string; + async getV2MessageById(reference: string): Promise { + const conversations = await this.v2client.conversations.list(); + for (const conversation of conversations) { + const messages = await conversation.messages(); + if (messages.find((m) => m.id === reference)) { + return messages.find((m) => m.id === reference) as DecodedMessageV2; + } + } + return null; + } + + async getReplyChain( + reference: string, + version: "v2" | "v3", + botAddress?: string, + ): Promise<{ + chain: Array<{ address: string; content: string }>; + isSenderInChain: boolean; }> { - const msg = await this.getMessageById(reference); - let receiver = this.members?.find( - (member) => member.inboxId === msg?.senderInboxId, + let msg: DecodedMessage | DecodedMessageV2 | null = null; + let senderAddress: string = ""; + + if (version === "v3") msg = await this.getMessageById(reference); + else if (version === "v2") msg = await this.getV2MessageById(reference); + + if (!msg) { + return { + chain: [], + isSenderInChain: false, + }; + } + + let sender = this.members?.find( + (member) => + member.inboxId === (msg as DecodedMessage).senderInboxId || + member.address === (msg as DecodedMessageV2).senderAddress, ); - if (!msg) return { messageChain: "", receiverFromChain: "" }; - let chain = `${msg?.content?.content ?? msg?.content}\n\n`; + senderAddress = sender?.address ?? ""; + + let content = msg?.content?.content ?? msg?.content; + let isSenderBot = senderAddress.toLowerCase() === botAddress?.toLowerCase(); + let chain = [{ address: senderAddress, content: content }]; if (msg?.content?.reference) { - const { messageChain, receiverFromChain } = await this.getReplyChain( - msg?.content?.reference, - ); - receiver = this.members?.find( - (member) => member.address === receiverFromChain, + const { chain: replyChain, isSenderInChain } = await this.getReplyChain( + msg.content.reference, + version, + botAddress, ); - chain = `${messageChain}\nUser:${chain}`; + chain = replyChain; + isSenderBot = isSenderBot || isSenderInChain; + + chain.push({ + address: senderAddress, + content: content, + }); } return { - messageChain: chain, - receiverFromChain: receiver?.address ?? "", + chain: chain, + isSenderInChain: isSenderBot, }; } async reply(message: string) { @@ -228,6 +257,18 @@ export default class HandlerContext { } } + async getCacheCreationDate() { + //Gets the creation date of the cache folder + //Could be used to check if the cache is outdated + //Generally indicates the deployment date of the bot + try { + const stats = await fs.stat(".cache"); + const cacheCreationDate = new Date(stats.birthtime); + return cacheCreationDate; + } catch (err) { + console.error(err); + } + } async sendTo(message: string, receivers: string[]) { const conversations = await this.v2client.conversations.list(); //Sends a 1 to 1 to multiple users diff --git a/packages/message-kit/src/lib/runner.ts b/packages/message-kit/src/lib/runner.ts index 13a20be1..815e287d 100644 --- a/packages/message-kit/src/lib/runner.ts +++ b/packages/message-kit/src/lib/runner.ts @@ -38,6 +38,16 @@ export default async function run(handler: Handler, config?: Config) { return; } + if (process?.env?.MSG_LOG === "true") { + console.log( + `msg_${version}:`, + typeof message?.content === "string" + ? message?.content.substring(0, 20) + + (message?.content.length > 20 ? "..." : "") + : message?.contentType?.typeId ?? + message?.content?.contentType?.typeId, + ); + } const context = await HandlerContext.create( conversation, message, @@ -60,15 +70,6 @@ export default async function run(handler: Handler, config?: Config) { const stream = await client.conversations.streamAllMessages(); try { for await (const message of stream) { - if (process?.env?.MSG_LOG) { - console.log( - `incoming_${version}:`, - typeof message?.content === "string" - ? message?.content - : message?.contentType.typeId, - ); - } - const conversation = await client.conversations.getConversationById( message?.conversationId ?? "", ); @@ -83,15 +84,6 @@ export default async function run(handler: Handler, config?: Config) { const stream = await v2client.conversations.streamAllMessages(); try { for await (const message of stream) { - if (process?.env?.MSG_LOG) { - console.log( - `incoming_${version}:`, - typeof message?.content === "string" - ? message?.content - : message?.contentType.typeId, - ); - } - handleMessage(version, message, message.conversation); } } catch (e) { @@ -107,8 +99,6 @@ export default async function run(handler: Handler, config?: Config) { const stream = await client.conversations.stream(); try { for await (const conversation of stream) { - if (process?.env?.MSG_LOG) - console.log(`incoming_${version}`, conversation?.id); handleConversation(version, conversation); } } catch (e) { @@ -120,8 +110,6 @@ export default async function run(handler: Handler, config?: Config) { const stream = await v2client.conversations.stream(); try { for await (const conversation of stream) { - if (process?.env?.MSG_LOG) - console.log(`incoming_${version}`, conversation?.topic); handleConversation(version, conversation); } } catch (e) { @@ -135,6 +123,9 @@ export default async function run(handler: Handler, config?: Config) { conversation: any, ) => { if (conversation) { + if (process?.env?.MSG_LOG === "true") + console.log(`conv_${version}`, conversation?.id ?? conversation.topic); + try { const context = await HandlerContext.create( conversation,