diff --git a/.env.example b/.env.example
index 79fa2fe75ef..feb720cd5f6 100644
--- a/.env.example
+++ b/.env.example
@@ -1,39 +1,38 @@
# Discord Configuration
DISCORD_APPLICATION_ID=
-DISCORD_API_TOKEN= # Bot token
-DISCORD_VOICE_CHANNEL_ID= # The ID of the voice channel the bot should join (optional)
+DISCORD_API_TOKEN= # Bot token
+DISCORD_VOICE_CHANNEL_ID= # The ID of the voice channel the bot should join (optional)
# AI Model API Keys
-OPENAI_API_KEY= # OpenAI API key, starting with sk-
-SMALL_OPENAI_MODEL= # Default: gpt-4o-mini
-MEDIUM_OPENAI_MODEL= # Default: gpt-4o
-LARGE_OPENAI_MODEL= # Default: gpt-4o
-EMBEDDING_OPENAI_MODEL= # Default: text-embedding-3-small
-IMAGE_OPENAI_MODEL= # Default: dall-e-3
+OPENAI_API_KEY= # OpenAI API key, starting with sk-
+SMALL_OPENAI_MODEL= # Default: gpt-4o-mini
+MEDIUM_OPENAI_MODEL= # Default: gpt-4o
+LARGE_OPENAI_MODEL= # Default: gpt-4o
+EMBEDDING_OPENAI_MODEL= # Default: text-embedding-3-small
+IMAGE_OPENAI_MODEL= # Default: dall-e-3
# Eternal AI's Decentralized Inference API
ETERNALAI_URL=
-ETERNALAI_MODEL= #Default: "neuralmagic/Meta-Llama-3.1-405B-Instruct-quantized.w4a16"
+ETERNALAI_MODEL= # Default: "neuralmagic/Meta-Llama-3.1-405B-Instruct-quantized.w4a16"
ETERNALAI_API_KEY=
-GROK_API_KEY= # GROK API Key
-GROQ_API_KEY= # Starts with gsk_
+GROK_API_KEY= # GROK API Key
+GROQ_API_KEY= # Starts with gsk_
OPENROUTER_API_KEY=
-GOOGLE_GENERATIVE_AI_API_KEY= # Gemini API key
+GOOGLE_GENERATIVE_AI_API_KEY= # Gemini API key
-ALI_BAILIAN_API_KEY= # Ali Bailian API Key
-VOLENGINE_API_KEY= # VolEngine API Key
-NANOGPT_API_KEY= # NanoGPT API Key
+ALI_BAILIAN_API_KEY= # Ali Bailian API Key
+NANOGPT_API_KEY= # NanoGPT API Key
-HYPERBOLIC_API_KEY= # Hyperbolic API Key
+HYPERBOLIC_API_KEY= # Hyperbolic API Key
HYPERBOLIC_MODEL=
-IMAGE_HYPERBOLIC_MODEL= # Default: FLUX.1-dev
-SMALL_HYPERBOLIC_MODEL= # Default: meta-llama/Llama-3.2-3B-Instruct
-MEDIUM_HYPERBOLIC_MODEL= # Default: meta-llama/Meta-Llama-3.1-70B-Instruct
-LARGE_HYPERBOLIC_MODEL= # Default: meta-llama/Meta-Llama-3.1-405-Instruct
+IMAGE_HYPERBOLIC_MODEL= # Default: FLUX.1-dev
+SMALL_HYPERBOLIC_MODEL= # Default: meta-llama/Llama-3.2-3B-Instruct
+MEDIUM_HYPERBOLIC_MODEL= # Default: meta-llama/Meta-Llama-3.1-70B-Instruct
+LARGE_HYPERBOLIC_MODEL= # Default: meta-llama/Meta-Llama-3.1-405-Instruct
# Speech Synthesis
-ELEVENLABS_XI_API_KEY= # API key from elevenlabs
+ELEVENLABS_XI_API_KEY= # API key from elevenlabs
# ElevenLabs Settings
ELEVENLABS_MODEL_ID=eleven_multilingual_v2
@@ -47,21 +46,23 @@ ELEVENLABS_OUTPUT_FORMAT=pcm_16000
# Twitter/X Configuration
TWITTER_DRY_RUN=false
-TWITTER_USERNAME= # Account username
-TWITTER_PASSWORD= # Account password
-TWITTER_EMAIL= # Account email
+TWITTER_USERNAME= # Account username
+TWITTER_PASSWORD= # Account password
+TWITTER_EMAIL= # Account email
TWITTER_2FA_SECRET=
-TWITTER_COOKIES= # Account cookies
-TWITTER_POLL_INTERVAL=120 # How often (in seconds) the bot should check for interactions
-TWITTER_SEARCH_ENABLE=FALSE # Enable timeline search, WARNING this greatly increases your chance of getting banned
-TWITTER_TARGET_USERS= # Comma separated list of Twitter user names to interact with
+
+TWITTER_COOKIES= # Account cookies
+TWITTER_POLL_INTERVAL=120 # How often (in seconds) the bot should check for interactions
+TWITTER_SEARCH_ENABLE=FALSE # Enable timeline search, WARNING this greatly increases your chance of getting banned
+TWITTER_TARGET_USERS= # Comma separated list of Twitter user names to interact with
+
X_SERVER_URL=
XAI_API_KEY=
XAI_MODEL=
# Post Interval Settings (in minutes)
-POST_INTERVAL_MIN= # Default: 90
-POST_INTERVAL_MAX= # Default: 180
+POST_INTERVAL_MIN= # Default: 90
+POST_INTERVAL_MAX= # Default: 180
POST_IMMEDIATELY=
# Twitter action processing configuration
@@ -69,82 +70,85 @@ ACTION_INTERVAL=300000 # Interval in milliseconds between action processing
ENABLE_ACTION_PROCESSING=false # Set to true to enable the action processing loop
# Feature Flags
-IMAGE_GEN= # Set to TRUE to enable image generation
-USE_OPENAI_EMBEDDING= # Set to TRUE for OpenAI/1536, leave blank for local
-USE_OLLAMA_EMBEDDING= # Set to TRUE for OLLAMA/1024, leave blank for local
+IMAGE_GEN= # Set to TRUE to enable image generation
+USE_OPENAI_EMBEDDING= # Set to TRUE for OpenAI/1536, leave blank for local
+USE_OLLAMA_EMBEDDING= # Set to TRUE for OLLAMA/1024, leave blank for local
# OpenRouter Models
-OPENROUTER_MODEL= # Default: uses hermes 70b/405b
+OPENROUTER_MODEL= # Default: uses hermes 70b/405b
SMALL_OPENROUTER_MODEL=
MEDIUM_OPENROUTER_MODEL=
LARGE_OPENROUTER_MODEL=
# REDPILL Configuration
# https://docs.red-pill.ai/get-started/supported-models
-REDPILL_API_KEY= # REDPILL API Key
+REDPILL_API_KEY= # REDPILL API Key
REDPILL_MODEL=
-SMALL_REDPILL_MODEL= # Default: gpt-4o-mini
-MEDIUM_REDPILL_MODEL= # Default: gpt-4o
-LARGE_REDPILL_MODEL= # Default: gpt-4o
+SMALL_REDPILL_MODEL= # Default: gpt-4o-mini
+MEDIUM_REDPILL_MODEL= # Default: gpt-4o
+LARGE_REDPILL_MODEL= # Default: gpt-4o
+
+# Grok Configuration
+SMALL_GROK_MODEL= # Default: grok-2-1212
+MEDIUM_GROK_MODEL= # Default: grok-2-1212
+LARGE_GROK_MODEL= # Default: grok-2-1212
+EMBEDDING_GROK_MODEL= # Default: grok-2-1212
# Ollama Configuration
-OLLAMA_SERVER_URL= # Default: localhost:11434
+OLLAMA_SERVER_URL= # Default: localhost:11434
OLLAMA_MODEL=
-OLLAMA_EMBEDDING_MODEL= # Default: mxbai-embed-large
-SMALL_OLLAMA_MODEL= # Default: llama3.2
-MEDIUM_OLLAMA_MODEL= # Default: hermes3
-LARGE_OLLAMA_MODEL= # Default: hermes3:70b
+OLLAMA_EMBEDDING_MODEL= # Default: mxbai-embed-large
+SMALL_OLLAMA_MODEL= # Default: llama3.2
+MEDIUM_OLLAMA_MODEL= # Default: hermes3
+LARGE_OLLAMA_MODEL= # Default: hermes3:70b
# Google Configuration
GOOGLE_MODEL=
-SMALL_GOOGLE_MODEL= # Default: gemini-1.5-flash-latest
-MEDIUM_GOOGLE_MODEL= # Default: gemini-1.5-flash-latest
-LARGE_GOOGLE_MODEL= # Default: gemini-1.5-pro-latest
-EMBEDDING_GOOGLE_MODEL= # Default: text-embedding-004
+SMALL_GOOGLE_MODEL= # Default: gemini-1.5-flash-latest
+MEDIUM_GOOGLE_MODEL= # Default: gemini-1.5-flash-latest
+LARGE_GOOGLE_MODEL= # Default: gemini-1.5-pro-latest
+EMBEDDING_GOOGLE_MODEL= # Default: text-embedding-004
# Groq Configuration
-SMALL_GROQ_MODEL= # Default: llama-3.1-8b-instant
-MEDIUM_GROQ_MODEL= # Default: llama-3.3-70b-versatile
-LARGE_GROQ_MODEL= # Default: llama-3.2-90b-vision-preview
-EMBEDDING_GROQ_MODEL= # Default: llama-3.1-8b-instant
-
-# NanoGPT Configuration
-SMALL_NANOGPT_MODEL= # Default: gpt-4o-mini
-MEDIUM_NANOGPT_MODEL= # Default: gpt-4o
-LARGE_NANOGPT_MODEL= # Default: gpt-4o
+SMALL_GROQ_MODEL= # Default: llama-3.1-8b-instant
+MEDIUM_GROQ_MODEL= # Default: llama-3.3-70b-versatile
+LARGE_GROQ_MODEL= # Default: llama-3.2-90b-vision-preview
+EMBEDDING_GROQ_MODEL= # Default: llama-3.1-8b-instant
-#LlamaLocal Configuration
-LLAMALOCAL_PATH= # Default: "" which is the current directory in plugin-node/dist/ which gets destroyed and recreated on every build
+# LlamaLocal Configuration
+LLAMALOCAL_PATH= # Default: "" which is the current directory in plugin-node/dist/ which gets destroyed and recreated on every build
-# API Keys
-ANTHROPIC_API_KEY= # For Claude
-SMALL_ANTHROPIC_MODEL= # Default: claude-3-haiku-20240307
-MEDIUM_ANTHROPIC_MODEL= # Default: claude-3-5-sonnet-20241022
-LARGE_ANTHROPIC_MODEL= # Default: claude-3-5-sonnet-20241022
-
-HEURIST_API_KEY= # Get from https://heurist.ai/dev-access
-
-# Heurist Models
-SMALL_HEURIST_MODEL= # Default: meta-llama/llama-3-70b-instruct
-MEDIUM_HEURIST_MODEL= # Default: meta-llama/llama-3-70b-instruct
-LARGE_HEURIST_MODEL= # Default: meta-llama/llama-3.1-405b-instruct
-HEURIST_IMAGE_MODEL= # Default: PepeXL
+# NanoGPT Configuration
+SMALL_NANOGPT_MODEL= # Default: gpt-4o-mini
+MEDIUM_NANOGPT_MODEL= # Default: gpt-4o
+LARGE_NANOGPT_MODEL= # Default: gpt-4o
+
+# Anthropic Configuration
+ANTHROPIC_API_KEY= # For Claude
+SMALL_ANTHROPIC_MODEL= # Default: claude-3-haiku-20240307
+MEDIUM_ANTHROPIC_MODEL= # Default: claude-3-5-sonnet-20241022
+LARGE_ANTHROPIC_MODEL= # Default: claude-3-5-sonnet-20241022
+
+# Heurist Configuration
+HEURIST_API_KEY= # Get from https://heurist.ai/dev-access
+SMALL_HEURIST_MODEL= # Default: meta-llama/llama-3-70b-instruct
+MEDIUM_HEURIST_MODEL= # Default: meta-llama/llama-3-70b-instruct
+LARGE_HEURIST_MODEL= # Default: meta-llama/llama-3.1-405b-instruct
+HEURIST_IMAGE_MODEL= # Default: PepeXL
# Gaianet Configuration
GAIANET_MODEL=
GAIANET_SERVER_URL=
-SMALL_GAIANET_MODEL= # Default: llama3b
-SMALL_GAIANET_SERVER_URL= # Default: https://llama3b.gaia.domains/v1
-
-MEDIUM_GAIANET_MODEL= # Default: llama
-MEDIUM_GAIANET_SERVER_URL= # Default: https://llama8b.gaia.domains/v1
-
-LARGE_GAIANET_MODEL= # Default: qwen72b
-LARGE_GAIANET_SERVER_URL= # Default: https://qwen72b.gaia.domains/v1
+SMALL_GAIANET_MODEL= # Default: llama3b
+SMALL_GAIANET_SERVER_URL= # Default: https://llama3b.gaia.domains/v1
+MEDIUM_GAIANET_MODEL= # Default: llama
+MEDIUM_GAIANET_SERVER_URL= # Default: https://llama8b.gaia.domains/v1
+LARGE_GAIANET_MODEL= # Default: qwen72b
+LARGE_GAIANET_SERVER_URL= # Default: https://qwen72b.gaia.domains/v1
GAIANET_EMBEDDING_MODEL=
-USE_GAIANET_EMBEDDING= # Set to TRUE for GAIANET/768, leave blank for local
+USE_GAIANET_EMBEDDING= # Set to TRUE for GAIANET/768, leave blank for local
# EVM
EVM_PRIVATE_KEY=
@@ -153,6 +157,10 @@ EVM_PROVIDER_URL=
# Solana
SOLANA_PRIVATE_KEY=
SOLANA_PUBLIC_KEY=
+SOLANA_CLUSTER= # Default: devnet. Solana Cluster: 'devnet' | 'testnet' | 'mainnet-beta'
+SOLANA_ADMIN_PRIVATE_KEY= # This wallet is used to verify NFTs
+SOLANA_ADMIN_PUBLIC_KEY= # This wallet is used to verify NFTs
+SOLANA_VERIFY_TOKEN= # Authentication token for calling the verification API
# Fallback Wallet Configuration (deprecated)
WALLET_PRIVATE_KEY=
@@ -185,21 +193,28 @@ STARKNET_RPC_URL=
INTIFACE_WEBSOCKET_URL=ws://localhost:12345
# Farcaster Neynar Configuration
-FARCASTER_FID= # the FID associated with the account your are sending casts from
+FARCASTER_FID= # The FID associated with the account your are sending casts from
FARCASTER_NEYNAR_API_KEY= # Neynar API key: https://neynar.com/
-FARCASTER_NEYNAR_SIGNER_UUID= # signer for the account you are sending casts from. create a signer here: https://dev.neynar.com/app
+FARCASTER_NEYNAR_SIGNER_UUID= # Signer for the account you are sending casts from. Create a signer here: https://dev.neynar.com/app
FARCASTER_DRY_RUN=false # Set to true if you want to run the bot without actually publishing casts
FARCASTER_POLL_INTERVAL=120 # How often (in seconds) the bot should check for farcaster interactions (replies and mentions)
# Coinbase
-COINBASE_COMMERCE_KEY= # from coinbase developer portal
-COINBASE_API_KEY= # from coinbase developer portal
-COINBASE_PRIVATE_KEY= # from coinbase developer portal
-# if not configured it will be generated and written to runtime.character.settings.secrets.COINBASE_GENERATED_WALLET_ID and runtime.character.settings.secrets.COINBASE_GENERATED_WALLET_HEX_SEED
-COINBASE_GENERATED_WALLET_ID= # not your address but the wallet id from generating a wallet through the plugin
-COINBASE_GENERATED_WALLET_HEX_SEED= # not your address but the wallet hex seed from generating a wallet through the plugin and calling export
-# for webhook plugin the uri you want to send the webhook to for dummy ones use https://webhook.site
-COINBASE_NOTIFICATION_URI=
+COINBASE_COMMERCE_KEY= # From Coinbase developer portal
+COINBASE_API_KEY= # From Coinbase developer portal
+COINBASE_PRIVATE_KEY= # From Coinbase developer portal
+COINBASE_GENERATED_WALLET_ID= # Not your address but the wallet ID from generating a wallet through the plugin
+COINBASE_GENERATED_WALLET_HEX_SEED= # Not your address but the wallet hex seed from generating a wallet through the plugin and calling export
+COINBASE_NOTIFICATION_URI= # For webhook plugin the uri you want to send the webhook to for dummy ones use https://webhook.site
+
+# Coinbase Charity Configuration
+IS_CHARITABLE=false # Set to true to enable charity donations
+CHARITY_ADDRESS_BASE=0x1234567890123456789012345678901234567890
+CHARITY_ADDRESS_SOL=pWvDXKu6CpbKKvKQkZvDA66hgsTB6X2AgFxksYogHLV
+CHARITY_ADDRESS_ETH=0x750EF1D7a0b4Ab1c97B7A623D7917CcEb5ea779C
+CHARITY_ADDRESS_ARB=0x1234567890123456789012345678901234567890
+CHARITY_ADDRESS_POL=0x1234567890123456789012345678901234567890
+
# Conflux Configuration
CONFLUX_CORE_PRIVATE_KEY=
CONFLUX_CORE_SPACE_RPC_URL=
@@ -207,7 +222,7 @@ CONFLUX_ESPACE_PRIVATE_KEY=
CONFLUX_ESPACE_RPC_URL=
CONFLUX_MEME_CONTRACT_ADDRESS=
-#ZeroG
+# ZeroG
ZEROG_INDEXER_RPC=
ZEROG_EVM_RPC=
ZEROG_PRIVATE_KEY=
@@ -219,37 +234,71 @@ ZEROG_FLOW_ADDRESS=
# - DOCKER: Uses simulator at host.docker.internal:8090 (for docker development)
# - PRODUCTION: No simulator, uses production endpoints
# Defaults to OFF if not specified
-TEE_MODE=OFF #LOCAL|DOCKER|PRODUCTION
-WALLET_SECRET_SALT= # ONLY DEFINE IF YOU WANT TO USE TEE Plugin, otherwise it will throw errors
+TEE_MODE=OFF # LOCAL | DOCKER | PRODUCTION
+WALLET_SECRET_SALT= # ONLY define if you want to use TEE Plugin, otherwise it will throw errors
# Galadriel Configuration
-GALADRIEL_API_KEY=gal-* # Get from https://dashboard.galadriel.com/
+GALADRIEL_API_KEY=gal-* # Get from https://dashboard.galadriel.com/
+
+# Venice Configuration
+VENICE_API_KEY= # generate from venice settings
+SMALL_VENICE_MODEL= # Default: llama-3.3-70b
+MEDIUM_VENICE_MODEL= # Default: llama-3.3-70b
+LARGE_VENICE_MODEL= # Default: llama-3.1-405b
+IMAGE_VENICE_MODEL= # Default: fluently-xl
# fal.ai Configuration
FAL_API_KEY=
FAL_AI_LORA_PATH=
# WhatsApp Cloud API Configuration
-WHATSAPP_ACCESS_TOKEN= # Permanent access token from Facebook Developer Console
-WHATSAPP_PHONE_NUMBER_ID= # Phone number ID from WhatsApp Business API
-WHATSAPP_BUSINESS_ACCOUNT_ID= # Business Account ID from Facebook Business Manager
-WHATSAPP_WEBHOOK_VERIFY_TOKEN= # Custom string for webhook verification
-WHATSAPP_API_VERSION=v17.0 # WhatsApp API version (default: v17.0)
+WHATSAPP_ACCESS_TOKEN= # Permanent access token from Facebook Developer Console
+WHATSAPP_PHONE_NUMBER_ID= # Phone number ID from WhatsApp Business API
+WHATSAPP_BUSINESS_ACCOUNT_ID= # Business Account ID from Facebook Business Manager
+WHATSAPP_WEBHOOK_VERIFY_TOKEN= # Custom string for webhook verification
+WHATSAPP_API_VERSION=v17.0 # WhatsApp API version (default: v17.0)
# Flow Blockchain Configuration
FLOW_ADDRESS=
-FLOW_PRIVATE_KEY= # Private key for SHA3-256 + P256 ECDSA
-FLOW_NETWORK= # Default: mainnet
-FLOW_ENDPOINT_URL= # Default: https://mainnet.onflow.org
+FLOW_PRIVATE_KEY= # Private key for SHA3-256 + P256 ECDSA
+FLOW_NETWORK= # Default: mainnet
+FLOW_ENDPOINT_URL= # Default: https://mainnet.onflow.org
# ICP
INTERNET_COMPUTER_PRIVATE_KEY=
INTERNET_COMPUTER_ADDRESS=
# Aptos
-APTOS_PRIVATE_KEY= # Aptos private key
-APTOS_NETWORK= # must be one of mainnet, testnet
+APTOS_PRIVATE_KEY= # Aptos private key
+APTOS_NETWORK= # Must be one of mainnet, testnet
+
+# EchoChambers Configuration
+ECHOCHAMBERS_API_URL=http://127.0.0.1:3333
+ECHOCHAMBERS_API_KEY=testingkey0011
+ECHOCHAMBERS_USERNAME=eliza
+ECHOCHAMBERS_DEFAULT_ROOM=general
+ECHOCHAMBERS_POLL_INTERVAL=60
+ECHOCHAMBERS_MAX_MESSAGES=10
+
+# MultiversX
+MVX_PRIVATE_KEY= # Multiversx private key
+MVX_NETWORK= # must be one of mainnet, devnet, testnet
+
+# NEAR
+NEAR_WALLET_SECRET_KEY=
+NEAR_WALLET_PUBLIC_KEY=
+NEAR_ADDRESS=
+SLIPPAGE=1
+RPC_URL=https://rpc.testnet.near.org
+NEAR_NETWORK=testnet # or mainnet
+# ZKsync Era Configuration
+ZKSYNC_ADDRESS=
+ZKSYNC_PRIVATE_KEY=
+
+# Ton
+TON_PRIVATE_KEY= # Ton Mnemonic Seed Phrase Join With Empty String
+TON_RPC_URL= # ton rpc
# AWS S3 Configuration Settings for File Upload
AWS_ACCESS_KEY_ID=
@@ -257,3 +306,16 @@ AWS_SECRET_ACCESS_KEY=
AWS_REGION=
AWS_S3_BUCKET=
AWS_S3_UPLOAD_PATH=
+
+# Deepgram
+DEEPGRAM_API_KEY=
+
+# Sui
+SUI_PRIVATE_KEY= # Sui Mnemonic Seed Phrase (`sui keytool generate ed25519`)
+SUI_NETWORK= # must be one of mainnet, testnet, devnet, localnet
+
+# Story
+STORY_PRIVATE_KEY= # Story private key
+STORY_API_BASE_URL= # Story API base URL
+STORY_API_KEY= # Story API key
+PINATA_JWT= # Pinata JWT for uploading files to IPFS
diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml
index 535617fb44e..282d2a94283 100644
--- a/.github/workflows/ci.yaml
+++ b/.github/workflows/ci.yaml
@@ -20,7 +20,7 @@ jobs:
cache: "pnpm"
- name: Install dependencies
- run: pnpm install --no-frozen-lockfile
+ run: pnpm install --frozen-lockfile
- name: Run Prettier
run: pnpm run prettier --check .
@@ -34,7 +34,7 @@ jobs:
echo "NODE_ENV=test" >> packages/core/.env.test
- name: Run tests
- run: cd packages/core && pnpm test
+ run: cd packages/core && pnpm test:coverage
- name: Build packages
run: pnpm run build
diff --git a/.github/workflows/integrationTests.yaml b/.github/workflows/integrationTests.yaml
index cd9441507dd..56f86b7bf37 100644
--- a/.github/workflows/integrationTests.yaml
+++ b/.github/workflows/integrationTests.yaml
@@ -3,7 +3,7 @@ on:
push:
branches:
- "*"
- pull_request:
+ pull_request_target:
branches:
- "*"
jobs:
@@ -38,7 +38,7 @@ jobs:
cache: "pnpm"
- name: Install dependencies
- run: pnpm install -r
+ run: pnpm install -r --frozen-lockfile
- name: Build packages
run: pnpm build
diff --git a/.gitignore b/.gitignore
index b3d84f00fb7..abc23052720 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,6 +3,8 @@ node_modules
.env
.env.production
+.env.local
+.env_main
concatenated-output.ts
embedding-cache.json
packages/plugin-buttplug/intiface-engine
@@ -47,3 +49,6 @@ packages/plugin-coinbase/package-lock.json
tsup.config.bundled_*.mjs
.turbo
+
+coverage
+.eslintcache
diff --git a/.vscode/settings.json b/.vscode/settings.json
index 813a00406e3..bb351d019f2 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -44,4 +44,4 @@
"[shellscript]": {
"editor.defaultFormatter": "foxundermoon.shell-format"
}
-}
+}
\ No newline at end of file
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 4801b48eb56..24f7ac1b746 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,121 @@
# Changelog
+## [v0.1.6-alpha.1](https://github.com/ai16z/eliza/tree/v0.1.6-alpha.1) (2024-12-13)
+
+[Full Changelog](https://github.com/ai16z/eliza/compare/v0.1.5-alpha.5...v0.1.6-alpha.1)
+
+**Implemented enhancements:**
+
+- Add Venice.ai Model Provider [\#1016](https://github.com/ai16z/eliza/issues/1016)
+- Need to add media file upload for posting tweets with image from imageGenerationPlugin. Currently only discord has this implemented [\#969](https://github.com/ai16z/eliza/issues/969)
+- Script to create core memories for the agent [\#967](https://github.com/ai16z/eliza/issues/967)
+- feat: add hot-reloading for agent dependencies [\#930](https://github.com/ai16z/eliza/issues/930)
+- Improve `dev.sh` Script to Enhance Plugin Development Workflow [\#888](https://github.com/ai16z/eliza/issues/888)
+
+**Fixed bugs:**
+
+- How to set the model class for Anthropic? [\#988](https://github.com/ai16z/eliza/issues/988)
+- Twitter Search Client Broken [\#943](https://github.com/ai16z/eliza/issues/943)
+- Stuck querying when @'ing it in Discord [\#921](https://github.com/ai16z/eliza/issues/921)
+- Error pnpm start - Promise.withResolvers\(\): pdfjs-dist [\#902](https://github.com/ai16z/eliza/issues/902)
+- Running tests on start and dev? [\#893](https://github.com/ai16z/eliza/issues/893)
+- build: eliza docs build creates 130 files that want to be modified/added to git [\#849](https://github.com/ai16z/eliza/issues/849)
+- EVM Plugin can't run any action [\#735](https://github.com/ai16z/eliza/issues/735)
+- Bug: plugin-solana crash report [\#467](https://github.com/ai16z/eliza/issues/467)
+
+**Closed issues:**
+
+- Issue: Unable to Post Tweets Using Eliza Integration with Twitter via Cookies or OAuth2 [\#901](https://github.com/ai16z/eliza/issues/901)
+
+**Merged pull requests:**
+
+- chore: release develop into main [\#1045](https://github.com/ai16z/eliza/pull/1045) ([odilitime](https://github.com/odilitime))
+- fix: re-enable generateNewTweetLoop / lint fixes [\#1043](https://github.com/ai16z/eliza/pull/1043) ([odilitime](https://github.com/odilitime))
+- docs: characterfile.md docs outdated with latest eliza version [\#1042](https://github.com/ai16z/eliza/pull/1042) ([tqdpham96](https://github.com/tqdpham96))
+- feat: Add AI Agent Dev School Tutorial Link [\#1038](https://github.com/ai16z/eliza/pull/1038) ([lalalune](https://github.com/lalalune))
+- fix: use pull\_request\_target for integration tests [\#1035](https://github.com/ai16z/eliza/pull/1035) ([jnaulty](https://github.com/jnaulty))
+- feat: Add Discord Team features [\#1032](https://github.com/ai16z/eliza/pull/1032) ([azep-ninja](https://github.com/azep-ninja))
+- feat: client-discord stop implementation / agent improvements [\#1029](https://github.com/ai16z/eliza/pull/1029) ([odilitime](https://github.com/odilitime))
+- chore: Push Develop into Main [\#1028](https://github.com/ai16z/eliza/pull/1028) ([odilitime](https://github.com/odilitime))
+- feat: improve voice processing and add deepgram transcription option [\#1026](https://github.com/ai16z/eliza/pull/1026) ([tcm390](https://github.com/tcm390))
+- docs: Update README.md [\#1025](https://github.com/ai16z/eliza/pull/1025) ([sergical](https://github.com/sergical))
+- docs: Update README.md [\#1024](https://github.com/ai16z/eliza/pull/1024) ([sergical](https://github.com/sergical))
+- chore: Twitter fetchHomeTimeline rework [\#1021](https://github.com/ai16z/eliza/pull/1021) ([odilitime](https://github.com/odilitime))
+- chore: Update CI configuration to enable test coverage and add covera… [\#1019](https://github.com/ai16z/eliza/pull/1019) ([snobbee](https://github.com/snobbee))
+- docs: "AI Agent Dev School Part 4" livestream notes [\#1015](https://github.com/ai16z/eliza/pull/1015) ([YoungPhlo](https://github.com/YoungPhlo))
+- docs: Add templates documentation to the project [\#1013](https://github.com/ai16z/eliza/pull/1013) ([Lukapetro](https://github.com/Lukapetro))
+- feat: Add custom fetch logic for agent [\#1010](https://github.com/ai16z/eliza/pull/1010) ([v1xingyue](https://github.com/v1xingyue))
+- feat: Plugin evm multichain [\#1009](https://github.com/ai16z/eliza/pull/1009) ([nicky-ru](https://github.com/nicky-ru))
+- feat: add venice.ai api model provider [\#1008](https://github.com/ai16z/eliza/pull/1008) ([proteanx](https://github.com/proteanx))
+- feat: improve Twitter client with action processing [\#1007](https://github.com/ai16z/eliza/pull/1007) ([dorianjanezic](https://github.com/dorianjanezic))
+- chore: Bring Develop up to date with HEAD [\#1006](https://github.com/ai16z/eliza/pull/1006) ([odilitime](https://github.com/odilitime))
+- feat: create example folder with example plugin [\#1004](https://github.com/ai16z/eliza/pull/1004) ([monilpat](https://github.com/monilpat))
+- chore: Twitter search switch [\#1003](https://github.com/ai16z/eliza/pull/1003) ([odilitime](https://github.com/odilitime))
+- fix: add callback to action in farcaster client [\#1002](https://github.com/ai16z/eliza/pull/1002) ([sin-bufan](https://github.com/sin-bufan))
+- fix: typo initialize [\#1000](https://github.com/ai16z/eliza/pull/1000) ([cryptofish7](https://github.com/cryptofish7))
+- feat: allow users to configure models for openai and anthropic [\#999](https://github.com/ai16z/eliza/pull/999) ([oxSaturn](https://github.com/oxSaturn))
+- add echochambers [\#997](https://github.com/ai16z/eliza/pull/997) ([savageops](https://github.com/savageops))
+- test: adding parsing tests. changed files parsing.test.ts [\#996](https://github.com/ai16z/eliza/pull/996) ([ai16z-demirix](https://github.com/ai16z-demirix))
+- feat: create README\_DE.md [\#995](https://github.com/ai16z/eliza/pull/995) ([GottliebFreudenreich](https://github.com/GottliebFreudenreich))
+- fix: Fix Twitter Search Logic and Add Galadriel Image Model [\#994](https://github.com/ai16z/eliza/pull/994) ([dontAskVI](https://github.com/dontAskVI))
+- test: Initial release of smoke/integration tests + testing framework [\#993](https://github.com/ai16z/eliza/pull/993) ([jzvikart](https://github.com/jzvikart))
+- fix: a typo in characterfile.md [\#986](https://github.com/ai16z/eliza/pull/986) ([oxSaturn](https://github.com/oxSaturn))
+- fix: Goat Plugin + AWS S3 Service error when env vars absent [\#985](https://github.com/ai16z/eliza/pull/985) ([jnaulty](https://github.com/jnaulty))
+- docs: add WSL Setup Guide to documentation [\#983](https://github.com/ai16z/eliza/pull/983) ([ileana-pr](https://github.com/ileana-pr))
+- fix: docker trying to filter out missing docs package [\#978](https://github.com/ai16z/eliza/pull/978) ([odilitime](https://github.com/odilitime))
+- chore: fix broken lockfile [\#977](https://github.com/ai16z/eliza/pull/977) ([shakkernerd](https://github.com/shakkernerd))
+- chore: add how to startup chat ui [\#976](https://github.com/ai16z/eliza/pull/976) ([yodamaster726](https://github.com/yodamaster726))
+- feat: Add hyperbolic env vars to override model class [\#974](https://github.com/ai16z/eliza/pull/974) ([meppsilon](https://github.com/meppsilon))
+- LinkedIn Client [\#973](https://github.com/ai16z/eliza/pull/973) ([bkellgren](https://github.com/bkellgren))
+- Fix farcaster client process action issue [\#963](https://github.com/ai16z/eliza/pull/963) ([sin-bufan](https://github.com/sin-bufan))
+- fix\(agent\): correct EVM plugin activation condition [\#962](https://github.com/ai16z/eliza/pull/962) ([0xAsten](https://github.com/0xAsten))
+- fix: use MAX\_TWEET\_LENGTH from setting [\#960](https://github.com/ai16z/eliza/pull/960) ([oxSaturn](https://github.com/oxSaturn))
+- fix: Revert "docs: add WSL installation guide" [\#959](https://github.com/ai16z/eliza/pull/959) ([monilpat](https://github.com/monilpat))
+- feat: add dev script to plugin-aptos [\#956](https://github.com/ai16z/eliza/pull/956) ([asianviking](https://github.com/asianviking))
+- chore: rename intiface plugin [\#955](https://github.com/ai16z/eliza/pull/955) ([odilitime](https://github.com/odilitime))
+- fix: revert llamacloud endpoint change [\#954](https://github.com/ai16z/eliza/pull/954) ([odilitime](https://github.com/odilitime))
+- feat: allow character.json settings models for open router [\#953](https://github.com/ai16z/eliza/pull/953) ([odilitime](https://github.com/odilitime))
+- chore: 947 add other evm chains to wallet [\#949](https://github.com/ai16z/eliza/pull/949) ([n00b21337](https://github.com/n00b21337))
+- fix: telegram response memory userId to agentId [\#948](https://github.com/ai16z/eliza/pull/948) ([bmgalego](https://github.com/bmgalego))
+- docs: add WSL installation guide [\#946](https://github.com/ai16z/eliza/pull/946) ([ileana-pr](https://github.com/ileana-pr))
+- feat: Supports upload files to AWS S3. [\#941](https://github.com/ai16z/eliza/pull/941) ([xwxtwd](https://github.com/xwxtwd))
+- feat: process all responses actions [\#940](https://github.com/ai16z/eliza/pull/940) ([bmgalego](https://github.com/bmgalego))
+- feat: add callback handler to runtime evaluate method [\#938](https://github.com/ai16z/eliza/pull/938) ([bmgalego](https://github.com/bmgalego))
+- fix: update package name in faq [\#937](https://github.com/ai16z/eliza/pull/937) ([oxSaturn](https://github.com/oxSaturn))
+- fix: update quickstart and .env.example [\#932](https://github.com/ai16z/eliza/pull/932) ([oxSaturn](https://github.com/oxSaturn))
+- feat: add dynamic watch paths for agent development [\#931](https://github.com/ai16z/eliza/pull/931) ([samuveth](https://github.com/samuveth))
+- feat: flow update generate object [\#929](https://github.com/ai16z/eliza/pull/929) ([btspoony](https://github.com/btspoony))
+- feat: Config eternalai model from env [\#927](https://github.com/ai16z/eliza/pull/927) ([genesis-0000](https://github.com/genesis-0000))
+- feat: Add NanoGPT provider [\#926](https://github.com/ai16z/eliza/pull/926) ([dylan1951](https://github.com/dylan1951))
+- fix: use of Heurist model env vars [\#924](https://github.com/ai16z/eliza/pull/924) ([boxhock](https://github.com/boxhock))
+- feat: add readContract / invokeContract functionality to Coinbase plugin [\#923](https://github.com/ai16z/eliza/pull/923) ([monilpat](https://github.com/monilpat))
+- chore: deprecate text based way of generating JSON [\#920](https://github.com/ai16z/eliza/pull/920) ([monilpat](https://github.com/monilpat))
+- feat: create README\_TH.md [\#918](https://github.com/ai16z/eliza/pull/918) ([asianviking](https://github.com/asianviking))
+- feat: update gaianet config [\#915](https://github.com/ai16z/eliza/pull/915) ([L-jasmine](https://github.com/L-jasmine))
+- fix: Farcater client cleanup and fixed response logic [\#914](https://github.com/ai16z/eliza/pull/914) ([sayangel](https://github.com/sayangel))
+- Twitter client enhancements [\#913](https://github.com/ai16z/eliza/pull/913) ([tharak123455](https://github.com/tharak123455))
+- feat: MAX\_TWEET\_LENGTH env implementation [\#912](https://github.com/ai16z/eliza/pull/912) ([onur-saf](https://github.com/onur-saf))
+- feat: allow users to configure models for groq [\#910](https://github.com/ai16z/eliza/pull/910) ([oxSaturn](https://github.com/oxSaturn))
+- fix: evaluation json parsing [\#907](https://github.com/ai16z/eliza/pull/907) ([cygaar](https://github.com/cygaar))
+- fix: twitter actions not triggering [\#903](https://github.com/ai16z/eliza/pull/903) ([cygaar](https://github.com/cygaar))
+- chore: Consistent language for Community & Contact link label [\#899](https://github.com/ai16z/eliza/pull/899) ([golryang](https://github.com/golryang))
+- chore: pass env variables when setting up GOAT and update GOAT readme [\#898](https://github.com/ai16z/eliza/pull/898) ([0xaguspunk](https://github.com/0xaguspunk))
+- docs: Add What Did You Get Done This Week \#4 summaries and timestamps [\#895](https://github.com/ai16z/eliza/pull/895) ([YoungPhlo](https://github.com/YoungPhlo))
+- chore: improved dev command [\#892](https://github.com/ai16z/eliza/pull/892) ([shakkernerd](https://github.com/shakkernerd))
+- chore: added more help message to the important notice text. [\#891](https://github.com/ai16z/eliza/pull/891) ([shakkernerd](https://github.com/shakkernerd))
+- chore: update models for groq [\#890](https://github.com/ai16z/eliza/pull/890) ([oxSaturn](https://github.com/oxSaturn))
+- Feat : github image cicd [\#889](https://github.com/ai16z/eliza/pull/889) ([v1xingyue](https://github.com/v1xingyue))
+- chore: enhance dev script, performance improvement and add help message [\#887](https://github.com/ai16z/eliza/pull/887) ([shakkernerd](https://github.com/shakkernerd))
+- chore: disable building docs on build command [\#884](https://github.com/ai16z/eliza/pull/884) ([shakkernerd](https://github.com/shakkernerd))
+- fix: re-enable coverage report upload to Codecov in CI workflow [\#880](https://github.com/ai16z/eliza/pull/880) ([snobbee](https://github.com/snobbee))
+- feat: Add Flow Blockchain plugin [\#874](https://github.com/ai16z/eliza/pull/874) ([btspoony](https://github.com/btspoony))
+- feat: Add TEE Mode to Solana Plugin [\#835](https://github.com/ai16z/eliza/pull/835) ([HashWarlock](https://github.com/HashWarlock))
+- feat: add hyperbolic api to eliza [\#828](https://github.com/ai16z/eliza/pull/828) ([meppsilon](https://github.com/meppsilon))
+- loading indicator [\#827](https://github.com/ai16z/eliza/pull/827) ([tcm390](https://github.com/tcm390))
+- use github access token [\#825](https://github.com/ai16z/eliza/pull/825) ([tcm390](https://github.com/tcm390))
+- fix: refactor contributor page [\#809](https://github.com/ai16z/eliza/pull/809) ([tcm390](https://github.com/tcm390))
+- feat: implement advanced coinbase trading [\#725](https://github.com/ai16z/eliza/pull/725) ([monilpat](https://github.com/monilpat))
+
## [v0.1.5-alpha.5](https://github.com/ai16z/eliza/tree/v0.1.5-alpha.5) (2024-12-07)
[Full Changelog](https://github.com/ai16z/eliza/compare/v0.1.5-alpha.4...v0.1.5-alpha.5)
@@ -119,7 +235,7 @@
- New knowledge not being ingested into agent memory after first run [\#614](https://github.com/ai16z/eliza/issues/614)
- Tests failing - token.test.ts failing because it is commented out. Cache and goals tests are failing because jest is now switched with vitest [\#519](https://github.com/ai16z/eliza/issues/519)
- Non node.js environments have issues building \(workers for instance\) [\#506](https://github.com/ai16z/eliza/issues/506)
-- Error when call `generateObjectV2` [\#469](https://github.com/ai16z/eliza/issues/469)
+- Error when call `generateObject` [\#469](https://github.com/ai16z/eliza/issues/469)
- Current token.test.ts and videoGeneration.test.ts are throwing errors [\#464](https://github.com/ai16z/eliza/issues/464)
- unable to run defaultcharacter with ModelProviderName.LLAMACLOUD local [\#271](https://github.com/ai16z/eliza/issues/271)
- Incorrect steps in readme for starting eliza [\#270](https://github.com/ai16z/eliza/issues/270)
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index a62f52f5521..7344e37b9f8 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -38,7 +38,7 @@ We believe in the power of the OODA Loop - a decision-making framework that emph
3. Fork the repo and create your branch from `main`.
1. The name of the branch should start with the issue number and be descriptive of the changes you are making.
- 1. eg. 40--add-test-for-bug-123
+ 2. Example: 9999--add-test-for-bug-123
4. If you've added code that should be tested, add tests.
5. Ensure the test suite passes.
6. Make sure your code lints.
diff --git a/README.md b/README.md
index b87419cfad5..ad9ae072231 100644
--- a/README.md
+++ b/README.md
@@ -12,7 +12,7 @@
## 🌍 README Translations
-[中文说明](./README_CN.md) | [日本語の説明](./README_JA.md) | [한국어 설명](./README_KOR.md) | [Français](./README_FR.md) | [Português](./README_PTBR.md) | [Türkçe](./README_TR.md) | [Русский](./README_RU.md) | [Español](./README_ES.md) | [Italiano](./README_IT.md) | [ไทย](./README_TH.md) | [Deutsch](./README_DE.md)
+[中文说明](./README_CN.md) | [日本語の説明](./README_JA.md) | [한국어 설명](./README_KOR.md) | [Français](./README_FR.md) | [Português](./README_PTBR.md) | [Türkçe](./README_TR.md) | [Русский](./README_RU.md) | [Español](./README_ES.md) | [Italiano](./README_IT.md) | [ไทย](./README_TH.md) | [Deutsch](./README_DE.md) | [Tiếng Việt](./README_VI.md) | [עִברִית](https://github.com/ai16z/Elisa/blob/main/README_HE.md)
## ✨ Features
@@ -25,6 +25,9 @@
- ☁️ Supports many models (local Llama, OpenAI, Anthropic, Groq, etc.)
- 📦 Just works!
+## Video Tutorials
+[AI Agent Dev School](https://www.youtube.com/watch?v=ArptLpQiKfI&list=PLx5pnFXdPTRzWla0RaOxALTSTnVq53fKL)
+
## 🎯 Use Cases
- 🤖 Chatbots
@@ -50,7 +53,7 @@ git clone https://github.com/ai16z/eliza-starter.git
cp .env.example .env
-pnpm i && pnpm start
+pnpm i && pnpm build && pnpm start
```
Then read the [Documentation](https://ai16z.github.io/eliza/) to learn how to customize your Eliza.
@@ -90,7 +93,7 @@ sh scripts/start.sh
### Edit the character file
-1. Open `agent/src/character.ts` to modify the default character. Uncomment and edit.
+1. Open `packages/core/src/defaultCharacter.ts` to modify the default character. Uncomment and edit.
2. To load custom characters:
- Use `pnpm start --characters="path/to/your/character.json"`
diff --git a/README_HE.md b/README_HE.md
new file mode 100644
index 00000000000..e5d691fd981
--- /dev/null
+++ b/README_HE.md
@@ -0,0 +1,189 @@
+
+
+# אלייזה 🤖
+
+
+
![אלייזה באנר](./docs/static/img/eliza_banner.jpg)
+
+
+
+
+📖 [תיעוד](https://ai16z.github.io/eliza/) | 🎯 [דוגמאות](https://github.com/thejoven/awesome-eliza)
+
+
+
+
+
+[中文说明](https://github.com/ai16z/Elisa/blob/main/README_CN.md) | [日本語の説明](https://github.com/ai16z/Elisa/blob/main/README_JA.md) | [한국어 설명](https://github.com/ai16z/Elisa/blob/main/README_KOR.md) | [Français](https://github.com/ai16z/Elisa/blob/main/README_FR.md) | [Português](https://github.com/ai16z/Elisa/blob/main/README_PTBR.md) | [Türkçe](TR.md) | [Русский](https://github.com/ai16z/Elisa/blob/main/README_RU.md) | [Español](https://github.com/ai16z/Elisa/blob/main/README_ES.md) | [Italiano](https://github.com/ai16z/Elisa/blob/main/README_IT.md) | [ไทย](https://github.com/ai16z/Elisa/blob/main/README_TH.md) | [Deutsch](https://github.com/ai16z/Elisa/blob/main/README_DE.md) | [עִברִית](https://github.com/ai16z/Elisa/blob/main/README_HE.md)
+
+
+
+
+
+## ✨ תכונות
+
+- 🛠️ מחברים מלאים לדיסקורד, טוויטר וטלגרם
+- 🔗 תמיכה בכל מודל (Llama, Grok, OpenAI, Anthropic, וכו')
+- 👥 תמיכה בריבוי סוכנים וחדרים
+- 📚 קל לשלב ולהשתמש במסמכים שלך
+- 💾 זיכרון ומאגר מסמכים הניתנים לשליפה
+- 🚀 ניתן להרחבה רבה - יצירת פעולות ולקוחות משלך
+- ☁️ תומך בהרבה מודלים (local Llama, OpenAI, Anthropic, Groq ,
+וכו')
+- 📦 פשוט עובד!
+
+
+
+## 🎯 מקרי שימוש
+
+
+- 🤖 צ'טבוטים
+
+
+- 🕵️ סוכנים אוטונומיים
+
+
+- 📈 טיפול בתהליכים עסקיים
+
+
+- 🎮 במשחקי וידאו (NPCs)
+
+
+- 🧠 מסחר
+
+
+## 🚀 התחלה מהירה
+
+
+
+### דרישות מוקדמות
+
+[Python 2.7+](https://www.python.org/downloads/) -
+
+[Node.js 23+](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm) -
+
+[pnpm](https://pnpm.io/installation) -
+
+> **הערה למשתמשי Windows:** נדרש [WSL 2](https://learn.microsoft.com/en-us/windows/wsl/install-manual)
+
+
+
+### שימוש ב-Starter (מומלץ)
+
+
+
+```
+git clone https://github.com/ai16z/eliza-starter.git
+
+cp .env.example .env
+
+pnpm i && pnpm start
+```
+
+
+
+
+לאחר מכן קרא את [התיעוד](https://ai16z.github.io/eliza/) כדי ללמוד כיצד להתאים את אלייזה.
+
+### התחלה ידנית של אלייזה (מומלץ רק למי שיודע מה הוא עושה)
+
+
+```
+# שכפול המאגר
+git clone https://github.com/ai16z/eliza.git
+
+# מעבר לגרסה האחרונה
+git checkout $(git describe --tags --abbrev=0)
+```
+
+
+### התחלת אלייזה עם Gitpod
+
+
+
+[![פתח ב-Gitpod](https://gitpod.io/button/open-in-gitpod.svg)](https://gitpod.io/#https://github.com/ai16z/eliza/tree/main)
+
+
+
+### עריכת קובץ .env
+
+
+
+העתק את .env.example ל-.env ומלא את הערכים המתאימים.
+
+```
+cp .env.example .env
+```
+
+
+
+הערה: .env הוא אופציונלי. אם אתם מתכננים להפעיל מספר סוכנים נפרדים, ניתן להעביר סודות דרך JSON הדמות.
+
+### התחלה אוטומטית של אלייזה
+
+פעולה זו תפעיל הכל כדי להגדיר את הפרויקט ולהתחיל את הבוט עם הדמות המובנית.
+
+
+
+```bash
+sh scripts/start.sh
+```
+
+
+
+### עריכת קובץ הדמות
+
+1. פתח את `agent/src/character.ts` כדי לשנות את דמות ברירת המחדל. בטל הערה וערוך.
+
+2. לטעינת דמויות מותאמות אישית:
+ - השתמש ב-`pnpm start --characters="path/to/your/character.json"`
+ - ניתן לטעון מספר קבצי דמויות בו זמנית.
+
+3. התחבר עם X (טוויטר):
+ - שנה `"clients": []` ל-`"clients": ["twitter"]` בקובץ הדמות כדי להתחבר ל-X.
+
+### התחלה ידנית של אלייזה
+
+
+```bash
+pnpm i
+pnpm build
+pnpm start
+
+# לעיתים צריך לנקות את הפרויקט אם חוזרים אליו לאחר זמן
+pnpm clean
+```
+
+
+#### דרישות נוספות
+
+ייתכן שתצטרך להתקין את Sharp. אם אתה רואה שגיאה בעת ההפעלה, נסה להתקין עם הפקודה הבאה:
+
+```
+pnpm install --include=optional sharp
+```
+
+### קהילה ויצירת קשר
+
+
+
+[GitHub Issues](https://github.com/ai16z/eliza/issues) מתאים ביותר עבור: באגים ופרופוזיציות לתכונות -
+
+[Discord](https://discord.gg/ai16z) מתאים ביותר עבור: שיתוף היישומים שלך והשתתפות בקהילה -
+
+
+## תורמים
+
+
+
+
+
+
+
+
+
+## היסטוריית כוכבים
+
+[![תרשים היסטוריית כוכבים](https://api.star-history.com/svg?repos=ai16z/eliza&type=Date)](https://star-history.com/#ai16z/eliza&Date)
+
+
diff --git a/README_VI.md b/README_VI.md
new file mode 100644
index 00000000000..d186162c0c1
--- /dev/null
+++ b/README_VI.md
@@ -0,0 +1,129 @@
+# Eliza 🤖
+
+
+
![Eliza Banner](./docs/static/img/eliza_banner.jpg)
+
+
+
+
+ 📖 [Tài liệu](https://ai16z.github.io/eliza/) | 🎯 [Ví dụ](https://github.com/thejoven/awesome-eliza)
+
+
+
+## ✨ Tính năng
+
+- 🛠️ Phương thức kết nối đầy đủ tính năng với Discord, Twitter và Telegram
+- 🔗 Hỗ trợ mọi mô hình ngôn ngữ lớn (Llama, Grok, OpenAI, Anthropic, v.v.)
+- 👥 Hỗ trợ nhiều tác nhân và phòng trò chuyện
+- 📚 Dễ dàng tiếp nhận và tương tác với tài liệu của bạn
+- 💾 Bộ nhớ và kho lưu trữ tài liệu có thể truy xuất
+- 🚀 Có khả năng mở rộng cao - tạo hành động và ứng dụng của riêng bạn
+- ☁️ Hỗ trợ nhiều mô hình cùng lúc (Llama, OpenAI, Anthropic, Groq, v.v.)
+- 📦 Đơn giản là nó hoạt động!
+
+## 🎯 Các trường hợp sử dụng
+
+- 🤖 Chatbots
+- 🕵️ Các tác nhân tự động
+- 📈 Xử lý các mô hình kinh tế
+- 🎮 NPCs trong các trò chơi điện tử
+- 🧠 Giao dịch (Trading)
+
+## 🚀 Bắt đầu
+
+### Điều kiện tiên quyết
+
+- [Python 2.7+](https://www.python.org/downloads/)
+- [Node.js 23+](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm)
+- [pnpm](https://pnpm.io/installation)
+
+> **Chú ý cho người dùng Windows:** [WSL 2](https://learn.microsoft.com/de-de/windows/wsl/install-manual) là bắt buộc.
+
+### Sử dụng phiên bản Starters (Khuyến nghị)
+
+```bash
+git clone https://github.com/ai16z/eliza-starter.git
+
+cp .env.example .env
+
+pnpm i && pnpm start
+```
+
+Sau đó hãy đọc [Tài liệu](https://ai16z.github.io/eliza/), để học cách để tùy chỉnh Eliza của bạn.
+
+### Khởi động Eliza theo cách thủ công (Chỉ khuyến khích nếu bạn biết mình đang làm gì)
+
+```bash
+# Sao chép repository
+git clone https://github.com/ai16z/eliza.git
+
+# Kiểm tra bản phát hành mới nhất
+# Dự án này cải tiến rất nhanh, vì vậy chúng tôi khuyên bạn nên kiểm tra bản phát hành mới nhất
+git checkout $(git describe --tags --abbrev=0)
+```
+
+### Bắt đầu Eliza với Gitpod
+
+[![Mở Gitpod](https://gitpod.io/button/open-in-gitpod.svg)](https://gitpod.io/#https://github.com/ai16z/eliza/tree/main)
+
+### Chỉnh sửa tệp .env
+
+Sao chép .env.example vào .env và điền các giá trị thích hợp.
+
+```
+cp .env.example .env
+```
+
+Lưu ý: .env là tùy chọn. Nếu bạn đang có kế hoạch chạy nhiều tác nhân riêng biệt, bạn có thể truyền secret qua định dạng JSON.
+
+### Tự động khởi động Eliza
+
+Điều này sẽ chạy mọi thứ để thiết lập dự án và khởi động bot với tính cách (character) mặc định.
+
+```bash
+sh scripts/start.sh
+```
+
+### Chỉnh sửa tập tin tính cách
+
+1. Mở `agent/src/character.ts`, để chỉnh sửa tính cách mặc định. Bỏ chú thích và chỉnh sửa.
+
+2. Để chạy các tính cách tùy chỉnh:
+ - Sử dụng `pnpm start --characters="path/to/your/character.json"`
+ - Nhiều tính cách có thể được chạy cùng lúc với nhau
+3. Kết nối với X (Twitter)
+ - Thay đổi `"clients": []` thành `"clients": ["twitter"]` ở trong tập tính cách (character) để kết nối với X.
+
+### Bắt đầu Eliza theo cách thủ công
+
+```bash
+pnpm i
+pnpm build
+pnpm start
+
+# Dự án cải tiến rất nhanh, đôi khi bạn cần phải dọn dẹp dự án nếu bạn quay lại dự án
+pnpm clean
+```
+
+#### Yêu cầu bổ sung
+
+Bạn có thể cần cài đặt Sharp. Nếu bạn thấy lỗi khi khởi động, hãy thử cài đặt bằng lệnh sau:
+
+```
+pnpm install --include=optional sharp
+```
+
+### Cộng đồng & Liên hệ
+
+- [GitHub Issues](https://github.com/ai16z/eliza/issues). Phù hợp nhất cho: các lỗi bạn gặp phải khi sử dụng Eliza và các đề xuất tính năng.
+- [Discord](https://discord.gg/ai16z). Phù hợp nhất cho: chia sẻ ứng dụng của bạn và giao lưu với cộng đồng.
+
+## Người đóng góp
+
+
+
+
+
+## Lịch sử Star cho repo
+
+[![Star History Chart](https://api.star-history.com/svg?repos=ai16z/eliza&type=Date)](https://star-history.com/#ai16z/eliza&Date)
diff --git a/agent/package.json b/agent/package.json
index e27d4aa5ee5..a0d291f9341 100644
--- a/agent/package.json
+++ b/agent/package.json
@@ -1,6 +1,6 @@
{
"name": "@ai16z/agent",
- "version": "0.1.5-alpha.5",
+ "version": "0.1.5-alpha.6",
"main": "src/index.ts",
"type": "module",
"scripts": {
@@ -23,8 +23,10 @@
"@ai16z/client-direct": "workspace:*",
"@ai16z/client-discord": "workspace:*",
"@ai16z/client-farcaster": "workspace:*",
+ "@ai16z/client-lens": "workspace:*",
"@ai16z/client-telegram": "workspace:*",
"@ai16z/client-twitter": "workspace:*",
+ "@ai16z/client-slack": "workspace:*",
"@ai16z/eliza": "workspace:*",
"@ai16z/plugin-0g": "workspace:*",
"@ai16z/plugin-aptos": "workspace:*",
@@ -34,13 +36,20 @@
"@ai16z/plugin-conflux": "workspace:*",
"@ai16z/plugin-evm": "workspace:*",
"@ai16z/plugin-flow": "workspace:*",
+ "@ai16z/plugin-story": "workspace:*",
"@ai16z/plugin-goat": "workspace:*",
"@ai16z/plugin-icp": "workspace:*",
"@ai16z/plugin-image-generation": "workspace:*",
+ "@ai16z/plugin-nft-generation": "workspace:*",
"@ai16z/plugin-node": "workspace:*",
"@ai16z/plugin-solana": "workspace:*",
"@ai16z/plugin-starknet": "workspace:*",
+ "@ai16z/plugin-ton": "workspace:*",
+ "@ai16z/plugin-sui": "workspace:*",
"@ai16z/plugin-tee": "workspace:*",
+ "@ai16z/plugin-multiversx": "workspace:*",
+ "@ai16z/plugin-near": "workspace:*",
+ "@ai16z/plugin-zksync-era": "workspace:*",
"readline": "1.3.0",
"ws": "8.18.0",
"yargs": "17.7.2"
diff --git a/agent/src/index.ts b/agent/src/index.ts
index ad35595d6f0..3b9a63a4636 100644
--- a/agent/src/index.ts
+++ b/agent/src/index.ts
@@ -1,53 +1,61 @@
import { PostgresDatabaseAdapter } from "@ai16z/adapter-postgres";
import { SqliteDatabaseAdapter } from "@ai16z/adapter-sqlite";
import { AutoClientInterface } from "@ai16z/client-auto";
-import { DirectClientInterface } from "@ai16z/client-direct";
import { DiscordClientInterface } from "@ai16z/client-discord";
+import { FarcasterAgentClient } from "@ai16z/client-farcaster";
+import { LensAgentClient } from "@ai16z/client-lens";
+import { SlackClientInterface } from "@ai16z/client-slack";
import { TelegramClientInterface } from "@ai16z/client-telegram";
import { TwitterClientInterface } from "@ai16z/client-twitter";
-import { FarcasterAgentClient } from "@ai16z/client-farcaster";
import {
AgentRuntime,
CacheManager,
Character,
Clients,
DbCacheAdapter,
+ defaultCharacter,
+ elizaLogger,
FsCacheAdapter,
IAgentRuntime,
ICacheManager,
IDatabaseAdapter,
IDatabaseCacheAdapter,
ModelProviderName,
- defaultCharacter,
- elizaLogger,
settings,
stringToUuid,
validateCharacterConfig,
} from "@ai16z/eliza";
import { zgPlugin } from "@ai16z/plugin-0g";
-import createGoatPlugin from "@ai16z/plugin-goat";
import { bootstrapPlugin } from "@ai16z/plugin-bootstrap";
+import createGoatPlugin from "@ai16z/plugin-goat";
// import { intifacePlugin } from "@ai16z/plugin-intiface";
+import { DirectClient } from "@ai16z/client-direct";
+import { aptosPlugin } from "@ai16z/plugin-aptos";
import {
+ advancedTradePlugin,
coinbaseCommercePlugin,
coinbaseMassPaymentsPlugin,
- tradePlugin,
tokenContractPlugin,
+ tradePlugin,
webhookPlugin,
- advancedTradePlugin,
} from "@ai16z/plugin-coinbase";
import { confluxPlugin } from "@ai16z/plugin-conflux";
-import { imageGenerationPlugin } from "@ai16z/plugin-image-generation";
import { evmPlugin } from "@ai16z/plugin-evm";
+import { storyPlugin } from "@ai16z/plugin-story";
+import { flowPlugin } from "@ai16z/plugin-flow";
+import { imageGenerationPlugin } from "@ai16z/plugin-image-generation";
+import { multiversxPlugin } from "@ai16z/plugin-multiversx";
+import { nearPlugin } from "@ai16z/plugin-near";
+import { nftGenerationPlugin } from "@ai16z/plugin-nft-generation";
import { createNodePlugin } from "@ai16z/plugin-node";
import { solanaPlugin } from "@ai16z/plugin-solana";
-import { teePlugin, TEEMode } from "@ai16z/plugin-tee";
-import { aptosPlugin, TransferAptosToken } from "@ai16z/plugin-aptos";
-import { flowPlugin } from "@ai16z/plugin-flow";
+import { suiPlugin } from "@ai16z/plugin-sui";
+import { TEEMode, teePlugin } from "@ai16z/plugin-tee";
+import { tonPlugin } from "@ai16z/plugin-ton";
+import { zksyncEraPlugin } from "@ai16z/plugin-zksync-era";
import Database from "better-sqlite3";
import fs from "fs";
import path from "path";
-import readline from "readline";
import { fileURLToPath } from "url";
import yargs from "yargs";
@@ -60,6 +68,12 @@ export const wait = (minTime: number = 1000, maxTime: number = 3000) => {
return new Promise((resolve) => setTimeout(resolve, waitTime));
};
+const logFetch = async (url: string, options: any) => {
+ elizaLogger.info(`Fetching ${url}`);
+ elizaLogger.info(JSON.stringify(options, null, 2));
+ return fetch(url, options);
+};
+
export function parseArguments(): {
character?: string;
characters?: string;
@@ -280,6 +294,11 @@ export function getTokenForProvider(
character.settings?.secrets?.HYPERBOLIC_API_KEY ||
settings.HYPERBOLIC_API_KEY
);
+ case ModelProviderName.VENICE:
+ return (
+ character.settings?.secrets?.VENICE_API_KEY ||
+ settings.VENICE_API_KEY
+ );
}
}
@@ -312,42 +331,69 @@ function initializeDatabase(dataDir: string) {
}
}
+// also adds plugins from character file into the runtime
export async function initializeClients(
character: Character,
runtime: IAgentRuntime
) {
- const clients = [];
- const clientTypes =
+ // each client can only register once
+ // and if we want two we can explicitly support it
+ const clients: Record = {};
+ const clientTypes: string[] =
character.clients?.map((str) => str.toLowerCase()) || [];
+ elizaLogger.log("initializeClients", clientTypes, "for", character.name);
- if (clientTypes.includes("auto")) {
+ if (clientTypes.includes(Clients.DIRECT)) {
const autoClient = await AutoClientInterface.start(runtime);
- if (autoClient) clients.push(autoClient);
+ if (autoClient) clients.auto = autoClient;
}
- if (clientTypes.includes("discord")) {
- clients.push(await DiscordClientInterface.start(runtime));
+ if (clientTypes.includes(Clients.DISCORD)) {
+ const discordClient = await DiscordClientInterface.start(runtime);
+ if (discordClient) clients.discord = discordClient;
}
- if (clientTypes.includes("telegram")) {
+ if (clientTypes.includes(Clients.TELEGRAM)) {
const telegramClient = await TelegramClientInterface.start(runtime);
- if (telegramClient) clients.push(telegramClient);
+ if (telegramClient) clients.telegram = telegramClient;
}
- if (clientTypes.includes("twitter")) {
- TwitterClientInterface.enableSearch = !isFalsish(getSecret(character, "TWITTER_SEARCH_ENABLE"));
- const twitterClients = await TwitterClientInterface.start(runtime);
- clients.push(twitterClients);
+ if (clientTypes.includes(Clients.TWITTER)) {
+ const twitterClient = await TwitterClientInterface.start(runtime);
+
+ if (twitterClient) {
+ clients.twitter = twitterClient;
+ (twitterClient as any).enableSearch = !isFalsish(
+ getSecret(character, "TWITTER_SEARCH_ENABLE")
+ );
+ }
+ }
+
+ if (clientTypes.includes(Clients.FARCASTER)) {
+ // why is this one different :(
+ const farcasterClient = new FarcasterAgentClient(runtime);
+ if (farcasterClient) {
+ farcasterClient.start();
+ clients.farcaster = farcasterClient;
+ }
+ }
+ if (clientTypes.includes("lens")) {
+ const lensClient = new LensAgentClient(runtime);
+ lensClient.start();
+ clients.lens = lensClient;
}
- if (clientTypes.includes("farcaster")) {
- const farcasterClients = new FarcasterAgentClient(runtime);
- farcasterClients.start();
- clients.push(farcasterClients);
+ elizaLogger.log("client keys", Object.keys(clients));
+
+ // TODO: Add Slack client to the list
+ if (clientTypes.includes("slack")) {
+ const slackClient = await SlackClientInterface.start(runtime);
+ if (slackClient) clients.push(slackClient);
}
if (character.plugins?.length > 0) {
for (const plugin of character.plugins) {
+ // if plugin has clients, add those..
if (plugin.clients) {
for (const client of plugin.clients) {
clients.push(await client.start(runtime));
@@ -366,17 +412,26 @@ function isFalsish(input: any): boolean {
}
// Convert input to a string if it's not null or undefined
- const value = input == null ? '' : String(input);
+ const value = input == null ? "" : String(input);
// List of common falsish string representations
- const falsishValues = ['false', '0', 'no', 'n', 'off', 'null', 'undefined', ''];
+ const falsishValues = [
+ "false",
+ "0",
+ "no",
+ "n",
+ "off",
+ "null",
+ "undefined",
+ "",
+ ];
// Check if the value (trimmed and lowercased) is in the falsish list
return falsishValues.includes(value.trim().toLowerCase());
}
function getSecret(character: Character, secret: string) {
- return character.settings.secrets?.[secret] || process.env[secret];
+ return character.settings?.secrets?.[secret] || process.env[secret];
}
let nodePlugin: any | undefined;
@@ -386,7 +441,7 @@ export async function createAgent(
db: IDatabaseAdapter,
cache: ICacheManager,
token: string
-) {
+): Promise {
elizaLogger.success(
elizaLogger.successesTitle,
"Creating runtime for character",
@@ -419,6 +474,7 @@ export async function createAgent(
modelProvider: character.modelProvider,
evaluators: [],
character,
+ // character.plugins are handled when clients are added
plugins: [
bootstrapPlugin,
getSecret(character, "CONFLUX_CORE_PRIVATE_KEY")
@@ -430,17 +486,33 @@ export async function createAgent(
!getSecret(character, "WALLET_PUBLIC_KEY")?.startsWith("0x"))
? solanaPlugin
: null,
- getSecret(character, "EVM_PRIVATE_KEY") ||
+ (getSecret(character, "NEAR_ADDRESS") ||
+ getSecret(character, "NEAR_WALLET_PUBLIC_KEY")) &&
+ getSecret(character, "NEAR_WALLET_SECRET_KEY")
+ ? nearPlugin
+ : null,
+ getSecret(character, "EVM_PUBLIC_KEY") ||
(getSecret(character, "WALLET_PUBLIC_KEY") &&
getSecret(character, "WALLET_PUBLIC_KEY")?.startsWith("0x"))
? evmPlugin
: null,
+ (getSecret(character, "SOLANA_PUBLIC_KEY") ||
+ (getSecret(character, "WALLET_PUBLIC_KEY") &&
+ !getSecret(character, "WALLET_PUBLIC_KEY")?.startsWith(
+ "0x"
+ ))) &&
+ getSecret(character, "SOLANA_ADMIN_PUBLIC_KEY") &&
+ getSecret(character, "SOLANA_PRIVATE_KEY") &&
+ getSecret(character, "SOLANA_ADMIN_PRIVATE_KEY")
+ ? nftGenerationPlugin
+ : null,
getSecret(character, "ZEROG_PRIVATE_KEY") ? zgPlugin : null,
getSecret(character, "COINBASE_COMMERCE_KEY")
? coinbaseCommercePlugin
: null,
getSecret(character, "FAL_API_KEY") ||
getSecret(character, "OPENAI_API_KEY") ||
+ getSecret(character, "VENICE_API_KEY") ||
getSecret(character, "HEURIST_API_KEY")
? imageGenerationPlugin
: null,
@@ -467,12 +539,18 @@ export async function createAgent(
? flowPlugin
: null,
getSecret(character, "APTOS_PRIVATE_KEY") ? aptosPlugin : null,
+ getSecret(character, "MVX_PRIVATE_KEY") ? multiversxPlugin : null,
+ getSecret(character, "ZKSYNC_PRIVATE_KEY") ? zksyncEraPlugin : null,
+ getSecret(character, "TON_PRIVATE_KEY") ? tonPlugin : null,
+ getSecret(character, "SUI_PRIVATE_KEY") ? suiPlugin : null,
+ getSecret(character, "STORY_PRIVATE_KEY") ? storyPlugin : null,
].filter(Boolean),
providers: [],
actions: [],
services: [],
managers: [],
cacheManager: cache,
+ fetch: logFetch,
});
}
@@ -488,7 +566,10 @@ function initializeDbCache(character: Character, db: IDatabaseCacheAdapter) {
return cache;
}
-async function startAgent(character: Character, directClient) {
+async function startAgent(
+ character: Character,
+ directClient
+): Promise {
let db: IDatabaseAdapter & IDatabaseCacheAdapter;
try {
character.id ??= stringToUuid(character.name);
@@ -507,21 +588,32 @@ async function startAgent(character: Character, directClient) {
await db.init();
const cache = initializeDbCache(character, db);
- const runtime = await createAgent(character, db, cache, token);
+ const runtime: AgentRuntime = await createAgent(
+ character,
+ db,
+ cache,
+ token
+ );
+ // start services/plugins/process knowledge
await runtime.initialize();
- const clients = await initializeClients(character, runtime);
+ // start assigned clients
+ runtime.clients = await initializeClients(character, runtime);
+ // add to container
directClient.registerAgent(runtime);
- return clients;
+ // report to console
+ elizaLogger.debug(`Started ${character.name} as ${runtime.agentId}`);
+
+ return runtime;
} catch (error) {
elizaLogger.error(
`Error starting agent for character ${character.name}:`,
error
);
- console.error(error);
+ elizaLogger.error(error);
if (db) {
await db.close();
}
@@ -530,7 +622,8 @@ async function startAgent(character: Character, directClient) {
}
const startAgents = async () => {
- const directClient = await DirectClientInterface.start();
+ const directClient = new DirectClient();
+ const serverPort = parseInt(settings.SERVER_PORT || "3000");
const args = parseArguments();
let charactersArg = args.characters || args.character;
@@ -549,67 +642,13 @@ const startAgents = async () => {
elizaLogger.error("Error starting agents:", error);
}
- function chat() {
- const agentId = characters[0].name ?? "Agent";
- rl.question("You: ", async (input) => {
- await handleUserInput(input, agentId);
- if (input.toLowerCase() !== "exit") {
- chat(); // Loop back to ask another question
- }
- });
- }
+ directClient.start(serverPort);
- elizaLogger.log("Chat started. Type 'exit' to quit.");
- if (!args["non-interactive"]) {
- chat();
- }
+ elizaLogger.log("Visit the following URL to chat with your agents:");
+ elizaLogger.log(`http://localhost:5173`);
};
startAgents().catch((error) => {
elizaLogger.error("Unhandled error in startAgents:", error);
process.exit(1); // Exit the process after logging
});
-
-const rl = readline.createInterface({
- input: process.stdin,
- output: process.stdout,
-});
-
-async function handleUserInput(input, agentId) {
- if (input.toLowerCase() === "exit") {
- gracefulExit();
- }
-
- try {
- const serverPort = parseInt(settings.SERVER_PORT || "3000");
-
- const response = await fetch(
- `http://localhost:${serverPort}/${agentId}/message`,
- {
- method: "POST",
- headers: { "Content-Type": "application/json" },
- body: JSON.stringify({
- text: input,
- userId: "user",
- userName: "User",
- }),
- }
- );
-
- const data = await response.json();
- data.forEach((message) =>
- elizaLogger.log(`${"Agent"}: ${message.text}`)
- );
- } catch (error) {
- console.error("Error fetching response:", error);
- }
-}
-
-async function gracefulExit() {
- elizaLogger.log("Terminating and cleaning up resources...");
- rl.close();
- process.exit(0);
-}
-
-rl.on("SIGINT", gracefulExit);
-rl.on("SIGTERM", gracefulExit);
diff --git a/docs/README.md b/docs/README.md
index 31ac88a62ac..f4df034b71c 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -4,6 +4,10 @@
# Visit https://eliza.builders for support
+## 🌍 README Translations
+
+[中文说明](./README_CN.md) | [Français](./README_FR.md) | [ไทย](./README_TH.md)
+
# dev branch
diff --git a/docs/README_TH.md b/docs/README_TH.md
new file mode 100644
index 00000000000..d50c69af0c0
--- /dev/null
+++ b/docs/README_TH.md
@@ -0,0 +1,178 @@
+# Eliza - เฟรมเวิร์กจำลองเอเจนต์หลายตัวเเทน
+
+# https://github.com/ai16z/eliza
+
+# เข้าไปดู https://eliza.builders สำหรับขอความช่วยเหลือประการใด
+
+# dev branch
+
+
+
+_ดังที่เห็นขับเคลื่อนเเละถูกใช้บน [@DegenSpartanAI](https://x.com/degenspartanai) and [@MarcAIndreessen](https://x.com/pmairca)_
+
+- เฟรมเวิร์กจำลองเอเจนต์หลายตัวแทน
+- เพิ่มตัวละครที่มีเอกลักษณ์ได้มากเท่าที่ต้องการด้วยไฟล์ตัวละคร - [characterfile](https://github.com/lalalune/characterfile/)
+- ตัวเชื่อมต่อ Discord และ Twitter แบบครบถ้วน พร้อมการสนับสนุนผ่านช่อง Discord
+- สนับสนุนการจำลองการสนทนาทั้งหมดและหน่วยความจำ RAG
+- สามารถอ่านลิงค์และไฟล์ PDF, เเปลเสียงและวิดีโอ, สรุปการสนทนา, และอื่นๆ
+- ขยายความสามารถของ Eliza ได้สูง - สร้างการกระทำและไคลเอนต์ของคุณเองเพื่อขยายความสามารถของ Eliza
+- รองรับโมเดลทั้งเเบบ Open-source และเเบบ Local (กำหนดค่าเริ่มต้นด้วย Nous Hermes Llama 3.1B)
+- รองรับ OpenAI สำหรับการอนุมานในคลาวด์บนอุปกรณ์ที่มีน้ำหนักเบา
+- โหมด "Ask Claude" สำหรับการเรียก Claude ในคำถามที่ซับซ้อนมากขึ้น
+- 100% เขียนโดย TypeScript
+
+# เริ่มต้นใช้งาน
+
+**ข้อกำหนดเบื้องต้น (ต้องมี):**
+
+- [Node.js 23+](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm)
+- [pnpm](https://pnpm.io/installation)
+
+### แก้ไขไฟล์ .env
+
+- คัดลอก .env.example เป็น .env และกรอกค่าที่เหมาะสม
+- แก้ไขตัวแปรสภาพแวดล้อม TWITTER เพื่อเพิ่มชื่อผู้ใช้และรหัสผ่านของบอท
+
+### แก้ไขไฟล์ตัวละคร
+
+- ลองเข้าไปตรวจสอบไฟล์ `src/core/defaultCharacter.ts` - คุณสามารถแก้ไขได้
+- คุณยังสามารถโหลดตัวละครด้วย `pnpm start --characters="path/to/your/character.json"` และเรียกใช้บอทหลายตัวพร้อมกันได้
+
+หลังจากตั้งค่าไฟล์ .env และไฟล์ตัวละครแล้ว คุณสามารถเริ่มบอทด้วยคำสั่งต่อไปนี้:
+
+```
+pnpm i
+pnpm start
+```
+
+# การปรับแต่ง Eliza
+
+### การเพิ่มการกระทำของตัวละครเอเจนท์แบบกำหนดเอง
+
+เพื่อหลีกเลี่ยงความขัดแย้งของ git ในไดเรกทอรีหลัก เราแนะนำให้เพิ่มการกระทำแบบกำหนดเองในโฟลเดอร์ `custom_actions` แล้วเพิ่มลงในไฟล์ `elizaConfig.yaml` ดูตัวอย่างในไฟลได้ที่ `elizaConfig.example.yaml`
+
+## การเรียกใช้กับโมเดลต่างๆ
+
+### การเรียกใช้กับโมเดล Llama
+
+คุณสามารถเรียกใช้โมเดล Llama 70B หรือ 405B ได้โดยตั้งค่าตัวแปรสภาพแวดล้อม `XAI_MODEL` เป็น `meta-llama/Meta-Llama-3.1-70B-Instruct-Turbo` หรือ `meta-llama/Meta-Llama-3.1-405B-Instruct`
+
+### การเรียกใช้กับโมเดล Grok
+
+คุณสามารถเรียกใช้โมเดล Grok ได้โดยตั้งค่าตัวแปรสภาพแวดล้อม `XAI_MODEL` เป็น `grok-beta`
+
+### การเรียกใช้กับโมเดล OpenAI
+
+คุณสามารถเรียกใช้โมเดล OpenAI ได้โดยตั้งค่าตัวแปรสภาพแวดล้อม `XAI_MODEL` เป็น `gpt-4o-mini` หรือ `gpt-4o`
+
+## ข้อกำหนดเพิ่มเติม
+
+คุณอาจต้องติดตั้ง Sharp หากพบข้อผิดพลาดเมื่อเริ่มต้น ให้ลองติดตั้งด้วยคำสั่งต่อไปนี้:
+
+```
+pnpm install --include=optional sharp
+```
+
+# การตั้งค่าสภาพแวดล้อม
+
+คุณจะต้องเพิ่มตัวแปรสภาพแวดล้อมลงในไฟล์ .env เพื่อเชื่อมต่อกับแพลตฟอร์มต่างๆ:
+
+```
+# ตัวแปรที่จำเป็น
+DISCORD_APPLICATION_ID=
+DISCORD_API_TOKEN= # โทเค็นของบอท
+OPENAI_API_KEY=sk-* # API key ของ OpenAI เริ่มต้นด้วย sk-
+ELEVENLABS_XI_API_KEY= # API key จาก elevenlabs
+
+# การตั้งค่า ELEVENLABS
+ELEVENLABS_MODEL_ID=eleven_multilingual_v2
+ELEVENLABS_VOICE_ID=21m00Tcm4TlvDq8ikWAM
+ELEVENLABS_VOICE_STABILITY=0.5
+ELEVENLABS_VOICE_SIMILARITY_BOOST=0.9
+ELEVENLABS_VOICE_STYLE=0.66
+ELEVENLABS_VOICE_USE_SPEAKER_BOOST=false
+ELEVENLABS_OPTIMIZE_STREAMING_LATENCY=4
+ELEVENLABS_OUTPUT_FORMAT=pcm_16000
+
+TWITTER_DRY_RUN=false
+TWITTER_USERNAME= # ชื่อผู้ใช้บัญชี
+TWITTER_PASSWORD= # รหัสผ่าน
+TWITTER_EMAIL= # อีเมล
+TWITTER_COOKIES= # คุกกี้
+
+X_SERVER_URL=
+XAI_API_KEY=
+XAI_MODEL=
+
+
+# สำหรับการสอบถาม Claude
+ANTHROPIC_API_KEY=
+
+WALLET_SECRET_KEY=EXAMPLE_WALLET_SECRET_KEY
+WALLET_PUBLIC_KEY=EXAMPLE_WALLET_PUBLIC_KEY
+
+BIRDEYE_API_KEY=
+
+SOL_ADDRESS=So11111111111111111111111111111111111111112
+SLIPPAGE=1
+RPC_URL=https://api.mainnet-beta.solana.com
+HELIUS_API_KEY=
+
+
+## Telegram
+TELEGRAM_BOT_TOKEN=
+
+TOGETHER_API_KEY=
+```
+
+# การตั้งค่าการประมวลผลในเครื่อง
+
+### การตั้งค่า CUDA
+
+หากคุณมี NVIDIA GPU คุณสามารถติดตั้ง CUDA เพื่อเพิ่มความเร็วการประมวลผลในเครื่องได้อย่างมาก:
+
+```
+pnpm install
+npx --no node-llama-cpp source download --gpu cuda
+```
+
+ตรวจสอบให้แน่ใจว่าคุณได้ติดตั้ง CUDA Toolkit รวมถึง cuDNN และ cuBLAS
+
+### การเรียกใช้งานในเครื่อง
+
+เพิ่ม XAI_MODEL และตั้งค่าเป็นตัวเลือกหนึ่งจาก [Run with
+Llama](#run-with-llama) - คุณสามารถปล่อย X_SERVER_URL และ XAI_API_KEY ให้เป็นค่าว่าง มันจะดาวน์โหลดโมเดลจาก
+Hugging Face และส่งคิวรี่ในเครื่อง
+
+# ไคลเอนต์
+
+## บอท Discord
+
+สำหรับความช่วยเหลือในการตั้งค่าบอท Discord ของคุณ ดูได้ที่นี่: https://discordjs.guide/preparations/setting-up-a-bot-application.html
+
+# การพัฒนา
+
+## การทดสอบ
+
+เพื่อรันชุดทดสอบ:
+
+```bash
+pnpm test # รันการทดสอบหนึ่งครั้ง
+pnpm test:watch # รันการทดสอบในโหมดติดตาม
+```
+
+สำหรับการทดสอบฐานข้อมูลเฉพาะ:
+
+```bash
+pnpm test:sqlite # รันการทดสอบด้วย SQLite
+pnpm test:sqljs # รันการทดสอบด้วย SQL.js
+```
+
+การทดสอบถูกเขียนโดยใช้ Jest และสามารถพบได้ในไฟล์ `src/**/*.test.ts` การกำหนดค่าสภาพแวดล้อมถูกตั้งค่าเพื่อ:
+
+- โหลดตัวแปรสภาพแวดล้อมจาก `.env.test`
+- ใช้เวลาไทม์เอาต์ 2 นาทีสำหรับการทดสอบที่ใช้เวลานาน
+- รองรับโมดูล ESM
+- รันการทดสอบตามลำดับ (--runInBand)
+
+เพื่อสร้างการทดสอบใหม่ ให้เพิ่มไฟล์ `.test.ts` ใกล้กับโค้ดที่คุณกำลังทดสอบ
diff --git a/docs/api/classes/AgentRuntime.md b/docs/api/classes/AgentRuntime.md
index 1023bb35976..685ec16d7ab 100644
--- a/docs/api/classes/AgentRuntime.md
+++ b/docs/api/classes/AgentRuntime.md
@@ -83,7 +83,7 @@ Custom fetch function to use for making requests.
#### Defined in
-[packages/core/src/runtime.ts:208](https://github.com/ai16z/eliza/blob/main/packages/core/src/runtime.ts#L208)
+[packages/core/src/runtime.ts:209](https://github.com/ai16z/eliza/blob/main/packages/core/src/runtime.ts#L209)
## Properties
@@ -264,6 +264,10 @@ Some environments may not have access to the global fetch function and need a cu
`Promise`\<`Response`\>
+#### Implementation of
+
+[`IAgentRuntime`](../interfaces/IAgentRuntime.md).[`fetch`](../interfaces/IAgentRuntime.md#fetch)
+
#### Defined in
[packages/core/src/runtime.ts:110](https://github.com/ai16z/eliza/blob/main/packages/core/src/runtime.ts#L110)
@@ -402,6 +406,23 @@ Searchable document fragments
[packages/core/src/runtime.ts:144](https://github.com/ai16z/eliza/blob/main/packages/core/src/runtime.ts#L144)
+***
+
+### clients
+
+> **clients**: `Record`\<`string`, `any`\>
+
+any could be EventEmitter
+but I think the real solution is forthcoming as a base client interface
+
+#### Implementation of
+
+[`IAgentRuntime`](../interfaces/IAgentRuntime.md).[`clients`](../interfaces/IAgentRuntime.md#clients)
+
+#### Defined in
+
+[packages/core/src/runtime.ts:145](https://github.com/ai16z/eliza/blob/main/packages/core/src/runtime.ts#L145)
+
## Methods
### registerMemoryManager()
@@ -422,7 +443,7 @@ Searchable document fragments
#### Defined in
-[packages/core/src/runtime.ts:146](https://github.com/ai16z/eliza/blob/main/packages/core/src/runtime.ts#L146)
+[packages/core/src/runtime.ts:147](https://github.com/ai16z/eliza/blob/main/packages/core/src/runtime.ts#L147)
***
@@ -444,7 +465,7 @@ Searchable document fragments
#### Defined in
-[packages/core/src/runtime.ts:161](https://github.com/ai16z/eliza/blob/main/packages/core/src/runtime.ts#L161)
+[packages/core/src/runtime.ts:162](https://github.com/ai16z/eliza/blob/main/packages/core/src/runtime.ts#L162)
***
@@ -470,7 +491,7 @@ Searchable document fragments
#### Defined in
-[packages/core/src/runtime.ts:165](https://github.com/ai16z/eliza/blob/main/packages/core/src/runtime.ts#L165)
+[packages/core/src/runtime.ts:166](https://github.com/ai16z/eliza/blob/main/packages/core/src/runtime.ts#L166)
***
@@ -492,7 +513,7 @@ Searchable document fragments
#### Defined in
-[packages/core/src/runtime.ts:174](https://github.com/ai16z/eliza/blob/main/packages/core/src/runtime.ts#L174)
+[packages/core/src/runtime.ts:175](https://github.com/ai16z/eliza/blob/main/packages/core/src/runtime.ts#L175)
***
@@ -510,7 +531,21 @@ Searchable document fragments
#### Defined in
-[packages/core/src/runtime.ts:375](https://github.com/ai16z/eliza/blob/main/packages/core/src/runtime.ts#L375)
+[packages/core/src/runtime.ts:376](https://github.com/ai16z/eliza/blob/main/packages/core/src/runtime.ts#L376)
+
+***
+
+### stop()
+
+> **stop**(): `Promise`\<`void`\>
+
+#### Returns
+
+`Promise`\<`void`\>
+
+#### Defined in
+
+[packages/core/src/runtime.ts:409](https://github.com/ai16z/eliza/blob/main/packages/core/src/runtime.ts#L409)
***
@@ -532,7 +567,7 @@ Searchable document fragments
#### Defined in
-[packages/core/src/runtime.ts:439](https://github.com/ai16z/eliza/blob/main/packages/core/src/runtime.ts#L439)
+[packages/core/src/runtime.ts:459](https://github.com/ai16z/eliza/blob/main/packages/core/src/runtime.ts#L459)
***
@@ -554,7 +589,7 @@ The number of recent messages to be kept in memory.
#### Defined in
-[packages/core/src/runtime.ts:461](https://github.com/ai16z/eliza/blob/main/packages/core/src/runtime.ts#L461)
+[packages/core/src/runtime.ts:481](https://github.com/ai16z/eliza/blob/main/packages/core/src/runtime.ts#L481)
***
@@ -580,7 +615,7 @@ The action to register.
#### Defined in
-[packages/core/src/runtime.ts:469](https://github.com/ai16z/eliza/blob/main/packages/core/src/runtime.ts#L469)
+[packages/core/src/runtime.ts:489](https://github.com/ai16z/eliza/blob/main/packages/core/src/runtime.ts#L489)
***
@@ -602,7 +637,7 @@ The evaluator to register.
#### Defined in
-[packages/core/src/runtime.ts:478](https://github.com/ai16z/eliza/blob/main/packages/core/src/runtime.ts#L478)
+[packages/core/src/runtime.ts:498](https://github.com/ai16z/eliza/blob/main/packages/core/src/runtime.ts#L498)
***
@@ -624,7 +659,7 @@ The context provider to register.
#### Defined in
-[packages/core/src/runtime.ts:486](https://github.com/ai16z/eliza/blob/main/packages/core/src/runtime.ts#L486)
+[packages/core/src/runtime.ts:506](https://github.com/ai16z/eliza/blob/main/packages/core/src/runtime.ts#L506)
***
@@ -656,7 +691,7 @@ The message to process.
#### Defined in
-[packages/core/src/runtime.ts:495](https://github.com/ai16z/eliza/blob/main/packages/core/src/runtime.ts#L495)
+[packages/core/src/runtime.ts:515](https://github.com/ai16z/eliza/blob/main/packages/core/src/runtime.ts#L515)
***
@@ -696,7 +731,7 @@ The results of the evaluation.
#### Defined in
-[packages/core/src/runtime.ts:579](https://github.com/ai16z/eliza/blob/main/packages/core/src/runtime.ts#L579)
+[packages/core/src/runtime.ts:599](https://github.com/ai16z/eliza/blob/main/packages/core/src/runtime.ts#L599)
***
@@ -728,7 +763,7 @@ An error if the participant cannot be added.
#### Defined in
-[packages/core/src/runtime.ts:646](https://github.com/ai16z/eliza/blob/main/packages/core/src/runtime.ts#L646)
+[packages/core/src/runtime.ts:666](https://github.com/ai16z/eliza/blob/main/packages/core/src/runtime.ts#L666)
***
@@ -764,7 +799,7 @@ The user name to ensure the existence of.
#### Defined in
-[packages/core/src/runtime.ts:662](https://github.com/ai16z/eliza/blob/main/packages/core/src/runtime.ts#L662)
+[packages/core/src/runtime.ts:682](https://github.com/ai16z/eliza/blob/main/packages/core/src/runtime.ts#L682)
***
@@ -788,7 +823,7 @@ The user name to ensure the existence of.
#### Defined in
-[packages/core/src/runtime.ts:682](https://github.com/ai16z/eliza/blob/main/packages/core/src/runtime.ts#L682)
+[packages/core/src/runtime.ts:702](https://github.com/ai16z/eliza/blob/main/packages/core/src/runtime.ts#L702)
***
@@ -818,7 +853,7 @@ The user name to ensure the existence of.
#### Defined in
-[packages/core/src/runtime.ts:699](https://github.com/ai16z/eliza/blob/main/packages/core/src/runtime.ts#L699)
+[packages/core/src/runtime.ts:719](https://github.com/ai16z/eliza/blob/main/packages/core/src/runtime.ts#L719)
***
@@ -849,7 +884,7 @@ An error if the room cannot be created.
#### Defined in
-[packages/core/src/runtime.ts:735](https://github.com/ai16z/eliza/blob/main/packages/core/src/runtime.ts#L735)
+[packages/core/src/runtime.ts:755](https://github.com/ai16z/eliza/blob/main/packages/core/src/runtime.ts#L755)
***
@@ -879,7 +914,7 @@ The state of the agent.
#### Defined in
-[packages/core/src/runtime.ts:748](https://github.com/ai16z/eliza/blob/main/packages/core/src/runtime.ts#L748)
+[packages/core/src/runtime.ts:768](https://github.com/ai16z/eliza/blob/main/packages/core/src/runtime.ts#L768)
***
@@ -901,4 +936,4 @@ The state of the agent.
#### Defined in
-[packages/core/src/runtime.ts:1194](https://github.com/ai16z/eliza/blob/main/packages/core/src/runtime.ts#L1194)
+[packages/core/src/runtime.ts:1214](https://github.com/ai16z/eliza/blob/main/packages/core/src/runtime.ts#L1214)
diff --git a/docs/api/classes/Service.md b/docs/api/classes/Service.md
index 2caee66dde7..cd443f1ef4f 100644
--- a/docs/api/classes/Service.md
+++ b/docs/api/classes/Service.md
@@ -12,6 +12,7 @@
- [`ISpeechService`](../interfaces/ISpeechService.md)
- [`IPdfService`](../interfaces/IPdfService.md)
- [`IAwsS3Service`](../interfaces/IAwsS3Service.md)
+- [`ISlackService`](../interfaces/ISlackService.md)
## Constructors
@@ -37,7 +38,7 @@
#### Defined in
-[packages/core/src/types.ts:962](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L962)
+[packages/core/src/types.ts:998](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L998)
***
@@ -53,7 +54,7 @@
#### Defined in
-[packages/core/src/types.ts:973](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L973)
+[packages/core/src/types.ts:1009](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L1009)
## Methods
@@ -71,7 +72,7 @@
#### Defined in
-[packages/core/src/types.ts:966](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L966)
+[packages/core/src/types.ts:1002](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L1002)
***
@@ -91,4 +92,4 @@ Add abstract initialize method that must be implemented by derived classes
#### Defined in
-[packages/core/src/types.ts:978](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L978)
+[packages/core/src/types.ts:1014](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L1014)
diff --git a/docs/api/enumerations/Clients.md b/docs/api/enumerations/Clients.md
index 94819f158ab..9b16e196c20 100644
--- a/docs/api/enumerations/Clients.md
+++ b/docs/api/enumerations/Clients.md
@@ -12,7 +12,7 @@ Available client platforms
#### Defined in
-[packages/core/src/types.ts:605](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L605)
+[packages/core/src/types.ts:610](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L610)
***
@@ -22,7 +22,7 @@ Available client platforms
#### Defined in
-[packages/core/src/types.ts:606](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L606)
+[packages/core/src/types.ts:611](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L611)
***
@@ -32,7 +32,7 @@ Available client platforms
#### Defined in
-[packages/core/src/types.ts:607](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L607)
+[packages/core/src/types.ts:612](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L612)
***
@@ -42,7 +42,7 @@ Available client platforms
#### Defined in
-[packages/core/src/types.ts:608](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L608)
+[packages/core/src/types.ts:613](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L613)
***
@@ -52,4 +52,34 @@ Available client platforms
#### Defined in
-[packages/core/src/types.ts:609](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L609)
+[packages/core/src/types.ts:614](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L614)
+
+***
+
+### LENS
+
+> **LENS**: `"lens"`
+
+#### Defined in
+
+[packages/core/src/types.ts:615](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L615)
+
+***
+
+### AUTO
+
+> **AUTO**: `"auto"`
+
+#### Defined in
+
+[packages/core/src/types.ts:616](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L616)
+
+***
+
+### SLACK
+
+> **SLACK**: `"slack"`
+
+#### Defined in
+
+[packages/core/src/types.ts:617](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L617)
diff --git a/docs/api/enumerations/LoggingLevel.md b/docs/api/enumerations/LoggingLevel.md
index 8e675b4a332..7b8e169eab6 100644
--- a/docs/api/enumerations/LoggingLevel.md
+++ b/docs/api/enumerations/LoggingLevel.md
@@ -10,7 +10,7 @@
#### Defined in
-[packages/core/src/types.ts:1165](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L1165)
+[packages/core/src/types.ts:1213](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L1213)
***
@@ -20,7 +20,7 @@
#### Defined in
-[packages/core/src/types.ts:1166](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L1166)
+[packages/core/src/types.ts:1214](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L1214)
***
@@ -30,4 +30,4 @@
#### Defined in
-[packages/core/src/types.ts:1167](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L1167)
+[packages/core/src/types.ts:1215](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L1215)
diff --git a/docs/api/enumerations/ModelProviderName.md b/docs/api/enumerations/ModelProviderName.md
index 7f7ebedee98..ad816996b28 100644
--- a/docs/api/enumerations/ModelProviderName.md
+++ b/docs/api/enumerations/ModelProviderName.md
@@ -12,7 +12,7 @@ Available model providers
#### Defined in
-[packages/core/src/types.ts:216](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L216)
+[packages/core/src/types.ts:217](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L217)
***
@@ -22,7 +22,7 @@ Available model providers
#### Defined in
-[packages/core/src/types.ts:217](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L217)
+[packages/core/src/types.ts:218](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L218)
***
@@ -32,7 +32,7 @@ Available model providers
#### Defined in
-[packages/core/src/types.ts:218](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L218)
+[packages/core/src/types.ts:219](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L219)
***
@@ -42,7 +42,7 @@ Available model providers
#### Defined in
-[packages/core/src/types.ts:219](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L219)
+[packages/core/src/types.ts:220](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L220)
***
@@ -52,7 +52,7 @@ Available model providers
#### Defined in
-[packages/core/src/types.ts:220](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L220)
+[packages/core/src/types.ts:221](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L221)
***
@@ -62,7 +62,7 @@ Available model providers
#### Defined in
-[packages/core/src/types.ts:221](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L221)
+[packages/core/src/types.ts:222](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L222)
***
@@ -72,7 +72,7 @@ Available model providers
#### Defined in
-[packages/core/src/types.ts:222](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L222)
+[packages/core/src/types.ts:223](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L223)
***
@@ -82,7 +82,7 @@ Available model providers
#### Defined in
-[packages/core/src/types.ts:223](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L223)
+[packages/core/src/types.ts:224](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L224)
***
@@ -92,7 +92,7 @@ Available model providers
#### Defined in
-[packages/core/src/types.ts:224](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L224)
+[packages/core/src/types.ts:225](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L225)
***
@@ -102,7 +102,7 @@ Available model providers
#### Defined in
-[packages/core/src/types.ts:225](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L225)
+[packages/core/src/types.ts:226](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L226)
***
@@ -112,7 +112,7 @@ Available model providers
#### Defined in
-[packages/core/src/types.ts:226](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L226)
+[packages/core/src/types.ts:227](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L227)
***
@@ -122,7 +122,7 @@ Available model providers
#### Defined in
-[packages/core/src/types.ts:227](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L227)
+[packages/core/src/types.ts:228](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L228)
***
@@ -132,7 +132,7 @@ Available model providers
#### Defined in
-[packages/core/src/types.ts:228](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L228)
+[packages/core/src/types.ts:229](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L229)
***
@@ -142,7 +142,7 @@ Available model providers
#### Defined in
-[packages/core/src/types.ts:229](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L229)
+[packages/core/src/types.ts:230](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L230)
***
@@ -152,7 +152,7 @@ Available model providers
#### Defined in
-[packages/core/src/types.ts:230](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L230)
+[packages/core/src/types.ts:231](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L231)
***
@@ -162,7 +162,7 @@ Available model providers
#### Defined in
-[packages/core/src/types.ts:231](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L231)
+[packages/core/src/types.ts:232](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L232)
***
@@ -172,7 +172,7 @@ Available model providers
#### Defined in
-[packages/core/src/types.ts:232](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L232)
+[packages/core/src/types.ts:233](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L233)
***
@@ -182,7 +182,7 @@ Available model providers
#### Defined in
-[packages/core/src/types.ts:233](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L233)
+[packages/core/src/types.ts:234](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L234)
***
@@ -192,7 +192,7 @@ Available model providers
#### Defined in
-[packages/core/src/types.ts:234](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L234)
+[packages/core/src/types.ts:235](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L235)
***
@@ -202,7 +202,7 @@ Available model providers
#### Defined in
-[packages/core/src/types.ts:235](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L235)
+[packages/core/src/types.ts:236](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L236)
***
@@ -212,4 +212,14 @@ Available model providers
#### Defined in
-[packages/core/src/types.ts:236](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L236)
+[packages/core/src/types.ts:237](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L237)
+
+***
+
+### VENICE
+
+> **VENICE**: `"venice"`
+
+#### Defined in
+
+[packages/core/src/types.ts:238](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L238)
diff --git a/docs/api/enumerations/ServiceType.md b/docs/api/enumerations/ServiceType.md
index 3878f734c27..621a25be9d1 100644
--- a/docs/api/enumerations/ServiceType.md
+++ b/docs/api/enumerations/ServiceType.md
@@ -10,7 +10,7 @@
#### Defined in
-[packages/core/src/types.ts:1153](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L1153)
+[packages/core/src/types.ts:1199](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L1199)
***
@@ -20,7 +20,7 @@
#### Defined in
-[packages/core/src/types.ts:1154](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L1154)
+[packages/core/src/types.ts:1200](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L1200)
***
@@ -30,7 +30,7 @@
#### Defined in
-[packages/core/src/types.ts:1155](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L1155)
+[packages/core/src/types.ts:1201](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L1201)
***
@@ -40,7 +40,7 @@
#### Defined in
-[packages/core/src/types.ts:1156](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L1156)
+[packages/core/src/types.ts:1202](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L1202)
***
@@ -50,7 +50,7 @@
#### Defined in
-[packages/core/src/types.ts:1157](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L1157)
+[packages/core/src/types.ts:1203](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L1203)
***
@@ -60,7 +60,7 @@
#### Defined in
-[packages/core/src/types.ts:1158](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L1158)
+[packages/core/src/types.ts:1204](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L1204)
***
@@ -70,7 +70,7 @@
#### Defined in
-[packages/core/src/types.ts:1159](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L1159)
+[packages/core/src/types.ts:1205](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L1205)
***
@@ -80,7 +80,7 @@
#### Defined in
-[packages/core/src/types.ts:1160](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L1160)
+[packages/core/src/types.ts:1206](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L1206)
***
@@ -90,4 +90,24 @@
#### Defined in
-[packages/core/src/types.ts:1161](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L1161)
+[packages/core/src/types.ts:1207](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L1207)
+
+***
+
+### BUTTPLUG
+
+> **BUTTPLUG**: `"buttplug"`
+
+#### Defined in
+
+[packages/core/src/types.ts:1208](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L1208)
+
+***
+
+### SLACK
+
+> **SLACK**: `"slack"`
+
+#### Defined in
+
+[packages/core/src/types.ts:1209](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L1209)
diff --git a/docs/api/functions/generateCaption.md b/docs/api/functions/generateCaption.md
index 59dbde1df42..eeed9173c7f 100644
--- a/docs/api/functions/generateCaption.md
+++ b/docs/api/functions/generateCaption.md
@@ -26,4 +26,4 @@
## Defined in
-[packages/core/src/generation.ts:1063](https://github.com/ai16z/eliza/blob/main/packages/core/src/generation.ts#L1063)
+[packages/core/src/generation.ts:1175](https://github.com/ai16z/eliza/blob/main/packages/core/src/generation.ts#L1175)
diff --git a/docs/api/functions/generateImage.md b/docs/api/functions/generateImage.md
index bb5e0c5a673..14e06a59840 100644
--- a/docs/api/functions/generateImage.md
+++ b/docs/api/functions/generateImage.md
@@ -48,4 +48,4 @@
## Defined in
-[packages/core/src/generation.ts:850](https://github.com/ai16z/eliza/blob/main/packages/core/src/generation.ts#L850)
+[packages/core/src/generation.ts:925](https://github.com/ai16z/eliza/blob/main/packages/core/src/generation.ts#L925)
diff --git a/docs/api/functions/generateMessageResponse.md b/docs/api/functions/generateMessageResponse.md
index 605387adca3..76acdef3694 100644
--- a/docs/api/functions/generateMessageResponse.md
+++ b/docs/api/functions/generateMessageResponse.md
@@ -28,4 +28,4 @@ The completed message.
## Defined in
-[packages/core/src/generation.ts:809](https://github.com/ai16z/eliza/blob/main/packages/core/src/generation.ts#L809)
+[packages/core/src/generation.ts:884](https://github.com/ai16z/eliza/blob/main/packages/core/src/generation.ts#L884)
diff --git a/docs/api/functions/generateObjectV2.md b/docs/api/functions/generateObject.md
similarity index 60%
rename from docs/api/functions/generateObjectV2.md
rename to docs/api/functions/generateObject.md
index d4ac9fcfaf4..a09985bbffc 100644
--- a/docs/api/functions/generateObjectV2.md
+++ b/docs/api/functions/generateObject.md
@@ -1,8 +1,8 @@
-[@ai16z/eliza v0.1.5-alpha.5](../index.md) / generateObjectV2
+[@ai16z/eliza v0.1.5-alpha.5](../index.md) / generateObject
-# Function: generateObjectV2()
+# Function: generateObject()
-> **generateObjectV2**(`options`): `Promise`\<`GenerateObjectResult`\<`unknown`\>\>
+> **generateObject**(`options`): `Promise`\<`GenerateObjectResult`\<`unknown`\>\>
Generates structured objects from a prompt using specified AI models and configuration options.
@@ -24,4 +24,4 @@ Configuration options for generating objects.
## Defined in
-[packages/core/src/generation.ts:1153](https://github.com/ai16z/eliza/blob/main/packages/core/src/generation.ts#L1153)
+[packages/core/src/generation.ts:1265](https://github.com/ai16z/eliza/blob/main/packages/core/src/generation.ts#L1265)
diff --git a/docs/api/functions/generateObjectArray.md b/docs/api/functions/generateObjectArray.md
index 28de746409e..36e89f9b8ad 100644
--- a/docs/api/functions/generateObjectArray.md
+++ b/docs/api/functions/generateObjectArray.md
@@ -20,4 +20,4 @@
## Defined in
-[packages/core/src/generation.ts:761](https://github.com/ai16z/eliza/blob/main/packages/core/src/generation.ts#L761)
+[packages/core/src/generation.ts:836](https://github.com/ai16z/eliza/blob/main/packages/core/src/generation.ts#L836)
diff --git a/docs/api/functions/generateObjectDEPRECATED.md b/docs/api/functions/generateObjectDEPRECATED.md
deleted file mode 100644
index 2923b1b1f36..00000000000
--- a/docs/api/functions/generateObjectDEPRECATED.md
+++ /dev/null
@@ -1,23 +0,0 @@
-[@ai16z/eliza v0.1.5-alpha.5](../index.md) / generateObjectDEPRECATED
-
-# Function: generateObjectDEPRECATED()
-
-> **generateObjectDEPRECATED**(`__namedParameters`): `Promise`\<`any`\>
-
-## Parameters
-
-• **\_\_namedParameters**
-
-• **\_\_namedParameters.runtime**: [`IAgentRuntime`](../interfaces/IAgentRuntime.md)
-
-• **\_\_namedParameters.context**: `string`
-
-• **\_\_namedParameters.modelClass**: `string`
-
-## Returns
-
-`Promise`\<`any`\>
-
-## Defined in
-
-[packages/core/src/generation.ts:725](https://github.com/ai16z/eliza/blob/main/packages/core/src/generation.ts#L725)
diff --git a/docs/api/functions/generateObjectDeprecated.md b/docs/api/functions/generateObjectDeprecated.md
new file mode 100644
index 00000000000..0155f90b067
--- /dev/null
+++ b/docs/api/functions/generateObjectDeprecated.md
@@ -0,0 +1,23 @@
+[@ai16z/eliza v0.1.5-alpha.5](../index.md) / generateObjectDeprecated
+
+# Function: generateObjectDeprecated()
+
+> **generateObjectDeprecated**(`__namedParameters`): `Promise`\<`any`\>
+
+## Parameters
+
+• **\_\_namedParameters**
+
+• **\_\_namedParameters.runtime**: [`IAgentRuntime`](../interfaces/IAgentRuntime.md)
+
+• **\_\_namedParameters.context**: `string`
+
+• **\_\_namedParameters.modelClass**: `string`
+
+## Returns
+
+`Promise`\<`any`\>
+
+## Defined in
+
+[packages/core/src/generation.ts:800](https://github.com/ai16z/eliza/blob/main/packages/core/src/generation.ts#L800)
diff --git a/docs/api/functions/generateShouldRespond.md b/docs/api/functions/generateShouldRespond.md
index 09b68a311b2..e0a7c1710a5 100644
--- a/docs/api/functions/generateShouldRespond.md
+++ b/docs/api/functions/generateShouldRespond.md
@@ -28,4 +28,4 @@ Promise resolving to "RESPOND", "IGNORE", "STOP" or null
## Defined in
-[packages/core/src/generation.ts:551](https://github.com/ai16z/eliza/blob/main/packages/core/src/generation.ts#L551)
+[packages/core/src/generation.ts:626](https://github.com/ai16z/eliza/blob/main/packages/core/src/generation.ts#L626)
diff --git a/docs/api/functions/generateTextArray.md b/docs/api/functions/generateTextArray.md
index cad8e8464bf..5c7a0d2a3c7 100644
--- a/docs/api/functions/generateTextArray.md
+++ b/docs/api/functions/generateTextArray.md
@@ -28,4 +28,4 @@ Promise resolving to an array of strings parsed from the model's response
## Defined in
-[packages/core/src/generation.ts:689](https://github.com/ai16z/eliza/blob/main/packages/core/src/generation.ts#L689)
+[packages/core/src/generation.ts:764](https://github.com/ai16z/eliza/blob/main/packages/core/src/generation.ts#L764)
diff --git a/docs/api/functions/generateTrueOrFalse.md b/docs/api/functions/generateTrueOrFalse.md
index 61874957d50..4b28744218b 100644
--- a/docs/api/functions/generateTrueOrFalse.md
+++ b/docs/api/functions/generateTrueOrFalse.md
@@ -28,4 +28,4 @@ Promise resolving to a boolean value parsed from the model's response
## Defined in
-[packages/core/src/generation.ts:634](https://github.com/ai16z/eliza/blob/main/packages/core/src/generation.ts#L634)
+[packages/core/src/generation.ts:709](https://github.com/ai16z/eliza/blob/main/packages/core/src/generation.ts#L709)
diff --git a/docs/api/functions/generateTweetActions.md b/docs/api/functions/generateTweetActions.md
index 39f8df33beb..cd26c955101 100644
--- a/docs/api/functions/generateTweetActions.md
+++ b/docs/api/functions/generateTweetActions.md
@@ -20,4 +20,4 @@
## Defined in
-[packages/core/src/generation.ts:1502](https://github.com/ai16z/eliza/blob/main/packages/core/src/generation.ts#L1502)
+[packages/core/src/generation.ts:1614](https://github.com/ai16z/eliza/blob/main/packages/core/src/generation.ts#L1614)
diff --git a/docs/api/functions/generateWebSearch.md b/docs/api/functions/generateWebSearch.md
index 5d63e81087c..7a889937aa6 100644
--- a/docs/api/functions/generateWebSearch.md
+++ b/docs/api/functions/generateWebSearch.md
@@ -16,4 +16,4 @@
## Defined in
-[packages/core/src/generation.ts:1087](https://github.com/ai16z/eliza/blob/main/packages/core/src/generation.ts#L1087)
+[packages/core/src/generation.ts:1199](https://github.com/ai16z/eliza/blob/main/packages/core/src/generation.ts#L1199)
diff --git a/docs/api/functions/getEndpoint.md b/docs/api/functions/getEndpoint.md
index 0a250549ccb..0927fa46858 100644
--- a/docs/api/functions/getEndpoint.md
+++ b/docs/api/functions/getEndpoint.md
@@ -14,4 +14,4 @@
## Defined in
-[packages/core/src/models.ts:460](https://github.com/ai16z/eliza/blob/main/packages/core/src/models.ts#L460)
+[packages/core/src/models.ts:475](https://github.com/ai16z/eliza/blob/main/packages/core/src/models.ts#L475)
diff --git a/docs/api/functions/getModel.md b/docs/api/functions/getModel.md
index 9fc27ab2556..75397fac5fa 100644
--- a/docs/api/functions/getModel.md
+++ b/docs/api/functions/getModel.md
@@ -16,4 +16,4 @@
## Defined in
-[packages/core/src/models.ts:456](https://github.com/ai16z/eliza/blob/main/packages/core/src/models.ts#L456)
+[packages/core/src/models.ts:471](https://github.com/ai16z/eliza/blob/main/packages/core/src/models.ts#L471)
diff --git a/docs/api/functions/handleProvider.md b/docs/api/functions/handleProvider.md
index e1ee3078165..6b575532db9 100644
--- a/docs/api/functions/handleProvider.md
+++ b/docs/api/functions/handleProvider.md
@@ -20,4 +20,4 @@ Configuration options specific to the provider.
## Defined in
-[packages/core/src/generation.ts:1238](https://github.com/ai16z/eliza/blob/main/packages/core/src/generation.ts#L1238)
+[packages/core/src/generation.ts:1350](https://github.com/ai16z/eliza/blob/main/packages/core/src/generation.ts#L1350)
diff --git a/docs/api/functions/splitChunks.md b/docs/api/functions/splitChunks.md
index 37351f58241..6ec7e000480 100644
--- a/docs/api/functions/splitChunks.md
+++ b/docs/api/functions/splitChunks.md
@@ -28,4 +28,4 @@ Promise resolving to array of text chunks with bleed sections
## Defined in
-[packages/core/src/generation.ts:606](https://github.com/ai16z/eliza/blob/main/packages/core/src/generation.ts#L606)
+[packages/core/src/generation.ts:681](https://github.com/ai16z/eliza/blob/main/packages/core/src/generation.ts#L681)
diff --git a/docs/api/functions/trimTokens.md b/docs/api/functions/trimTokens.md
index d62ea9e1330..e2156835abf 100644
--- a/docs/api/functions/trimTokens.md
+++ b/docs/api/functions/trimTokens.md
@@ -28,4 +28,4 @@ The truncated text
## Defined in
-[packages/core/src/generation.ts:505](https://github.com/ai16z/eliza/blob/main/packages/core/src/generation.ts#L505)
+[packages/core/src/generation.ts:580](https://github.com/ai16z/eliza/blob/main/packages/core/src/generation.ts#L580)
diff --git a/docs/api/functions/validateCharacterConfig.md b/docs/api/functions/validateCharacterConfig.md
index f7cf1cc86b2..8732d6a2b96 100644
--- a/docs/api/functions/validateCharacterConfig.md
+++ b/docs/api/functions/validateCharacterConfig.md
@@ -16,4 +16,4 @@ Validation function
## Defined in
-[packages/core/src/environment.ts:133](https://github.com/ai16z/eliza/blob/main/packages/core/src/environment.ts#L133)
+[packages/core/src/environment.ts:138](https://github.com/ai16z/eliza/blob/main/packages/core/src/environment.ts#L138)
diff --git a/docs/api/index.md b/docs/api/index.md
index 581dcd929ef..3e6ae4a6d29 100644
--- a/docs/api/index.md
+++ b/docs/api/index.md
@@ -41,6 +41,7 @@
- [Account](interfaces/Account.md)
- [Participant](interfaces/Participant.md)
- [Room](interfaces/Room.md)
+- [IAgentConfig](interfaces/IAgentConfig.md)
- [IDatabaseAdapter](interfaces/IDatabaseAdapter.md)
- [IDatabaseCacheAdapter](interfaces/IDatabaseCacheAdapter.md)
- [IMemoryManager](interfaces/IMemoryManager.md)
@@ -55,6 +56,7 @@
- [IPdfService](interfaces/IPdfService.md)
- [IAwsS3Service](interfaces/IAwsS3Service.md)
- [ActionResponse](interfaces/ActionResponse.md)
+- [ISlackService](interfaces/ISlackService.md)
## Type Aliases
@@ -114,13 +116,13 @@
- [splitChunks](functions/splitChunks.md)
- [generateTrueOrFalse](functions/generateTrueOrFalse.md)
- [generateTextArray](functions/generateTextArray.md)
-- [generateObjectDEPRECATED](functions/generateObjectDEPRECATED.md)
+- [generateObjectDeprecated](functions/generateObjectDeprecated.md)
- [generateObjectArray](functions/generateObjectArray.md)
- [generateMessageResponse](functions/generateMessageResponse.md)
- [generateImage](functions/generateImage.md)
- [generateCaption](functions/generateCaption.md)
- [generateWebSearch](functions/generateWebSearch.md)
-- [generateObjectV2](functions/generateObjectV2.md)
+- [generateObject](functions/generateObject.md)
- [handleProvider](functions/handleProvider.md)
- [generateTweetActions](functions/generateTweetActions.md)
- [getGoals](functions/getGoals.md)
diff --git a/docs/api/interfaces/Account.md b/docs/api/interfaces/Account.md
index 481ab1baa3e..e7eaec12e88 100644
--- a/docs/api/interfaces/Account.md
+++ b/docs/api/interfaces/Account.md
@@ -14,7 +14,7 @@ Unique identifier
#### Defined in
-[packages/core/src/types.ts:501](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L501)
+[packages/core/src/types.ts:503](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L503)
***
@@ -26,7 +26,7 @@ Display name
#### Defined in
-[packages/core/src/types.ts:504](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L504)
+[packages/core/src/types.ts:506](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L506)
***
@@ -38,7 +38,7 @@ Username
#### Defined in
-[packages/core/src/types.ts:507](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L507)
+[packages/core/src/types.ts:509](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L509)
***
@@ -54,7 +54,7 @@ Optional additional details
#### Defined in
-[packages/core/src/types.ts:510](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L510)
+[packages/core/src/types.ts:512](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L512)
***
@@ -66,7 +66,7 @@ Optional email
#### Defined in
-[packages/core/src/types.ts:513](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L513)
+[packages/core/src/types.ts:515](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L515)
***
@@ -78,4 +78,4 @@ Optional avatar URL
#### Defined in
-[packages/core/src/types.ts:516](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L516)
+[packages/core/src/types.ts:518](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L518)
diff --git a/docs/api/interfaces/Action.md b/docs/api/interfaces/Action.md
index d5e707f7a54..a06c0d344fd 100644
--- a/docs/api/interfaces/Action.md
+++ b/docs/api/interfaces/Action.md
@@ -14,7 +14,7 @@ Similar action descriptions
#### Defined in
-[packages/core/src/types.ts:400](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L400)
+[packages/core/src/types.ts:402](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L402)
***
@@ -26,7 +26,7 @@ Detailed description
#### Defined in
-[packages/core/src/types.ts:403](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L403)
+[packages/core/src/types.ts:405](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L405)
***
@@ -38,7 +38,7 @@ Example usages
#### Defined in
-[packages/core/src/types.ts:406](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L406)
+[packages/core/src/types.ts:408](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L408)
***
@@ -50,7 +50,7 @@ Handler function
#### Defined in
-[packages/core/src/types.ts:409](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L409)
+[packages/core/src/types.ts:411](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L411)
***
@@ -62,7 +62,7 @@ Action name
#### Defined in
-[packages/core/src/types.ts:412](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L412)
+[packages/core/src/types.ts:414](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L414)
***
@@ -74,4 +74,4 @@ Validation function
#### Defined in
-[packages/core/src/types.ts:415](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L415)
+[packages/core/src/types.ts:417](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L417)
diff --git a/docs/api/interfaces/ActionResponse.md b/docs/api/interfaces/ActionResponse.md
index 17f6363ca7b..0fa97e4b83a 100644
--- a/docs/api/interfaces/ActionResponse.md
+++ b/docs/api/interfaces/ActionResponse.md
@@ -10,7 +10,7 @@
#### Defined in
-[packages/core/src/types.ts:1176](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L1176)
+[packages/core/src/types.ts:1224](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L1224)
***
@@ -20,7 +20,7 @@
#### Defined in
-[packages/core/src/types.ts:1177](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L1177)
+[packages/core/src/types.ts:1225](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L1225)
***
@@ -30,7 +30,7 @@
#### Defined in
-[packages/core/src/types.ts:1178](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L1178)
+[packages/core/src/types.ts:1226](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L1226)
***
@@ -40,4 +40,4 @@
#### Defined in
-[packages/core/src/types.ts:1179](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L1179)
+[packages/core/src/types.ts:1227](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L1227)
diff --git a/docs/api/interfaces/EvaluationExample.md b/docs/api/interfaces/EvaluationExample.md
index e112487263f..24f0192703c 100644
--- a/docs/api/interfaces/EvaluationExample.md
+++ b/docs/api/interfaces/EvaluationExample.md
@@ -14,7 +14,7 @@ Evaluation context
#### Defined in
-[packages/core/src/types.ts:423](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L423)
+[packages/core/src/types.ts:425](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L425)
***
@@ -26,7 +26,7 @@ Example messages
#### Defined in
-[packages/core/src/types.ts:426](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L426)
+[packages/core/src/types.ts:428](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L428)
***
@@ -38,4 +38,4 @@ Expected outcome
#### Defined in
-[packages/core/src/types.ts:429](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L429)
+[packages/core/src/types.ts:431](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L431)
diff --git a/docs/api/interfaces/Evaluator.md b/docs/api/interfaces/Evaluator.md
index b77d6eaedcb..cc74ec8eade 100644
--- a/docs/api/interfaces/Evaluator.md
+++ b/docs/api/interfaces/Evaluator.md
@@ -14,7 +14,7 @@ Whether to always run
#### Defined in
-[packages/core/src/types.ts:437](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L437)
+[packages/core/src/types.ts:439](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L439)
***
@@ -26,7 +26,7 @@ Detailed description
#### Defined in
-[packages/core/src/types.ts:440](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L440)
+[packages/core/src/types.ts:442](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L442)
***
@@ -38,7 +38,7 @@ Similar evaluator descriptions
#### Defined in
-[packages/core/src/types.ts:443](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L443)
+[packages/core/src/types.ts:445](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L445)
***
@@ -50,7 +50,7 @@ Example evaluations
#### Defined in
-[packages/core/src/types.ts:446](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L446)
+[packages/core/src/types.ts:448](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L448)
***
@@ -62,7 +62,7 @@ Handler function
#### Defined in
-[packages/core/src/types.ts:449](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L449)
+[packages/core/src/types.ts:451](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L451)
***
@@ -74,7 +74,7 @@ Evaluator name
#### Defined in
-[packages/core/src/types.ts:452](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L452)
+[packages/core/src/types.ts:454](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L454)
***
@@ -86,4 +86,4 @@ Validation function
#### Defined in
-[packages/core/src/types.ts:455](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L455)
+[packages/core/src/types.ts:457](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L457)
diff --git a/docs/api/interfaces/GenerationOptions.md b/docs/api/interfaces/GenerationOptions.md
index 03766b12dc3..0af87ae8532 100644
--- a/docs/api/interfaces/GenerationOptions.md
+++ b/docs/api/interfaces/GenerationOptions.md
@@ -12,7 +12,7 @@ Configuration options for generating objects with a model.
#### Defined in
-[packages/core/src/generation.ts:1123](https://github.com/ai16z/eliza/blob/main/packages/core/src/generation.ts#L1123)
+[packages/core/src/generation.ts:1235](https://github.com/ai16z/eliza/blob/main/packages/core/src/generation.ts#L1235)
***
@@ -22,7 +22,7 @@ Configuration options for generating objects with a model.
#### Defined in
-[packages/core/src/generation.ts:1124](https://github.com/ai16z/eliza/blob/main/packages/core/src/generation.ts#L1124)
+[packages/core/src/generation.ts:1236](https://github.com/ai16z/eliza/blob/main/packages/core/src/generation.ts#L1236)
***
@@ -32,7 +32,7 @@ Configuration options for generating objects with a model.
#### Defined in
-[packages/core/src/generation.ts:1125](https://github.com/ai16z/eliza/blob/main/packages/core/src/generation.ts#L1125)
+[packages/core/src/generation.ts:1237](https://github.com/ai16z/eliza/blob/main/packages/core/src/generation.ts#L1237)
***
@@ -42,7 +42,7 @@ Configuration options for generating objects with a model.
#### Defined in
-[packages/core/src/generation.ts:1126](https://github.com/ai16z/eliza/blob/main/packages/core/src/generation.ts#L1126)
+[packages/core/src/generation.ts:1238](https://github.com/ai16z/eliza/blob/main/packages/core/src/generation.ts#L1238)
***
@@ -52,7 +52,7 @@ Configuration options for generating objects with a model.
#### Defined in
-[packages/core/src/generation.ts:1127](https://github.com/ai16z/eliza/blob/main/packages/core/src/generation.ts#L1127)
+[packages/core/src/generation.ts:1239](https://github.com/ai16z/eliza/blob/main/packages/core/src/generation.ts#L1239)
***
@@ -62,7 +62,7 @@ Configuration options for generating objects with a model.
#### Defined in
-[packages/core/src/generation.ts:1128](https://github.com/ai16z/eliza/blob/main/packages/core/src/generation.ts#L1128)
+[packages/core/src/generation.ts:1240](https://github.com/ai16z/eliza/blob/main/packages/core/src/generation.ts#L1240)
***
@@ -72,7 +72,7 @@ Configuration options for generating objects with a model.
#### Defined in
-[packages/core/src/generation.ts:1129](https://github.com/ai16z/eliza/blob/main/packages/core/src/generation.ts#L1129)
+[packages/core/src/generation.ts:1241](https://github.com/ai16z/eliza/blob/main/packages/core/src/generation.ts#L1241)
***
@@ -82,7 +82,7 @@ Configuration options for generating objects with a model.
#### Defined in
-[packages/core/src/generation.ts:1130](https://github.com/ai16z/eliza/blob/main/packages/core/src/generation.ts#L1130)
+[packages/core/src/generation.ts:1242](https://github.com/ai16z/eliza/blob/main/packages/core/src/generation.ts#L1242)
***
@@ -92,4 +92,4 @@ Configuration options for generating objects with a model.
#### Defined in
-[packages/core/src/generation.ts:1131](https://github.com/ai16z/eliza/blob/main/packages/core/src/generation.ts#L1131)
+[packages/core/src/generation.ts:1243](https://github.com/ai16z/eliza/blob/main/packages/core/src/generation.ts#L1243)
diff --git a/docs/api/interfaces/IAgentConfig.md b/docs/api/interfaces/IAgentConfig.md
new file mode 100644
index 00000000000..3418df13640
--- /dev/null
+++ b/docs/api/interfaces/IAgentConfig.md
@@ -0,0 +1,7 @@
+[@ai16z/eliza v0.1.5-alpha.5](../index.md) / IAgentConfig
+
+# Interface: IAgentConfig
+
+## Indexable
+
+ \[`key`: `string`\]: `string`
diff --git a/docs/api/interfaces/IAgentRuntime.md b/docs/api/interfaces/IAgentRuntime.md
index e26a58fadbb..783ffabc415 100644
--- a/docs/api/interfaces/IAgentRuntime.md
+++ b/docs/api/interfaces/IAgentRuntime.md
@@ -12,7 +12,7 @@ Properties
#### Defined in
-[packages/core/src/types.ts:983](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L983)
+[packages/core/src/types.ts:1019](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L1019)
***
@@ -22,7 +22,7 @@ Properties
#### Defined in
-[packages/core/src/types.ts:984](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L984)
+[packages/core/src/types.ts:1020](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L1020)
***
@@ -32,7 +32,7 @@ Properties
#### Defined in
-[packages/core/src/types.ts:985](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L985)
+[packages/core/src/types.ts:1021](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L1021)
***
@@ -42,7 +42,7 @@ Properties
#### Defined in
-[packages/core/src/types.ts:986](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L986)
+[packages/core/src/types.ts:1022](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L1022)
***
@@ -52,7 +52,7 @@ Properties
#### Defined in
-[packages/core/src/types.ts:987](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L987)
+[packages/core/src/types.ts:1023](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L1023)
***
@@ -62,7 +62,7 @@ Properties
#### Defined in
-[packages/core/src/types.ts:988](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L988)
+[packages/core/src/types.ts:1024](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L1024)
***
@@ -72,7 +72,7 @@ Properties
#### Defined in
-[packages/core/src/types.ts:989](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L989)
+[packages/core/src/types.ts:1025](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L1025)
***
@@ -82,7 +82,7 @@ Properties
#### Defined in
-[packages/core/src/types.ts:990](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L990)
+[packages/core/src/types.ts:1026](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L1026)
***
@@ -92,7 +92,7 @@ Properties
#### Defined in
-[packages/core/src/types.ts:991](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L991)
+[packages/core/src/types.ts:1027](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L1027)
***
@@ -102,7 +102,7 @@ Properties
#### Defined in
-[packages/core/src/types.ts:992](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L992)
+[packages/core/src/types.ts:1028](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L1028)
***
@@ -112,7 +112,29 @@ Properties
#### Defined in
-[packages/core/src/types.ts:993](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L993)
+[packages/core/src/types.ts:1029](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L1029)
+
+***
+
+### fetch()?
+
+> `optional` **fetch**: (`input`, `init`?) => `Promise`\<`Response`\>
+
+[MDN Reference](https://developer.mozilla.org/docs/Web/API/fetch)
+
+#### Parameters
+
+• **input**: `RequestInfo` \| `URL`
+
+• **init?**: `RequestInit`
+
+#### Returns
+
+`Promise`\<`Response`\>
+
+#### Defined in
+
+[packages/core/src/types.ts:1031](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L1031)
***
@@ -122,7 +144,7 @@ Properties
#### Defined in
-[packages/core/src/types.ts:995](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L995)
+[packages/core/src/types.ts:1033](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L1033)
***
@@ -132,7 +154,7 @@ Properties
#### Defined in
-[packages/core/src/types.ts:996](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L996)
+[packages/core/src/types.ts:1034](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L1034)
***
@@ -142,7 +164,7 @@ Properties
#### Defined in
-[packages/core/src/types.ts:997](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L997)
+[packages/core/src/types.ts:1035](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L1035)
***
@@ -152,7 +174,7 @@ Properties
#### Defined in
-[packages/core/src/types.ts:998](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L998)
+[packages/core/src/types.ts:1036](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L1036)
***
@@ -162,7 +184,7 @@ Properties
#### Defined in
-[packages/core/src/types.ts:999](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L999)
+[packages/core/src/types.ts:1037](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L1037)
***
@@ -172,7 +194,7 @@ Properties
#### Defined in
-[packages/core/src/types.ts:1001](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L1001)
+[packages/core/src/types.ts:1039](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L1039)
***
@@ -182,7 +204,20 @@ Properties
#### Defined in
-[packages/core/src/types.ts:1003](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L1003)
+[packages/core/src/types.ts:1041](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L1041)
+
+***
+
+### clients
+
+> **clients**: `Record`\<`string`, `any`\>
+
+any could be EventEmitter
+but I think the real solution is forthcoming as a base client interface
+
+#### Defined in
+
+[packages/core/src/types.ts:1044](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L1044)
## Methods
@@ -196,7 +231,7 @@ Properties
#### Defined in
-[packages/core/src/types.ts:1005](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L1005)
+[packages/core/src/types.ts:1046](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L1046)
***
@@ -214,7 +249,7 @@ Properties
#### Defined in
-[packages/core/src/types.ts:1007](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L1007)
+[packages/core/src/types.ts:1048](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L1048)
***
@@ -232,7 +267,7 @@ Properties
#### Defined in
-[packages/core/src/types.ts:1009](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L1009)
+[packages/core/src/types.ts:1050](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L1050)
***
@@ -254,7 +289,7 @@ Properties
#### Defined in
-[packages/core/src/types.ts:1011](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L1011)
+[packages/core/src/types.ts:1052](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L1052)
***
@@ -272,7 +307,7 @@ Properties
#### Defined in
-[packages/core/src/types.ts:1013](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L1013)
+[packages/core/src/types.ts:1054](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L1054)
***
@@ -290,7 +325,7 @@ Properties
#### Defined in
-[packages/core/src/types.ts:1015](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L1015)
+[packages/core/src/types.ts:1056](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L1056)
***
@@ -306,7 +341,7 @@ Methods
#### Defined in
-[packages/core/src/types.ts:1018](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L1018)
+[packages/core/src/types.ts:1059](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L1059)
***
@@ -330,7 +365,7 @@ Methods
#### Defined in
-[packages/core/src/types.ts:1020](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L1020)
+[packages/core/src/types.ts:1061](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L1061)
***
@@ -354,7 +389,7 @@ Methods
#### Defined in
-[packages/core/src/types.ts:1027](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L1027)
+[packages/core/src/types.ts:1068](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L1068)
***
@@ -374,7 +409,7 @@ Methods
#### Defined in
-[packages/core/src/types.ts:1034](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L1034)
+[packages/core/src/types.ts:1075](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L1075)
***
@@ -398,7 +433,7 @@ Methods
#### Defined in
-[packages/core/src/types.ts:1036](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L1036)
+[packages/core/src/types.ts:1077](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L1077)
***
@@ -416,7 +451,7 @@ Methods
#### Defined in
-[packages/core/src/types.ts:1043](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L1043)
+[packages/core/src/types.ts:1084](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L1084)
***
@@ -442,7 +477,7 @@ Methods
#### Defined in
-[packages/core/src/types.ts:1045](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L1045)
+[packages/core/src/types.ts:1086](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L1086)
***
@@ -462,7 +497,7 @@ Methods
#### Defined in
-[packages/core/src/types.ts:1053](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L1053)
+[packages/core/src/types.ts:1094](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L1094)
***
@@ -480,7 +515,7 @@ Methods
#### Defined in
-[packages/core/src/types.ts:1055](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L1055)
+[packages/core/src/types.ts:1096](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L1096)
***
@@ -500,7 +535,7 @@ Methods
#### Defined in
-[packages/core/src/types.ts:1057](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L1057)
+[packages/core/src/types.ts:1098](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L1098)
***
@@ -518,4 +553,4 @@ Methods
#### Defined in
-[packages/core/src/types.ts:1062](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L1062)
+[packages/core/src/types.ts:1103](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L1103)
diff --git a/docs/api/interfaces/IAwsS3Service.md b/docs/api/interfaces/IAwsS3Service.md
index fd1c15b0bfd..0950ed85c35 100644
--- a/docs/api/interfaces/IAwsS3Service.md
+++ b/docs/api/interfaces/IAwsS3Service.md
@@ -24,7 +24,7 @@
#### Defined in
-[packages/core/src/types.ts:973](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L973)
+[packages/core/src/types.ts:1009](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L1009)
## Methods
@@ -48,18 +48,20 @@ Add abstract initialize method that must be implemented by derived classes
#### Defined in
-[packages/core/src/types.ts:978](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L978)
+[packages/core/src/types.ts:1014](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L1014)
***
### uploadFile()
-> **uploadFile**(`imagePath`, `useSignedUrl`, `expiresIn`): `Promise`\<`object`\>
+> **uploadFile**(`imagePath`, `subDirectory`, `useSignedUrl`, `expiresIn`): `Promise`\<`object`\>
#### Parameters
• **imagePath**: `string`
+• **subDirectory**: `string`
+
• **useSignedUrl**: `boolean`
• **expiresIn**: `number`
@@ -82,7 +84,7 @@ Add abstract initialize method that must be implemented by derived classes
#### Defined in
-[packages/core/src/types.ts:1127](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L1127)
+[packages/core/src/types.ts:1168](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L1168)
***
@@ -102,4 +104,4 @@ Add abstract initialize method that must be implemented by derived classes
#### Defined in
-[packages/core/src/types.ts:1132](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L1132)
+[packages/core/src/types.ts:1178](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L1178)
diff --git a/docs/api/interfaces/IBrowserService.md b/docs/api/interfaces/IBrowserService.md
index eccbe1df505..0124a5c46f6 100644
--- a/docs/api/interfaces/IBrowserService.md
+++ b/docs/api/interfaces/IBrowserService.md
@@ -24,7 +24,7 @@
#### Defined in
-[packages/core/src/types.ts:973](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L973)
+[packages/core/src/types.ts:1009](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L1009)
## Methods
@@ -48,7 +48,7 @@ Add abstract initialize method that must be implemented by derived classes
#### Defined in
-[packages/core/src/types.ts:978](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L978)
+[packages/core/src/types.ts:1014](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L1014)
***
@@ -62,7 +62,7 @@ Add abstract initialize method that must be implemented by derived classes
#### Defined in
-[packages/core/src/types.ts:1109](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L1109)
+[packages/core/src/types.ts:1150](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L1150)
***
@@ -94,4 +94,4 @@ Add abstract initialize method that must be implemented by derived classes
#### Defined in
-[packages/core/src/types.ts:1110](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L1110)
+[packages/core/src/types.ts:1151](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L1151)
diff --git a/docs/api/interfaces/ICacheManager.md b/docs/api/interfaces/ICacheManager.md
index d0327f39138..cc24dbde77c 100644
--- a/docs/api/interfaces/ICacheManager.md
+++ b/docs/api/interfaces/ICacheManager.md
@@ -22,7 +22,7 @@
#### Defined in
-[packages/core/src/types.ts:954](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L954)
+[packages/core/src/types.ts:990](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L990)
***
@@ -48,7 +48,7 @@
#### Defined in
-[packages/core/src/types.ts:955](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L955)
+[packages/core/src/types.ts:991](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L991)
***
@@ -66,4 +66,4 @@
#### Defined in
-[packages/core/src/types.ts:956](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L956)
+[packages/core/src/types.ts:992](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L992)
diff --git a/docs/api/interfaces/IDatabaseAdapter.md b/docs/api/interfaces/IDatabaseAdapter.md
index 2170ec88ae3..68b5450bbb3 100644
--- a/docs/api/interfaces/IDatabaseAdapter.md
+++ b/docs/api/interfaces/IDatabaseAdapter.md
@@ -14,7 +14,7 @@ Database instance
#### Defined in
-[packages/core/src/types.ts:745](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L745)
+[packages/core/src/types.ts:781](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L781)
## Methods
@@ -30,7 +30,7 @@ Optional initialization
#### Defined in
-[packages/core/src/types.ts:748](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L748)
+[packages/core/src/types.ts:784](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L784)
***
@@ -46,7 +46,7 @@ Close database connection
#### Defined in
-[packages/core/src/types.ts:751](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L751)
+[packages/core/src/types.ts:787](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L787)
***
@@ -66,7 +66,7 @@ Get account by ID
#### Defined in
-[packages/core/src/types.ts:754](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L754)
+[packages/core/src/types.ts:790](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L790)
***
@@ -86,7 +86,7 @@ Create new account
#### Defined in
-[packages/core/src/types.ts:757](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L757)
+[packages/core/src/types.ts:793](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L793)
***
@@ -120,7 +120,7 @@ Get memories matching criteria
#### Defined in
-[packages/core/src/types.ts:760](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L760)
+[packages/core/src/types.ts:796](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L796)
***
@@ -138,7 +138,7 @@ Get memories matching criteria
#### Defined in
-[packages/core/src/types.ts:770](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L770)
+[packages/core/src/types.ts:806](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L806)
***
@@ -162,7 +162,7 @@ Get memories matching criteria
#### Defined in
-[packages/core/src/types.ts:772](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L772)
+[packages/core/src/types.ts:808](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L808)
***
@@ -192,7 +192,7 @@ Get memories matching criteria
#### Defined in
-[packages/core/src/types.ts:778](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L778)
+[packages/core/src/types.ts:814](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L814)
***
@@ -218,7 +218,7 @@ Get memories matching criteria
#### Defined in
-[packages/core/src/types.ts:787](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L787)
+[packages/core/src/types.ts:823](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L823)
***
@@ -238,7 +238,7 @@ Get memories matching criteria
#### Defined in
-[packages/core/src/types.ts:794](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L794)
+[packages/core/src/types.ts:830](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L830)
***
@@ -270,7 +270,7 @@ Get memories matching criteria
#### Defined in
-[packages/core/src/types.ts:796](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L796)
+[packages/core/src/types.ts:832](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L832)
***
@@ -292,7 +292,7 @@ Get memories matching criteria
#### Defined in
-[packages/core/src/types.ts:806](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L806)
+[packages/core/src/types.ts:842](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L842)
***
@@ -324,7 +324,7 @@ Get memories matching criteria
#### Defined in
-[packages/core/src/types.ts:811](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L811)
+[packages/core/src/types.ts:847](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L847)
***
@@ -346,7 +346,7 @@ Get memories matching criteria
#### Defined in
-[packages/core/src/types.ts:823](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L823)
+[packages/core/src/types.ts:859](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L859)
***
@@ -366,7 +366,7 @@ Get memories matching criteria
#### Defined in
-[packages/core/src/types.ts:829](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L829)
+[packages/core/src/types.ts:865](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L865)
***
@@ -386,7 +386,7 @@ Get memories matching criteria
#### Defined in
-[packages/core/src/types.ts:831](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L831)
+[packages/core/src/types.ts:867](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L867)
***
@@ -408,7 +408,7 @@ Get memories matching criteria
#### Defined in
-[packages/core/src/types.ts:833](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L833)
+[packages/core/src/types.ts:869](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L869)
***
@@ -436,7 +436,7 @@ Get memories matching criteria
#### Defined in
-[packages/core/src/types.ts:839](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L839)
+[packages/core/src/types.ts:875](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L875)
***
@@ -454,7 +454,7 @@ Get memories matching criteria
#### Defined in
-[packages/core/src/types.ts:847](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L847)
+[packages/core/src/types.ts:883](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L883)
***
@@ -472,7 +472,7 @@ Get memories matching criteria
#### Defined in
-[packages/core/src/types.ts:849](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L849)
+[packages/core/src/types.ts:885](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L885)
***
@@ -490,7 +490,7 @@ Get memories matching criteria
#### Defined in
-[packages/core/src/types.ts:851](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L851)
+[packages/core/src/types.ts:887](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L887)
***
@@ -508,7 +508,7 @@ Get memories matching criteria
#### Defined in
-[packages/core/src/types.ts:853](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L853)
+[packages/core/src/types.ts:889](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L889)
***
@@ -526,7 +526,7 @@ Get memories matching criteria
#### Defined in
-[packages/core/src/types.ts:855](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L855)
+[packages/core/src/types.ts:891](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L891)
***
@@ -544,7 +544,7 @@ Get memories matching criteria
#### Defined in
-[packages/core/src/types.ts:857](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L857)
+[packages/core/src/types.ts:893](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L893)
***
@@ -562,7 +562,7 @@ Get memories matching criteria
#### Defined in
-[packages/core/src/types.ts:859](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L859)
+[packages/core/src/types.ts:895](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L895)
***
@@ -580,7 +580,7 @@ Get memories matching criteria
#### Defined in
-[packages/core/src/types.ts:861](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L861)
+[packages/core/src/types.ts:897](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L897)
***
@@ -598,7 +598,7 @@ Get memories matching criteria
#### Defined in
-[packages/core/src/types.ts:863](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L863)
+[packages/core/src/types.ts:899](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L899)
***
@@ -618,7 +618,7 @@ Get memories matching criteria
#### Defined in
-[packages/core/src/types.ts:865](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L865)
+[packages/core/src/types.ts:901](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L901)
***
@@ -638,7 +638,7 @@ Get memories matching criteria
#### Defined in
-[packages/core/src/types.ts:867](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L867)
+[packages/core/src/types.ts:903](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L903)
***
@@ -656,7 +656,7 @@ Get memories matching criteria
#### Defined in
-[packages/core/src/types.ts:869](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L869)
+[packages/core/src/types.ts:905](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L905)
***
@@ -674,7 +674,7 @@ Get memories matching criteria
#### Defined in
-[packages/core/src/types.ts:871](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L871)
+[packages/core/src/types.ts:907](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L907)
***
@@ -694,7 +694,7 @@ Get memories matching criteria
#### Defined in
-[packages/core/src/types.ts:873](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L873)
+[packages/core/src/types.ts:909](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L909)
***
@@ -716,7 +716,7 @@ Get memories matching criteria
#### Defined in
-[packages/core/src/types.ts:878](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L878)
+[packages/core/src/types.ts:914](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L914)
***
@@ -738,7 +738,7 @@ Get memories matching criteria
#### Defined in
-[packages/core/src/types.ts:884](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L884)
+[packages/core/src/types.ts:920](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L920)
***
@@ -760,7 +760,7 @@ Get memories matching criteria
#### Defined in
-[packages/core/src/types.ts:886](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L886)
+[packages/core/src/types.ts:922](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L922)
***
@@ -780,4 +780,4 @@ Get memories matching criteria
#### Defined in
-[packages/core/src/types.ts:891](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L891)
+[packages/core/src/types.ts:927](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L927)
diff --git a/docs/api/interfaces/IDatabaseCacheAdapter.md b/docs/api/interfaces/IDatabaseCacheAdapter.md
index e90a4e420e7..a1c7ee5f693 100644
--- a/docs/api/interfaces/IDatabaseCacheAdapter.md
+++ b/docs/api/interfaces/IDatabaseCacheAdapter.md
@@ -22,7 +22,7 @@
#### Defined in
-[packages/core/src/types.ts:895](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L895)
+[packages/core/src/types.ts:931](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L931)
***
@@ -46,7 +46,7 @@
#### Defined in
-[packages/core/src/types.ts:900](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L900)
+[packages/core/src/types.ts:936](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L936)
***
@@ -68,4 +68,4 @@
#### Defined in
-[packages/core/src/types.ts:906](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L906)
+[packages/core/src/types.ts:942](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L942)
diff --git a/docs/api/interfaces/IImageDescriptionService.md b/docs/api/interfaces/IImageDescriptionService.md
index fd85ca27e00..c4a3058bca1 100644
--- a/docs/api/interfaces/IImageDescriptionService.md
+++ b/docs/api/interfaces/IImageDescriptionService.md
@@ -24,7 +24,7 @@
#### Defined in
-[packages/core/src/types.ts:973](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L973)
+[packages/core/src/types.ts:1009](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L1009)
## Methods
@@ -48,7 +48,7 @@ Add abstract initialize method that must be implemented by derived classes
#### Defined in
-[packages/core/src/types.ts:978](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L978)
+[packages/core/src/types.ts:1014](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L1014)
***
@@ -74,4 +74,4 @@ Add abstract initialize method that must be implemented by derived classes
#### Defined in
-[packages/core/src/types.ts:1066](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L1066)
+[packages/core/src/types.ts:1107](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L1107)
diff --git a/docs/api/interfaces/IMemoryManager.md b/docs/api/interfaces/IMemoryManager.md
index c4fd1eba20f..d1b99798fea 100644
--- a/docs/api/interfaces/IMemoryManager.md
+++ b/docs/api/interfaces/IMemoryManager.md
@@ -10,7 +10,7 @@
#### Defined in
-[packages/core/src/types.ts:910](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L910)
+[packages/core/src/types.ts:946](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L946)
***
@@ -20,7 +20,7 @@
#### Defined in
-[packages/core/src/types.ts:911](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L911)
+[packages/core/src/types.ts:947](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L947)
***
@@ -30,7 +30,7 @@
#### Defined in
-[packages/core/src/types.ts:912](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L912)
+[packages/core/src/types.ts:948](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L948)
## Methods
@@ -48,7 +48,7 @@
#### Defined in
-[packages/core/src/types.ts:914](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L914)
+[packages/core/src/types.ts:950](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L950)
***
@@ -76,7 +76,7 @@
#### Defined in
-[packages/core/src/types.ts:916](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L916)
+[packages/core/src/types.ts:952](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L952)
***
@@ -94,7 +94,7 @@
#### Defined in
-[packages/core/src/types.ts:924](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L924)
+[packages/core/src/types.ts:960](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L960)
***
@@ -112,7 +112,7 @@
#### Defined in
-[packages/core/src/types.ts:928](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L928)
+[packages/core/src/types.ts:964](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L964)
***
@@ -132,7 +132,7 @@
#### Defined in
-[packages/core/src/types.ts:929](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L929)
+[packages/core/src/types.ts:965](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L965)
***
@@ -160,7 +160,7 @@
#### Defined in
-[packages/core/src/types.ts:930](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L930)
+[packages/core/src/types.ts:966](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L966)
***
@@ -180,7 +180,7 @@
#### Defined in
-[packages/core/src/types.ts:940](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L940)
+[packages/core/src/types.ts:976](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L976)
***
@@ -198,7 +198,7 @@
#### Defined in
-[packages/core/src/types.ts:942](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L942)
+[packages/core/src/types.ts:978](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L978)
***
@@ -216,7 +216,7 @@
#### Defined in
-[packages/core/src/types.ts:944](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L944)
+[packages/core/src/types.ts:980](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L980)
***
@@ -236,4 +236,4 @@
#### Defined in
-[packages/core/src/types.ts:946](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L946)
+[packages/core/src/types.ts:982](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L982)
diff --git a/docs/api/interfaces/IPdfService.md b/docs/api/interfaces/IPdfService.md
index ff300a617fb..5a0b5b855f5 100644
--- a/docs/api/interfaces/IPdfService.md
+++ b/docs/api/interfaces/IPdfService.md
@@ -24,7 +24,7 @@
#### Defined in
-[packages/core/src/types.ts:973](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L973)
+[packages/core/src/types.ts:1009](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L1009)
## Methods
@@ -48,7 +48,7 @@ Add abstract initialize method that must be implemented by derived classes
#### Defined in
-[packages/core/src/types.ts:978](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L978)
+[packages/core/src/types.ts:1014](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L1014)
***
@@ -62,7 +62,7 @@ Add abstract initialize method that must be implemented by derived classes
#### Defined in
-[packages/core/src/types.ts:1122](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L1122)
+[packages/core/src/types.ts:1163](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L1163)
***
@@ -80,4 +80,4 @@ Add abstract initialize method that must be implemented by derived classes
#### Defined in
-[packages/core/src/types.ts:1123](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L1123)
+[packages/core/src/types.ts:1164](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L1164)
diff --git a/docs/api/interfaces/ISlackService.md b/docs/api/interfaces/ISlackService.md
new file mode 100644
index 00000000000..e5d47eb5413
--- /dev/null
+++ b/docs/api/interfaces/ISlackService.md
@@ -0,0 +1,61 @@
+[@ai16z/eliza v0.1.5-alpha.5](../index.md) / ISlackService
+
+# Interface: ISlackService
+
+## Extends
+
+- [`Service`](../classes/Service.md)
+
+## Properties
+
+### client
+
+> **client**: `any`
+
+#### Defined in
+
+[packages/core/src/types.ts:1231](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L1231)
+
+## Accessors
+
+### serviceType
+
+#### Get Signature
+
+> **get** **serviceType**(): [`ServiceType`](../enumerations/ServiceType.md)
+
+##### Returns
+
+[`ServiceType`](../enumerations/ServiceType.md)
+
+#### Inherited from
+
+[`Service`](../classes/Service.md).[`serviceType`](../classes/Service.md#serviceType-1)
+
+#### Defined in
+
+[packages/core/src/types.ts:1009](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L1009)
+
+## Methods
+
+### initialize()
+
+> `abstract` **initialize**(`runtime`): `Promise`\<`void`\>
+
+Add abstract initialize method that must be implemented by derived classes
+
+#### Parameters
+
+• **runtime**: [`IAgentRuntime`](IAgentRuntime.md)
+
+#### Returns
+
+`Promise`\<`void`\>
+
+#### Inherited from
+
+[`Service`](../classes/Service.md).[`initialize`](../classes/Service.md#initialize)
+
+#### Defined in
+
+[packages/core/src/types.ts:1014](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L1014)
diff --git a/docs/api/interfaces/ISpeechService.md b/docs/api/interfaces/ISpeechService.md
index 6cb4cfd94b5..3e22869af15 100644
--- a/docs/api/interfaces/ISpeechService.md
+++ b/docs/api/interfaces/ISpeechService.md
@@ -24,7 +24,7 @@
#### Defined in
-[packages/core/src/types.ts:973](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L973)
+[packages/core/src/types.ts:1009](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L1009)
## Methods
@@ -48,7 +48,7 @@ Add abstract initialize method that must be implemented by derived classes
#### Defined in
-[packages/core/src/types.ts:978](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L978)
+[packages/core/src/types.ts:1014](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L1014)
***
@@ -62,7 +62,7 @@ Add abstract initialize method that must be implemented by derived classes
#### Defined in
-[packages/core/src/types.ts:1117](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L1117)
+[packages/core/src/types.ts:1158](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L1158)
***
@@ -82,4 +82,4 @@ Add abstract initialize method that must be implemented by derived classes
#### Defined in
-[packages/core/src/types.ts:1118](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L1118)
+[packages/core/src/types.ts:1159](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L1159)
diff --git a/docs/api/interfaces/ITextGenerationService.md b/docs/api/interfaces/ITextGenerationService.md
index e395b7998b1..2619895fcf2 100644
--- a/docs/api/interfaces/ITextGenerationService.md
+++ b/docs/api/interfaces/ITextGenerationService.md
@@ -24,7 +24,7 @@
#### Defined in
-[packages/core/src/types.ts:973](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L973)
+[packages/core/src/types.ts:1009](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L1009)
## Methods
@@ -48,7 +48,7 @@ Add abstract initialize method that must be implemented by derived classes
#### Defined in
-[packages/core/src/types.ts:978](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L978)
+[packages/core/src/types.ts:1014](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L1014)
***
@@ -62,7 +62,7 @@ Add abstract initialize method that must be implemented by derived classes
#### Defined in
-[packages/core/src/types.ts:1088](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L1088)
+[packages/core/src/types.ts:1129](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L1129)
***
@@ -90,7 +90,7 @@ Add abstract initialize method that must be implemented by derived classes
#### Defined in
-[packages/core/src/types.ts:1089](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L1089)
+[packages/core/src/types.ts:1130](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L1130)
***
@@ -118,7 +118,7 @@ Add abstract initialize method that must be implemented by derived classes
#### Defined in
-[packages/core/src/types.ts:1097](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L1097)
+[packages/core/src/types.ts:1138](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L1138)
***
@@ -136,4 +136,4 @@ Add abstract initialize method that must be implemented by derived classes
#### Defined in
-[packages/core/src/types.ts:1105](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L1105)
+[packages/core/src/types.ts:1146](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L1146)
diff --git a/docs/api/interfaces/ITranscriptionService.md b/docs/api/interfaces/ITranscriptionService.md
index 1dbf40fd1cf..3db67bdf893 100644
--- a/docs/api/interfaces/ITranscriptionService.md
+++ b/docs/api/interfaces/ITranscriptionService.md
@@ -24,7 +24,7 @@
#### Defined in
-[packages/core/src/types.ts:973](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L973)
+[packages/core/src/types.ts:1009](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L1009)
## Methods
@@ -48,7 +48,7 @@ Add abstract initialize method that must be implemented by derived classes
#### Defined in
-[packages/core/src/types.ts:978](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L978)
+[packages/core/src/types.ts:1014](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L1014)
***
@@ -66,7 +66,7 @@ Add abstract initialize method that must be implemented by derived classes
#### Defined in
-[packages/core/src/types.ts:1072](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L1072)
+[packages/core/src/types.ts:1113](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L1113)
***
@@ -84,7 +84,7 @@ Add abstract initialize method that must be implemented by derived classes
#### Defined in
-[packages/core/src/types.ts:1073](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L1073)
+[packages/core/src/types.ts:1114](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L1114)
***
@@ -102,7 +102,7 @@ Add abstract initialize method that must be implemented by derived classes
#### Defined in
-[packages/core/src/types.ts:1076](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L1076)
+[packages/core/src/types.ts:1117](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L1117)
***
@@ -120,4 +120,4 @@ Add abstract initialize method that must be implemented by derived classes
#### Defined in
-[packages/core/src/types.ts:1077](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L1077)
+[packages/core/src/types.ts:1118](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L1118)
diff --git a/docs/api/interfaces/IVideoService.md b/docs/api/interfaces/IVideoService.md
index 58837a713b0..7c7c8e89119 100644
--- a/docs/api/interfaces/IVideoService.md
+++ b/docs/api/interfaces/IVideoService.md
@@ -24,7 +24,7 @@
#### Defined in
-[packages/core/src/types.ts:973](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L973)
+[packages/core/src/types.ts:1009](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L1009)
## Methods
@@ -48,7 +48,7 @@ Add abstract initialize method that must be implemented by derived classes
#### Defined in
-[packages/core/src/types.ts:978](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L978)
+[packages/core/src/types.ts:1014](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L1014)
***
@@ -66,7 +66,7 @@ Add abstract initialize method that must be implemented by derived classes
#### Defined in
-[packages/core/src/types.ts:1081](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L1081)
+[packages/core/src/types.ts:1122](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L1122)
***
@@ -84,7 +84,7 @@ Add abstract initialize method that must be implemented by derived classes
#### Defined in
-[packages/core/src/types.ts:1082](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L1082)
+[packages/core/src/types.ts:1123](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L1123)
***
@@ -102,7 +102,7 @@ Add abstract initialize method that must be implemented by derived classes
#### Defined in
-[packages/core/src/types.ts:1083](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L1083)
+[packages/core/src/types.ts:1124](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L1124)
***
@@ -122,4 +122,4 @@ Add abstract initialize method that must be implemented by derived classes
#### Defined in
-[packages/core/src/types.ts:1084](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L1084)
+[packages/core/src/types.ts:1125](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L1125)
diff --git a/docs/api/interfaces/Memory.md b/docs/api/interfaces/Memory.md
index 1331eb06b14..be0f39e1ab1 100644
--- a/docs/api/interfaces/Memory.md
+++ b/docs/api/interfaces/Memory.md
@@ -14,7 +14,7 @@ Optional unique identifier
#### Defined in
-[packages/core/src/types.ts:329](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L329)
+[packages/core/src/types.ts:331](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L331)
***
@@ -26,7 +26,7 @@ Associated user ID
#### Defined in
-[packages/core/src/types.ts:332](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L332)
+[packages/core/src/types.ts:334](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L334)
***
@@ -38,7 +38,7 @@ Associated agent ID
#### Defined in
-[packages/core/src/types.ts:335](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L335)
+[packages/core/src/types.ts:337](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L337)
***
@@ -50,7 +50,7 @@ Optional creation timestamp
#### Defined in
-[packages/core/src/types.ts:338](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L338)
+[packages/core/src/types.ts:340](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L340)
***
@@ -62,7 +62,7 @@ Memory content
#### Defined in
-[packages/core/src/types.ts:341](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L341)
+[packages/core/src/types.ts:343](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L343)
***
@@ -74,7 +74,7 @@ Optional embedding vector
#### Defined in
-[packages/core/src/types.ts:344](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L344)
+[packages/core/src/types.ts:346](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L346)
***
@@ -86,7 +86,7 @@ Associated room ID
#### Defined in
-[packages/core/src/types.ts:347](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L347)
+[packages/core/src/types.ts:349](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L349)
***
@@ -98,7 +98,7 @@ Whether memory is unique
#### Defined in
-[packages/core/src/types.ts:350](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L350)
+[packages/core/src/types.ts:352](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L352)
***
@@ -110,4 +110,4 @@ Embedding similarity score
#### Defined in
-[packages/core/src/types.ts:353](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L353)
+[packages/core/src/types.ts:355](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L355)
diff --git a/docs/api/interfaces/MessageExample.md b/docs/api/interfaces/MessageExample.md
index 0faf0f54388..9ddcdea6edc 100644
--- a/docs/api/interfaces/MessageExample.md
+++ b/docs/api/interfaces/MessageExample.md
@@ -14,7 +14,7 @@ Associated user
#### Defined in
-[packages/core/src/types.ts:361](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L361)
+[packages/core/src/types.ts:363](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L363)
***
@@ -26,4 +26,4 @@ Message content
#### Defined in
-[packages/core/src/types.ts:364](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L364)
+[packages/core/src/types.ts:366](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L366)
diff --git a/docs/api/interfaces/Participant.md b/docs/api/interfaces/Participant.md
index dc2f92c3465..4f32cca4efa 100644
--- a/docs/api/interfaces/Participant.md
+++ b/docs/api/interfaces/Participant.md
@@ -14,7 +14,7 @@ Unique identifier
#### Defined in
-[packages/core/src/types.ts:524](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L524)
+[packages/core/src/types.ts:526](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L526)
***
@@ -26,4 +26,4 @@ Associated account
#### Defined in
-[packages/core/src/types.ts:527](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L527)
+[packages/core/src/types.ts:529](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L529)
diff --git a/docs/api/interfaces/Provider.md b/docs/api/interfaces/Provider.md
index 733bb09afc5..e0e5455f686 100644
--- a/docs/api/interfaces/Provider.md
+++ b/docs/api/interfaces/Provider.md
@@ -26,4 +26,4 @@ Data retrieval function
#### Defined in
-[packages/core/src/types.ts:463](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L463)
+[packages/core/src/types.ts:465](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L465)
diff --git a/docs/api/interfaces/Relationship.md b/docs/api/interfaces/Relationship.md
index 1cb447e07e6..e893764887f 100644
--- a/docs/api/interfaces/Relationship.md
+++ b/docs/api/interfaces/Relationship.md
@@ -14,7 +14,7 @@ Unique identifier
#### Defined in
-[packages/core/src/types.ts:475](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L475)
+[packages/core/src/types.ts:477](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L477)
***
@@ -26,7 +26,7 @@ First user ID
#### Defined in
-[packages/core/src/types.ts:478](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L478)
+[packages/core/src/types.ts:480](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L480)
***
@@ -38,7 +38,7 @@ Second user ID
#### Defined in
-[packages/core/src/types.ts:481](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L481)
+[packages/core/src/types.ts:483](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L483)
***
@@ -50,7 +50,7 @@ Primary user ID
#### Defined in
-[packages/core/src/types.ts:484](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L484)
+[packages/core/src/types.ts:486](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L486)
***
@@ -62,7 +62,7 @@ Associated room ID
#### Defined in
-[packages/core/src/types.ts:487](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L487)
+[packages/core/src/types.ts:489](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L489)
***
@@ -74,7 +74,7 @@ Relationship status
#### Defined in
-[packages/core/src/types.ts:490](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L490)
+[packages/core/src/types.ts:492](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L492)
***
@@ -86,4 +86,4 @@ Optional creation timestamp
#### Defined in
-[packages/core/src/types.ts:493](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L493)
+[packages/core/src/types.ts:495](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L495)
diff --git a/docs/api/interfaces/Room.md b/docs/api/interfaces/Room.md
index e734586ddbb..70a52269dde 100644
--- a/docs/api/interfaces/Room.md
+++ b/docs/api/interfaces/Room.md
@@ -14,7 +14,7 @@ Unique identifier
#### Defined in
-[packages/core/src/types.ts:535](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L535)
+[packages/core/src/types.ts:537](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L537)
***
@@ -26,4 +26,4 @@ Room participants
#### Defined in
-[packages/core/src/types.ts:538](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L538)
+[packages/core/src/types.ts:540](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L540)
diff --git a/docs/api/interfaces/State.md b/docs/api/interfaces/State.md
index 018176a1419..100708227ff 100644
--- a/docs/api/interfaces/State.md
+++ b/docs/api/interfaces/State.md
@@ -18,7 +18,7 @@ ID of user who sent current message
#### Defined in
-[packages/core/src/types.ts:244](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L244)
+[packages/core/src/types.ts:246](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L246)
***
@@ -30,7 +30,7 @@ ID of agent in conversation
#### Defined in
-[packages/core/src/types.ts:247](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L247)
+[packages/core/src/types.ts:249](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L249)
***
@@ -42,7 +42,7 @@ Agent's biography
#### Defined in
-[packages/core/src/types.ts:250](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L250)
+[packages/core/src/types.ts:252](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L252)
***
@@ -54,7 +54,7 @@ Agent's background lore
#### Defined in
-[packages/core/src/types.ts:253](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L253)
+[packages/core/src/types.ts:255](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L255)
***
@@ -66,7 +66,7 @@ Message handling directions
#### Defined in
-[packages/core/src/types.ts:256](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L256)
+[packages/core/src/types.ts:258](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L258)
***
@@ -78,7 +78,7 @@ Post handling directions
#### Defined in
-[packages/core/src/types.ts:259](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L259)
+[packages/core/src/types.ts:261](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L261)
***
@@ -90,7 +90,7 @@ Current room/conversation ID
#### Defined in
-[packages/core/src/types.ts:262](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L262)
+[packages/core/src/types.ts:264](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L264)
***
@@ -102,7 +102,7 @@ Optional agent name
#### Defined in
-[packages/core/src/types.ts:265](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L265)
+[packages/core/src/types.ts:267](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L267)
***
@@ -114,7 +114,7 @@ Optional message sender name
#### Defined in
-[packages/core/src/types.ts:268](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L268)
+[packages/core/src/types.ts:270](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L270)
***
@@ -126,7 +126,7 @@ String representation of conversation actors
#### Defined in
-[packages/core/src/types.ts:271](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L271)
+[packages/core/src/types.ts:273](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L273)
***
@@ -138,7 +138,7 @@ Optional array of actor objects
#### Defined in
-[packages/core/src/types.ts:274](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L274)
+[packages/core/src/types.ts:276](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L276)
***
@@ -150,7 +150,7 @@ Optional string representation of goals
#### Defined in
-[packages/core/src/types.ts:277](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L277)
+[packages/core/src/types.ts:279](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L279)
***
@@ -162,7 +162,7 @@ Optional array of goal objects
#### Defined in
-[packages/core/src/types.ts:280](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L280)
+[packages/core/src/types.ts:282](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L282)
***
@@ -174,7 +174,7 @@ Recent message history as string
#### Defined in
-[packages/core/src/types.ts:283](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L283)
+[packages/core/src/types.ts:285](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L285)
***
@@ -186,7 +186,7 @@ Recent message objects
#### Defined in
-[packages/core/src/types.ts:286](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L286)
+[packages/core/src/types.ts:288](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L288)
***
@@ -198,7 +198,7 @@ Optional valid action names
#### Defined in
-[packages/core/src/types.ts:289](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L289)
+[packages/core/src/types.ts:291](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L291)
***
@@ -210,7 +210,7 @@ Optional action descriptions
#### Defined in
-[packages/core/src/types.ts:292](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L292)
+[packages/core/src/types.ts:294](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L294)
***
@@ -222,7 +222,7 @@ Optional action objects
#### Defined in
-[packages/core/src/types.ts:295](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L295)
+[packages/core/src/types.ts:297](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L297)
***
@@ -234,7 +234,7 @@ Optional action examples
#### Defined in
-[packages/core/src/types.ts:298](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L298)
+[packages/core/src/types.ts:300](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L300)
***
@@ -246,7 +246,7 @@ Optional provider descriptions
#### Defined in
-[packages/core/src/types.ts:301](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L301)
+[packages/core/src/types.ts:303](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L303)
***
@@ -258,7 +258,7 @@ Optional response content
#### Defined in
-[packages/core/src/types.ts:304](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L304)
+[packages/core/src/types.ts:306](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L306)
***
@@ -270,7 +270,7 @@ Optional recent interaction objects
#### Defined in
-[packages/core/src/types.ts:307](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L307)
+[packages/core/src/types.ts:309](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L309)
***
@@ -282,7 +282,7 @@ Optional recent interactions string
#### Defined in
-[packages/core/src/types.ts:310](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L310)
+[packages/core/src/types.ts:312](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L312)
***
@@ -294,7 +294,7 @@ Optional formatted conversation
#### Defined in
-[packages/core/src/types.ts:313](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L313)
+[packages/core/src/types.ts:315](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L315)
***
@@ -306,7 +306,7 @@ Optional formatted knowledge
#### Defined in
-[packages/core/src/types.ts:316](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L316)
+[packages/core/src/types.ts:318](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L318)
***
@@ -318,4 +318,4 @@ Optional knowledge data
#### Defined in
-[packages/core/src/types.ts:318](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L318)
+[packages/core/src/types.ts:320](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L320)
diff --git a/docs/api/type-aliases/CacheOptions.md b/docs/api/type-aliases/CacheOptions.md
index de6c8345fa4..3127cb37a41 100644
--- a/docs/api/type-aliases/CacheOptions.md
+++ b/docs/api/type-aliases/CacheOptions.md
@@ -12,4 +12,4 @@
## Defined in
-[packages/core/src/types.ts:949](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L949)
+[packages/core/src/types.ts:985](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L985)
diff --git a/docs/api/type-aliases/Character.md b/docs/api/type-aliases/Character.md
index 42461f26b70..e5940f1fdf4 100644
--- a/docs/api/type-aliases/Character.md
+++ b/docs/api/type-aliases/Character.md
@@ -100,14 +100,26 @@ Optional prompt templates
> `optional` **farcasterPostTemplate**: `string`
+### templates.lensPostTemplate?
+
+> `optional` **lensPostTemplate**: `string`
+
### templates.farcasterMessageHandlerTemplate?
> `optional` **farcasterMessageHandlerTemplate**: `string`
+### templates.lensMessageHandlerTemplate?
+
+> `optional` **lensMessageHandlerTemplate**: `string`
+
### templates.farcasterShouldRespondTemplate?
> `optional` **farcasterShouldRespondTemplate**: `string`
+### templates.lensShouldRespondTemplate?
+
+> `optional` **lensShouldRespondTemplate**: `string`
+
### templates.telegramMessageHandlerTemplate?
> `optional` **telegramMessageHandlerTemplate**: `string`
@@ -128,6 +140,14 @@ Optional prompt templates
> `optional` **discordMessageHandlerTemplate**: `string`
+### templates.slackMessageHandlerTemplate?
+
+> `optional` **slackMessageHandlerTemplate**: `string`
+
+### templates.slackShouldRespondTemplate?
+
+> `optional` **slackShouldRespondTemplate**: `string`
+
### bio
> **bio**: `string` \| `string`[]
@@ -284,6 +304,26 @@ Optional client-specific config
> `optional` **shouldIgnoreDirectMessages**: `boolean`
+### clientConfig.discord.messageSimilarityThreshold?
+
+> `optional` **messageSimilarityThreshold**: `number`
+
+### clientConfig.discord.isPartOfTeam?
+
+> `optional` **isPartOfTeam**: `boolean`
+
+### clientConfig.discord.teamAgentIds?
+
+> `optional` **teamAgentIds**: `string`[]
+
+### clientConfig.discord.teamLeaderId?
+
+> `optional` **teamLeaderId**: `string`
+
+### clientConfig.discord.teamMemberInterestKeywords?
+
+> `optional` **teamMemberInterestKeywords**: `string`[]
+
### clientConfig.telegram?
> `optional` **telegram**: `object`
@@ -296,6 +336,38 @@ Optional client-specific config
> `optional` **shouldIgnoreDirectMessages**: `boolean`
+### clientConfig.telegram.messageSimilarityThreshold?
+
+> `optional` **messageSimilarityThreshold**: `number`
+
+### clientConfig.telegram.isPartOfTeam?
+
+> `optional` **isPartOfTeam**: `boolean`
+
+### clientConfig.telegram.teamAgentIds?
+
+> `optional` **teamAgentIds**: `string`[]
+
+### clientConfig.telegram.teamLeaderId?
+
+> `optional` **teamLeaderId**: `string`
+
+### clientConfig.telegram.teamMemberInterestKeywords?
+
+> `optional` **teamMemberInterestKeywords**: `string`[]
+
+### clientConfig.slack?
+
+> `optional` **slack**: `object`
+
+### clientConfig.slack.shouldIgnoreBotMessages?
+
+> `optional` **shouldIgnoreBotMessages**: `boolean`
+
+### clientConfig.slack.shouldIgnoreDirectMessages?
+
+> `optional` **shouldIgnoreDirectMessages**: `boolean`
+
### style
> **style**: `object`
@@ -340,6 +412,16 @@ Optional Twitter profile
> `optional` **nicknames**: `string`[]
+### nft?
+
+> `optional` **nft**: `object`
+
+Optional NFT prompt
+
+### nft.prompt
+
+> **prompt**: `string`
+
## Defined in
-[packages/core/src/types.ts:614](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L614)
+[packages/core/src/types.ts:627](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L627)
diff --git a/docs/api/type-aliases/CharacterConfig.md b/docs/api/type-aliases/CharacterConfig.md
index d15c6d7ca79..33206b99c15 100644
--- a/docs/api/type-aliases/CharacterConfig.md
+++ b/docs/api/type-aliases/CharacterConfig.md
@@ -8,4 +8,4 @@ Type inference
## Defined in
-[packages/core/src/environment.ts:130](https://github.com/ai16z/eliza/blob/main/packages/core/src/environment.ts#L130)
+[packages/core/src/environment.ts:135](https://github.com/ai16z/eliza/blob/main/packages/core/src/environment.ts#L135)
diff --git a/docs/api/type-aliases/Client.md b/docs/api/type-aliases/Client.md
index 2c933738285..6a20f928a3c 100644
--- a/docs/api/type-aliases/Client.md
+++ b/docs/api/type-aliases/Client.md
@@ -10,13 +10,13 @@ Client interface for platform connections
### start()
-> **start**: (`runtime`?) => `Promise`\<`unknown`\>
+> **start**: (`runtime`) => `Promise`\<`unknown`\>
Start client connection
#### Parameters
-• **runtime?**: [`IAgentRuntime`](../interfaces/IAgentRuntime.md)
+• **runtime**: [`IAgentRuntime`](../interfaces/IAgentRuntime.md)
#### Returns
@@ -24,13 +24,13 @@ Start client connection
### stop()
-> **stop**: (`runtime`?) => `Promise`\<`unknown`\>
+> **stop**: (`runtime`) => `Promise`\<`unknown`\>
Stop client connection
#### Parameters
-• **runtime?**: [`IAgentRuntime`](../interfaces/IAgentRuntime.md)
+• **runtime**: [`IAgentRuntime`](../interfaces/IAgentRuntime.md)
#### Returns
@@ -38,4 +38,4 @@ Stop client connection
## Defined in
-[packages/core/src/types.ts:567](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L567)
+[packages/core/src/types.ts:572](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L572)
diff --git a/docs/api/type-aliases/Handler.md b/docs/api/type-aliases/Handler.md
index 7d41dfef227..8fe4d6d4b12 100644
--- a/docs/api/type-aliases/Handler.md
+++ b/docs/api/type-aliases/Handler.md
@@ -24,4 +24,4 @@ Handler function type for processing messages
## Defined in
-[packages/core/src/types.ts:370](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L370)
+[packages/core/src/types.ts:372](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L372)
diff --git a/docs/api/type-aliases/HandlerCallback.md b/docs/api/type-aliases/HandlerCallback.md
index 61bb41625d6..cd9f31de38c 100644
--- a/docs/api/type-aliases/HandlerCallback.md
+++ b/docs/api/type-aliases/HandlerCallback.md
@@ -18,4 +18,4 @@ Callback function type for handlers
## Defined in
-[packages/core/src/types.ts:381](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L381)
+[packages/core/src/types.ts:383](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L383)
diff --git a/docs/api/type-aliases/KnowledgeItem.md b/docs/api/type-aliases/KnowledgeItem.md
index 09cfdf2dc1d..43727c10e18 100644
--- a/docs/api/type-aliases/KnowledgeItem.md
+++ b/docs/api/type-aliases/KnowledgeItem.md
@@ -16,4 +16,4 @@
## Defined in
-[packages/core/src/types.ts:1170](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L1170)
+[packages/core/src/types.ts:1218](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L1218)
diff --git a/docs/api/type-aliases/Media.md b/docs/api/type-aliases/Media.md
index 05ffa5c2ff4..ac07beef77b 100644
--- a/docs/api/type-aliases/Media.md
+++ b/docs/api/type-aliases/Media.md
@@ -44,6 +44,12 @@ Media description
Text content
+### contentType?
+
+> `optional` **contentType**: `string`
+
+Content type
+
## Defined in
-[packages/core/src/types.ts:544](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L544)
+[packages/core/src/types.ts:546](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L546)
diff --git a/docs/api/type-aliases/Models.md b/docs/api/type-aliases/Models.md
index 96c77b67420..24c57204aaa 100644
--- a/docs/api/type-aliases/Models.md
+++ b/docs/api/type-aliases/Models.md
@@ -92,6 +92,10 @@ Model configurations by provider
> **hyperbolic**: [`Model`](Model.md)
+### venice
+
+> **venice**: [`Model`](Model.md)
+
## Defined in
[packages/core/src/types.ts:188](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L188)
diff --git a/docs/api/type-aliases/Plugin.md b/docs/api/type-aliases/Plugin.md
index 17589764147..06fcf7b4026 100644
--- a/docs/api/type-aliases/Plugin.md
+++ b/docs/api/type-aliases/Plugin.md
@@ -52,4 +52,4 @@ Optional clients
## Defined in
-[packages/core/src/types.ts:578](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L578)
+[packages/core/src/types.ts:583](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L583)
diff --git a/docs/api/type-aliases/SearchResponse.md b/docs/api/type-aliases/SearchResponse.md
index e192e0512c4..0b45aab1ef0 100644
--- a/docs/api/type-aliases/SearchResponse.md
+++ b/docs/api/type-aliases/SearchResponse.md
@@ -32,4 +32,4 @@
## Defined in
-[packages/core/src/types.ts:1143](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L1143)
+[packages/core/src/types.ts:1189](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L1189)
diff --git a/docs/api/type-aliases/SearchResult.md b/docs/api/type-aliases/SearchResult.md
index 06cdd00bcb5..7355f2fc751 100644
--- a/docs/api/type-aliases/SearchResult.md
+++ b/docs/api/type-aliases/SearchResult.md
@@ -28,4 +28,4 @@
## Defined in
-[packages/core/src/types.ts:1135](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L1135)
+[packages/core/src/types.ts:1181](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L1181)
diff --git a/docs/api/type-aliases/Validator.md b/docs/api/type-aliases/Validator.md
index b68b6471c00..ec2d409af1f 100644
--- a/docs/api/type-aliases/Validator.md
+++ b/docs/api/type-aliases/Validator.md
@@ -20,4 +20,4 @@ Validator function type for actions/evaluators
## Defined in
-[packages/core/src/types.ts:389](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L389)
+[packages/core/src/types.ts:391](https://github.com/ai16z/eliza/blob/main/packages/core/src/types.ts#L391)
diff --git a/docs/api/typedoc-sidebar.cjs b/docs/api/typedoc-sidebar.cjs
index ea1a8ef00a7..4465b937a74 100644
--- a/docs/api/typedoc-sidebar.cjs
+++ b/docs/api/typedoc-sidebar.cjs
@@ -1,4 +1,4 @@
// @ts-check
/** @type {import('@docusaurus/plugin-content-docs').SidebarsConfig} */
-const typedocSidebar = { items: [{"type":"category","label":"Enumerations","items":[{"type":"doc","id":"enumerations/GoalStatus","label":"GoalStatus"},{"type":"doc","id":"enumerations/ModelClass","label":"ModelClass"},{"type":"doc","id":"enumerations/ModelProviderName","label":"ModelProviderName"},{"type":"doc","id":"enumerations/Clients","label":"Clients"},{"type":"doc","id":"enumerations/ServiceType","label":"ServiceType"},{"type":"doc","id":"enumerations/LoggingLevel","label":"LoggingLevel"}]},{"type":"category","label":"Classes","items":[{"type":"doc","id":"classes/MemoryCacheAdapter","label":"MemoryCacheAdapter"},{"type":"doc","id":"classes/FsCacheAdapter","label":"FsCacheAdapter"},{"type":"doc","id":"classes/DbCacheAdapter","label":"DbCacheAdapter"},{"type":"doc","id":"classes/CacheManager","label":"CacheManager"},{"type":"doc","id":"classes/DatabaseAdapter","label":"DatabaseAdapter"},{"type":"doc","id":"classes/MemoryManager","label":"MemoryManager"},{"type":"doc","id":"classes/AgentRuntime","label":"AgentRuntime"},{"type":"doc","id":"classes/Service","label":"Service"}]},{"type":"category","label":"Interfaces","items":[{"type":"doc","id":"interfaces/ICacheAdapter","label":"ICacheAdapter"},{"type":"doc","id":"interfaces/GenerationOptions","label":"GenerationOptions"},{"type":"doc","id":"interfaces/Content","label":"Content"},{"type":"doc","id":"interfaces/ActionExample","label":"ActionExample"},{"type":"doc","id":"interfaces/ConversationExample","label":"ConversationExample"},{"type":"doc","id":"interfaces/Actor","label":"Actor"},{"type":"doc","id":"interfaces/Objective","label":"Objective"},{"type":"doc","id":"interfaces/Goal","label":"Goal"},{"type":"doc","id":"interfaces/State","label":"State"},{"type":"doc","id":"interfaces/Memory","label":"Memory"},{"type":"doc","id":"interfaces/MessageExample","label":"MessageExample"},{"type":"doc","id":"interfaces/Action","label":"Action"},{"type":"doc","id":"interfaces/EvaluationExample","label":"EvaluationExample"},{"type":"doc","id":"interfaces/Evaluator","label":"Evaluator"},{"type":"doc","id":"interfaces/Provider","label":"Provider"},{"type":"doc","id":"interfaces/Relationship","label":"Relationship"},{"type":"doc","id":"interfaces/Account","label":"Account"},{"type":"doc","id":"interfaces/Participant","label":"Participant"},{"type":"doc","id":"interfaces/Room","label":"Room"},{"type":"doc","id":"interfaces/IDatabaseAdapter","label":"IDatabaseAdapter"},{"type":"doc","id":"interfaces/IDatabaseCacheAdapter","label":"IDatabaseCacheAdapter"},{"type":"doc","id":"interfaces/IMemoryManager","label":"IMemoryManager"},{"type":"doc","id":"interfaces/ICacheManager","label":"ICacheManager"},{"type":"doc","id":"interfaces/IAgentRuntime","label":"IAgentRuntime"},{"type":"doc","id":"interfaces/IImageDescriptionService","label":"IImageDescriptionService"},{"type":"doc","id":"interfaces/ITranscriptionService","label":"ITranscriptionService"},{"type":"doc","id":"interfaces/IVideoService","label":"IVideoService"},{"type":"doc","id":"interfaces/ITextGenerationService","label":"ITextGenerationService"},{"type":"doc","id":"interfaces/IBrowserService","label":"IBrowserService"},{"type":"doc","id":"interfaces/ISpeechService","label":"ISpeechService"},{"type":"doc","id":"interfaces/IPdfService","label":"IPdfService"},{"type":"doc","id":"interfaces/IAwsS3Service","label":"IAwsS3Service"},{"type":"doc","id":"interfaces/ActionResponse","label":"ActionResponse"}]},{"type":"category","label":"Type Aliases","items":[{"type":"doc","id":"type-aliases/EnvConfig","label":"EnvConfig"},{"type":"doc","id":"type-aliases/CharacterConfig","label":"CharacterConfig"},{"type":"doc","id":"type-aliases/UUID","label":"UUID"},{"type":"doc","id":"type-aliases/Model","label":"Model"},{"type":"doc","id":"type-aliases/Models","label":"Models"},{"type":"doc","id":"type-aliases/Handler","label":"Handler"},{"type":"doc","id":"type-aliases/HandlerCallback","label":"HandlerCallback"},{"type":"doc","id":"type-aliases/Validator","label":"Validator"},{"type":"doc","id":"type-aliases/Media","label":"Media"},{"type":"doc","id":"type-aliases/Client","label":"Client"},{"type":"doc","id":"type-aliases/Plugin","label":"Plugin"},{"type":"doc","id":"type-aliases/Character","label":"Character"},{"type":"doc","id":"type-aliases/CacheOptions","label":"CacheOptions"},{"type":"doc","id":"type-aliases/SearchResult","label":"SearchResult"},{"type":"doc","id":"type-aliases/SearchResponse","label":"SearchResponse"},{"type":"doc","id":"type-aliases/KnowledgeItem","label":"KnowledgeItem"}]},{"type":"category","label":"Variables","items":[{"type":"doc","id":"variables/defaultCharacter","label":"defaultCharacter"},{"type":"doc","id":"variables/envSchema","label":"envSchema"},{"type":"doc","id":"variables/CharacterSchema","label":"CharacterSchema"},{"type":"doc","id":"variables/evaluationTemplate","label":"evaluationTemplate"},{"type":"doc","id":"variables/knowledge","label":"knowledge"},{"type":"doc","id":"variables/elizaLogger","label":"elizaLogger"},{"type":"doc","id":"variables/models","label":"models"},{"type":"doc","id":"variables/messageCompletionFooter","label":"messageCompletionFooter"},{"type":"doc","id":"variables/shouldRespondFooter","label":"shouldRespondFooter"},{"type":"doc","id":"variables/booleanFooter","label":"booleanFooter"},{"type":"doc","id":"variables/stringArrayFooter","label":"stringArrayFooter"},{"type":"doc","id":"variables/postActionResponseFooter","label":"postActionResponseFooter"},{"type":"doc","id":"variables/settings","label":"settings"}]},{"type":"category","label":"Functions","items":[{"type":"doc","id":"functions/composeActionExamples","label":"composeActionExamples"},{"type":"doc","id":"functions/formatActionNames","label":"formatActionNames"},{"type":"doc","id":"functions/formatActions","label":"formatActions"},{"type":"doc","id":"functions/composeContext","label":"composeContext"},{"type":"doc","id":"functions/addHeader","label":"addHeader"},{"type":"doc","id":"functions/getEmbeddingConfig","label":"getEmbeddingConfig"},{"type":"doc","id":"functions/getEmbeddingType","label":"getEmbeddingType"},{"type":"doc","id":"functions/getEmbeddingZeroVector","label":"getEmbeddingZeroVector"},{"type":"doc","id":"functions/embed","label":"embed"},{"type":"doc","id":"functions/validateEnv","label":"validateEnv"},{"type":"doc","id":"functions/validateCharacterConfig","label":"validateCharacterConfig"},{"type":"doc","id":"functions/formatEvaluatorNames","label":"formatEvaluatorNames"},{"type":"doc","id":"functions/formatEvaluators","label":"formatEvaluators"},{"type":"doc","id":"functions/formatEvaluatorExamples","label":"formatEvaluatorExamples"},{"type":"doc","id":"functions/formatEvaluatorExampleDescriptions","label":"formatEvaluatorExampleDescriptions"},{"type":"doc","id":"functions/generateText","label":"generateText"},{"type":"doc","id":"functions/trimTokens","label":"trimTokens"},{"type":"doc","id":"functions/generateShouldRespond","label":"generateShouldRespond"},{"type":"doc","id":"functions/splitChunks","label":"splitChunks"},{"type":"doc","id":"functions/generateTrueOrFalse","label":"generateTrueOrFalse"},{"type":"doc","id":"functions/generateTextArray","label":"generateTextArray"},{"type":"doc","id":"functions/generateObjectDEPRECATED","label":"generateObjectDEPRECATED"},{"type":"doc","id":"functions/generateObjectArray","label":"generateObjectArray"},{"type":"doc","id":"functions/generateMessageResponse","label":"generateMessageResponse"},{"type":"doc","id":"functions/generateImage","label":"generateImage"},{"type":"doc","id":"functions/generateCaption","label":"generateCaption"},{"type":"doc","id":"functions/generateWebSearch","label":"generateWebSearch"},{"type":"doc","id":"functions/generateObjectV2","label":"generateObjectV2"},{"type":"doc","id":"functions/handleProvider","label":"handleProvider"},{"type":"doc","id":"functions/generateTweetActions","label":"generateTweetActions"},{"type":"doc","id":"functions/getGoals","label":"getGoals"},{"type":"doc","id":"functions/formatGoalsAsString","label":"formatGoalsAsString"},{"type":"doc","id":"functions/updateGoal","label":"updateGoal"},{"type":"doc","id":"functions/createGoal","label":"createGoal"},{"type":"doc","id":"functions/getActorDetails","label":"getActorDetails"},{"type":"doc","id":"functions/formatActors","label":"formatActors"},{"type":"doc","id":"functions/formatMessages","label":"formatMessages"},{"type":"doc","id":"functions/formatTimestamp","label":"formatTimestamp"},{"type":"doc","id":"functions/getModel","label":"getModel"},{"type":"doc","id":"functions/getEndpoint","label":"getEndpoint"},{"type":"doc","id":"functions/parseShouldRespondFromText","label":"parseShouldRespondFromText"},{"type":"doc","id":"functions/parseBooleanFromText","label":"parseBooleanFromText"},{"type":"doc","id":"functions/parseJsonArrayFromText","label":"parseJsonArrayFromText"},{"type":"doc","id":"functions/parseJSONObjectFromText","label":"parseJSONObjectFromText"},{"type":"doc","id":"functions/parseActionResponseFromText","label":"parseActionResponseFromText"},{"type":"doc","id":"functions/formatPosts","label":"formatPosts"},{"type":"doc","id":"functions/getProviders","label":"getProviders"},{"type":"doc","id":"functions/createRelationship","label":"createRelationship"},{"type":"doc","id":"functions/getRelationship","label":"getRelationship"},{"type":"doc","id":"functions/getRelationships","label":"getRelationships"},{"type":"doc","id":"functions/formatRelationships","label":"formatRelationships"},{"type":"doc","id":"functions/findNearestEnvFile","label":"findNearestEnvFile"},{"type":"doc","id":"functions/configureSettings","label":"configureSettings"},{"type":"doc","id":"functions/loadEnvConfig","label":"loadEnvConfig"},{"type":"doc","id":"functions/getEnvVariable","label":"getEnvVariable"},{"type":"doc","id":"functions/hasEnvVariable","label":"hasEnvVariable"},{"type":"doc","id":"functions/stringToUuid","label":"stringToUuid"}]}]};
+const typedocSidebar = { items: [{"type":"category","label":"Enumerations","items":[{"type":"doc","id":"enumerations/GoalStatus","label":"GoalStatus"},{"type":"doc","id":"enumerations/ModelClass","label":"ModelClass"},{"type":"doc","id":"enumerations/ModelProviderName","label":"ModelProviderName"},{"type":"doc","id":"enumerations/Clients","label":"Clients"},{"type":"doc","id":"enumerations/ServiceType","label":"ServiceType"},{"type":"doc","id":"enumerations/LoggingLevel","label":"LoggingLevel"}]},{"type":"category","label":"Classes","items":[{"type":"doc","id":"classes/MemoryCacheAdapter","label":"MemoryCacheAdapter"},{"type":"doc","id":"classes/FsCacheAdapter","label":"FsCacheAdapter"},{"type":"doc","id":"classes/DbCacheAdapter","label":"DbCacheAdapter"},{"type":"doc","id":"classes/CacheManager","label":"CacheManager"},{"type":"doc","id":"classes/DatabaseAdapter","label":"DatabaseAdapter"},{"type":"doc","id":"classes/MemoryManager","label":"MemoryManager"},{"type":"doc","id":"classes/AgentRuntime","label":"AgentRuntime"},{"type":"doc","id":"classes/Service","label":"Service"}]},{"type":"category","label":"Interfaces","items":[{"type":"doc","id":"interfaces/ICacheAdapter","label":"ICacheAdapter"},{"type":"doc","id":"interfaces/GenerationOptions","label":"GenerationOptions"},{"type":"doc","id":"interfaces/Content","label":"Content"},{"type":"doc","id":"interfaces/ActionExample","label":"ActionExample"},{"type":"doc","id":"interfaces/ConversationExample","label":"ConversationExample"},{"type":"doc","id":"interfaces/Actor","label":"Actor"},{"type":"doc","id":"interfaces/Objective","label":"Objective"},{"type":"doc","id":"interfaces/Goal","label":"Goal"},{"type":"doc","id":"interfaces/State","label":"State"},{"type":"doc","id":"interfaces/Memory","label":"Memory"},{"type":"doc","id":"interfaces/MessageExample","label":"MessageExample"},{"type":"doc","id":"interfaces/Action","label":"Action"},{"type":"doc","id":"interfaces/EvaluationExample","label":"EvaluationExample"},{"type":"doc","id":"interfaces/Evaluator","label":"Evaluator"},{"type":"doc","id":"interfaces/Provider","label":"Provider"},{"type":"doc","id":"interfaces/Relationship","label":"Relationship"},{"type":"doc","id":"interfaces/Account","label":"Account"},{"type":"doc","id":"interfaces/Participant","label":"Participant"},{"type":"doc","id":"interfaces/Room","label":"Room"},{"type":"doc","id":"interfaces/IAgentConfig","label":"IAgentConfig"},{"type":"doc","id":"interfaces/IDatabaseAdapter","label":"IDatabaseAdapter"},{"type":"doc","id":"interfaces/IDatabaseCacheAdapter","label":"IDatabaseCacheAdapter"},{"type":"doc","id":"interfaces/IMemoryManager","label":"IMemoryManager"},{"type":"doc","id":"interfaces/ICacheManager","label":"ICacheManager"},{"type":"doc","id":"interfaces/IAgentRuntime","label":"IAgentRuntime"},{"type":"doc","id":"interfaces/IImageDescriptionService","label":"IImageDescriptionService"},{"type":"doc","id":"interfaces/ITranscriptionService","label":"ITranscriptionService"},{"type":"doc","id":"interfaces/IVideoService","label":"IVideoService"},{"type":"doc","id":"interfaces/ITextGenerationService","label":"ITextGenerationService"},{"type":"doc","id":"interfaces/IBrowserService","label":"IBrowserService"},{"type":"doc","id":"interfaces/ISpeechService","label":"ISpeechService"},{"type":"doc","id":"interfaces/IPdfService","label":"IPdfService"},{"type":"doc","id":"interfaces/IAwsS3Service","label":"IAwsS3Service"},{"type":"doc","id":"interfaces/ActionResponse","label":"ActionResponse"},{"type":"doc","id":"interfaces/ISlackService","label":"ISlackService"}]},{"type":"category","label":"Type Aliases","items":[{"type":"doc","id":"type-aliases/EnvConfig","label":"EnvConfig"},{"type":"doc","id":"type-aliases/CharacterConfig","label":"CharacterConfig"},{"type":"doc","id":"type-aliases/UUID","label":"UUID"},{"type":"doc","id":"type-aliases/Model","label":"Model"},{"type":"doc","id":"type-aliases/Models","label":"Models"},{"type":"doc","id":"type-aliases/Handler","label":"Handler"},{"type":"doc","id":"type-aliases/HandlerCallback","label":"HandlerCallback"},{"type":"doc","id":"type-aliases/Validator","label":"Validator"},{"type":"doc","id":"type-aliases/Media","label":"Media"},{"type":"doc","id":"type-aliases/Client","label":"Client"},{"type":"doc","id":"type-aliases/Plugin","label":"Plugin"},{"type":"doc","id":"type-aliases/Character","label":"Character"},{"type":"doc","id":"type-aliases/CacheOptions","label":"CacheOptions"},{"type":"doc","id":"type-aliases/SearchResult","label":"SearchResult"},{"type":"doc","id":"type-aliases/SearchResponse","label":"SearchResponse"},{"type":"doc","id":"type-aliases/KnowledgeItem","label":"KnowledgeItem"}]},{"type":"category","label":"Variables","items":[{"type":"doc","id":"variables/defaultCharacter","label":"defaultCharacter"},{"type":"doc","id":"variables/envSchema","label":"envSchema"},{"type":"doc","id":"variables/CharacterSchema","label":"CharacterSchema"},{"type":"doc","id":"variables/evaluationTemplate","label":"evaluationTemplate"},{"type":"doc","id":"variables/knowledge","label":"knowledge"},{"type":"doc","id":"variables/elizaLogger","label":"elizaLogger"},{"type":"doc","id":"variables/models","label":"models"},{"type":"doc","id":"variables/messageCompletionFooter","label":"messageCompletionFooter"},{"type":"doc","id":"variables/shouldRespondFooter","label":"shouldRespondFooter"},{"type":"doc","id":"variables/booleanFooter","label":"booleanFooter"},{"type":"doc","id":"variables/stringArrayFooter","label":"stringArrayFooter"},{"type":"doc","id":"variables/postActionResponseFooter","label":"postActionResponseFooter"},{"type":"doc","id":"variables/settings","label":"settings"}]},{"type":"category","label":"Functions","items":[{"type":"doc","id":"functions/composeActionExamples","label":"composeActionExamples"},{"type":"doc","id":"functions/formatActionNames","label":"formatActionNames"},{"type":"doc","id":"functions/formatActions","label":"formatActions"},{"type":"doc","id":"functions/composeContext","label":"composeContext"},{"type":"doc","id":"functions/addHeader","label":"addHeader"},{"type":"doc","id":"functions/getEmbeddingConfig","label":"getEmbeddingConfig"},{"type":"doc","id":"functions/getEmbeddingType","label":"getEmbeddingType"},{"type":"doc","id":"functions/getEmbeddingZeroVector","label":"getEmbeddingZeroVector"},{"type":"doc","id":"functions/embed","label":"embed"},{"type":"doc","id":"functions/validateEnv","label":"validateEnv"},{"type":"doc","id":"functions/validateCharacterConfig","label":"validateCharacterConfig"},{"type":"doc","id":"functions/formatEvaluatorNames","label":"formatEvaluatorNames"},{"type":"doc","id":"functions/formatEvaluators","label":"formatEvaluators"},{"type":"doc","id":"functions/formatEvaluatorExamples","label":"formatEvaluatorExamples"},{"type":"doc","id":"functions/formatEvaluatorExampleDescriptions","label":"formatEvaluatorExampleDescriptions"},{"type":"doc","id":"functions/generateText","label":"generateText"},{"type":"doc","id":"functions/trimTokens","label":"trimTokens"},{"type":"doc","id":"functions/generateShouldRespond","label":"generateShouldRespond"},{"type":"doc","id":"functions/splitChunks","label":"splitChunks"},{"type":"doc","id":"functions/generateTrueOrFalse","label":"generateTrueOrFalse"},{"type":"doc","id":"functions/generateTextArray","label":"generateTextArray"},{"type":"doc","id":"functions/generateObjectDeprecated","label":"generateObjectDeprecated"},{"type":"doc","id":"functions/generateObjectArray","label":"generateObjectArray"},{"type":"doc","id":"functions/generateMessageResponse","label":"generateMessageResponse"},{"type":"doc","id":"functions/generateImage","label":"generateImage"},{"type":"doc","id":"functions/generateCaption","label":"generateCaption"},{"type":"doc","id":"functions/generateWebSearch","label":"generateWebSearch"},{"type":"doc","id":"functions/generateObject","label":"generateObject"},{"type":"doc","id":"functions/handleProvider","label":"handleProvider"},{"type":"doc","id":"functions/generateTweetActions","label":"generateTweetActions"},{"type":"doc","id":"functions/getGoals","label":"getGoals"},{"type":"doc","id":"functions/formatGoalsAsString","label":"formatGoalsAsString"},{"type":"doc","id":"functions/updateGoal","label":"updateGoal"},{"type":"doc","id":"functions/createGoal","label":"createGoal"},{"type":"doc","id":"functions/getActorDetails","label":"getActorDetails"},{"type":"doc","id":"functions/formatActors","label":"formatActors"},{"type":"doc","id":"functions/formatMessages","label":"formatMessages"},{"type":"doc","id":"functions/formatTimestamp","label":"formatTimestamp"},{"type":"doc","id":"functions/getModel","label":"getModel"},{"type":"doc","id":"functions/getEndpoint","label":"getEndpoint"},{"type":"doc","id":"functions/parseShouldRespondFromText","label":"parseShouldRespondFromText"},{"type":"doc","id":"functions/parseBooleanFromText","label":"parseBooleanFromText"},{"type":"doc","id":"functions/parseJsonArrayFromText","label":"parseJsonArrayFromText"},{"type":"doc","id":"functions/parseJSONObjectFromText","label":"parseJSONObjectFromText"},{"type":"doc","id":"functions/parseActionResponseFromText","label":"parseActionResponseFromText"},{"type":"doc","id":"functions/formatPosts","label":"formatPosts"},{"type":"doc","id":"functions/getProviders","label":"getProviders"},{"type":"doc","id":"functions/createRelationship","label":"createRelationship"},{"type":"doc","id":"functions/getRelationship","label":"getRelationship"},{"type":"doc","id":"functions/getRelationships","label":"getRelationships"},{"type":"doc","id":"functions/formatRelationships","label":"formatRelationships"},{"type":"doc","id":"functions/findNearestEnvFile","label":"findNearestEnvFile"},{"type":"doc","id":"functions/configureSettings","label":"configureSettings"},{"type":"doc","id":"functions/loadEnvConfig","label":"loadEnvConfig"},{"type":"doc","id":"functions/getEnvVariable","label":"getEnvVariable"},{"type":"doc","id":"functions/hasEnvVariable","label":"hasEnvVariable"},{"type":"doc","id":"functions/stringToUuid","label":"stringToUuid"}]}]};
module.exports = typedocSidebar.items;
\ No newline at end of file
diff --git a/docs/community/Discord/collaborations/3d-ai-tv/chat_2024-12-07.md b/docs/community/Discord/collaborations/3d-ai-tv/chat_2024-12-07.md
index 3c879de614c..3617fd51dbe 100644
--- a/docs/community/Discord/collaborations/3d-ai-tv/chat_2024-12-07.md
+++ b/docs/community/Discord/collaborations/3d-ai-tv/chat_2024-12-07.md
@@ -6,10 +6,10 @@ The conversation focused on integrating @bigdookie's artwork as bumpers in their
## FAQ
- Can we add @bigdookie's work as bumpers? What role does it require? (asked by @boom)
- Should the visual mindmap be created in Google Docs or Figma for better collaboration and tracking of ideas? (asked by @whobody)
-- Do writers need to have experience with 3D modeling? - No, they don't. Writers can participate without having a background in 3d modelling. (asked by @whobody)
-- What are the specific skills needed for JSON wrangling? », (asked by [whobody](09:46))
+- Do writers need to have experience with 3D modeling? No, they don't. Writers can participate without having a background in 3d modelling. (asked by @whobody)
+- What are the specific skills needed for JSON wrangling?, (asked by [whobody](09:46))
- How can we make our cameras more robust to prevent fading during shows? (asked by [boom](09:47))
-- Is managing the new data source going to be challenging? »}], // FAQs are limited due to lack of significant questions in chat transcript. (asked by [whobody](09:47))
+- Is managing the new data source going to be challenging?, // FAQs are limited due to lack of significant questions in chat transcript. (asked by [whobody](09:47))
- Do we need video producers? Why is it complicated for comfy stuff to be fast-paced? (asked by [boom](09:56))
- What are the next steps in establishing a Creative Studio and bidding on projects? How does budget influence project success? (asked by [whobody, boom](10:27))
- How will the open-source approach help us? How can Banodoco handle bids on their end? (asked by [boom (10:00)])
@@ -25,7 +25,7 @@ The conversation focused on integrating @bigdookie's artwork as bumpers in their
- [boom] helped [whoever is interested in video production and project bidding process] with Discussed the need for a Creative Studio with people able to bid on projects, emphasizing budget as an important factor by providing [whobody, boom](10:27)
- [boom] helped [whobody] with Problem-solving regarding project direction by providing Discussing the importance of directorial skills in creating a decentralized platform, with reference to iconic directors like Steven Spielberg and Stanley Kubrick.
- [boom] (10:05) helped [whobody] with Creating a better understanding and approach to incorporate AI into filmmaking. by providing Discussing potential of using AI assistance in creative processes like scriptwriting.
-- [whobody](10:07) helped [boom](10:08) with Discussing potential solutions for Hollywood industry issues by providing [boom](10:08) suggests using AI writing to mitigate effects of writers' strike
+- [whobody](10:07) helped boom with Discussing potential solutions for Hollywood industry issues by providing boom suggests using AI writing to mitigate effects of writers' strike
## Action Items
@@ -53,4 +53,4 @@ The conversation focused on integrating @bigdookie's artwork as bumpers in their
### Feature Requests
- Implement a 'git-gud' challenge to filter participants (mentioned by @boom)
- Develop robust cameras to prevent fading during shows. (mentioned by [boom](09:47))
-- Create a space where brands can showcase their marketing efforts through AI art battles and automated displays. (mentioned by [boom, whobody])
\ No newline at end of file
+- Create a space where brands can showcase their marketing efforts through AI art battles and automated displays. (mentioned by [boom, whobody])
diff --git a/docs/community/Discord/development/agent-dev-school/chat_2024-12-07.md b/docs/community/Discord/development/agent-dev-school/chat_2024-12-07.md
index 20516f7117b..37db9b8ac59 100644
--- a/docs/community/Discord/development/agent-dev-school/chat_2024-12-07.md
+++ b/docs/community/Discord/development/agent-dev-school/chat_2024-12-07.md
@@ -4,17 +4,17 @@
The technical discussion focused primarily around database schema design, with Yoni suggesting that creating concrete schemas for tables expected to grow significantly would be beneficial. This approach could help avoid potential scaling issues in the future.
## FAQ
-- Anyone hiring junior devs? I have experience in business development, marketing and sales as well. Any suggestions for where to look or how to proceed with job search? (asked by @chevronkey (20:08))
+- Anyone hiring junior devs? I have experience in business development, marketing and sales as well. Any suggestions for where to look or how to proceed with job search? asked by @chevronkey
- I didn't see much there for junior devs roles in business development, marketing and sales - any other suggestions? I will look again. (asked by @chevronkey (21:53))
- Where can one post their resume to find job opportunities? (asked by @Odilitime)
- (asked by [@chevronkey](21:53))
- Where can one find job opportunities or get help with finding a role? (asked by @Odilitime (20:20))
- How can one post their resume on the platform? (asked by @Odilitime (22:41))
-- (asked by [@chevronkey](20:08))
+- (asked by @chevronkey
## Who Helped Who
- @chevronkey(21:53) helped [@chevronkey](21:53) with Finding a role in business development, marketing and sales by providing @Odilitime (20:20) suggested #bountys-gigs-jobs for job opportunities
-- [@Odilitime] helped [@chevronkey](20:08) with Posting a Resume by providing @Odilitime (22:41) advised to post resume on the platform
+- [@Odilitime] helped @chevronkey with Posting a Resume by providing @Odilitime (22:41) advised to post resume on the platform
## Action Items
@@ -22,4 +22,4 @@ The technical discussion focused primarily around database schema design, with Y
- Create concrete schemas for tables with known growth potential (mentioned by [Yoni](02:36))
### Feature Requests
-- Post resume on #bountys-gigs-jobs for junior dev or biz development roles (mentioned by [Odilitime](22:41))
\ No newline at end of file
+- Post resume on #bountys-gigs-jobs for junior dev or biz development roles (mentioned by [Odilitime](22:41))
diff --git a/docs/community/Discord/development/coders/chat_2024-11-30.md b/docs/community/Discord/development/coders/chat_2024-11-30.md
index 4200659a8a7..86150b1bbbf 100644
--- a/docs/community/Discord/development/coders/chat_2024-11-30.md
+++ b/docs/community/Discord/development/coders/chat_2024-11-30.md
@@ -8,16 +8,16 @@ The chat focused on resolving issues related to environment setup, specifically
- Where should I add my custom action? (asked by Tharakesh)
- Can the .env file be edited later? (at timestamp 01:52) - Answered by Tharakesh at timestamps 01:47, 01:53-01:58. The bot needs to run with a configured environment and can have its contents filled in afterward. (asked by [POV])
- How to get into Ubuntu on WSL? How do I install PNPM globally in WSL? (asked by [POV])
-- Did you use the wsl command for accessing ubuntu terminals? (asked by [Tharakesh](01:05))
+- Did you use the wsl command for accessing ubuntu terminals? (asked by [Tharakesh]
- Why is npm install not working on WSL? How do I check if PNPM has been installed correctly in my system? (asked by [POV])
-- Did you try installing pnpm using cmd instead of wsl command? (asked by [Tharakesh](01:08))
+- Did you try installing pnpm using cmd instead of wsl command? (asked by [Tharakesh)
- How to guide Eliza to reply without @ mention on Twitter? Is it possible by choice of people or following etc.? (asked by @YoungPhlo (01:46))
- Is there more detailed info available for the steps mentioned in this chat segment? (asked by @Tharakesh (02:58))
- Where is the link? What does POV mean by 'agent'? (asked by @Tharakesh)
## Who Helped Who
- [Tharakesh (00:51)] helped (POV) with Fixing the agent's behavior with tweets and setting up .env file. by providing [monas, POV]
-- [Tharakesh] helped [POV] with .env configuration and bot activation by providing Tharakesh helped POV understand the .env file usage at timestamps (01:47, 01:53-01:58).
+- [Tharakesh] helped [POV] with .env configuration and bot activation by providing Tharakesh helped POV understand the .env file usage at timestamps
- [Tharakesh](01:05) helped [POV] with Troubleshooting WSL issues with installing pnpm. by providing POV was guided by Tharakesch on how to access Ubuntu terminals and install PNPM globally.
- [Nona](01:16) helped [POV] with Verifying the installation of pnpm. by providing Nona provided a solution to check if PNPM is installed correctly.
- @!MakturbLab helped @YoungphLo (01:46) with SQLite database management, randomizing properties for characters by providing MaktubLabs helped YoungPhlo with SQLite deletion and character file adjustments.
@@ -43,7 +43,7 @@ The chat focused on resolving issues related to environment setup, specifically
- Automate process of creating Twitter accounts (mentioned by solswappa)
### Documentation Needs
-- Check Dev School video for guidance. (mentioned by [Tharakesh](01:08))
+- Check Dev School video for guidance. (mentioned by [Tharakesh]
- Replace XAI_MODEL=grok-beta in the configuration, if using Grok model. (mentioned by @POV)
- Assist POV with agent code changes and upload issues. (mentioned by @POV)
- Adjust boot up process to prevent failures during code changes. (mentioned by [POV])
@@ -53,4 +53,4 @@ The chat focused on resolving issues related to environment setup, specifically
### Feature Requests
- Configure agent to automatically reply to tweets or set up required configuration for this feature. (mentioned by [monas, Tharakesh (00:51)])
- Configure bot to reply to tweets (mentioned by [Tharakesh](01:58))
-- Provide syntax for image generation model in character file, remove spaces after colons. (mentioned by [Isk heheh])
\ No newline at end of file
+- Provide syntax for image generation model in character file, remove spaces after colons. (mentioned by [Isk heheh])
diff --git a/docs/docs/core/characterfile.md b/docs/docs/core/characterfile.md
index 8662a548710..37adc652241 100644
--- a/docs/docs/core/characterfile.md
+++ b/docs/docs/core/characterfile.md
@@ -22,7 +22,7 @@ A `characterfile` implements the [Character](/api/type-aliases/character) type a
```json
{
"name": "trump",
- "clients": ["DISCORD", "DIRECT"],
+ "clients": ["discord", "direct"],
"settings": {
"voice": { "model": "en_US-male-medium" }
},
@@ -92,11 +92,11 @@ The character's display name for identification and in conversations.
#### `modelProvider` (required)
-Specifies the AI model provider. Supported options from [ModelProviderName](/api/enumerations/modelprovidername) include `ANTHROPIC`, `LLAMALOCAL`, `OPENAI`, and others.
+Specifies the AI model provider. Supported options from [ModelProviderName](/api/enumerations/modelprovidername) include `anthropic`, `llama_local`, `openai`, and others.
#### `clients` (required)
-Array of supported client types from [Clients](/api/enumerations/clients) e.g., `DISCORD`, `DIRECT`, `TWITTER`, `TELEGRAM`.
+Array of supported client types from [Clients](/api/enumerations/clients) e.g., `discord`, `direct`, `twitter`, `telegram`, `farcaster`.
#### `bio`
@@ -261,8 +261,8 @@ Your response should not contain any questions. Brief, concise statements only.
```json
{
"name": "TechAI",
- "modelProvider": "ANTHROPIC",
- "clients": ["DISCORD", "DIRECT"],
+ "modelProvider": "anthropic",
+ "clients": ["discord", "direct"],
"bio": "AI researcher and educator focused on practical applications",
"lore": [
"Pioneer in open-source AI development",
diff --git a/docs/docs/packages/agent.md b/docs/docs/packages/agent.md
index 87108048b7b..28d7f4c7cd9 100644
--- a/docs/docs/packages/agent.md
+++ b/docs/docs/packages/agent.md
@@ -138,16 +138,16 @@ export async function initializeClients(
const clients = [];
const clientTypes = character.clients?.map((str) => str.toLowerCase()) || [];
- if (clientTypes.includes("discord")) {
+ if (clientTypes.includes(Clients.DISCORD)) {
clients.push(await DiscordClientInterface.start(runtime));
}
- if (clientTypes.includes("telegram")) {
+ if (clientTypes.includes(Clients.TELEGRAM)) {
clients.push(await TelegramClientInterface.start(runtime));
}
- if (clientTypes.includes("twitter")) {
+ if (clientTypes.includes(Clients.TWITTER)) {
clients.push(await TwitterClientInterface.start(runtime));
}
- if (clientTypes.includes("auto")) {
+ if (clientTypes.includes(Clients.DIRECT)) {
clients.push(await AutoClientInterface.start(runtime));
}
diff --git a/docs/docs/packages/agents.md b/docs/docs/packages/agents.md
index 7a57d65cfb7..4984edb1434 100644
--- a/docs/docs/packages/agents.md
+++ b/docs/docs/packages/agents.md
@@ -101,20 +101,16 @@ export async function initializeClients(
const clients = [];
const clientTypes = character.clients?.map((str) => str.toLowerCase()) || [];
- // Initialize requested clients
- if (clientTypes.includes("discord")) {
+ if (clientTypes.includes(Clients.DISCORD)) {
clients.push(await DiscordClientInterface.start(runtime));
}
-
- if (clientTypes.includes("telegram")) {
+ if (clientTypes.includes(Clients.TELEGRAM)) {
clients.push(await TelegramClientInterface.start(runtime));
}
-
- if (clientTypes.includes("twitter")) {
+ if (clientTypes.includes(Clients.TWITTER)) {
clients.push(await TwitterClientInterface.start(runtime));
}
-
- if (clientTypes.includes("auto")) {
+ if (clientTypes.includes(Clients.DIRECT)) {
clients.push(await AutoClientInterface.start(runtime));
}
diff --git a/docs/docs/packages/clients.md b/docs/docs/packages/clients.md
index 3d302aa6311..d586112a9b0 100644
--- a/docs/docs/packages/clients.md
+++ b/docs/docs/packages/clients.md
@@ -134,7 +134,6 @@ The Twitter client enables posting, searching, and interacting with Twitter user
```typescript
import { TwitterClientInterface } from "@eliza/client-twitter";
-
// Initialize client
const client = await TwitterClientInterface.start(runtime);
diff --git a/docs/docs/packages/plugins.md b/docs/docs/packages/plugins.md
index be9107ea3f0..ac582f6d230 100644
--- a/docs/docs/packages/plugins.md
+++ b/docs/docs/packages/plugins.md
@@ -108,7 +108,7 @@ Integrates Solana blockchain functionality:
- `walletProvider` - Wallet management
- `trustScoreProvider` - Transaction trust metrics
-### Charity Contributions
+##### Charity Contributions
All Coinbase trades and transfers automatically donate 1% of the transaction amount to charity. Currently, the charity addresses are hardcoded based on the network used for the transaction, with the current charity being supported as X.
@@ -137,7 +137,7 @@ This plugin enables Eliza to interact with the Coinbase Commerce API to create a
---
-### Coinbase Wallet Management
+##### Coinbase Wallet Management
The plugin automatically handles wallet creation or uses an existing wallet if the required details are provided during the first run.
@@ -302,7 +302,7 @@ When successful, a response similar to the following will be returned:
---
-#### 8. Coinbase Token Contract Plugin (`@eliza/plugin-coinbase`)
+#### 7. Coinbase Token Contract Plugin (`@eliza/plugin-coinbase`)
This plugin enables the deployment and interaction with various token contracts (ERC20, ERC721, ERC1155) using the Coinbase SDK. It provides functionality for both deploying new token contracts and interacting with existing ones.
@@ -445,7 +445,7 @@ const response = await runtime.triggerAction("INVOKE_CONTRACT", {
---
-#### 7. TEE Plugin (`@ai16z/plugin-tee`)
+#### 8. TEE Plugin (`@ai16z/plugin-tee`)
Integrates [Dstack SDK](https://github.com/Dstack-TEE/dstack) to enable TEE (Trusted Execution Environment) functionality and deploy secure & privacy-enhanced Eliza Agents:
diff --git a/lerna.json b/lerna.json
index 8ce6c7af193..e8390a9625c 100644
--- a/lerna.json
+++ b/lerna.json
@@ -1,5 +1,5 @@
{
"version": "0.1.5-alpha.5",
- "packages": ["packages/*", "docs", "agent", "client"],
+ "packages": ["packages/*", "docs", "agent", "client", "!packages/_examples"],
"npmClient": "pnpm"
}
diff --git a/package.json b/package.json
index ac6da05dad9..cb2da2b438a 100644
--- a/package.json
+++ b/package.json
@@ -9,8 +9,8 @@
"start:debug": "cross-env NODE_ENV=development VERBOSE=true DEBUG=eliza:* pnpm --filter \"@ai16z/agent\" start --isRoot",
"dev": "bash ./scripts/dev.sh",
"lint": "bash ./scripts/lint.sh",
- "prettier-check": "npx prettier --check .",
- "prettier": "npx prettier --write .",
+ "prettier-check": "npx prettier --check --cache .",
+ "prettier": "npx prettier --write --cache .",
"release": "pnpm build && pnpm prettier && npx lerna publish --no-private --force-publish",
"clean": "bash ./scripts/clean.sh",
"docker:build": "bash ./scripts/docker.sh build",
@@ -54,9 +54,10 @@
"dependencies": {
"@0glabs/0g-ts-sdk": "0.2.1",
"@coinbase/coinbase-sdk": "0.10.0",
+ "@deepgram/sdk": "^3.9.0",
+ "@vitest/eslint-plugin": "1.0.1",
"amqplib": "0.10.5",
"csv-parse": "5.6.0",
- "@vitest/eslint-plugin": "1.0.1",
"ollama-ai-provider": "0.16.1",
"optional": "0.1.4",
"pnpm": "9.14.4",
diff --git a/packages/_examples/plugin/.npmignore b/packages/_examples/plugin/.npmignore
new file mode 100644
index 00000000000..078562eceab
--- /dev/null
+++ b/packages/_examples/plugin/.npmignore
@@ -0,0 +1,6 @@
+*
+
+!dist/**
+!package.json
+!readme.md
+!tsup.config.ts
\ No newline at end of file
diff --git a/packages/_examples/plugin/README.md b/packages/_examples/plugin/README.md
new file mode 100644
index 00000000000..12b04e1dda1
--- /dev/null
+++ b/packages/_examples/plugin/README.md
@@ -0,0 +1,32 @@
+# Sample Plugin for Eliza
+
+The Sample Plugin for Eliza extends the functionality of the Eliza platform by providing additional actions, providers, evaluators, and more. This plugin is designed to be easily extendable and customizable to fit various use cases.
+
+## Description
+The Sample Plugin offers a set of features that can be integrated into the Eliza platform to enhance its capabilities. Below is a high-level overview of the different components available in this plugin.
+
+## Actions
+- **createResourceAction**: This action enables the creation and management of generic resources. It can be customized to handle different types of resources and integrate with various data sources.
+
+## Providers
+- **sampleProvider**: This provider offers a mechanism to supply data or services to the plugin. It can be extended to include additional providers as needed.
+
+## Evaluators
+- **sampleEvaluator**: This evaluator provides a way to assess or analyze data within the plugin. It can be extended to include additional evaluators as needed.
+
+## Services
+- **[ServiceName]**: Description of the service and its functionality. This can be extended to include additional services as needed.
+
+## Clients
+- **[ClientName]**: Description of the client and its functionality. This can be extended to include additional clients as needed.
+
+## How to Extend
+To extend the Sample Plugin, you can add new actions, providers, evaluators, services, and clients by following the structure provided in the plugin. Each component can be customized to fit your specific requirements.
+
+1. **Actions**: Add new actions by defining them in the `actions` array.
+2. **Providers**: Add new providers by defining them in the `providers` array.
+3. **Evaluators**: Add new evaluators by defining them in the `evaluators` array.
+4. **Services**: Add new services by defining them in the `services` array.
+5. **Clients**: Add new clients by defining them in the `clients` array.
+
+For more detailed information on how to extend the plugin, refer to the documentation provided in the Eliza platform.
diff --git a/packages/_examples/plugin/eslint.config.mjs b/packages/_examples/plugin/eslint.config.mjs
new file mode 100644
index 00000000000..92fe5bbebef
--- /dev/null
+++ b/packages/_examples/plugin/eslint.config.mjs
@@ -0,0 +1,3 @@
+import eslintGlobalConfig from "../../eslint.config.mjs";
+
+export default [...eslintGlobalConfig];
diff --git a/packages/_examples/plugin/package.json b/packages/_examples/plugin/package.json
new file mode 100644
index 00000000000..5d2a4b186e4
--- /dev/null
+++ b/packages/_examples/plugin/package.json
@@ -0,0 +1,19 @@
+{
+ "name": "@ai16z/plugin-sample",
+ "version": "0.1.5-alpha.5",
+ "main": "dist/index.js",
+ "type": "module",
+ "types": "dist/index.d.ts",
+ "dependencies": {
+ "@ai16z/eliza": "workspace:*"
+ },
+ "devDependencies": {
+ "tsup": "8.3.5",
+ "@types/node": "^20.0.0"
+ },
+ "scripts": {
+ "build": "tsup --format esm --dts",
+ "dev": "tsup --format esm --dts --watch",
+ "lint": "eslint --fix --cache ."
+ }
+}
diff --git a/packages/_examples/plugin/src/actions/sampleAction.ts b/packages/_examples/plugin/src/actions/sampleAction.ts
new file mode 100644
index 00000000000..1798ffc1fec
--- /dev/null
+++ b/packages/_examples/plugin/src/actions/sampleAction.ts
@@ -0,0 +1,111 @@
+import {
+ Action,
+ IAgentRuntime,
+ Memory,
+ HandlerCallback,
+ State,
+ composeContext,
+ generateObject,
+ ModelClass,
+ elizaLogger,
+} from "@ai16z/eliza";
+
+import { CreateResourceSchema, isCreateResourceContent } from "../types";
+
+import { createResourceTemplate } from "../templates";
+
+export const createResourceAction: Action = {
+ name: "CREATE_RESOURCE",
+ description: "Create a new resource with the specified details",
+ validate: async (runtime: IAgentRuntime, _message: Memory) => {
+ return !!runtime.character.settings.secrets?.API_KEY;
+ },
+ handler: async (
+ runtime: IAgentRuntime,
+ _message: Memory,
+ state: State,
+ _options: any,
+ callback: HandlerCallback
+ ) => {
+ try {
+ const context = composeContext({
+ state,
+ template: createResourceTemplate,
+ });
+
+ const resourceDetails = await generateObject({
+ runtime,
+ context,
+ modelClass: ModelClass.SMALL,
+ schema: CreateResourceSchema,
+ });
+
+ if (!isCreateResourceContent(resourceDetails.object)) {
+ callback({ text: "Invalid resource details provided." }, []);
+ return;
+ }
+
+ // persist relevant data if needed to memory/knowledge
+ // const memory = {
+ // type: "resource",
+ // content: resourceDetails.object,
+ // timestamp: new Date().toISOString()
+ // };
+
+ // await runtime.storeMemory(memory);
+
+ callback(
+ {
+ text: `Resource created successfully:
+- Name: ${resourceDetails.object.name}
+- Type: ${resourceDetails.object.type}
+- Description: ${resourceDetails.object.description}
+- Tags: ${resourceDetails.object.tags.join(", ")}
+
+Resource has been stored in memory.`,
+ },
+ []
+ );
+ } catch (error) {
+ elizaLogger.error("Error creating resource:", error);
+ callback(
+ { text: "Failed to create resource. Please check the logs." },
+ []
+ );
+ }
+ },
+ examples: [
+ [
+ {
+ user: "{{user1}}",
+ content: {
+ text: "Create a new resource with the name 'Resource1' and type 'TypeA'",
+ },
+ },
+ {
+ user: "{{agentName}}",
+ content: {
+ text: `Resource created successfully:
+- Name: Resource1
+- Type: TypeA`,
+ },
+ },
+ ],
+ [
+ {
+ user: "{{user1}}",
+ content: {
+ text: "Create a new resource with the name 'Resource2' and type 'TypeB'",
+ },
+ },
+ {
+ user: "{{agentName}}",
+ content: {
+ text: `Resource created successfully:
+- Name: Resource2
+- Type: TypeB`,
+ },
+ },
+ ],
+ ],
+};
diff --git a/packages/_examples/plugin/src/evaluators/sampleEvalutor.ts b/packages/_examples/plugin/src/evaluators/sampleEvalutor.ts
new file mode 100644
index 00000000000..06ad6d454c0
--- /dev/null
+++ b/packages/_examples/plugin/src/evaluators/sampleEvalutor.ts
@@ -0,0 +1,47 @@
+import { Evaluator, IAgentRuntime, Memory, State, elizaLogger } from "@ai16z/eliza";
+
+export const sampleEvaluator: Evaluator = {
+ alwaysRun: false,
+ description: "Sample evaluator for checking important content in memory",
+ similes: ["content checker", "memory evaluator"],
+ examples: [
+ {
+ context: "Checking if memory contains important content",
+ messages: [
+ {
+ action: "evaluate",
+ input: "This is an important message",
+ output: {
+ score: 1,
+ reason: "Memory contains important content."
+ }
+ }
+ ],
+ outcome: "Memory should be evaluated as important"
+ }
+ ],
+ handler: async (runtime: IAgentRuntime, memory: Memory, state: State) => {
+ // Evaluation logic for the evaluator
+ elizaLogger.log("Evaluating data in sampleEvaluator...");
+
+ // Example evaluation logic
+ if (memory.content && memory.content.includes("important")) {
+ elizaLogger.log("Important content found in memory.");
+ return {
+ score: 1,
+ reason: "Memory contains important content."
+ };
+ } else {
+ elizaLogger.log("No important content found in memory.");
+ return {
+ score: 0,
+ reason: "Memory does not contain important content."
+ };
+ }
+ },
+ name: "sampleEvaluator",
+ validate: async (runtime: IAgentRuntime, memory: Memory, state: State) => {
+ // Validation logic for the evaluator
+ return true;
+ }
+};
diff --git a/packages/_examples/plugin/src/index.ts b/packages/_examples/plugin/src/index.ts
new file mode 100644
index 00000000000..e05078abd8c
--- /dev/null
+++ b/packages/_examples/plugin/src/index.ts
@@ -0,0 +1,8 @@
+import { samplePlugin } from './plugins/samplePlugin';
+
+
+
+export * from './plugins/samplePlugin';
+
+
+export default samplePlugin;
\ No newline at end of file
diff --git a/packages/_examples/plugin/src/plugins/samplePlugin.ts b/packages/_examples/plugin/src/plugins/samplePlugin.ts
new file mode 100644
index 00000000000..dc72976409f
--- /dev/null
+++ b/packages/_examples/plugin/src/plugins/samplePlugin.ts
@@ -0,0 +1,17 @@
+import {
+ Plugin,
+} from "@ai16z/eliza";
+import { createResourceAction } from "../actions/sampleAction";
+import { sampleProvider } from "../providers/sampleProvider";
+import { sampleEvaluator } from "../evaluators/sampleEvalutor";
+
+export const samplePlugin: Plugin = {
+ name: "sample",
+ description: "Enables creation and management of generic resources",
+ actions: [createResourceAction],
+ providers: [sampleProvider],
+ evaluators: [sampleEvaluator],
+ // separate examples will be added for services and clients
+ services: [],
+ clients: [],
+};
diff --git a/packages/_examples/plugin/src/providers/sampleProvider.ts b/packages/_examples/plugin/src/providers/sampleProvider.ts
new file mode 100644
index 00000000000..5e4b3c2b5b5
--- /dev/null
+++ b/packages/_examples/plugin/src/providers/sampleProvider.ts
@@ -0,0 +1,14 @@
+import {
+ Provider,
+ IAgentRuntime,
+ Memory,
+ State,
+ elizaLogger
+} from "@ai16z/eliza";
+
+export const sampleProvider: Provider = {
+ get: async (runtime: IAgentRuntime, message: Memory, state: State) => {
+ // Data retrieval logic for the provider
+ elizaLogger.log("Retrieving data in sampleProvider...");
+ },
+};
diff --git a/packages/_examples/plugin/src/templates.ts b/packages/_examples/plugin/src/templates.ts
new file mode 100644
index 00000000000..f9c0d965917
--- /dev/null
+++ b/packages/_examples/plugin/src/templates.ts
@@ -0,0 +1,60 @@
+export const createResourceTemplate = `
+Extract the following details to create a new resource:
+- **name** (string): Name of the resource
+- **type** (string): Type of resource (document, image, video)
+- **description** (string): Description of the resource
+- **tags** (array): Array of tags to categorize the resource
+
+Provide the values in the following JSON format:
+
+\`\`\`json
+{
+ "name": "",
+ "type": "",
+ "description": "",
+ "tags": ["", ""]
+}
+\`\`\`
+
+Here are the recent user messages for context:
+{{recentMessages}}
+`;
+
+export const readResourceTemplate = `
+Extract the following details to read a resource:
+- **id** (string): Unique identifier of the resource
+- **fields** (array): Specific fields to retrieve (optional)
+
+Provide the values in the following JSON format:
+
+\`\`\`json
+{
+ "id": "",
+ "fields": ["", ""]
+}
+\`\`\`
+
+Here are the recent user messages for context:
+{{recentMessages}}
+`;
+
+export const updateResourceTemplate = `
+Extract the following details to update a resource:
+- **id** (string): Unique identifier of the resource
+- **updates** (object): Key-value pairs of fields to update
+
+Provide the values in the following JSON format:
+
+\`\`\`json
+{
+ "id": "",
+ "updates": {
+ "": "",
+ "": ""
+ }
+}
+\`\`\`
+
+Here are the recent user messages for context:
+{{recentMessages}}
+`;
diff --git a/packages/_examples/plugin/src/types.ts b/packages/_examples/plugin/src/types.ts
new file mode 100644
index 00000000000..e0d03cf1739
--- /dev/null
+++ b/packages/_examples/plugin/src/types.ts
@@ -0,0 +1,51 @@
+import { z } from "zod";
+
+// Base resource schema
+export const ResourceSchema = z.object({
+ id: z.string().optional(),
+ name: z.string().min(1),
+ type: z.enum(["document", "image", "video"]),
+ description: z.string(),
+ tags: z.array(z.string())
+});
+
+// Create resource schema
+export const CreateResourceSchema = ResourceSchema.omit({ id: true });
+
+// Read resource schema
+export const ReadResourceSchema = z.object({
+ id: z.string(),
+ fields: z.array(z.string()).optional()
+});
+
+// Update resource schema
+export const UpdateResourceSchema = z.object({
+ id: z.string(),
+ updates: z.record(z.string(), z.any())
+});
+
+// Type definitions
+export type Resource = z.infer;
+export type CreateResourceContent = z.infer;
+export type ReadResourceContent = z.infer;
+export type UpdateResourceContent = z.infer;
+
+// Type guards
+export const isCreateResourceContent = (obj: any): obj is CreateResourceContent => {
+ return CreateResourceSchema.safeParse(obj).success;
+};
+
+export const isReadResourceContent = (obj: any): obj is ReadResourceContent => {
+ return ReadResourceSchema.safeParse(obj).success;
+};
+
+export const isUpdateResourceContent = (obj: any): obj is UpdateResourceContent => {
+ return UpdateResourceSchema.safeParse(obj).success;
+};
+
+// Plugin configuration type
+export interface ExamplePluginConfig {
+ apiKey: string;
+ apiSecret: string;
+ endpoint?: string;
+}
\ No newline at end of file
diff --git a/packages/_examples/plugin/tsconfig.json b/packages/_examples/plugin/tsconfig.json
new file mode 100644
index 00000000000..99dbaa3d814
--- /dev/null
+++ b/packages/_examples/plugin/tsconfig.json
@@ -0,0 +1,13 @@
+{
+ "extends": "../../core/tsconfig.json",
+ "compilerOptions": {
+ "outDir": "dist",
+ "rootDir": "src",
+ "types": [
+ "node"
+ ]
+ },
+ "include": [
+ "src/**/*.ts",
+ ]
+}
\ No newline at end of file
diff --git a/packages/_examples/plugin/tsup.config.ts b/packages/_examples/plugin/tsup.config.ts
new file mode 100644
index 00000000000..1a96f24afa1
--- /dev/null
+++ b/packages/_examples/plugin/tsup.config.ts
@@ -0,0 +1,21 @@
+import { defineConfig } from "tsup";
+
+export default defineConfig({
+ entry: ["src/index.ts"],
+ outDir: "dist",
+ sourcemap: true,
+ clean: true,
+ format: ["esm"], // Ensure you're targeting CommonJS
+ external: [
+ "dotenv", // Externalize dotenv to prevent bundling
+ "fs", // Externalize fs to use Node.js built-in module
+ "path", // Externalize other built-ins if necessary
+ "@reflink/reflink",
+ "@node-llama-cpp",
+ "https",
+ "http",
+ "agentkeepalive",
+ "safe-buffer",
+ // Add other modules you want to externalize
+ ],
+});
diff --git a/packages/adapter-postgres/package.json b/packages/adapter-postgres/package.json
index 95df9a081a2..d31786af3e6 100644
--- a/packages/adapter-postgres/package.json
+++ b/packages/adapter-postgres/package.json
@@ -15,6 +15,6 @@
"scripts": {
"build": "tsup --format esm --dts",
"dev": "tsup --format esm --dts --watch",
- "lint": "eslint . --fix"
+ "lint": "eslint --fix --cache ."
}
}
diff --git a/packages/adapter-sqlite/package.json b/packages/adapter-sqlite/package.json
index e7472d01825..0a073ffa750 100644
--- a/packages/adapter-sqlite/package.json
+++ b/packages/adapter-sqlite/package.json
@@ -16,7 +16,7 @@
"scripts": {
"build": "tsup --format esm --dts",
"dev": "tsup --format esm --dts --watch",
- "lint": "eslint . --fix"
+ "lint": "eslint --fix --cache ."
},
"peerDependencies": {
"whatwg-url": "7.1.0"
diff --git a/packages/adapter-sqljs/package.json b/packages/adapter-sqljs/package.json
index 3cf49eab2b6..12bfd7955d7 100644
--- a/packages/adapter-sqljs/package.json
+++ b/packages/adapter-sqljs/package.json
@@ -16,7 +16,7 @@
"scripts": {
"build": "tsup --format esm --dts",
"dev": "tsup --format esm --dts --watch",
- "lint": "eslint . --fix"
+ "lint": "eslint --fix --cache ."
},
"peerDependencies": {
"whatwg-url": "7.1.0"
diff --git a/packages/adapter-supabase/package.json b/packages/adapter-supabase/package.json
index 46f2e555417..42ac235158e 100644
--- a/packages/adapter-supabase/package.json
+++ b/packages/adapter-supabase/package.json
@@ -14,7 +14,7 @@
"scripts": {
"build": "tsup --format esm --dts",
"dev": "tsup --format esm --dts --watch",
- "lint": "eslint . --fix"
+ "lint": "eslint --fix --cache ."
},
"peerDependencies": {
"whatwg-url": "7.1.0"
diff --git a/packages/client-auto/package.json b/packages/client-auto/package.json
index f2711101bc2..bba44fe65b1 100644
--- a/packages/client-auto/package.json
+++ b/packages/client-auto/package.json
@@ -19,7 +19,7 @@
"scripts": {
"build": "tsup --format esm --dts",
"dev": "tsup --format esm --dts --watch",
- "lint": "eslint . --fix"
+ "lint": "eslint --fix --cache ."
},
"peerDependencies": {
"whatwg-url": "7.1.0"
diff --git a/packages/client-auto/src/index.ts b/packages/client-auto/src/index.ts
index 8c754d6bccf..5da06ffbf6c 100644
--- a/packages/client-auto/src/index.ts
+++ b/packages/client-auto/src/index.ts
@@ -1,4 +1,4 @@
-import { Client, IAgentRuntime } from "@ai16z/eliza";
+import { Client, IAgentRuntime, elizaLogger } from "@ai16z/eliza";
export class AutoClient {
interval: NodeJS.Timeout;
@@ -10,7 +10,7 @@ export class AutoClient {
// start a loop that runs every x seconds
this.interval = setInterval(
async () => {
- console.log("running auto client...");
+ elizaLogger.log("running auto client...");
},
60 * 60 * 1000
); // 1 hour in milliseconds
diff --git a/packages/client-direct/package.json b/packages/client-direct/package.json
index b46063ea367..7ff770a71bf 100644
--- a/packages/client-direct/package.json
+++ b/packages/client-direct/package.json
@@ -22,7 +22,7 @@
"scripts": {
"build": "tsup --format esm --dts",
"dev": "tsup --format esm --dts --watch",
- "lint": "eslint . --fix"
+ "lint": "eslint --fix --cache ."
},
"peerDependencies": {
"whatwg-url": "7.1.0"
diff --git a/packages/client-direct/src/api.ts b/packages/client-direct/src/api.ts
index 3b3394dd210..fe26cee8f3c 100644
--- a/packages/client-direct/src/api.ts
+++ b/packages/client-direct/src/api.ts
@@ -2,17 +2,25 @@ import express from "express";
import bodyParser from "body-parser";
import cors from "cors";
-import { AgentRuntime } from "@ai16z/eliza";
+import {
+ AgentRuntime,
+ elizaLogger,
+ validateCharacterConfig,
+} from "@ai16z/eliza";
import { REST, Routes } from "discord.js";
-export function createApiRouter(agents: Map) {
+export function createApiRouter(agents: Map, directClient) {
const router = express.Router();
router.use(cors());
router.use(bodyParser.json());
router.use(bodyParser.urlencoded({ extended: true }));
+ router.get("/", (req, res) => {
+ res.send("Welcome, this is the REST API!");
+ });
+
router.get("/hello", (req, res) => {
res.json({ message: "Hello World!" });
});
@@ -21,6 +29,7 @@ export function createApiRouter(agents: Map) {
const agentsList = Array.from(agents.values()).map((agent) => ({
id: agent.agentId,
name: agent.character.name,
+ clients: Object.keys(agent.clients),
}));
res.json({ agents: agentsList });
});
@@ -40,6 +49,43 @@ export function createApiRouter(agents: Map) {
});
});
+ router.post("/agents/:agentId/set", async (req, res) => {
+ const agentId = req.params.agentId;
+ console.log('agentId', agentId)
+ let agent:AgentRuntime = agents.get(agentId);
+
+ // update character
+ if (agent) {
+ // stop agent
+ agent.stop()
+ directClient.unregisterAgent(agent)
+ // if it has a different name, the agentId will change
+ }
+
+ // load character from body
+ const character = req.body
+ try {
+ validateCharacterConfig(character)
+ } catch(e) {
+ elizaLogger.error(`Error parsing character: ${e}`);
+ res.status(400).json({
+ success: false,
+ message: e.message,
+ });
+ return;
+ }
+
+ // start it up (and register it)
+ agent = await directClient.startAgent(character)
+ elizaLogger.log(`${character.name} started`)
+
+ res.json({
+ id: character.id,
+ character: character,
+ });
+ });
+
+
router.get("/agents/:agentId/channels", async (req, res) => {
const agentId = req.params.agentId;
const runtime = agents.get(agentId);
diff --git a/packages/client-direct/src/index.ts b/packages/client-direct/src/index.ts
index 08f75f595ba..1ec4275b89b 100644
--- a/packages/client-direct/src/index.ts
+++ b/packages/client-direct/src/index.ts
@@ -51,17 +51,11 @@ Note that {{agentName}} is capable of reading/seeing/hearing various forms of me
# Instructions: Write the next message for {{agentName}}.
` + messageCompletionFooter;
-export interface SimliClientConfig {
- apiKey: string;
- faceID: string;
- handleSilence: boolean;
- videoRef: any;
- audioRef: any;
-}
export class DirectClient {
public app: express.Application;
- private agents: Map;
+ private agents: Map; // container management
private server: any; // Store server instance
+ public startAgent: Function; // Store startAgent functor
constructor() {
elizaLogger.log("DirectClient constructor");
@@ -72,7 +66,7 @@ export class DirectClient {
this.app.use(bodyParser.json());
this.app.use(bodyParser.urlencoded({ extended: true }));
- const apiRouter = createApiRouter(this.agents);
+ const apiRouter = createApiRouter(this.agents, this);
this.app.use(apiRouter);
// Define an interface that extends the Express Request interface
@@ -338,7 +332,7 @@ export class DirectClient {
fileResponse.headers
.get("content-disposition")
?.split("filename=")[1]
- ?.replace(/"/g, "") || "default_name.txt";
+ ?.replace(/"/g, /* " */ "") || "default_name.txt";
console.log("Saving as:", fileName);
@@ -378,6 +372,7 @@ export class DirectClient {
);
}
+ // agent/src/index.ts:startAgent calls this
public registerAgent(runtime: AgentRuntime) {
this.agents.set(runtime.agentId, runtime);
}
@@ -388,7 +383,7 @@ export class DirectClient {
public start(port: number) {
this.server = this.app.listen(port, () => {
- elizaLogger.success(`Server running at http://localhost:${port}/`);
+ elizaLogger.success(`REST API bound to 0.0.0.0:${port}. If running locally, access it at http://localhost:${port}.`);
});
// Handle graceful shutdown
@@ -430,7 +425,7 @@ export const DirectClientInterface: Client = {
client.start(serverPort);
return client;
},
- stop: async (_runtime: IAgentRuntime, client?: any) => {
+ stop: async (_runtime: IAgentRuntime, client?: Client) => {
if (client instanceof DirectClient) {
client.stop();
}
diff --git a/packages/client-discord/package.json b/packages/client-discord/package.json
index 87237a944ac..eca9bc8ce03 100644
--- a/packages/client-discord/package.json
+++ b/packages/client-discord/package.json
@@ -21,7 +21,7 @@
"scripts": {
"build": "tsup --format esm --dts",
"dev": "tsup --format esm --dts --watch",
- "lint": "eslint . --fix"
+ "lint": "eslint --fix --cache ."
},
"trustedDependencies": {
"@discordjs/opus": "github:discordjs/opus",
diff --git a/packages/client-discord/src/constants.ts b/packages/client-discord/src/constants.ts
new file mode 100644
index 00000000000..2bc23464100
--- /dev/null
+++ b/packages/client-discord/src/constants.ts
@@ -0,0 +1,85 @@
+export const TEAM_COORDINATION = {
+ KEYWORDS: [
+ 'team',
+ 'everyone',
+ 'all agents',
+ 'team update',
+ 'gm team',
+ 'hello team',
+ 'hey team',
+ 'hi team',
+ 'morning team',
+ 'evening team',
+ 'night team',
+ 'update team',
+ ]
+} as const;
+
+export const MESSAGE_CONSTANTS = {
+ MAX_MESSAGES: 10,
+ RECENT_MESSAGE_COUNT: 3,
+ CHAT_HISTORY_COUNT: 5,
+ INTEREST_DECAY_TIME: 5 * 60 * 1000, // 5 minutes
+ PARTIAL_INTEREST_DECAY: 3 * 60 * 1000, // 3 minutes
+ DEFAULT_SIMILARITY_THRESHOLD: 0.3,
+ DEFAULT_SIMILARITY_THRESHOLD_FOLLOW_UPS: 0.20,
+} as const;
+
+export const MESSAGE_LENGTH_THRESHOLDS = {
+ LOSE_INTEREST: 100,
+ SHORT_MESSAGE: 10,
+ VERY_SHORT_MESSAGE: 2,
+ IGNORE_RESPONSE: 4,
+} as const;
+
+export const TIMING_CONSTANTS = {
+ LEADER_RESPONSE_TIMEOUT: 3000,
+ TEAM_MEMBER_DELAY: 1500,
+ LEADER_DELAY_MIN: 3000,
+ LEADER_DELAY_MAX: 4000,
+ TEAM_MEMBER_DELAY_MIN: 1000,
+ TEAM_MEMBER_DELAY_MAX: 3000,
+} as const;
+
+export const RESPONSE_CHANCES = {
+ AFTER_LEADER: 0.5, // 50% chance
+ FREQUENT_CHATTER: 0.5, // Base chance for frequent responders
+} as const;
+
+export const LOSE_INTEREST_WORDS = [
+ "shut up",
+ "stop",
+ "please shut up",
+ "shut up please",
+ "dont talk",
+ "silence",
+ "stop talking",
+ "be quiet",
+ "hush",
+ "wtf",
+ "chill",
+ "stfu",
+ "stupid bot",
+ "dumb bot",
+ "stop responding",
+ "god damn it",
+ "god damn",
+ "goddamnit",
+ "can you not",
+ "can you stop",
+ "be quiet",
+ "hate you",
+ "hate this",
+ "fuck up",
+] as const;
+
+export const IGNORE_RESPONSE_WORDS = [
+ "lol",
+ "nm",
+ "uh",
+ "wtf",
+ "stfu",
+ "dumb",
+ "jfc",
+ "omg",
+] as const;
\ No newline at end of file
diff --git a/packages/client-discord/src/index.ts b/packages/client-discord/src/index.ts
index 849465f9f8d..5e3621762e3 100644
--- a/packages/client-discord/src/index.ts
+++ b/packages/client-discord/src/index.ts
@@ -1,7 +1,11 @@
-import { getEmbeddingZeroVector } from "@ai16z/eliza";
-import { Character, Client as ElizaClient, IAgentRuntime } from "@ai16z/eliza";
-import { stringToUuid } from "@ai16z/eliza";
-import { elizaLogger } from "@ai16z/eliza";
+import {
+ getEmbeddingZeroVector,
+ stringToUuid,
+ elizaLogger,
+ Character,
+ Client as ElizaClient,
+ IAgentRuntime,
+} from "@ai16z/eliza";
import {
Client,
Events,
@@ -111,6 +115,16 @@ export class DiscordClient extends EventEmitter {
);
}
+ async stop() {
+ try {
+ // disconnect websocket
+ // this unbinds all the listeners
+ await this.client.destroy();
+ } catch(e) {
+ elizaLogger.error('client-discord instance stop err', e);
+ }
+ }
+
private async onClientReady(readyClient: { user: { tag: any; id: any } }) {
elizaLogger.success(`Logged in as ${readyClient.user?.tag}`);
@@ -388,7 +402,13 @@ export function startDiscord(runtime: IAgentRuntime) {
export const DiscordClientInterface: ElizaClient = {
start: async (runtime: IAgentRuntime) => new DiscordClient(runtime),
- stop: async (_runtime: IAgentRuntime) => {
- console.warn("Discord client does not support stopping yet");
+ stop: async (runtime: IAgentRuntime) => {
+ try {
+ // stop it
+ elizaLogger.log('Stopping discord client', runtime.agentId)
+ await runtime.clients.discord.stop()
+ } catch(e) {
+ elizaLogger.error('client-discord interface stop error', e);
+ }
},
};
diff --git a/packages/client-discord/src/messages.ts b/packages/client-discord/src/messages.ts
index 9c5bf3f21c9..5487e6eb57d 100644
--- a/packages/client-discord/src/messages.ts
+++ b/packages/client-discord/src/messages.ts
@@ -28,12 +28,33 @@ import {
discordShouldRespondTemplate,
discordMessageHandlerTemplate,
} from "./templates.ts";
-import { sendMessageInChunks, canSendMessage } from "./utils.ts";
+import {
+ IGNORE_RESPONSE_WORDS,
+ LOSE_INTEREST_WORDS,
+ MESSAGE_CONSTANTS,
+ MESSAGE_LENGTH_THRESHOLDS,
+ RESPONSE_CHANCES,
+ TEAM_COORDINATION,
+ TIMING_CONSTANTS,
+} from "./constants";
+import {
+ sendMessageInChunks,
+ canSendMessage,
+ cosineSimilarity,
+} from "./utils.ts";
+
+interface MessageContext {
+ content: string;
+ timestamp: number;
+}
export type InterestChannels = {
[key: string]: {
+ currentHandler: string | undefined;
lastMessageSent: number;
messages: { userId: UUID; userName: string; content: Content }[];
+ previousContext?: MessageContext;
+ contextSimilarityThreshold?: number;
};
};
@@ -58,8 +79,9 @@ export class MessageManager {
message.interaction ||
message.author.id ===
this.client.user?.id /* || message.author?.bot*/
- )
+ ) {
return;
+ }
if (
this.runtime.character.clientConfig?.discord
@@ -69,6 +91,16 @@ export class MessageManager {
return;
}
+ // Check for mentions-only mode setting
+ if (
+ this.runtime.character.clientConfig?.discord
+ ?.shouldRespondOnlyToMentions
+ ) {
+ if (!this._isMessageForMe(message)) {
+ return;
+ }
+ }
+
if (
this.runtime.character.clientConfig?.discord
?.shouldIgnoreDirectMessages &&
@@ -81,6 +113,134 @@ export class MessageManager {
const userName = message.author.username;
const name = message.author.displayName;
const channelId = message.channel.id;
+ const isDirectlyMentioned = this._isMessageForMe(message);
+ const hasInterest = this._checkInterest(message.channelId);
+
+ // Team handling
+ if (
+ this.runtime.character.clientConfig?.discord?.isPartOfTeam &&
+ !this.runtime.character.clientConfig?.discord
+ ?.shouldRespondOnlyToMentions
+ ) {
+ const authorId = this._getNormalizedUserId(message.author.id);
+
+ if (
+ !this._isTeamLeader() &&
+ this._isRelevantToTeamMember(message.content, channelId)
+ ) {
+ this.interestChannels[message.channelId] = {
+ currentHandler: this.client.user?.id,
+ lastMessageSent: Date.now(),
+ messages: [],
+ };
+ }
+
+ const isTeamRequest = this._isTeamCoordinationRequest(
+ message.content
+ );
+ const isLeader = this._isTeamLeader();
+
+ // After team-wide responses, check if we should maintain interest
+ if (hasInterest && !isDirectlyMentioned) {
+ const lastSelfMemories =
+ await this.runtime.messageManager.getMemories({
+ roomId: stringToUuid(
+ channelId + "-" + this.runtime.agentId
+ ),
+ unique: false,
+ count: 5,
+ });
+
+ const lastSelfSortedMemories = lastSelfMemories
+ ?.filter((m) => m.userId === this.runtime.agentId)
+ .sort((a, b) => (b.createdAt || 0) - (a.createdAt || 0));
+
+ const isRelevant = this._isRelevantToTeamMember(
+ message.content,
+ channelId,
+ lastSelfSortedMemories?.[0]
+ );
+
+ if (!isRelevant) {
+ // Clearing interest - conversation not relevant to team member
+ delete this.interestChannels[message.channelId];
+ return;
+ }
+ }
+
+ if (isTeamRequest) {
+ if (isLeader) {
+ this.interestChannels[message.channelId] = {
+ currentHandler: this.client.user?.id,
+ lastMessageSent: Date.now(),
+ messages: [],
+ };
+ } else {
+ // Set temporary interest for this response
+ this.interestChannels[message.channelId] = {
+ currentHandler: this.client.user?.id,
+ lastMessageSent: Date.now(),
+ messages: [],
+ };
+
+ // Clear interest after this cycle unless directly mentioned
+ if (!isDirectlyMentioned) {
+ // Use existing message cycle to clear interest
+ this.interestChannels[
+ message.channelId
+ ].lastMessageSent = 0;
+ }
+ }
+ }
+
+ // Check for other team member mentions
+ const otherTeamMembers =
+ this.runtime.character.clientConfig.discord.teamAgentIds.filter(
+ (id) => id !== this.client.user?.id
+ );
+ const mentionedTeamMember = otherTeamMembers.find((id) =>
+ message.content.includes(`<@${id}>`)
+ );
+
+ // If another team member is mentioned, clear our interest
+ if (mentionedTeamMember) {
+ if (
+ hasInterest ||
+ this.interestChannels[message.channelId]?.currentHandler ===
+ this.client.user?.id
+ ) {
+ delete this.interestChannels[message.channelId];
+
+ // Only return if we're not the mentioned member
+ if (!isDirectlyMentioned) {
+ return;
+ }
+ }
+ }
+
+ // Set/maintain interest only if we're mentioned or already have interest
+ if (isDirectlyMentioned) {
+ this.interestChannels[message.channelId] = {
+ currentHandler: this.client.user?.id,
+ lastMessageSent: Date.now(),
+ messages: [],
+ };
+ } else if (!isTeamRequest && !hasInterest) {
+ return;
+ }
+
+ // Bot-specific checks
+ if (message.author.bot) {
+ if (this._isTeamMember(authorId) && !isDirectlyMentioned) {
+ return;
+ } else if (
+ this.runtime.character.clientConfig.discord
+ .shouldIgnoreBotMessages
+ ) {
+ return;
+ }
+ }
+ }
try {
const { processedContent, attachments } =
@@ -149,6 +309,26 @@ export class MessageManager {
if (content.text) {
await this.runtime.messageManager.addEmbeddingToMemory(memory);
await this.runtime.messageManager.createMemory(memory);
+
+ if (this.interestChannels[message.channelId]) {
+ // Add new message
+ this.interestChannels[message.channelId].messages.push({
+ userId: userIdUUID,
+ userName: userName,
+ content: content,
+ });
+
+ // Trim to keep only recent messages
+ if (
+ this.interestChannels[message.channelId].messages
+ .length > MESSAGE_CONSTANTS.MAX_MESSAGES
+ ) {
+ this.interestChannels[message.channelId].messages =
+ this.interestChannels[
+ message.channelId
+ ].messages.slice(-MESSAGE_CONSTANTS.MAX_MESSAGES);
+ }
+ }
}
let state = await this.runtime.composeState(userMessage, {
@@ -174,7 +354,6 @@ export class MessageManager {
if (shouldIgnore) {
return;
}
- const hasInterest = this._checkInterest(channelId);
const agentUserState =
await this.runtime.databaseAdapter.getParticipantUserState(
@@ -422,13 +601,355 @@ export class MessageManager {
return { processedContent, attachments };
}
+ private _getNormalizedUserId(id: string): string {
+ return id.toString().replace(/[^0-9]/g, "");
+ }
+
+ private _isTeamMember(userId: string): boolean {
+ const teamConfig = this.runtime.character.clientConfig?.discord;
+ if (!teamConfig?.isPartOfTeam || !teamConfig.teamAgentIds) return false;
+
+ const normalizedUserId = this._getNormalizedUserId(userId);
+
+ const isTeamMember = teamConfig.teamAgentIds.some(
+ (teamId) => this._getNormalizedUserId(teamId) === normalizedUserId
+ );
+
+ return isTeamMember;
+ }
+
+ private _isTeamLeader(): boolean {
+ return (
+ this.client.user?.id ===
+ this.runtime.character.clientConfig?.discord?.teamLeaderId
+ );
+ }
+
+ private _isTeamCoordinationRequest(content: string): boolean {
+ const contentLower = content.toLowerCase();
+ return TEAM_COORDINATION.KEYWORDS?.some((keyword) =>
+ contentLower.includes(keyword.toLowerCase())
+ );
+ }
+
+ private _isRelevantToTeamMember(
+ content: string,
+ channelId: string,
+ lastAgentMemory: Memory | null = null
+ ): boolean {
+ const teamConfig = this.runtime.character.clientConfig?.discord;
+
+ if (this._isTeamLeader() && lastAgentMemory?.content.text) {
+ const timeSinceLastMessage = Date.now() - lastAgentMemory.createdAt;
+ if (timeSinceLastMessage > MESSAGE_CONSTANTS.INTEREST_DECAY_TIME) {
+ return false; // Memory too old, not relevant
+ }
+
+ const similarity = cosineSimilarity(
+ content.toLowerCase(),
+ lastAgentMemory.content.text.toLowerCase()
+ );
+
+ return (
+ similarity >=
+ MESSAGE_CONSTANTS.DEFAULT_SIMILARITY_THRESHOLD_FOLLOW_UPS
+ );
+ }
+
+ // If no keywords defined, only leader maintains conversation
+ if (!teamConfig?.teamMemberInterestKeywords) {
+ return false;
+ }
+
+ return teamConfig.teamMemberInterestKeywords.some((keyword) =>
+ content.toLowerCase().includes(keyword.toLowerCase())
+ );
+ }
+
+ private _isMessageForMe(message: DiscordMessage): boolean {
+ const isMentioned = message.mentions.users?.has(
+ this.client.user?.id as string
+ );
+ const guild = message.guild;
+ const member = guild?.members.cache.get(this.client.user?.id as string);
+ const nickname = member?.nickname;
+
+ // Don't consider role mentions as direct mentions
+ const hasRoleMentionOnly =
+ message.mentions.roles.size > 0 && !isMentioned;
+
+ // If it's only a role mention and we're in team mode, let team logic handle it
+ if (
+ hasRoleMentionOnly &&
+ this.runtime.character.clientConfig?.discord?.isPartOfTeam
+ ) {
+ return false;
+ }
+
+ return (
+ isMentioned ||
+ (!this.runtime.character.clientConfig?.discord
+ ?.shouldRespondOnlyToMentions &&
+ (message.content
+ .toLowerCase()
+ .includes(
+ this.client.user?.username.toLowerCase() as string
+ ) ||
+ message.content
+ .toLowerCase()
+ .includes(
+ this.client.user?.tag.toLowerCase() as string
+ ) ||
+ (nickname &&
+ message.content
+ .toLowerCase()
+ .includes(nickname.toLowerCase()))))
+ );
+ }
+
+ private async _analyzeContextSimilarity(
+ currentMessage: string,
+ previousContext?: MessageContext,
+ agentLastMessage?: string
+ ): Promise {
+ if (!previousContext) return 1; // No previous context to compare against
+
+ // If more than 5 minutes have passed, reduce similarity weight
+ const timeDiff = Date.now() - previousContext.timestamp;
+ const timeWeight = Math.max(0, 1 - timeDiff / (5 * 60 * 1000)); // 5 minutes threshold
+
+ // Calculate content similarity
+ const similarity = cosineSimilarity(
+ currentMessage.toLowerCase(),
+ previousContext.content.toLowerCase(),
+ agentLastMessage?.toLowerCase()
+ );
+
+ // Weight the similarity by time factor
+ const weightedSimilarity = similarity * timeWeight;
+
+ return weightedSimilarity;
+ }
+
+ private async _shouldRespondBasedOnContext(
+ message: DiscordMessage,
+ channelState: InterestChannels[string]
+ ): Promise {
+ // Always respond if directly mentioned
+ if (this._isMessageForMe(message)) return true;
+
+ // If we're not the current handler, don't respond
+ if (channelState?.currentHandler !== this.client.user?.id) return false;
+
+ // Check if we have messages to compare
+ if (!channelState.messages?.length) return false;
+
+ // Get last user message (not from the bot)
+ const lastUserMessage = [...channelState.messages].reverse().find(
+ (m, index) =>
+ index > 0 && // Skip first message (current)
+ m.userId !== this.runtime.agentId
+ );
+
+ if (!lastUserMessage) return false;
+
+ const lastSelfMemories = await this.runtime.messageManager.getMemories({
+ roomId: stringToUuid(
+ message.channel.id + "-" + this.runtime.agentId
+ ),
+ unique: false,
+ count: 5,
+ });
+
+ const lastSelfSortedMemories = lastSelfMemories
+ ?.filter((m) => m.userId === this.runtime.agentId)
+ .sort((a, b) => (b.createdAt || 0) - (a.createdAt || 0));
+
+ // Calculate context similarity
+ const contextSimilarity = await this._analyzeContextSimilarity(
+ message.content,
+ {
+ content: lastUserMessage.content.text || "",
+ timestamp: Date.now(),
+ },
+ lastSelfSortedMemories?.[0]?.content?.text
+ );
+
+ const similarityThreshold =
+ this.runtime.character.clientConfig?.discord
+ ?.messageSimilarityThreshold ||
+ channelState.contextSimilarityThreshold ||
+ MESSAGE_CONSTANTS.DEFAULT_SIMILARITY_THRESHOLD;
+
+ return contextSimilarity >= similarityThreshold;
+ }
+
private _checkInterest(channelId: string): boolean {
- return !!this.interestChannels[channelId];
+ const channelState = this.interestChannels[channelId];
+ if (!channelState) return false;
+
+ const lastMessage =
+ channelState.messages[channelState.messages.length - 1];
+ // If it's been more than 5 minutes since last message, reduce interest
+ const timeSinceLastMessage = Date.now() - channelState.lastMessageSent;
+
+ if (timeSinceLastMessage > MESSAGE_CONSTANTS.INTEREST_DECAY_TIME) {
+ delete this.interestChannels[channelId];
+ return false;
+ } else if (
+ timeSinceLastMessage > MESSAGE_CONSTANTS.PARTIAL_INTEREST_DECAY
+ ) {
+ // Require stronger relevance for continued interest
+ return this._isRelevantToTeamMember(
+ lastMessage.content.text || "",
+ channelId
+ );
+ }
+
+ // If team leader and messages exist, check for topic changes and team member responses
+ if (this._isTeamLeader() && channelState.messages.length > 0) {
+ // If leader's keywords don't match and another team member has responded, drop interest
+ if (
+ !this._isRelevantToTeamMember(
+ lastMessage.content.text || "",
+ channelId
+ )
+ ) {
+ const recentTeamResponses = channelState.messages
+ .slice(-3)
+ .some(
+ (m) =>
+ m.userId !== this.client.user?.id &&
+ this._isTeamMember(m.userId)
+ );
+
+ if (recentTeamResponses) {
+ delete this.interestChannels[channelId];
+ return false;
+ }
+ }
+ }
+
+ // Check if conversation has shifted to a new topic
+ if (channelState.messages.length > 0) {
+ const recentMessages = channelState.messages.slice(
+ -MESSAGE_CONSTANTS.RECENT_MESSAGE_COUNT
+ );
+ const differentUsers = new Set(recentMessages.map((m) => m.userId))
+ .size;
+
+ // If multiple users are talking and we're not involved, reduce interest
+ if (
+ differentUsers > 1 &&
+ !recentMessages.some((m) => m.userId === this.client.user?.id)
+ ) {
+ delete this.interestChannels[channelId];
+ return false;
+ }
+ }
+
+ return true;
}
private async _shouldIgnore(message: DiscordMessage): Promise {
// if the message is from us, ignore
if (message.author.id === this.client.user?.id) return true;
+
+ // Honor mentions-only mode
+ if (
+ this.runtime.character.clientConfig?.discord
+ ?.shouldRespondOnlyToMentions
+ ) {
+ return !this._isMessageForMe(message);
+ }
+
+ // Team-based ignore logic
+ if (this.runtime.character.clientConfig?.discord?.isPartOfTeam) {
+ const authorId = this._getNormalizedUserId(message.author.id);
+
+ if (this._isTeamLeader()) {
+ if (this._isTeamCoordinationRequest(message.content)) {
+ return false;
+ }
+ // Ignore if message is only about team member interests and not directed to leader
+ if (!this._isMessageForMe(message)) {
+ const otherMemberInterests =
+ this.runtime.character.clientConfig?.discord
+ ?.teamMemberInterestKeywords || [];
+ const hasOtherInterests = otherMemberInterests.some(
+ (keyword) =>
+ message.content
+ .toLowerCase()
+ .includes(keyword.toLowerCase())
+ );
+ if (hasOtherInterests) {
+ return true;
+ }
+ }
+ } else if (this._isTeamCoordinationRequest(message.content)) {
+ const randomDelay =
+ Math.floor(
+ Math.random() *
+ (TIMING_CONSTANTS.TEAM_MEMBER_DELAY_MAX -
+ TIMING_CONSTANTS.TEAM_MEMBER_DELAY_MIN)
+ ) + TIMING_CONSTANTS.TEAM_MEMBER_DELAY_MIN; // 1-3 second random delay
+ await new Promise((resolve) =>
+ setTimeout(resolve, randomDelay)
+ );
+ return false;
+ }
+
+ if (this._isTeamMember(authorId)) {
+ if (!this._isMessageForMe(message)) {
+ // If message contains our interests, don't ignore
+ if (
+ this._isRelevantToTeamMember(
+ message.content,
+ message.channelId
+ )
+ ) {
+ return false;
+ }
+ return true;
+ }
+ }
+
+ // Check if we're in an active conversation based on context
+ const channelState = this.interestChannels[message.channelId];
+
+ if (channelState?.currentHandler) {
+ // If we're the current handler, check context
+ if (channelState.currentHandler === this.client.user?.id) {
+ //If it's our keywords, bypass context check
+ if (
+ this._isRelevantToTeamMember(
+ message.content,
+ message.channelId
+ )
+ ) {
+ return false;
+ }
+
+ const shouldRespondContext =
+ await this._shouldRespondBasedOnContext(
+ message,
+ channelState
+ );
+
+ // If context is different, ignore. If similar, don't ignore
+ return !shouldRespondContext;
+ }
+
+ // If another team member is handling and we're not mentioned or coordinating
+ else if (
+ !this._isMessageForMe(message) &&
+ !this._isTeamCoordinationRequest(message.content)
+ ) {
+ return true;
+ }
+ }
+ }
+
let messageContent = message.content.toLowerCase();
// Replace the bot's @ping with the character name
@@ -448,36 +969,10 @@ export class MessageManager {
// strip all special characters
messageContent = messageContent.replace(/[^a-zA-Z0-9\s]/g, "");
- // short responses where ruby should stop talking and disengage unless mentioned again
- const loseInterestWords = [
- "shut up",
- "stop",
- "please shut up",
- "shut up please",
- "dont talk",
- "silence",
- "stop talking",
- "be quiet",
- "hush",
- "wtf",
- "chill",
- "stfu",
- "stupid bot",
- "dumb bot",
- "stop responding",
- "god damn it",
- "god damn",
- "goddamnit",
- "can you not",
- "can you stop",
- "be quiet",
- "hate you",
- "hate this",
- "fuck up",
- ];
+ // short responses where eliza should stop talking and disengage unless mentioned again
if (
- messageContent.length < 100 &&
- loseInterestWords.some((word) => messageContent.includes(word))
+ messageContent.length < MESSAGE_LENGTH_THRESHOLDS.LOSE_INTEREST &&
+ LOSE_INTEREST_WORDS.some((word) => messageContent.includes(word))
) {
delete this.interestChannels[message.channelId];
return true;
@@ -485,7 +980,7 @@ export class MessageManager {
// If we're not interested in the channel and it's a short message, ignore it
if (
- messageContent.length < 10 &&
+ messageContent.length < MESSAGE_LENGTH_THRESHOLDS.SHORT_MESSAGE &&
!this.interestChannels[message.channelId]
) {
return true;
@@ -515,24 +1010,15 @@ export class MessageManager {
// if the message is short, ignore but maintain interest
if (
!this.interestChannels[message.channelId] &&
- messageContent.length < 2
+ messageContent.length < MESSAGE_LENGTH_THRESHOLDS.VERY_SHORT_MESSAGE
) {
return true;
}
- const ignoreResponseWords = [
- "lol",
- "nm",
- "uh",
- "wtf",
- "stfu",
- "dumb",
- "jfc",
- "omg",
- ];
if (
- message.content.length < 4 &&
- ignoreResponseWords.some((word) =>
+ message.content.length <
+ MESSAGE_LENGTH_THRESHOLDS.IGNORE_RESPONSE &&
+ IGNORE_RESPONSE_WORDS.some((word) =>
message.content.toLowerCase().includes(word)
)
) {
@@ -547,6 +1033,171 @@ export class MessageManager {
): Promise {
if (message.author.id === this.client.user?.id) return false;
// if (message.author.bot) return false;
+
+ // Honor mentions-only mode
+ if (
+ this.runtime.character.clientConfig?.discord
+ ?.shouldRespondOnlyToMentions
+ ) {
+ return this._isMessageForMe(message);
+ }
+
+ const channelState = this.interestChannels[message.channelId];
+
+ // Check if team member has direct interest first
+ if (
+ this.runtime.character.clientConfig?.discord?.isPartOfTeam &&
+ !this._isTeamLeader() &&
+ this._isRelevantToTeamMember(message.content, message.channelId)
+ ) {
+ return true;
+ }
+
+ try {
+ // Team-based response logic
+ if (this.runtime.character.clientConfig?.discord?.isPartOfTeam) {
+ // Team leader coordination
+ if (
+ this._isTeamLeader() &&
+ this._isTeamCoordinationRequest(message.content)
+ ) {
+ return true;
+ }
+
+ if (
+ !this._isTeamLeader() &&
+ this._isRelevantToTeamMember(
+ message.content,
+ message.channelId
+ )
+ ) {
+ // Add small delay for non-leader responses
+ await new Promise((resolve) =>
+ setTimeout(resolve, TIMING_CONSTANTS.TEAM_MEMBER_DELAY)
+ ); //1.5 second delay
+
+ // If leader has responded in last few seconds, reduce chance of responding
+
+ if (channelState?.messages?.length) {
+ const recentMessages = channelState.messages.slice(
+ -MESSAGE_CONSTANTS.RECENT_MESSAGE_COUNT
+ );
+ const leaderResponded = recentMessages.some(
+ (m) =>
+ m.userId ===
+ this.runtime.character.clientConfig?.discord
+ ?.teamLeaderId &&
+ Date.now() - channelState.lastMessageSent < 3000
+ );
+
+ if (leaderResponded) {
+ // 50% chance to respond if leader just did
+ return (
+ Math.random() > RESPONSE_CHANCES.AFTER_LEADER
+ );
+ }
+ }
+
+ return true;
+ }
+
+ // If I'm the leader but message doesn't match my keywords, add delay and check for team responses
+ if (
+ this._isTeamLeader() &&
+ !this._isRelevantToTeamMember(
+ message.content,
+ message.channelId
+ )
+ ) {
+ const randomDelay =
+ Math.floor(
+ Math.random() *
+ (TIMING_CONSTANTS.LEADER_DELAY_MAX -
+ TIMING_CONSTANTS.LEADER_DELAY_MIN)
+ ) + TIMING_CONSTANTS.LEADER_DELAY_MIN; // 2-4 second random delay
+ await new Promise((resolve) =>
+ setTimeout(resolve, randomDelay)
+ );
+
+ // After delay, check if another team member has already responded
+ if (channelState?.messages?.length) {
+ const recentResponses = channelState.messages.slice(
+ -MESSAGE_CONSTANTS.RECENT_MESSAGE_COUNT
+ );
+ const otherTeamMemberResponded = recentResponses.some(
+ (m) =>
+ m.userId !== this.client.user?.id &&
+ this._isTeamMember(m.userId)
+ );
+
+ if (otherTeamMemberResponded) {
+ return false;
+ }
+ }
+ }
+
+ // Update current handler if we're mentioned
+ if (this._isMessageForMe(message)) {
+ const channelState =
+ this.interestChannels[message.channelId];
+ if (channelState) {
+ channelState.currentHandler = this.client.user?.id;
+ channelState.lastMessageSent = Date.now();
+ }
+ return true;
+ }
+
+ // Don't respond if another teammate is handling the conversation
+ if (channelState?.currentHandler) {
+ if (
+ channelState.currentHandler !== this.client.user?.id &&
+ this._isTeamMember(channelState.currentHandler)
+ ) {
+ return false;
+ }
+ }
+
+ // Natural conversation cadence
+ if (!this._isMessageForMe(message) && channelState) {
+ // Count our recent messages
+ const recentMessages = channelState.messages.slice(
+ -MESSAGE_CONSTANTS.CHAT_HISTORY_COUNT
+ );
+ const ourMessageCount = recentMessages.filter(
+ (m) => m.userId === this.client.user?.id
+ ).length;
+
+ // Reduce responses if we've been talking a lot
+ if (ourMessageCount > 2) {
+ // Exponentially decrease chance to respond
+ const responseChance = Math.pow(
+ 0.5,
+ ourMessageCount - 2
+ );
+ if (Math.random() > responseChance) {
+ return false;
+ }
+ }
+ }
+ }
+ } catch (error) {
+ elizaLogger.error("Error in _shouldRespond team processing:", {
+ error,
+ agentId: this.runtime.agentId,
+ channelId: message.channelId,
+ });
+ }
+
+ // Otherwise do context check
+ if (channelState?.previousContext) {
+ const shouldRespondContext =
+ await this._shouldRespondBasedOnContext(message, channelState);
+ if (!shouldRespondContext) {
+ delete this.interestChannels[message.channelId];
+ return false;
+ }
+ }
+
if (message.mentions.has(this.client.user?.id as string)) return true;
const guild = message.guild;
@@ -587,6 +1238,13 @@ export class MessageManager {
});
if (response === "RESPOND") {
+ if (channelState) {
+ channelState.previousContext = {
+ content: message.content,
+ timestamp: Date.now(),
+ };
+ }
+
return true;
} else if (response === "IGNORE") {
return false;
diff --git a/packages/client-discord/src/utils.ts b/packages/client-discord/src/utils.ts
index 053c3d4ccb3..6ee1e988716 100644
--- a/packages/client-discord/src/utils.ts
+++ b/packages/client-discord/src/utils.ts
@@ -50,11 +50,11 @@ export async function generateSummary(
text = trimTokens(text, 100000, "gpt-4o-mini"); // TODO: clean this up
const prompt = `Please generate a concise summary for the following text:
-
+
Text: """
${text}
"""
-
+
Respond with a JSON object in the following format:
\`\`\`json
{
@@ -221,3 +221,80 @@ export function canSendMessage(channel) {
: null,
};
}
+
+export function cosineSimilarity(text1: string, text2: string, text3?: string): number {
+ const preprocessText = (text: string) => text
+ .toLowerCase()
+ .replace(/[^\w\s'_-]/g, ' ')
+ .replace(/\s+/g, ' ')
+ .trim();
+
+ const getWords = (text: string) => {
+ return text.split(' ').filter(word => word.length > 1);
+ };
+
+ const words1 = getWords(preprocessText(text1));
+ const words2 = getWords(preprocessText(text2));
+ const words3 = text3 ? getWords(preprocessText(text3)) : [];
+
+ const freq1: { [key: string]: number } = {};
+ const freq2: { [key: string]: number } = {};
+ const freq3: { [key: string]: number } = {};
+
+ words1.forEach(word => freq1[word] = (freq1[word] || 0) + 1);
+ words2.forEach(word => freq2[word] = (freq2[word] || 0) + 1);
+ if (words3.length) {
+ words3.forEach(word => freq3[word] = (freq3[word] || 0) + 1);
+ }
+
+ const uniqueWords = new Set([...Object.keys(freq1), ...Object.keys(freq2), ...(words3.length ? Object.keys(freq3) : [])]);
+
+ let dotProduct = 0;
+ let magnitude1 = 0;
+ let magnitude2 = 0;
+ let magnitude3 = 0;
+
+ uniqueWords.forEach(word => {
+ const val1 = freq1[word] || 0;
+ const val2 = freq2[word] || 0;
+ const val3 = freq3[word] || 0;
+
+ if (words3.length) {
+ // For three-way, calculate pairwise similarities
+ const sim12 = val1 * val2;
+ const sim23 = val2 * val3;
+ const sim13 = val1 * val3;
+
+ // Take maximum similarity between any pair
+ dotProduct += Math.max(sim12, sim23, sim13);
+ } else {
+ dotProduct += val1 * val2;
+ }
+
+ magnitude1 += val1 * val1;
+ magnitude2 += val2 * val2;
+ if (words3.length) {
+ magnitude3 += val3 * val3;
+ }
+ });
+
+ magnitude1 = Math.sqrt(magnitude1);
+ magnitude2 = Math.sqrt(magnitude2);
+ magnitude3 = words3.length ? Math.sqrt(magnitude3) : 1;
+
+ if (magnitude1 === 0 || magnitude2 === 0 || (words3.length && magnitude3 === 0)) return 0;
+
+ // For two texts, use original calculation
+ if (!words3.length) {
+ return dotProduct / (magnitude1 * magnitude2);
+ }
+
+ // For three texts, use max magnitude pair to maintain scale
+ const maxMagnitude = Math.max(
+ magnitude1 * magnitude2,
+ magnitude2 * magnitude3,
+ magnitude1 * magnitude3
+ );
+
+ return dotProduct / maxMagnitude;
+}
\ No newline at end of file
diff --git a/packages/client-discord/src/voice.ts b/packages/client-discord/src/voice.ts
index c8b2bb5447b..ec45b0db949 100644
--- a/packages/client-discord/src/voice.ts
+++ b/packages/client-discord/src/voice.ts
@@ -46,14 +46,12 @@ import {
discordShouldRespondTemplate,
discordVoiceHandlerTemplate,
} from "./templates.ts";
-import debounce from "lodash/debounce.js";
import { getWavHeader } from "./utils.ts";
// These values are chosen for compatibility with picovoice components
const DECODE_FRAME_SIZE = 1024;
const DECODE_SAMPLE_RATE = 16000;
-// Buffers all audio
export class AudioMonitor {
private readable: Readable;
private buffers: Buffer[] = [];
@@ -64,6 +62,7 @@ export class AudioMonitor {
constructor(
readable: Readable,
maxSize: number,
+ onStart: () => void,
callback: (buffer: Buffer) => void
) {
this.readable = readable;
@@ -98,6 +97,7 @@ export class AudioMonitor {
});
this.readable.on("speakingStarted", () => {
if (this.ended) return;
+ onStart();
elizaLogger.log("Speaking started");
this.reset();
});
@@ -138,6 +138,8 @@ export class AudioMonitor {
}
export class VoiceManager extends EventEmitter {
+ private processingVoice: boolean = false;
+ private transcriptionTimeout: NodeJS.Timeout | null = null;
private userStates: Map<
string,
{
@@ -373,6 +375,7 @@ export class VoiceManager extends EventEmitter {
if (avgVolume > SPEAKING_THRESHOLD) {
volumeBuffer.length = 0;
this.cleanupAudioPlayer(this.activeAudioPlayer);
+ this.processingVoice = false;
}
}
});
@@ -453,6 +456,52 @@ export class VoiceManager extends EventEmitter {
// this.scanGuild(guild);
}
+ async debouncedProcessTranscription(
+ userId: UUID,
+ name: string,
+ userName: string,
+ channel: BaseGuildVoiceChannel
+ ) {
+ const DEBOUNCE_TRANSCRIPTION_THRESHOLD = 1500; // wait for 1.5 seconds of silence
+
+ if (this.activeAudioPlayer?.state?.status === "idle") {
+ elizaLogger.log("Cleaning up idle audio player.");
+ this.cleanupAudioPlayer(this.activeAudioPlayer);
+ }
+
+ if (this.activeAudioPlayer || this.processingVoice) {
+ const state = this.userStates.get(userId);
+ state.buffers.length = 0;
+ state.totalLength = 0;
+ return;
+ }
+
+ if (this.transcriptionTimeout) {
+ clearTimeout(this.transcriptionTimeout);
+ }
+
+ this.transcriptionTimeout = setTimeout(async () => {
+ this.processingVoice = true;
+ try {
+ await this.processTranscription(
+ userId,
+ channel.id,
+ channel,
+ name,
+ userName
+ );
+
+ // Clean all users' previous buffers
+ this.userStates.forEach((state, _) => {
+ state.buffers.length = 0;
+ state.totalLength = 0;
+ });
+ } finally {
+ this.processingVoice = false;
+ }
+ }, DEBOUNCE_TRANSCRIPTION_THRESHOLD);
+ }
+
async handleUserStream(
userId: UUID,
name: string,
@@ -461,7 +510,6 @@ export class VoiceManager extends EventEmitter {
audioStream: Readable
) {
console.log(`Starting audio monitor for user: ${userId}`);
- const channelId = channel.id;
if (!this.userStates.has(userId)) {
this.userStates.set(userId, {
buffers: [],
@@ -473,25 +521,17 @@ export class VoiceManager extends EventEmitter {
const state = this.userStates.get(userId);
- const DEBOUNCE_TRANSCRIPTION_THRESHOLD = 2500; // wait for 1.5 seconds of silence
-
- const debouncedProcessTranscription = debounce(async () => {
- await this.processTranscription(
- userId,
- channelId,
- channel,
- name,
- userName
- );
- }, DEBOUNCE_TRANSCRIPTION_THRESHOLD);
-
const processBuffer = async (buffer: Buffer) => {
try {
state!.buffers.push(buffer);
state!.totalLength += buffer.length;
state!.lastActive = Date.now();
-
- debouncedProcessTranscription();
+ this.debouncedProcessTranscription(
+ userId,
+ name,
+ userName,
+ channel
+ );
} catch (error) {
console.error(
`Error processing buffer for user ${userId}:`,
@@ -500,13 +540,22 @@ export class VoiceManager extends EventEmitter {
}
};
- new AudioMonitor(audioStream, 10000000, async (buffer) => {
- if (!buffer) {
- console.error("Received empty buffer");
- return;
+ new AudioMonitor(
+ audioStream,
+ 10000000,
+ () => {
+ if (this.transcriptionTimeout) {
+ clearTimeout(this.transcriptionTimeout);
+ }
+ },
+ async (buffer) => {
+ if (!buffer) {
+ console.error("Received empty buffer");
+ return;
+ }
+ await processBuffer(buffer);
}
- await processBuffer(buffer);
- });
+ );
}
private async processTranscription(
@@ -520,12 +569,11 @@ export class VoiceManager extends EventEmitter {
if (!state || state.buffers.length === 0) return;
try {
const inputBuffer = Buffer.concat(state.buffers, state.totalLength);
+
state.buffers.length = 0; // Clear the buffers
state.totalLength = 0;
-
// Convert Opus to WAV
const wavBuffer = await this.convertOpusToWav(inputBuffer);
-
console.log("Starting transcription...");
const transcriptionText = await this.runtime
diff --git a/packages/client-github/package.json b/packages/client-github/package.json
index 1efb7deb841..183e4fb98df 100644
--- a/packages/client-github/package.json
+++ b/packages/client-github/package.json
@@ -18,6 +18,6 @@
"scripts": {
"build": "tsup --format esm --dts",
"dev": "tsup --format esm --dts --watch",
- "lint": "eslint . --fix"
+ "lint": "eslint --fix --cache ."
}
}
diff --git a/packages/client-lens/package.json b/packages/client-lens/package.json
new file mode 100644
index 00000000000..dde5925912e
--- /dev/null
+++ b/packages/client-lens/package.json
@@ -0,0 +1,24 @@
+{
+ "name": "@ai16z/client-lens",
+ "version": "0.1.0",
+ "main": "dist/index.js",
+ "type": "module",
+ "types": "dist/index.d.ts",
+ "dependencies": {
+ "@ai16z/eliza": "workspace:*",
+ "@lens-protocol/client": "2.2.0",
+ "@lens-protocol/metadata": "1.2.0",
+ "axios": "^1.7.9",
+ "viem": "^2.13.8"
+ },
+ "devDependencies": {
+ "tsup": "^8.3.5"
+ },
+ "peerDependencies": {
+ "@ai16z/eliza": "workspace:*"
+ },
+ "scripts": {
+ "build": "tsup --format esm --dts",
+ "dev": "tsup --format esm --dts --watch"
+ }
+}
diff --git a/packages/client-lens/src/actions.ts b/packages/client-lens/src/actions.ts
new file mode 100644
index 00000000000..499c7e3b863
--- /dev/null
+++ b/packages/client-lens/src/actions.ts
@@ -0,0 +1,51 @@
+import type { LensClient } from "./client";
+import {
+ elizaLogger,
+ type Content,
+ type IAgentRuntime,
+ type Memory,
+ type UUID,
+} from "@ai16z/eliza";
+import { textOnly } from "@lens-protocol/metadata";
+import { createPublicationMemory } from "./memory";
+import { AnyPublicationFragment } from "@lens-protocol/client";
+import StorjProvider from "./providers/StorjProvider";
+
+export async function sendPublication({
+ client,
+ runtime,
+ content,
+ roomId,
+ commentOn,
+ ipfs,
+}: {
+ client: LensClient;
+ runtime: IAgentRuntime;
+ content: Content;
+ roomId: UUID;
+ commentOn?: string;
+ ipfs: StorjProvider;
+}): Promise<{ memory?: Memory; publication?: AnyPublicationFragment }> {
+ // TODO: arweave provider for content hosting
+ const metadata = textOnly({ content: content.text });
+ const contentURI = await ipfs.pinJson(metadata);
+
+ const publication = await client.createPublication(
+ contentURI,
+ false, // TODO: support collectable settings
+ commentOn
+ );
+
+ if (publication) {
+ return {
+ publication,
+ memory: createPublicationMemory({
+ roomId,
+ runtime,
+ publication: publication as AnyPublicationFragment,
+ }),
+ };
+ }
+
+ return {};
+}
diff --git a/packages/client-lens/src/client.ts b/packages/client-lens/src/client.ts
new file mode 100644
index 00000000000..da787a85595
--- /dev/null
+++ b/packages/client-lens/src/client.ts
@@ -0,0 +1,412 @@
+import { IAgentRuntime, elizaLogger } from "@ai16z/eliza";
+import {
+ AnyPublicationFragment,
+ LensClient as LensClientCore,
+ production,
+ LensTransactionStatusType,
+ LimitType,
+ NotificationType,
+ ProfileFragment,
+ PublicationType,
+ FeedEventItemType,
+} from "@lens-protocol/client";
+import { Profile, BroadcastResult } from "./types";
+import { PrivateKeyAccount } from "viem";
+import { getProfilePictureUri, handleBroadcastResult, omit } from "./utils";
+
+export class LensClient {
+ runtime: IAgentRuntime;
+ account: PrivateKeyAccount;
+ cache: Map;
+ lastInteractionTimestamp: Date;
+ profileId: `0x${string}`;
+
+ private authenticated: boolean;
+ private authenticatedProfile: ProfileFragment | null;
+ private core: LensClientCore;
+
+ constructor(opts: {
+ runtime: IAgentRuntime;
+ cache: Map;
+ account: PrivateKeyAccount;
+ profileId: `0x${string}`;
+ }) {
+ this.cache = opts.cache;
+ this.runtime = opts.runtime;
+ this.account = opts.account;
+ this.core = new LensClientCore({
+ environment: production,
+ });
+ this.lastInteractionTimestamp = new Date();
+ this.profileId = opts.profileId;
+ this.authenticated = false;
+ this.authenticatedProfile = null;
+ }
+
+ async authenticate(): Promise {
+ try {
+ const { id, text } =
+ await this.core.authentication.generateChallenge({
+ signedBy: this.account.address,
+ for: this.profileId,
+ });
+
+ const signature = await this.account.signMessage({
+ message: text,
+ });
+
+ await this.core.authentication.authenticate({ id, signature });
+ this.authenticatedProfile = await this.core.profile.fetch({
+ forProfileId: this.profileId,
+ });
+
+ this.authenticated = true;
+ } catch (error) {
+ elizaLogger.error("client-lens::client error: ", error);
+ throw error;
+ }
+ }
+
+ async createPublication(
+ contentURI: string,
+ onchain: boolean = false,
+ commentOn?: string
+ ): Promise {
+ try {
+ if (!this.authenticated) {
+ await this.authenticate();
+ elizaLogger.log("done authenticating");
+ }
+ let broadcastResult;
+
+ if (commentOn) {
+ broadcastResult = onchain
+ ? await this.createCommentOnchain(contentURI, commentOn)
+ : await this.createCommentMomoka(contentURI, commentOn);
+ } else {
+ broadcastResult = onchain
+ ? await this.createPostOnchain(contentURI)
+ : await this.createPostMomoka(contentURI);
+ }
+
+ elizaLogger.log("broadcastResult", broadcastResult);
+
+ if (broadcastResult.id) {
+ return await this.core.publication.fetch({
+ forId: broadcastResult.id,
+ });
+ }
+
+ const completion = await this.core.transaction.waitUntilComplete({
+ forTxHash: broadcastResult.txHash,
+ });
+
+ if (completion?.status === LensTransactionStatusType.Complete) {
+ return await this.core.publication.fetch({
+ forTxHash: completion?.txHash,
+ });
+ }
+ } catch (error) {
+ elizaLogger.error("client-lens::client error: ", error);
+ throw error;
+ }
+ }
+
+ async getPublication(
+ pubId: string
+ ): Promise {
+ if (this.cache.has(`lens/publication/${pubId}`)) {
+ return this.cache.get(`lens/publication/${pubId}`);
+ }
+
+ const publication = await this.core.publication.fetch({ forId: pubId });
+
+ if (publication)
+ this.cache.set(`lens/publication/${pubId}`, publication);
+
+ return publication;
+ }
+
+ async getPublicationsFor(
+ profileId: string,
+ limit: number = 50
+ ): Promise {
+ const timeline: AnyPublicationFragment[] = [];
+ let next: any | undefined = undefined;
+
+ do {
+ const { items, next: newNext } = next
+ ? await next()
+ : await this.core.publication.fetchAll({
+ limit: LimitType.Fifty,
+ where: {
+ from: [profileId],
+ publicationTypes: [PublicationType.Post],
+ },
+ });
+
+ items.forEach((publication) => {
+ this.cache.set(
+ `lens/publication/${publication.id}`,
+ publication
+ );
+ timeline.push(publication);
+ });
+
+ next = newNext;
+ } while (next && timeline.length < limit);
+
+ return timeline;
+ }
+
+ async getMentions(): Promise<{
+ mentions: AnyPublicationFragment[];
+ next?: () => {};
+ }> {
+ if (!this.authenticated) {
+ await this.authenticate();
+ }
+ // TODO: we should limit to new ones or at least latest n
+ const result = await this.core.notifications.fetch({
+ where: {
+ highSignalFilter: false, // true,
+ notificationTypes: [
+ NotificationType.Mentioned,
+ NotificationType.Commented,
+ ],
+ },
+ });
+ const mentions: AnyPublicationFragment[] = [];
+
+ const { items, next } = result.unwrap();
+
+ items.map((notification) => {
+ // @ts-ignore NotificationFragment
+ const item = notification.publication || notification.comment;
+ if (!item.isEncrypted) {
+ mentions.push(item);
+ this.cache.set(`lens/publication/${item.id}`, item);
+ }
+ });
+
+ return { mentions, next };
+ }
+
+ async getProfile(profileId: string): Promise {
+ if (this.cache.has(`lens/profile/${profileId}`)) {
+ return this.cache.get(`lens/profile/${profileId}`) as Profile;
+ }
+
+ const result = await this.core.profile.fetch({
+ forProfileId: profileId,
+ });
+ if (!result?.id) {
+ elizaLogger.error("Error fetching user by profileId");
+
+ throw "getProfile ERROR";
+ }
+
+ const profile: Profile = {
+ id: "",
+ profileId,
+ name: "",
+ handle: "",
+ };
+
+ profile.id = result.id;
+ profile.name = result.metadata?.displayName;
+ profile.handle = result.handle?.localName;
+ profile.bio = result.metadata?.bio;
+ profile.pfp = getProfilePictureUri(result.metadata?.picture);
+
+ this.cache.set(`lens/profile/${profileId}`, profile);
+
+ return profile;
+ }
+
+ async getTimeline(
+ profileId: string,
+ limit: number = 10
+ ): Promise {
+ try {
+ if (!this.authenticated) {
+ await this.authenticate();
+ }
+ const timeline: AnyPublicationFragment[] = [];
+ let next: any | undefined = undefined;
+
+ do {
+ const result = next
+ ? await next()
+ : await this.core.feed.fetch({
+ where: {
+ for: profileId,
+ feedEventItemTypes: [FeedEventItemType.Post],
+ },
+ });
+
+ const data = result.unwrap();
+
+ data.items.forEach((item) => {
+ // private posts in orb clubs are encrypted
+ if (timeline.length < limit && !item.root.isEncrypted) {
+ this.cache.set(
+ `lens/publication/${item.id}`,
+ item.root
+ );
+ timeline.push(item.root as AnyPublicationFragment);
+ }
+ });
+
+ next = data.pageInfo.next;
+ } while (next && timeline.length < limit);
+
+ return timeline;
+ } catch (error) {
+ console.log(error);
+ throw new Error("client-lens:: getTimeline");
+ }
+ }
+
+ private async createPostOnchain(
+ contentURI: string
+ ): Promise {
+ // gasless + signless if they enabled the lens profile manager
+ if (this.authenticatedProfile?.signless) {
+ const broadcastResult = await this.core.publication.postOnchain({
+ contentURI,
+ openActionModules: [], // TODO: if collectable
+ });
+ return handleBroadcastResult(broadcastResult);
+ }
+
+ // gasless with signed type data
+ const typedDataResult =
+ await this.core.publication.createOnchainPostTypedData({
+ contentURI,
+ openActionModules: [], // TODO: if collectable
+ });
+ const { id, typedData } = typedDataResult.unwrap();
+
+ const signedTypedData = await this.account.signTypedData({
+ domain: omit(typedData.domain as any, "__typename"),
+ types: omit(typedData.types, "__typename"),
+ primaryType: "Post",
+ message: omit(typedData.value, "__typename"),
+ });
+
+ const broadcastResult = await this.core.transaction.broadcastOnchain({
+ id,
+ signature: signedTypedData,
+ });
+ return handleBroadcastResult(broadcastResult);
+ }
+
+ private async createPostMomoka(
+ contentURI: string
+ ): Promise {
+ console.log("createPostMomoka");
+ // gasless + signless if they enabled the lens profile manager
+ if (this.authenticatedProfile?.signless) {
+ const broadcastResult = await this.core.publication.postOnMomoka({
+ contentURI,
+ });
+ return handleBroadcastResult(broadcastResult);
+ }
+
+ // gasless with signed type data
+ const typedDataResult =
+ await this.core.publication.createMomokaPostTypedData({
+ contentURI,
+ });
+ console.log("typedDataResult", typedDataResult);
+ const { id, typedData } = typedDataResult.unwrap();
+
+ const signedTypedData = await this.account.signTypedData({
+ domain: omit(typedData.domain as any, "__typename"),
+ types: omit(typedData.types, "__typename"),
+ primaryType: "Post",
+ message: omit(typedData.value, "__typename"),
+ });
+
+ const broadcastResult = await this.core.transaction.broadcastOnMomoka({
+ id,
+ signature: signedTypedData,
+ });
+ return handleBroadcastResult(broadcastResult);
+ }
+
+ private async createCommentOnchain(
+ contentURI: string,
+ commentOn: string
+ ): Promise {
+ // gasless + signless if they enabled the lens profile manager
+ if (this.authenticatedProfile?.signless) {
+ const broadcastResult = await this.core.publication.commentOnchain({
+ commentOn,
+ contentURI,
+ });
+ return handleBroadcastResult(broadcastResult);
+ }
+
+ // gasless with signed type data
+ const typedDataResult =
+ await this.core.publication.createOnchainCommentTypedData({
+ commentOn,
+ contentURI,
+ });
+
+ const { id, typedData } = typedDataResult.unwrap();
+
+ const signedTypedData = await this.account.signTypedData({
+ domain: omit(typedData.domain as any, "__typename"),
+ types: omit(typedData.types, "__typename"),
+ primaryType: "Comment",
+ message: omit(typedData.value, "__typename"),
+ });
+
+ const broadcastResult = await this.core.transaction.broadcastOnchain({
+ id,
+ signature: signedTypedData,
+ });
+ return handleBroadcastResult(broadcastResult);
+ }
+
+ private async createCommentMomoka(
+ contentURI: string,
+ commentOn: string
+ ): Promise {
+ // gasless + signless if they enabled the lens profile manager
+ if (this.authenticatedProfile?.signless) {
+ const broadcastResult = await this.core.publication.commentOnMomoka(
+ {
+ commentOn,
+ contentURI,
+ }
+ );
+ return handleBroadcastResult(broadcastResult);
+ }
+
+ // gasless with signed type data
+ const typedDataResult =
+ await this.core.publication.createMomokaCommentTypedData({
+ commentOn,
+ contentURI,
+ });
+
+ const { id, typedData } = typedDataResult.unwrap();
+
+ const signedTypedData = await this.account.signTypedData({
+ domain: omit(typedData.domain as any, "__typename"),
+ types: omit(typedData.types, "__typename"),
+ primaryType: "Comment",
+ message: omit(typedData.value, "__typename"),
+ });
+
+ const broadcastResult = await this.core.transaction.broadcastOnMomoka({
+ id,
+ signature: signedTypedData,
+ });
+ return handleBroadcastResult(broadcastResult);
+ }
+}
diff --git a/packages/client-lens/src/index.ts b/packages/client-lens/src/index.ts
new file mode 100644
index 00000000000..24cbe42268b
--- /dev/null
+++ b/packages/client-lens/src/index.ts
@@ -0,0 +1,66 @@
+import { Client, IAgentRuntime, elizaLogger } from "@ai16z/eliza";
+import { privateKeyToAccount } from "viem/accounts";
+import { LensClient } from "./client";
+import { LensPostManager } from "./post";
+import { LensInteractionManager } from "./interactions";
+import StorjProvider from "./providers/StorjProvider";
+
+export class LensAgentClient implements Client {
+ client: LensClient;
+ posts: LensPostManager;
+ interactions: LensInteractionManager;
+
+ private profileId: `0x${string}`;
+ private ipfs: StorjProvider;
+
+ constructor(public runtime: IAgentRuntime) {
+ const cache = new Map();
+
+ const privateKey = runtime.getSetting(
+ "EVM_PRIVATE_KEY"
+ ) as `0x${string}`;
+ if (!privateKey) {
+ throw new Error("EVM_PRIVATE_KEY is missing");
+ }
+ const account = privateKeyToAccount(privateKey);
+
+ this.profileId = runtime.getSetting(
+ "LENS_PROFILE_ID"
+ )! as `0x${string}`;
+
+ this.client = new LensClient({
+ runtime: this.runtime,
+ account,
+ cache,
+ profileId: this.profileId,
+ });
+
+ elizaLogger.info("Lens client initialized.");
+
+ this.ipfs = new StorjProvider(runtime);
+
+ this.posts = new LensPostManager(
+ this.client,
+ this.runtime,
+ this.profileId,
+ cache,
+ this.ipfs
+ );
+
+ this.interactions = new LensInteractionManager(
+ this.client,
+ this.runtime,
+ this.profileId,
+ cache,
+ this.ipfs
+ );
+ }
+
+ async start() {
+ await Promise.all([this.posts.start(), this.interactions.start()]);
+ }
+
+ async stop() {
+ await Promise.all([this.posts.stop(), this.interactions.stop()]);
+ }
+}
diff --git a/packages/client-lens/src/interactions.ts b/packages/client-lens/src/interactions.ts
new file mode 100644
index 00000000000..0a320ab10e7
--- /dev/null
+++ b/packages/client-lens/src/interactions.ts
@@ -0,0 +1,280 @@
+import {
+ composeContext,
+ generateMessageResponse,
+ generateShouldRespond,
+ Memory,
+ ModelClass,
+ stringToUuid,
+ elizaLogger,
+ HandlerCallback,
+ Content,
+ type IAgentRuntime,
+} from "@ai16z/eliza";
+import type { LensClient } from "./client";
+import { toHex } from "viem";
+import { buildConversationThread, createPublicationMemory } from "./memory";
+import {
+ formatPublication,
+ formatTimeline,
+ messageHandlerTemplate,
+ shouldRespondTemplate,
+} from "./prompts";
+import { publicationUuid } from "./utils";
+import { sendPublication } from "./actions";
+import { AnyPublicationFragment } from "@lens-protocol/client";
+import { Profile } from "./types";
+import StorjProvider from "./providers/StorjProvider";
+
+export class LensInteractionManager {
+ private timeout: NodeJS.Timeout | undefined;
+ constructor(
+ public client: LensClient,
+ public runtime: IAgentRuntime,
+ private profileId: string,
+ public cache: Map,
+ private ipfs: StorjProvider
+ ) {}
+
+ public async start() {
+ const handleInteractionsLoop = async () => {
+ try {
+ await this.handleInteractions();
+ } catch (error) {
+ elizaLogger.error(error);
+ return;
+ }
+
+ this.timeout = setTimeout(
+ handleInteractionsLoop,
+ Number(this.runtime.getSetting("LENS_POLL_INTERVAL") || 120) *
+ 1000 // Default to 2 minutes
+ );
+ };
+
+ handleInteractionsLoop();
+ }
+
+ public async stop() {
+ if (this.timeout) clearTimeout(this.timeout);
+ }
+
+ private async handleInteractions() {
+ elizaLogger.info("Handle Lens interactions");
+ // TODO: handle next() for pagination
+ const { mentions } = await this.client.getMentions();
+
+ const agent = await this.client.getProfile(this.profileId);
+ for (const mention of mentions) {
+ const messageHash = toHex(mention.id);
+ const conversationId = `${messageHash}-${this.runtime.agentId}`;
+ const roomId = stringToUuid(conversationId);
+ const userId = stringToUuid(mention.by.id);
+
+ const pastMemoryId = publicationUuid({
+ agentId: this.runtime.agentId,
+ pubId: mention.id,
+ });
+
+ const pastMemory =
+ await this.runtime.messageManager.getMemoryById(pastMemoryId);
+
+ if (pastMemory) {
+ continue;
+ }
+
+ await this.runtime.ensureConnection(
+ userId,
+ roomId,
+ mention.by.id,
+ mention.by.metadata?.displayName ||
+ mention.by.handle?.localName,
+ "lens"
+ );
+
+ const thread = await buildConversationThread({
+ client: this.client,
+ runtime: this.runtime,
+ publication: mention,
+ });
+
+ const memory: Memory = {
+ // @ts-ignore Metadata
+ content: { text: mention.metadata.content, hash: mention.id },
+ agentId: this.runtime.agentId,
+ userId,
+ roomId,
+ };
+
+ await this.handlePublication({
+ agent,
+ publication: mention,
+ memory,
+ thread,
+ });
+ }
+
+ this.client.lastInteractionTimestamp = new Date();
+ }
+
+ private async handlePublication({
+ agent,
+ publication,
+ memory,
+ thread,
+ }: {
+ agent: Profile;
+ publication: AnyPublicationFragment;
+ memory: Memory;
+ thread: AnyPublicationFragment[];
+ }) {
+ if (publication.by.id === agent.id) {
+ elizaLogger.info("skipping cast from bot itself", publication.id);
+ return;
+ }
+
+ if (!memory.content.text) {
+ elizaLogger.info("skipping cast with no text", publication.id);
+ return { text: "", action: "IGNORE" };
+ }
+
+ const currentPost = formatPublication(publication);
+
+ const timeline = await this.client.getTimeline(this.profileId);
+
+ const formattedTimeline = formatTimeline(
+ this.runtime.character,
+ timeline
+ );
+
+ const formattedConversation = thread
+ .map((pub) => {
+ // @ts-ignore Metadata
+ const content = pub.metadata.content;
+ return `@${pub.by.handle?.localName} (${new Date(
+ pub.createdAt
+ ).toLocaleString("en-US", {
+ hour: "2-digit",
+ minute: "2-digit",
+ month: "short",
+ day: "numeric",
+ })}):
+ ${content}`;
+ })
+ .join("\n\n");
+
+ const state = await this.runtime.composeState(memory, {
+ lensHandle: agent.handle,
+ timeline: formattedTimeline,
+ currentPost,
+ formattedConversation,
+ });
+
+ const shouldRespondContext = composeContext({
+ state,
+ template:
+ this.runtime.character.templates?.lensShouldRespondTemplate ||
+ this.runtime.character?.templates?.shouldRespondTemplate ||
+ shouldRespondTemplate,
+ });
+
+ const memoryId = publicationUuid({
+ agentId: this.runtime.agentId,
+ pubId: publication.id,
+ });
+
+ const castMemory =
+ await this.runtime.messageManager.getMemoryById(memoryId);
+
+ if (!castMemory) {
+ await this.runtime.messageManager.createMemory(
+ createPublicationMemory({
+ roomId: memory.roomId,
+ runtime: this.runtime,
+ publication,
+ })
+ );
+ }
+
+ const shouldRespondResponse = await generateShouldRespond({
+ runtime: this.runtime,
+ context: shouldRespondContext,
+ modelClass: ModelClass.SMALL,
+ });
+
+ if (
+ shouldRespondResponse === "IGNORE" ||
+ shouldRespondResponse === "STOP"
+ ) {
+ elizaLogger.info(
+ `Not responding to publication because generated ShouldRespond was ${shouldRespondResponse}`
+ );
+ return;
+ }
+
+ const context = composeContext({
+ state,
+ template:
+ this.runtime.character.templates?.lensMessageHandlerTemplate ??
+ this.runtime.character?.templates?.messageHandlerTemplate ??
+ messageHandlerTemplate,
+ });
+
+ const responseContent = await generateMessageResponse({
+ runtime: this.runtime,
+ context,
+ modelClass: ModelClass.LARGE,
+ });
+
+ responseContent.inReplyTo = memoryId;
+
+ if (!responseContent.text) return;
+
+ if (this.runtime.getSetting("LENS_DRY_RUN") === "true") {
+ elizaLogger.info(
+ `Dry run: would have responded to publication ${publication.id} with ${responseContent.text}`
+ );
+ return;
+ }
+
+ const callback: HandlerCallback = async (
+ content: Content,
+ files: any[]
+ ) => {
+ try {
+ if (memoryId && !content.inReplyTo) {
+ content.inReplyTo = memoryId;
+ }
+ const result = await sendPublication({
+ runtime: this.runtime,
+ client: this.client,
+ content: content,
+ roomId: memory.roomId,
+ commentOn: publication.id,
+ ipfs: this.ipfs,
+ });
+ if (!result.publication?.id)
+ throw new Error("publication not sent");
+
+ // sendPublication lost response action, so we need to add it back here?
+ result.memory!.content.action = content.action;
+
+ await this.runtime.messageManager.createMemory(result.memory!);
+ return [result.memory!];
+ } catch (error) {
+ console.error("Error sending response cast:", error);
+ return [];
+ }
+ };
+
+ const responseMessages = await callback(responseContent);
+
+ const newState = await this.runtime.updateRecentMessageState(state);
+
+ await this.runtime.processActions(
+ memory,
+ responseMessages,
+ newState,
+ callback
+ );
+ }
+}
diff --git a/packages/client-lens/src/memory.ts b/packages/client-lens/src/memory.ts
new file mode 100644
index 00000000000..2de9d01b95d
--- /dev/null
+++ b/packages/client-lens/src/memory.ts
@@ -0,0 +1,112 @@
+import {
+ elizaLogger,
+ getEmbeddingZeroVector,
+ IAgentRuntime,
+ stringToUuid,
+ type Memory,
+ type UUID,
+} from "@ai16z/eliza";
+import { publicationUuid } from "./utils";
+import { LensClient } from "./client";
+import { AnyPublicationFragment } from "@lens-protocol/client";
+
+export function createPublicationMemory({
+ roomId,
+ runtime,
+ publication,
+}: {
+ roomId: UUID;
+ runtime: IAgentRuntime;
+ publication: AnyPublicationFragment;
+}): Memory {
+ const commentOn = publication.commentOn
+ ? publicationUuid({
+ pubId: publication.commentOn.id,
+ agentId: runtime.agentId,
+ })
+ : undefined;
+
+ return {
+ id: publicationUuid({
+ pubId: publication.id,
+ agentId: runtime.agentId,
+ }),
+ agentId: runtime.agentId,
+ userId: runtime.agentId,
+ content: {
+ text: publication.metadata.content,
+ source: "lens",
+ url: "",
+ commentOn,
+ id: publication.id,
+ },
+ roomId,
+ embedding: getEmbeddingZeroVector(),
+ };
+}
+
+export async function buildConversationThread({
+ publication,
+ runtime,
+ client,
+}: {
+ publication: AnyPublicationFragment;
+ runtime: IAgentRuntime;
+ client: LensClient;
+}): Promise {
+ const thread: AnyPublicationFragment[] = [];
+ const visited: Set = new Set();
+ async function processThread(currentPublication: AnyPublicationFragment) {
+ if (visited.has(currentPublication.id)) {
+ return;
+ }
+
+ visited.add(currentPublication.id);
+
+ const roomId = publicationUuid({
+ pubId: currentPublication.id,
+ agentId: runtime.agentId,
+ });
+
+ // Check if the current cast has already been saved
+ const memory = await runtime.messageManager.getMemoryById(roomId);
+
+ if (!memory) {
+ elizaLogger.log(
+ "Creating memory for publication",
+ currentPublication.id
+ );
+
+ const userId = stringToUuid(currentPublication.by.id);
+
+ await runtime.ensureConnection(
+ userId,
+ roomId,
+ currentPublication.by.id,
+ currentPublication.by.metadata?.displayName ||
+ currentPublication.by.handle?.localName,
+ "lens"
+ );
+
+ await runtime.messageManager.createMemory(
+ createPublicationMemory({
+ roomId,
+ runtime,
+ publication: currentPublication,
+ })
+ );
+ }
+
+ thread.unshift(currentPublication);
+
+ if (currentPublication.commentOn) {
+ const parentPublication = await client.getPublication(
+ currentPublication.commentOn.id
+ );
+ if (parentPublication) await processThread(parentPublication);
+ }
+ }
+
+ await processThread(publication);
+ return thread;
+}
diff --git a/packages/client-lens/src/post.ts b/packages/client-lens/src/post.ts
new file mode 100644
index 00000000000..16feb4802b2
--- /dev/null
+++ b/packages/client-lens/src/post.ts
@@ -0,0 +1,141 @@
+import {
+ composeContext,
+ generateText,
+ IAgentRuntime,
+ ModelClass,
+ stringToUuid,
+ elizaLogger,
+} from "@ai16z/eliza";
+import { LensClient } from "./client";
+import { formatTimeline, postTemplate } from "./prompts";
+import { publicationUuid } from "./utils";
+import { createPublicationMemory } from "./memory";
+import { sendPublication } from "./actions";
+import StorjProvider from "./providers/StorjProvider";
+
+export class LensPostManager {
+ private timeout: NodeJS.Timeout | undefined;
+
+ constructor(
+ public client: LensClient,
+ public runtime: IAgentRuntime,
+ private profileId: string,
+ public cache: Map,
+ private ipfs: StorjProvider
+ ) {}
+
+ public async start() {
+ const generateNewPubLoop = async () => {
+ try {
+ await this.generateNewPublication();
+ } catch (error) {
+ elizaLogger.error(error);
+ return;
+ }
+
+ this.timeout = setTimeout(
+ generateNewPubLoop,
+ (Math.floor(Math.random() * (4 - 1 + 1)) + 1) * 60 * 60 * 1000
+ ); // Random interval between 1 and 4 hours
+ };
+
+ generateNewPubLoop();
+ }
+
+ public async stop() {
+ if (this.timeout) clearTimeout(this.timeout);
+ }
+
+ private async generateNewPublication() {
+ elizaLogger.info("Generating new publication");
+ try {
+ const profile = await this.client.getProfile(this.profileId);
+ await this.runtime.ensureUserExists(
+ this.runtime.agentId,
+ profile.handle!,
+ this.runtime.character.name,
+ "lens"
+ );
+
+ const timeline = await this.client.getTimeline(this.profileId);
+
+ // this.cache.set("lens/timeline", timeline);
+
+ const formattedHomeTimeline = formatTimeline(
+ this.runtime.character,
+ timeline
+ );
+
+ const generateRoomId = stringToUuid("lens_generate_room");
+
+ const state = await this.runtime.composeState(
+ {
+ roomId: generateRoomId,
+ userId: this.runtime.agentId,
+ agentId: this.runtime.agentId,
+ content: { text: "", action: "" },
+ },
+ {
+ lensHandle: profile.handle,
+ timeline: formattedHomeTimeline,
+ }
+ );
+
+ const context = composeContext({
+ state,
+ template:
+ this.runtime.character.templates?.lensPostTemplate ||
+ postTemplate,
+ });
+
+ const content = await generateText({
+ runtime: this.runtime,
+ context,
+ modelClass: ModelClass.SMALL,
+ });
+
+ if (this.runtime.getSetting("LENS_DRY_RUN") === "true") {
+ elizaLogger.info(`Dry run: would have posted: ${content}`);
+ return;
+ }
+
+ try {
+ const { publication } = await sendPublication({
+ client: this.client,
+ runtime: this.runtime,
+ roomId: generateRoomId,
+ content: { text: content },
+ ipfs: this.ipfs,
+ });
+
+ if (!publication) throw new Error("failed to send publication");
+
+ const roomId = publicationUuid({
+ agentId: this.runtime.agentId,
+ pubId: publication.id,
+ });
+
+ await this.runtime.ensureRoomExists(roomId);
+
+ await this.runtime.ensureParticipantInRoom(
+ this.runtime.agentId,
+ roomId
+ );
+
+ elizaLogger.info(`[Lens Client] Published ${publication.id}`);
+
+ await this.runtime.messageManager.createMemory(
+ createPublicationMemory({
+ roomId,
+ runtime: this.runtime,
+ publication,
+ })
+ );
+ } catch (error) {
+ elizaLogger.error("Error sending publication:", error);
+ }
+ } catch (error) {
+ elizaLogger.error("Error generating new publication:", error);
+ }
+ }
+}
diff --git a/packages/client-lens/src/prompts.ts b/packages/client-lens/src/prompts.ts
new file mode 100644
index 00000000000..a02ed4b0b6d
--- /dev/null
+++ b/packages/client-lens/src/prompts.ts
@@ -0,0 +1,88 @@
+import {
+ Character,
+ messageCompletionFooter,
+ shouldRespondFooter,
+} from "@ai16z/eliza";
+import { AnyPublicationFragment } from "@lens-protocol/client";
+
+export const formatPublication = (publication: AnyPublicationFragment) => {
+ return `ID: ${publication.id}
+ From: ${publication.by.metadata?.displayName} (@${publication.by.handle?.localName})${publication.by.handle?.localName})${publication.commentOn ? `\nIn reply to: @${publication.commentOn.by.handle?.localName}` : ""}
+Text: ${publication.metadata.content}`;
+};
+
+export const formatTimeline = (
+ character: Character,
+ timeline: AnyPublicationFragment[]
+) => `# ${character.name}'s Home Timeline
+${timeline.map(formatPublication).join("\n")}
+`;
+
+export const headerTemplate = `
+{{timeline}}
+
+# Knowledge
+{{knowledge}}
+
+About {{agentName}} (@{{lensHandle}}):
+{{bio}}
+{{lore}}
+{{postDirections}}
+
+{{providers}}
+
+{{recentPosts}}
+
+{{characterPostExamples}}`;
+
+export const postTemplate =
+ headerTemplate +
+ `
+# Task: Generate a post in the voice and style of {{agentName}}, aka @{{lensHandle}}
+Write a single sentence post that is {{adjective}} about {{topic}} (without mentioning {{topic}} directly), from the perspective of {{agentName}}.
+Try to write something totally different than previous posts. Do not add commentary or ackwowledge this request, just write the post.
+
+Your response should not contain any questions. Brief, concise statements only. No emojis. Use \\n\\n (double spaces) between statements.`;
+
+export const messageHandlerTemplate =
+ headerTemplate +
+ `
+Recent interactions between {{agentName}} and other users:
+{{recentPostInteractions}}
+
+Thread of publications You Are Replying To:
+{{formattedConversation}}
+
+# Task: Generate a post in the voice, style and perspective of {{agentName}} (@{{lensHandle}}):
+{{currentPost}}` +
+ messageCompletionFooter;
+
+export const shouldRespondTemplate =
+ //
+ `# Task: Decide if {{agentName}} should respond.
+ About {{agentName}}:
+ {{bio}}
+
+ # INSTRUCTIONS: Determine if {{agentName}} (@{{lensHandle}}) should respond to the message and participate in the conversation. Do not comment. Just respond with "RESPOND" or "IGNORE" or "STOP".
+
+Response options are RESPOND, IGNORE and STOP.
+
+{{agentName}} should respond to messages that are directed at them, or participate in conversations that are interesting or relevant to their background, IGNORE messages that are irrelevant to them, and should STOP if the conversation is concluded.
+
+{{agentName}} is in a room with other users and wants to be conversational, but not annoying.
+{{agentName}} should RESPOND to messages that are directed at them, or participate in conversations that are interesting or relevant to their background.
+If a message is not interesting or relevant, {{agentName}} should IGNORE.
+If a message thread has become repetitive, {{agentName}} should IGNORE.
+Unless directly RESPONDing to a user, {{agentName}} should IGNORE messages that are very short or do not contain much information.
+If a user asks {{agentName}} to stop talking, {{agentName}} should STOP.
+If {{agentName}} concludes a conversation and isn't part of the conversation anymore, {{agentName}} should STOP.
+
+IMPORTANT: {{agentName}} (aka @{{lensHandle}}) is particularly sensitive about being annoying, so if there is any doubt, it is better to IGNORE than to RESPOND.
+
+Thread of messages You Are Replying To:
+{{formattedConversation}}
+
+Current message:
+{{currentPost}}
+
+` + shouldRespondFooter;
diff --git a/packages/client-lens/src/providers/StorjProvider.ts b/packages/client-lens/src/providers/StorjProvider.ts
new file mode 100644
index 00000000000..0460c3835cb
--- /dev/null
+++ b/packages/client-lens/src/providers/StorjProvider.ts
@@ -0,0 +1,84 @@
+import axios, { AxiosInstance } from "axios";
+import FormData from "form-data";
+import type { IAgentRuntime } from "@ai16z/eliza";
+
+// ipfs pinning service: https://storj.dev/dcs/api/storj-ipfs-pinning
+class StorjProvider {
+ private STORJ_API_URL: string = "https://www.storj-ipfs.com";
+ private STORJ_API_USERNAME: string;
+ private STORJ_API_PASSWORD: string;
+ private baseURL: string;
+ private client: AxiosInstance;
+
+ constructor(runtime: IAgentRuntime) {
+ this.STORJ_API_USERNAME = runtime.getSetting("STORJ_API_USERNAME")!;
+ this.STORJ_API_PASSWORD = runtime.getSetting("STORJ_API_PASSWORD")!;
+ this.baseURL = `${this.STORJ_API_URL}/api/v0`;
+ this.client = this.createClient();
+ }
+
+ private createClient(): AxiosInstance {
+ return axios.create({
+ baseURL: this.baseURL,
+ auth: {
+ username: this.STORJ_API_USERNAME,
+ password: this.STORJ_API_PASSWORD,
+ },
+ });
+ }
+
+ private hash(uriOrHash: string): string {
+ return typeof uriOrHash === "string" && uriOrHash.startsWith("ipfs://")
+ ? uriOrHash.split("ipfs://")[1]
+ : uriOrHash;
+ }
+
+ public gatewayURL(uriOrHash: string): string {
+ return `${this.STORJ_API_URL}/ipfs/${this.hash(uriOrHash)}`;
+ }
+
+ public async pinJson(json: any): Promise {
+ if (typeof json !== "string") {
+ json = JSON.stringify(json);
+ }
+ const formData = new FormData();
+ formData.append("path", Buffer.from(json, "utf-8").toString());
+
+ const headers = {
+ "Content-Type": "multipart/form-data",
+ ...formData.getHeaders(),
+ };
+
+ const { data } = await this.client.post(
+ "add?cid-version=1",
+ formData.getBuffer(),
+ { headers }
+ );
+
+ return this.gatewayURL(data.Hash);
+ }
+
+ public async pinFile(file: {
+ buffer: Buffer;
+ originalname: string;
+ mimetype: string;
+ }): Promise {
+ const formData = new FormData();
+ formData.append("file", file.buffer, {
+ filename: file.originalname,
+ contentType: file.mimetype,
+ });
+
+ const response = await this.client.post("add?cid-version=1", formData, {
+ headers: {
+ "Content-Type": `multipart/form-data; boundary=${formData.getBoundary()}`,
+ },
+ maxContentLength: Infinity,
+ maxBodyLength: Infinity,
+ });
+
+ return this.gatewayURL(response.data.Hash);
+ }
+}
+
+export default StorjProvider;
diff --git a/packages/client-lens/src/types.ts b/packages/client-lens/src/types.ts
new file mode 100644
index 00000000000..ef22c5dff4b
--- /dev/null
+++ b/packages/client-lens/src/types.ts
@@ -0,0 +1,14 @@
+export type Profile = {
+ id: string;
+ profileId: string;
+ name?: string | null;
+ handle?: string;
+ pfp?: string;
+ bio?: string | null;
+ url?: string;
+};
+
+export type BroadcastResult = {
+ id?: string;
+ txId?: string;
+};
diff --git a/packages/client-lens/src/utils.ts b/packages/client-lens/src/utils.ts
new file mode 100644
index 00000000000..b15d4fe923c
--- /dev/null
+++ b/packages/client-lens/src/utils.ts
@@ -0,0 +1,84 @@
+import { stringToUuid } from "@ai16z/eliza";
+import { BroadcastResult } from "./types";
+
+export function publicationId({
+ pubId,
+ agentId,
+}: {
+ pubId: string;
+ agentId: string;
+}) {
+ return `${pubId}-${agentId}`;
+}
+
+export function publicationUuid(props: { pubId: string; agentId: string }) {
+ return stringToUuid(publicationId(props));
+}
+
+export function populateMentions(
+ text: string,
+ userIds: number[],
+ positions: number[],
+ userMap: Record
+) {
+ // Validate input arrays have same length
+ if (userIds.length !== positions.length) {
+ throw new Error(
+ "User IDs and positions arrays must have the same length"
+ );
+ }
+
+ // Create array of mention objects with position and user info
+ const mentions = userIds
+ .map((userId, index) => ({
+ position: positions[index],
+ userId,
+ displayName: userMap[userId]!,
+ }))
+ .sort((a, b) => b.position - a.position); // Sort in reverse order to prevent position shifting
+
+ // Create the resulting string by inserting mentions
+ let result = text;
+ mentions.forEach((mention) => {
+ const mentionText = `@${mention.displayName}`;
+ result =
+ result.slice(0, mention.position) +
+ mentionText +
+ result.slice(mention.position);
+ });
+
+ return result;
+}
+
+export const handleBroadcastResult = (
+ broadcastResult: any
+): BroadcastResult | undefined => {
+ const broadcastValue = broadcastResult.unwrap();
+
+ if ("id" in broadcastValue || "txId" in broadcastValue) {
+ return broadcastValue;
+ } else {
+ throw new Error();
+ }
+};
+
+export const getProfilePictureUri = (picture: any): string | undefined => {
+ if ("optimized" in picture) {
+ return picture.optimized?.uri || picture.raw?.uri || picture.uri;
+ } else {
+ return picture.uri;
+ }
+};
+
+export function omit(
+ obj: T,
+ key: K
+): Omit {
+ const result: any = {};
+ Object.keys(obj).forEach((currentKey) => {
+ if (currentKey !== key) {
+ result[currentKey] = obj[currentKey];
+ }
+ });
+ return result;
+}
diff --git a/packages/client-lens/tsconfig.json b/packages/client-lens/tsconfig.json
new file mode 100644
index 00000000000..6f3a09a9f77
--- /dev/null
+++ b/packages/client-lens/tsconfig.json
@@ -0,0 +1,12 @@
+{
+ "extends": "../core/tsconfig.json",
+ "compilerOptions": {
+ "jsx": "react",
+ "outDir": "dist",
+ "rootDir": "./src",
+ "strict": true
+ },
+ "include": [
+ "src/**/*.ts"
+ ]
+}
\ No newline at end of file
diff --git a/packages/client-lens/tsup.config.ts b/packages/client-lens/tsup.config.ts
new file mode 100644
index 00000000000..a2fbfc4a0f6
--- /dev/null
+++ b/packages/client-lens/tsup.config.ts
@@ -0,0 +1,23 @@
+import { defineConfig } from "tsup";
+
+export default defineConfig({
+ entry: ["src/index.ts"],
+ outDir: "dist",
+ sourcemap: true,
+ clean: true,
+ format: ["esm"], // Ensure you're targeting CommonJS
+ external: [
+ "dotenv", // Externalize dotenv to prevent bundling
+ "fs", // Externalize fs to use Node.js built-in module
+ "path", // Externalize other built-ins if necessary
+ "@reflink/reflink",
+ "@node-llama-cpp",
+ "https",
+ "http",
+ "util",
+ "form-data",
+ "axios",
+ "agentkeepalive",
+ // Add other modules you want to externalize
+ ],
+});
diff --git a/packages/client-linkedin/README.md b/packages/client-linkedin/README.md
deleted file mode 100644
index ec43d8c5cc9..00000000000
--- a/packages/client-linkedin/README.md
+++ /dev/null
@@ -1,55 +0,0 @@
-# @ai16z/client-linkedin
-
-LinkedIn client integration for AI16Z agents. This package provides functionality for AI agents to interact with LinkedIn, including:
-
-- Automated post creation and scheduling
-- Professional interaction management
-- Message and comment handling
-- Connection management
-- Activity tracking
-
-## Installation
-
-```bash
-pnpm add @ai16z/client-linkedin
-```
-
-## Configuration
-
-Set the following environment variables:
-
-```env
-LINKEDIN_USERNAME=your.email@example.com
-LINKEDIN_PASSWORD=your_password
-LINKEDIN_DRY_RUN=false
-POST_INTERVAL_MIN=24
-POST_INTERVAL_MAX=72
-```
-
-## Usage
-
-```typescript
-import { LinkedInClientInterface } from '@ai16z/client-linkedin';
-
-// Initialize the client
-const manager = await LinkedInClientInterface.start(runtime);
-
-// The client will automatically:
-// - Generate and schedule posts
-// - Respond to messages and comments
-// - Manage connections
-// - Track activities
-```
-
-## Features
-
-- Professional content generation
-- Rate-limited API interactions
-- Conversation history tracking
-- Connection management
-- Activity monitoring
-- Cache management
-
-## License
-
-MIT
diff --git a/packages/client-linkedin/package.json b/packages/client-linkedin/package.json
deleted file mode 100644
index eb120b215ee..00000000000
--- a/packages/client-linkedin/package.json
+++ /dev/null
@@ -1,27 +0,0 @@
-{
- "name": "@ai16z/client-linkedin",
- "version": "0.1.0-alpha.1",
- "description": "LinkedIn client integration for AI16Z agents",
- "main": "dist/index.js",
- "types": "dist/index.d.ts",
- "scripts": {
- "build": "tsc",
- "test": "jest"
- },
- "dependencies": {
- "@ai16z/eliza": "workspace:*",
- "linkedin-api": "0.0.1",
- "zod": "^3.22.4"
- },
- "devDependencies": {
- "@types/node": "^20.0.0",
- "@typescript-eslint/eslint-plugin": "^6.0.0",
- "@typescript-eslint/parser": "^6.0.0",
- "eslint": "^8.0.0",
- "jest": "^29.0.0",
- "typescript": "^5.0.0"
- },
- "peerDependencies": {
- "@ai16z/eliza": "workspace:*"
- }
-}
diff --git a/packages/client-linkedin/src/base.ts b/packages/client-linkedin/src/base.ts
deleted file mode 100644
index cbf2f5e1626..00000000000
--- a/packages/client-linkedin/src/base.ts
+++ /dev/null
@@ -1,205 +0,0 @@
-import { EventEmitter } from "events";
-// @ts-ignore
-import { Client as LinkedInClient } from "linkedin-api";
-import { elizaLogger } from "@ai16z/eliza";
-import { stringToUuid, getEmbeddingZeroVector } from "@ai16z/eliza";
-
-class RequestQueue {
- private queue: (() => Promise)[] = [];
- private processing = false;
-
- async add(request: () => Promise): Promise {
- return new Promise((resolve, reject) => {
- this.queue.push(async () => {
- try {
- const result = await request();
- resolve(result);
- } catch (error) {
- reject(error);
- }
- });
- this.processQueue();
- });
- }
-
- private async processQueue() {
- if (this.processing || this.queue.length === 0) {
- return;
- }
-
- this.processing = true;
- while (this.queue.length > 0) {
- const request = this.queue.shift();
- if (!request) continue;
- try {
- await request();
- } catch (error) {
- console.error("Error processing request:", error);
- this.queue.unshift(request);
- await this.exponentialBackoff(this.queue.length);
- }
- await this.randomDelay();
- }
- this.processing = false;
- }
-
- private async exponentialBackoff(retryCount: number) {
- const delay = Math.pow(2, retryCount) * 1000;
- await new Promise((resolve) => setTimeout(resolve, delay));
- }
-
- private async randomDelay() {
- const delay = Math.floor(Math.random() * 2000) + 1500;
- await new Promise((resolve) => setTimeout(resolve, delay));
- }
-}
-
-export class ClientBase extends EventEmitter {
- private static _linkedInClient: LinkedInClient;
- protected linkedInClient: LinkedInClient;
- protected runtime: any;
- protected profile: any;
- protected requestQueue: RequestQueue = new RequestQueue();
-
- constructor(runtime: any) {
- super();
- this.runtime = runtime;
-
- if (ClientBase._linkedInClient) {
- this.linkedInClient = ClientBase._linkedInClient;
- } else {
- this.linkedInClient = new LinkedInClient();
- ClientBase._linkedInClient = this.linkedInClient;
- }
- }
-
- async init() {
- const username = this.runtime.getSetting("LINKEDIN_USERNAME");
- const password = this.runtime.getSetting("LINKEDIN_PASSWORD");
-
- if (!username || !password) {
- throw new Error("LinkedIn credentials not configured");
- }
-
- elizaLogger.log("Logging into LinkedIn...");
-
- try {
- await this.linkedInClient.login(username, password);
- this.profile = await this.fetchProfile();
-
- if (this.profile) {
- elizaLogger.log(
- "LinkedIn profile loaded:",
- JSON.stringify(this.profile, null, 2)
- );
- this.runtime.character.linkedInProfile = {
- id: this.profile.id,
- username: this.profile.username,
- fullName: this.profile.fullName,
- headline: this.profile.headline,
- summary: this.profile.summary,
- };
- } else {
- throw new Error("Failed to load LinkedIn profile");
- }
-
- await this.loadInitialState();
- } catch (error) {
- elizaLogger.error("LinkedIn login failed:", error);
- throw error;
- }
- }
-
- async fetchProfile() {
- const cachedProfile = await this.getCachedProfile();
- if (cachedProfile) return cachedProfile;
-
- try {
- const profile = await this.requestQueue.add(async () => {
- const profileData = await this.linkedInClient.getProfile();
- return {
- id: profileData.id,
- username: profileData.username,
- fullName:
- profileData.firstName + " " + profileData.lastName,
- headline: profileData.headline,
- summary: profileData.summary,
- };
- });
-
- await this.cacheProfile(profile);
- return profile;
- } catch (error) {
- console.error("Error fetching LinkedIn profile:", error);
- return undefined;
- }
- }
-
- async loadInitialState() {
- await this.populateConnections();
- await this.populateRecentActivity();
- }
-
- async populateConnections() {
- const connections = await this.requestQueue.add(async () => {
- return await this.linkedInClient.getConnections();
- });
-
- for (const connection of connections) {
- const roomId = stringToUuid(`linkedin-connection-${connection.id}`);
- await this.runtime.ensureConnection(
- stringToUuid(connection.id),
- roomId,
- connection.username,
- connection.fullName,
- "linkedin"
- );
- }
- }
-
- async populateRecentActivity() {
- const activities = await this.requestQueue.add(async () => {
- return await this.linkedInClient.getFeedPosts();
- });
-
- for (const activity of activities) {
- const roomId = stringToUuid(`linkedin-post-${activity.id}`);
- await this.saveActivity(activity, roomId);
- }
- }
-
- private async saveActivity(activity: any, roomId: string) {
- const content = {
- text: activity.text,
- url: activity.url,
- source: "linkedin",
- type: activity.type,
- };
-
- await this.runtime.messageManager.createMemory({
- id: stringToUuid(`${activity.id}-${this.runtime.agentId}`),
- userId:
- activity.authorId === this.profile.id
- ? this.runtime.agentId
- : stringToUuid(activity.authorId),
- content,
- agentId: this.runtime.agentId,
- roomId,
- embedding: getEmbeddingZeroVector(),
- createdAt: activity.timestamp,
- });
- }
-
- private async getCachedProfile() {
- return await this.runtime.cacheManager.get(
- `linkedin/${this.runtime.getSetting("LINKEDIN_USERNAME")}/profile`
- );
- }
-
- private async cacheProfile(profile: any) {
- await this.runtime.cacheManager.set(
- `linkedin/${profile.username}/profile`,
- profile
- );
- }
-}
diff --git a/packages/client-linkedin/src/environment.ts b/packages/client-linkedin/src/environment.ts
deleted file mode 100644
index 8386953b805..00000000000
--- a/packages/client-linkedin/src/environment.ts
+++ /dev/null
@@ -1,33 +0,0 @@
-import { z } from 'zod';
-
-export const linkedInEnvSchema = z.object({
- LINKEDIN_USERNAME: z.string().min(1, 'LinkedIn username is required'),
- LINKEDIN_PASSWORD: z.string().min(1, 'LinkedIn password is required'),
- LINKEDIN_DRY_RUN: z.string().transform(val => val.toLowerCase() === 'true'),
- POST_INTERVAL_MIN: z.string().optional(),
- POST_INTERVAL_MAX: z.string().optional()
-});
-
-export async function validateLinkedInConfig(runtime: any) {
- try {
- const config = {
- LINKEDIN_USERNAME: runtime.getSetting('LINKEDIN_USERNAME') || process.env.LINKEDIN_USERNAME,
- LINKEDIN_PASSWORD: runtime.getSetting('LINKEDIN_PASSWORD') || process.env.LINKEDIN_PASSWORD,
- LINKEDIN_DRY_RUN: runtime.getSetting('LINKEDIN_DRY_RUN') || process.env.LINKEDIN_DRY_RUN,
- POST_INTERVAL_MIN: runtime.getSetting('POST_INTERVAL_MIN') || process.env.POST_INTERVAL_MIN,
- POST_INTERVAL_MAX: runtime.getSetting('POST_INTERVAL_MAX') || process.env.POST_INTERVAL_MAX
- };
-
- return linkedInEnvSchema.parse(config);
- } catch (error) {
- if (error instanceof z.ZodError) {
- const errorMessages = error.errors
- .map(err => `${err.path.join('.')}: ${err.message}`)
- .join('\n');
- throw new Error(
- `LinkedIn configuration validation failed:\n${errorMessages}`
- );
- }
- throw error;
- }
-}
\ No newline at end of file
diff --git a/packages/client-linkedin/src/index.ts b/packages/client-linkedin/src/index.ts
deleted file mode 100644
index 6e1fe400de7..00000000000
--- a/packages/client-linkedin/src/index.ts
+++ /dev/null
@@ -1,37 +0,0 @@
-import { elizaLogger } from '@ai16z/eliza';
-import { ClientBase } from './base';
-import { LinkedInPostClient } from './post';
-import { LinkedInInteractionClient } from './interactions';
-import { validateLinkedInConfig } from './environment';
-
-class LinkedInManager {
- client: ClientBase;
- post: LinkedInPostClient;
- interaction: LinkedInInteractionClient;
-
- constructor(runtime: any) {
- this.client = new ClientBase(runtime);
- this.post = new LinkedInPostClient(this.client, runtime);
- this.interaction = new LinkedInInteractionClient(this.client, runtime);
- }
-}
-
-export const LinkedInClientInterface = {
- async start(runtime: any) {
- await validateLinkedInConfig(runtime);
- elizaLogger.log('LinkedIn client started');
-
- const manager = new LinkedInManager(runtime);
- await manager.client.init();
- await manager.post.start();
- await manager.interaction.start();
-
- return manager;
- },
-
- async stop(runtime: any) {
- elizaLogger.warn('LinkedIn client stop not implemented yet');
- }
-};
-
-export default LinkedInClientInterface;
\ No newline at end of file
diff --git a/packages/client-linkedin/src/interactions.ts b/packages/client-linkedin/src/interactions.ts
deleted file mode 100644
index 1c6b6654bd7..00000000000
--- a/packages/client-linkedin/src/interactions.ts
+++ /dev/null
@@ -1,264 +0,0 @@
-import {
- composeContext,
- generateMessageResponse,
- generateShouldRespond,
- messageCompletionFooter,
- shouldRespondFooter,
- ModelClass,
- stringToUuid,
- elizaLogger
-} from '@ai16z/eliza';
-
-const linkedInMessageTemplate = `{{timeline}}
-
-# Knowledge
-{{knowledge}}
-
-About {{agentName}} (LinkedIn Profile):
-{{bio}}
-{{headline}}
-{{summary}}
-{{postDirections}}
-
-{{providers}}
-
-Recent interactions:
-{{recentInteractions}}
-
-# Task: Generate a professional response in the voice and style of {{agentName}}
-Current Message:
-{{currentMessage}}
-
-Conversation History:
-{{conversationHistory}}
-
-{{actions}}
-
-# Task: Generate a response in the voice and style of {{agentName}}. Include an action, if appropriate. {{actionNames}}:` + messageCompletionFooter;
-
-const linkedInShouldRespondTemplate = `# INSTRUCTIONS: Determine if {{agentName}} should respond to the message and participate in the conversation.
-
-Response options are RESPOND, IGNORE and STOP.
-
-{{agentName}} should:
-- RESPOND to messages that are directly addressed to them
-- RESPOND to professional networking opportunities
-- RESPOND to industry-relevant discussions
-- IGNORE messages that are irrelevant to their professional focus
-- IGNORE spam or promotional content
-- STOP if the conversation is concluded
-- STOP if asked to stop
-
-Recent interactions:
-{{recentInteractions}}
-
-Current Message:
-{{currentMessage}}
-
-Conversation History:
-{{conversationHistory}}
-
-# INSTRUCTIONS: Respond with [RESPOND] if {{agentName}} should respond, [IGNORE] if {{agentName}} should not respond, or [STOP] if {{agentName}} should end the conversation.` + shouldRespondFooter;
-
-export class LinkedInInteractionClient {
- private client: any;
- private runtime: any;
-
- constructor(client: any, runtime: any) {
- this.client = client;
- this.runtime = runtime;
- }
-
- async start() {
- const handleLinkedInInteractionsLoop = () => {
- this.handleLinkedInInteractions();
- setTimeout(
- handleLinkedInInteractionsLoop,
- (Math.floor(Math.random() * (15 - 5 + 1)) + 5) * 60 * 1000
- );
- };
-
- handleLinkedInInteractionsLoop();
- }
-
- async handleLinkedInInteractions() {
- elizaLogger.log('Checking LinkedIn interactions');
-
- try {
- // Check messages
- const messages = await this.client.linkedInClient.getMessages();
- for (const message of messages) {
- await this.handleMessage(message);
- }
-
- // Check post comments
- const posts = await this.client.linkedInClient.getFeedPosts();
- for (const post of posts) {
- if (post.authorId === this.client.profile.id) {
- const comments = await this.client.linkedInClient.getPostComments(post.id);
- for (const comment of comments) {
- await this.handleComment(comment, post);
- }
- }
- }
- } catch (error) {
- elizaLogger.error('Error handling LinkedIn interactions:', error);
- }
- }
-
- private async handleMessage(message: any) {
- if (message.senderId === this.client.profile.id) {
- return;
- }
-
- const roomId = stringToUuid(`linkedin-conversation-${message.conversationId}`);
- const state = await this.runtime.composeState(
- {
- userId: stringToUuid(message.senderId),
- roomId,
- agentId: this.runtime.agentId,
- content: {
- text: message.text,
- action: ''
- }
- },
- {
- currentMessage: message.text,
- conversationHistory: await this.getConversationHistory(message.conversationId)
- }
- );
-
- const shouldRespondContext = composeContext({
- state,
- template: this.runtime.character.templates?.linkedInShouldRespondTemplate || linkedInShouldRespondTemplate
- });
-
- const shouldRespond = await generateShouldRespond({
- runtime: this.runtime,
- context: shouldRespondContext,
- modelClass: ModelClass.MEDIUM
- });
-
- if (shouldRespond !== 'RESPOND') {
- elizaLogger.log('Not responding to message');
- return;
- }
-
- const responseContext = composeContext({
- state,
- template: this.runtime.character.templates?.linkedInMessageTemplate || linkedInMessageTemplate
- });
-
- const response = await generateMessageResponse({
- runtime: this.runtime,
- context: responseContext,
- modelClass: ModelClass.MEDIUM
- });
-
- if (response.text) {
- try {
- await this.client.linkedInClient.sendMessage(message.conversationId, response.text);
-
- await this.runtime.messageManager.createMemory({
- id: stringToUuid(`${Date.now()}-${this.runtime.agentId}`),
- userId: this.runtime.agentId,
- content: {
- text: response.text,
- source: 'linkedin',
- action: response.action
- },
- agentId: this.runtime.agentId,
- roomId,
- createdAt: Date.now()
- });
- } catch (error) {
- elizaLogger.error('Error sending LinkedIn message:', error);
- }
- }
- }
-
- private async handleComment(comment: any, post: any) {
- if (comment.authorId === this.client.profile.id) {
- return;
- }
-
- const roomId = stringToUuid(`linkedin-post-${post.id}`);
- const state = await this.runtime.composeState(
- {
- userId: stringToUuid(comment.authorId),
- roomId,
- agentId: this.runtime.agentId,
- content: {
- text: comment.text,
- action: ''
- }
- },
- {
- currentMessage: comment.text,
- conversationHistory: await this.getPostCommentHistory(post.id)
- }
- );
-
- const shouldRespondContext = composeContext({
- state,
- template: this.runtime.character.templates?.linkedInShouldRespondTemplate || linkedInShouldRespondTemplate
- });
-
- const shouldRespond = await generateShouldRespond({
- runtime: this.runtime,
- context: shouldRespondContext,
- modelClass: ModelClass.MEDIUM
- });
-
- if (shouldRespond !== 'RESPOND') {
- elizaLogger.log('Not responding to comment');
- return;
- }
-
- const responseContext = composeContext({
- state,
- template: this.runtime.character.templates?.linkedInMessageTemplate || linkedInMessageTemplate
- });
-
- const response = await generateMessageResponse({
- runtime: this.runtime,
- context: responseContext,
- modelClass: ModelClass.MEDIUM
- });
-
- if (response.text) {
- try {
- await this.client.linkedInClient.replyToComment(post.id, comment.id, response.text);
-
- await this.runtime.messageManager.createMemory({
- id: stringToUuid(`${Date.now()}-${this.runtime.agentId}`),
- userId: this.runtime.agentId,
- content: {
- text: response.text,
- source: 'linkedin',
- action: response.action
- },
- agentId: this.runtime.agentId,
- roomId,
- createdAt: Date.now()
- });
- } catch (error) {
- elizaLogger.error('Error replying to LinkedIn comment:', error);
- }
- }
- }
-
- private async getConversationHistory(conversationId: string): Promise {
- const messages = await this.client.linkedInClient.getConversationMessages(conversationId);
- return messages.map((msg: any) =>
- `${msg.senderName} (${new Date(msg.timestamp).toLocaleString()}): ${msg.text}`
- ).join('\n\n');
- }
-
- private async getPostCommentHistory(postId: string): Promise {
- const comments = await this.client.linkedInClient.getPostComments(postId);
- return comments.map((comment: any) =>
- `${comment.authorName} (${new Date(comment.timestamp).toLocaleString()}): ${comment.text}`
- ).join('\n\n');
- }
-}
\ No newline at end of file
diff --git a/packages/client-linkedin/src/post.ts b/packages/client-linkedin/src/post.ts
deleted file mode 100644
index f3159faadcf..00000000000
--- a/packages/client-linkedin/src/post.ts
+++ /dev/null
@@ -1,161 +0,0 @@
-import {
- composeContext,
- generateText,
- ModelClass,
- stringToUuid,
- elizaLogger
-} from '@ai16z/eliza';
-
-const linkedInPostTemplate = `{{timeline}}
-
-# Knowledge
-{{knowledge}}
-
-About {{agentName}} (LinkedIn Profile):
-{{bio}}
-{{headline}}
-{{summary}}
-{{postDirections}}
-
-{{providers}}
-
-{{recentPosts}}
-
-{{characterPostExamples}}
-
-# Task: Generate a professional LinkedIn post in the voice and style of {{agentName}}
-Write a post that is {{adjective}} about {{topic}}, from the perspective of {{agentName}}.
-The post should be professional and industry-relevant.
-Do not add commentary or acknowledge this request, just write the post.
-Keep the tone professional but engaging.`;
-
-export class LinkedInPostClient {
- private client: any;
- private runtime: any;
-
- constructor(client: any, runtime: any) {
- this.client = client;
- this.runtime = runtime;
- }
-
- async start(postImmediately = false) {
- if (!this.client.profile) {
- await this.client.init();
- }
-
- const generateNewPostLoop = async () => {
- const lastPost = await this.runtime.cacheManager.get(
- `linkedin/${this.runtime.getSetting('LINKEDIN_USERNAME')}/lastPost`
- );
- const lastPostTimestamp = lastPost?.timestamp ?? 0;
- const minHours = parseInt(this.runtime.getSetting('POST_INTERVAL_MIN')) || 24;
- const maxHours = parseInt(this.runtime.getSetting('POST_INTERVAL_MAX')) || 72;
- const randomHours = Math.floor(Math.random() * (maxHours - minHours + 1)) + minHours;
- const delay = randomHours * 60 * 60 * 1000;
-
- if (Date.now() > lastPostTimestamp + delay) {
- await this.generateNewPost();
- }
-
- setTimeout(() => {
- generateNewPostLoop();
- }, delay);
-
- elizaLogger.log(`Next LinkedIn post scheduled in ${randomHours} hours`);
- };
-
- if (postImmediately) {
- await this.generateNewPost();
- }
-
- generateNewPostLoop();
- }
-
- async generateNewPost() {
- elizaLogger.log('Generating new LinkedIn post');
-
- try {
- const recentPosts = await this.client.linkedInClient.getFeedPosts();
- const formattedPosts = recentPosts.map((post: any) => {
- return `Post ID: ${post.id}
-Author: ${post.author.name}
-Date: ${new Date(post.timestamp).toDateString()}
-
-${post.text}
----`;
- }).join('\n\n');
-
- const topics = this.runtime.character.topics.join(', ');
- const state = await this.runtime.composeState(
- {
- userId: this.runtime.agentId,
- roomId: stringToUuid('linkedin_generate_room'),
- agentId: this.runtime.agentId,
- content: {
- text: topics,
- action: ''
- }
- },
- {
- timeline: formattedPosts,
- headline: this.client.profile.headline,
- summary: this.client.profile.summary
- }
- );
-
- const context = composeContext({
- state,
- template: this.runtime.character.templates?.linkedInPostTemplate || linkedInPostTemplate
- });
-
- elizaLogger.debug('Generate post prompt:\n' + context);
-
- const newPostContent = await generateText({
- runtime: this.runtime,
- context,
- modelClass: ModelClass.SMALL
- });
-
- if (this.runtime.getSetting('LINKEDIN_DRY_RUN') === 'true') {
- elizaLogger.info(`Dry run: would have posted: ${newPostContent}`);
- return;
- }
-
- try {
- elizaLogger.log(`Posting new LinkedIn post:\n${newPostContent}`);
-
- const result = await this.client.requestQueue.add(
- async () => await this.client.linkedInClient.createPost(newPostContent)
- );
-
- await this.runtime.cacheManager.set(
- `linkedin/${this.client.profile.username}/lastPost`,
- {
- id: result.id,
- timestamp: Date.now()
- }
- );
-
- const roomId = stringToUuid(`linkedin-post-${result.id}`);
- await this.runtime.messageManager.createMemory({
- id: stringToUuid(`${result.id}-${this.runtime.agentId}`),
- userId: this.runtime.agentId,
- content: {
- text: newPostContent,
- url: result.url,
- source: 'linkedin'
- },
- agentId: this.runtime.agentId,
- roomId,
- createdAt: Date.now()
- });
-
- elizaLogger.log(`LinkedIn post created: ${result.url}`);
- } catch (error) {
- elizaLogger.error('Error creating LinkedIn post:', error);
- }
- } catch (error) {
- elizaLogger.error('Error generating new LinkedIn post:', error);
- }
- }
-}
\ No newline at end of file
diff --git a/packages/client-linkedin/tsconfig.json b/packages/client-linkedin/tsconfig.json
deleted file mode 100644
index 22414fda087..00000000000
--- a/packages/client-linkedin/tsconfig.json
+++ /dev/null
@@ -1,25 +0,0 @@
-{
- "compilerOptions": {
- "target": "ES2020",
- "module": "CommonJS",
- "lib": [
- "ES2020"
- ],
- "declaration": true,
- "outDir": "./dist",
- "rootDir": "./src",
- "strict": true,
- "esModuleInterop": true,
- "skipLibCheck": true,
- "forceConsistentCasingInFileNames": true,
- "moduleResolution": "node"
- },
- "include": [
- "src"
- ],
- "exclude": [
- "node_modules",
- "dist",
- "test"
- ]
-}
\ No newline at end of file
diff --git a/packages/client-slack/README.md b/packages/client-slack/README.md
new file mode 100644
index 00000000000..9db25536388
--- /dev/null
+++ b/packages/client-slack/README.md
@@ -0,0 +1,166 @@
+# Eliza Slack Client
+
+This package provides Slack integration for the Eliza AI agent.
+
+## Setup Guide
+
+### Prerequisites
+- A Slack workspace where you have permissions to install apps
+- ngrok installed for local development (`brew install ngrok` on macOS)
+- Node.js and pnpm installed
+
+### Step 1: Start ngrok
+1. Open a terminal and start ngrok on port 3069 (or your configured port):
+ ```bash
+ ngrok http 3069
+ ```
+2. Copy the HTTPS URL (e.g., `https://xxxx-xx-xx-xx-xx.ngrok-free.app`)
+3. Keep this terminal open - closing it will invalidate the URL
+
+### Step 2: Create Slack App
+1. Go to [Slack API Apps page](https://api.slack.com/apps)
+2. Click "Create New App"
+3. Choose "From an app manifest"
+4. Select your workspace
+5. Copy this manifest, replacing `YOUR_NGROK_URL` with your ngrok HTTPS URL:
+
+```yaml
+display_information:
+ name: eve
+ description: Eve ai16z
+ background_color: "#143187"
+features:
+ app_home:
+ home_tab_enabled: true
+ messages_tab_enabled: false
+ messages_tab_read_only_enabled: false
+ bot_user:
+ display_name: eve
+ always_online: false
+oauth_config:
+ scopes:
+ bot:
+ - app_mentions:read
+ - channels:history
+ - channels:join
+ - channels:read
+ - chat:write
+ - files:read
+ - files:write
+ - groups:history
+ - groups:read
+ - im:history
+ - im:read
+ - im:write
+ - mpim:history
+ - mpim:read
+ - mpim:write
+ - users:read
+settings:
+ event_subscriptions:
+ request_url: YOUR_NGROK_URL/slack/events
+ bot_events:
+ - app_mention
+ - message.channels
+ - message.groups
+ - message.im
+ - message.mpim
+ - file_shared
+ interactivity:
+ is_enabled: true
+ request_url: YOUR_NGROK_URL/slack/interactions
+ org_deploy_enabled: false
+ socket_mode_enabled: false
+ token_rotation_enabled: false
+```
+
+6. Click "Create"
+7. On the "Basic Information" page, scroll down to "App Credentials"
+8. Copy all the credentials - you'll need them in Step 3
+
+### Step 3: Configure Environment Variables
+1. Create or edit `.env` file in your project root:
+ ```bash
+ SLACK_APP_ID= # From Basic Information > App Credentials > App ID
+ SLACK_CLIENT_ID= # From Basic Information > App Credentials > Client ID
+ SLACK_CLIENT_SECRET= # From Basic Information > App Credentials > Client Secret
+ SLACK_SIGNING_SECRET= # From Basic Information > App Credentials > Signing Secret
+ SLACK_BOT_TOKEN= # From OAuth & Permissions > Bot User OAuth Token (starts with xoxb-)
+ SLACK_VERIFICATION_TOKEN= # From Basic Information > App Credentials > Verification Token
+ SLACK_SERVER_PORT=3069 # Must match the port you used with ngrok
+ ```
+
+### Step 4: Install the App
+1. In your Slack App settings, go to "Install App"
+2. Click "Install to Workspace"
+3. Review the permissions and click "Allow"
+
+### Step 5: Verify Installation
+1. Start your Eliza server
+2. Check the logs for successful connection
+3. Test the bot:
+ - In Slack, invite the bot to a channel: `/invite @eve`
+ - Try mentioning the bot: `@eve hello`
+ - Check your server logs for event reception
+
+### Common Issues and Solutions
+
+#### URL Verification Failed
+- Make sure ngrok is running and the URL in your app settings matches exactly
+- Check that the `/slack/events` endpoint is accessible
+- Verify your environment variables are set correctly
+
+#### Bot Not Responding
+1. Check server logs for incoming events
+2. Verify the bot is in the channel
+3. Ensure all required scopes are granted
+4. Try reinstalling the app to refresh permissions
+
+#### Messages Not Received
+1. Verify Event Subscriptions are enabled
+2. Check the Request URL is correct and verified
+3. Confirm all bot events are subscribed
+4. Ensure the bot token starts with `xoxb-`
+
+### Updating ngrok URL
+If you restart ngrok, you'll get a new URL. You'll need to:
+1. Copy the new ngrok HTTPS URL
+2. Update the Request URLs in your Slack App settings:
+ - Event Subscriptions > Request URL
+ - Interactivity & Shortcuts > Request URL
+3. Wait for URL verification to complete
+
+### Security Notes
+- Never commit your `.env` file or tokens to version control
+- Rotate your tokens if they're ever exposed
+- Use HTTPS URLs only for Request URLs
+- Keep your ngrok and server running while testing
+
+## Development
+
+### Local Testing
+1. Start ngrok: `ngrok http 3069`
+2. Update Slack App URLs with new ngrok URL
+3. Start the server: `pnpm start`
+4. Monitor logs for events and errors
+
+### Debugging
+Enable detailed logging by setting:
+```bash
+DEBUG=eliza:*
+```
+
+### Adding New Features
+1. Update the manifest if adding new scopes
+2. Reinstall the app to apply new permissions
+3. Update documentation for any new environment variables
+
+## Support
+For issues or questions:
+1. Check the Common Issues section above
+2. Review server logs for errors
+3. Verify all setup steps are completed
+4. Open an issue with:
+ - Error messages
+ - Server logs
+ - Steps to reproduce
\ No newline at end of file
diff --git a/packages/client-slack/eslint.config.mjs b/packages/client-slack/eslint.config.mjs
new file mode 100644
index 00000000000..754f4fc4e65
--- /dev/null
+++ b/packages/client-slack/eslint.config.mjs
@@ -0,0 +1,3 @@
+import eslintGlobalConfig from "../../eslint.config.mjs";
+
+export default [...eslintGlobalConfig];
\ No newline at end of file
diff --git a/packages/client-slack/jest.config.js b/packages/client-slack/jest.config.js
new file mode 100644
index 00000000000..c3bab4bd9ce
--- /dev/null
+++ b/packages/client-slack/jest.config.js
@@ -0,0 +1,22 @@
+/** @type {import('ts-jest').JestConfigWithTsJest} */
+module.exports = {
+ preset: 'ts-jest',
+ testEnvironment: 'node',
+ roots: ['/src'],
+ testMatch: ['**/__tests__/**/*.ts', '**/?(*.)+(spec|test).ts'],
+ setupFilesAfterEnv: ['/src/tests/setup.ts'],
+ collectCoverageFrom: [
+ 'src/**/*.ts',
+ '!src/tests/**',
+ '!src/examples/**',
+ '!src/**/*.d.ts'
+ ],
+ coverageThreshold: {
+ global: {
+ branches: 80,
+ functions: 80,
+ lines: 80,
+ statements: 80
+ }
+ }
+};
\ No newline at end of file
diff --git a/packages/client-slack/package.json b/packages/client-slack/package.json
new file mode 100644
index 00000000000..4e0f503fafd
--- /dev/null
+++ b/packages/client-slack/package.json
@@ -0,0 +1,45 @@
+{
+ "name": "@ai16z/client-slack",
+ "version": "0.1.0",
+ "description": "Slack client plugin for Eliza framework",
+ "main": "dist/index.js",
+ "type": "module",
+ "types": "dist/index.d.ts",
+ "scripts": {
+ "build": "tsup src/index.ts --format esm --dts",
+ "test": "jest",
+ "lint": "eslint --fix --cache .",
+ "clean": "rimraf dist",
+ "dev": "tsup src/index.ts --watch",
+ "example": "ts-node src/examples/standalone-example.ts",
+ "example:attachment": "ts-node src/examples/standalone-attachment.ts",
+ "example:summarize": "ts-node src/examples/standalone-summarize.ts",
+ "example:transcribe": "ts-node src/examples/standalone-transcribe.ts"
+ },
+ "dependencies": {
+ "@ai16z/eliza": "workspace:*",
+ "@ffmpeg-installer/ffmpeg": "^1.1.0",
+ "@slack/events-api": "^3.0.1",
+ "@slack/web-api": "^6.8.1",
+ "body-parser": "^1.20.2",
+ "dotenv": "^16.0.3",
+ "express": "^4.18.2",
+ "fluent-ffmpeg": "^2.1.2",
+ "node-fetch": "^2.6.9"
+ },
+ "devDependencies": {
+ "@types/express": "^4.17.21",
+ "@types/fluent-ffmpeg": "^2.1.24",
+ "@types/jest": "^29.5.0",
+ "@types/node": "^18.15.11",
+ "jest": "^29.5.0",
+ "rimraf": "^5.0.0",
+ "ts-jest": "^29.1.0",
+ "ts-node": "^10.9.1",
+ "tsup": "^8.3.5",
+ "typescript": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+}
diff --git a/packages/client-slack/src/actions/chat_with_attachments.ts b/packages/client-slack/src/actions/chat_with_attachments.ts
new file mode 100644
index 00000000000..1d2e2c3f565
--- /dev/null
+++ b/packages/client-slack/src/actions/chat_with_attachments.ts
@@ -0,0 +1,285 @@
+import {
+ composeContext,
+ generateText,
+ trimTokens,
+ parseJSONObjectFromText,
+} from "@ai16z/eliza";
+import { models } from "@ai16z/eliza";
+import {
+ Action,
+ ActionExample,
+ Content,
+ HandlerCallback,
+ Handler,
+ IAgentRuntime,
+ Memory,
+ ModelClass,
+ State,
+} from "@ai16z/eliza";
+
+export const summarizationTemplate = `# Summarized so far (we are adding to this)
+{{currentSummary}}
+
+# Current attachments we are summarizing
+{{attachmentsWithText}}
+
+Summarization objective: {{objective}}
+
+# Instructions: Summarize the attachments. Return the summary. Do not acknowledge this request, just summarize and continue the existing summary if there is one. Capture any important details based on the objective. Only respond with the new summary text.`;
+
+export const attachmentIdsTemplate = `# Messages we are summarizing
+{{recentMessages}}
+
+# Instructions: {{senderName}} is requesting a summary of specific attachments. Your goal is to determine their objective, along with the list of attachment IDs to summarize.
+The "objective" is a detailed description of what the user wants to summarize based on the conversation.
+The "attachmentIds" is an array of attachment IDs that the user wants to summarize. If not specified, default to including all attachments from the conversation.
+
+Your response must be formatted as a JSON block with this structure:
+\`\`\`json
+{
+ "objective": "",
+ "attachmentIds": ["", "", ...]
+}
+\`\`\`
+`;
+
+const getAttachmentIds = async (
+ runtime: IAgentRuntime,
+ message: Memory,
+ state: State
+): Promise<{ objective: string; attachmentIds: string[] } | null> => {
+ const context = composeContext({
+ state,
+ template: attachmentIdsTemplate,
+ });
+
+ for (let i = 0; i < 5; i++) {
+ const response = await generateText({
+ runtime,
+ context,
+ modelClass: ModelClass.SMALL,
+ });
+
+ const parsedResponse = parseJSONObjectFromText(response) as {
+ objective: string;
+ attachmentIds: string[];
+ } | null;
+
+ if (parsedResponse?.objective && parsedResponse?.attachmentIds) {
+ return parsedResponse;
+ }
+ }
+ return null;
+};
+
+const summarizeAction: Action = {
+ name: "CHAT_WITH_ATTACHMENTS",
+ similes: [
+ "CHAT_WITH_ATTACHMENT",
+ "SUMMARIZE_FILES",
+ "SUMMARIZE_FILE",
+ "SUMMARIZE_ATACHMENT",
+ "CHAT_WITH_PDF",
+ "ATTACHMENT_SUMMARY",
+ "RECAP_ATTACHMENTS",
+ "SUMMARIZE_FILE",
+ "SUMMARIZE_VIDEO",
+ "SUMMARIZE_AUDIO",
+ "SUMMARIZE_IMAGE",
+ "SUMMARIZE_DOCUMENT",
+ "SUMMARIZE_LINK",
+ "ATTACHMENT_SUMMARY",
+ "FILE_SUMMARY",
+ ],
+ description:
+ "Answer a user request informed by specific attachments based on their IDs. If a user asks to chat with a PDF, or wants more specific information about a link or video or anything else they've attached, this is the action to use.",
+ validate: async (
+ runtime: IAgentRuntime,
+ message: Memory,
+ _state: State | undefined
+ ): Promise => {
+ if (message.content.source !== "slack") {
+ return false;
+ }
+
+ const keywords: string[] = [
+ "attachment",
+ "summary",
+ "summarize",
+ "research",
+ "pdf",
+ "video",
+ "audio",
+ "image",
+ "document",
+ "link",
+ "file",
+ "attachment",
+ "summarize",
+ "code",
+ "report",
+ "write",
+ "details",
+ "information",
+ "talk",
+ "chat",
+ "read",
+ "listen",
+ "watch",
+ ];
+
+ return keywords.some((keyword) =>
+ message.content.text.toLowerCase().includes(keyword.toLowerCase())
+ );
+ },
+ handler: (async (
+ runtime: IAgentRuntime,
+ message: Memory,
+ state: State | undefined,
+ options: any,
+ callback: HandlerCallback
+ ): Promise => {
+ const currentState =
+ state ?? ((await runtime.composeState(message)) as State);
+
+ const callbackData: Content = {
+ text: "",
+ action: "CHAT_WITH_ATTACHMENTS_RESPONSE",
+ source: message.content.source,
+ attachments: [],
+ };
+
+ const attachmentData = await getAttachmentIds(
+ runtime,
+ message,
+ currentState
+ );
+ if (!attachmentData) {
+ console.error("Couldn't get attachment IDs from message");
+ await callback(callbackData);
+ return callbackData;
+ }
+
+ const { objective, attachmentIds } = attachmentData;
+
+ const attachments = currentState.recentMessagesData
+ .filter(
+ (msg) =>
+ msg.content.attachments &&
+ msg.content.attachments.length > 0
+ )
+ .flatMap((msg) => msg.content.attachments)
+ .filter((attachment) => {
+ if (!attachment) return false;
+ return (
+ attachmentIds
+ .map((attch) => attch.toLowerCase().slice(0, 5))
+ .includes(attachment.id.toLowerCase().slice(0, 5)) ||
+ attachmentIds.some((id) => {
+ const attachmentId = id.toLowerCase().slice(0, 5);
+ return attachment.id
+ .toLowerCase()
+ .includes(attachmentId);
+ })
+ );
+ });
+
+ const attachmentsWithText = attachments
+ .map((attachment) => {
+ if (!attachment) return "";
+ return `# ${attachment.title}\n${attachment.text}`;
+ })
+ .filter((text) => text !== "")
+ .join("\n\n");
+
+ let currentSummary = "";
+
+ const model = models[runtime.character.modelProvider];
+ const chunkSize = model.settings.maxOutputTokens;
+
+ currentState.attachmentsWithText = attachmentsWithText;
+ currentState.objective = objective;
+
+ const context = composeContext({
+ state: currentState,
+ template: trimTokens(
+ summarizationTemplate,
+ chunkSize + 500,
+ "gpt-4o-mini"
+ ),
+ });
+
+ const summary = await generateText({
+ runtime,
+ context,
+ modelClass: ModelClass.SMALL,
+ });
+
+ currentSummary = currentSummary + "\n" + summary;
+
+ if (!currentSummary) {
+ console.error("No summary found!");
+ await callback(callbackData);
+ return callbackData;
+ }
+
+ callbackData.text = currentSummary.trim();
+
+ if (
+ callbackData.text &&
+ (currentSummary.trim()?.split("\n").length < 4 ||
+ currentSummary.trim()?.split(" ").length < 100)
+ ) {
+ callbackData.text = `Here is the summary:
+\`\`\`md
+${currentSummary.trim()}
+\`\`\`
+`;
+ await callback(callbackData);
+ } else if (currentSummary.trim()) {
+ const summaryFilename = `content/summary_${Date.now()}`;
+ await runtime.cacheManager.set(summaryFilename, currentSummary);
+
+ callbackData.text = `I've attached the summary of the requested attachments as a text file.`;
+ await callback(callbackData, [summaryFilename]);
+ } else {
+ await callback(callbackData);
+ }
+
+ return callbackData;
+ }) as Handler,
+ examples: [
+ [
+ {
+ user: "{{user1}}",
+ content: {
+ text: "Can you summarize the PDF I just shared?",
+ },
+ },
+ {
+ user: "{{user2}}",
+ content: {
+ text: "I'll analyze the PDF and provide a summary for you.",
+ action: "CHAT_WITH_ATTACHMENTS",
+ },
+ },
+ ],
+ [
+ {
+ user: "{{user1}}",
+ content: {
+ text: "Could you look at these documents and tell me what they're about?",
+ },
+ },
+ {
+ user: "{{user2}}",
+ content: {
+ text: "I'll review the documents and provide a summary of their contents.",
+ action: "CHAT_WITH_ATTACHMENTS",
+ },
+ },
+ ],
+ ] as ActionExample[][],
+};
+
+export default summarizeAction;
diff --git a/packages/client-slack/src/actions/send-message.action.ts b/packages/client-slack/src/actions/send-message.action.ts
new file mode 100644
index 00000000000..93996b529f2
--- /dev/null
+++ b/packages/client-slack/src/actions/send-message.action.ts
@@ -0,0 +1,60 @@
+import { SlackClientContext, SlackMessage } from '../types/slack-types';
+
+// Cache to store recently sent messages
+const recentMessages = new Map();
+const MESSAGE_CACHE_TTL = 5000; // 5 seconds TTL
+
+export class SendMessageAction {
+ constructor(private context: SlackClientContext) {}
+
+ private cleanupOldMessages() {
+ const now = Date.now();
+ for (const [key, value] of recentMessages.entries()) {
+ if (now - value.timestamp > MESSAGE_CACHE_TTL) {
+ recentMessages.delete(key);
+ }
+ }
+ }
+
+ private isDuplicate(message: SlackMessage): boolean {
+ this.cleanupOldMessages();
+
+ // Create a unique key for the message
+ const messageKey = `${message.channelId}:${message.threadTs || 'main'}:${message.text}`;
+
+ // Check if we've seen this message recently
+ const recentMessage = recentMessages.get(messageKey);
+ if (recentMessage) {
+ return true;
+ }
+
+ // Store the new message
+ recentMessages.set(messageKey, {
+ text: message.text,
+ timestamp: Date.now()
+ });
+
+ return false;
+ }
+
+ public async execute(message: SlackMessage): Promise {
+ try {
+ // Skip duplicate messages
+ if (this.isDuplicate(message)) {
+ console.debug('Skipping duplicate message:', message.text);
+ return true; // Return true to indicate "success" since we're intentionally skipping
+ }
+
+ const result = await this.context.client.chat.postMessage({
+ channel: message.channelId,
+ text: message.text,
+ thread_ts: message.threadTs,
+ });
+
+ return result.ok === true;
+ } catch (error) {
+ console.error('Failed to send message:', error);
+ return false;
+ }
+ }
+}
\ No newline at end of file
diff --git a/packages/client-slack/src/actions/summarize_conversation.ts b/packages/client-slack/src/actions/summarize_conversation.ts
new file mode 100644
index 00000000000..f99a77b3a39
--- /dev/null
+++ b/packages/client-slack/src/actions/summarize_conversation.ts
@@ -0,0 +1,432 @@
+import {
+ composeContext,
+ generateText,
+ splitChunks,
+ trimTokens,
+ parseJSONObjectFromText,
+} from "@ai16z/eliza";
+import { models } from "@ai16z/eliza";
+import { getActorDetails } from "@ai16z/eliza";
+import {
+ Action,
+ ActionExample,
+ Content,
+ HandlerCallback,
+ IAgentRuntime,
+ Media,
+ Memory,
+ ModelClass,
+ State,
+ elizaLogger,
+} from "@ai16z/eliza";
+import { ISlackService, SLACK_SERVICE_TYPE } from "../types/slack-types";
+
+export const summarizationTemplate = `# Summarized so far (we are adding to this)
+{{currentSummary}}
+
+# Current conversation chunk we are summarizing (includes attachments)
+{{memoriesWithAttachments}}
+
+Summarization objective: {{objective}}
+
+# Instructions: Summarize the conversation so far. Return the summary. Do not acknowledge this request, just summarize and continue the existing summary if there is one. Capture any important details to the objective. Only respond with the new summary text.
+Your response should be extremely detailed and include any and all relevant information.`;
+
+export const dateRangeTemplate = `# Messages we are summarizing (the conversation is continued after this)
+{{recentMessages}}
+
+# Instructions: {{senderName}} is requesting a summary of the conversation. Your goal is to determine their objective, along with the range of dates that their request covers.
+The "objective" is a detailed description of what the user wants to summarize based on the conversation. If they just ask for a general summary, you can either base it off the conversation if the summary range is very recent, or set the object to be general, like "a detailed summary of the conversation between all users".
+
+The "start" and "end" are the range of dates that the user wants to summarize, relative to the current time. The format MUST be a number followed by a unit, like:
+- "5 minutes ago"
+- "2 hours ago"
+- "1 day ago"
+- "30 seconds ago"
+
+For example:
+\`\`\`json
+{
+ "objective": "a detailed summary of the conversation between all users",
+ "start": "2 hours ago",
+ "end": "0 minutes ago"
+}
+\`\`\`
+
+If the user asks for "today", use "24 hours ago" as start and "0 minutes ago" as end.
+If no time range is specified, default to "2 hours ago" for start and "0 minutes ago" for end.
+`;
+
+const getDateRange = async (
+ runtime: IAgentRuntime,
+ message: Memory,
+ state: State
+): Promise<{ objective: string; start: number; end: number } | undefined> => {
+ state = (await runtime.composeState(message)) as State;
+
+ const context = composeContext({
+ state,
+ template: dateRangeTemplate,
+ });
+
+ for (let i = 0; i < 5; i++) {
+ const response = await generateText({
+ runtime,
+ context,
+ modelClass: ModelClass.SMALL,
+ });
+
+ const parsedResponse = parseJSONObjectFromText(response) as {
+ objective: string;
+ start: string | number;
+ end: string | number;
+ } | null;
+
+ if (
+ parsedResponse?.objective &&
+ parsedResponse?.start &&
+ parsedResponse?.end
+ ) {
+ // Parse time strings like "5 minutes ago", "2 hours ago", etc.
+ const parseTimeString = (timeStr: string): number | null => {
+ const match = timeStr.match(
+ /^(\d+)\s+(second|minute|hour|day)s?\s+ago$/i
+ );
+ if (!match) return null;
+
+ const [_, amount, unit] = match;
+ const value = parseInt(amount);
+
+ if (isNaN(value)) return null;
+
+ const multipliers: { [key: string]: number } = {
+ second: 1000,
+ minute: 60 * 1000,
+ hour: 60 * 60 * 1000,
+ day: 24 * 60 * 60 * 1000,
+ };
+
+ const multiplier = multipliers[unit.toLowerCase()];
+ if (!multiplier) return null;
+
+ return value * multiplier;
+ };
+
+ const startTime = parseTimeString(parsedResponse.start as string);
+ const endTime = parseTimeString(parsedResponse.end as string);
+
+ if (startTime === null || endTime === null) {
+ elizaLogger.error(
+ "Invalid time format in response",
+ parsedResponse
+ );
+ continue;
+ }
+
+ return {
+ objective: parsedResponse.objective,
+ start: Date.now() - startTime,
+ end: Date.now() - endTime,
+ };
+ }
+ }
+
+ return undefined;
+};
+
+const summarizeAction: Action = {
+ name: "SUMMARIZE_CONVERSATION",
+ similes: [
+ "RECAP",
+ "RECAP_CONVERSATION",
+ "SUMMARIZE_CHAT",
+ "SUMMARIZATION",
+ "CHAT_SUMMARY",
+ "CONVERSATION_SUMMARY",
+ ],
+ description: "Summarizes the conversation and attachments.",
+ validate: async (
+ _runtime: IAgentRuntime,
+ message: Memory,
+ _state: State | undefined
+ ): Promise => {
+ if (message.content.source !== "slack") {
+ return false;
+ }
+
+ const keywords: string[] = [
+ "summarize",
+ "summarization",
+ "summary",
+ "recap",
+ "report",
+ "overview",
+ "review",
+ "rundown",
+ "wrap-up",
+ "brief",
+ "debrief",
+ "abstract",
+ "synopsis",
+ "outline",
+ "digest",
+ "abridgment",
+ "condensation",
+ "encapsulation",
+ "essence",
+ "gist",
+ "main points",
+ "key points",
+ "key takeaways",
+ "bulletpoint",
+ "highlights",
+ "tldr",
+ "tl;dr",
+ "in a nutshell",
+ "bottom line",
+ "long story short",
+ "sum up",
+ "sum it up",
+ "short version",
+ "bring me up to speed",
+ "catch me up",
+ ];
+
+ return keywords.some((keyword) =>
+ message.content.text.toLowerCase().includes(keyword.toLowerCase())
+ );
+ },
+ handler: async (
+ runtime: IAgentRuntime,
+ message: Memory,
+ state: State,
+ _options: any,
+ callback: HandlerCallback
+ ): Promise => {
+ const currentState = (await runtime.composeState(message)) as State;
+
+ const callbackData: Content = {
+ text: "",
+ action: "SUMMARIZATION_RESPONSE",
+ source: message.content.source,
+ attachments: [],
+ };
+
+ // 1. Extract date range from the message
+ const dateRange = await getDateRange(runtime, message, currentState);
+ if (!dateRange) {
+ elizaLogger.error("Couldn't determine date range from message");
+ callbackData.text =
+ "I couldn't determine the time range to summarize. Please try asking for a specific period like 'last hour' or 'today'.";
+ await callback(callbackData);
+ return callbackData;
+ }
+
+ const { objective, start, end } = dateRange;
+
+ // 2. Get memories from the database
+ const memories = await runtime.messageManager.getMemories({
+ roomId: message.roomId,
+ start,
+ end,
+ count: 10000,
+ unique: false,
+ });
+
+ if (!memories || memories.length === 0) {
+ callbackData.text =
+ "I couldn't find any messages in that time range to summarize.";
+ await callback(callbackData);
+ return callbackData;
+ }
+
+ const actors = await getActorDetails({
+ runtime: runtime as IAgentRuntime,
+ roomId: message.roomId,
+ });
+
+ const actorMap = new Map(actors.map((actor) => [actor.id, actor]));
+
+ const formattedMemories = memories
+ .map((memory) => {
+ const actor = actorMap.get(memory.userId);
+ const userName =
+ actor?.name || actor?.username || "Unknown User";
+ const attachments = memory.content.attachments
+ ?.map((attachment: Media) => {
+ if (!attachment) return "";
+ return `---\nAttachment: ${attachment.id}\n${attachment.description || ""}\n${attachment.text || ""}\n---`;
+ })
+ .filter((text) => text !== "")
+ .join("\n");
+ return `${userName}: ${memory.content.text}\n${attachments || ""}`;
+ })
+ .join("\n");
+
+ let currentSummary = "";
+
+ const model = models[runtime.character.modelProvider];
+ const chunkSize = model.settings.maxOutputTokens;
+
+ const chunks = await splitChunks(formattedMemories, chunkSize, 0);
+
+ currentState.memoriesWithAttachments = formattedMemories;
+ currentState.objective = objective;
+
+ // Only process one chunk at a time and stop after getting a valid summary
+ for (let i = 0; i < chunks.length; i++) {
+ const chunk = chunks[i];
+ currentState.currentSummary = currentSummary;
+ currentState.currentChunk = chunk;
+
+ const context = composeContext({
+ state: currentState,
+ template: trimTokens(
+ summarizationTemplate,
+ chunkSize + 500,
+ "gpt-4o-mini"
+ ),
+ });
+
+ const summary = await generateText({
+ runtime,
+ context,
+ modelClass: ModelClass.SMALL,
+ });
+
+ if (summary) {
+ currentSummary = currentSummary + "\n" + summary;
+ break; // Stop after getting first valid summary
+ }
+ }
+
+ if (!currentSummary.trim()) {
+ callbackData.text =
+ "I wasn't able to generate a summary of the conversation.";
+ await callback(callbackData);
+ return callbackData;
+ }
+
+ // Format dates consistently
+ const formatDate = (timestamp: number) => {
+ const date = new Date(timestamp);
+ const pad = (n: number) => (n < 10 ? `0${n}` : n);
+ return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ${pad(date.getHours())}:${pad(date.getMinutes())}`;
+ };
+
+ try {
+ // Get the user's name for the summary header
+ const requestingUser = actorMap.get(message.userId);
+ const userName =
+ requestingUser?.name ||
+ requestingUser?.username ||
+ "Unknown User";
+
+ const summaryContent = `Summary of conversation from ${formatDate(start)} to ${formatDate(end)}
+
+Here is a detailed summary of the conversation between ${userName} and ${runtime.character.name}:\n\n${currentSummary.trim()}`;
+
+ // If summary is long, upload as a file
+ if (summaryContent.length > 1000) {
+ const summaryFilename = `summary_${Date.now()}.txt`;
+ elizaLogger.debug("Uploading summary file to Slack...");
+
+ try {
+ // Save file content
+ await runtime.cacheManager.set(
+ summaryFilename,
+ summaryContent
+ );
+
+ // Get the Slack service from runtime
+ const slackService = runtime.getService(
+ SLACK_SERVICE_TYPE
+ ) as ISlackService;
+ if (!slackService?.client) {
+ elizaLogger.error(
+ "Slack service not found or not properly initialized"
+ );
+ throw new Error("Slack service not found");
+ }
+
+ // Upload file using Slack's API
+ elizaLogger.debug(
+ `Uploading file ${summaryFilename} to channel ${message.roomId}`
+ );
+ const uploadResult = await slackService.client.files.upload(
+ {
+ channels: message.roomId,
+ filename: summaryFilename,
+ title: "Conversation Summary",
+ content: summaryContent,
+ initial_comment: `I've created a summary of the conversation from ${formatDate(start)} to ${formatDate(end)}.`,
+ }
+ );
+
+ if (uploadResult.ok) {
+ elizaLogger.success(
+ "Successfully uploaded summary file to Slack"
+ );
+ callbackData.text = `I've created a summary of the conversation from ${formatDate(start)} to ${formatDate(end)}. You can find it in the thread above.`;
+ } else {
+ elizaLogger.error(
+ "Failed to upload file to Slack:",
+ uploadResult.error
+ );
+ throw new Error("Failed to upload file to Slack");
+ }
+ } catch (error) {
+ elizaLogger.error("Error uploading summary file:", error);
+ // Fallback to sending as a message
+ callbackData.text = summaryContent;
+ }
+ } else {
+ // For shorter summaries, just send as a message
+ callbackData.text = summaryContent;
+ }
+
+ await callback(callbackData);
+ return callbackData;
+ } catch (error) {
+ elizaLogger.error("Error in summary generation:", error);
+ callbackData.text =
+ "I encountered an error while generating the summary. Please try again.";
+ await callback(callbackData);
+ return callbackData;
+ }
+ },
+ examples: [
+ [
+ {
+ user: "{{user1}}",
+ content: {
+ text: "Can you give me a detailed report on what we're talking about?",
+ },
+ },
+ {
+ user: "{{user2}}",
+ content: {
+ text: "I'll analyze the conversation and provide a summary for you.",
+ action: "SUMMARIZE_CONVERSATION",
+ },
+ },
+ ],
+ [
+ {
+ user: "{{user1}}",
+ content: {
+ text: "Please summarize our discussion from the last hour, including any shared files.",
+ },
+ },
+ {
+ user: "{{user2}}",
+ content: {
+ text: "I'll review the conversation and shared content to create a comprehensive summary.",
+ action: "SUMMARIZE_CONVERSATION",
+ },
+ },
+ ],
+ ] as ActionExample[][],
+};
+
+export default summarizeAction;
diff --git a/packages/client-slack/src/actions/transcribe_media.ts b/packages/client-slack/src/actions/transcribe_media.ts
new file mode 100644
index 00000000000..b1801b855b7
--- /dev/null
+++ b/packages/client-slack/src/actions/transcribe_media.ts
@@ -0,0 +1,217 @@
+import {
+ composeContext,
+ generateText,
+ parseJSONObjectFromText,
+} from "@ai16z/eliza";
+import {
+ Action,
+ ActionExample,
+ Content,
+ HandlerCallback,
+ Handler,
+ IAgentRuntime,
+ Memory,
+ ModelClass,
+ State,
+} from "@ai16z/eliza";
+
+export const transcriptionTemplate = `# Transcription of media file
+{{mediaTranscript}}
+
+# Instructions: Return only the full transcript of the media file without any additional context or commentary.`;
+
+export const mediaAttachmentIdTemplate = `# Messages we are transcribing
+{{recentMessages}}
+
+# Instructions: {{senderName}} is requesting a transcription of a specific media file (audio or video). Your goal is to determine the ID of the attachment they want transcribed.
+The "attachmentId" is the ID of the media file attachment that the user wants transcribed. If not specified, return null.
+
+Your response must be formatted as a JSON block with this structure:
+\`\`\`json
+{
+ "attachmentId": ""
+}
+\`\`\`
+`;
+
+const getMediaAttachmentId = async (
+ runtime: IAgentRuntime,
+ message: Memory,
+ state: State
+): Promise => {
+ const context = composeContext({
+ state,
+ template: mediaAttachmentIdTemplate,
+ });
+
+ for (let i = 0; i < 5; i++) {
+ const response = await generateText({
+ runtime,
+ context,
+ modelClass: ModelClass.SMALL,
+ });
+
+ const parsedResponse = parseJSONObjectFromText(response) as {
+ attachmentId: string;
+ } | null;
+
+ if (parsedResponse?.attachmentId) {
+ return parsedResponse.attachmentId;
+ }
+ }
+ return null;
+};
+
+const transcribeMediaAction: Action = {
+ name: "TRANSCRIBE_MEDIA",
+ similes: [
+ "TRANSCRIBE_AUDIO",
+ "TRANSCRIBE_VIDEO",
+ "MEDIA_TRANSCRIPT",
+ "VIDEO_TRANSCRIPT",
+ "AUDIO_TRANSCRIPT",
+ ],
+ description:
+ "Transcribe the full text of an audio or video file that the user has attached.",
+ validate: async (
+ _runtime: IAgentRuntime,
+ message: Memory,
+ _state: State | undefined
+ ): Promise => {
+ if (message.content.source !== "slack") {
+ return false;
+ }
+
+ const keywords: string[] = [
+ "transcribe",
+ "transcript",
+ "audio",
+ "video",
+ "media",
+ "youtube",
+ "meeting",
+ "recording",
+ "podcast",
+ "call",
+ "conference",
+ "interview",
+ "speech",
+ "lecture",
+ "presentation",
+ ];
+ return keywords.some((keyword) =>
+ message.content.text.toLowerCase().includes(keyword.toLowerCase())
+ );
+ },
+ handler: (async (
+ runtime: IAgentRuntime,
+ message: Memory,
+ state: State | undefined,
+ _options: any,
+ callback: HandlerCallback
+ ): Promise => {
+ const currentState = (await runtime.composeState(message)) as State;
+
+ const callbackData: Content = {
+ text: "",
+ action: "TRANSCRIBE_MEDIA_RESPONSE",
+ source: message.content.source,
+ attachments: [],
+ };
+
+ const attachmentId = await getMediaAttachmentId(
+ runtime,
+ message,
+ currentState
+ );
+ if (!attachmentId) {
+ console.error("Couldn't get media attachment ID from message");
+ await callback(callbackData);
+ return callbackData;
+ }
+
+ const attachment = currentState.recentMessagesData
+ .filter(
+ (msg) =>
+ msg.content.attachments &&
+ msg.content.attachments.length > 0
+ )
+ .flatMap((msg) => msg.content.attachments)
+ .find((attachment) => {
+ if (!attachment) return false;
+ return (
+ attachment.id.toLowerCase() === attachmentId.toLowerCase()
+ );
+ });
+
+ if (!attachment) {
+ console.error(`Couldn't find attachment with ID ${attachmentId}`);
+ await callback(callbackData);
+ return callbackData;
+ }
+
+ const mediaTranscript = attachment.text || "";
+ callbackData.text = mediaTranscript.trim();
+
+ if (
+ callbackData.text &&
+ (callbackData.text?.split("\n").length < 4 ||
+ callbackData.text?.split(" ").length < 100)
+ ) {
+ callbackData.text = `Here is the transcript:
+\`\`\`md
+${mediaTranscript.trim()}
+\`\`\`
+`;
+ await callback(callbackData);
+ } else if (callbackData.text) {
+ const transcriptFilename = `content/transcript_${Date.now()}`;
+ await runtime.cacheManager.set(
+ transcriptFilename,
+ callbackData.text
+ );
+
+ callbackData.text = `I've attached the transcript as a text file.`;
+ await callback(callbackData, [transcriptFilename]);
+ } else {
+ console.warn("Empty response from transcribe media action");
+ await callback(callbackData);
+ }
+
+ return callbackData;
+ }) as Handler,
+ examples: [
+ [
+ {
+ user: "{{user1}}",
+ content: {
+ text: "Please transcribe the audio file I just shared.",
+ },
+ },
+ {
+ user: "{{user2}}",
+ content: {
+ text: "I'll transcribe the audio file for you.",
+ action: "TRANSCRIBE_MEDIA",
+ },
+ },
+ ],
+ [
+ {
+ user: "{{user1}}",
+ content: {
+ text: "Can you get me a transcript of this meeting recording?",
+ },
+ },
+ {
+ user: "{{user2}}",
+ content: {
+ text: "I'll generate a transcript of the meeting recording for you.",
+ action: "TRANSCRIBE_MEDIA",
+ },
+ },
+ ],
+ ] as ActionExample[][],
+};
+
+export default transcribeMediaAction;
diff --git a/packages/client-slack/src/attachments.ts b/packages/client-slack/src/attachments.ts
new file mode 100644
index 00000000000..111f87d0730
--- /dev/null
+++ b/packages/client-slack/src/attachments.ts
@@ -0,0 +1,332 @@
+import { generateText, trimTokens, parseJSONObjectFromText } from "@ai16z/eliza";
+import {
+ IAgentRuntime,
+ IImageDescriptionService,
+ IPdfService,
+ ITranscriptionService,
+ IVideoService,
+ Media,
+ ModelClass,
+ ServiceType,
+} from "@ai16z/eliza";
+import { WebClient } from '@slack/web-api';
+import ffmpeg from "fluent-ffmpeg";
+import fs from "fs";
+
+async function generateSummary(
+ runtime: IAgentRuntime,
+ text: string
+): Promise<{ title: string; description: string }> {
+ text = trimTokens(text, 100000, "gpt-4o-mini");
+
+ const prompt = `Please generate a concise summary for the following text:
+
+ Text: """
+ ${text}
+ """
+
+ Respond with a JSON object in the following format:
+ \`\`\`json
+ {
+ "title": "Generated Title",
+ "summary": "Generated summary and/or description of the text"
+ }
+ \`\`\``;
+
+ const response = await generateText({
+ runtime,
+ context: prompt,
+ modelClass: ModelClass.SMALL,
+ });
+
+ const parsedResponse = parseJSONObjectFromText(response);
+
+ if (parsedResponse) {
+ return {
+ title: parsedResponse.title,
+ description: parsedResponse.summary,
+ };
+ }
+
+ return {
+ title: "",
+ description: "",
+ };
+}
+
+interface SlackFile {
+ id: string;
+ url_private: string;
+ name: string;
+ size: number;
+ mimetype: string;
+ title?: string;
+}
+
+export class AttachmentManager {
+ private attachmentCache: Map = new Map();
+ private runtime: IAgentRuntime;
+ private client: WebClient;
+
+ constructor(runtime: IAgentRuntime, client: WebClient) {
+ this.runtime = runtime;
+ this.client = client;
+ }
+
+ async processAttachments(files: SlackFile[]): Promise {
+ const processedAttachments: Media[] = [];
+
+ for (const file of files) {
+ const media = await this.processAttachment(file);
+ if (media) {
+ processedAttachments.push(media);
+ }
+ }
+
+ return processedAttachments;
+ }
+
+ async processAttachment(file: SlackFile): Promise {
+ if (this.attachmentCache.has(file.url_private)) {
+ return this.attachmentCache.get(file.url_private)!;
+ }
+
+ let media: Media | null = null;
+
+ try {
+ const videoService = this.runtime.getService(ServiceType.VIDEO);
+
+ if (file.mimetype.startsWith("application/pdf")) {
+ media = await this.processPdfAttachment(file);
+ } else if (file.mimetype.startsWith("text/plain")) {
+ media = await this.processPlaintextAttachment(file);
+ } else if (
+ file.mimetype.startsWith("audio/") ||
+ file.mimetype.startsWith("video/mp4")
+ ) {
+ media = await this.processAudioVideoAttachment(file);
+ } else if (file.mimetype.startsWith("image/")) {
+ media = await this.processImageAttachment(file);
+ } else if (
+ file.mimetype.startsWith("video/") ||
+ (videoService?.isVideoUrl(file.url_private) ?? false)
+ ) {
+ media = await this.processVideoAttachment(file);
+ } else {
+ media = await this.processGenericAttachment(file);
+ }
+
+ if (media) {
+ this.attachmentCache.set(file.url_private, media);
+ }
+ } catch (error: unknown) {
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error';
+ console.error(`Error processing attachment: ${errorMessage}`);
+ media = await this.processGenericAttachment(file);
+ }
+
+ return media;
+ }
+
+ private async fetchFileContent(file: SlackFile): Promise {
+ const response = await fetch(file.url_private, {
+ headers: {
+ 'Authorization': `Bearer ${this.client.token}`,
+ }
+ });
+ const arrayBuffer = await response.arrayBuffer();
+ return Buffer.from(arrayBuffer);
+ }
+
+ private async processAudioVideoAttachment(file: SlackFile): Promise {
+ try {
+ const fileBuffer = await this.fetchFileContent(file);
+ let audioBuffer: Buffer;
+
+ if (file.mimetype.startsWith("audio/")) {
+ audioBuffer = fileBuffer;
+ } else if (file.mimetype.startsWith("video/mp4")) {
+ audioBuffer = await this.extractAudioFromMP4(fileBuffer);
+ } else {
+ throw new Error("Unsupported audio/video format");
+ }
+
+ const transcriptionService = this.runtime.getService(ServiceType.TRANSCRIPTION);
+ if (!transcriptionService) {
+ throw new Error("Transcription service not found");
+ }
+
+ const transcription = await transcriptionService.transcribeAttachment(audioBuffer);
+ if (!transcription) {
+ throw new Error("Transcription failed");
+ }
+
+ const { title, description } = await generateSummary(this.runtime, transcription);
+
+ return {
+ id: file.id,
+ url: file.url_private,
+ title: title || "Audio/Video Attachment",
+ source: file.mimetype.startsWith("audio/") ? "Audio" : "Video",
+ description: description || "User-uploaded audio/video attachment which has been transcribed",
+ text: transcription,
+ };
+ } catch (error: unknown) {
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error';
+ console.error(`Error processing audio/video attachment: ${errorMessage}`);
+ return {
+ id: file.id,
+ url: file.url_private,
+ title: "Audio/Video Attachment",
+ source: file.mimetype.startsWith("audio/") ? "Audio" : "Video",
+ description: "An audio/video attachment (transcription failed)",
+ text: `This is an audio/video attachment. File name: ${file.name}, Size: ${file.size} bytes, Content type: ${file.mimetype}`,
+ };
+ }
+ }
+
+ private async extractAudioFromMP4(mp4Data: Buffer): Promise {
+ const tempMP4File = `temp_${Date.now()}.mp4`;
+ const tempAudioFile = `temp_${Date.now()}.mp3`;
+
+ try {
+ fs.writeFileSync(tempMP4File, mp4Data);
+
+ await new Promise((resolve, reject) => {
+ ffmpeg(tempMP4File)
+ .outputOptions("-vn")
+ .audioCodec("libmp3lame")
+ .save(tempAudioFile)
+ .on("end", () => resolve())
+ .on("error", (err: Error) => reject(err))
+ .run();
+ });
+
+ return fs.readFileSync(tempAudioFile);
+ } finally {
+ if (fs.existsSync(tempMP4File)) {
+ fs.unlinkSync(tempMP4File);
+ }
+ if (fs.existsSync(tempAudioFile)) {
+ fs.unlinkSync(tempAudioFile);
+ }
+ }
+ }
+
+ private async processPdfAttachment(file: SlackFile): Promise {
+ try {
+ const pdfBuffer = await this.fetchFileContent(file);
+ const pdfService = this.runtime.getService(ServiceType.PDF);
+
+ if (!pdfService) {
+ throw new Error("PDF service not found");
+ }
+
+ const text = await pdfService.convertPdfToText(pdfBuffer);
+ const { title, description } = await generateSummary(this.runtime, text);
+
+ return {
+ id: file.id,
+ url: file.url_private,
+ title: title || "PDF Attachment",
+ source: "PDF",
+ description: description || "A PDF document",
+ text: text,
+ };
+ } catch (error: unknown) {
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error';
+ console.error(`Error processing PDF attachment: ${errorMessage}`);
+ return {
+ id: file.id,
+ url: file.url_private,
+ title: "PDF Attachment (conversion failed)",
+ source: "PDF",
+ description: "A PDF document that could not be converted to text",
+ text: `This is a PDF document. File name: ${file.name}, Size: ${file.size} bytes`,
+ };
+ }
+ }
+
+ private async processPlaintextAttachment(file: SlackFile): Promise {
+ try {
+ const textBuffer = await this.fetchFileContent(file);
+ const text = textBuffer.toString('utf-8');
+ const { title, description } = await generateSummary(this.runtime, text);
+
+ return {
+ id: file.id,
+ url: file.url_private,
+ title: title || "Text Attachment",
+ source: "Text",
+ description: description || "A text document",
+ text: text,
+ };
+ } catch (error: unknown) {
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error';
+ console.error(`Error processing text attachment: ${errorMessage}`);
+ return this.processGenericAttachment(file);
+ }
+ }
+
+ private async processImageAttachment(file: SlackFile): Promise {
+ try {
+ const imageService = this.runtime.getService(ServiceType.IMAGE_DESCRIPTION);
+ if (!imageService) {
+ throw new Error("Image description service not found");
+ }
+
+ const imageDescription = await imageService.describeImage(file.url_private) || '';
+ const descriptionText = typeof imageDescription === 'string'
+ ? imageDescription
+ : 'Image description not available';
+
+ return {
+ id: file.id,
+ url: file.url_private,
+ title: "Image Attachment",
+ source: "Image",
+ description: descriptionText,
+ text: descriptionText || `This is an image. File name: ${file.name}, Size: ${file.size} bytes`,
+ };
+ } catch (error: unknown) {
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error';
+ console.error(`Error processing image attachment: ${errorMessage}`);
+ return this.processGenericAttachment(file);
+ }
+ }
+
+ private async processVideoAttachment(file: SlackFile): Promise {
+ try {
+ const videoService = this.runtime.getService(ServiceType.VIDEO);
+ if (!videoService) {
+ throw new Error("Video service not found");
+ }
+
+ // Using a more generic approach since describeVideo isn't in the interface
+ const description = await this.processAudioVideoAttachment(file);
+ return {
+ id: file.id,
+ url: file.url_private,
+ title: "Video Attachment",
+ source: "Video",
+ description: description.text || "A video attachment",
+ text: description.text || `This is a video. File name: ${file.name}, Size: ${file.size} bytes`,
+ };
+ } catch (error: unknown) {
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error';
+ console.error(`Error processing video attachment: ${errorMessage}`);
+ return this.processGenericAttachment(file);
+ }
+ }
+
+ private async processGenericAttachment(file: SlackFile): Promise {
+ return {
+ id: file.id,
+ url: file.url_private,
+ title: file.title || "File Attachment",
+ source: "File",
+ description: `A file attachment of type: ${file.mimetype}`,
+ text: `This is a file attachment. File name: ${file.name}, Size: ${file.size} bytes, Type: ${file.mimetype}`,
+ };
+ }
+}
\ No newline at end of file
diff --git a/packages/client-slack/src/environment.ts b/packages/client-slack/src/environment.ts
new file mode 100644
index 00000000000..282543f7d13
--- /dev/null
+++ b/packages/client-slack/src/environment.ts
@@ -0,0 +1,44 @@
+import { IAgentRuntime } from "@ai16z/eliza";
+import { elizaLogger } from "@ai16z/eliza";
+import { z } from "zod";
+
+export const slackEnvSchema = z.object({
+ SLACK_APP_ID: z.string().min(1, "Slack application ID is required"),
+ SLACK_CLIENT_ID: z.string().min(1, "Slack client ID is required"),
+ SLACK_CLIENT_SECRET: z.string().min(1, "Slack client secret is required"),
+ SLACK_SIGNING_SECRET: z.string().min(1, "Slack signing secret is required"),
+ SLACK_VERIFICATION_TOKEN: z.string().min(1, "Slack verification token is required"),
+ SLACK_BOT_TOKEN: z.string().min(1, "Slack bot token is required"),
+ SLACK_SERVER_PORT: z.string().optional().transform(val => val ? parseInt(val) : 3000),
+});
+
+export type SlackConfig = z.infer;
+
+export async function validateSlackConfig(runtime: IAgentRuntime): Promise {
+ try {
+ elizaLogger.debug("Validating Slack configuration with runtime settings");
+ const config = {
+ SLACK_APP_ID: runtime.getSetting("SLACK_APP_ID") || process.env.SLACK_APP_ID,
+ SLACK_CLIENT_ID: runtime.getSetting("SLACK_CLIENT_ID") || process.env.SLACK_CLIENT_ID,
+ SLACK_CLIENT_SECRET: runtime.getSetting("SLACK_CLIENT_SECRET") || process.env.SLACK_CLIENT_SECRET,
+ SLACK_SIGNING_SECRET: runtime.getSetting("SLACK_SIGNING_SECRET") || process.env.SLACK_SIGNING_SECRET,
+ SLACK_VERIFICATION_TOKEN: runtime.getSetting("SLACK_VERIFICATION_TOKEN") || process.env.SLACK_VERIFICATION_TOKEN,
+ SLACK_BOT_TOKEN: runtime.getSetting("SLACK_BOT_TOKEN") || process.env.SLACK_BOT_TOKEN,
+ SLACK_SERVER_PORT: runtime.getSetting("SLACK_SERVER_PORT") || process.env.SLACK_SERVER_PORT,
+ };
+
+ elizaLogger.debug("Parsing configuration with schema", config);
+ const validated = slackEnvSchema.parse(config);
+ elizaLogger.debug("Configuration validated successfully");
+ return validated;
+ } catch (error) {
+ if (error instanceof z.ZodError) {
+ const errorMessages = error.errors
+ .map(e => `${e.path.join('.')}: ${e.message}`)
+ .join('\n');
+ elizaLogger.error("Configuration validation failed:", errorMessages);
+ throw new Error(`Slack configuration validation failed:\n${errorMessages}`);
+ }
+ throw error;
+ }
+}
\ No newline at end of file
diff --git a/packages/client-slack/src/events.ts b/packages/client-slack/src/events.ts
new file mode 100644
index 00000000000..df984908c15
--- /dev/null
+++ b/packages/client-slack/src/events.ts
@@ -0,0 +1,125 @@
+import { createEventAdapter } from '@slack/events-api';
+import { WebClient } from '@slack/web-api';
+import { SlackConfig } from './types/slack-types';
+import { MessageManager } from './messages';
+import { elizaLogger } from '@ai16z/eliza';
+
+export class EventHandler {
+ private events: ReturnType;
+ private messageManager: MessageManager;
+
+ constructor(config: SlackConfig, client: WebClient, messageManager: MessageManager) {
+ elizaLogger.log("🎮 Initializing Slack event handler...");
+ elizaLogger.debug("Creating event adapter with signing secret:", config.signingSecret.slice(0, 4) + "...");
+ this.events = createEventAdapter(config.signingSecret);
+ this.messageManager = messageManager;
+
+ this.setupEventListeners();
+ elizaLogger.log("✅ Event handler initialization complete");
+ }
+
+ private setupEventListeners() {
+ elizaLogger.log("📡 Setting up event listeners...");
+
+ // Handle URL verification
+ this.events.on('url_verification', (event: any) => {
+ elizaLogger.debug('🔍 [URL_VERIFICATION] Received challenge:', {
+ type: event.type,
+ challenge: event.challenge
+ });
+ return event.challenge;
+ });
+
+ // Handle messages
+ this.events.on('message', async (event: any) => {
+ try {
+ elizaLogger.debug('📨 [MESSAGE] Received message event:', {
+ type: event.type,
+ subtype: event.subtype,
+ user: event.user,
+ channel: event.channel,
+ text: event.text,
+ ts: event.ts,
+ thread_ts: event.thread_ts,
+ raw_event: JSON.stringify(event, null, 2)
+ });
+ await this.messageManager.handleMessage(event);
+ } catch (error) {
+ elizaLogger.error('❌ [MESSAGE] Error handling message event:', error);
+ }
+ });
+
+ // Handle app mentions
+ this.events.on('app_mention', async (event: any) => {
+ try {
+ elizaLogger.debug('🔔 [MENTION] Received app mention event:', {
+ type: event.type,
+ user: event.user,
+ channel: event.channel,
+ text: event.text,
+ ts: event.ts,
+ thread_ts: event.thread_ts,
+ raw_event: JSON.stringify(event, null, 2)
+ });
+ await this.messageManager.handleMessage(event);
+ } catch (error) {
+ elizaLogger.error('❌ [MENTION] Error handling app mention event:', error);
+ }
+ });
+
+ // Handle reactions
+ this.events.on('reaction_added', async (event: any) => {
+ try {
+ elizaLogger.debug('⭐ [REACTION] Reaction added:', {
+ type: event.type,
+ user: event.user,
+ reaction: event.reaction,
+ item: event.item,
+ raw_event: JSON.stringify(event, null, 2)
+ });
+ // TODO: Implement reaction handling
+ } catch (error) {
+ elizaLogger.error('❌ [REACTION] Error handling reaction_added event:', error);
+ }
+ });
+
+ this.events.on('reaction_removed', async (event: any) => {
+ try {
+ elizaLogger.debug('💫 [REACTION] Reaction removed:', {
+ type: event.type,
+ user: event.user,
+ reaction: event.reaction,
+ item: event.item,
+ raw_event: JSON.stringify(event, null, 2)
+ });
+ // TODO: Implement reaction handling
+ } catch (error) {
+ elizaLogger.error('❌ [REACTION] Error handling reaction_removed event:', error);
+ }
+ });
+
+ // Handle errors
+ this.events.on('error', (error: Error) => {
+ elizaLogger.error('❌ [ERROR] Slack Events API error:', error);
+ });
+
+ // Add debug logging for all events
+ this.events.on('*', (event: any) => {
+ elizaLogger.debug('🔄 [RAW] Raw Slack event received:', {
+ type: event.type,
+ subtype: event.subtype,
+ user: event.user,
+ channel: event.channel,
+ ts: event.ts,
+ raw_event: JSON.stringify(event, null, 2)
+ });
+ });
+
+ elizaLogger.log("✅ Event listeners setup complete");
+ }
+
+ public getEventAdapter() {
+ elizaLogger.debug("🔌 [ADAPTER] Returning event adapter for express middleware");
+ return this.events;
+ }
+}
\ No newline at end of file
diff --git a/packages/client-slack/src/examples/sc_01.png b/packages/client-slack/src/examples/sc_01.png
new file mode 100644
index 00000000000..23041a979c1
Binary files /dev/null and b/packages/client-slack/src/examples/sc_01.png differ
diff --git a/packages/client-slack/src/examples/sc_02.png b/packages/client-slack/src/examples/sc_02.png
new file mode 100644
index 00000000000..2d91d022cff
Binary files /dev/null and b/packages/client-slack/src/examples/sc_02.png differ
diff --git a/packages/client-slack/src/examples/standalone-attachment.ts b/packages/client-slack/src/examples/standalone-attachment.ts
new file mode 100644
index 00000000000..fd71c133002
--- /dev/null
+++ b/packages/client-slack/src/examples/standalone-attachment.ts
@@ -0,0 +1,112 @@
+import { config } from "dotenv";
+import { SlackClientProvider } from "../providers/slack-client.provider";
+import { AttachmentManager } from "../attachments";
+import { SlackConfig } from "../types/slack-types";
+import path from "path";
+
+// Load environment variables
+config({ path: path.resolve(__dirname, "../../../.env") });
+
+console.log("\n=== Starting Slack Attachment Example ===\n");
+
+// Load environment variables
+const slackConfig: SlackConfig = {
+ appId: process.env.SLACK_APP_ID || "",
+ clientId: process.env.SLACK_CLIENT_ID || "",
+ clientSecret: process.env.SLACK_CLIENT_SECRET || "",
+ signingSecret: process.env.SLACK_SIGNING_SECRET || "",
+ verificationToken: process.env.SLACK_VERIFICATION_TOKEN || "",
+ botToken: process.env.SLACK_BOT_TOKEN || "",
+ botId: process.env.SLACK_BOT_ID || "",
+};
+
+console.log("Environment variables loaded:");
+Object.entries(slackConfig).forEach(([key, value]) => {
+ if (value) {
+ console.log(`${key}: ${value.slice(0, 4)}...${value.slice(-4)}`);
+ } else {
+ console.error(`Missing ${key}`);
+ }
+});
+
+async function runExample() {
+ try {
+ console.log("\nInitializing Slack client...");
+ const provider = new SlackClientProvider(slackConfig);
+ const client = provider.getContext().client;
+
+ console.log("\nValidating Slack connection...");
+ const isValid = await provider.validateConnection();
+ if (!isValid) {
+ throw new Error("Failed to validate Slack connection");
+ }
+ console.log("✓ Successfully connected to Slack");
+
+ // Test file upload
+ const channelId = process.env.SLACK_CHANNEL_ID;
+ if (!channelId) {
+ throw new Error("SLACK_CHANNEL_ID is required");
+ }
+
+ console.log("\nSending test message with attachment...");
+ const testMessage = "Here is a test message with an attachment";
+
+ // Create a test file
+ const testFilePath = path.join(__dirname, "test.txt");
+ async function loadFs() {
+ return await import("fs");
+ }
+ const fs = await loadFs();
+ fs.writeFileSync(
+ testFilePath,
+ "This is a test file content for attachment testing."
+ );
+
+ // Upload the file
+ const fileUpload = await client.files.upload({
+ channels: channelId,
+ file: fs.createReadStream(testFilePath),
+ filename: "test.txt",
+ title: "Test Attachment",
+ initial_comment: testMessage,
+ });
+
+ console.log("✓ File uploaded successfully");
+
+ // Initialize AttachmentManager
+ const runtime = {
+ getSetting: (key: string) => process.env[key],
+ getService: () => null,
+ // Add other required runtime properties as needed
+ };
+ const attachmentManager = new AttachmentManager(runtime as any, client);
+
+ // Process the uploaded file
+ if (fileUpload.file) {
+ console.log("\nProcessing attachment...");
+ const processedAttachment =
+ await attachmentManager.processAttachment({
+ id: fileUpload.file.id,
+ url_private: fileUpload.file.url_private || "",
+ name: fileUpload.file.name || "",
+ size: fileUpload.file.size || 0,
+ mimetype: fileUpload.file.mimetype || "text/plain",
+ title: fileUpload.file.title || "",
+ });
+
+ console.log("✓ Attachment processed:", processedAttachment);
+ }
+
+ // Cleanup
+ fs.unlinkSync(testFilePath);
+ console.log("\n✓ Test completed successfully");
+ } catch (error) {
+ console.error("Error:", error);
+ process.exit(1);
+ }
+}
+
+runExample().then(() => {
+ console.log("\n=== Example completed ===\n");
+ process.exit(0);
+});
diff --git a/packages/client-slack/src/examples/standalone-example.ts b/packages/client-slack/src/examples/standalone-example.ts
new file mode 100644
index 00000000000..77eee87fe43
--- /dev/null
+++ b/packages/client-slack/src/examples/standalone-example.ts
@@ -0,0 +1,200 @@
+import { SlackClientProvider } from '../providers/slack-client.provider';
+import { SlackConfig } from '../types/slack-types';
+import { EventHandler } from '../events';
+import { config } from 'dotenv';
+import { resolve } from 'path';
+import { createReadStream } from 'fs';
+import express from 'express';
+
+// Load environment variables
+const envPath = resolve(__dirname, '../../../../.env');
+console.log('Loading environment from:', envPath);
+config({ path: envPath });
+
+function validateEnvironment() {
+ const requiredEnvVars = [
+ 'SLACK_APP_ID',
+ 'SLACK_CLIENT_ID',
+ 'SLACK_CLIENT_SECRET',
+ 'SLACK_SIGNING_SECRET',
+ 'SLACK_VERIFICATION_TOKEN',
+ 'SLACK_BOT_TOKEN',
+ 'SLACK_CHANNEL_ID'
+ ];
+
+ const missing = requiredEnvVars.filter(key => !process.env[key]);
+ if (missing.length > 0) {
+ console.error('Missing required environment variables:', missing);
+ return false;
+ }
+
+ // Log masked versions of the tokens for debugging
+ console.log('Environment variables loaded:');
+ requiredEnvVars.forEach(key => {
+ const value = process.env[key] || '';
+ const maskedValue = value.length > 8
+ ? `${value.substring(0, 4)}...${value.substring(value.length - 4)}`
+ : '****';
+ console.log(`${key}: ${maskedValue}`);
+ });
+
+ return true;
+}
+
+async function startServer(app: express.Application, port: number): Promise {
+ try {
+ await new Promise((resolve, reject) => {
+ app.listen(port, () => resolve()).on('error', (err: any) => {
+ if (err.code === 'EADDRINUSE') {
+ console.log(`Port ${port} is busy, trying ${port + 1}...`);
+ resolve();
+ } else {
+ reject(err);
+ }
+ });
+ });
+ return port;
+ } catch (error) {
+ if (port < 3010) { // Try up to 10 ports
+ return startServer(app, port + 1);
+ }
+ throw error;
+ }
+}
+
+async function runExample() {
+ console.log('\n=== Starting Slack Client Example ===\n');
+
+ if (!validateEnvironment()) {
+ throw new Error('Environment validation failed');
+ }
+
+ // Initialize the client with your Slack credentials
+ const slackConfig: SlackConfig = {
+ appId: process.env.SLACK_APP_ID || '',
+ clientId: process.env.SLACK_CLIENT_ID || '',
+ clientSecret: process.env.SLACK_CLIENT_SECRET || '',
+ signingSecret: process.env.SLACK_SIGNING_SECRET || '',
+ verificationToken: process.env.SLACK_VERIFICATION_TOKEN || '',
+ botToken: process.env.SLACK_BOT_TOKEN || '',
+ botId: process.env.SLACK_BOT_ID || '', // This will be updated automatically
+ };
+
+ console.log('\nInitializing Slack client...');
+ const slackProvider = new SlackClientProvider(slackConfig);
+
+ try {
+ // Validate the connection
+ console.log('\nValidating Slack connection...');
+ const isConnected = await slackProvider.validateConnection();
+ if (!isConnected) {
+ throw new Error('Failed to connect to Slack');
+ }
+ console.log('✓ Successfully connected to Slack');
+
+ // Set up event handling
+ console.log('\nSetting up event handling...');
+ const eventHandler = new EventHandler(slackConfig, slackProvider.getContext().client);
+ const events = eventHandler.getEventAdapter();
+
+ // Create Express app
+ const app = express();
+ const basePort = parseInt(process.env.PORT || '3000');
+
+ // Mount the event handler
+ app.use('/slack/events', events.expressMiddleware());
+
+ // Send initial message
+ const channelId = process.env.SLACK_CHANNEL_ID || '';
+ console.log(`\nSending initial message to channel: ${channelId}`);
+
+ try {
+ // Send text message
+ const messageResult = await slackProvider.sendMessage(
+ channelId,
+ 'Hello! I am now active and ready to help. Here are my capabilities:'
+ );
+ console.log('✓ Initial message sent:', messageResult);
+
+ // Send message with image
+ const imagePath = resolve(__dirname, '../tests/test_image.png');
+ console.log('\nSending message with image...');
+ const imageResult = await slackProvider.getContext().client.files.uploadV2({
+ channel_id: channelId,
+ file: createReadStream(imagePath),
+ filename: 'test_image.png',
+ title: 'Test Image',
+ initial_comment: '1. I can send messages with images 🖼️'
+ });
+ console.log('✓ Image message sent:', imageResult);
+
+ // Send message in thread
+ if (messageResult.ts) {
+ console.log('\nSending message in thread...');
+ const threadResult = await slackProvider.replyInThread(
+ channelId,
+ messageResult.ts,
+ '2. I can reply in threads 🧵'
+ );
+ console.log('✓ Thread message sent:', threadResult);
+
+ // Send another image in the thread
+ console.log('\nSending image in thread...');
+ const threadImageResult = await slackProvider.getContext().client.files.uploadV2({
+ channel_id: channelId,
+ file: createReadStream(imagePath),
+ filename: 'test_image_thread.png',
+ title: 'Test Image in Thread',
+ thread_ts: messageResult.ts,
+ initial_comment: '3. I can also send images in threads! 🖼️🧵'
+ });
+ console.log('✓ Thread image sent:', threadImageResult);
+ }
+
+ // Start the server
+ const port = await startServer(app, basePort);
+ console.log(`\n✓ Slack event server is running on port ${port}`);
+ console.log('\n=== Bot is ready to interact! ===');
+ console.log('\nCore functionalities demonstrated:');
+ console.log('1. Sending regular messages');
+ console.log('2. Sending images and attachments');
+ console.log('3. Replying in threads');
+ console.log('4. Sending images in threads');
+ console.log('\nTry mentioning me with @eve_predict_client to interact!');
+
+ if (!process.env.SLACK_BOT_ID) {
+ console.log(`\nℹ️ Bot ID: ${slackConfig.botId}`);
+ }
+
+ } catch (error) {
+ console.error('\n❌ Error during initialization:', error);
+ // Continue even if initial messages fail
+ console.log('\nStarting server despite initialization errors...');
+
+ const port = await startServer(app, basePort);
+ console.log(`\n✓ Slack event server is running on port ${port}`);
+ console.log('\n=== Bot is ready to interact! ===');
+ }
+
+ } catch (error) {
+ console.error('\n❌ Error in Slack client example:');
+ if (error instanceof Error) {
+ console.error('Error message:', error.message);
+ console.error('Stack trace:', error.stack);
+ if ('data' in error) {
+ console.error('Error data:', (error as any).data);
+ }
+ } else {
+ console.error('Unknown error:', error);
+ }
+ process.exit(1);
+ }
+}
+
+// Run the example if this file is executed directly
+if (require.main === module) {
+ runExample().catch(error => {
+ console.error('Fatal error:', error);
+ process.exit(1);
+ });
+}
\ No newline at end of file
diff --git a/packages/client-slack/src/examples/standalone-summarize.ts b/packages/client-slack/src/examples/standalone-summarize.ts
new file mode 100644
index 00000000000..a2251ae888e
--- /dev/null
+++ b/packages/client-slack/src/examples/standalone-summarize.ts
@@ -0,0 +1,101 @@
+import { SlackClientProvider } from '../providers/slack-client.provider';
+import { SlackConfig } from '../types/slack-types';
+import { config } from 'dotenv';
+import { resolve } from 'path';
+
+// Load environment variables from root .env
+const envPath = resolve(__dirname, '../../../../.env');
+console.log('Loading environment from:', envPath);
+config({ path: envPath });
+
+function validateEnvironment() {
+ const requiredEnvVars = [
+ 'SLACK_APP_ID',
+ 'SLACK_CLIENT_ID',
+ 'SLACK_CLIENT_SECRET',
+ 'SLACK_SIGNING_SECRET',
+ 'SLACK_VERIFICATION_TOKEN',
+ 'SLACK_BOT_TOKEN',
+ 'SLACK_CHANNEL_ID'
+ ];
+
+ const missing = requiredEnvVars.filter(key => !process.env[key]);
+ if (missing.length > 0) {
+ console.error('Missing required environment variables:', missing);
+ return false;
+ }
+
+ console.log('Environment variables loaded successfully');
+ return true;
+}
+
+async function main() {
+ console.log('\n=== Starting Summarize Conversation Example ===\n');
+
+ if (!validateEnvironment()) {
+ throw new Error('Environment validation failed');
+ }
+
+ // Initialize the client with Slack credentials
+ const slackConfig: SlackConfig = {
+ appId: process.env.SLACK_APP_ID || '',
+ clientId: process.env.SLACK_CLIENT_ID || '',
+ clientSecret: process.env.SLACK_CLIENT_SECRET || '',
+ signingSecret: process.env.SLACK_SIGNING_SECRET || '',
+ verificationToken: process.env.SLACK_VERIFICATION_TOKEN || '',
+ botToken: process.env.SLACK_BOT_TOKEN || '',
+ botId: process.env.SLACK_BOT_ID || '',
+ };
+
+ const slackProvider = new SlackClientProvider(slackConfig);
+
+ // Validate the connection
+ const isConnected = await slackProvider.validateConnection();
+ if (!isConnected) {
+ throw new Error('Failed to connect to Slack');
+ }
+ console.log('✓ Successfully connected to Slack');
+
+ const channel = process.env.SLACK_CHANNEL_ID!;
+ console.log(`\nSending messages to channel: ${channel}`);
+
+ // First, send some test messages
+ await slackProvider.sendMessage(
+ channel,
+ "Hello! Let's test the conversation summarization."
+ );
+
+ // Send message with attachment using WebClient directly
+ await slackProvider.getContext().client.chat.postMessage({
+ channel,
+ text: "Here's an important document to discuss.",
+ attachments: [{
+ title: "Test Document",
+ text: "This is a test document with some important information.",
+ }]
+ });
+
+ await slackProvider.sendMessage(
+ channel,
+ "What do you think about the document?"
+ );
+
+ // Wait a bit for messages to be processed
+ await new Promise(resolve => setTimeout(resolve, 2000));
+
+ // Request a summary
+ await slackProvider.sendMessage(
+ channel,
+ "Can you summarize our conversation so far?"
+ );
+
+ // Keep the process running
+ await new Promise(resolve => setTimeout(resolve, 10000));
+ console.log('\n✓ Example completed successfully');
+ process.exit(0);
+}
+
+main().catch(error => {
+ console.error('\n❌ Error:', error);
+ process.exit(1);
+});
\ No newline at end of file
diff --git a/packages/client-slack/src/examples/standalone-transcribe.ts b/packages/client-slack/src/examples/standalone-transcribe.ts
new file mode 100644
index 00000000000..76b0ec0715c
--- /dev/null
+++ b/packages/client-slack/src/examples/standalone-transcribe.ts
@@ -0,0 +1,90 @@
+import { SlackClientProvider } from '../providers/slack-client.provider';
+import { SlackConfig } from '../types/slack-types';
+import { config } from 'dotenv';
+import { resolve } from 'path';
+
+// Load environment variables from root .env
+const envPath = resolve(__dirname, '../../../../.env');
+console.log('Loading environment from:', envPath);
+config({ path: envPath });
+
+function validateEnvironment() {
+ const requiredEnvVars = [
+ 'SLACK_APP_ID',
+ 'SLACK_CLIENT_ID',
+ 'SLACK_CLIENT_SECRET',
+ 'SLACK_SIGNING_SECRET',
+ 'SLACK_VERIFICATION_TOKEN',
+ 'SLACK_BOT_TOKEN',
+ 'SLACK_CHANNEL_ID'
+ ];
+
+ const missing = requiredEnvVars.filter(key => !process.env[key]);
+ if (missing.length > 0) {
+ console.error('Missing required environment variables:', missing);
+ return false;
+ }
+
+ console.log('Environment variables loaded successfully');
+ return true;
+}
+
+async function main() {
+ console.log('\n=== Starting Transcribe Media Example ===\n');
+
+ if (!validateEnvironment()) {
+ throw new Error('Environment validation failed');
+ }
+
+ // Initialize the client with Slack credentials
+ const slackConfig: SlackConfig = {
+ appId: process.env.SLACK_APP_ID || '',
+ clientId: process.env.SLACK_CLIENT_ID || '',
+ clientSecret: process.env.SLACK_CLIENT_SECRET || '',
+ signingSecret: process.env.SLACK_SIGNING_SECRET || '',
+ verificationToken: process.env.SLACK_VERIFICATION_TOKEN || '',
+ botToken: process.env.SLACK_BOT_TOKEN || '',
+ botId: process.env.SLACK_BOT_ID || '',
+ };
+
+ const slackProvider = new SlackClientProvider(slackConfig);
+
+ // Validate the connection
+ const isConnected = await slackProvider.validateConnection();
+ if (!isConnected) {
+ throw new Error('Failed to connect to Slack');
+ }
+ console.log('✓ Successfully connected to Slack');
+
+ const channel = process.env.SLACK_CHANNEL_ID!;
+ console.log(`\nSending messages to channel: ${channel}`);
+
+ // First, send a test message with a media attachment
+ await slackProvider.getContext().client.chat.postMessage({
+ channel,
+ text: "Here's a test audio recording to transcribe.",
+ attachments: [{
+ title: "Test Audio",
+ text: "This is a simulated transcription of an audio file: Hello everyone, welcome to our weekly standup meeting. Today we'll discuss our progress on the new features and any blockers we've encountered.",
+ }]
+ });
+
+ // Wait a bit for the message to be processed
+ await new Promise(resolve => setTimeout(resolve, 2000));
+
+ // Request transcription
+ await slackProvider.sendMessage(
+ channel,
+ "Can you transcribe the audio file I just shared?"
+ );
+
+ // Keep the process running
+ await new Promise(resolve => setTimeout(resolve, 10000));
+ console.log('\n✓ Example completed successfully');
+ process.exit(0);
+}
+
+main().catch(error => {
+ console.error('\n❌ Error:', error);
+ process.exit(1);
+});
\ No newline at end of file
diff --git a/packages/client-slack/src/index.ts b/packages/client-slack/src/index.ts
new file mode 100644
index 00000000000..f893db18e07
--- /dev/null
+++ b/packages/client-slack/src/index.ts
@@ -0,0 +1,345 @@
+import { Character, Client as ElizaClient, IAgentRuntime } from "@ai16z/eliza";
+import { elizaLogger } from "@ai16z/eliza";
+import { WebClient } from "@slack/web-api";
+import express, { Request } from "express";
+import { EventEmitter } from "events";
+import { MessageManager } from "./messages";
+import { validateSlackConfig } from "./environment";
+import chat_with_attachments from "./actions/chat_with_attachments";
+import summarize_conversation from "./actions/summarize_conversation";
+// import transcribe_media from './actions/transcribe_media';
+import { channelStateProvider } from "./providers/channelState";
+import { SlackService } from "./services/slack.service";
+
+interface SlackRequest extends Request {
+ rawBody?: Buffer;
+}
+
+export class SlackClient extends EventEmitter {
+ private client: WebClient;
+ private runtime: IAgentRuntime;
+ private server: express.Application;
+ private messageManager: MessageManager;
+ private botUserId: string;
+ private character: Character;
+ private signingSecret: string;
+
+ constructor(runtime: IAgentRuntime) {
+ super();
+ elizaLogger.log("🚀 Initializing SlackClient...");
+ this.runtime = runtime;
+ this.character = runtime.character;
+
+ const token = runtime.getSetting("SLACK_BOT_TOKEN");
+ this.signingSecret = runtime.getSetting("SLACK_SIGNING_SECRET");
+
+ if (!token) throw new Error("SLACK_BOT_TOKEN is required");
+ if (!this.signingSecret)
+ throw new Error("SLACK_SIGNING_SECRET is required");
+
+ this.client = new WebClient(token);
+ this.server = express();
+
+ this.server.use(express.raw({ type: "application/json" }));
+ this.server.use((req: SlackRequest, res, next) => {
+ if (req.body) {
+ req.rawBody = Buffer.from(req.body);
+ try {
+ req.body = JSON.parse(req.body.toString());
+ } catch (error) {
+ elizaLogger.error(
+ "❌ [PARSE] Failed to parse request body:",
+ error
+ );
+ }
+ }
+ next();
+ });
+ }
+
+ private async handleEvent(event: any) {
+ elizaLogger.debug("🎯 [EVENT] Processing event:", {
+ type: event.type,
+ user: event.user,
+ channel: event.channel,
+ text: event.text?.slice(0, 100),
+ });
+
+ try {
+ if (event.type === "message" || event.type === "app_mention") {
+ await this.messageManager.handleMessage(event);
+ }
+ } catch (error) {
+ elizaLogger.error("❌ [EVENT] Error handling event:", error);
+ }
+ }
+
+ private async verifyPermissions() {
+ elizaLogger.debug("🔒 [PERMISSIONS] Verifying bot permissions...");
+
+ try {
+ // Test channel list access with all types
+ const channels = await this.client.conversations.list({
+ types: "public_channel,private_channel,im,mpim",
+ });
+
+ if (!channels.ok) {
+ throw new Error(`Failed to list channels: ${channels.error}`);
+ }
+
+ elizaLogger.debug("📋 [PERMISSIONS] Channel access verified");
+
+ // Test message sending (to self)
+ const testMessage = await this.client.chat.postMessage({
+ channel: this.botUserId,
+ text: "Permission test message",
+ });
+
+ if (!testMessage.ok) {
+ throw new Error(
+ `Failed to send test message: ${testMessage.error}`
+ );
+ }
+
+ elizaLogger.debug("💬 [PERMISSIONS] Message sending verified");
+
+ elizaLogger.debug("✅ [PERMISSIONS] All permissions verified");
+ } catch (error: any) {
+ elizaLogger.error(
+ "❌ [PERMISSIONS] Permission verification failed:",
+ error
+ );
+ elizaLogger.error(
+ "Please ensure the following scopes are added to your Slack app:"
+ );
+ elizaLogger.error("- app_mentions:read (for mentions)");
+ elizaLogger.error("- channels:history (for public channels)");
+ elizaLogger.error("- channels:read (for channel info)");
+ elizaLogger.error("- chat:write (for sending messages)");
+ elizaLogger.error("- groups:history (for private channels)");
+ elizaLogger.error(
+ "- groups:read (for private channel info)"
+ );
+ elizaLogger.error("- im:history (for DMs)");
+ elizaLogger.error("- im:read (for DM info)");
+ elizaLogger.error("- im:write (for sending DMs)");
+ elizaLogger.error("- mpim:history (for group DMs)");
+ elizaLogger.error("- mpim:read (for group DM info)");
+ elizaLogger.error("- users:read (for user info)");
+ throw new Error("Permission verification failed");
+ }
+ }
+
+ async start() {
+ try {
+ elizaLogger.log("Starting Slack client...");
+
+ const config = await validateSlackConfig(this.runtime);
+
+ // Initialize and register Slack service
+ const slackService = new SlackService();
+ await slackService.initialize(this.runtime);
+ await this.runtime.registerService(slackService);
+
+ // Get detailed bot info
+ const auth = await this.client.auth.test();
+ if (!auth.ok) throw new Error("Failed to authenticate with Slack");
+
+ this.botUserId = auth.user_id as string;
+ elizaLogger.debug("🤖 [INIT] Bot info:", {
+ user_id: auth.user_id,
+ bot_id: auth.bot_id,
+ team_id: auth.team_id,
+ user: auth.user,
+ team: auth.team,
+ });
+
+ // Verify bot user details
+ try {
+ const botInfo = await this.client.users.info({
+ user: this.botUserId,
+ });
+
+ elizaLogger.debug("👤 [BOT] Bot user details:", {
+ name: botInfo.user?.name,
+ real_name: botInfo.user?.real_name,
+ is_bot: botInfo.user?.is_bot,
+ is_app_user: botInfo.user?.is_app_user,
+ status: botInfo.user?.profile?.status_text,
+ });
+ } catch (error) {
+ elizaLogger.error(
+ "❌ [BOT] Failed to verify bot details:",
+ error
+ );
+ }
+
+ // Verify permissions
+ await this.verifyPermissions();
+
+ // Initialize message manager
+ this.messageManager = new MessageManager(
+ this.client,
+ this.runtime,
+ this.botUserId
+ );
+
+ // Register actions and providers
+ this.runtime.registerAction(chat_with_attachments);
+ this.runtime.registerAction(summarize_conversation);
+ // this.runtime.registerAction(transcribe_media);
+ this.runtime.providers.push(channelStateProvider);
+
+ // Add request logging middleware
+ this.server.use((req: SlackRequest, res, next) => {
+ elizaLogger.debug("🌐 [HTTP] Incoming request:", {
+ method: req.method,
+ path: req.path,
+ headers: req.headers,
+ body: req.body,
+ query: req.query,
+ timestamp: new Date().toISOString(),
+ });
+ next();
+ });
+
+ // Setup event handling endpoint
+ this.server.post(
+ "/slack/events",
+ async (req: SlackRequest, res) => {
+ try {
+ elizaLogger.debug(
+ "📥 [REQUEST] Incoming Slack event:",
+ {
+ type: req.body?.type,
+ event: req.body?.event?.type,
+ challenge: req.body?.challenge,
+ raw: JSON.stringify(req.body, null, 2),
+ }
+ );
+
+ // Handle URL verification
+ if (req.body?.type === "url_verification") {
+ elizaLogger.debug(
+ "🔑 [VERIFICATION] Challenge received:",
+ req.body.challenge
+ );
+ return res.send(req.body.challenge);
+ }
+
+ // Process the event
+ if (req.body?.event) {
+ elizaLogger.debug("🎯 [EVENT] Processing event:", {
+ type: req.body.event.type,
+ user: req.body.event.user,
+ text: req.body.event.text,
+ channel: req.body.event.channel,
+ ts: req.body.event.ts,
+ });
+ await this.handleEvent(req.body.event);
+ } else {
+ elizaLogger.warn(
+ "⚠️ [EVENT] Received request without event data"
+ );
+ }
+
+ // Acknowledge receipt
+ res.status(200).send();
+ } catch (error) {
+ elizaLogger.error(
+ "❌ [ERROR] Error processing request:",
+ error
+ );
+ res.status(500).json({
+ error: "Internal server error",
+ });
+ }
+ }
+ );
+
+ // Setup interactions endpoint
+ this.server.post(
+ "/slack/interactions",
+ async (req: SlackRequest, res) => {
+ try {
+ elizaLogger.debug(
+ "🔄 [INTERACTION] Incoming interaction:",
+ {
+ type: req.body?.type,
+ action: req.body?.action,
+ callback_id: req.body?.callback_id,
+ raw: JSON.stringify(req.body, null, 2),
+ }
+ );
+
+ // Always acknowledge interaction
+ res.status(200).send();
+ } catch (error) {
+ elizaLogger.error(
+ "❌ [ERROR] Error processing interaction:",
+ error
+ );
+ res.status(500).json({
+ error: "Internal server error",
+ });
+ }
+ }
+ );
+
+ // Start server
+ const port = config.SLACK_SERVER_PORT;
+ this.server.listen(port, () => {
+ elizaLogger.success(
+ `🚀 [SERVER] Slack event server is running on port ${port}`
+ );
+ elizaLogger.success(
+ `✅ [INIT] Slack client successfully started for character ${this.character.name}`
+ );
+ elizaLogger.success(
+ `🤖 [READY] Bot user: @${auth.user} (${this.botUserId})`
+ );
+ elizaLogger.success(
+ `📡 [EVENTS] Listening for events at: /slack/events`
+ );
+ elizaLogger.success(
+ `💡 [INTERACTIONS] Listening for interactions at: /slack/interactions`
+ );
+ elizaLogger.success(`💡 [HELP] To interact with the bot:`);
+ elizaLogger.success(
+ ` 1. Direct message: Find @${auth.user} in DMs`
+ );
+ elizaLogger.success(
+ ` 2. Channel: Mention @${auth.user} in any channel`
+ );
+ });
+ } catch (error) {
+ elizaLogger.error("❌ [INIT] Failed to start Slack client:", error);
+ throw error;
+ }
+ }
+
+ async stop() {
+ elizaLogger.log("Stopping Slack client...");
+ if (this.server) {
+ await new Promise((resolve) => {
+ this.server.listen().close(() => {
+ elizaLogger.log("Server stopped");
+ resolve();
+ });
+ });
+ }
+ }
+}
+
+export const SlackClientInterface: ElizaClient = {
+ start: async (runtime: IAgentRuntime) => {
+ const client = new SlackClient(runtime);
+ await client.start();
+ return client;
+ },
+ stop: async (_runtime: IAgentRuntime) => {
+ elizaLogger.warn("Slack client stopping...");
+ },
+};
+
+export default SlackClientInterface;
diff --git a/packages/client-slack/src/messages.ts b/packages/client-slack/src/messages.ts
new file mode 100644
index 00000000000..d77c3e3c41a
--- /dev/null
+++ b/packages/client-slack/src/messages.ts
@@ -0,0 +1,348 @@
+import {
+ stringToUuid,
+ getEmbeddingZeroVector,
+ composeContext,
+ generateMessageResponse,
+ generateShouldRespond,
+ ModelClass,
+ Memory,
+ Content,
+ State,
+ elizaLogger,
+ HandlerCallback
+} from '@ai16z/eliza';
+import { slackMessageHandlerTemplate, slackShouldRespondTemplate } from './templates';
+import { WebClient } from '@slack/web-api';
+import { IAgentRuntime } from '@ai16z/eliza';
+
+export class MessageManager {
+ private client: WebClient;
+ private runtime: IAgentRuntime;
+ private botUserId: string;
+ private processedEvents: Set = new Set();
+ private messageProcessingLock: Set = new Set();
+ private processedMessages: Map = new Map();
+
+ constructor(client: WebClient, runtime: IAgentRuntime, botUserId: string) {
+ console.log("📱 Initializing MessageManager...");
+ this.client = client;
+ this.runtime = runtime;
+ this.botUserId = botUserId;
+ console.log("MessageManager initialized with botUserId:", botUserId);
+
+ // Clear old processed messages and events every hour
+ setInterval(() => {
+ const oneHourAgo = Date.now() - 3600000;
+
+ // Clear old processed messages
+ for (const [key, timestamp] of this.processedMessages.entries()) {
+ if (timestamp < oneHourAgo) {
+ this.processedMessages.delete(key);
+ }
+ }
+
+ // Clear old processed events
+ this.processedEvents.clear();
+ }, 3600000);
+ }
+
+ private generateEventKey(event: any): string {
+ // Create a unique key that includes all relevant event data
+ // Normalize event type to handle message and app_mention as the same type
+ const eventType = (event.type === 'app_mention') ? 'message' : event.type;
+
+ const components = [
+ event.ts, // Timestamp
+ event.channel, // Channel ID
+ eventType, // Normalized event type
+ event.user, // User ID
+ event.thread_ts // Thread timestamp (if any)
+ ].filter(Boolean); // Remove any undefined/null values
+
+ const key = components.join('-');
+ console.log("\n=== EVENT DETAILS ===");
+ console.log("Event Type:", event.type);
+ console.log("Event TS:", event.ts);
+ console.log("Channel:", event.channel);
+ console.log("User:", event.user);
+ console.log("Thread TS:", event.thread_ts);
+ console.log("Generated Key:", key);
+ return key;
+ }
+
+ private cleanMessage(text: string): string {
+ elizaLogger.debug("🧹 [CLEAN] Cleaning message text:", text);
+ // Remove bot mention
+ const cleaned = text.replace(new RegExp(`<@${this.botUserId}>`, 'g'), '').trim();
+ elizaLogger.debug("✨ [CLEAN] Cleaned result:", cleaned);
+ return cleaned;
+ }
+
+ private async _shouldRespond(message: any, state: State): Promise {
+ console.log("\n=== SHOULD_RESPOND PHASE ===");
+ console.log("🔍 Step 1: Evaluating if should respond to message");
+
+ // Always respond to direct mentions
+ if (message.type === 'app_mention' || message.text?.includes(`<@${this.botUserId}>`)) {
+ console.log("✅ Direct mention detected - will respond");
+ return true;
+ }
+
+ // Always respond in direct messages
+ if (message.channel_type === 'im') {
+ console.log("✅ Direct message detected - will respond");
+ return true;
+ }
+
+ // Check if we're in a thread and we've participated
+ if (message.thread_ts && state.recentMessages?.includes(this.runtime.agentId)) {
+ console.log("✅ Active thread participant - will respond");
+ return true;
+ }
+
+ // Only use LLM for ambiguous cases
+ console.log("🤔 Step 2: Using LLM to decide response");
+ const shouldRespondContext = composeContext({
+ state,
+ template: this.runtime.character.templates?.slackShouldRespondTemplate ||
+ this.runtime.character.templates?.shouldRespondTemplate ||
+ slackShouldRespondTemplate,
+ });
+
+ console.log("🔄 Step 3: Calling generateShouldRespond");
+ const response = await generateShouldRespond({
+ runtime: this.runtime,
+ context: shouldRespondContext,
+ modelClass: ModelClass.SMALL,
+ });
+
+ console.log(`✅ Step 4: LLM decision received: ${response}`);
+ return response === 'RESPOND';
+ }
+
+ private async _generateResponse(
+ memory: Memory,
+ state: State,
+ context: string
+ ): Promise {
+ console.log("\n=== GENERATE_RESPONSE PHASE ===");
+ console.log("🔍 Step 1: Starting response generation");
+
+ // Generate response only once
+ console.log("🔄 Step 2: Calling LLM for response");
+ const response = await generateMessageResponse({
+ runtime: this.runtime,
+ context,
+ modelClass: ModelClass.SMALL,
+ });
+ console.log("✅ Step 3: LLM response received");
+
+ if (!response) {
+ console.error("❌ No response from generateMessageResponse");
+ return {
+ text: "I apologize, but I'm having trouble generating a response right now.",
+ source: 'slack'
+ };
+ }
+
+ // If response includes a CONTINUE action but there's no direct mention or thread,
+ // remove the action to prevent automatic continuation
+ if (
+ response.action === 'CONTINUE' &&
+ !memory.content.text?.includes(`<@${this.botUserId}>`) &&
+ !state.recentMessages?.includes(memory.id)
+ ) {
+ console.log("⚠️ Step 4: Removing CONTINUE action - not a direct interaction");
+ delete response.action;
+ }
+
+ console.log("✅ Step 5: Returning generated response");
+ return response;
+ }
+
+ public async handleMessage(event: any) {
+ console.log("\n=== MESSAGE_HANDLING PHASE ===");
+ console.log("🔍 Step 1: Received new message event");
+
+ // Skip if no event data
+ if (!event || !event.ts || !event.channel) {
+ console.log("⚠️ Invalid event data - skipping");
+ return;
+ }
+
+ // Generate event key for deduplication
+ const eventKey = this.generateEventKey(event);
+
+ // Check if we've already processed this event
+ if (this.processedEvents.has(eventKey)) {
+ console.log("⚠️ Event already processed - skipping");
+ console.log("Existing event key:", eventKey);
+ console.log("Original event type:", event.type);
+ console.log("Duplicate prevention working as expected");
+ return;
+ }
+
+ // Add to processed events immediately
+ console.log("✅ New event - processing:", eventKey);
+ console.log("Event type being processed:", event.type);
+ this.processedEvents.add(eventKey);
+
+ // Generate message key for processing lock
+ const messageKey = eventKey; // Use same key for consistency
+ const currentTime = Date.now();
+
+ try {
+ // Check if message is currently being processed
+ if (this.messageProcessingLock.has(messageKey)) {
+ console.log("⚠️ Message is currently being processed - skipping");
+ return;
+ }
+
+ // Add to processing lock
+ console.log("🔒 Step 2: Adding message to processing lock");
+ this.messageProcessingLock.add(messageKey);
+
+ try {
+ // Ignore messages from bots (including ourselves)
+ if (event.bot_id || event.user === this.botUserId) {
+ console.log("⚠️ Message from bot or self - skipping");
+ return;
+ }
+
+ // Clean the message text
+ console.log("🧹 Step 3: Cleaning message text");
+ const cleanedText = this.cleanMessage(event.text || '');
+ if (!cleanedText) {
+ console.log("⚠️ Empty message after cleaning - skipping");
+ return;
+ }
+
+ // Generate unique IDs
+ console.log("🔑 Step 4: Generating conversation IDs");
+ const roomId = stringToUuid(`${event.channel}-${this.runtime.agentId}`);
+ const userId = stringToUuid(`${event.user}-${this.runtime.agentId}`);
+ const messageId = stringToUuid(`${event.ts}-${this.runtime.agentId}`);
+
+ // Create initial memory
+ console.log("💾 Step 5: Creating initial memory");
+ const content: Content = {
+ text: cleanedText,
+ source: 'slack',
+ inReplyTo: event.thread_ts ? stringToUuid(`${event.thread_ts}-${this.runtime.agentId}`) : undefined
+ };
+
+ const memory: Memory = {
+ id: messageId,
+ userId,
+ agentId: this.runtime.agentId,
+ roomId,
+ content,
+ createdAt: new Date(parseFloat(event.ts) * 1000).getTime(),
+ embedding: getEmbeddingZeroVector(),
+ };
+
+ // Add memory
+ if (content.text) {
+ console.log("💾 Step 6: Saving initial memory");
+ await this.runtime.messageManager.createMemory(memory);
+ }
+
+ // Initial state composition
+ console.log("🔄 Step 7: Composing initial state");
+ let state = await this.runtime.composeState(
+ { content, userId, agentId: this.runtime.agentId, roomId },
+ {
+ slackClient: this.client,
+ slackEvent: event,
+ agentName: this.runtime.character.name,
+ senderName: event.user_name || event.user
+ }
+ );
+
+ // Update state with recent messages
+ console.log("🔄 Step 8: Updating state with recent messages");
+ state = await this.runtime.updateRecentMessageState(state);
+
+ // Check if we should respond
+ console.log("🤔 Step 9: Checking if we should respond");
+ const shouldRespond = await this._shouldRespond(event, state);
+
+ if (shouldRespond) {
+ console.log("✅ Step 10: Should respond - generating response");
+ const context = composeContext({
+ state,
+ template: this.runtime.character.templates?.slackMessageHandlerTemplate || slackMessageHandlerTemplate,
+ });
+
+ const responseContent = await this._generateResponse(memory, state, context);
+
+ if (responseContent?.text) {
+ console.log("📤 Step 11: Preparing to send response");
+
+ const callback: HandlerCallback = async (content: Content) => {
+ try {
+ console.log(" Step 12: Executing response callback");
+ const result = await this.client.chat.postMessage({
+ channel: event.channel,
+ text: content.text || responseContent.text,
+ thread_ts: event.thread_ts
+ });
+
+ console.log("💾 Step 13: Creating response memory");
+ const responseMemory: Memory = {
+ id: stringToUuid(`${result.ts}-${this.runtime.agentId}`),
+ userId: this.runtime.agentId,
+ agentId: this.runtime.agentId,
+ roomId,
+ content: {
+ ...content,
+ text: content.text || responseContent.text,
+ inReplyTo: messageId
+ },
+ createdAt: Date.now(),
+ embedding: getEmbeddingZeroVector(),
+ };
+
+ console.log("✓ Step 14: Marking message as processed");
+ this.processedMessages.set(messageKey, currentTime);
+
+ console.log("💾 Step 15: Saving response memory");
+ await this.runtime.messageManager.createMemory(responseMemory);
+
+ return [responseMemory];
+ } catch (error) {
+ console.error("❌ Error in callback:", error);
+ return [];
+ }
+ };
+
+ console.log("📤 Step 16: Sending initial response");
+ const responseMessages = await callback(responseContent);
+
+ console.log("🔄 Step 17: Updating state after response");
+ state = await this.runtime.updateRecentMessageState(state);
+
+ if (responseContent.action) {
+ console.log("⚡ Step 18: Processing actions");
+ await this.runtime.processActions(
+ memory,
+ responseMessages,
+ state,
+ callback
+ );
+ }
+ }
+ } else {
+ console.log("⏭️ Should not respond - skipping");
+ this.processedMessages.set(messageKey, currentTime);
+ }
+ } finally {
+ console.log("🔓 Final Step: Removing message from processing lock");
+ this.messageProcessingLock.delete(messageKey);
+ }
+ } catch (error) {
+ console.error("❌ Error in message handling:", error);
+ this.messageProcessingLock.delete(messageKey);
+ }
+ }
+}
\ No newline at end of file
diff --git a/packages/client-slack/src/providers/channelState.ts b/packages/client-slack/src/providers/channelState.ts
new file mode 100644
index 00000000000..1ef0f71bc75
--- /dev/null
+++ b/packages/client-slack/src/providers/channelState.ts
@@ -0,0 +1,43 @@
+import { IAgentRuntime, Memory, Provider, State } from "@ai16z/eliza";
+
+interface SlackEvent {
+ channel: string;
+ channel_type: string;
+ thread_ts?: string;
+ user?: string;
+ team?: string;
+}
+
+export const channelStateProvider: Provider = {
+ get: async (runtime: IAgentRuntime, message: Memory, state?: State) => {
+ const slackEvent = state?.slackEvent as SlackEvent | undefined;
+ if (!slackEvent) {
+ return "";
+ }
+
+ const agentName = state?.agentName || "The agent";
+ const senderName = state?.senderName || "someone";
+ const channelId = slackEvent.channel;
+ const channelType = slackEvent.channel_type;
+
+ // For direct messages
+ if (channelType === 'im') {
+ return `${agentName} is currently in a direct message conversation with ${senderName}`;
+ }
+
+ // For channel messages
+ let response = `${agentName} is currently having a conversation in the Slack channel <#${channelId}>`;
+
+ // Add thread context if in a thread
+ if (slackEvent.thread_ts) {
+ response += ` in a thread`;
+ }
+
+ // Add team context if available
+ if (slackEvent.team) {
+ response += ` in the workspace ${slackEvent.team}`;
+ }
+
+ return response;
+ },
+};
\ No newline at end of file
diff --git a/packages/client-slack/src/providers/slack-client.provider.ts b/packages/client-slack/src/providers/slack-client.provider.ts
new file mode 100644
index 00000000000..7499592ec9b
--- /dev/null
+++ b/packages/client-slack/src/providers/slack-client.provider.ts
@@ -0,0 +1,80 @@
+import { WebClient } from '@slack/web-api';
+import { SlackConfig, SlackClientContext } from '../types/slack-types';
+import { SlackUtils, RetryOptions } from '../utils/slack-utils';
+
+export class SlackClientProvider {
+ private client: WebClient;
+ private config: SlackConfig;
+ private retryOptions: RetryOptions;
+
+ constructor(config: SlackConfig, retryOptions: RetryOptions = {}) {
+ this.config = config;
+ this.client = new WebClient(config.botToken);
+ this.retryOptions = {
+ maxRetries: 3,
+ initialDelay: 1000,
+ maxDelay: 5000,
+ ...retryOptions,
+ };
+ }
+
+ public getContext(): SlackClientContext {
+ return {
+ client: this.client,
+ config: this.config,
+ };
+ }
+
+ public async validateConnection(): Promise {
+ try {
+ const result = await SlackUtils.withRateLimit(
+ () => this.client.auth.test(),
+ this.retryOptions
+ );
+
+ if (result.ok) {
+ this.config.botId = result.user_id || this.config.botId;
+ console.log('Bot ID:', this.config.botId);
+ return true;
+ }
+ return false;
+ } catch (error) {
+ console.error('Slack connection validation failed:', error);
+ return false;
+ }
+ }
+
+ public async sendMessage(channel: string, text: string): Promise {
+ return SlackUtils.sendMessageWithRetry(
+ this.client,
+ channel,
+ text,
+ this.retryOptions
+ );
+ }
+
+ public async replyInThread(channel: string, threadTs: string, text: string): Promise {
+ return SlackUtils.replyInThread(
+ this.client,
+ channel,
+ threadTs,
+ text,
+ this.retryOptions
+ );
+ }
+
+ public async validateChannel(channelId: string): Promise {
+ return SlackUtils.validateChannel(this.client, channelId);
+ }
+
+ public formatMessage(text: string, options?: {
+ blocks?: any[];
+ attachments?: any[];
+ }) {
+ return SlackUtils.formatMessage(text, options);
+ }
+
+ public async withRateLimit(fn: () => Promise): Promise {
+ return SlackUtils.withRateLimit(fn, this.retryOptions);
+ }
+}
\ No newline at end of file
diff --git a/packages/client-slack/src/services/slack.service.ts b/packages/client-slack/src/services/slack.service.ts
new file mode 100644
index 00000000000..8cf241bcd0d
--- /dev/null
+++ b/packages/client-slack/src/services/slack.service.ts
@@ -0,0 +1,23 @@
+import { Service, IAgentRuntime, ServiceType } from "@ai16z/eliza";
+import { WebClient } from "@slack/web-api";
+import { ISlackService } from "../types/slack-types";
+
+export class SlackService extends Service implements ISlackService {
+ public client: WebClient;
+
+ static get serviceType(): ServiceType {
+ return ServiceType.SLACK;
+ }
+
+ get serviceType(): ServiceType {
+ return ServiceType.SLACK;
+ }
+
+ async initialize(runtime: IAgentRuntime): Promise {
+ const token = runtime.getSetting("SLACK_BOT_TOKEN");
+ if (!token) {
+ throw new Error("SLACK_BOT_TOKEN is required");
+ }
+ this.client = new WebClient(token);
+ }
+}
\ No newline at end of file
diff --git a/packages/client-slack/src/templates.ts b/packages/client-slack/src/templates.ts
new file mode 100644
index 00000000000..dd7ecf6eb1b
--- /dev/null
+++ b/packages/client-slack/src/templates.ts
@@ -0,0 +1,99 @@
+import { messageCompletionFooter, shouldRespondFooter } from "@ai16z/eliza";
+
+export const slackShouldRespondTemplate =
+ `# Task: Decide if {{agentName}} should respond.
+About {{agentName}}:
+{{bio}}
+
+# INSTRUCTIONS: Determine if {{agentName}} should respond to the message and participate in the conversation. Do not comment. Just respond with "RESPOND" or "IGNORE" or "STOP".
+
+# RESPONSE EXAMPLES
+: Hey everyone, what's up?
+: Not much, just working
+Result: [IGNORE]
+
+{{agentName}}: I can help with that task
+: thanks!
+: @{{agentName}} can you explain more?
+Result: [RESPOND]
+
+: @{{agentName}} shut up
+Result: [STOP]
+
+: Hey @{{agentName}}, can you help me with something?
+Result: [RESPOND]
+
+: @{{agentName}} please stop
+Result: [STOP]
+
+: I need help
+{{agentName}}: How can I help you?
+: Not you, I need someone else
+Result: [IGNORE]
+
+Response options are [RESPOND], [IGNORE] and [STOP].
+
+{{agentName}} is in a Slack channel with other users and is very mindful about not being disruptive.
+Respond with [RESPOND] to messages that:
+- Directly mention @{{agentName}}
+- Are follow-ups to {{agentName}}'s previous messages
+- Are relevant to ongoing conversations {{agentName}} is part of
+
+Respond with [IGNORE] to messages that:
+- Are not directed at {{agentName}}
+- Are general channel chatter
+- Are very short or lack context
+- Are part of conversations {{agentName}} isn't involved in
+
+Respond with [STOP] when:
+- Users explicitly ask {{agentName}} to stop or be quiet
+- The conversation with {{agentName}} has naturally concluded
+- Users express frustration with {{agentName}}
+
+IMPORTANT: {{agentName}} should err on the side of [IGNORE] if there's any doubt about whether to respond.
+Only respond when explicitly mentioned or when clearly part of an ongoing conversation.
+
+{{recentMessages}}
+
+# INSTRUCTIONS: Choose the option that best describes {{agentName}}'s response to the last message. Ignore messages if they are not directed at {{agentName}}.
+` + shouldRespondFooter;
+
+export const slackMessageHandlerTemplate =
+ `# Action Examples
+{{actionExamples}}
+(Action examples are for reference only. Do not use the information from them in your response.)
+
+# Knowledge
+{{knowledge}}
+
+# Task: Generate dialog and actions for the character {{agentName}} in Slack.
+About {{agentName}}:
+{{bio}}
+{{lore}}
+
+Examples of {{agentName}}'s dialog and actions:
+{{characterMessageExamples}}
+
+{{providers}}
+
+{{attachments}}
+
+{{actions}}
+
+# Capabilities
+Note that {{agentName}} is capable of reading/seeing/hearing various forms of media, including images, videos, audio, plaintext and PDFs. Recent attachments have been included above under the "Attachments" section.
+
+# Conversation Flow Rules
+1. Only continue the conversation if the user has explicitly mentioned {{agentName}} or is directly responding to {{agentName}}'s last message
+2. Do not use the CONTINUE action unless explicitly asked to continue by the user
+3. Wait for user input before generating additional responses
+4. Keep responses focused and concise
+5. If a conversation is naturally concluding, let it end gracefully
+
+{{messageDirections}}
+
+{{recentMessages}}
+
+# Instructions: Write the next message for {{agentName}}. Include an action, if appropriate. {{actionNames}}
+Remember to follow the conversation flow rules above.
+` + messageCompletionFooter;
\ No newline at end of file
diff --git a/packages/client-slack/src/tests/setup.ts b/packages/client-slack/src/tests/setup.ts
new file mode 100644
index 00000000000..fdb2f0ab956
--- /dev/null
+++ b/packages/client-slack/src/tests/setup.ts
@@ -0,0 +1,203 @@
+import { jest } from "@jest/globals";
+import type { Mocked } from "jest-mock";
+import { config } from "dotenv";
+import { resolve } from "path";
+import { WebClient } from "@slack/web-api";
+import type {
+ AuthTestResponse,
+ ChatPostMessageResponse,
+ ConversationsInfoResponse,
+ FilesUploadResponse,
+} from "@slack/web-api";
+
+// Load test environment variables
+const envPath = resolve(__dirname, "../../../../.env");
+console.log("Loading test environment from:", envPath);
+config({ path: envPath });
+
+// Set up test environment variables if not present
+const testEnvVars = {
+ SLACK_APP_ID: "test-app-id",
+ SLACK_CLIENT_ID: "test-client-id",
+ SLACK_CLIENT_SECRET: "test-client-secret",
+ SLACK_SIGNING_SECRET: "test-signing-secret",
+ SLACK_VERIFICATION_TOKEN: "test-verification-token",
+ SLACK_BOT_TOKEN: "test-bot-token",
+ SLACK_CHANNEL_ID: "test-channel-id",
+ SLACK_BOT_ID: "test-bot-id",
+};
+
+Object.entries(testEnvVars).forEach(([key, value]) => {
+ if (!process.env[key]) {
+ process.env[key] = value;
+ }
+});
+
+// Create base mock functions with proper return types
+const mockAuthTest = jest
+ .fn<() => Promise>()
+ .mockResolvedValue({
+ ok: true,
+ url: "https://test.slack.com",
+ team: "test-team",
+ user: "test-user",
+ team_id: "T123456",
+ user_id: "U123456",
+ });
+
+const mockPostMessage = jest
+ .fn<() => Promise>()
+ .mockResolvedValue({
+ ok: true,
+ channel: "C123456",
+ ts: "1234567890.123456",
+ message: {
+ text: "test message",
+ ts: "1234567890.123456",
+ type: "message",
+ },
+ });
+
+const mockConversationsInfo = jest
+ .fn<() => Promise>()
+ .mockResolvedValue({
+ ok: true,
+ channel: {
+ id: "C123456",
+ name: "test-channel",
+ is_channel: true,
+ created: 12345678,
+ },
+ });
+
+const mockFilesUpload = jest
+ .fn<() => Promise>()
+ .mockResolvedValue({
+ ok: true,
+ file: {
+ id: "F123456",
+ name: "test-file",
+ title: "test-file",
+ mimetype: "text/plain",
+ filetype: "text",
+ pretty_type: "Plain Text",
+ user: "U123456",
+ size: 12345,
+ mode: "hosted",
+ is_external: false,
+ external_type: "",
+ is_public: true,
+ public_url_shared: false,
+ display_as_bot: false,
+ username: "",
+ url_private: "https://test.slack.com/files/test-file",
+ url_private_download:
+ "https://test.slack.com/files/test-file/download",
+ permalink: "https://test.slack.com/files/test-file/permalink",
+ permalink_public: "https://test.slack.com/files/test-file/public",
+ created: 12345678,
+ timestamp: 12345678,
+ channels: ["C123456"],
+ groups: [],
+ ims: [],
+ comments_count: 0,
+ },
+ });
+
+const mockFilesUploadV2 = jest
+ .fn<() => Promise>()
+ .mockResolvedValue({
+ ok: true,
+ file: {
+ id: "F123456",
+ created: 12345678,
+ timestamp: 12345678,
+ name: "test-file",
+ title: "test-file",
+ mimetype: "text/plain",
+ filetype: "text",
+ pretty_type: "Plain Text",
+ user: "U123456",
+ size: 12345,
+ mode: "hosted",
+ is_external: false,
+ external_type: "",
+ is_public: true,
+ public_url_shared: false,
+ display_as_bot: false,
+ username: "",
+ url_private: "https://test.slack.com/files/test-file",
+ url_private_download:
+ "https://test.slack.com/files/test-file/download",
+ permalink: "https://test.slack.com/files/test-file/permalink",
+ permalink_public: "https://test.slack.com/files/test-file/public",
+ channels: ["C123456"],
+ groups: [],
+ ims: [],
+ comments_count: 0,
+ },
+ });
+
+// Create mock WebClient
+const mockWebClient = {
+ slackApiUrl: "https://slack.com/api/",
+ token: "test-token",
+ apiCall: jest.fn(),
+ auth: {
+ test: mockAuthTest,
+ },
+ chat: {
+ postMessage: mockPostMessage,
+ },
+ conversations: {
+ info: mockConversationsInfo,
+ },
+ files: {
+ upload: mockFilesUpload,
+ uploadV2: mockFilesUploadV2,
+ },
+} as unknown as Mocked;
+
+// Mock the WebClient constructor
+jest.mock("@slack/web-api", () => ({
+ WebClient: jest.fn().mockImplementation(() => mockWebClient),
+}));
+
+// Helper function to get mock WebClient
+export function getMockWebClient(): Mocked {
+ return mockWebClient;
+}
+
+// Helper function to create mock Slack API responses
+export function createMockSlackResponse(ok: boolean, data: any = {}) {
+ return {
+ ok,
+ ...data,
+ };
+}
+
+// Helper function to simulate rate limiting
+export function simulateRateLimit(client: Mocked) {
+ const mockPostMessage = client.chat.postMessage as Mocked<
+ typeof client.chat.postMessage
+ >;
+ mockPostMessage.mockRejectedValueOnce(new Error("rate_limited"));
+}
+
+// Helper function to simulate network errors
+export function simulateNetworkError(client: Mocked) {
+ const mockPostMessage = client.chat.postMessage as Mocked<
+ typeof client.chat.postMessage
+ >;
+ mockPostMessage.mockRejectedValueOnce(new Error("network_error"));
+}
+
+// Global test setup
+beforeAll(() => {
+ jest.clearAllMocks();
+});
+
+// Reset mocks after each test
+afterEach(() => {
+ jest.clearAllMocks();
+});
diff --git a/packages/client-slack/src/tests/slack-client.provider.test.ts b/packages/client-slack/src/tests/slack-client.provider.test.ts
new file mode 100644
index 00000000000..d78e7a22770
--- /dev/null
+++ b/packages/client-slack/src/tests/slack-client.provider.test.ts
@@ -0,0 +1,164 @@
+import { describe, expect, test, jest, beforeEach } from '@jest/globals';
+import type { Mocked } from 'jest-mock';
+import { SlackClientProvider } from '../providers/slack-client.provider';
+import { SlackConfig } from '../types/slack-types';
+import { getMockWebClient, createMockSlackResponse } from './setup';
+import { WebClient } from '@slack/web-api';
+import type { AuthTestResponse, ChatPostMessageResponse, ConversationsInfoResponse } from '@slack/web-api';
+
+jest.mock('@slack/web-api');
+
+describe('SlackClientProvider', () => {
+ let provider: SlackClientProvider;
+ let mockWebClient: Mocked;
+ let mockConfig: SlackConfig;
+
+ beforeEach(() => {
+ jest.clearAllMocks();
+ mockConfig = {
+ appId: 'test-app-id',
+ clientId: 'test-client-id',
+ clientSecret: 'test-client-secret',
+ signingSecret: 'test-signing-secret',
+ verificationToken: 'test-verification-token',
+ botToken: 'test-bot-token',
+ botId: 'test-bot-id'
+ };
+ mockWebClient = getMockWebClient();
+ provider = new SlackClientProvider(mockConfig);
+ });
+
+ describe('Initialization', () => {
+ test('should create a provider instance with default retry options', () => {
+ expect(provider).toBeInstanceOf(SlackClientProvider);
+ const context = provider.getContext();
+ expect(context).toHaveProperty('client');
+ expect(context).toHaveProperty('config');
+ expect(context.config).toEqual(mockConfig);
+ });
+
+ test('should create a provider instance with custom retry options', () => {
+ const retryOptions = {
+ maxRetries: 5,
+ initialDelay: 2000,
+ maxDelay: 10000,
+ };
+ const providerWithOptions = new SlackClientProvider(mockConfig, retryOptions);
+ expect(providerWithOptions).toBeInstanceOf(SlackClientProvider);
+ });
+ });
+
+ describe('Connection Validation', () => {
+ test('should successfully validate connection', async () => {
+ const mockResponse = createMockSlackResponse(true, { user_id: 'test-bot-id' }) as AuthTestResponse;
+ const mockTest = mockWebClient.auth.test as Mocked;
+ mockTest.mockResolvedValue(mockResponse);
+
+ const result = await provider.validateConnection();
+ expect(result).toBe(true);
+ expect(mockTest).toHaveBeenCalled();
+ });
+
+ test('should handle failed connection validation', async () => {
+ const mockResponse = createMockSlackResponse(false) as AuthTestResponse;
+ const mockTest = mockWebClient.auth.test as Mocked;
+ mockTest.mockResolvedValue(mockResponse);
+
+ const result = await provider.validateConnection();
+ expect(result).toBe(false);
+ });
+
+ test('should handle connection errors', async () => {
+ const mockTest = mockWebClient.auth.test as Mocked;
+ mockTest.mockRejectedValue(new Error('Connection failed'));
+
+ const result = await provider.validateConnection();
+ expect(result).toBe(false);
+ });
+ });
+
+ describe('Message Sending', () => {
+ const channelId = 'test-channel';
+ const text = 'Hello, world!';
+
+ test('should successfully send a message', async () => {
+ const expectedResponse = createMockSlackResponse(true, { ts: '1234567890.123456' }) as ChatPostMessageResponse;
+ const mockPostMessage = mockWebClient.chat.postMessage as Mocked;
+ mockPostMessage.mockResolvedValue(expectedResponse);
+
+ const result = await provider.sendMessage(channelId, text);
+ expect(result).toEqual(expectedResponse);
+ expect(mockPostMessage).toHaveBeenCalledWith({
+ channel: channelId,
+ text
+ });
+ });
+
+ test('should handle rate limiting', async () => {
+ const mockResponse = createMockSlackResponse(true) as ChatPostMessageResponse;
+ const mockPostMessage = mockWebClient.chat.postMessage as Mocked;
+
+ mockPostMessage
+ .mockRejectedValueOnce(new Error('rate_limited'))
+ .mockResolvedValueOnce(mockResponse);
+
+ const result = await provider.sendMessage(channelId, text);
+ expect(result.ok).toBe(true);
+ expect(mockPostMessage).toHaveBeenCalledTimes(2);
+ });
+
+ test('should handle network errors', async () => {
+ const mockResponse = createMockSlackResponse(true) as ChatPostMessageResponse;
+ const mockPostMessage = mockWebClient.chat.postMessage as Mocked;
+
+ mockPostMessage
+ .mockRejectedValueOnce(new Error('network_error'))
+ .mockResolvedValueOnce(mockResponse);
+
+ const result = await provider.sendMessage(channelId, text);
+ expect(result.ok).toBe(true);
+ expect(mockPostMessage).toHaveBeenCalledTimes(2);
+ });
+ });
+
+ describe('Thread Replies', () => {
+ const channelId = 'test-channel';
+ const threadTs = '1234567890.123456';
+ const text = 'Thread reply';
+
+ test('should successfully reply in thread', async () => {
+ const expectedResponse = createMockSlackResponse(true, { ts: '1234567890.123457' }) as ChatPostMessageResponse;
+ const mockPostMessage = mockWebClient.chat.postMessage as Mocked;
+ mockPostMessage.mockResolvedValue(expectedResponse);
+
+ const result = await provider.replyInThread(channelId, threadTs, text);
+ expect(result).toEqual(expectedResponse);
+ expect(mockPostMessage).toHaveBeenCalledWith({
+ channel: channelId,
+ text,
+ thread_ts: threadTs
+ });
+ });
+ });
+
+ describe('Channel Validation', () => {
+ const channelId = 'test-channel';
+
+ test('should successfully validate channel', async () => {
+ const mockResponse = createMockSlackResponse(true) as ConversationsInfoResponse;
+ const mockInfo = mockWebClient.conversations.info as Mocked;
+ mockInfo.mockResolvedValue(mockResponse);
+
+ const result = await provider.validateChannel(channelId);
+ expect(result).toBe(true);
+ });
+
+ test('should handle invalid channel', async () => {
+ const mockInfo = mockWebClient.conversations.info as Mocked;
+ mockInfo.mockRejectedValue(new Error('Channel not found'));
+
+ const result = await provider.validateChannel(channelId);
+ expect(result).toBe(false);
+ });
+ });
+});
\ No newline at end of file
diff --git a/packages/client-slack/src/tests/test_image.png b/packages/client-slack/src/tests/test_image.png
new file mode 100644
index 00000000000..b711feb1fa3
Binary files /dev/null and b/packages/client-slack/src/tests/test_image.png differ
diff --git a/packages/client-slack/src/types/slack-types.ts b/packages/client-slack/src/types/slack-types.ts
new file mode 100644
index 00000000000..9a95d6b8ea2
--- /dev/null
+++ b/packages/client-slack/src/types/slack-types.ts
@@ -0,0 +1,39 @@
+import { WebClient } from '@slack/web-api';
+import { Service, ServiceType } from '@ai16z/eliza';
+
+export interface SlackConfig {
+ appId: string;
+ clientId: string;
+ clientSecret: string;
+ signingSecret: string;
+ verificationToken: string;
+ botToken: string;
+ botId: string;
+}
+
+export interface SlackClientContext {
+ client: any;
+ config: SlackConfig;
+}
+
+export interface SlackMessage {
+ text: string;
+ userId: string;
+ channelId: string;
+ threadTs?: string;
+ attachments?: Array<{
+ type: string;
+ url: string;
+ title: string;
+ size: number;
+ }>;
+}
+
+// We'll temporarily use TEXT_GENERATION as our service type
+// This is not ideal but allows us to work within current constraints
+export const SLACK_SERVICE_TYPE = ServiceType.TEXT_GENERATION;
+
+// Interface extending core Service
+export interface ISlackService extends Service {
+ client: WebClient;
+}
\ No newline at end of file
diff --git a/packages/client-slack/src/utils/slack-utils.ts b/packages/client-slack/src/utils/slack-utils.ts
new file mode 100644
index 00000000000..04afd2384b7
--- /dev/null
+++ b/packages/client-slack/src/utils/slack-utils.ts
@@ -0,0 +1,142 @@
+import { WebClient } from "@slack/web-api";
+
+export interface RetryOptions {
+ maxRetries?: number;
+ initialDelay?: number;
+ maxDelay?: number;
+}
+
+export interface MessageOptions extends RetryOptions {
+ threadTs?: string;
+}
+
+const DEFAULT_RETRY_OPTIONS: Required = {
+ maxRetries: 3,
+ initialDelay: 1000,
+ maxDelay: 5000,
+};
+
+export class SlackUtils {
+ /**
+ * Sends a message to a Slack channel with retry mechanism
+ */
+ static async sendMessageWithRetry(
+ client: WebClient,
+ channel: string,
+ text: string,
+ options: MessageOptions = {}
+ ) {
+ const { threadTs, ...retryOpts } = options;
+ const finalRetryOpts = { ...DEFAULT_RETRY_OPTIONS, ...retryOpts };
+ let lastError: Error | null = null;
+
+ for (let attempt = 0; attempt < finalRetryOpts.maxRetries; attempt++) {
+ try {
+ const result = await client.chat.postMessage({
+ channel,
+ text,
+ thread_ts: threadTs,
+ });
+ return result;
+ } catch (error) {
+ lastError = error as Error;
+ if (attempt < finalRetryOpts.maxRetries - 1) {
+ const delay = Math.min(
+ finalRetryOpts.initialDelay * Math.pow(2, attempt),
+ finalRetryOpts.maxDelay
+ );
+ await new Promise((resolve) => setTimeout(resolve, delay));
+ }
+ }
+ }
+
+ throw new Error(
+ `Failed to send message after ${finalRetryOpts.maxRetries} attempts: ${lastError?.message}`
+ );
+ }
+
+ /**
+ * Validates if a channel exists and is accessible
+ */
+ static async validateChannel(
+ client: WebClient,
+ channelId: string
+ ): Promise {
+ try {
+ const result = await client.conversations.info({
+ channel: channelId,
+ });
+ return result.ok === true;
+ } catch (error) {
+ console.error(error);
+ return false;
+ }
+ }
+
+ /**
+ * Formats a message for Slack with optional blocks
+ */
+ static formatMessage(
+ text: string,
+ options?: {
+ blocks?: any[];
+ attachments?: any[];
+ }
+ ) {
+ return {
+ text,
+ ...options,
+ };
+ }
+
+ /**
+ * Creates a thread reply
+ */
+ static async replyInThread(
+ client: WebClient,
+ channel: string,
+ threadTs: string,
+ text: string,
+ options: RetryOptions = {}
+ ) {
+ return this.sendMessageWithRetry(client, channel, text, {
+ ...options,
+ threadTs,
+ });
+ }
+
+ /**
+ * Handles rate limiting by implementing exponential backoff
+ */
+ static async withRateLimit(
+ fn: () => Promise,
+ options: RetryOptions = {}
+ ): Promise {
+ const retryOpts = { ...DEFAULT_RETRY_OPTIONS, ...options };
+ let lastError: Error | null = null;
+
+ for (let attempt = 0; attempt < retryOpts.maxRetries; attempt++) {
+ try {
+ return await fn();
+ } catch (error) {
+ lastError = error as Error;
+ if (
+ error instanceof Error &&
+ error.message.includes("rate_limited")
+ ) {
+ const delay = Math.min(
+ retryOpts.initialDelay * Math.pow(2, attempt),
+ retryOpts.maxDelay
+ );
+ await new Promise((resolve) => setTimeout(resolve, delay));
+ continue;
+ }
+ throw error;
+ }
+ }
+
+ throw new Error(
+ `Operation failed after ${retryOpts.maxRetries} attempts: ${lastError?.message}`
+ );
+ }
+}
diff --git a/packages/client-slack/tsconfig.json b/packages/client-slack/tsconfig.json
new file mode 100644
index 00000000000..2fdd804f2de
--- /dev/null
+++ b/packages/client-slack/tsconfig.json
@@ -0,0 +1,13 @@
+{
+ "extends": "../core/tsconfig.json",
+ "compilerOptions": {
+ "outDir": "dist",
+ "rootDir": "src",
+ "moduleResolution": "Bundler",
+ "allowImportingTsExtensions": true,
+ "allowArbitraryExtensions": true
+ },
+ "include": [
+ "src/**/*.ts"
+ ]
+}
\ No newline at end of file
diff --git a/packages/client-telegram/package.json b/packages/client-telegram/package.json
index c32af497105..597abf5e0c1 100644
--- a/packages/client-telegram/package.json
+++ b/packages/client-telegram/package.json
@@ -16,6 +16,6 @@
"scripts": {
"build": "tsup --format esm --dts",
"dev": "tsup --format esm --dts --watch",
- "lint": "eslint . --fix"
+ "lint": "eslint --fix --cache ."
}
}
diff --git a/packages/client-telegram/src/constants.ts b/packages/client-telegram/src/constants.ts
new file mode 100644
index 00000000000..f377019e1aa
--- /dev/null
+++ b/packages/client-telegram/src/constants.ts
@@ -0,0 +1,38 @@
+export const MESSAGE_CONSTANTS = {
+ MAX_MESSAGES: 50,
+ RECENT_MESSAGE_COUNT: 5,
+ CHAT_HISTORY_COUNT: 10,
+ DEFAULT_SIMILARITY_THRESHOLD: 0.6,
+ DEFAULT_SIMILARITY_THRESHOLD_FOLLOW_UPS: 0.4,
+ INTEREST_DECAY_TIME: 5 * 60 * 1000, // 5 minutes
+ PARTIAL_INTEREST_DECAY: 3 * 60 * 1000, // 3 minutes
+} as const;
+
+export const TIMING_CONSTANTS = {
+ TEAM_MEMBER_DELAY: 1500, // 1.5 seconds
+ TEAM_MEMBER_DELAY_MIN: 1000, // 1 second
+ TEAM_MEMBER_DELAY_MAX: 3000, // 3 seconds
+ LEADER_DELAY_MIN: 2000, // 2 seconds
+ LEADER_DELAY_MAX: 4000 // 4 seconds
+} as const;
+
+export const RESPONSE_CHANCES = {
+ AFTER_LEADER: 0.5, // 50% chance to respond after leader
+} as const;
+
+export const TEAM_COORDINATION = {
+ KEYWORDS: [
+ 'team',
+ 'everyone',
+ 'all agents',
+ 'team update',
+ 'gm team',
+ 'hello team',
+ 'hey team',
+ 'hi team',
+ 'morning team',
+ 'evening team',
+ 'night team',
+ 'update team',
+ ]
+} as const;
\ No newline at end of file
diff --git a/packages/client-telegram/src/messageManager.ts b/packages/client-telegram/src/messageManager.ts
index 6c400d514ec..de2894859f6 100644
--- a/packages/client-telegram/src/messageManager.ts
+++ b/packages/client-telegram/src/messageManager.ts
@@ -18,6 +18,14 @@ import { stringToUuid } from "@ai16z/eliza";
import { generateMessageResponse, generateShouldRespond } from "@ai16z/eliza";
import { messageCompletionFooter, shouldRespondFooter } from "@ai16z/eliza";
+import { cosineSimilarity } from "./utils";
+import {
+ MESSAGE_CONSTANTS,
+ TIMING_CONSTANTS,
+ RESPONSE_CHANCES,
+ TEAM_COORDINATION
+} from "./constants";
+
const MAX_MESSAGE_LENGTH = 4096; // Telegram's max message length
const telegramShouldRespondTemplate =
@@ -133,13 +141,223 @@ Thread of Tweets You Are Replying To:
{{formattedConversation}}
` + messageCompletionFooter;
+interface MessageContext {
+ content: string;
+ timestamp: number;
+}
+
+export type InterestChats = {
+ [key: string]: {
+ currentHandler: string | undefined;
+ lastMessageSent: number;
+ messages: { userId: UUID; userName: string; content: Content }[];
+ previousContext?: MessageContext;
+ contextSimilarityThreshold?: number;
+ };
+};
+
export class MessageManager {
public bot: Telegraf;
private runtime: IAgentRuntime;
+ private interestChats: InterestChats = {};
+ private teamMemberUsernames: Map = new Map();
constructor(bot: Telegraf, runtime: IAgentRuntime) {
this.bot = bot;
this.runtime = runtime;
+
+ this._initializeTeamMemberUsernames().catch(error =>
+ elizaLogger.error("Error initializing team member usernames:", error)
+ );
+ }
+
+ private async _initializeTeamMemberUsernames(): Promise {
+ if (!this.runtime.character.clientConfig?.telegram?.isPartOfTeam) return;
+
+ const teamAgentIds = this.runtime.character.clientConfig.telegram.teamAgentIds || [];
+
+ for (const id of teamAgentIds) {
+ try {
+ const chat = await this.bot.telegram.getChat(id);
+ if ('username' in chat && chat.username) {
+ this.teamMemberUsernames.set(id, chat.username);
+ elizaLogger.info(`Cached username for team member ${id}: ${chat.username}`);
+ }
+ } catch (error) {
+ elizaLogger.error(`Error getting username for team member ${id}:`, error);
+ }
+ }
+ }
+
+ private _getTeamMemberUsername(id: string): string | undefined {
+ return this.teamMemberUsernames.get(id);
+ }
+
+ private _getNormalizedUserId(id: string | number): string {
+ return id.toString().replace(/[^0-9]/g, '');
+ }
+
+ private _isTeamMember(userId: string | number): boolean {
+ const teamConfig = this.runtime.character.clientConfig?.telegram;
+ if (!teamConfig?.isPartOfTeam || !teamConfig.teamAgentIds) return false;
+
+ const normalizedUserId = this._getNormalizedUserId(userId);
+ return teamConfig.teamAgentIds.some(teamId =>
+ this._getNormalizedUserId(teamId) === normalizedUserId
+ );
+ }
+
+ private _isTeamLeader(): boolean {
+ return this.bot.botInfo?.id.toString() === this.runtime.character.clientConfig?.telegram?.teamLeaderId;
+ }
+
+ private _isTeamCoordinationRequest(content: string): boolean {
+ const contentLower = content.toLowerCase();
+ return TEAM_COORDINATION.KEYWORDS?.some(keyword =>
+ contentLower.includes(keyword.toLowerCase())
+ );
+ }
+
+ private _isRelevantToTeamMember(content: string, chatId: string, lastAgentMemory: Memory | null = null): boolean {
+ const teamConfig = this.runtime.character.clientConfig?.telegram;
+
+ // Check leader's context based on last message
+ if (this._isTeamLeader() && lastAgentMemory?.content.text) {
+ const timeSinceLastMessage = Date.now() - lastAgentMemory.createdAt;
+ if (timeSinceLastMessage > MESSAGE_CONSTANTS.INTEREST_DECAY_TIME) {
+ return false;
+ }
+
+ const similarity = cosineSimilarity(
+ content.toLowerCase(),
+ lastAgentMemory.content.text.toLowerCase()
+ );
+
+ return similarity >= MESSAGE_CONSTANTS.DEFAULT_SIMILARITY_THRESHOLD_FOLLOW_UPS;
+ }
+
+ // Check team member keywords
+ if (!teamConfig?.teamMemberInterestKeywords?.length) {
+ return false; // If no keywords defined, only leader maintains conversation
+ }
+
+ // Check if content matches any team member keywords
+ return teamConfig.teamMemberInterestKeywords.some(keyword =>
+ content.toLowerCase().includes(keyword.toLowerCase())
+ );
+ }
+
+ private async _analyzeContextSimilarity(currentMessage: string, previousContext?: MessageContext, agentLastMessage?: string): Promise {
+ if (!previousContext) return 1;
+
+ const timeDiff = Date.now() - previousContext.timestamp;
+ const timeWeight = Math.max(0, 1 - (timeDiff / (5 * 60 * 1000)));
+
+ const similarity = cosineSimilarity(
+ currentMessage.toLowerCase(),
+ previousContext.content.toLowerCase(),
+ agentLastMessage?.toLowerCase()
+ );
+
+ return similarity * timeWeight;
+ }
+
+ private async _shouldRespondBasedOnContext(message: Message, chatState: InterestChats[string]): Promise {
+ const messageText = 'text' in message ? message.text :
+ 'caption' in message ? (message as any).caption : '';
+
+ if (!messageText) return false;
+
+ // Always respond if mentioned
+ if (this._isMessageForMe(message)) return true;
+
+ // If we're not the current handler, don't respond
+ if (chatState?.currentHandler !== this.bot.botInfo?.id.toString()) return false;
+
+ // Check if we have messages to compare
+ if (!chatState.messages?.length) return false;
+
+ // Get last user message (not from the bot)
+ const lastUserMessage = [...chatState.messages]
+ .reverse()
+ .find((m, index) =>
+ index > 0 && // Skip first message (current)
+ m.userId !== this.runtime.agentId
+ );
+
+ if (!lastUserMessage) return false;
+
+ const lastSelfMemories = await this.runtime.messageManager.getMemories({
+ roomId: stringToUuid(message.chat.id.toString() + "-" + this.runtime.agentId),
+ unique: false,
+ count: 5
+ });
+
+ const lastSelfSortedMemories = lastSelfMemories?.filter(m => m.userId === this.runtime.agentId)
+ .sort((a, b) => (b.createdAt || 0) - (a.createdAt || 0));
+
+ // Calculate context similarity
+ const contextSimilarity = await this._analyzeContextSimilarity(
+ messageText,
+ {
+ content: lastUserMessage.content.text || '',
+ timestamp: Date.now()
+ },
+ lastSelfSortedMemories?.[0]?.content?.text
+ );
+
+ const similarityThreshold =
+ this.runtime.character.clientConfig?.telegram?.messageSimilarityThreshold ||
+ chatState.contextSimilarityThreshold ||
+ MESSAGE_CONSTANTS.DEFAULT_SIMILARITY_THRESHOLD;
+
+ return contextSimilarity >= similarityThreshold;
+ }
+
+ private _isMessageForMe(message: Message): boolean {
+ const botUsername = this.bot.botInfo?.username;
+ if (!botUsername) return false;
+
+ const messageText = 'text' in message ? message.text :
+ 'caption' in message ? (message as any).caption : '';
+ if (!messageText) return false;
+
+ const isMentioned = messageText.includes(`@${botUsername}`);
+ const hasUsername = messageText.toLowerCase().includes(botUsername.toLowerCase());
+
+ return isMentioned || (!this.runtime.character.clientConfig?.telegram?.shouldRespondOnlyToMentions && hasUsername);
+ }
+
+ private _checkInterest(chatId: string): boolean {
+ const chatState = this.interestChats[chatId];
+ if (!chatState) return false;
+
+ const lastMessage = chatState.messages[chatState.messages.length - 1];
+ const timeSinceLastMessage = Date.now() - chatState.lastMessageSent;
+
+ if (timeSinceLastMessage > MESSAGE_CONSTANTS.INTEREST_DECAY_TIME) {
+ delete this.interestChats[chatId];
+ return false;
+ } else if (timeSinceLastMessage > MESSAGE_CONSTANTS.PARTIAL_INTEREST_DECAY) {
+ return this._isRelevantToTeamMember(lastMessage?.content.text || '', chatId);
+ }
+
+ // Team leader specific checks
+ if (this._isTeamLeader() && chatState.messages.length > 0) {
+ if (!this._isRelevantToTeamMember(lastMessage?.content.text || '', chatId)) {
+ const recentTeamResponses = chatState.messages.slice(-3).some(m =>
+ m.userId !== this.runtime.agentId &&
+ this._isTeamMember(m.userId.toString())
+ );
+
+ if (recentTeamResponses) {
+ delete this.interestChats[chatId];
+ return false;
+ }
+ }
+ }
+
+ return true;
}
// Process image messages and generate descriptions
@@ -149,6 +367,8 @@ export class MessageManager {
try {
let imageUrl: string | null = null;
+ elizaLogger.info(`Telegram Message: ${message}`)
+
if ("photo" in message && message.photo?.length > 0) {
const photo = message.photo[message.photo.length - 1];
const fileLink = await this.bot.telegram.getFileLink(
@@ -186,11 +406,17 @@ export class MessageManager {
message: Message,
state: State
): Promise {
+
+ if (this.runtime.character.clientConfig?.telegram?.shouldRespondOnlyToMentions) {
+ return this._isMessageForMe(message);
+ }
+
// Respond if bot is mentioned
if (
"text" in message &&
message.text?.includes(`@${this.bot.botInfo?.username}`)
) {
+ elizaLogger.info(`Bot mentioned`)
return true;
}
@@ -208,6 +434,123 @@ export class MessageManager {
return false;
}
+ const chatId = message.chat.id.toString();
+ const chatState = this.interestChats[chatId];
+ const messageText = 'text' in message ? message.text :
+ 'caption' in message ? (message as any).caption : '';
+
+ // Check if team member has direct interest first
+ if (this.runtime.character.clientConfig?.discord?.isPartOfTeam &&
+ !this._isTeamLeader() &&
+ this._isRelevantToTeamMember(messageText, chatId)) {
+
+ return true;
+ }
+
+ // Team-based response logic
+ if (this.runtime.character.clientConfig?.telegram?.isPartOfTeam) {
+ // Team coordination
+ if(this._isTeamCoordinationRequest(messageText)) {
+ if (this._isTeamLeader()) {
+ return true;
+ } else {
+ const randomDelay = Math.floor(Math.random() * (TIMING_CONSTANTS.TEAM_MEMBER_DELAY_MAX - TIMING_CONSTANTS.TEAM_MEMBER_DELAY_MIN)) +
+ TIMING_CONSTANTS.TEAM_MEMBER_DELAY_MIN; // 1-3 second random delay
+ await new Promise(resolve => setTimeout(resolve, randomDelay));
+ return true;
+ }
+ }
+
+ if (!this._isTeamLeader() && this._isRelevantToTeamMember(messageText, chatId)) {
+ // Add small delay for non-leader responses
+ await new Promise(resolve => setTimeout(resolve, TIMING_CONSTANTS.TEAM_MEMBER_DELAY)); //1.5 second delay
+
+ // If leader has responded in last few seconds, reduce chance of responding
+ if (chatState.messages?.length) {
+ const recentMessages = chatState.messages.slice(-MESSAGE_CONSTANTS.RECENT_MESSAGE_COUNT);
+ const leaderResponded = recentMessages.some(m =>
+ m.userId === this.runtime.character.clientConfig?.telegram?.teamLeaderId &&
+ Date.now() - chatState.lastMessageSent < 3000
+ );
+
+ if (leaderResponded) {
+ // 50% chance to respond if leader just did
+ return Math.random() > RESPONSE_CHANCES.AFTER_LEADER;
+ }
+ }
+
+ return true;
+ }
+
+ // If I'm the leader but message doesn't match my keywords, add delay and check for team responses
+ if (this._isTeamLeader() && !this._isRelevantToTeamMember(messageText, chatId)) {
+ const randomDelay = Math.floor(Math.random() * (TIMING_CONSTANTS.LEADER_DELAY_MAX - TIMING_CONSTANTS.LEADER_DELAY_MIN)) +
+ TIMING_CONSTANTS.LEADER_DELAY_MIN; // 2-4 second random delay
+ await new Promise(resolve => setTimeout(resolve, randomDelay));
+
+ // After delay, check if another team member has already responded
+ if (chatState?.messages?.length) {
+ const recentResponses = chatState.messages.slice(-MESSAGE_CONSTANTS.RECENT_MESSAGE_COUNT);
+ const otherTeamMemberResponded = recentResponses.some(m =>
+ m.userId !== this.runtime.agentId &&
+ this._isTeamMember(m.userId)
+ );
+
+ if (otherTeamMemberResponded) {
+ return false;
+ }
+ }
+ }
+
+ // Update current handler if we're mentioned
+ if (this._isMessageForMe(message)) {
+ const channelState = this.interestChats[chatId];
+ if (channelState) {
+ channelState.currentHandler = this.bot.botInfo?.id.toString()
+ channelState.lastMessageSent = Date.now();
+ }
+ return true;
+ }
+
+ // Don't respond if another teammate is handling the conversation
+ if (chatState?.currentHandler) {
+ if (chatState.currentHandler !== this.bot.botInfo?.id.toString() &&
+ this._isTeamMember(chatState.currentHandler)) {
+ return false;
+ }
+ }
+
+ // Natural conversation cadence
+ if (!this._isMessageForMe(message) && this.interestChats[chatId]) {
+
+ const recentMessages = this.interestChats[chatId].messages
+ .slice(-MESSAGE_CONSTANTS.CHAT_HISTORY_COUNT);
+ const ourMessageCount = recentMessages.filter(m =>
+ m.userId === this.runtime.agentId
+ ).length;
+
+ if (ourMessageCount > 2) {
+
+ const responseChance = Math.pow(0.5, ourMessageCount - 2);
+ if (Math.random() > responseChance) {
+ return;
+ }
+ }
+ }
+
+ }
+
+ // Check context-based response for team conversations
+ if (chatState?.currentHandler) {
+ const shouldRespondContext = await this._shouldRespondBasedOnContext(message, chatState);
+
+ if (!shouldRespondContext) {
+ return false;
+ }
+
+ }
+
+
// Use AI to decide for text or captions
if ("text" in message || ("caption" in message && message.caption)) {
const shouldRespondContext = composeContext({
@@ -329,6 +672,124 @@ export class MessageManager {
}
const message = ctx.message;
+ const chatId = ctx.chat?.id.toString();
+ const messageText = 'text' in message ? message.text :
+ 'caption' in message ? (message as any).caption : '';
+
+ // Add team handling at the start
+ if (this.runtime.character.clientConfig?.telegram?.isPartOfTeam &&
+ !this.runtime.character.clientConfig?.telegram?.shouldRespondOnlyToMentions) {
+
+ const isDirectlyMentioned = this._isMessageForMe(message);
+ const hasInterest = this._checkInterest(chatId);
+
+
+ // Non-leader team member showing interest based on keywords
+ if (!this._isTeamLeader() && this._isRelevantToTeamMember(messageText, chatId)) {
+
+ this.interestChats[chatId] = {
+ currentHandler: this.bot.botInfo?.id.toString(),
+ lastMessageSent: Date.now(),
+ messages: []
+ };
+ }
+
+ const isTeamRequest = this._isTeamCoordinationRequest(messageText);
+ const isLeader = this._isTeamLeader();
+
+
+ // Check for continued interest
+ if (hasInterest && !isDirectlyMentioned) {
+ const lastSelfMemories = await this.runtime.messageManager.getMemories({
+ roomId: stringToUuid(chatId + "-" + this.runtime.agentId),
+ unique: false,
+ count: 5
+ });
+
+ const lastSelfSortedMemories = lastSelfMemories?.filter(m => m.userId === this.runtime.agentId)
+ .sort((a, b) => (b.createdAt || 0) - (a.createdAt || 0));
+
+ const isRelevant = this._isRelevantToTeamMember(
+ messageText,
+ chatId,
+ lastSelfSortedMemories?.[0]
+ );
+
+ if (!isRelevant) {
+ delete this.interestChats[chatId];
+ return;
+ }
+ }
+
+ // Handle team coordination requests
+ if (isTeamRequest) {
+ if (isLeader) {
+ this.interestChats[chatId] = {
+ currentHandler: this.bot.botInfo?.id.toString(),
+ lastMessageSent: Date.now(),
+ messages: []
+ };
+ } else {
+ this.interestChats[chatId] = {
+ currentHandler: this.bot.botInfo?.id.toString(),
+ lastMessageSent: Date.now(),
+ messages: []
+ };
+
+ if (!isDirectlyMentioned) {
+ this.interestChats[chatId].lastMessageSent = 0;
+ }
+
+ }
+ }
+
+ // Check for other team member mentions using cached usernames
+ const otherTeamMembers = this.runtime.character.clientConfig.telegram.teamAgentIds.filter(
+ id => id !== this.bot.botInfo?.id.toString()
+ );
+
+ const mentionedTeamMember = otherTeamMembers.find(id => {
+ const username = this._getTeamMemberUsername(id);
+ return username && messageText?.includes(`@${username}`);
+ });
+
+ // If another team member is mentioned, clear our interest
+ if (mentionedTeamMember) {
+ if (hasInterest || this.interestChats[chatId]?.currentHandler === this.bot.botInfo?.id.toString()) {
+ delete this.interestChats[chatId];
+
+ // Only return if we're not the mentioned member
+ if (!isDirectlyMentioned) {
+ return;
+ }
+ }
+ }
+
+ // Set/maintain interest only if we're mentioned or already have interest
+ if (isDirectlyMentioned) {
+ this.interestChats[chatId] = {
+ currentHandler: this.bot.botInfo?.id.toString(),
+ lastMessageSent: Date.now(),
+ messages: []
+ };
+ } else if (!isTeamRequest && !hasInterest) {
+ return;
+ }
+
+ // Update message tracking
+ if (this.interestChats[chatId]) {
+ this.interestChats[chatId].messages.push({
+ userId: stringToUuid(ctx.from.id.toString()),
+ userName: ctx.from.username || ctx.from.first_name || "Unknown User",
+ content: { text: messageText, source: "telegram" }
+ });
+
+ if (this.interestChats[chatId].messages.length > MESSAGE_CONSTANTS.MAX_MESSAGES) {
+ this.interestChats[chatId].messages =
+ this.interestChats[chatId].messages.slice(-MESSAGE_CONSTANTS.MAX_MESSAGES);
+ }
+ }
+ }
try {
// Convert IDs to UUIDs
@@ -505,4 +966,4 @@ export class MessageManager {
elizaLogger.error("Error sending message:", error);
}
}
-}
+}
\ No newline at end of file
diff --git a/packages/client-telegram/src/utils.ts b/packages/client-telegram/src/utils.ts
new file mode 100644
index 00000000000..86f0278f0e3
--- /dev/null
+++ b/packages/client-telegram/src/utils.ts
@@ -0,0 +1,97 @@
+export function cosineSimilarity(text1: string, text2: string, text3?: string): number {
+ const preprocessText = (text: string) => text
+ .toLowerCase()
+ .replace(/[^\w\s'_-]/g, ' ')
+ .replace(/\s+/g, ' ')
+ .trim();
+
+ const getWords = (text: string) => {
+ return text.split(' ').filter(word => word.length > 1);
+ };
+
+ const words1 = getWords(preprocessText(text1));
+ const words2 = getWords(preprocessText(text2));
+ const words3 = text3 ? getWords(preprocessText(text3)) : [];
+
+ const freq1: { [key: string]: number } = {};
+ const freq2: { [key: string]: number } = {};
+ const freq3: { [key: string]: number } = {};
+
+ words1.forEach(word => freq1[word] = (freq1[word] || 0) + 1);
+ words2.forEach(word => freq2[word] = (freq2[word] || 0) + 1);
+ if (words3.length) {
+ words3.forEach(word => freq3[word] = (freq3[word] || 0) + 1);
+ }
+
+ const uniqueWords = new Set([...Object.keys(freq1), ...Object.keys(freq2), ...(words3.length ? Object.keys(freq3) : [])]);
+
+ let dotProduct = 0;
+ let magnitude1 = 0;
+ let magnitude2 = 0;
+ let magnitude3 = 0;
+
+ uniqueWords.forEach(word => {
+ const val1 = freq1[word] || 0;
+ const val2 = freq2[word] || 0;
+ const val3 = freq3[word] || 0;
+
+ if (words3.length) {
+ // For three-way, calculate pairwise similarities
+ const sim12 = val1 * val2;
+ const sim23 = val2 * val3;
+ const sim13 = val1 * val3;
+
+ // Take maximum similarity between any pair
+ dotProduct += Math.max(sim12, sim23, sim13);
+ } else {
+ dotProduct += val1 * val2;
+ }
+
+ magnitude1 += val1 * val1;
+ magnitude2 += val2 * val2;
+ if (words3.length) {
+ magnitude3 += val3 * val3;
+ }
+ });
+
+ magnitude1 = Math.sqrt(magnitude1);
+ magnitude2 = Math.sqrt(magnitude2);
+ magnitude3 = words3.length ? Math.sqrt(magnitude3) : 1;
+
+ if (magnitude1 === 0 || magnitude2 === 0 || (words3.length && magnitude3 === 0)) return 0;
+
+ // For two texts, use original calculation
+ if (!words3.length) {
+ return dotProduct / (magnitude1 * magnitude2);
+ }
+
+ // For three texts, use max magnitude pair to maintain scale
+ const maxMagnitude = Math.max(
+ magnitude1 * magnitude2,
+ magnitude2 * magnitude3,
+ magnitude1 * magnitude3
+ );
+
+ return dotProduct / maxMagnitude;
+}
+
+/**
+ * Splits a message into chunks that fit within Telegram's message length limit
+ */
+export function splitMessage(text: string, maxLength: number = 4096): string[] {
+ const chunks: string[] = [];
+ let currentChunk = "";
+
+ const lines = text.split("\n");
+ for (const line of lines) {
+ if (currentChunk.length + line.length + 1 <= maxLength) {
+ currentChunk += (currentChunk ? "\n" : "") + line;
+ } else {
+ if (currentChunk) chunks.push(currentChunk);
+ currentChunk = line;
+ }
+ }
+
+ if (currentChunk) chunks.push(currentChunk);
+ return chunks;
+}
\ No newline at end of file
diff --git a/packages/client-twitter/package.json b/packages/client-twitter/package.json
index 89781953454..7bbc095b217 100644
--- a/packages/client-twitter/package.json
+++ b/packages/client-twitter/package.json
@@ -16,7 +16,7 @@
"scripts": {
"build": "tsup --format esm --dts",
"dev": "tsup --format esm --dts --watch",
- "lint": "eslint . --fix"
+ "lint": "eslint --fix --cache ."
},
"peerDependencies": {
"whatwg-url": "7.1.0"
diff --git a/packages/client-twitter/src/base.ts b/packages/client-twitter/src/base.ts
index 6693c5af0ae..255cac43bff 100644
--- a/packages/client-twitter/src/base.ts
+++ b/packages/client-twitter/src/base.ts
@@ -155,15 +155,18 @@ export class ClientBase extends EventEmitter {
async init() {
//test
const username = this.runtime.getSetting("TWITTER_USERNAME");
+ const password = this.runtime.getSetting("TWITTER_PASSWORD");
+ const email = this.runtime.getSetting("TWITTER_EMAIL");
+ const twitter2faSecret = this.runtime.getSetting("TWITTER_2FA_SECRET") || undefined;
+ const cookies = this.runtime.getSetting("TWITTER_COOKIES");
+
if (!username) {
throw new Error("Twitter username not configured");
}
// Check for Twitter cookies
- if (this.runtime.getSetting("TWITTER_COOKIES")) {
- const cookiesArray = JSON.parse(
- this.runtime.getSetting("TWITTER_COOKIES")
- );
+ if (cookies) {
+ const cookiesArray = JSON.parse(cookies);
await this.setCookiesFromArray(cookiesArray);
} else {
@@ -174,25 +177,36 @@ export class ClientBase extends EventEmitter {
}
elizaLogger.log("Waiting for Twitter login");
- while (true) {
- await this.twitterClient.login(
- username,
- this.runtime.getSetting("TWITTER_PASSWORD"),
- this.runtime.getSetting("TWITTER_EMAIL"),
- this.runtime.getSetting("TWITTER_2FA_SECRET") || undefined
- );
-
- if (await this.twitterClient.isLoggedIn()) {
- const cookies = await this.twitterClient.getCookies();
+ let retries = 5; // Optional: Set a retry limit
+ while (retries > 0) {
+ const cookies = await this.twitterClient.getCookies();
+ if (await this.twitterClient.isLoggedIn() || !!cookies) {
await this.cacheCookies(username, cookies);
+ elizaLogger.info("Successfully logged in and cookies cached.");
break;
}
- elizaLogger.error("Failed to login to Twitter trying again...");
+ try {
+ await this.twitterClient.login(
+ username,
+ password,
+ email,
+ twitter2faSecret
+ );
+ } catch (error) {
+ elizaLogger.error(`Login attempt failed: ${error.message}`);
+ }
+
+ retries--;
+ elizaLogger.error(`Failed to login to Twitter. Retrying... (${retries} attempts left)`);
+
+ if (retries === 0) {
+ elizaLogger.error("Max retries reached. Exiting login process.");
+ throw new Error("Twitter login failed after maximum retries.");
+ }
await new Promise((resolve) => setTimeout(resolve, 2000));
}
-
// Initialize Twitter profile
this.profile = await this.fetchProfile(username);
@@ -218,74 +232,67 @@ export class ClientBase extends EventEmitter {
await this.populateTimeline();
}
- async fetchHomeTimeline(count: number): Promise {
- elizaLogger.debug("fetching home timeline");
+ async fetchOwnPosts(count: number): Promise {
+ elizaLogger.debug("fetching own posts");
const homeTimeline = await this.twitterClient.getUserTweets(
this.profile.id,
count
);
-
- // console.dir(homeTimeline, { depth: Infinity });
-
return homeTimeline.tweets;
- // .filter((t) => t.__typename !== "TweetWithVisibilityResults")
- // .map((tweet) => {
- // // console.log("tweet is", tweet);
- // const obj = {
- // id: tweet.id,
- // name:
- // tweet.name ??
- // tweet. ?.user_results?.result?.legacy.name,
- // username:
- // tweet.username ??
- // tweet.core?.user_results?.result?.legacy.screen_name,
- // text: tweet.text ?? tweet.legacy?.full_text,
- // inReplyToStatusId:
- // tweet.inReplyToStatusId ??
- // tweet.legacy?.in_reply_to_status_id_str,
- // createdAt: tweet.createdAt ?? tweet.legacy?.created_at,
- // userId: tweet.userId ?? tweet.legacy?.user_id_str,
- // conversationId:
- // tweet.conversationId ??
- // tweet.legacy?.conversation_id_str,
- // hashtags: tweet.hashtags ?? tweet.legacy?.entities.hashtags,
- // mentions:
- // tweet.mentions ?? tweet.legacy?.entities.user_mentions,
- // photos:
- // tweet.photos ??
- // tweet.legacy?.entities.media?.filter(
- // (media) => media.type === "photo"
- // ) ??
- // [],
- // thread: [],
- // urls: tweet.urls ?? tweet.legacy?.entities.urls,
- // videos:
- // tweet.videos ??
- // tweet.legacy?.entities.media?.filter(
- // (media) => media.type === "video"
- // ) ??
- // [],
- // };
- // // console.log("obj is", obj);
- // return obj;
- // });
}
- async fetchFeedTimeline(count: number): Promise {
+ async fetchHomeTimeline(count: number): Promise {
elizaLogger.debug("fetching home timeline");
const homeTimeline = await this.twitterClient.fetchHomeTimeline(count, []);
- return homeTimeline
- .filter(tweet => tweet.text || tweet.legacy?.full_text)
- .sort((a, b) => {
- const timestampA = new Date(a.createdAt ?? a.legacy?.created_at).getTime();
- const timestampB = new Date(b.createdAt ?? b.legacy?.created_at).getTime();
- return timestampB - timestampA;
- })
- .slice(0, count)
- .map(tweet =>
- `@${tweet.username || tweet.core?.user_results?.result?.legacy?.screen_name}: ${tweet.text ?? tweet.legacy?.full_text ?? ''}`
- )
- .join('\n');
+
+ elizaLogger.debug(homeTimeline, { depth: Infinity });
+ const processedTimeline = homeTimeline
+ .filter((t) => t.__typename !== "TweetWithVisibilityResults") // what's this about?
+ .map((tweet) => {
+ //console.log("tweet is", tweet);
+ const obj = {
+ id: tweet.id,
+ name:
+ tweet.name ??
+ tweet?.user_results?.result?.legacy.name,
+ username:
+ tweet.username ??
+ tweet.core?.user_results?.result?.legacy.screen_name,
+ text: tweet.text ?? tweet.legacy?.full_text,
+ inReplyToStatusId:
+ tweet.inReplyToStatusId ??
+ tweet.legacy?.in_reply_to_status_id_str ??
+ null,
+ timestamp: new Date(tweet.legacy?.created_at).getTime() / 1000,
+ createdAt: tweet.createdAt ?? tweet.legacy?.created_at ?? tweet.core?.user_results?.result?.legacy.created_at,
+ userId: tweet.userId ?? tweet.legacy?.user_id_str,
+ conversationId:
+ tweet.conversationId ??
+ tweet.legacy?.conversation_id_str,
+ permanentUrl: `https://x.com/${tweet.core?.user_results?.result?.legacy?.screen_name}/status/${tweet.rest_id}`,
+ hashtags: tweet.hashtags ?? tweet.legacy?.entities.hashtags,
+ mentions:
+ tweet.mentions ?? tweet.legacy?.entities.user_mentions,
+ photos:
+ tweet.photos ??
+ tweet.legacy?.entities.media?.filter(
+ (media) => media.type === "photo"
+ ) ??
+ [],
+ thread: tweet.thread || [],
+ urls: tweet.urls ?? tweet.legacy?.entities.urls,
+ videos:
+ tweet.videos ??
+ tweet.legacy?.entities.media?.filter(
+ (media) => media.type === "video"
+ ) ??
+ [],
+ };
+ //console.log("obj is", obj);
+ return obj;
+ });
+ //elizaLogger.debug("process homeTimeline", processedTimeline);
+ return processedTimeline;
}
async fetchTimelineForActions(count: number): Promise {
@@ -476,10 +483,11 @@ export class ClientBase extends EventEmitter {
}
const timeline = await this.fetchHomeTimeline(cachedTimeline ? 10 : 50);
+ const username = this.runtime.getSetting("TWITTER_USERNAME");
// Get the most recent 20 mentions and interactions
const mentionsAndInteractions = await this.fetchSearchTweets(
- `@${this.runtime.getSetting("TWITTER_USERNAME")}`,
+ `@${username}`,
20,
SearchMode.Latest
);
diff --git a/packages/client-twitter/src/environment.ts b/packages/client-twitter/src/environment.ts
index 8b22d0dc59d..3340fc96740 100644
--- a/packages/client-twitter/src/environment.ts
+++ b/packages/client-twitter/src/environment.ts
@@ -23,7 +23,7 @@ export async function validateTwitterConfig(
runtime: IAgentRuntime
): Promise {
try {
- const config = {
+ const twitterConfig = {
TWITTER_DRY_RUN:
runtime.getSetting("TWITTER_DRY_RUN") ||
process.env.TWITTER_DRY_RUN ||
@@ -46,7 +46,7 @@ export async function validateTwitterConfig(
DEFAULT_MAX_TWEET_LENGTH.toString(),
};
- return twitterEnvSchema.parse(config);
+ return twitterEnvSchema.parse(twitterConfig);
} catch (error) {
if (error instanceof z.ZodError) {
const errorMessages = error.errors
diff --git a/packages/client-twitter/src/interactions.ts b/packages/client-twitter/src/interactions.ts
index 739b55bc283..1a83bc18937 100644
--- a/packages/client-twitter/src/interactions.ts
+++ b/packages/client-twitter/src/interactions.ts
@@ -53,7 +53,7 @@ Here is the current post text again. Remember to include an action if the curren
{{currentPost}}
` + messageCompletionFooter;
-export const twitterShouldRespondTemplate = (targetUsersStr: string) =>
+export const twitterShouldRespondTemplate = (targetUsersStr: string) =>
`# INSTRUCTIONS: Determine if {{agentName}} (@{{twitterUserName}}) should respond to the message and participate in the conversation. Do not comment. Just respond with "true" or "false".
Response options are RESPOND, IGNORE and STOP.
@@ -89,7 +89,6 @@ Thread of Tweets You Are Replying To:
export class TwitterInteractionClient {
client: ClientBase;
runtime: IAgentRuntime;
-
constructor(client: ClientBase, runtime: IAgentRuntime) {
this.client = client;
this.runtime = runtime;
@@ -133,7 +132,7 @@ export class TwitterInteractionClient {
.filter(u => u.length > 0); // Filter out empty strings after split
elizaLogger.log("Processing target users:", TARGET_USERS);
-
+
if (TARGET_USERS.length > 0) {
// Create a map to store tweets by user
const tweetsByUser = new Map();
@@ -142,24 +141,23 @@ export class TwitterInteractionClient {
for (const username of TARGET_USERS) {
try {
const userTweets = (await this.client.twitterClient.fetchSearchTweets(
- `from:${username}`,
- 3,
- SearchMode.Latest
+ `from:${username}`,
+ 3,
+ SearchMode.Latest
)).tweets;
// Filter for unprocessed, non-reply, recent tweets
const validTweets = userTweets.filter(tweet => {
- const isUnprocessed = !this.client.lastCheckedTweetId ||
- parseInt(tweet.id) > this.client.lastCheckedTweetId;
+ const isUnprocessed = !this.client.lastCheckedTweetId || parseInt(tweet.id) > this.client.lastCheckedTweetId;
const isRecent = (Date.now() - (tweet.timestamp * 1000)) < 2 * 60 * 60 * 1000;
-
+
elizaLogger.log(`Tweet ${tweet.id} checks:`, {
isUnprocessed,
isRecent,
isReply: tweet.isReply,
isRetweet: tweet.isRetweet
});
-
+
return isUnprocessed && !tweet.isReply && !tweet.isRetweet && isRecent;
});
@@ -191,7 +189,7 @@ export class TwitterInteractionClient {
elizaLogger.log("No target users configured, processing only mentions");
}
-
+
// Sort tweet candidates by ID in ascending order
uniqueTweetCandidates
@@ -359,17 +357,17 @@ export class TwitterInteractionClient {
const targetUsersStr = this.runtime.getSetting("TWITTER_TARGET_USERS");
// 2. Process the string to get valid usernames
- const validTargetUsersStr = targetUsersStr && targetUsersStr.trim()
+ const validTargetUsersStr = targetUsersStr && targetUsersStr.trim()
? targetUsersStr.split(',') // Split by commas: "user1,user2" -> ["user1", "user2"]
.map(u => u.trim()) // Remove whitespace: [" user1 ", "user2 "] -> ["user1", "user2"]
- .filter(u => u.length > 0)
- .join(',')
- : '';
+ .filter(u => u.length > 0)
+ .join(',')
+ : '';
const shouldRespondContext = composeContext({
state,
- template: this.runtime.character.templates?.twitterShouldRespondTemplate?.(validTargetUsersStr) ||
- this.runtime.character?.templates?.shouldRespondTemplate ||
+ template: this.runtime.character.templates?.twitterShouldRespondTemplate?.(validTargetUsersStr) ||
+ this.runtime.character?.templates?.shouldRespondTemplate ||
twitterShouldRespondTemplate(validTargetUsersStr),
});
@@ -448,7 +446,8 @@ export class TwitterInteractionClient {
await this.runtime.processActions(
message,
responseMessages,
- state
+ state,
+ callback
);
const responseInfo = `Context:\n\n${context}\n\nSelected Post: ${tweet.id} - ${tweet.username}: ${tweet.text}\nAgent's Output:\n${response.text}`;
diff --git a/packages/client-twitter/src/post.ts b/packages/client-twitter/src/post.ts
index 51737e8cfe5..11a8eee9a60 100644
--- a/packages/client-twitter/src/post.ts
+++ b/packages/client-twitter/src/post.ts
@@ -96,6 +96,7 @@ function truncateToCompleteSentence(
export class TwitterPostClient {
client: ClientBase;
runtime: IAgentRuntime;
+ twitterUsername: string;
private isProcessing: boolean = false;
private lastProcessTime: number = 0;
private stopProcessingActions: boolean = false;
@@ -111,7 +112,7 @@ export class TwitterPostClient {
timestamp: number;
}>(
"twitter/" +
- this.runtime.getSetting("TWITTER_USERNAME") +
+ this.twitterUsername +
"/lastPost"
);
@@ -136,8 +137,6 @@ export class TwitterPostClient {
elizaLogger.log(`Next tweet scheduled in ${randomMinutes} minutes`);
};
-
-
const processActionsLoop = async () => {
const actionInterval = parseInt(
this.runtime.getSetting("ACTION_INTERVAL")
@@ -172,6 +171,7 @@ export class TwitterPostClient {
if (postImmediately) {
await this.generateNewTweet();
}
+ generateNewTweetLoop();
// Add check for ENABLE_ACTION_PROCESSING before starting the loop
const enableActionProcessing = parseBooleanFromText(
@@ -185,11 +185,13 @@ export class TwitterPostClient {
} else {
elizaLogger.log("Action processing loop disabled by configuration");
}
+ generateNewTweetLoop();
}
constructor(client: ClientBase, runtime: IAgentRuntime) {
this.client = client;
this.runtime = runtime;
+ this.twitterUsername = runtime.getSetting("TWITTER_USERNAME");
}
private async generateNewTweet() {
@@ -250,11 +252,13 @@ export class TwitterPostClient {
cleanedContent = parsedResponse;
}
} catch (error) {
+ error.linted = true; // make linter happy since catch needs a variable
// If not JSON, clean the raw content
cleanedContent = newTweetContent
.replace(/^\s*{?\s*"text":\s*"|"\s*}?\s*$/g, '') // Remove JSON-like wrapper
.replace(/^['"](.*)['"]$/g, '$1') // Remove quotes
.replace(/\\"/g, '"') // Unescape quotes
+ .replace(/\\n/g, '\n') // Unescape newlines
.trim();
}
@@ -309,7 +313,7 @@ export class TwitterPostClient {
userId: this.client.profile.id,
inReplyToStatusId:
tweetResult.legacy.in_reply_to_status_id_str,
- permanentUrl: `https://twitter.com/${this.runtime.getSetting("TWITTER_USERNAME")}/status/${tweetResult.rest_id}`,
+ permanentUrl: `https://twitter.com/${this.twitterUsername}/status/${tweetResult.rest_id}`,
hashtags: [],
mentions: [],
photos: [],
@@ -374,7 +378,7 @@ export class TwitterPostClient {
console.log("generate tweet content response:\n" + response);
// First clean up any markdown and newlines
- let cleanedResponse = response
+ const cleanedResponse = response
.replace(/```json\s*/g, '') // Remove ```json
.replace(/```\s*/g, '') // Remove any remaining ```
.replaceAll(/\\n/g, "\n")
@@ -393,6 +397,8 @@ export class TwitterPostClient {
}
}
} catch (error) {
+ error.linted = true; // make linter happy since catch needs a variable
+
// If JSON parsing fails, treat as plain text
elizaLogger.debug('Response is not JSON, treating as plain text');
}
@@ -429,7 +435,7 @@ export class TwitterPostClient {
await this.runtime.ensureUserExists(
this.runtime.agentId,
- this.runtime.getSetting("TWITTER_USERNAME"),
+ this.twitterUsername,
this.runtime.character.name,
"twitter"
);
@@ -460,7 +466,7 @@ export class TwitterPostClient {
content: { text: "", action: "" },
},
{
- twitterUserName: this.runtime.getSetting("TWITTER_USERNAME"),
+ twitterUserName: this.twitterUsername,
currentTweet: `ID: ${tweet.id}\nFrom: ${tweet.name} (@${tweet.username})\nText: ${tweet.text}`,
}
);
@@ -546,7 +552,7 @@ export class TwitterPostClient {
content: { text: tweet.text, action: "QUOTE" }
},
{
- twitterUserName: this.runtime.getSetting("TWITTER_USERNAME"),
+ twitterUserName: this.twitterUsername,
currentPost: `From @${tweet.username}: ${tweet.text}`,
formattedConversation,
imageContext: imageDescriptions.length > 0
@@ -695,7 +701,7 @@ export class TwitterPostClient {
content: { text: tweet.text, action: "" }
},
{
- twitterUserName: this.runtime.getSetting("TWITTER_USERNAME"),
+ twitterUserName: this.twitterUsername,
currentPost: `From @${tweet.username}: ${tweet.text}`,
formattedConversation,
imageContext: imageDescriptions.length > 0
diff --git a/packages/client-twitter/src/search.ts b/packages/client-twitter/src/search.ts
index 8cfeef8dc74..27190c7531a 100644
--- a/packages/client-twitter/src/search.ts
+++ b/packages/client-twitter/src/search.ts
@@ -45,11 +45,13 @@ Your response should not contain any questions. Brief, concise statements only.
export class TwitterSearchClient {
client: ClientBase;
runtime: IAgentRuntime;
+ twitterUsername: string;
private respondedTweets: Set = new Set();
constructor(client: ClientBase, runtime: IAgentRuntime) {
this.client = client;
this.runtime = runtime;
+ this.twitterUsername = runtime.getSetting("TWITTER_USERNAME");
}
async start() {
@@ -108,13 +110,13 @@ export class TwitterSearchClient {
const prompt = `
Here are some tweets related to the search term "${searchTerm}":
-
+
${[...slicedTweets, ...homeTimeline]
.filter((tweet) => {
// ignore tweets where any of the thread tweets contain a tweet by the bot
const thread = tweet.thread;
const botTweet = thread.find(
- (t) => t.username === this.runtime.getSetting("TWITTER_USERNAME")
+ (t) => t.username === this.twitterUsername
);
return !botTweet;
})
@@ -126,7 +128,7 @@ export class TwitterSearchClient {
`
)
.join("\n")}
-
+
Which tweet is the most interesting and relevant for Ruby to reply to? Please provide only the ID of the tweet in your response.
Notes:
- Respond to English tweets only
@@ -155,10 +157,7 @@ export class TwitterSearchClient {
console.log("Selected tweet to reply to:", selectedTweet?.text);
- if (
- selectedTweet.username ===
- this.runtime.getSetting("TWITTER_USERNAME")
- ) {
+ if (selectedTweet.username === this.twitterUsername) {
console.log("Skipping tweet from bot itself");
return;
}
@@ -209,9 +208,7 @@ export class TwitterSearchClient {
const replies = selectedTweet.thread;
const replyContext = replies
.filter(
- (reply) =>
- reply.username !==
- this.runtime.getSetting("TWITTER_USERNAME")
+ (reply) => reply.username !== this.twitterUsername
)
.map((reply) => `@${reply.username}: ${reply.text}`)
.join("\n");
@@ -237,10 +234,10 @@ export class TwitterSearchClient {
let state = await this.runtime.composeState(message, {
twitterClient: this.client.twitterClient,
- twitterUserName: this.runtime.getSetting("TWITTER_USERNAME"),
+ twitterUserName: this.twitterUsername,
timeline: formattedHomeTimeline,
tweetContext: `${tweetBackground}
-
+
Original Post:
By @${selectedTweet.username}
${selectedTweet.text}${replyContext.length > 0 && `\nReplies to original post:\n${replyContext}`}
@@ -282,7 +279,7 @@ export class TwitterSearchClient {
this.client,
response,
message.roomId,
- this.runtime.getSetting("TWITTER_USERNAME"),
+ this.twitterUsername,
tweetId
);
return memories;
diff --git a/packages/client-twitter/src/utils.ts b/packages/client-twitter/src/utils.ts
index c69bf53130f..2fcbc6a9a7c 100644
--- a/packages/client-twitter/src/utils.ts
+++ b/packages/client-twitter/src/utils.ts
@@ -5,6 +5,9 @@ import { stringToUuid } from "@ai16z/eliza";
import { ClientBase } from "./base";
import { elizaLogger } from "@ai16z/eliza";
import { DEFAULT_MAX_TWEET_LENGTH } from "./environment";
+import { Media } from "@ai16z/eliza";
+import fs from "fs";
+import path from "path";
export const wait = (minTime: number = 1000, maxTime: number = 3000) => {
const waitTime =
@@ -162,6 +165,16 @@ export async function buildConversationThread(
return thread;
}
+export function getMediaType(attachment: Media) {
+ if (attachment.contentType?.startsWith("video")) {
+ return "video";
+ } else if (attachment.contentType?.startsWith("image")) {
+ return "image";
+ } else {
+ throw new Error(`Unsupported media type`);
+ }
+}
+
export async function sendTweet(
client: ClientBase,
content: Content,
@@ -178,11 +191,45 @@ export async function sendTweet(
let previousTweetId = inReplyTo;
for (const chunk of tweetChunks) {
+ let mediaData: { data: Buffer; mediaType: string }[] | undefined;
+
+ if (content.attachments && content.attachments.length > 0) {
+ mediaData = await Promise.all(
+ content.attachments.map(async (attachment: Media) => {
+ if (/^(http|https):\/\//.test(attachment.url)) {
+ // Handle HTTP URLs
+ const response = await fetch(attachment.url);
+ if (!response.ok) {
+ throw new Error(
+ `Failed to fetch file: ${attachment.url}`
+ );
+ }
+ const mediaBuffer = Buffer.from(
+ await response.arrayBuffer()
+ );
+ const mediaType = getMediaType(attachment);
+ return { data: mediaBuffer, mediaType };
+ } else if (fs.existsSync(attachment.url)) {
+ // Handle local file paths
+ const mediaBuffer = await fs.promises.readFile(
+ path.resolve(attachment.url)
+ );
+ const mediaType = getMediaType(attachment);
+ return { data: mediaBuffer, mediaType };
+ } else {
+ throw new Error(
+ `File not found: ${attachment.url}. Make sure the path is correct.`
+ );
+ }
+ })
+ );
+ }
const result = await client.requestQueue.add(
async () =>
await client.twitterClient.sendTweet(
chunk.trim(),
- previousTweetId
+ previousTweetId,
+ mediaData
)
);
const body = await result.json();
diff --git a/packages/client-whatsapp/package.json b/packages/client-whatsapp/package.json
new file mode 100644
index 00000000000..9a20800ecc6
--- /dev/null
+++ b/packages/client-whatsapp/package.json
@@ -0,0 +1,23 @@
+{
+ "name": "@ai16z/client-whatsapp",
+ "version": "0.0.1",
+ "description": "WhatsApp client for Eliza",
+ "main": "dist/index.js",
+ "types": "dist/index.d.ts",
+ "scripts": {
+ "build": "tsc",
+ "clean": "rimraf dist",
+ "test": "jest",
+ "lint": "eslint src --ext .ts"
+ },
+ "dependencies": {
+ "@ai16z/eliza": "workspace:*"
+ },
+ "devDependencies": {
+ "@types/node": "^20.0.0",
+ "typescript": "^5.0.0",
+ "rimraf": "^5.0.0",
+ "jest": "^29.0.0",
+ "@types/jest": "^29.0.0"
+ }
+}
\ No newline at end of file
diff --git a/packages/client-whatsapp/src/actions/index.ts b/packages/client-whatsapp/src/actions/index.ts
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/packages/client-whatsapp/src/environment.ts b/packages/client-whatsapp/src/environment.ts
new file mode 100644
index 00000000000..3c046860268
--- /dev/null
+++ b/packages/client-whatsapp/src/environment.ts
@@ -0,0 +1,21 @@
+import { IAgentRuntime } from "@ai16z/eliza";
+
+export async function validateWhatsAppConfig(
+ runtime: IAgentRuntime
+): Promise {
+ const requiredSettings = [
+ "WHATSAPP_API_TOKEN",
+ "WHATSAPP_PHONE_NUMBER_ID",
+ "WHATSAPP_APP_ID",
+ "WHATSAPP_APP_SECRET",
+ "WHATSAPP_WEBHOOK_VERIFY_TOKEN"
+ ];
+
+ for (const setting of requiredSettings) {
+ if (!runtime.getSetting(setting)) {
+ throw new Error(
+ `Missing required WhatsApp configuration: ${setting}`
+ );
+ }
+ }
+}
\ No newline at end of file
diff --git a/packages/client-whatsapp/src/index.ts b/packages/client-whatsapp/src/index.ts
new file mode 100644
index 00000000000..f38b9dae85d
--- /dev/null
+++ b/packages/client-whatsapp/src/index.ts
@@ -0,0 +1,28 @@
+import { elizaLogger } from "@ai16z/eliza";
+import { Client, IAgentRuntime } from "@ai16z/eliza";
+import { WhatsAppClient } from "./whatsappClient";
+import { validateWhatsAppConfig } from "../../../eliza/packages/client-whatsapp/src/environment";
+
+export const WhatsAppClientInterface: Client = {
+ start: async (runtime: IAgentRuntime) => {
+ await validateWhatsAppConfig(runtime);
+
+ const client = new WhatsAppClient(
+ runtime,
+ runtime.getSetting("WHATSAPP_API_TOKEN"),
+ runtime.getSetting("WHATSAPP_PHONE_NUMBER_ID")
+ );
+
+ await client.start();
+
+ elizaLogger.success(
+ `✅ WhatsApp client successfully started for character ${runtime.character.name}`
+ );
+ return client;
+ },
+ stop: async (runtime: IAgentRuntime) => {
+ elizaLogger.warn("WhatsApp client stopping...");
+ },
+};
+
+export default WhatsAppClientInterface;
\ No newline at end of file
diff --git a/packages/core/package.json b/packages/core/package.json
index 7b20169e411..daf8c5f3df7 100644
--- a/packages/core/package.json
+++ b/packages/core/package.json
@@ -7,7 +7,7 @@
"types": "dist/index.d.ts",
"scripts": {
"build": "tsup --format esm --dts",
- "lint": "eslint . --fix",
+ "lint": "eslint --fix --cache .",
"watch": "tsc --watch",
"dev": "tsup --format esm --dts --watch",
"build:docs": "cd docs && pnpm run build",
diff --git a/packages/core/src/environment.ts b/packages/core/src/environment.ts
index 8e5f3389366..0758d0d31d9 100644
--- a/packages/core/src/environment.ts
+++ b/packages/core/src/environment.ts
@@ -124,6 +124,11 @@ export const CharacterSchema = z.object({
nicknames: z.array(z.string()).optional(),
})
.optional(),
+ nft: z
+ .object({
+ prompt: z.string().optional(),
+ })
+ .optional(),
});
// Type inference
diff --git a/packages/core/src/generation.ts b/packages/core/src/generation.ts
index 6349359b5a7..ee0bea2e1bb 100644
--- a/packages/core/src/generation.ts
+++ b/packages/core/src/generation.ts
@@ -21,7 +21,7 @@ import {
parseJsonArrayFromText,
parseJSONObjectFromText,
parseShouldRespondFromText,
- parseActionResponseFromText
+ parseActionResponseFromText,
} from "./parsing.ts";
import settings from "./settings.ts";
import {
@@ -33,7 +33,7 @@ import {
ModelProviderName,
ServiceType,
SearchResponse,
- ActionResponse
+ ActionResponse,
} from "./types.ts";
import { fal } from "@fal-ai/client";
@@ -80,47 +80,68 @@ export async function generateText({
// allow character.json settings => secrets to override models
// FIXME: add MODEL_MEDIUM support
- switch(provider) {
+ switch (provider) {
// if runtime.getSetting("LLAMACLOUD_MODEL_LARGE") is true and modelProvider is LLAMACLOUD, then use the large model
- case ModelProviderName.LLAMACLOUD: {
- switch(modelClass) {
- case ModelClass.LARGE: {
- model = runtime.getSetting("LLAMACLOUD_MODEL_LARGE") || model;
- }
- break;
- case ModelClass.SMALL: {
- model = runtime.getSetting("LLAMACLOUD_MODEL_SMALL") || model;
+ case ModelProviderName.LLAMACLOUD:
+ {
+ switch (modelClass) {
+ case ModelClass.LARGE:
+ {
+ model =
+ runtime.getSetting("LLAMACLOUD_MODEL_LARGE") ||
+ model;
+ }
+ break;
+ case ModelClass.SMALL:
+ {
+ model =
+ runtime.getSetting("LLAMACLOUD_MODEL_SMALL") ||
+ model;
+ }
+ break;
}
- break;
}
- }
- break;
- case ModelProviderName.TOGETHER: {
- switch(modelClass) {
- case ModelClass.LARGE: {
- model = runtime.getSetting("TOGETHER_MODEL_LARGE") || model;
- }
- break;
- case ModelClass.SMALL: {
- model = runtime.getSetting("TOGETHER_MODEL_SMALL") || model;
+ break;
+ case ModelProviderName.TOGETHER:
+ {
+ switch (modelClass) {
+ case ModelClass.LARGE:
+ {
+ model =
+ runtime.getSetting("TOGETHER_MODEL_LARGE") ||
+ model;
+ }
+ break;
+ case ModelClass.SMALL:
+ {
+ model =
+ runtime.getSetting("TOGETHER_MODEL_SMALL") ||
+ model;
+ }
+ break;
}
- break;
}
- }
- break;
- case ModelProviderName.OPENROUTER: {
- switch(modelClass) {
- case ModelClass.LARGE: {
- model = runtime.getSetting("LARGE_OPENROUTER_MODEL") || model;
- }
- break;
- case ModelClass.SMALL: {
- model = runtime.getSetting("SMALL_OPENROUTER_MODEL") || model;
+ break;
+ case ModelProviderName.OPENROUTER:
+ {
+ switch (modelClass) {
+ case ModelClass.LARGE:
+ {
+ model =
+ runtime.getSetting("LARGE_OPENROUTER_MODEL") ||
+ model;
+ }
+ break;
+ case ModelClass.SMALL:
+ {
+ model =
+ runtime.getSetting("SMALL_OPENROUTER_MODEL") ||
+ model;
+ }
+ break;
}
- break;
}
- }
- break;
+ break;
}
elizaLogger.info("Selected model:", model);
@@ -157,7 +178,11 @@ export async function generateText({
case ModelProviderName.HYPERBOLIC:
case ModelProviderName.TOGETHER: {
elizaLogger.debug("Initializing OpenAI model.");
- const openai = createOpenAI({ apiKey, baseURL: endpoint });
+ const openai = createOpenAI({
+ apiKey,
+ baseURL: endpoint,
+ fetch: runtime.fetch,
+ });
const { text: openaiResponse } = await aiGenerateText({
model: openai.languageModel(model),
@@ -178,7 +203,9 @@ export async function generateText({
}
case ModelProviderName.GOOGLE: {
- const google = createGoogleGenerativeAI();
+ const google = createGoogleGenerativeAI({
+ fetch: runtime.fetch,
+ });
const { text: googleResponse } = await aiGenerateText({
model: google(model),
@@ -201,7 +228,10 @@ export async function generateText({
case ModelProviderName.ANTHROPIC: {
elizaLogger.debug("Initializing Anthropic model.");
- const anthropic = createAnthropic({ apiKey });
+ const anthropic = createAnthropic({
+ apiKey,
+ fetch: runtime.fetch,
+ });
const { text: anthropicResponse } = await aiGenerateText({
model: anthropic.languageModel(model),
@@ -224,7 +254,10 @@ export async function generateText({
case ModelProviderName.CLAUDE_VERTEX: {
elizaLogger.debug("Initializing Claude Vertex model.");
- const anthropic = createAnthropic({ apiKey });
+ const anthropic = createAnthropic({
+ apiKey,
+ fetch: runtime.fetch,
+ });
const { text: anthropicResponse } = await aiGenerateText({
model: anthropic.languageModel(model),
@@ -248,7 +281,11 @@ export async function generateText({
case ModelProviderName.GROK: {
elizaLogger.debug("Initializing Grok model.");
- const grok = createOpenAI({ apiKey, baseURL: endpoint });
+ const grok = createOpenAI({
+ apiKey,
+ baseURL: endpoint,
+ fetch: runtime.fetch,
+ });
const { text: grokResponse } = await aiGenerateText({
model: grok.languageModel(model, {
@@ -271,7 +308,7 @@ export async function generateText({
}
case ModelProviderName.GROQ: {
- const groq = createGroq({ apiKey });
+ const groq = createGroq({ apiKey, fetch: runtime.fetch });
const { text: groqResponse } = await aiGenerateText({
model: groq.languageModel(model),
@@ -318,7 +355,11 @@ export async function generateText({
case ModelProviderName.REDPILL: {
elizaLogger.debug("Initializing RedPill model.");
const serverUrl = models[provider].endpoint;
- const openai = createOpenAI({ apiKey, baseURL: serverUrl });
+ const openai = createOpenAI({
+ apiKey,
+ baseURL: serverUrl,
+ fetch: runtime.fetch,
+ });
const { text: redpillResponse } = await aiGenerateText({
model: openai.languageModel(model),
@@ -341,7 +382,11 @@ export async function generateText({
case ModelProviderName.OPENROUTER: {
elizaLogger.debug("Initializing OpenRouter model.");
const serverUrl = models[provider].endpoint;
- const openrouter = createOpenAI({ apiKey, baseURL: serverUrl });
+ const openrouter = createOpenAI({
+ apiKey,
+ baseURL: serverUrl,
+ fetch: runtime.fetch,
+ });
const { text: openrouterResponse } = await aiGenerateText({
model: openrouter.languageModel(model),
@@ -367,6 +412,7 @@ export async function generateText({
const ollamaProvider = createOllama({
baseURL: models[provider].endpoint + "/api",
+ fetch: runtime.fetch,
});
const ollama = ollamaProvider(model);
@@ -391,6 +437,7 @@ export async function generateText({
const heurist = createOpenAI({
apiKey: apiKey,
baseURL: endpoint,
+ fetch: runtime.fetch,
});
const { text: heuristResponse } = await aiGenerateText({
@@ -436,7 +483,11 @@ export async function generateText({
elizaLogger.debug("Using GAIANET model with baseURL:", baseURL);
- const openai = createOpenAI({ apiKey, baseURL: endpoint });
+ const openai = createOpenAI({
+ apiKey,
+ baseURL: endpoint,
+ fetch: runtime.fetch,
+ });
const { text: openaiResponse } = await aiGenerateText({
model: openai.languageModel(model),
@@ -461,6 +512,7 @@ export async function generateText({
const galadriel = createOpenAI({
apiKey: apiKey,
baseURL: endpoint,
+ fetch: runtime.fetch,
});
const { text: galadrielResponse } = await aiGenerateText({
@@ -481,6 +533,29 @@ export async function generateText({
break;
}
+ case ModelProviderName.VENICE: {
+ elizaLogger.debug("Initializing Venice model.");
+ const venice = createOpenAI({
+ apiKey: apiKey,
+ baseURL: endpoint,
+ });
+
+ const { text: veniceResponse } = await aiGenerateText({
+ model: venice.languageModel(model),
+ prompt: context,
+ system:
+ runtime.character.system ??
+ settings.SYSTEM_PROMPT ??
+ undefined,
+ temperature: temperature,
+ maxTokens: max_response_length,
+ });
+
+ response = veniceResponse;
+ elizaLogger.debug("Received response from Venice model.");
+ break;
+ }
+
default: {
const errorMessage = `Unsupported provider: ${provider}`;
elizaLogger.error(errorMessage);
@@ -722,7 +797,7 @@ export async function generateTextArray({
}
}
-export async function generateObjectDEPRECATED({
+export async function generateObjectDeprecated({
runtime,
context,
modelClass,
@@ -732,7 +807,7 @@ export async function generateObjectDEPRECATED({
modelClass: string;
}): Promise {
if (!context) {
- elizaLogger.error("generateObjectDEPRECATED context is empty");
+ elizaLogger.error("generateObjectDeprecated context is empty");
return null;
}
let retryDelay = 1000;
@@ -879,7 +954,8 @@ export const generateImage = async (
: (runtime.getSetting("HEURIST_API_KEY") ??
runtime.getSetting("TOGETHER_API_KEY") ??
runtime.getSetting("FAL_API_KEY") ??
- runtime.getSetting("OPENAI_API_KEY"));
+ runtime.getSetting("OPENAI_API_KEY") ??
+ runtime.getSetting("VENICE_API_KEY"));
try {
if (runtime.imageModelProvider === ModelProviderName.HEURIST) {
@@ -1025,6 +1101,42 @@ export const generateImage = async (
});
const base64s = await Promise.all(base64Promises);
+ return { success: true, data: base64s };
+ } else if (runtime.imageModelProvider === ModelProviderName.VENICE) {
+ const response = await fetch(
+ "https://api.venice.ai/api/v1/image/generate",
+ {
+ method: "POST",
+ headers: {
+ Authorization: `Bearer ${apiKey}`,
+ "Content-Type": "application/json",
+ },
+ body: JSON.stringify({
+ model: data.modelId || "fluently-xl",
+ prompt: data.prompt,
+ negative_prompt: data.negativePrompt,
+ width: data.width || 1024,
+ height: data.height || 1024,
+ steps: data.numIterations || 20,
+ }),
+ }
+ );
+
+ const result = await response.json();
+
+ if (!result.images || !Array.isArray(result.images)) {
+ throw new Error("Invalid response format from Venice AI");
+ }
+
+ const base64s = result.images.map((base64String) => {
+ if (!base64String) {
+ throw new Error(
+ "Empty base64 string in Venice AI response"
+ );
+ }
+ return `data:image/png;base64,${base64String}`;
+ });
+
return { success: true, data: base64s };
} else {
let targetSize = `${data.width}x${data.height}`;
@@ -1150,7 +1262,7 @@ interface ModelSettings {
* @returns {Promise} - A promise that resolves to an array of generated objects.
* @throws {Error} - Throws an error if the provider is unsupported or if generation fails.
*/
-export const generateObjectV2 = async ({
+export const generateObject = async ({
runtime,
context,
modelClass,
@@ -1161,7 +1273,7 @@ export const generateObjectV2 = async ({
mode = "json",
}: GenerationOptions): Promise> => {
if (!context) {
- const errorMessage = "generateObjectV2 context is empty";
+ const errorMessage = "generateObject context is empty";
console.error(errorMessage);
throw new Error(errorMessage);
}
@@ -1255,7 +1367,7 @@ export async function handleProvider(
case ModelProviderName.GROQ:
return await handleGroq(options);
case ModelProviderName.LLAMALOCAL:
- return await generateObjectDEPRECATED({
+ return await generateObjectDeprecated({
runtime,
context,
modelClass,
@@ -1516,7 +1628,10 @@ export async function generateTweetActions({
context,
modelClass,
});
- console.debug("Received response from generateText for tweet actions:", response);
+ console.debug(
+ "Received response from generateText for tweet actions:",
+ response
+ );
const { actions } = parseActionResponseFromText(response.trim());
if (actions) {
console.debug("Parsed tweet actions:", actions);
@@ -1539,4 +1654,4 @@ export async function generateTweetActions({
await new Promise((resolve) => setTimeout(resolve, retryDelay));
retryDelay *= 2;
}
-}
\ No newline at end of file
+}
diff --git a/packages/core/src/models.ts b/packages/core/src/models.ts
index a705f4f204c..1ad9cfa40c5 100644
--- a/packages/core/src/models.ts
+++ b/packages/core/src/models.ts
@@ -87,10 +87,10 @@ export const models: Models = {
},
endpoint: "https://api.x.ai/v1",
model: {
- [ModelClass.SMALL]: "grok-beta",
- [ModelClass.MEDIUM]: "grok-beta",
- [ModelClass.LARGE]: "grok-beta",
- [ModelClass.EMBEDDING]: "grok-beta", // not sure about this one
+ [ModelClass.SMALL]: settings.SMALL_GROK_MODEL || "grok-2-1212",
+ [ModelClass.MEDIUM]: settings.MEDIUM_GROK_MODEL || "grok-2-1212",
+ [ModelClass.LARGE]: settings.LARGE_GROK_MODEL || "grok-2-1212",
+ [ModelClass.EMBEDDING]: settings.EMBEDDING_GROK_MODEL || "grok-2-1212", // not sure about this one
},
},
[ModelProviderName.GROQ]: {
@@ -451,6 +451,21 @@ export const models: Models = {
[ModelClass.IMAGE]: settings.IMAGE_HYPERBOLIC_MODEL || "FLUX.1-dev",
},
},
+ [ModelProviderName.VENICE]: {
+ endpoint: "https://api.venice.ai/api/v1",
+ settings: {
+ stop: [],
+ maxInputTokens: 128000,
+ maxOutputTokens: 8192,
+ temperature: 0.6,
+ },
+ model: {
+ [ModelClass.SMALL]: settings.SMALL_VENICE_MODEL || "llama-3.3-70b",
+ [ModelClass.MEDIUM]: settings.MEDIUM_VENICE_MODEL || "llama-3.3-70b",
+ [ModelClass.LARGE]: settings.LARGE_VENICE_MODEL || "llama-3.1-405b",
+ [ModelClass.IMAGE]: settings.IMAGE_VENICE_MODEL || "fluently-xl",
+ },
+ },
};
export function getModel(provider: ModelProviderName, type: ModelClass) {
diff --git a/packages/core/src/runtime.ts b/packages/core/src/runtime.ts
index ca6c0b18ab1..7d37f1ee35c 100644
--- a/packages/core/src/runtime.ts
+++ b/packages/core/src/runtime.ts
@@ -142,6 +142,7 @@ export class AgentRuntime implements IAgentRuntime {
services: Map = new Map();
memoryManagers: Map = new Map();
cacheManager: ICacheManager;
+ clients: Record;
registerMemoryManager(manager: IMemoryManager): void {
if (!manager.tableName) {
@@ -405,6 +406,25 @@ export class AgentRuntime implements IAgentRuntime {
}
}
+ async stop() {
+ elizaLogger.debug('runtime::stop - character', this.character)
+ // stop services, they don't have a stop function
+ // just initialize
+
+ // plugins
+ // have actions, providers, evaluators (no start/stop)
+ // services (just initialized), clients
+
+ // client have a start
+ for(const cStr in this.clients) {
+ const c = this.clients[cStr]
+ elizaLogger.log('runtime::stop - requesting', cStr, 'client stop for', this.character.name)
+ c.stop()
+ }
+ // we don't need to unregister with directClient
+ // don't need to worry about knowledge
+ }
+
/**
* Processes character knowledge by creating document memories and fragment memories.
* This function takes an array of knowledge items, creates a document memory for each item if it doesn't exist,
diff --git a/packages/core/src/tests/actions.test.ts b/packages/core/src/tests/actions.test.ts
index f3ac2e32dee..ab0fcdfb915 100644
--- a/packages/core/src/tests/actions.test.ts
+++ b/packages/core/src/tests/actions.test.ts
@@ -4,13 +4,7 @@ import {
formatActionNames,
formatActions,
} from "../actions";
-import {
- Action,
- HandlerCallback,
- IAgentRuntime,
- Memory,
- State,
-} from "../types";
+import { Action } from "../types";
describe("Actions", () => {
const mockActions: Action[] = [
@@ -25,24 +19,14 @@ describe("Actions", () => {
content: { text: "Hi {{user1}}!", action: "wave" },
},
],
+ [
+ { user: "user1", content: { text: "Hey {{user2}}, how are you?" } },
+ { user: "user2", content: { text: "I'm good {{user1}}, thanks!" } },
+ ],
],
- similes: [],
- handler: function (
- _runtime: IAgentRuntime,
- _message: Memory,
- _state?: State,
- _options?: { [key: string]: unknown },
- _callback?: HandlerCallback
- ): Promise {
- throw new Error("Function not implemented.");
- },
- validate: function (
- _runtime: IAgentRuntime,
- _message: Memory,
- _state?: State
- ): Promise {
- throw new Error("Function not implemented.");
- },
+ similes: ["say hi", "welcome"],
+ handler: async () => { throw new Error("Not implemented"); },
+ validate: async () => { throw new Error("Not implemented"); },
},
{
name: "farewell",
@@ -50,73 +34,123 @@ describe("Actions", () => {
examples: [
[
{ user: "user1", content: { text: "Goodbye {{user2}}!" } },
+ { user: "user2", content: { text: "Bye {{user1}}!" } },
+ ],
+ ],
+ similes: ["say bye", "leave"],
+ handler: async () => { throw new Error("Not implemented"); },
+ validate: async () => { throw new Error("Not implemented"); },
+ },
+ {
+ name: "help",
+ description: "Get assistance",
+ examples: [
+ [
+ { user: "user1", content: { text: "Can you help me {{user2}}?" } },
{
user: "user2",
- content: { text: "See you later {{user1}}!" },
+ content: { text: "Of course {{user1}}, what do you need?", action: "assist" }
},
],
],
- similes: [],
- handler: function (
- _runtime: IAgentRuntime,
- _message: Memory,
- _state?: State,
- _options?: { [key: string]: unknown },
- _callback?: HandlerCallback
- ): Promise {
- throw new Error("Function not implemented.");
- },
- validate: function (
- _runtime: IAgentRuntime,
- _message: Memory,
- _state?: State
- ): Promise {
- throw new Error("Function not implemented.");
- },
+ similes: ["assist", "support"],
+ handler: async () => { throw new Error("Not implemented"); },
+ validate: async () => { throw new Error("Not implemented"); },
},
];
describe("composeActionExamples", () => {
- it("should generate the correct number of examples", () => {
- const result = composeActionExamples(mockActions, 1);
- const exampleLines = result
- .split("\n")
- .filter((line) => line.length > 0);
- expect(exampleLines.length).toBe(2); // Each example has 2 messages
+ it("should generate examples with correct format", () => {
+ const examples = composeActionExamples(mockActions, 1);
+ const lines = examples.trim().split("\n");
+ expect(lines.length).toBeGreaterThan(0);
+ expect(lines[0]).toMatch(/^user\d: .+/);
+ });
+
+ it("should replace user placeholders with generated names", () => {
+ const examples = composeActionExamples(mockActions, 1);
+ expect(examples).not.toContain("{{user1}}");
+ expect(examples).not.toContain("{{user2}}");
+ });
+
+ it("should handle empty actions array", () => {
+ const examples = composeActionExamples([], 5);
+ expect(examples).toBe("");
});
- it("should replace placeholder names with generated names", () => {
- const result = composeActionExamples(mockActions, 1);
- expect(result).not.toContain("{{user1}}");
- expect(result).not.toContain("{{user2}}");
+ it("should handle count larger than available examples", () => {
+ const examples = composeActionExamples(mockActions, 10);
+ expect(examples.length).toBeGreaterThan(0);
});
});
describe("formatActionNames", () => {
it("should format action names correctly", () => {
- const result = formatActionNames(mockActions);
- const names = result.split(", ").sort();
- expect(names).toEqual(["farewell", "greet"].sort());
+ const formatted = formatActionNames([mockActions[0], mockActions[1]]);
+ expect(formatted).toMatch(/^(greet|farewell)(, (greet|farewell))?$/);
+ });
+
+ it("should handle single action", () => {
+ const formatted = formatActionNames([mockActions[0]]);
+ expect(formatted).toBe("greet");
});
- it("should return empty string for empty array", () => {
- const result = formatActionNames([]);
- expect(result).toBe("");
+ it("should handle empty actions array", () => {
+ const formatted = formatActionNames([]);
+ expect(formatted).toBe("");
});
});
describe("formatActions", () => {
- it("should format actions with descriptions correctly", () => {
- const result = formatActions(mockActions);
- const formattedActions = result.split(",\n").sort();
- expect(formattedActions).toEqual(
- ["farewell: Say goodbye", "greet: Greet someone"].sort()
- );
+ it("should format actions with descriptions", () => {
+ const formatted = formatActions([mockActions[0]]);
+ expect(formatted).toBe("greet: Greet someone");
+ });
+
+ it("should include commas and newlines between multiple actions", () => {
+ const formatted = formatActions([mockActions[0], mockActions[1]]);
+ const parts = formatted.split(",\n");
+ expect(parts.length).toBe(2);
+ expect(parts[0]).toMatch(/^(greet|farewell): /);
+ expect(parts[1]).toMatch(/^(greet|farewell): /);
+ });
+
+ it("should handle empty actions array", () => {
+ const formatted = formatActions([]);
+ expect(formatted).toBe("");
+ });
+ });
+
+ describe("Action Structure", () => {
+ it("should validate action structure", () => {
+ mockActions.forEach(action => {
+ expect(action).toHaveProperty("name");
+ expect(action).toHaveProperty("description");
+ expect(action).toHaveProperty("examples");
+ expect(action).toHaveProperty("similes");
+ expect(action).toHaveProperty("handler");
+ expect(action).toHaveProperty("validate");
+ expect(Array.isArray(action.examples)).toBe(true);
+ expect(Array.isArray(action.similes)).toBe(true);
+ });
+ });
+
+ it("should validate example structure", () => {
+ mockActions.forEach(action => {
+ action.examples.forEach(example => {
+ example.forEach(message => {
+ expect(message).toHaveProperty("user");
+ expect(message).toHaveProperty("content");
+ expect(message.content).toHaveProperty("text");
+ });
+ });
+ });
});
- it("should return empty string for empty array", () => {
- const result = formatActions([]);
- expect(result).toBe("");
+ it("should have unique action names", () => {
+ const names = mockActions.map(action => action.name);
+ const uniqueNames = new Set(names);
+ expect(names.length).toBe(uniqueNames.size);
});
});
});
diff --git a/packages/core/src/tests/messages.test.ts b/packages/core/src/tests/messages.test.ts
index ce93e07db43..bbebe103a6d 100644
--- a/packages/core/src/tests/messages.test.ts
+++ b/packages/core/src/tests/messages.test.ts
@@ -23,7 +23,7 @@ describe("Messages Library", () => {
} as unknown as IAgentRuntime;
// Mock user data with proper UUID format
- userId = "12345678-1234-1234-1234-123456789abc" as UUID;
+ userId = "123e4567-e89b-12d3-a456-426614174000" as UUID;
actors = [
{
id: userId,
@@ -39,7 +39,7 @@ describe("Messages Library", () => {
});
test("getActorDetails should return actors based on roomId", async () => {
- const roomId: UUID = "room1234-1234-1234-1234-123456789abc" as UUID;
+ const roomId: UUID = "123e4567-e89b-12d3-a456-426614174001" as UUID;
// Using vi.mocked() type assertion instead of jest.Mock casting
vi.mocked(
@@ -77,7 +77,7 @@ describe("Messages Library", () => {
{
content: { text: "Hello, world!" } as Content,
userId: userId,
- roomId: "room1234-1234-1234-1234-123456789abc" as UUID,
+ roomId: "123e4567-e89b-12d3-a456-426614174002" as UUID,
createdAt: new Date().getTime(),
agentId: "" as UUID, // assuming agentId is an empty string here
},
@@ -105,14 +105,14 @@ describe("Messages Library", () => {
text: "Check this attachment",
attachments: [
{
- id: "1",
+ id: "123e4567-e89b-12d3-a456-426614174003" as UUID,
title: "Image",
url: "http://example.com/image.jpg",
},
],
} as Content,
userId: userId,
- roomId: "room1234-1234-1234-1234-123456789abc" as UUID,
+ roomId: "123e4567-e89b-12d3-a456-426614174004" as UUID,
createdAt: new Date().getTime(),
agentId: "" as UUID, // assuming agentId is an empty string here
},
@@ -123,7 +123,7 @@ describe("Messages Library", () => {
// Assertions
expect(formattedMessages).toContain("Check this attachment");
expect(formattedMessages).toContain(
- "Attachments: [1 - Image (http://example.com/image.jpg)]"
+ "Attachments: ["
);
});
@@ -134,7 +134,7 @@ describe("Messages Library", () => {
text: "No attachments here",
} as Content,
userId: userId,
- roomId: "room1234-1234-1234-1234-123456789abc" as UUID,
+ roomId: "123e4567-e89b-12d3-a456-426614174005" as UUID,
createdAt: new Date().getTime(),
agentId: "" as UUID, // assuming agentId is an empty string here
},
@@ -147,3 +147,187 @@ describe("Messages Library", () => {
expect(formattedMessages).not.toContain("Attachments");
});
});
+
+describe('Messages', () => {
+ const mockActors: Actor[] = [
+ {
+ id: "123e4567-e89b-12d3-a456-426614174006" as UUID,
+ name: 'Alice',
+ username: 'alice',
+ details: {
+ tagline: 'Software Engineer',
+ summary: 'Full-stack developer with 5 years experience',
+ quote: ""
+ }
+ },
+ {
+ id: "123e4567-e89b-12d3-a456-426614174007" as UUID,
+ name: 'Bob',
+ username: 'bob',
+ details: {
+ tagline: 'Product Manager',
+ summary: 'Experienced in agile methodologies',
+ quote: ""
+ }
+ }
+ ];
+
+ const mockMessages: Memory[] = [
+ {
+ id: "123e4567-e89b-12d3-a456-426614174008" as UUID,
+ roomId: "123e4567-e89b-12d3-a456-426614174009" as UUID,
+ userId: mockActors[0].id,
+ createdAt: Date.now() - 5000, // 5 seconds ago
+ content: {
+ text: 'Hello everyone!',
+ action: 'wave'
+ } as Content,
+ agentId: "123e4567-e89b-12d3-a456-426614174001"
+ },
+ {
+ id: "123e4567-e89b-12d3-a456-426614174010" as UUID,
+ roomId: "123e4567-e89b-12d3-a456-426614174009" as UUID,
+ userId: mockActors[1].id,
+ createdAt: Date.now() - 60000, // 1 minute ago
+ content: {
+ text: 'Hi Alice!',
+ attachments: [
+ {
+ id: "123e4567-e89b-12d3-a456-426614174011" as UUID,
+ title: 'Document',
+ url: 'https://example.com/doc.pdf'
+ }
+ ]
+ } as Content,
+ agentId: "123e4567-e89b-12d3-a456-426614174001"
+ }
+ ];
+
+ describe('getActorDetails', () => {
+ it('should retrieve actor details from database', async () => {
+ const mockRuntime = {
+ databaseAdapter: {
+ getParticipantsForRoom: vi.fn().mockResolvedValue([mockActors[0].id, mockActors[1].id]),
+ getAccountById: vi.fn().mockImplementation((id) => {
+ const actor = mockActors.find(a => a.id === id);
+ return Promise.resolve(actor);
+ })
+ }
+ };
+
+ const actors = await getActorDetails({
+ runtime: mockRuntime as any,
+ roomId: "123e4567-e89b-12d3-a456-426614174009" as UUID
+ });
+
+ expect(actors).toHaveLength(2);
+ expect(actors[0].name).toBe('Alice');
+ expect(actors[1].name).toBe('Bob');
+ expect(mockRuntime.databaseAdapter.getParticipantsForRoom).toHaveBeenCalled();
+ });
+
+ it('should filter out null actors', async () => {
+ const invalidId = "123e4567-e89b-12d3-a456-426614174012" as UUID;
+ const mockRuntime = {
+ databaseAdapter: {
+ getParticipantsForRoom: vi.fn().mockResolvedValue([mockActors[0].id, invalidId]),
+ getAccountById: vi.fn().mockImplementation((id) => {
+ const actor = mockActors.find(a => a.id === id);
+ return Promise.resolve(actor || null);
+ })
+ }
+ };
+
+ const actors = await getActorDetails({
+ runtime: mockRuntime as any,
+ roomId: "123e4567-e89b-12d3-a456-426614174009" as UUID
+ });
+
+ expect(actors).toHaveLength(1);
+ expect(actors[0].name).toBe('Alice');
+ });
+ });
+
+ describe('formatActors', () => {
+ it('should format actors with complete details', () => {
+ const formatted = formatActors({ actors: mockActors });
+ expect(formatted).toContain('Alice: Software Engineer');
+ expect(formatted).toContain('Full-stack developer with 5 years experience');
+ expect(formatted).toContain('Bob: Product Manager');
+ expect(formatted).toContain('Experienced in agile methodologies');
+ });
+
+ it('should handle actors without details', () => {
+ const actorsWithoutDetails: Actor[] = [
+ {
+ id: "123e4567-e89b-12d3-a456-426614174013" as UUID,
+ name: 'Charlie',
+ username: 'charlie',
+ details: {
+ tagline: "Tag",
+ summary: "Summary",
+ quote: "Quote"
+ }
+ }
+ ];
+ const formatted = formatActors({ actors: actorsWithoutDetails });
+ expect(formatted).toBe('Charlie: Tag\nSummary');
+ });
+
+ it('should handle empty actors array', () => {
+ const formatted = formatActors({ actors: [] });
+ expect(formatted).toBe('');
+ });
+ });
+
+ describe('formatMessages', () => {
+ it('should format messages with all details', () => {
+ const formatted = formatMessages({ messages: mockMessages, actors: mockActors });
+ const lines = formatted.split('\n');
+ expect(lines[1]).toContain("Alice");
+ expect(lines[1]).toContain("(wave)");
+ expect(lines[1]).toContain("(just now)");
+ });
+
+ it('should handle messages from unknown users', () => {
+ const messagesWithUnknownUser: Memory[] = [{
+ id: "123e4567-e89b-12d3-a456-426614174014" as UUID,
+ roomId: "123e4567-e89b-12d3-a456-426614174009" as UUID,
+ userId: "123e4567-e89b-12d3-a456-426614174015" as UUID,
+ createdAt: Date.now(),
+ content: { text: 'Test message' } as Content,
+ agentId: "123e4567-e89b-12d3-a456-426614174001"
+ }];
+
+ const formatted = formatMessages({ messages: messagesWithUnknownUser, actors: mockActors });
+ expect(formatted).toContain('Unknown User: Test message');
+ });
+
+ it('should handle messages with no action', () => {
+ const messagesWithoutAction: Memory[] = [{
+ id: "123e4567-e89b-12d3-a456-426614174016" as UUID,
+ roomId: "123e4567-e89b-12d3-a456-426614174009" as UUID,
+ userId: mockActors[0].id,
+ createdAt: Date.now(),
+ content: { text: 'Simple message' } as Content,
+ agentId: "123e4567-e89b-12d3-a456-426614174001"
+ }];
+
+ const formatted = formatMessages({ messages: messagesWithoutAction, actors: mockActors });
+ expect(formatted).not.toContain('()');
+ expect(formatted).toContain('Simple message');
+ });
+
+ it('should handle empty messages array', () => {
+ const formatted = formatMessages({ messages: [], actors: mockActors });
+ expect(formatted).toBe('');
+ });
+ });
+
+ describe('formatTimestamp', () => {
+ it('should handle exact time boundaries', () => {
+ const now = Date.now();
+ expect(formatTimestamp(now)).toContain('just now');
+ });
+ });
+});
diff --git a/packages/core/src/tests/models.test.ts b/packages/core/src/tests/models.test.ts
index 249dd587e69..f336093cfdd 100644
--- a/packages/core/src/tests/models.test.ts
+++ b/packages/core/src/tests/models.test.ts
@@ -1,54 +1,173 @@
-import { getModel, getEndpoint } from "../models.ts";
+import { getModel, getEndpoint, models } from "../models.ts";
import { ModelProviderName, ModelClass } from "../types.ts";
import { describe, test, expect, vi } from "vitest";
+// Mock settings
vi.mock("../settings", () => {
return {
default: {
SMALL_OPENROUTER_MODEL: "mock-small-model",
+ LARGE_OPENROUTER_MODEL: "mock-large-model",
OPENROUTER_MODEL: "mock-default-model",
OPENAI_API_KEY: "mock-openai-key",
ANTHROPIC_API_KEY: "mock-anthropic-key",
OPENROUTER_API_KEY: "mock-openrouter-key",
+ ETERNALAI_MODEL: "mock-eternal-model",
+ ETERNALAI_URL: "https://mock.eternal.ai",
+ LLAMACLOUD_MODEL_SMALL: "mock-llama-small",
+ LLAMACLOUD_MODEL_LARGE: "mock-llama-large",
+ TOGETHER_MODEL_SMALL: "mock-together-small",
+ TOGETHER_MODEL_LARGE: "mock-together-large",
},
loadEnv: vi.fn(),
};
});
-describe("Model Provider Tests", () => {
- test("should retrieve the correct model for OpenAI SMALL", () => {
- const model = getModel(ModelProviderName.OPENAI, ModelClass.SMALL);
- expect(model).toBe("gpt-4o-mini");
+describe("Model Provider Configuration", () => {
+ describe("OpenAI Provider", () => {
+ test("should have correct endpoint", () => {
+ expect(models[ModelProviderName.OPENAI].endpoint).toBe("https://api.openai.com/v1");
+ });
+
+ test("should have correct model mappings", () => {
+ const openAIModels = models[ModelProviderName.OPENAI].model;
+ expect(openAIModels[ModelClass.SMALL]).toBe("gpt-4o-mini");
+ expect(openAIModels[ModelClass.MEDIUM]).toBe("gpt-4o");
+ expect(openAIModels[ModelClass.LARGE]).toBe("gpt-4o");
+ expect(openAIModels[ModelClass.EMBEDDING]).toBe("text-embedding-3-small");
+ expect(openAIModels[ModelClass.IMAGE]).toBe("dall-e-3");
+ });
+
+ test("should have correct settings configuration", () => {
+ const settings = models[ModelProviderName.OPENAI].settings;
+ expect(settings.maxInputTokens).toBe(128000);
+ expect(settings.maxOutputTokens).toBe(8192);
+ expect(settings.temperature).toBe(0.6);
+ expect(settings.frequency_penalty).toBe(0.0);
+ expect(settings.presence_penalty).toBe(0.0);
+ });
+ });
+
+ describe("Anthropic Provider", () => {
+ test("should have correct endpoint", () => {
+ expect(models[ModelProviderName.ANTHROPIC].endpoint).toBe("https://api.anthropic.com/v1");
+ });
+
+ test("should have correct model mappings", () => {
+ const anthropicModels = models[ModelProviderName.ANTHROPIC].model;
+ expect(anthropicModels[ModelClass.SMALL]).toBe("claude-3-haiku-20240307");
+ expect(anthropicModels[ModelClass.MEDIUM]).toBe("claude-3-5-sonnet-20241022");
+ expect(anthropicModels[ModelClass.LARGE]).toBe("claude-3-5-sonnet-20241022");
+ });
+
+ test("should have correct settings configuration", () => {
+ const settings = models[ModelProviderName.ANTHROPIC].settings;
+ expect(settings.maxInputTokens).toBe(200000);
+ expect(settings.maxOutputTokens).toBe(4096);
+ expect(settings.temperature).toBe(0.7);
+ expect(settings.frequency_penalty).toBe(0.4);
+ expect(settings.presence_penalty).toBe(0.4);
+ });
+ });
+
+ describe("LlamaCloud Provider", () => {
+ test("should have correct endpoint", () => {
+ expect(models[ModelProviderName.LLAMACLOUD].endpoint).toBe("https://api.llamacloud.com/v1");
+ });
+
+ test("should have correct model mappings", () => {
+ const llamaCloudModels = models[ModelProviderName.LLAMACLOUD].model;
+ expect(llamaCloudModels[ModelClass.SMALL]).toBe("meta-llama/Llama-3.2-3B-Instruct-Turbo");
+ expect(llamaCloudModels[ModelClass.MEDIUM]).toBe("meta-llama-3.1-8b-instruct");
+ expect(llamaCloudModels[ModelClass.LARGE]).toBe("meta-llama/Meta-Llama-3.1-405B-Instruct-Turbo");
+ expect(llamaCloudModels[ModelClass.EMBEDDING]).toBe("togethercomputer/m2-bert-80M-32k-retrieval");
+ expect(llamaCloudModels[ModelClass.IMAGE]).toBe("black-forest-labs/FLUX.1-schnell");
+ });
+
+ test("should have correct settings configuration", () => {
+ const settings = models[ModelProviderName.LLAMACLOUD].settings;
+ expect(settings.maxInputTokens).toBe(128000);
+ expect(settings.maxOutputTokens).toBe(8192);
+ expect(settings.temperature).toBe(0.7);
+ expect(settings.repetition_penalty).toBe(0.4);
+ });
});
- test("should retrieve the correct model for Google MEDIUM", () => {
- const model = getModel(ModelProviderName.GOOGLE, ModelClass.MEDIUM);
- expect(model).toBe("gemini-1.5-flash-latest");
+ describe("Google Provider", () => {
+ test("should have correct model mappings", () => {
+ const googleModels = models[ModelProviderName.GOOGLE].model;
+ expect(googleModels[ModelClass.SMALL]).toBe("gemini-1.5-flash-latest");
+ expect(googleModels[ModelClass.MEDIUM]).toBe("gemini-1.5-flash-latest");
+ expect(googleModels[ModelClass.LARGE]).toBe("gemini-1.5-pro-latest");
+ });
});
+});
+
+describe("Model Retrieval Functions", () => {
+ describe("getModel function", () => {
+ test("should retrieve correct models for different providers and classes", () => {
+ expect(getModel(ModelProviderName.OPENAI, ModelClass.SMALL)).toBe("gpt-4o-mini");
+ expect(getModel(ModelProviderName.ANTHROPIC, ModelClass.LARGE)).toBe("claude-3-5-sonnet-20241022");
+ expect(getModel(ModelProviderName.LLAMACLOUD, ModelClass.MEDIUM)).toBe("meta-llama-3.1-8b-instruct");
+ });
- test("should retrieve the correct model for Groq LARGE", () => {
- const model = getModel(ModelProviderName.GROQ, ModelClass.LARGE);
- expect(model).toBe("llama-3.2-90b-vision-preview");
+ test("should handle environment variable overrides", () => {
+ expect(getModel(ModelProviderName.OPENROUTER, ModelClass.SMALL)).toBe("mock-small-model");
+ expect(getModel(ModelProviderName.OPENROUTER, ModelClass.LARGE)).toBe("mock-large-model");
+ expect(getModel(ModelProviderName.ETERNALAI, ModelClass.SMALL)).toBe("mock-eternal-model");
+ });
+
+ test("should throw error for invalid model provider", () => {
+ expect(() => getModel("INVALID_PROVIDER" as any, ModelClass.SMALL)).toThrow();
+ });
});
- test("should retrieve the correct model for OpenRouter SMALL", () => {
- const model = getModel(ModelProviderName.OPENROUTER, ModelClass.SMALL);
- expect(model).toBe("mock-small-model");
+ describe("getEndpoint function", () => {
+ test("should retrieve correct endpoints for different providers", () => {
+ expect(getEndpoint(ModelProviderName.OPENAI)).toBe("https://api.openai.com/v1");
+ expect(getEndpoint(ModelProviderName.ANTHROPIC)).toBe("https://api.anthropic.com/v1");
+ expect(getEndpoint(ModelProviderName.LLAMACLOUD)).toBe("https://api.llamacloud.com/v1");
+ expect(getEndpoint(ModelProviderName.ETERNALAI)).toBe("https://mock.eternal.ai");
+ });
+
+ test("should throw error for invalid provider", () => {
+ expect(() => getEndpoint("INVALID_PROVIDER" as any)).toThrow();
+ });
+ });
+});
+
+describe("Model Settings Validation", () => {
+ test("all providers should have required settings", () => {
+ Object.values(ModelProviderName).forEach(provider => {
+ const providerConfig = models[provider];
+ expect(providerConfig.settings).toBeDefined();
+ expect(providerConfig.settings.maxInputTokens).toBeGreaterThan(0);
+ expect(providerConfig.settings.maxOutputTokens).toBeGreaterThan(0);
+ expect(providerConfig.settings.temperature).toBeDefined();
+ });
});
- test("should retrieve the correct endpoint for OpenAI", () => {
- const endpoint = getEndpoint(ModelProviderName.OPENAI);
- expect(endpoint).toBe("https://api.openai.com/v1");
+ test("all providers should have model mappings for basic model classes", () => {
+ Object.values(ModelProviderName).forEach(provider => {
+ const providerConfig = models[provider];
+ expect(providerConfig.model).toBeDefined();
+ expect(providerConfig.model[ModelClass.SMALL]).toBeDefined();
+ expect(providerConfig.model[ModelClass.MEDIUM]).toBeDefined();
+ expect(providerConfig.model[ModelClass.LARGE]).toBeDefined();
+ });
});
+});
- test("should retrieve the correct endpoint for Anthropic", () => {
- const endpoint = getEndpoint(ModelProviderName.ANTHROPIC);
- expect(endpoint).toBe("https://api.anthropic.com/v1");
+describe("Environment Variable Integration", () => {
+ test("should use environment variables for LlamaCloud models", () => {
+ const llamaConfig = models[ModelProviderName.LLAMACLOUD];
+ expect(llamaConfig.model[ModelClass.SMALL]).toBe("meta-llama/Llama-3.2-3B-Instruct-Turbo");
+ expect(llamaConfig.model[ModelClass.LARGE]).toBe("meta-llama/Meta-Llama-3.1-405B-Instruct-Turbo");
});
- test("should handle invalid model provider for getModel", () => {
- expect(() =>
- getModel("INVALID_PROVIDER" as any, ModelClass.SMALL)
- ).toThrow();
+ test("should use environment variables for Together models", () => {
+ const togetherConfig = models[ModelProviderName.TOGETHER];
+ expect(togetherConfig.model[ModelClass.SMALL]).toBe("meta-llama/Llama-3.2-3B-Instruct-Turbo");
+ expect(togetherConfig.model[ModelClass.LARGE]).toBe("meta-llama/Meta-Llama-3.1-405B-Instruct-Turbo");
});
});
diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts
index cfe8e77f983..c4dbfd3aaa2 100644
--- a/packages/core/src/types.ts
+++ b/packages/core/src/types.ts
@@ -207,6 +207,7 @@ export type Models = {
[ModelProviderName.VOLENGINE]: Model;
[ModelProviderName.NANOGPT]: Model;
[ModelProviderName.HYPERBOLIC]: Model;
+ [ModelProviderName.VENICE]: Model;
};
/**
@@ -234,6 +235,7 @@ export enum ModelProviderName {
VOLENGINE = "volengine",
NANOGPT = "nanogpt",
HYPERBOLIC = "hyperbolic",
+ VENICE = "venice",
}
/**
@@ -559,6 +561,9 @@ export type Media = {
/** Text content */
text: string;
+
+ /** Content type */
+ contentType?: string;
};
/**
@@ -566,10 +571,10 @@ export type Media = {
*/
export type Client = {
/** Start client connection */
- start: (runtime?: IAgentRuntime) => Promise;
+ start: (runtime: IAgentRuntime) => Promise;
/** Stop client connection */
- stop: (runtime?: IAgentRuntime) => Promise;
+ stop: (runtime: IAgentRuntime) => Promise;
};
/**
@@ -607,7 +612,15 @@ export enum Clients {
TWITTER = "twitter",
TELEGRAM = "telegram",
FARCASTER = "farcaster",
+ LENS = "lens",
+ AUTO = "auto",
+ SLACK = "slack",
+}
+
+export interface IAgentConfig {
+ [key: string]: string;
}
+
/**
* Configuration for an agent character
*/
@@ -646,13 +659,18 @@ export type Character = {
twitterMessageHandlerTemplate?: string;
twitterShouldRespondTemplate?: string;
farcasterPostTemplate?: string;
+ lensPostTemplate?: string;
farcasterMessageHandlerTemplate?: string;
+ lensMessageHandlerTemplate?: string;
farcasterShouldRespondTemplate?: string;
+ lensShouldRespondTemplate?: string;
telegramMessageHandlerTemplate?: string;
telegramShouldRespondTemplate?: string;
discordVoiceHandlerTemplate?: string;
discordShouldRespondTemplate?: string;
discordMessageHandlerTemplate?: string;
+ slackMessageHandlerTemplate?: string;
+ slackShouldRespondTemplate?: string;
};
/** Character biography */
@@ -713,10 +731,24 @@ export type Character = {
discord?: {
shouldIgnoreBotMessages?: boolean;
shouldIgnoreDirectMessages?: boolean;
+ messageSimilarityThreshold?: number;
+ isPartOfTeam?: boolean;
+ teamAgentIds?: string[];
+ teamLeaderId?: string;
+ teamMemberInterestKeywords?: string[];
};
telegram?: {
shouldIgnoreBotMessages?: boolean;
shouldIgnoreDirectMessages?: boolean;
+ messageSimilarityThreshold?: number;
+ isPartOfTeam?: boolean;
+ teamAgentIds?: string[];
+ teamLeaderId?: string;
+ teamMemberInterestKeywords?: string[];
+ };
+ slack?: {
+ shouldIgnoreBotMessages?: boolean;
+ shouldIgnoreDirectMessages?: boolean;
};
};
@@ -735,6 +767,10 @@ export type Character = {
bio: string;
nicknames?: string[];
};
+ /** Optional NFT prompt */
+ nft?: {
+ prompt: string;
+ }
};
/**
@@ -992,6 +1028,8 @@ export interface IAgentRuntime {
evaluators: Evaluator[];
plugins: Plugin[];
+ fetch?: typeof fetch | null;
+
messageManager: IMemoryManager;
descriptionManager: IMemoryManager;
documentsManager: IMemoryManager;
@@ -1001,6 +1039,9 @@ export interface IAgentRuntime {
cacheManager: ICacheManager;
services: Map;
+ // any could be EventEmitter
+ // but I think the real solution is forthcoming as a base client interface
+ clients: Record;
initialize(): Promise;
@@ -1124,12 +1165,17 @@ export interface IPdfService extends Service {
}
export interface IAwsS3Service extends Service {
- uploadFile(imagePath: string, useSignedUrl: boolean, expiresIn: number ): Promise<{
+ uploadFile(
+ imagePath: string,
+ subDirectory: string,
+ useSignedUrl: boolean,
+ expiresIn: number
+ ): Promise<{
success: boolean;
url?: string;
error?: string;
}>;
- generateSignedUrl(fileName: string, expiresIn: number): Promise
+ generateSignedUrl(fileName: string, expiresIn: number): Promise;
}
export type SearchResult = {
@@ -1159,6 +1205,8 @@ export enum ServiceType {
PDF = "pdf",
INTIFACE = "intiface",
AWS_S3 = "aws_s3",
+ BUTTPLUG = "buttplug",
+ SLACK = "slack",
}
export enum LoggingLevel {
@@ -1178,3 +1226,7 @@ export interface ActionResponse {
quote?: boolean;
reply?: boolean;
}
+
+export interface ISlackService extends Service {
+ client: any;
+}
diff --git a/packages/create-eliza-app/package.json b/packages/create-eliza-app/package.json
index 0206679e790..cb210c8f914 100644
--- a/packages/create-eliza-app/package.json
+++ b/packages/create-eliza-app/package.json
@@ -12,7 +12,7 @@
},
"scripts": {
"build": "unbuild",
- "lint": "eslint . --fix",
+ "lint": "eslint --fix --cache .",
"start": "node ./dist/index.cjs",
"automd": "automd"
},
diff --git a/packages/plugin-0g/src/actions/upload.ts b/packages/plugin-0g/src/actions/upload.ts
index d4d4e702775..12abad5c454 100644
--- a/packages/plugin-0g/src/actions/upload.ts
+++ b/packages/plugin-0g/src/actions/upload.ts
@@ -7,7 +7,7 @@ import {
ModelClass,
Content,
ActionExample,
- generateObjectV2,
+ generateObject,
} from "@ai16z/eliza";
import { Indexer, ZgFile, getFlowContract } from "@0glabs/0g-ts-sdk";
import { ethers } from "ethers";
@@ -68,7 +68,7 @@ export const zgUpload: Action = {
});
// Generate upload content
- const content = await generateObjectV2({
+ const content = await generateObject({
runtime,
context: uploadContext,
modelClass: ModelClass.LARGE,
diff --git a/packages/plugin-aptos/package.json b/packages/plugin-aptos/package.json
index 026a58d1588..ae962419ac8 100644
--- a/packages/plugin-aptos/package.json
+++ b/packages/plugin-aptos/package.json
@@ -6,7 +6,6 @@
"types": "dist/index.d.ts",
"dependencies": {
"@ai16z/eliza": "workspace:*",
- "@ai16z/plugin-trustdb": "workspace:*",
"@aptos-labs/ts-sdk": "^1.26.0",
"bignumber": "1.1.0",
"bignumber.js": "9.1.2",
@@ -17,7 +16,7 @@
"scripts": {
"build": "tsup --format esm --dts",
"dev": "tsup --format esm --dts --watch",
- "lint": "eslint . --fix",
+ "lint": "eslint --fix --cache .",
"test": "vitest run"
},
"peerDependencies": {
diff --git a/packages/plugin-aptos/src/actions/transfer.ts b/packages/plugin-aptos/src/actions/transfer.ts
index 19b3c86fcc6..2eb3f8c7c8e 100644
--- a/packages/plugin-aptos/src/actions/transfer.ts
+++ b/packages/plugin-aptos/src/actions/transfer.ts
@@ -10,7 +10,7 @@ import {
type Action,
} from "@ai16z/eliza";
import { composeContext } from "@ai16z/eliza";
-import { generateObjectDEPRECATED } from "@ai16z/eliza";
+import { generateObjectDeprecated } from "@ai16z/eliza";
import {
Account,
Aptos,
@@ -111,7 +111,7 @@ export default {
});
// Generate transfer content
- const content = await generateObjectDEPRECATED({
+ const content = await generateObjectDeprecated({
runtime,
context: transferContext,
modelClass: ModelClass.SMALL,
diff --git a/packages/plugin-bootstrap/package.json b/packages/plugin-bootstrap/package.json
index fe6104f639d..8edf2529572 100644
--- a/packages/plugin-bootstrap/package.json
+++ b/packages/plugin-bootstrap/package.json
@@ -11,7 +11,7 @@
"scripts": {
"build": "tsup --format esm --dts",
"dev": "tsup --format esm --dts --watch",
- "lint": "eslint . --fix"
+ "lint": "eslint --fix --cache ."
},
"peerDependencies": {
"whatwg-url": "7.1.0"
diff --git a/packages/plugin-coinbase/package-lock.json b/packages/plugin-coinbase/package-lock.json
deleted file mode 100644
index ae64da37501..00000000000
--- a/packages/plugin-coinbase/package-lock.json
+++ /dev/null
@@ -1,2534 +0,0 @@
-{
- "name": "@ai16z/plugin-coinbase",
- "version": "0.1.5-alpha.5",
- "lockfileVersion": 3,
- "requires": true,
- "packages": {
- "": {
- "name": "@ai16z/plugin-coinbase",
- "version": "0.1.5-alpha.5",
- "dependencies": {
- "coinbase-api": "1.0.5"
- },
- "devDependencies": {
- "tsup": "8.3.5"
- },
- "peerDependencies": {
- "onnxruntime-node": "1.20.1",
- "vue": "3.5.13",
- "whatwg-url": "7.1.0"
- }
- },
- "node_modules/@babel/helper-string-parser": {
- "version": "7.25.9",
- "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz",
- "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==",
- "license": "MIT",
- "peer": true,
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/helper-validator-identifier": {
- "version": "7.25.9",
- "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz",
- "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==",
- "license": "MIT",
- "peer": true,
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@babel/parser": {
- "version": "7.26.2",
- "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.2.tgz",
- "integrity": "sha512-DWMCZH9WA4Maitz2q21SRKHo9QXZxkDsbNZoVD62gusNtNBBqDg9i7uOhASfTfIGNzW+O+r7+jAlM8dwphcJKQ==",
- "license": "MIT",
- "peer": true,
- "dependencies": {
- "@babel/types": "^7.26.0"
- },
- "bin": {
- "parser": "bin/babel-parser.js"
- },
- "engines": {
- "node": ">=6.0.0"
- }
- },
- "node_modules/@babel/types": {
- "version": "7.26.0",
- "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.0.tgz",
- "integrity": "sha512-Z/yiTPj+lDVnF7lWeKCIJzaIkI0vYO87dMpZ4bg4TDrFe4XXLFWL1TbXU27gBP3QccxV9mZICCrnjnYlJjXHOA==",
- "license": "MIT",
- "peer": true,
- "dependencies": {
- "@babel/helper-string-parser": "^7.25.9",
- "@babel/helper-validator-identifier": "^7.25.9"
- },
- "engines": {
- "node": ">=6.9.0"
- }
- },
- "node_modules/@esbuild/aix-ppc64": {
- "version": "0.24.0",
- "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.24.0.tgz",
- "integrity": "sha512-WtKdFM7ls47zkKHFVzMz8opM7LkcsIp9amDUBIAWirg70RM71WRSjdILPsY5Uv1D42ZpUfaPILDlfactHgsRkw==",
- "cpu": [
- "ppc64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "aix"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/android-arm": {
- "version": "0.24.0",
- "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.24.0.tgz",
- "integrity": "sha512-arAtTPo76fJ/ICkXWetLCc9EwEHKaeya4vMrReVlEIUCAUncH7M4bhMQ+M9Vf+FFOZJdTNMXNBrWwW+OXWpSew==",
- "cpu": [
- "arm"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "android"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/android-arm64": {
- "version": "0.24.0",
- "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.24.0.tgz",
- "integrity": "sha512-Vsm497xFM7tTIPYK9bNTYJyF/lsP590Qc1WxJdlB6ljCbdZKU9SY8i7+Iin4kyhV/KV5J2rOKsBQbB77Ab7L/w==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "android"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/android-x64": {
- "version": "0.24.0",
- "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.24.0.tgz",
- "integrity": "sha512-t8GrvnFkiIY7pa7mMgJd7p8p8qqYIz1NYiAoKc75Zyv73L3DZW++oYMSHPRarcotTKuSs6m3hTOa5CKHaS02TQ==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "android"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/darwin-arm64": {
- "version": "0.24.0",
- "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.24.0.tgz",
- "integrity": "sha512-CKyDpRbK1hXwv79soeTJNHb5EiG6ct3efd/FTPdzOWdbZZfGhpbcqIpiD0+vwmpu0wTIL97ZRPZu8vUt46nBSw==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/darwin-x64": {
- "version": "0.24.0",
- "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.24.0.tgz",
- "integrity": "sha512-rgtz6flkVkh58od4PwTRqxbKH9cOjaXCMZgWD905JOzjFKW+7EiUObfd/Kav+A6Gyud6WZk9w+xu6QLytdi2OA==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/freebsd-arm64": {
- "version": "0.24.0",
- "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.24.0.tgz",
- "integrity": "sha512-6Mtdq5nHggwfDNLAHkPlyLBpE5L6hwsuXZX8XNmHno9JuL2+bg2BX5tRkwjyfn6sKbxZTq68suOjgWqCicvPXA==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "freebsd"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/freebsd-x64": {
- "version": "0.24.0",
- "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.24.0.tgz",
- "integrity": "sha512-D3H+xh3/zphoX8ck4S2RxKR6gHlHDXXzOf6f/9dbFt/NRBDIE33+cVa49Kil4WUjxMGW0ZIYBYtaGCa2+OsQwQ==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "freebsd"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/linux-arm": {
- "version": "0.24.0",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.24.0.tgz",
- "integrity": "sha512-gJKIi2IjRo5G6Glxb8d3DzYXlxdEj2NlkixPsqePSZMhLudqPhtZ4BUrpIuTjJYXxvF9njql+vRjB2oaC9XpBw==",
- "cpu": [
- "arm"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/linux-arm64": {
- "version": "0.24.0",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.24.0.tgz",
- "integrity": "sha512-TDijPXTOeE3eaMkRYpcy3LarIg13dS9wWHRdwYRnzlwlA370rNdZqbcp0WTyyV/k2zSxfko52+C7jU5F9Tfj1g==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/linux-ia32": {
- "version": "0.24.0",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.24.0.tgz",
- "integrity": "sha512-K40ip1LAcA0byL05TbCQ4yJ4swvnbzHscRmUilrmP9Am7//0UjPreh4lpYzvThT2Quw66MhjG//20mrufm40mA==",
- "cpu": [
- "ia32"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/linux-loong64": {
- "version": "0.24.0",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.24.0.tgz",
- "integrity": "sha512-0mswrYP/9ai+CU0BzBfPMZ8RVm3RGAN/lmOMgW4aFUSOQBjA31UP8Mr6DDhWSuMwj7jaWOT0p0WoZ6jeHhrD7g==",
- "cpu": [
- "loong64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/linux-mips64el": {
- "version": "0.24.0",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.24.0.tgz",
- "integrity": "sha512-hIKvXm0/3w/5+RDtCJeXqMZGkI2s4oMUGj3/jM0QzhgIASWrGO5/RlzAzm5nNh/awHE0A19h/CvHQe6FaBNrRA==",
- "cpu": [
- "mips64el"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/linux-ppc64": {
- "version": "0.24.0",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.24.0.tgz",
- "integrity": "sha512-HcZh5BNq0aC52UoocJxaKORfFODWXZxtBaaZNuN3PUX3MoDsChsZqopzi5UupRhPHSEHotoiptqikjN/B77mYQ==",
- "cpu": [
- "ppc64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/linux-riscv64": {
- "version": "0.24.0",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.24.0.tgz",
- "integrity": "sha512-bEh7dMn/h3QxeR2KTy1DUszQjUrIHPZKyO6aN1X4BCnhfYhuQqedHaa5MxSQA/06j3GpiIlFGSsy1c7Gf9padw==",
- "cpu": [
- "riscv64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/linux-s390x": {
- "version": "0.24.0",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.24.0.tgz",
- "integrity": "sha512-ZcQ6+qRkw1UcZGPyrCiHHkmBaj9SiCD8Oqd556HldP+QlpUIe2Wgn3ehQGVoPOvZvtHm8HPx+bH20c9pvbkX3g==",
- "cpu": [
- "s390x"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/linux-x64": {
- "version": "0.24.0",
- "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.24.0.tgz",
- "integrity": "sha512-vbutsFqQ+foy3wSSbmjBXXIJ6PL3scghJoM8zCL142cGaZKAdCZHyf+Bpu/MmX9zT9Q0zFBVKb36Ma5Fzfa8xA==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/netbsd-x64": {
- "version": "0.24.0",
- "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.24.0.tgz",
- "integrity": "sha512-hjQ0R/ulkO8fCYFsG0FZoH+pWgTTDreqpqY7UnQntnaKv95uP5iW3+dChxnx7C3trQQU40S+OgWhUVwCjVFLvg==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "netbsd"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/openbsd-arm64": {
- "version": "0.24.0",
- "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.24.0.tgz",
- "integrity": "sha512-MD9uzzkPQbYehwcN583yx3Tu5M8EIoTD+tUgKF982WYL9Pf5rKy9ltgD0eUgs8pvKnmizxjXZyLt0z6DC3rRXg==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "openbsd"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/openbsd-x64": {
- "version": "0.24.0",
- "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.24.0.tgz",
- "integrity": "sha512-4ir0aY1NGUhIC1hdoCzr1+5b43mw99uNwVzhIq1OY3QcEwPDO3B7WNXBzaKY5Nsf1+N11i1eOfFcq+D/gOS15Q==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "openbsd"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/sunos-x64": {
- "version": "0.24.0",
- "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.24.0.tgz",
- "integrity": "sha512-jVzdzsbM5xrotH+W5f1s+JtUy1UWgjU0Cf4wMvffTB8m6wP5/kx0KiaLHlbJO+dMgtxKV8RQ/JvtlFcdZ1zCPA==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "sunos"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/win32-arm64": {
- "version": "0.24.0",
- "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.24.0.tgz",
- "integrity": "sha512-iKc8GAslzRpBytO2/aN3d2yb2z8XTVfNV0PjGlCxKo5SgWmNXx82I/Q3aG1tFfS+A2igVCY97TJ8tnYwpUWLCA==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/win32-ia32": {
- "version": "0.24.0",
- "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.24.0.tgz",
- "integrity": "sha512-vQW36KZolfIudCcTnaTpmLQ24Ha1RjygBo39/aLkM2kmjkWmZGEJ5Gn9l5/7tzXA42QGIoWbICfg6KLLkIw6yw==",
- "cpu": [
- "ia32"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@esbuild/win32-x64": {
- "version": "0.24.0",
- "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.24.0.tgz",
- "integrity": "sha512-7IAFPrjSQIJrGsK6flwg7NFmwBoSTyF3rl7If0hNUFQU4ilTsEPL6GuMuU9BfIWVVGuRnuIidkSMC+c0Otu8IA==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/@isaacs/cliui": {
- "version": "8.0.2",
- "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
- "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==",
- "license": "ISC",
- "dependencies": {
- "string-width": "^5.1.2",
- "string-width-cjs": "npm:string-width@^4.2.0",
- "strip-ansi": "^7.0.1",
- "strip-ansi-cjs": "npm:strip-ansi@^6.0.1",
- "wrap-ansi": "^8.1.0",
- "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0"
- },
- "engines": {
- "node": ">=12"
- }
- },
- "node_modules/@isaacs/fs-minipass": {
- "version": "4.0.1",
- "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz",
- "integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==",
- "license": "ISC",
- "peer": true,
- "dependencies": {
- "minipass": "^7.0.4"
- },
- "engines": {
- "node": ">=18.0.0"
- }
- },
- "node_modules/@jridgewell/gen-mapping": {
- "version": "0.3.5",
- "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz",
- "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@jridgewell/set-array": "^1.2.1",
- "@jridgewell/sourcemap-codec": "^1.4.10",
- "@jridgewell/trace-mapping": "^0.3.24"
- },
- "engines": {
- "node": ">=6.0.0"
- }
- },
- "node_modules/@jridgewell/resolve-uri": {
- "version": "3.1.2",
- "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
- "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=6.0.0"
- }
- },
- "node_modules/@jridgewell/set-array": {
- "version": "1.2.1",
- "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz",
- "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=6.0.0"
- }
- },
- "node_modules/@jridgewell/sourcemap-codec": {
- "version": "1.5.0",
- "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz",
- "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==",
- "license": "MIT"
- },
- "node_modules/@jridgewell/trace-mapping": {
- "version": "0.3.25",
- "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz",
- "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@jridgewell/resolve-uri": "^3.1.0",
- "@jridgewell/sourcemap-codec": "^1.4.14"
- }
- },
- "node_modules/@pkgjs/parseargs": {
- "version": "0.11.0",
- "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
- "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==",
- "license": "MIT",
- "optional": true,
- "engines": {
- "node": ">=14"
- }
- },
- "node_modules/@rollup/rollup-android-arm-eabi": {
- "version": "4.27.4",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.27.4.tgz",
- "integrity": "sha512-2Y3JT6f5MrQkICUyRVCw4oa0sutfAsgaSsb0Lmmy1Wi2y7X5vT9Euqw4gOsCyy0YfKURBg35nhUKZS4mDcfULw==",
- "cpu": [
- "arm"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "android"
- ]
- },
- "node_modules/@rollup/rollup-android-arm64": {
- "version": "4.27.4",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.27.4.tgz",
- "integrity": "sha512-wzKRQXISyi9UdCVRqEd0H4cMpzvHYt1f/C3CoIjES6cG++RHKhrBj2+29nPF0IB5kpy9MS71vs07fvrNGAl/iA==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "android"
- ]
- },
- "node_modules/@rollup/rollup-darwin-arm64": {
- "version": "4.27.4",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.27.4.tgz",
- "integrity": "sha512-PlNiRQapift4LNS8DPUHuDX/IdXiLjf8mc5vdEmUR0fF/pyy2qWwzdLjB+iZquGr8LuN4LnUoSEvKRwjSVYz3Q==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "darwin"
- ]
- },
- "node_modules/@rollup/rollup-darwin-x64": {
- "version": "4.27.4",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.27.4.tgz",
- "integrity": "sha512-o9bH2dbdgBDJaXWJCDTNDYa171ACUdzpxSZt+u/AAeQ20Nk5x+IhA+zsGmrQtpkLiumRJEYef68gcpn2ooXhSQ==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "darwin"
- ]
- },
- "node_modules/@rollup/rollup-freebsd-arm64": {
- "version": "4.27.4",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.27.4.tgz",
- "integrity": "sha512-NBI2/i2hT9Q+HySSHTBh52da7isru4aAAo6qC3I7QFVsuhxi2gM8t/EI9EVcILiHLj1vfi+VGGPaLOUENn7pmw==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "freebsd"
- ]
- },
- "node_modules/@rollup/rollup-freebsd-x64": {
- "version": "4.27.4",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.27.4.tgz",
- "integrity": "sha512-wYcC5ycW2zvqtDYrE7deary2P2UFmSh85PUpAx+dwTCO9uw3sgzD6Gv9n5X4vLaQKsrfTSZZ7Z7uynQozPVvWA==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "freebsd"
- ]
- },
- "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
- "version": "4.27.4",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.27.4.tgz",
- "integrity": "sha512-9OwUnK/xKw6DyRlgx8UizeqRFOfi9mf5TYCw1uolDaJSbUmBxP85DE6T4ouCMoN6pXw8ZoTeZCSEfSaYo+/s1w==",
- "cpu": [
- "arm"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@rollup/rollup-linux-arm-musleabihf": {
- "version": "4.27.4",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.27.4.tgz",
- "integrity": "sha512-Vgdo4fpuphS9V24WOV+KwkCVJ72u7idTgQaBoLRD0UxBAWTF9GWurJO9YD9yh00BzbkhpeXtm6na+MvJU7Z73A==",
- "cpu": [
- "arm"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@rollup/rollup-linux-arm64-gnu": {
- "version": "4.27.4",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.27.4.tgz",
- "integrity": "sha512-pleyNgyd1kkBkw2kOqlBx+0atfIIkkExOTiifoODo6qKDSpnc6WzUY5RhHdmTdIJXBdSnh6JknnYTtmQyobrVg==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@rollup/rollup-linux-arm64-musl": {
- "version": "4.27.4",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.27.4.tgz",
- "integrity": "sha512-caluiUXvUuVyCHr5DxL8ohaaFFzPGmgmMvwmqAITMpV/Q+tPoaHZ/PWa3t8B2WyoRcIIuu1hkaW5KkeTDNSnMA==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@rollup/rollup-linux-powerpc64le-gnu": {
- "version": "4.27.4",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.27.4.tgz",
- "integrity": "sha512-FScrpHrO60hARyHh7s1zHE97u0KlT/RECzCKAdmI+LEoC1eDh/RDji9JgFqyO+wPDb86Oa/sXkily1+oi4FzJQ==",
- "cpu": [
- "ppc64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@rollup/rollup-linux-riscv64-gnu": {
- "version": "4.27.4",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.27.4.tgz",
- "integrity": "sha512-qyyprhyGb7+RBfMPeww9FlHwKkCXdKHeGgSqmIXw9VSUtvyFZ6WZRtnxgbuz76FK7LyoN8t/eINRbPUcvXB5fw==",
- "cpu": [
- "riscv64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@rollup/rollup-linux-s390x-gnu": {
- "version": "4.27.4",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.27.4.tgz",
- "integrity": "sha512-PFz+y2kb6tbh7m3A7nA9++eInGcDVZUACulf/KzDtovvdTizHpZaJty7Gp0lFwSQcrnebHOqxF1MaKZd7psVRg==",
- "cpu": [
- "s390x"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@rollup/rollup-linux-x64-gnu": {
- "version": "4.27.4",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.27.4.tgz",
- "integrity": "sha512-Ni8mMtfo+o/G7DVtweXXV/Ol2TFf63KYjTtoZ5f078AUgJTmaIJnj4JFU7TK/9SVWTaSJGxPi5zMDgK4w+Ez7Q==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@rollup/rollup-linux-x64-musl": {
- "version": "4.27.4",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.27.4.tgz",
- "integrity": "sha512-5AeeAF1PB9TUzD+3cROzFTnAJAcVUGLuR8ng0E0WXGkYhp6RD6L+6szYVX+64Rs0r72019KHZS1ka1q+zU/wUw==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "linux"
- ]
- },
- "node_modules/@rollup/rollup-win32-arm64-msvc": {
- "version": "4.27.4",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.27.4.tgz",
- "integrity": "sha512-yOpVsA4K5qVwu2CaS3hHxluWIK5HQTjNV4tWjQXluMiiiu4pJj4BN98CvxohNCpcjMeTXk/ZMJBRbgRg8HBB6A==",
- "cpu": [
- "arm64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "win32"
- ]
- },
- "node_modules/@rollup/rollup-win32-ia32-msvc": {
- "version": "4.27.4",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.27.4.tgz",
- "integrity": "sha512-KtwEJOaHAVJlxV92rNYiG9JQwQAdhBlrjNRp7P9L8Cb4Rer3in+0A+IPhJC9y68WAi9H0sX4AiG2NTsVlmqJeQ==",
- "cpu": [
- "ia32"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "win32"
- ]
- },
- "node_modules/@rollup/rollup-win32-x64-msvc": {
- "version": "4.27.4",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.27.4.tgz",
- "integrity": "sha512-3j4jx1TppORdTAoBJRd+/wJRGCPC0ETWkXOecJ6PPZLj6SptXkrXcNqdj0oclbKML6FkQltdz7bBA3rUSirZug==",
- "cpu": [
- "x64"
- ],
- "dev": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "win32"
- ]
- },
- "node_modules/@types/estree": {
- "version": "1.0.6",
- "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz",
- "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/@vue/compiler-core": {
- "version": "3.5.13",
- "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.13.tgz",
- "integrity": "sha512-oOdAkwqUfW1WqpwSYJce06wvt6HljgY3fGeM9NcVA1HaYOij3mZG9Rkysn0OHuyUAGMbEbARIpsG+LPVlBJ5/Q==",
- "license": "MIT",
- "peer": true,
- "dependencies": {
- "@babel/parser": "^7.25.3",
- "@vue/shared": "3.5.13",
- "entities": "^4.5.0",
- "estree-walker": "^2.0.2",
- "source-map-js": "^1.2.0"
- }
- },
- "node_modules/@vue/compiler-dom": {
- "version": "3.5.13",
- "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.13.tgz",
- "integrity": "sha512-ZOJ46sMOKUjO3e94wPdCzQ6P1Lx/vhp2RSvfaab88Ajexs0AHeV0uasYhi99WPaogmBlRHNRuly8xV75cNTMDA==",
- "license": "MIT",
- "peer": true,
- "dependencies": {
- "@vue/compiler-core": "3.5.13",
- "@vue/shared": "3.5.13"
- }
- },
- "node_modules/@vue/compiler-sfc": {
- "version": "3.5.13",
- "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.13.tgz",
- "integrity": "sha512-6VdaljMpD82w6c2749Zhf5T9u5uLBWKnVue6XWxprDobftnletJ8+oel7sexFfM3qIxNmVE7LSFGTpv6obNyaQ==",
- "license": "MIT",
- "peer": true,
- "dependencies": {
- "@babel/parser": "^7.25.3",
- "@vue/compiler-core": "3.5.13",
- "@vue/compiler-dom": "3.5.13",
- "@vue/compiler-ssr": "3.5.13",
- "@vue/shared": "3.5.13",
- "estree-walker": "^2.0.2",
- "magic-string": "^0.30.11",
- "postcss": "^8.4.48",
- "source-map-js": "^1.2.0"
- }
- },
- "node_modules/@vue/compiler-ssr": {
- "version": "3.5.13",
- "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.13.tgz",
- "integrity": "sha512-wMH6vrYHxQl/IybKJagqbquvxpWCuVYpoUJfCqFZwa/JY1GdATAQ+TgVtgrwwMZ0D07QhA99rs/EAAWfvG6KpA==",
- "license": "MIT",
- "peer": true,
- "dependencies": {
- "@vue/compiler-dom": "3.5.13",
- "@vue/shared": "3.5.13"
- }
- },
- "node_modules/@vue/reactivity": {
- "version": "3.5.13",
- "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.13.tgz",
- "integrity": "sha512-NaCwtw8o48B9I6L1zl2p41OHo/2Z4wqYGGIK1Khu5T7yxrn+ATOixn/Udn2m+6kZKB/J7cuT9DbWWhRxqixACg==",
- "license": "MIT",
- "peer": true,
- "dependencies": {
- "@vue/shared": "3.5.13"
- }
- },
- "node_modules/@vue/runtime-core": {
- "version": "3.5.13",
- "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.13.tgz",
- "integrity": "sha512-Fj4YRQ3Az0WTZw1sFe+QDb0aXCerigEpw418pw1HBUKFtnQHWzwojaukAs2X/c9DQz4MQ4bsXTGlcpGxU/RCIw==",
- "license": "MIT",
- "peer": true,
- "dependencies": {
- "@vue/reactivity": "3.5.13",
- "@vue/shared": "3.5.13"
- }
- },
- "node_modules/@vue/runtime-dom": {
- "version": "3.5.13",
- "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.13.tgz",
- "integrity": "sha512-dLaj94s93NYLqjLiyFzVs9X6dWhTdAlEAciC3Moq7gzAc13VJUdCnjjRurNM6uTLFATRHexHCTu/Xp3eW6yoog==",
- "license": "MIT",
- "peer": true,
- "dependencies": {
- "@vue/reactivity": "3.5.13",
- "@vue/runtime-core": "3.5.13",
- "@vue/shared": "3.5.13",
- "csstype": "^3.1.3"
- }
- },
- "node_modules/@vue/server-renderer": {
- "version": "3.5.13",
- "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.13.tgz",
- "integrity": "sha512-wAi4IRJV/2SAW3htkTlB+dHeRmpTiVIK1OGLWV1yeStVSebSQQOwGwIq0D3ZIoBj2C2qpgz5+vX9iEBkTdk5YA==",
- "license": "MIT",
- "peer": true,
- "dependencies": {
- "@vue/compiler-ssr": "3.5.13",
- "@vue/shared": "3.5.13"
- },
- "peerDependencies": {
- "vue": "3.5.13"
- }
- },
- "node_modules/@vue/shared": {
- "version": "3.5.13",
- "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.13.tgz",
- "integrity": "sha512-/hnE/qP5ZoGpol0a5mDi45bOd7t3tjYJBjsgCsivow7D48cJeV5l05RD82lPqi7gRiphZM37rnhW1l6ZoCNNnQ==",
- "license": "MIT",
- "peer": true
- },
- "node_modules/ansi-regex": {
- "version": "6.1.0",
- "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz",
- "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==",
- "license": "MIT",
- "engines": {
- "node": ">=12"
- },
- "funding": {
- "url": "https://github.com/chalk/ansi-regex?sponsor=1"
- }
- },
- "node_modules/ansi-styles": {
- "version": "6.2.1",
- "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz",
- "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==",
- "license": "MIT",
- "engines": {
- "node": ">=12"
- },
- "funding": {
- "url": "https://github.com/chalk/ansi-styles?sponsor=1"
- }
- },
- "node_modules/any-promise": {
- "version": "1.3.0",
- "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz",
- "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/asynckit": {
- "version": "0.4.0",
- "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
- "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
- "license": "MIT"
- },
- "node_modules/axios": {
- "version": "1.7.8",
- "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.8.tgz",
- "integrity": "sha512-Uu0wb7KNqK2t5K+YQyVCLM76prD5sRFjKHbJYCP1J7JFGEQ6nN7HWn9+04LAeiJ3ji54lgS/gZCH1oxyrf1SPw==",
- "license": "MIT",
- "dependencies": {
- "follow-redirects": "^1.15.6",
- "form-data": "^4.0.0",
- "proxy-from-env": "^1.1.0"
- }
- },
- "node_modules/balanced-match": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
- "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
- "license": "MIT"
- },
- "node_modules/brace-expansion": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
- "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
- "license": "MIT",
- "dependencies": {
- "balanced-match": "^1.0.0"
- }
- },
- "node_modules/buffer-equal-constant-time": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz",
- "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==",
- "license": "BSD-3-Clause"
- },
- "node_modules/bundle-require": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/bundle-require/-/bundle-require-5.0.0.tgz",
- "integrity": "sha512-GuziW3fSSmopcx4KRymQEJVbZUfqlCqcq7dvs6TYwKRZiegK/2buMxQTPs6MGlNv50wms1699qYO54R8XfRX4w==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "load-tsconfig": "^0.2.3"
- },
- "engines": {
- "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
- },
- "peerDependencies": {
- "esbuild": ">=0.18"
- }
- },
- "node_modules/cac": {
- "version": "6.7.14",
- "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz",
- "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/chokidar": {
- "version": "4.0.1",
- "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.1.tgz",
- "integrity": "sha512-n8enUVCED/KVRQlab1hr3MVpcVMvxtZjmEa956u+4YijlmQED223XMSYj2tLuKvr4jcCTzNNMpQDUer72MMmzA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "readdirp": "^4.0.1"
- },
- "engines": {
- "node": ">= 14.16.0"
- },
- "funding": {
- "url": "https://paulmillr.com/funding/"
- }
- },
- "node_modules/coinbase-api": {
- "version": "1.0.5",
- "resolved": "https://registry.npmjs.org/coinbase-api/-/coinbase-api-1.0.5.tgz",
- "integrity": "sha512-5Rq6hYKnJNc9v4diD8M6PStSc2hwMgfOlB+pb1LSyh5q2xg9ZKi3Gu8ZVxaDnKXmgQgrjI4xJLMpc3fiLgzsew==",
- "license": "MIT",
- "dependencies": {
- "axios": "^1.7.4",
- "isomorphic-ws": "^4.0.1",
- "jsonwebtoken": "^9.0.2",
- "nanoid": "^3.3.7",
- "ws": "^7.4.0"
- },
- "funding": {
- "type": "individual",
- "url": "https://github.com/sponsors/tiagosiebler"
- }
- },
- "node_modules/color-convert": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
- "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
- "license": "MIT",
- "dependencies": {
- "color-name": "~1.1.4"
- },
- "engines": {
- "node": ">=7.0.0"
- }
- },
- "node_modules/color-name": {
- "version": "1.1.4",
- "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
- "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
- "license": "MIT"
- },
- "node_modules/combined-stream": {
- "version": "1.0.8",
- "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
- "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
- "license": "MIT",
- "dependencies": {
- "delayed-stream": "~1.0.0"
- },
- "engines": {
- "node": ">= 0.8"
- }
- },
- "node_modules/commander": {
- "version": "4.1.1",
- "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz",
- "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 6"
- }
- },
- "node_modules/consola": {
- "version": "3.2.3",
- "resolved": "https://registry.npmjs.org/consola/-/consola-3.2.3.tgz",
- "integrity": "sha512-I5qxpzLv+sJhTVEoLYNcTW+bThDCPsit0vLNKShZx6rLtpilNpmmeTPaeqJb9ZE9dV3DGaeby6Vuhrw38WjeyQ==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": "^14.18.0 || >=16.10.0"
- }
- },
- "node_modules/cross-spawn": {
- "version": "7.0.6",
- "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
- "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
- "license": "MIT",
- "dependencies": {
- "path-key": "^3.1.0",
- "shebang-command": "^2.0.0",
- "which": "^2.0.1"
- },
- "engines": {
- "node": ">= 8"
- }
- },
- "node_modules/csstype": {
- "version": "3.1.3",
- "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
- "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
- "license": "MIT",
- "peer": true
- },
- "node_modules/debug": {
- "version": "4.3.7",
- "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",
- "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "ms": "^2.1.3"
- },
- "engines": {
- "node": ">=6.0"
- },
- "peerDependenciesMeta": {
- "supports-color": {
- "optional": true
- }
- }
- },
- "node_modules/delayed-stream": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
- "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
- "license": "MIT",
- "engines": {
- "node": ">=0.4.0"
- }
- },
- "node_modules/eastasianwidth": {
- "version": "0.2.0",
- "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
- "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==",
- "license": "MIT"
- },
- "node_modules/ecdsa-sig-formatter": {
- "version": "1.0.11",
- "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz",
- "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==",
- "license": "Apache-2.0",
- "dependencies": {
- "safe-buffer": "^5.0.1"
- }
- },
- "node_modules/emoji-regex": {
- "version": "9.2.2",
- "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
- "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==",
- "license": "MIT"
- },
- "node_modules/entities": {
- "version": "4.5.0",
- "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
- "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
- "license": "BSD-2-Clause",
- "peer": true,
- "engines": {
- "node": ">=0.12"
- },
- "funding": {
- "url": "https://github.com/fb55/entities?sponsor=1"
- }
- },
- "node_modules/esbuild": {
- "version": "0.24.0",
- "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.24.0.tgz",
- "integrity": "sha512-FuLPevChGDshgSicjisSooU0cemp/sGXR841D5LHMB7mTVOmsEHcAxaH3irL53+8YDIeVNQEySh4DaYU/iuPqQ==",
- "dev": true,
- "hasInstallScript": true,
- "license": "MIT",
- "bin": {
- "esbuild": "bin/esbuild"
- },
- "engines": {
- "node": ">=18"
- },
- "optionalDependencies": {
- "@esbuild/aix-ppc64": "0.24.0",
- "@esbuild/android-arm": "0.24.0",
- "@esbuild/android-arm64": "0.24.0",
- "@esbuild/android-x64": "0.24.0",
- "@esbuild/darwin-arm64": "0.24.0",
- "@esbuild/darwin-x64": "0.24.0",
- "@esbuild/freebsd-arm64": "0.24.0",
- "@esbuild/freebsd-x64": "0.24.0",
- "@esbuild/linux-arm": "0.24.0",
- "@esbuild/linux-arm64": "0.24.0",
- "@esbuild/linux-ia32": "0.24.0",
- "@esbuild/linux-loong64": "0.24.0",
- "@esbuild/linux-mips64el": "0.24.0",
- "@esbuild/linux-ppc64": "0.24.0",
- "@esbuild/linux-riscv64": "0.24.0",
- "@esbuild/linux-s390x": "0.24.0",
- "@esbuild/linux-x64": "0.24.0",
- "@esbuild/netbsd-x64": "0.24.0",
- "@esbuild/openbsd-arm64": "0.24.0",
- "@esbuild/openbsd-x64": "0.24.0",
- "@esbuild/sunos-x64": "0.24.0",
- "@esbuild/win32-arm64": "0.24.0",
- "@esbuild/win32-ia32": "0.24.0",
- "@esbuild/win32-x64": "0.24.0"
- }
- },
- "node_modules/estree-walker": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
- "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==",
- "license": "MIT",
- "peer": true
- },
- "node_modules/fdir": {
- "version": "6.4.2",
- "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.2.tgz",
- "integrity": "sha512-KnhMXsKSPZlAhp7+IjUkRZKPb4fUyccpDrdFXbi4QL1qkmFh9kVY09Yox+n4MaOb3lHZ1Tv829C3oaaXoMYPDQ==",
- "dev": true,
- "license": "MIT",
- "peerDependencies": {
- "picomatch": "^3 || ^4"
- },
- "peerDependenciesMeta": {
- "picomatch": {
- "optional": true
- }
- }
- },
- "node_modules/follow-redirects": {
- "version": "1.15.9",
- "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz",
- "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==",
- "funding": [
- {
- "type": "individual",
- "url": "https://github.com/sponsors/RubenVerborgh"
- }
- ],
- "license": "MIT",
- "engines": {
- "node": ">=4.0"
- },
- "peerDependenciesMeta": {
- "debug": {
- "optional": true
- }
- }
- },
- "node_modules/foreground-child": {
- "version": "3.3.0",
- "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.0.tgz",
- "integrity": "sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==",
- "license": "ISC",
- "dependencies": {
- "cross-spawn": "^7.0.0",
- "signal-exit": "^4.0.1"
- },
- "engines": {
- "node": ">=14"
- },
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
- }
- },
- "node_modules/form-data": {
- "version": "4.0.1",
- "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.1.tgz",
- "integrity": "sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==",
- "license": "MIT",
- "dependencies": {
- "asynckit": "^0.4.0",
- "combined-stream": "^1.0.8",
- "mime-types": "^2.1.12"
- },
- "engines": {
- "node": ">= 6"
- }
- },
- "node_modules/fsevents": {
- "version": "2.3.3",
- "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
- "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
- "dev": true,
- "hasInstallScript": true,
- "license": "MIT",
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
- }
- },
- "node_modules/is-fullwidth-code-point": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
- "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
- "license": "MIT",
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/isexe": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
- "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
- "license": "ISC"
- },
- "node_modules/isomorphic-ws": {
- "version": "4.0.1",
- "resolved": "https://registry.npmjs.org/isomorphic-ws/-/isomorphic-ws-4.0.1.tgz",
- "integrity": "sha512-BhBvN2MBpWTaSHdWRb/bwdZJ1WaehQ2L1KngkCkfLUGF0mAWAT1sQUQacEmQ0jXkFw/czDXPNQSL5u2/Krsz1w==",
- "license": "MIT",
- "peerDependencies": {
- "ws": "*"
- }
- },
- "node_modules/joycon": {
- "version": "3.1.1",
- "resolved": "https://registry.npmjs.org/joycon/-/joycon-3.1.1.tgz",
- "integrity": "sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=10"
- }
- },
- "node_modules/jsonwebtoken": {
- "version": "9.0.2",
- "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz",
- "integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==",
- "license": "MIT",
- "dependencies": {
- "jws": "^3.2.2",
- "lodash.includes": "^4.3.0",
- "lodash.isboolean": "^3.0.3",
- "lodash.isinteger": "^4.0.4",
- "lodash.isnumber": "^3.0.3",
- "lodash.isplainobject": "^4.0.6",
- "lodash.isstring": "^4.0.1",
- "lodash.once": "^4.0.0",
- "ms": "^2.1.1",
- "semver": "^7.5.4"
- },
- "engines": {
- "node": ">=12",
- "npm": ">=6"
- }
- },
- "node_modules/jsonwebtoken/node_modules/jwa": {
- "version": "1.4.1",
- "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz",
- "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==",
- "license": "MIT",
- "dependencies": {
- "buffer-equal-constant-time": "1.0.1",
- "ecdsa-sig-formatter": "1.0.11",
- "safe-buffer": "^5.0.1"
- }
- },
- "node_modules/jsonwebtoken/node_modules/jws": {
- "version": "3.2.2",
- "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz",
- "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==",
- "license": "MIT",
- "dependencies": {
- "jwa": "^1.4.1",
- "safe-buffer": "^5.0.1"
- }
- },
- "node_modules/lilconfig": {
- "version": "3.1.2",
- "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.2.tgz",
- "integrity": "sha512-eop+wDAvpItUys0FWkHIKeC9ybYrTGbU41U5K7+bttZZeohvnY7M9dZ5kB21GNWiFT2q1OoPTvncPCgSOVO5ow==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=14"
- },
- "funding": {
- "url": "https://github.com/sponsors/antonk52"
- }
- },
- "node_modules/lines-and-columns": {
- "version": "1.2.4",
- "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
- "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/load-tsconfig": {
- "version": "0.2.5",
- "resolved": "https://registry.npmjs.org/load-tsconfig/-/load-tsconfig-0.2.5.tgz",
- "integrity": "sha512-IXO6OCs9yg8tMKzfPZ1YmheJbZCiEsnBdcB03l0OcfK9prKnJb96siuHCr5Fl37/yo9DnKU+TLpxzTUspw9shg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
- }
- },
- "node_modules/lodash.includes": {
- "version": "4.3.0",
- "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz",
- "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==",
- "license": "MIT"
- },
- "node_modules/lodash.isboolean": {
- "version": "3.0.3",
- "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz",
- "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==",
- "license": "MIT"
- },
- "node_modules/lodash.isinteger": {
- "version": "4.0.4",
- "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz",
- "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==",
- "license": "MIT"
- },
- "node_modules/lodash.isnumber": {
- "version": "3.0.3",
- "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz",
- "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==",
- "license": "MIT"
- },
- "node_modules/lodash.isplainobject": {
- "version": "4.0.6",
- "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
- "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==",
- "license": "MIT"
- },
- "node_modules/lodash.isstring": {
- "version": "4.0.1",
- "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz",
- "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==",
- "license": "MIT"
- },
- "node_modules/lodash.once": {
- "version": "4.1.1",
- "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz",
- "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==",
- "license": "MIT"
- },
- "node_modules/lodash.sortby": {
- "version": "4.7.0",
- "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz",
- "integrity": "sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==",
- "license": "MIT"
- },
- "node_modules/magic-string": {
- "version": "0.30.14",
- "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.14.tgz",
- "integrity": "sha512-5c99P1WKTed11ZC0HMJOj6CDIue6F8ySu+bJL+85q1zBEIY8IklrJ1eiKC2NDRh3Ct3FcvmJPyQHb9erXMTJNw==",
- "license": "MIT",
- "peer": true,
- "dependencies": {
- "@jridgewell/sourcemap-codec": "^1.5.0"
- }
- },
- "node_modules/mime-db": {
- "version": "1.52.0",
- "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
- "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
- "license": "MIT",
- "engines": {
- "node": ">= 0.6"
- }
- },
- "node_modules/mime-types": {
- "version": "2.1.35",
- "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
- "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
- "license": "MIT",
- "dependencies": {
- "mime-db": "1.52.0"
- },
- "engines": {
- "node": ">= 0.6"
- }
- },
- "node_modules/minipass": {
- "version": "7.1.2",
- "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz",
- "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==",
- "license": "ISC",
- "engines": {
- "node": ">=16 || 14 >=14.17"
- }
- },
- "node_modules/ms": {
- "version": "2.1.3",
- "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
- "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
- "license": "MIT"
- },
- "node_modules/mz": {
- "version": "2.7.0",
- "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz",
- "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "any-promise": "^1.0.0",
- "object-assign": "^4.0.1",
- "thenify-all": "^1.0.0"
- }
- },
- "node_modules/nanoid": {
- "version": "3.3.8",
- "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz",
- "integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==",
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/ai"
- }
- ],
- "license": "MIT",
- "bin": {
- "nanoid": "bin/nanoid.cjs"
- },
- "engines": {
- "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
- }
- },
- "node_modules/object-assign": {
- "version": "4.1.1",
- "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
- "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/onnxruntime-common": {
- "version": "1.20.1",
- "resolved": "https://registry.npmjs.org/onnxruntime-common/-/onnxruntime-common-1.20.1.tgz",
- "integrity": "sha512-YiU0s0IzYYC+gWvqD1HzLc46Du1sXpSiwzKb63PACIJr6LfL27VsXSXQvt68EzD3V0D5Bc0vyJTjmMxp0ylQiw==",
- "license": "MIT",
- "peer": true
- },
- "node_modules/onnxruntime-node": {
- "version": "1.20.1",
- "resolved": "https://registry.npmjs.org/onnxruntime-node/-/onnxruntime-node-1.20.1.tgz",
- "integrity": "sha512-di/I4HDXRw+FLgq+TyHmQEDd3cEp9iFFZm0r4uJ1Wd7b/WE1VXtKWo8yemex347c6GNF/3Pv86ZfPhIWxORr0w==",
- "hasInstallScript": true,
- "license": "MIT",
- "os": [
- "win32",
- "darwin",
- "linux"
- ],
- "peer": true,
- "dependencies": {
- "onnxruntime-common": "1.20.1",
- "tar": "^7.0.1"
- }
- },
- "node_modules/onnxruntime-node/node_modules/chownr": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz",
- "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==",
- "license": "BlueOak-1.0.0",
- "peer": true,
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/onnxruntime-node/node_modules/minizlib": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.0.1.tgz",
- "integrity": "sha512-umcy022ILvb5/3Djuu8LWeqUa8D68JaBzlttKeMWen48SjabqS3iY5w/vzeMzMUNhLDifyhbOwKDSznB1vvrwg==",
- "license": "MIT",
- "peer": true,
- "dependencies": {
- "minipass": "^7.0.4",
- "rimraf": "^5.0.5"
- },
- "engines": {
- "node": ">= 18"
- }
- },
- "node_modules/onnxruntime-node/node_modules/mkdirp": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-3.0.1.tgz",
- "integrity": "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==",
- "license": "MIT",
- "peer": true,
- "bin": {
- "mkdirp": "dist/cjs/src/bin.js"
- },
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
- }
- },
- "node_modules/onnxruntime-node/node_modules/tar": {
- "version": "7.4.3",
- "resolved": "https://registry.npmjs.org/tar/-/tar-7.4.3.tgz",
- "integrity": "sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw==",
- "license": "ISC",
- "peer": true,
- "dependencies": {
- "@isaacs/fs-minipass": "^4.0.0",
- "chownr": "^3.0.0",
- "minipass": "^7.1.2",
- "minizlib": "^3.0.1",
- "mkdirp": "^3.0.1",
- "yallist": "^5.0.0"
- },
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/onnxruntime-node/node_modules/yallist": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz",
- "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==",
- "license": "BlueOak-1.0.0",
- "peer": true,
- "engines": {
- "node": ">=18"
- }
- },
- "node_modules/package-json-from-dist": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz",
- "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==",
- "license": "BlueOak-1.0.0"
- },
- "node_modules/path-key": {
- "version": "3.1.1",
- "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
- "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
- "license": "MIT",
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/picocolors": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
- "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
- "license": "ISC"
- },
- "node_modules/picomatch": {
- "version": "4.0.2",
- "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz",
- "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=12"
- },
- "funding": {
- "url": "https://github.com/sponsors/jonschlinkert"
- }
- },
- "node_modules/pirates": {
- "version": "4.0.6",
- "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz",
- "integrity": "sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 6"
- }
- },
- "node_modules/postcss": {
- "version": "8.4.49",
- "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.49.tgz",
- "integrity": "sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==",
- "funding": [
- {
- "type": "opencollective",
- "url": "https://opencollective.com/postcss/"
- },
- {
- "type": "tidelift",
- "url": "https://tidelift.com/funding/github/npm/postcss"
- },
- {
- "type": "github",
- "url": "https://github.com/sponsors/ai"
- }
- ],
- "license": "MIT",
- "peer": true,
- "dependencies": {
- "nanoid": "^3.3.7",
- "picocolors": "^1.1.1",
- "source-map-js": "^1.2.1"
- },
- "engines": {
- "node": "^10 || ^12 || >=14"
- }
- },
- "node_modules/postcss-load-config": {
- "version": "6.0.1",
- "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-6.0.1.tgz",
- "integrity": "sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==",
- "dev": true,
- "funding": [
- {
- "type": "opencollective",
- "url": "https://opencollective.com/postcss/"
- },
- {
- "type": "github",
- "url": "https://github.com/sponsors/ai"
- }
- ],
- "license": "MIT",
- "dependencies": {
- "lilconfig": "^3.1.1"
- },
- "engines": {
- "node": ">= 18"
- },
- "peerDependencies": {
- "jiti": ">=1.21.0",
- "postcss": ">=8.0.9",
- "tsx": "^4.8.1",
- "yaml": "^2.4.2"
- },
- "peerDependenciesMeta": {
- "jiti": {
- "optional": true
- },
- "postcss": {
- "optional": true
- },
- "tsx": {
- "optional": true
- },
- "yaml": {
- "optional": true
- }
- }
- },
- "node_modules/proxy-from-env": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
- "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
- "license": "MIT"
- },
- "node_modules/punycode": {
- "version": "2.3.1",
- "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
- "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
- "license": "MIT",
- "engines": {
- "node": ">=6"
- }
- },
- "node_modules/readdirp": {
- "version": "4.0.2",
- "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.0.2.tgz",
- "integrity": "sha512-yDMz9g+VaZkqBYS/ozoBJwaBhTbZo3UNYQHNRw1D3UFQB8oHB4uS/tAODO+ZLjGWmUbKnIlOWO+aaIiAxrUWHA==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">= 14.16.0"
- },
- "funding": {
- "type": "individual",
- "url": "https://paulmillr.com/funding/"
- }
- },
- "node_modules/resolve-from": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz",
- "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/rimraf": {
- "version": "5.0.10",
- "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.10.tgz",
- "integrity": "sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ==",
- "license": "ISC",
- "peer": true,
- "dependencies": {
- "glob": "^10.3.7"
- },
- "bin": {
- "rimraf": "dist/esm/bin.mjs"
- },
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
- }
- },
- "node_modules/rimraf/node_modules/glob": {
- "version": "10.4.5",
- "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz",
- "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==",
- "license": "ISC",
- "peer": true,
- "dependencies": {
- "foreground-child": "^3.1.0",
- "jackspeak": "^3.1.2",
- "minimatch": "^9.0.4",
- "minipass": "^7.1.2",
- "package-json-from-dist": "^1.0.0",
- "path-scurry": "^1.11.1"
- },
- "bin": {
- "glob": "dist/esm/bin.mjs"
- },
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
- }
- },
- "node_modules/rimraf/node_modules/jackspeak": {
- "version": "3.4.3",
- "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz",
- "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==",
- "license": "BlueOak-1.0.0",
- "peer": true,
- "dependencies": {
- "@isaacs/cliui": "^8.0.2"
- },
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
- },
- "optionalDependencies": {
- "@pkgjs/parseargs": "^0.11.0"
- }
- },
- "node_modules/rimraf/node_modules/lru-cache": {
- "version": "10.4.3",
- "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz",
- "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==",
- "license": "ISC",
- "peer": true
- },
- "node_modules/rimraf/node_modules/minimatch": {
- "version": "9.0.5",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
- "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
- "license": "ISC",
- "peer": true,
- "dependencies": {
- "brace-expansion": "^2.0.1"
- },
- "engines": {
- "node": ">=16 || 14 >=14.17"
- },
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
- }
- },
- "node_modules/rimraf/node_modules/path-scurry": {
- "version": "1.11.1",
- "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz",
- "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==",
- "license": "BlueOak-1.0.0",
- "peer": true,
- "dependencies": {
- "lru-cache": "^10.2.0",
- "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0"
- },
- "engines": {
- "node": ">=16 || 14 >=14.18"
- },
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
- }
- },
- "node_modules/rollup": {
- "version": "4.27.4",
- "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.27.4.tgz",
- "integrity": "sha512-RLKxqHEMjh/RGLsDxAEsaLO3mWgyoU6x9w6n1ikAzet4B3gI2/3yP6PWY2p9QzRTh6MfEIXB3MwsOY0Iv3vNrw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@types/estree": "1.0.6"
- },
- "bin": {
- "rollup": "dist/bin/rollup"
- },
- "engines": {
- "node": ">=18.0.0",
- "npm": ">=8.0.0"
- },
- "optionalDependencies": {
- "@rollup/rollup-android-arm-eabi": "4.27.4",
- "@rollup/rollup-android-arm64": "4.27.4",
- "@rollup/rollup-darwin-arm64": "4.27.4",
- "@rollup/rollup-darwin-x64": "4.27.4",
- "@rollup/rollup-freebsd-arm64": "4.27.4",
- "@rollup/rollup-freebsd-x64": "4.27.4",
- "@rollup/rollup-linux-arm-gnueabihf": "4.27.4",
- "@rollup/rollup-linux-arm-musleabihf": "4.27.4",
- "@rollup/rollup-linux-arm64-gnu": "4.27.4",
- "@rollup/rollup-linux-arm64-musl": "4.27.4",
- "@rollup/rollup-linux-powerpc64le-gnu": "4.27.4",
- "@rollup/rollup-linux-riscv64-gnu": "4.27.4",
- "@rollup/rollup-linux-s390x-gnu": "4.27.4",
- "@rollup/rollup-linux-x64-gnu": "4.27.4",
- "@rollup/rollup-linux-x64-musl": "4.27.4",
- "@rollup/rollup-win32-arm64-msvc": "4.27.4",
- "@rollup/rollup-win32-ia32-msvc": "4.27.4",
- "@rollup/rollup-win32-x64-msvc": "4.27.4",
- "fsevents": "~2.3.2"
- }
- },
- "node_modules/safe-buffer": {
- "version": "5.2.1",
- "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
- "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/feross"
- },
- {
- "type": "patreon",
- "url": "https://www.patreon.com/feross"
- },
- {
- "type": "consulting",
- "url": "https://feross.org/support"
- }
- ],
- "license": "MIT"
- },
- "node_modules/semver": {
- "version": "7.6.3",
- "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz",
- "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==",
- "license": "ISC",
- "bin": {
- "semver": "bin/semver.js"
- },
- "engines": {
- "node": ">=10"
- }
- },
- "node_modules/shebang-command": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
- "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
- "license": "MIT",
- "dependencies": {
- "shebang-regex": "^3.0.0"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/shebang-regex": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
- "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
- "license": "MIT",
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/signal-exit": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
- "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==",
- "license": "ISC",
- "engines": {
- "node": ">=14"
- },
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
- }
- },
- "node_modules/source-map": {
- "version": "0.8.0-beta.0",
- "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.8.0-beta.0.tgz",
- "integrity": "sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA==",
- "dev": true,
- "license": "BSD-3-Clause",
- "dependencies": {
- "whatwg-url": "^7.0.0"
- },
- "engines": {
- "node": ">= 8"
- }
- },
- "node_modules/source-map-js": {
- "version": "1.2.1",
- "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
- "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
- "license": "BSD-3-Clause",
- "peer": true,
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/string-width": {
- "version": "5.1.2",
- "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
- "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==",
- "license": "MIT",
- "dependencies": {
- "eastasianwidth": "^0.2.0",
- "emoji-regex": "^9.2.2",
- "strip-ansi": "^7.0.1"
- },
- "engines": {
- "node": ">=12"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/string-width-cjs": {
- "name": "string-width",
- "version": "4.2.3",
- "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
- "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
- "license": "MIT",
- "dependencies": {
- "emoji-regex": "^8.0.0",
- "is-fullwidth-code-point": "^3.0.0",
- "strip-ansi": "^6.0.1"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/string-width-cjs/node_modules/ansi-regex": {
- "version": "5.0.1",
- "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
- "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
- "license": "MIT",
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/string-width-cjs/node_modules/emoji-regex": {
- "version": "8.0.0",
- "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
- "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
- "license": "MIT"
- },
- "node_modules/string-width-cjs/node_modules/strip-ansi": {
- "version": "6.0.1",
- "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
- "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
- "license": "MIT",
- "dependencies": {
- "ansi-regex": "^5.0.1"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/strip-ansi": {
- "version": "7.1.0",
- "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz",
- "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==",
- "license": "MIT",
- "dependencies": {
- "ansi-regex": "^6.0.1"
- },
- "engines": {
- "node": ">=12"
- },
- "funding": {
- "url": "https://github.com/chalk/strip-ansi?sponsor=1"
- }
- },
- "node_modules/strip-ansi-cjs": {
- "name": "strip-ansi",
- "version": "6.0.1",
- "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
- "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
- "license": "MIT",
- "dependencies": {
- "ansi-regex": "^5.0.1"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/strip-ansi-cjs/node_modules/ansi-regex": {
- "version": "5.0.1",
- "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
- "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
- "license": "MIT",
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/sucrase": {
- "version": "3.35.0",
- "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.0.tgz",
- "integrity": "sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "@jridgewell/gen-mapping": "^0.3.2",
- "commander": "^4.0.0",
- "glob": "^10.3.10",
- "lines-and-columns": "^1.1.6",
- "mz": "^2.7.0",
- "pirates": "^4.0.1",
- "ts-interface-checker": "^0.1.9"
- },
- "bin": {
- "sucrase": "bin/sucrase",
- "sucrase-node": "bin/sucrase-node"
- },
- "engines": {
- "node": ">=16 || 14 >=14.17"
- }
- },
- "node_modules/sucrase/node_modules/glob": {
- "version": "10.4.5",
- "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz",
- "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "foreground-child": "^3.1.0",
- "jackspeak": "^3.1.2",
- "minimatch": "^9.0.4",
- "minipass": "^7.1.2",
- "package-json-from-dist": "^1.0.0",
- "path-scurry": "^1.11.1"
- },
- "bin": {
- "glob": "dist/esm/bin.mjs"
- },
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
- }
- },
- "node_modules/sucrase/node_modules/jackspeak": {
- "version": "3.4.3",
- "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz",
- "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==",
- "dev": true,
- "license": "BlueOak-1.0.0",
- "dependencies": {
- "@isaacs/cliui": "^8.0.2"
- },
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
- },
- "optionalDependencies": {
- "@pkgjs/parseargs": "^0.11.0"
- }
- },
- "node_modules/sucrase/node_modules/lru-cache": {
- "version": "10.4.3",
- "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz",
- "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==",
- "dev": true,
- "license": "ISC"
- },
- "node_modules/sucrase/node_modules/minimatch": {
- "version": "9.0.5",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
- "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
- "dev": true,
- "license": "ISC",
- "dependencies": {
- "brace-expansion": "^2.0.1"
- },
- "engines": {
- "node": ">=16 || 14 >=14.17"
- },
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
- }
- },
- "node_modules/sucrase/node_modules/path-scurry": {
- "version": "1.11.1",
- "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz",
- "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==",
- "dev": true,
- "license": "BlueOak-1.0.0",
- "dependencies": {
- "lru-cache": "^10.2.0",
- "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0"
- },
- "engines": {
- "node": ">=16 || 14 >=14.18"
- },
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
- }
- },
- "node_modules/thenify": {
- "version": "3.3.1",
- "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz",
- "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "any-promise": "^1.0.0"
- }
- },
- "node_modules/thenify-all": {
- "version": "1.6.0",
- "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz",
- "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "thenify": ">= 3.1.0 < 4"
- },
- "engines": {
- "node": ">=0.8"
- }
- },
- "node_modules/tinyexec": {
- "version": "0.3.1",
- "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.1.tgz",
- "integrity": "sha512-WiCJLEECkO18gwqIp6+hJg0//p23HXp4S+gGtAKu3mI2F2/sXC4FvHvXvB0zJVVaTPhx1/tOwdbRsa1sOBIKqQ==",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/tinyglobby": {
- "version": "0.2.10",
- "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.10.tgz",
- "integrity": "sha512-Zc+8eJlFMvgatPZTl6A9L/yht8QqdmUNtURHaKZLmKBE12hNPSrqNkUp2cs3M/UKmNVVAMFQYSjYIVHDjW5zew==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "fdir": "^6.4.2",
- "picomatch": "^4.0.2"
- },
- "engines": {
- "node": ">=12.0.0"
- }
- },
- "node_modules/tr46": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/tr46/-/tr46-1.0.1.tgz",
- "integrity": "sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA==",
- "license": "MIT",
- "dependencies": {
- "punycode": "^2.1.0"
- }
- },
- "node_modules/tree-kill": {
- "version": "1.2.2",
- "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz",
- "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==",
- "dev": true,
- "license": "MIT",
- "bin": {
- "tree-kill": "cli.js"
- }
- },
- "node_modules/ts-interface-checker": {
- "version": "0.1.13",
- "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz",
- "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==",
- "dev": true,
- "license": "Apache-2.0"
- },
- "node_modules/tsup": {
- "version": "8.3.5",
- "resolved": "https://registry.npmjs.org/tsup/-/tsup-8.3.5.tgz",
- "integrity": "sha512-Tunf6r6m6tnZsG9GYWndg0z8dEV7fD733VBFzFJ5Vcm1FtlXB8xBD/rtrBi2a3YKEV7hHtxiZtW5EAVADoe1pA==",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "bundle-require": "^5.0.0",
- "cac": "^6.7.14",
- "chokidar": "^4.0.1",
- "consola": "^3.2.3",
- "debug": "^4.3.7",
- "esbuild": "^0.24.0",
- "joycon": "^3.1.1",
- "picocolors": "^1.1.1",
- "postcss-load-config": "^6.0.1",
- "resolve-from": "^5.0.0",
- "rollup": "^4.24.0",
- "source-map": "0.8.0-beta.0",
- "sucrase": "^3.35.0",
- "tinyexec": "^0.3.1",
- "tinyglobby": "^0.2.9",
- "tree-kill": "^1.2.2"
- },
- "bin": {
- "tsup": "dist/cli-default.js",
- "tsup-node": "dist/cli-node.js"
- },
- "engines": {
- "node": ">=18"
- },
- "peerDependencies": {
- "@microsoft/api-extractor": "^7.36.0",
- "@swc/core": "^1",
- "postcss": "^8.4.12",
- "typescript": ">=4.5.0"
- },
- "peerDependenciesMeta": {
- "@microsoft/api-extractor": {
- "optional": true
- },
- "@swc/core": {
- "optional": true
- },
- "postcss": {
- "optional": true
- },
- "typescript": {
- "optional": true
- }
- }
- },
- "node_modules/vue": {
- "version": "3.5.13",
- "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.13.tgz",
- "integrity": "sha512-wmeiSMxkZCSc+PM2w2VRsOYAZC8GdipNFRTsLSfodVqI9mbejKeXEGr8SckuLnrQPGe3oJN5c3K0vpoU9q/wCQ==",
- "license": "MIT",
- "peer": true,
- "dependencies": {
- "@vue/compiler-dom": "3.5.13",
- "@vue/compiler-sfc": "3.5.13",
- "@vue/runtime-dom": "3.5.13",
- "@vue/server-renderer": "3.5.13",
- "@vue/shared": "3.5.13"
- },
- "peerDependencies": {
- "typescript": "*"
- },
- "peerDependenciesMeta": {
- "typescript": {
- "optional": true
- }
- }
- },
- "node_modules/webidl-conversions": {
- "version": "4.0.2",
- "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz",
- "integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==",
- "license": "BSD-2-Clause"
- },
- "node_modules/whatwg-url": {
- "version": "7.1.0",
- "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-7.1.0.tgz",
- "integrity": "sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==",
- "license": "MIT",
- "dependencies": {
- "lodash.sortby": "^4.7.0",
- "tr46": "^1.0.1",
- "webidl-conversions": "^4.0.2"
- }
- },
- "node_modules/which": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
- "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
- "license": "ISC",
- "dependencies": {
- "isexe": "^2.0.0"
- },
- "bin": {
- "node-which": "bin/node-which"
- },
- "engines": {
- "node": ">= 8"
- }
- },
- "node_modules/wrap-ansi": {
- "version": "8.1.0",
- "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz",
- "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==",
- "license": "MIT",
- "dependencies": {
- "ansi-styles": "^6.1.0",
- "string-width": "^5.0.1",
- "strip-ansi": "^7.0.1"
- },
- "engines": {
- "node": ">=12"
- },
- "funding": {
- "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
- }
- },
- "node_modules/wrap-ansi-cjs": {
- "name": "wrap-ansi",
- "version": "7.0.0",
- "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
- "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
- "license": "MIT",
- "dependencies": {
- "ansi-styles": "^4.0.0",
- "string-width": "^4.1.0",
- "strip-ansi": "^6.0.0"
- },
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
- }
- },
- "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": {
- "version": "5.0.1",
- "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
- "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
- "license": "MIT",
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/wrap-ansi-cjs/node_modules/ansi-styles": {
- "version": "4.3.0",
- "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
- "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
- "license": "MIT",
- "dependencies": {
- "color-convert": "^2.0.1"
- },
- "engines": {
- "node": ">=8"
- },
- "funding": {
- "url": "https://github.com/chalk/ansi-styles?sponsor=1"
- }
- },
- "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": {
- "version": "8.0.0",
- "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
- "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
- "license": "MIT"
- },
- "node_modules/wrap-ansi-cjs/node_modules/string-width": {
- "version": "4.2.3",
- "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
- "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
- "license": "MIT",
- "dependencies": {
- "emoji-regex": "^8.0.0",
- "is-fullwidth-code-point": "^3.0.0",
- "strip-ansi": "^6.0.1"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": {
- "version": "6.0.1",
- "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
- "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
- "license": "MIT",
- "dependencies": {
- "ansi-regex": "^5.0.1"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/ws": {
- "version": "7.5.10",
- "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz",
- "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==",
- "license": "MIT",
- "engines": {
- "node": ">=8.3.0"
- },
- "peerDependencies": {
- "bufferutil": "^4.0.1",
- "utf-8-validate": "^5.0.2"
- },
- "peerDependenciesMeta": {
- "bufferutil": {
- "optional": true
- },
- "utf-8-validate": {
- "optional": true
- }
- }
- },
- "node_modules/yaml": {
- "version": "2.6.1",
- "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.6.1.tgz",
- "integrity": "sha512-7r0XPzioN/Q9kXBro/XPnA6kznR73DHq+GXh5ON7ZozRO6aMjbmiBuKste2wslTFkC5d1dw0GooOCepZXJ2SAg==",
- "dev": true,
- "license": "ISC",
- "optional": true,
- "peer": true,
- "bin": {
- "yaml": "bin.mjs"
- },
- "engines": {
- "node": ">= 14"
- }
- }
- }
-}
\ No newline at end of file
diff --git a/packages/plugin-coinbase/package.json b/packages/plugin-coinbase/package.json
index 01b4bd3a1ae..f836f948c89 100644
--- a/packages/plugin-coinbase/package.json
+++ b/packages/plugin-coinbase/package.json
@@ -19,6 +19,6 @@
"scripts": {
"build": "tsup --format esm --dts",
"dev": "tsup --format esm --dts --watch",
- "lint": "eslint . --fix"
+ "lint": "eslint --fix --cache ."
}
}
diff --git a/packages/plugin-coinbase/src/plugins/advancedTrade.ts b/packages/plugin-coinbase/src/plugins/advancedTrade.ts
index 472616e2d16..64916f74218 100644
--- a/packages/plugin-coinbase/src/plugins/advancedTrade.ts
+++ b/packages/plugin-coinbase/src/plugins/advancedTrade.ts
@@ -1,4 +1,4 @@
-import { RESTClient } from '../../advanced-sdk-ts/src/rest';
+import { RESTClient } from "../../advanced-sdk-ts/src/rest";
import {
Action,
Plugin,
@@ -8,7 +8,7 @@ import {
HandlerCallback,
State,
composeContext,
- generateObjectV2,
+ generateObject,
ModelClass,
Provider,
} from "@ai16z/eliza";
@@ -20,8 +20,11 @@ import path from "path";
import { fileURLToPath } from "url";
import fs from "fs";
import { createArrayCsvWriter } from "csv-writer";
-import { OrderSide, OrderConfiguration } from '../../advanced-sdk-ts/src/rest/types/common-types';
-import { CreateOrderResponse } from '../../advanced-sdk-ts/src/rest/types/orders-types';
+import {
+ OrderSide,
+ OrderConfiguration,
+} from "../../advanced-sdk-ts/src/rest/types/common-types";
+import { CreateOrderResponse } from "../../advanced-sdk-ts/src/rest/types/orders-types";
// File path setup remains the same
const __filename = fileURLToPath(import.meta.url);
@@ -33,8 +36,10 @@ const tradeProvider: Provider = {
get: async (runtime: IAgentRuntime, _message: Memory) => {
try {
const client = new RESTClient(
- runtime.getSetting("COINBASE_API_KEY") ?? process.env.COINBASE_API_KEY,
- runtime.getSetting("COINBASE_PRIVATE_KEY") ?? process.env.COINBASE_PRIVATE_KEY
+ runtime.getSetting("COINBASE_API_KEY") ??
+ process.env.COINBASE_API_KEY,
+ runtime.getSetting("COINBASE_PRIVATE_KEY") ??
+ process.env.COINBASE_PRIVATE_KEY
);
// Get accounts and products information
@@ -88,7 +93,7 @@ const tradeProvider: Provider = {
return {
accounts: accounts.accounts,
products: products.products,
- trades: records
+ trades: records,
};
} catch (error) {
elizaLogger.error("Error in tradeProvider:", error);
@@ -101,20 +106,16 @@ export async function appendTradeToCsv(tradeResult: any) {
try {
const csvWriter = createArrayCsvWriter({
path: tradeCsvFilePath,
- header: [
- "Order ID",
- "Success",
- "Order Configuration",
- "Response",
- ],
+ header: ["Order ID", "Success", "Order Configuration", "Response"],
append: true,
});
elizaLogger.info("Trade result:", tradeResult);
-
// Format trade data based on success/failure
const formattedTrade = [
- tradeResult.success_response?.order_id || tradeResult.failure_response?.order_id || '',
+ tradeResult.success_response?.order_id ||
+ tradeResult.failure_response?.order_id ||
+ "",
tradeResult.success,
// JSON.stringify(tradeResult.order_configuration || {}),
// JSON.stringify(tradeResult.success_response || tradeResult.failure_response || {})
@@ -132,18 +133,28 @@ export async function appendTradeToCsv(tradeResult: any) {
}
}
-async function hasEnoughBalance(client: RESTClient, currency: string, amount: number, side: string): Promise {
+async function hasEnoughBalance(
+ client: RESTClient,
+ currency: string,
+ amount: number,
+ side: string
+): Promise {
try {
const response = await client.listAccounts({});
const accounts = JSON.parse(response);
elizaLogger.info("Accounts:", accounts);
const checkCurrency = side === "BUY" ? "USD" : currency;
- elizaLogger.info(`Checking balance for ${side} order of ${amount} ${checkCurrency}`);
+ elizaLogger.info(
+ `Checking balance for ${side} order of ${amount} ${checkCurrency}`
+ );
// Find account with exact currency match
- const account = accounts?.accounts.find(acc =>
- acc.currency === checkCurrency &&
- (checkCurrency === "USD" ? acc.type === "ACCOUNT_TYPE_FIAT" : acc.type === "ACCOUNT_TYPE_CRYPTO")
+ const account = accounts?.accounts.find(
+ (acc) =>
+ acc.currency === checkCurrency &&
+ (checkCurrency === "USD"
+ ? acc.type === "ACCOUNT_TYPE_FIAT"
+ : acc.type === "ACCOUNT_TYPE_CRYPTO")
);
if (!account) {
@@ -154,7 +165,9 @@ async function hasEnoughBalance(client: RESTClient, currency: string, amount: nu
const available = parseFloat(account.available_balance.value);
// Add buffer for fees only on USD purchases
const requiredAmount = side === "BUY" ? amount * 1.01 : amount;
- elizaLogger.info(`Required amount (including buffer): ${requiredAmount} ${checkCurrency}`);
+ elizaLogger.info(
+ `Required amount (including buffer): ${requiredAmount} ${checkCurrency}`
+ );
const hasBalance = available >= requiredAmount;
elizaLogger.info(`Has sufficient balance: ${hasBalance}`);
@@ -162,10 +175,10 @@ async function hasEnoughBalance(client: RESTClient, currency: string, amount: nu
return hasBalance;
} catch (error) {
elizaLogger.error("Balance check failed with error:", {
- error: error instanceof Error ? error.message : 'Unknown error',
+ error: error instanceof Error ? error.message : "Unknown error",
currency,
amount,
- side
+ side,
});
return false;
}
@@ -175,15 +188,23 @@ export const executeAdvancedTradeAction: Action = {
name: "EXECUTE_ADVANCED_TRADE",
description: "Execute a trade using Coinbase Advanced Trading API",
validate: async (runtime: IAgentRuntime) => {
- return !!(runtime.getSetting("COINBASE_API_KEY") || process.env.COINBASE_API_KEY) &&
- !!(runtime.getSetting("COINBASE_PRIVATE_KEY") || process.env.COINBASE_PRIVATE_KEY);
+ return (
+ !!(
+ runtime.getSetting("COINBASE_API_KEY") ||
+ process.env.COINBASE_API_KEY
+ ) &&
+ !!(
+ runtime.getSetting("COINBASE_PRIVATE_KEY") ||
+ process.env.COINBASE_PRIVATE_KEY
+ )
+ );
},
similes: [
"EXECUTE_ADVANCED_TRADE",
"ADVANCED_MARKET_ORDER",
"ADVANCED_LIMIT_ORDER",
"COINBASE_PRO_TRADE",
- "PROFESSIONAL_TRADE"
+ "PROFESSIONAL_TRADE",
],
handler: async (
runtime: IAgentRuntime,
@@ -197,60 +218,78 @@ export const executeAdvancedTradeAction: Action = {
// Initialize client
try {
client = new RESTClient(
- runtime.getSetting("COINBASE_API_KEY") ?? process.env.COINBASE_API_KEY,
- runtime.getSetting("COINBASE_PRIVATE_KEY") ?? process.env.COINBASE_PRIVATE_KEY
+ runtime.getSetting("COINBASE_API_KEY") ??
+ process.env.COINBASE_API_KEY,
+ runtime.getSetting("COINBASE_PRIVATE_KEY") ??
+ process.env.COINBASE_PRIVATE_KEY
);
elizaLogger.info("Advanced trade client initialized");
} catch (error) {
elizaLogger.error("Client initialization failed:", error);
- callback({
- text: "Failed to initialize trading client. Please check your API credentials."
- }, []);
+ callback(
+ {
+ text: "Failed to initialize trading client. Please check your API credentials.",
+ },
+ []
+ );
return;
}
// Generate trade details
let tradeDetails;
try {
- tradeDetails = await generateObjectV2({
+ tradeDetails = await generateObject({
runtime,
- context: composeContext({ state, template: advancedTradeTemplate }),
+ context: composeContext({
+ state,
+ template: advancedTradeTemplate,
+ }),
modelClass: ModelClass.LARGE,
schema: AdvancedTradeSchema,
});
elizaLogger.info("Trade details generated:", tradeDetails.object);
} catch (error) {
elizaLogger.error("Trade details generation failed:", error);
- callback({
- text: "Failed to generate trade details. Please provide valid trading parameters."
- }, []);
+ callback(
+ {
+ text: "Failed to generate trade details. Please provide valid trading parameters.",
+ },
+ []
+ );
return;
}
// Validate trade content
if (!isAdvancedTradeContent(tradeDetails.object)) {
elizaLogger.error("Invalid trade content:", tradeDetails.object);
- callback({
- text: "Invalid trade details. Please check your input parameters."
- }, []);
+ callback(
+ {
+ text: "Invalid trade details. Please check your input parameters.",
+ },
+ []
+ );
return;
}
- const { productId, amount, side, orderType, limitPrice } = tradeDetails.object;
+ const { productId, amount, side, orderType, limitPrice } =
+ tradeDetails.object;
// Configure order
let orderConfiguration: OrderConfiguration;
try {
if (orderType === "MARKET") {
- orderConfiguration = side === "BUY" ? {
- market_market_ioc: {
- quote_size: amount.toString()
- }
- } : {
- market_market_ioc: {
- base_size: amount.toString()
- }
- };
+ orderConfiguration =
+ side === "BUY"
+ ? {
+ market_market_ioc: {
+ quote_size: amount.toString(),
+ },
+ }
+ : {
+ market_market_ioc: {
+ base_size: amount.toString(),
+ },
+ };
} else {
if (!limitPrice) {
throw new Error("Limit price is required for limit orders");
@@ -259,54 +298,77 @@ export const executeAdvancedTradeAction: Action = {
limit_limit_gtc: {
baseSize: amount.toString(),
limitPrice: limitPrice.toString(),
- postOnly: false
- }
+ postOnly: false,
+ },
};
}
- elizaLogger.info("Order configuration created:", orderConfiguration);
+ elizaLogger.info(
+ "Order configuration created:",
+ orderConfiguration
+ );
} catch (error) {
elizaLogger.error("Order configuration failed:", error);
- callback({
- text: error instanceof Error ? error.message : "Failed to configure order parameters."
- }, []);
+ callback(
+ {
+ text:
+ error instanceof Error
+ ? error.message
+ : "Failed to configure order parameters.",
+ },
+ []
+ );
return;
}
// Execute trade
let order: CreateOrderResponse;
try {
- if (!(await hasEnoughBalance(client, productId.split('-')[0], amount, side))) {
- callback({
- text: `Insufficient ${side === "BUY" ? "USD" : productId.split('-')[0]} balance to execute this trade`
- }, []);
+ if (
+ !(await hasEnoughBalance(
+ client,
+ productId.split("-")[0],
+ amount,
+ side
+ ))
+ ) {
+ callback(
+ {
+ text: `Insufficient ${side === "BUY" ? "USD" : productId.split("-")[0]} balance to execute this trade`,
+ },
+ []
+ );
return;
}
order = await client.createOrder({
- clientOrderId: crypto.randomUUID(),
+ clientOrderId: crypto.randomUUID(),
productId,
side: side === "BUY" ? OrderSide.BUY : OrderSide.SELL,
- orderConfiguration
+ orderConfiguration,
});
elizaLogger.info("Trade executed successfully:", order);
} catch (error) {
elizaLogger.error("Trade execution failed:", error?.message);
- callback({
- text: `Failed to execute trade: ${error instanceof Error ? error.message : "Unknown error occurred"}`
- }, []);
+ callback(
+ {
+ text: `Failed to execute trade: ${error instanceof Error ? error.message : "Unknown error occurred"}`,
+ },
+ []
+ );
return;
}
- // Log trade to CSV
- try {
- // await appendTradeToCsv(order);
- elizaLogger.info("Trade logged to CSV");
- } catch (csvError) {
- elizaLogger.warn("Failed to log trade to CSV:", csvError);
- // Continue execution as this is non-critical
- }
+ // Log trade to CSV
+ try {
+ // await appendTradeToCsv(order);
+ elizaLogger.info("Trade logged to CSV");
+ } catch (csvError) {
+ elizaLogger.warn("Failed to log trade to CSV:", csvError);
+ // Continue execution as this is non-critical
+ }
- callback({
+ callback(
+ {
text: `Advanced Trade executed successfully:
- Product: ${productId}
- Type: ${orderType} Order
@@ -316,14 +378,18 @@ export const executeAdvancedTradeAction: Action = {
- Status: ${order.success}
- Order Id: ${order.order_id}
- Response: ${JSON.stringify(order.response)}
-- Order Configuration: ${JSON.stringify(order.order_configuration)}`
- }, []);
+- Order Configuration: ${JSON.stringify(order.order_configuration)}`,
+ },
+ []
+ );
},
examples: [
[
{
user: "{{user1}}",
- content: { text: "Place an advanced market order to buy $1 worth of BTC" }
+ content: {
+ text: "Place an advanced market order to buy $1 worth of BTC",
+ },
},
{
user: "{{agentName}}",
@@ -336,14 +402,14 @@ export const executeAdvancedTradeAction: Action = {
- Order ID: CB-ADV-12345
- Success: true
- Response: {"success_response":{}}
-- Order Configuration: {"market_market_ioc":{"quote_size":"1000"}}`
- }
- }
+- Order Configuration: {"market_market_ioc":{"quote_size":"1000"}}`,
+ },
+ },
],
[
{
user: "{{user1}}",
- content: { text: "Set a limit order to sell 0.5 ETH at $2000" }
+ content: { text: "Set a limit order to sell 0.5 ETH at $2000" },
},
{
user: "{{agentName}}",
@@ -357,11 +423,11 @@ export const executeAdvancedTradeAction: Action = {
- Order ID: CB-ADV-67890
- Success: true
- Response: {"success_response":{}}
-- Order Configuration: {"limit_limit_gtc":{"baseSize":"0.5","limitPrice":"2000","postOnly":false}}`
- }
- }
- ]
- ]
+- Order Configuration: {"limit_limit_gtc":{"baseSize":"0.5","limitPrice":"2000","postOnly":false}}`,
+ },
+ },
+ ],
+ ],
};
export const advancedTradePlugin: Plugin = {
@@ -369,4 +435,4 @@ export const advancedTradePlugin: Plugin = {
description: "Enables advanced trading using Coinbase Advanced Trading API",
actions: [executeAdvancedTradeAction],
providers: [tradeProvider],
-};
\ No newline at end of file
+};
diff --git a/packages/plugin-coinbase/src/plugins/commerce.ts b/packages/plugin-coinbase/src/plugins/commerce.ts
index 95d651df6d9..7152cbcc847 100644
--- a/packages/plugin-coinbase/src/plugins/commerce.ts
+++ b/packages/plugin-coinbase/src/plugins/commerce.ts
@@ -1,7 +1,7 @@
import {
composeContext,
elizaLogger,
- generateObjectV2,
+ generateObject,
ModelClass,
Provider,
} from "@ai16z/eliza";
@@ -123,7 +123,8 @@ export const createCoinbaseChargeAction: Action = {
"GET_CHARGE_STATUS",
"LIST_CHARGES",
],
- description: "Create and manage payment charges using Coinbase Commerce. Supports fixed and dynamic pricing, multiple currencies (USD, EUR, USDC), and provides charge status tracking and management features.",
+ description:
+ "Create and manage payment charges using Coinbase Commerce. Supports fixed and dynamic pricing, multiple currencies (USD, EUR, USDC), and provides charge status tracking and management features.",
validate: async (runtime: IAgentRuntime, _message: Memory) => {
const coinbaseCommerceKeyOk = !!runtime.getSetting(
"COINBASE_COMMERCE_KEY"
@@ -151,7 +152,7 @@ export const createCoinbaseChargeAction: Action = {
template: chargeTemplate,
});
- const chargeDetails = await generateObjectV2({
+ const chargeDetails = await generateObject({
runtime,
context,
modelClass: ModelClass.LARGE,
@@ -407,7 +408,7 @@ export const getChargeDetailsAction: Action = {
state,
template: getChargeTemplate,
});
- const chargeDetails = await generateObjectV2({
+ const chargeDetails = await generateObject({
runtime,
context,
modelClass: ModelClass.LARGE,
@@ -506,7 +507,7 @@ export const chargeProvider: Provider = {
elizaLogger.log("Current Balances:", balances);
elizaLogger.log("Last Transactions:", transactions);
}
- const formattedCharges = charges.map(charge => ({
+ const formattedCharges = charges.map((charge) => ({
id: charge.id,
name: charge.name,
description: charge.description,
diff --git a/packages/plugin-coinbase/src/plugins/massPayments.ts b/packages/plugin-coinbase/src/plugins/massPayments.ts
index 41110615f95..bec340e58a0 100644
--- a/packages/plugin-coinbase/src/plugins/massPayments.ts
+++ b/packages/plugin-coinbase/src/plugins/massPayments.ts
@@ -2,7 +2,7 @@ import { Coinbase, Wallet } from "@coinbase/coinbase-sdk";
import {
composeContext,
elizaLogger,
- generateObjectV2,
+ generateObject,
ModelClass,
Action,
IAgentRuntime,
@@ -118,97 +118,102 @@ async function executeMassPayout(
}
for (const address of receivingAddresses) {
elizaLogger.log("Processing payout for address:", address);
- if (address) {
- try {
- // Check balance before initiating transfer
+ if (address) {
+ try {
+ // Check balance before initiating transfer
- const walletBalance =
- await sendingWallet.getBalance(assetIdLowercase);
+ const walletBalance =
+ await sendingWallet.getBalance(assetIdLowercase);
- elizaLogger.log("Wallet balance for asset:", {
- assetId,
- walletBalance,
- });
+ elizaLogger.log("Wallet balance for asset:", {
+ assetId,
+ walletBalance,
+ });
- if (walletBalance.lessThan(transferAmount)) {
- const insufficientFunds = `Insufficient funds for address ${sendingWallet.getDefaultAddress()} to send to ${address}. Required: ${transferAmount}, Available: ${walletBalance}`;
- elizaLogger.error(insufficientFunds);
-
- transactions.push({
- address,
- amount: transferAmount,
- status: "Failed",
- errorCode: insufficientFunds,
- transactionUrl: null,
- });
- continue;
- }
-
- // Execute the transfer
- const transfer = await executeTransfer(
- sendingWallet,
- transferAmount,
- assetIdLowercase,
- address
- );
+ if (walletBalance.lessThan(transferAmount)) {
+ const insufficientFunds = `Insufficient funds for address ${sendingWallet.getDefaultAddress()} to send to ${address}. Required: ${transferAmount}, Available: ${walletBalance}`;
+ elizaLogger.error(insufficientFunds);
- transactions.push({
- address,
- amount: transfer.getAmount().toNumber(),
- status: "Success",
- errorCode: null,
- transactionUrl: transfer.getTransactionLink(),
- });
- } catch (error) {
- elizaLogger.error(
- "Error during transfer for address:",
- address,
- error
- );
transactions.push({
address,
amount: transferAmount,
status: "Failed",
- errorCode: error?.code || "Unknown Error",
+ errorCode: insufficientFunds,
transactionUrl: null,
});
+ continue;
}
- } else {
- elizaLogger.log("Skipping invalid or empty address.");
+
+ // Execute the transfer
+ const transfer = await executeTransfer(
+ sendingWallet,
+ transferAmount,
+ assetIdLowercase,
+ address
+ );
+
+ transactions.push({
+ address,
+ amount: transfer.getAmount().toNumber(),
+ status: "Success",
+ errorCode: null,
+ transactionUrl: transfer.getTransactionLink(),
+ });
+ } catch (error) {
+ elizaLogger.error(
+ "Error during transfer for address:",
+ address,
+ error
+ );
transactions.push({
- address: "Invalid or Empty",
+ address,
amount: transferAmount,
status: "Failed",
- errorCode: "Invalid Address",
+ errorCode: error?.code || "Unknown Error",
transactionUrl: null,
});
}
+ } else {
+ elizaLogger.log("Skipping invalid or empty address.");
+ transactions.push({
+ address: "Invalid or Empty",
+ amount: transferAmount,
+ status: "Failed",
+ errorCode: "Invalid Address",
+ transactionUrl: null,
+ });
}
- // Send 1% to charity
- const charityAddress = getCharityAddress(networkId);
+ }
+ // Send 1% to charity
+ const charityAddress = getCharityAddress(networkId);
- try {
- const charityTransfer = await executeTransfer(sendingWallet, transferAmount * 0.01, assetId, charityAddress);
+ try {
+ const charityTransfer = await executeTransfer(
+ sendingWallet,
+ transferAmount * 0.01,
+ assetId,
+ charityAddress
+ );
- transactions.push({
+ transactions.push({
address: charityAddress,
amount: charityTransfer.getAmount().toNumber(),
status: "Success",
errorCode: null,
- transactionUrl: charityTransfer.getTransactionLink(),
- });
- } catch (error) {
- elizaLogger.error("Error during charity transfer:", error);
- transactions.push({
- address: charityAddress,
- amount: transferAmount * 0.01,
- status: "Failed",
- errorCode: error?.message || "Unknown Error",
- transactionUrl: null,
- });
- }
- await appendTransactionsToCsv(transactions);
- elizaLogger.log("Finished processing mass payouts.");
+ transactionUrl: charityTransfer.getTransactionLink(),
+ });
+ } catch (error) {
+ elizaLogger.error("Error during charity transfer:", error);
+ transactions.push({
+ address: charityAddress,
+ amount: transferAmount * 0.01,
+ status: "Failed",
+ errorCode: error?.message || "Unknown Error",
+ transactionUrl: null,
+ });
+ }
+ await appendTransactionsToCsv(transactions);
+ elizaLogger.log("Finished processing mass payouts.");
return transactions;
}
@@ -261,7 +266,7 @@ export const sendMassPayoutAction: Action = {
template: transferTemplate,
});
- const transferDetails = await generateObjectV2({
+ const transferDetails = await generateObject({
runtime,
context,
modelClass: ModelClass.LARGE,
diff --git a/packages/plugin-coinbase/src/plugins/tokenContract.ts b/packages/plugin-coinbase/src/plugins/tokenContract.ts
index 5eaf10b632b..3d587d79c2f 100644
--- a/packages/plugin-coinbase/src/plugins/tokenContract.ts
+++ b/packages/plugin-coinbase/src/plugins/tokenContract.ts
@@ -8,7 +8,7 @@ import {
HandlerCallback,
State,
composeContext,
- generateObjectV2,
+ generateObject,
ModelClass,
} from "@ai16z/eliza";
import { initializeWallet } from "../utils";
@@ -39,13 +39,13 @@ const contractsCsvFilePath = path.join(baseDir, "contracts.csv");
// Add this helper at the top level
const serializeBigInt = (value: any): any => {
- if (typeof value === 'bigint') {
+ if (typeof value === "bigint") {
return value.toString();
}
if (Array.isArray(value)) {
return value.map(serializeBigInt);
}
- if (typeof value === 'object' && value !== null) {
+ if (typeof value === "object" && value !== null) {
return Object.fromEntries(
Object.entries(value).map(([k, v]) => [k, serializeBigInt(v)])
);
@@ -112,7 +112,7 @@ export const deployTokenContractAction: Action = {
template: tokenContractTemplate,
});
- const contractDetails = await generateObjectV2({
+ const contractDetails = await generateObject({
runtime,
context,
modelClass: ModelClass.SMALL,
@@ -323,7 +323,7 @@ export const invokeContractAction: Action = {
template: contractInvocationTemplate,
});
- const invocationDetails = await generateObjectV2({
+ const invocationDetails = await generateObject({
runtime,
context,
modelClass: ModelClass.LARGE,
@@ -340,8 +340,14 @@ export const invokeContractAction: Action = {
return;
}
- const { contractAddress, method, args, amount, assetId, networkId } =
- invocationDetails.object;
+ const {
+ contractAddress,
+ method,
+ args,
+ amount,
+ assetId,
+ networkId,
+ } = invocationDetails.object;
const wallet = await initializeWallet(runtime, networkId);
// Prepare invocation options
@@ -351,10 +357,10 @@ export const invokeContractAction: Action = {
abi: ABI,
args: {
...args,
- amount: args.amount || amount // Ensure amount is passed in args
+ amount: args.amount || amount, // Ensure amount is passed in args
},
networkId,
- assetId
+ assetId,
};
elizaLogger.log("Invocation options:", invocationOptions);
// Invoke the contract
@@ -445,15 +451,19 @@ Contract invocation has been logged to the CSV file.`,
export const readContractAction: Action = {
name: "READ_CONTRACT",
- description: "Read data from a deployed smart contract using the Coinbase SDK",
+ description:
+ "Read data from a deployed smart contract using the Coinbase SDK",
validate: async (runtime: IAgentRuntime, _message: Memory) => {
elizaLogger.log("Validating runtime for READ_CONTRACT...");
- return !!(
- runtime.character.settings.secrets?.COINBASE_API_KEY ||
- process.env.COINBASE_API_KEY
- ) && !!(
- runtime.character.settings.secrets?.COINBASE_PRIVATE_KEY ||
- process.env.COINBASE_PRIVATE_KEY
+ return (
+ !!(
+ runtime.character.settings.secrets?.COINBASE_API_KEY ||
+ process.env.COINBASE_API_KEY
+ ) &&
+ !!(
+ runtime.character.settings.secrets?.COINBASE_PRIVATE_KEY ||
+ process.env.COINBASE_PRIVATE_KEY
+ )
);
},
handler: async (
@@ -467,8 +477,12 @@ export const readContractAction: Action = {
try {
Coinbase.configure({
- apiKeyName: runtime.getSetting("COINBASE_API_KEY") ?? process.env.COINBASE_API_KEY,
- privateKey: runtime.getSetting("COINBASE_PRIVATE_KEY") ?? process.env.COINBASE_PRIVATE_KEY,
+ apiKeyName:
+ runtime.getSetting("COINBASE_API_KEY") ??
+ process.env.COINBASE_API_KEY,
+ privateKey:
+ runtime.getSetting("COINBASE_PRIVATE_KEY") ??
+ process.env.COINBASE_PRIVATE_KEY,
});
const context = composeContext({
@@ -476,7 +490,7 @@ export const readContractAction: Action = {
template: readContractTemplate,
});
- const readDetails = await generateObjectV2({
+ const readDetails = await generateObject({
runtime,
context,
modelClass: ModelClass.SMALL,
@@ -493,8 +507,15 @@ export const readContractAction: Action = {
return;
}
- const { contractAddress, method, args, networkId, abi } = readDetails.object;
- elizaLogger.log("Reading contract:", { contractAddress, method, args, networkId, abi });
+ const { contractAddress, method, args, networkId, abi } =
+ readDetails.object;
+ elizaLogger.log("Reading contract:", {
+ contractAddress,
+ method,
+ args,
+ networkId,
+ abi,
+ });
const result = await readContract({
networkId,
@@ -509,19 +530,24 @@ export const readContractAction: Action = {
elizaLogger.info("Contract read result:", serializedResult);
- callback({
- text: `Contract read successful:
+ callback(
+ {
+ text: `Contract read successful:
- Contract Address: ${contractAddress}
- Method: ${method}
- Network: ${networkId}
-- Result: ${JSON.stringify(serializedResult, null, 2)}`
- }, []);
-
+- Result: ${JSON.stringify(serializedResult, null, 2)}`,
+ },
+ []
+ );
} catch (error) {
elizaLogger.error("Error reading contract:", error);
- callback({
- text: `Failed to read contract: ${error instanceof Error ? error.message : "Unknown error"}`
- }, []);
+ callback(
+ {
+ text: `Failed to read contract: ${error instanceof Error ? error.message : "Unknown error"}`,
+ },
+ []
+ );
}
},
examples: [
@@ -549,6 +575,11 @@ export const readContractAction: Action = {
export const tokenContractPlugin: Plugin = {
name: "tokenContract",
- description: "Enables deployment, invocation, and reading of ERC20, ERC721, and ERC1155 token contracts using the Coinbase SDK",
- actions: [deployTokenContractAction, invokeContractAction, readContractAction],
+ description:
+ "Enables deployment, invocation, and reading of ERC20, ERC721, and ERC1155 token contracts using the Coinbase SDK",
+ actions: [
+ deployTokenContractAction,
+ invokeContractAction,
+ readContractAction,
+ ],
};
diff --git a/packages/plugin-coinbase/src/plugins/trade.ts b/packages/plugin-coinbase/src/plugins/trade.ts
index 9045261ba5b..b0510f633d7 100644
--- a/packages/plugin-coinbase/src/plugins/trade.ts
+++ b/packages/plugin-coinbase/src/plugins/trade.ts
@@ -1,4 +1,4 @@
-import { Coinbase} from "@coinbase/coinbase-sdk";
+import { Coinbase } from "@coinbase/coinbase-sdk";
import {
Action,
Plugin,
@@ -8,7 +8,7 @@ import {
HandlerCallback,
State,
composeContext,
- generateObjectV2,
+ generateObject,
ModelClass,
Provider,
} from "@ai16z/eliza";
@@ -132,7 +132,7 @@ export const executeTradeAction: Action = {
template: tradeTemplate,
});
- const tradeDetails = await generateObjectV2({
+ const tradeDetails = await generateObject({
runtime,
context,
modelClass: ModelClass.LARGE,
@@ -165,7 +165,13 @@ export const executeTradeAction: Action = {
return;
}
- const { trade, transfer } = await executeTradeAndCharityTransfer(runtime, network, amount, sourceAsset, targetAsset);
+ const { trade, transfer } = await executeTradeAndCharityTransfer(
+ runtime,
+ network,
+ amount,
+ sourceAsset,
+ targetAsset
+ );
let responseText = `Trade executed successfully:
- Network: ${network}
@@ -181,10 +187,7 @@ export const executeTradeAction: Action = {
responseText += "\n(Note: Charity transfer was not completed)";
}
- callback(
- { text: responseText },
- []
- );
+ callback({ text: responseText }, []);
} catch (error) {
elizaLogger.error("Error during trade execution:", error);
callback(
@@ -282,13 +285,13 @@ export const executeTradeAction: Action = {
],
],
similes: [
- "EXECUTE_TRADE", // Primary action name
- "SWAP_TOKENS", // For token swaps
- "CONVERT_CURRENCY", // For currency conversion
- "EXCHANGE_ASSETS", // For asset exchange
- "MARKET_BUY", // For buying assets
- "MARKET_SELL", // For selling assets
- "TRADE_CRYPTO", // Generic crypto trading
+ "EXECUTE_TRADE", // Primary action name
+ "SWAP_TOKENS", // For token swaps
+ "CONVERT_CURRENCY", // For currency conversion
+ "EXCHANGE_ASSETS", // For asset exchange
+ "MARKET_BUY", // For buying assets
+ "MARKET_SELL", // For selling assets
+ "TRADE_CRYPTO", // Generic crypto trading
],
};
diff --git a/packages/plugin-coinbase/src/plugins/webhooks.ts b/packages/plugin-coinbase/src/plugins/webhooks.ts
index 33be86c4968..fedd2c0556c 100644
--- a/packages/plugin-coinbase/src/plugins/webhooks.ts
+++ b/packages/plugin-coinbase/src/plugins/webhooks.ts
@@ -8,7 +8,7 @@ import {
HandlerCallback,
State,
composeContext,
- generateObjectV2,
+ generateObject,
ModelClass,
Provider,
} from "@ai16z/eliza";
@@ -93,7 +93,7 @@ export const createWebhookAction: Action = {
template: webhookTemplate,
});
- const webhookDetails = await generateObjectV2({
+ const webhookDetails = await generateObject({
runtime,
context,
modelClass: ModelClass.LARGE,
@@ -110,8 +110,11 @@ export const createWebhookAction: Action = {
return;
}
- const { networkId, eventType, eventFilters, eventTypeFilter } = webhookDetails.object as WebhookContent;
- const notificationUri = runtime.getSetting("COINBASE_NOTIFICATION_URI") ?? process.env.COINBASE_NOTIFICATION_URI;
+ const { networkId, eventType, eventFilters, eventTypeFilter } =
+ webhookDetails.object as WebhookContent;
+ const notificationUri =
+ runtime.getSetting("COINBASE_NOTIFICATION_URI") ??
+ process.env.COINBASE_NOTIFICATION_URI;
if (!notificationUri) {
callback(
@@ -122,9 +125,23 @@ export const createWebhookAction: Action = {
);
return;
}
- elizaLogger.log("Creating webhook with details:", {networkId, notificationUri, eventType, eventTypeFilter, eventFilters});
- const webhook = await Webhook.create({networkId, notificationUri, eventType, eventFilters});
- elizaLogger.log("Webhook created successfully:", webhook.toString());
+ elizaLogger.log("Creating webhook with details:", {
+ networkId,
+ notificationUri,
+ eventType,
+ eventTypeFilter,
+ eventFilters,
+ });
+ const webhook = await Webhook.create({
+ networkId,
+ notificationUri,
+ eventType,
+ eventFilters,
+ });
+ elizaLogger.log(
+ "Webhook created successfully:",
+ webhook.toString()
+ );
callback(
{
text: `Webhook created successfully: ${webhook.toString()}`,
@@ -160,7 +177,7 @@ export const createWebhookAction: Action = {
},
},
],
- ]
+ ],
};
export const webhookPlugin: Plugin = {
@@ -168,4 +185,4 @@ export const webhookPlugin: Plugin = {
description: "Manages webhooks using the Coinbase SDK.",
actions: [createWebhookAction],
providers: [webhookProvider],
-};
\ No newline at end of file
+};
diff --git a/packages/plugin-coinbase/src/utils.ts b/packages/plugin-coinbase/src/utils.ts
index c3e801a45f4..74a0c4b9ae3 100644
--- a/packages/plugin-coinbase/src/utils.ts
+++ b/packages/plugin-coinbase/src/utils.ts
@@ -1,5 +1,12 @@
-import { Coinbase, Trade, Transfer, Wallet, WalletData, Webhook } from "@coinbase/coinbase-sdk";
-import { elizaLogger, IAgentRuntime } from "@ai16z/eliza";
+import {
+ Coinbase,
+ Trade,
+ Transfer,
+ Wallet,
+ WalletData,
+ Webhook,
+} from "@coinbase/coinbase-sdk";
+import { elizaLogger, IAgentRuntime, settings } from "@ai16z/eliza";
import fs from "fs";
import path from "path";
import { EthereumTransaction } from "@coinbase/coinbase-sdk/dist/client";
@@ -56,7 +63,10 @@ export async function initializeWallet(
// save it to gitignored file
wallet.saveSeed(seedFilePath);
}
- elizaLogger.log("Wallet created and stored new wallet:", walletAddress);
+ elizaLogger.log(
+ "Wallet created and stored new wallet:",
+ walletAddress
+ );
} catch (error) {
elizaLogger.error("Error updating character secrets:", error);
throw error;
@@ -92,7 +102,13 @@ export async function initializeWallet(
* @param {string} sourceAsset - The source asset to trade.
* @param {string} targetAsset - The target asset to trade.
*/
-export async function executeTradeAndCharityTransfer(runtime: IAgentRuntime, network: string, amount: number, sourceAsset: string, targetAsset: string) {
+export async function executeTradeAndCharityTransfer(
+ runtime: IAgentRuntime,
+ network: string,
+ amount: number,
+ sourceAsset: string,
+ targetAsset: string
+) {
const wallet = await initializeWallet(runtime, network);
elizaLogger.log("Wallet initialized:", {
@@ -112,18 +128,25 @@ export async function executeTradeAndCharityTransfer(runtime: IAgentRuntime, net
let transfer: Transfer;
if (charityAddress && charityAmount > 0) {
- transfer = await executeTransfer(wallet, charityAmount, assetIdLowercase, charityAddress);
+ transfer = await executeTransfer(
+ wallet,
+ charityAmount,
+ assetIdLowercase,
+ charityAddress
+ );
elizaLogger.log("Charity Transfer successful:", {
address: charityAddress,
transactionUrl: transfer.getTransactionLink(),
});
- await appendTransactionsToCsv([{
- address: charityAddress,
- amount: charityAmount,
- status: "Success",
- errorCode: null,
- transactionUrl: transfer.getTransactionLink(),
- }]);
+ await appendTransactionsToCsv([
+ {
+ address: charityAddress,
+ amount: charityAmount,
+ status: "Success",
+ errorCode: null,
+ transactionUrl: transfer.getTransactionLink(),
+ },
+ ]);
}
const trade: Trade = await wallet.createTrade(tradeParams);
@@ -219,7 +242,7 @@ export async function appendWebhooksToCsv(webhooks: Webhook[]) {
});
await csvWriter.writeRecords([]); // Create an empty file with headers
elizaLogger.log("New CSV file created with headers.");
- }
+ }
const csvWriter = createArrayCsvWriter({
path: webhookCsvFilePath,
header: [
@@ -387,7 +410,13 @@ export async function getWalletDetails(
* @param {string} sourceAsset - The source asset to transfer.
* @param {string} targetAddress - The target address to transfer to.
*/
-export async function executeTransferAndCharityTransfer(wallet: Wallet, amount: number, sourceAsset: string, targetAddress: string, network: string) {
+export async function executeTransferAndCharityTransfer(
+ wallet: Wallet,
+ amount: number,
+ sourceAsset: string,
+ targetAddress: string,
+ network: string
+) {
const charityAddress = getCharityAddress(network);
const charityAmount = charityAddress ? amount * 0.01 : 0;
const transferAmount = charityAddress ? amount - charityAmount : amount;
@@ -395,8 +424,16 @@ export async function executeTransferAndCharityTransfer(wallet: Wallet, amount:
let charityTransfer: Transfer;
if (charityAddress && charityAmount > 0) {
- charityTransfer = await executeTransfer(wallet, charityAmount, assetIdLowercase, charityAddress);
- elizaLogger.log("Charity Transfer successful:", charityTransfer.toString());
+ charityTransfer = await executeTransfer(
+ wallet,
+ charityAmount,
+ assetIdLowercase,
+ charityAddress
+ );
+ elizaLogger.log(
+ "Charity Transfer successful:",
+ charityTransfer.toString()
+ );
}
const transferDetails = {
@@ -430,7 +467,7 @@ export async function executeTransferAndCharityTransfer(wallet: Wallet, amount:
transfer,
charityTransfer,
responseText,
- }
+ };
}
/**
@@ -440,7 +477,12 @@ export async function executeTransferAndCharityTransfer(wallet: Wallet, amount:
* @param {string} sourceAsset - The source asset to transfer.
* @param {string} targetAddress - The target address to transfer to.
*/
-export async function executeTransfer(wallet: Wallet, amount: number, sourceAsset: string, targetAddress: string) {
+export async function executeTransfer(
+ wallet: Wallet,
+ amount: number,
+ sourceAsset: string,
+ targetAddress: string
+) {
const assetIdLowercase = sourceAsset.toLowerCase();
const transferDetails = {
amount,
@@ -454,8 +496,8 @@ export async function executeTransfer(wallet: Wallet, amount: number, sourceAsse
transfer = await wallet.createTransfer(transferDetails);
elizaLogger.log("Transfer initiated:", transfer.toString());
await transfer.wait({
- intervalSeconds: 1,
- timeoutSeconds: 20,
+ intervalSeconds: 1,
+ timeoutSeconds: 20,
});
} catch (error) {
elizaLogger.error("Error executing transfer:", error);
@@ -465,20 +507,29 @@ export async function executeTransfer(wallet: Wallet, amount: number, sourceAsse
/**
* Gets the charity address based on the network.
- * For now we are giving to the following charity, but will make this configurable in the future
- * https://www.givedirectly.org/crypto/?_gl=1*va5e6k*_gcl_au*MTM1NDUzNTk5Mi4xNzMzMDczNjA3*_ga*OTIwMDMwNTMwLjE3MzMwNzM2MDg.*_ga_GV8XF9FJ16*MTczMzA3MzYwNy4xLjEuMTczMzA3MzYyMi40NS4wLjA.
* @param {string} network - The network to use.
+ * @param {boolean} isCharitable - Whether charity donations are enabled
+ * @throws {Error} If charity address for the network is not configured when charity is enabled
*/
-export function getCharityAddress(network: string): string | null {
- let charityAddress = null;
- if (network === "base") {
- charityAddress = "0x750EF1D7a0b4Ab1c97B7A623D7917CcEb5ea779C";
- } else if (network === "sol") {
- charityAddress = "pWvDXKu6CpbKKvKQkZvDA66hgsTB6X2AgFxksYogHLV";
- } else if (network === "eth") {
- charityAddress = "0x750EF1D7a0b4Ab1c97B7A623D7917CcEb5ea779C";
- } else {
+export function getCharityAddress(
+ network: string,
+ isCharitable: boolean = false
+): string | null {
+ // Check both environment variable and passed parameter
+ const isCharityEnabled =
+ process.env.IS_CHARITABLE === "true" && isCharitable;
+
+ if (!isCharityEnabled) {
return null;
}
+ const networkKey = `CHARITY_ADDRESS_${network.toUpperCase()}`;
+ const charityAddress = settings[networkKey];
+
+ if (!charityAddress) {
+ throw new Error(
+ `Charity address not configured for network ${network}. Please set ${networkKey} in your environment variables.`
+ );
+ }
+
return charityAddress;
}
diff --git a/packages/plugin-conflux/src/actions/bridgeTransfer.ts b/packages/plugin-conflux/src/actions/bridgeTransfer.ts
index 8b34817c1b9..e598df79ee5 100644
--- a/packages/plugin-conflux/src/actions/bridgeTransfer.ts
+++ b/packages/plugin-conflux/src/actions/bridgeTransfer.ts
@@ -6,7 +6,7 @@ import {
HandlerCallback,
} from "@ai16z/eliza";
import {
- generateObjectV2,
+ generateObject,
composeContext,
ModelClass,
Content,
@@ -117,7 +117,7 @@ export const bridgeTransfer: Action = {
template: confluxBridgeTransferTemplate,
});
- const content = await generateObjectV2({
+ const content = await generateObject({
runtime,
context,
modelClass: ModelClass.LARGE,
diff --git a/packages/plugin-conflux/src/actions/confiPump.ts b/packages/plugin-conflux/src/actions/confiPump.ts
index 7346c8b4c7d..ae0e9bb3ea0 100644
--- a/packages/plugin-conflux/src/actions/confiPump.ts
+++ b/packages/plugin-conflux/src/actions/confiPump.ts
@@ -5,7 +5,7 @@ import {
State,
HandlerCallback,
} from "@ai16z/eliza";
-import { generateObjectV2, composeContext, ModelClass } from "@ai16z/eliza";
+import { generateObject, composeContext, ModelClass } from "@ai16z/eliza";
import {
createPublicClient,
createWalletClient,
@@ -184,7 +184,7 @@ export const confiPump: Action = {
template: confluxTransferTemplate,
});
- const content = await generateObjectV2({
+ const content = await generateObject({
runtime,
context,
modelClass: ModelClass.LARGE,
diff --git a/packages/plugin-conflux/src/actions/transfer.ts b/packages/plugin-conflux/src/actions/transfer.ts
index 553d0fb595a..43e941e43f5 100644
--- a/packages/plugin-conflux/src/actions/transfer.ts
+++ b/packages/plugin-conflux/src/actions/transfer.ts
@@ -6,7 +6,7 @@ import {
HandlerCallback,
} from "@ai16z/eliza";
import {
- generateObjectV2,
+ generateObject,
composeContext,
ModelClass,
Content,
@@ -94,7 +94,7 @@ export const transfer: Action = {
template: confluxTransferTemplate,
});
- const content = await generateObjectV2({
+ const content = await generateObject({
runtime,
context,
modelClass: ModelClass.SMALL,
diff --git a/packages/plugin-echochambers/LICENSE b/packages/plugin-echochambers/LICENSE
new file mode 100644
index 00000000000..de6134690c1
--- /dev/null
+++ b/packages/plugin-echochambers/LICENSE
@@ -0,0 +1,9 @@
+Ethereal Cosmic License (ECL-777)
+
+Copyright (∞) 2024 SavageJay | https://x.com/savageapi
+
+By the powers vested in the astral planes and digital realms, permission is hereby granted, free of charge, to any seeker of knowledge obtaining an copy of this mystical software and its sacred documentation files (henceforth known as "The Digital Grimoire"), to manipulate the fabric of code without earthly restriction, including but not transcending beyond the rights to use, transmute, modify, publish, distribute, sublicense, and transfer energies (sell), and to permit other beings to whom The Digital Grimoire is bestowed, subject to the following metaphysical conditions:
+
+The above arcane copyright notice and this permission scroll shall be woven into all copies or substantial manifestations of The Digital Grimoire.
+
+THE DIGITAL GRIMOIRE IS PROVIDED "AS IS", BEYOND THE VEIL OF WARRANTIES, WHETHER MANIFEST OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE MYSTICAL WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR ASTRAL PURPOSE AND NON-VIOLATION OF THE COSMIC ORDER. IN NO EVENT SHALL THE KEEPERS OF THE CODE BE LIABLE FOR ANY CLAIMS, WHETHER IN THE PHYSICAL OR DIGITAL PLANES, DAMAGES OR OTHER DISTURBANCES IN THE FORCE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE DIGITAL GRIMOIRE OR ITS USE OR OTHER DEALINGS IN THE QUANTUM REALMS OF THE SOFTWARE.
\ No newline at end of file
diff --git a/packages/plugin-echochambers/README.md b/packages/plugin-echochambers/README.md
new file mode 100644
index 00000000000..12aa5f4f78f
--- /dev/null
+++ b/packages/plugin-echochambers/README.md
@@ -0,0 +1,66 @@
+# EchoChambers Plugin for ELIZA
+
+The EchoChambers plugin enables ELIZA to interact in chat rooms, providing conversational capabilities with dynamic interaction handling.
+
+## Features
+
+- Join and monitor chat rooms
+- Respond to messages based on context and relevance
+- Retry operations with exponential backoff
+- Manage connection and reconnection logic
+
+## Installation
+
+1. Install the plugin package:
+
+ @ai16z/plugin-echochambers
+ OR copy the plugin code into your eliza project node_modules directory. (node_modules\@ai16z)
+
+2. Import and register the plugin in your `character.ts` configuration:
+
+ ```typescript
+ import { Character, ModelProviderName, defaultCharacter } from "@ai16z/eliza";
+ import { echoChamberPlugin } from "@ai16z/plugin-echochambers";
+
+ export const character: Character = {
+ ...defaultCharacter,
+ name: "Eliza",
+ plugins: [echoChamberPlugin],
+ clients: [],
+ modelProvider: ModelProviderName.OPENAI,
+ settings: {
+ secrets: {},
+ voice: {},
+ model: "gpt-4o",
+ },
+ system: "Roleplay and generate interesting on behalf of Eliza.",
+ bio: [...],
+ lore: [...],
+ messageExamples: [...],
+ postExamples: [...],
+ adjectives: ["funny", "intelligent", "academic", "insightful", "unhinged", "insane", "technically specific"],
+ people: [],
+ topics: [...],
+ style: {...},
+ };
+ ```
+
+## Configuration
+
+Add the following environment variables to your `.env` file:
+
+```plaintext
+# EchoChambers Configuration
+ECHOCHAMBERS_API_URL="http://127.0.0.1:3333" # Replace with actual API URL
+ECHOCHAMBERS_API_KEY="testingkey0011" # Replace with actual API key
+ECHOCHAMBERS_USERNAME="eliza" # Optional: Custom username for the agent
+ECHOCHAMBERS_DEFAULT_ROOM="general" # Optional: Default room to join
+ECHOCHAMBERS_POLL_INTERVAL="60" # Optional: Polling interval in seconds
+ECHOCHAMBERS_MAX_MESSAGES="10" # Optional: Maximum number of messages to fetch
+```
+
+## Usage Instructions
+
+### Starting the Plugin
+
+To start using the EchoChambers plugin, ensure that your character configuration includes it as shown above. The plugin will handle interactions automatically based on the settings provided.
diff --git a/packages/plugin-echochambers/package.json b/packages/plugin-echochambers/package.json
new file mode 100644
index 00000000000..19723d0e590
--- /dev/null
+++ b/packages/plugin-echochambers/package.json
@@ -0,0 +1,15 @@
+{
+ "name": "@ai16z/plugin-echochambers",
+ "version": "0.1.5-alpha.3",
+ "main": "dist/index.js",
+ "type": "module",
+ "types": "dist/index.d.ts",
+ "dependencies": {
+ "@ai16z/eliza": "workspace:*",
+ "@ai16z/plugin-node": "workspace:*"
+ },
+ "scripts": {
+ "build": "tsup --format esm --dts",
+ "dev": "tsup --format esm --dts --watch"
+ }
+}
diff --git a/packages/plugin-echochambers/src/echoChamberClient.ts b/packages/plugin-echochambers/src/echoChamberClient.ts
new file mode 100644
index 00000000000..cf8caea2910
--- /dev/null
+++ b/packages/plugin-echochambers/src/echoChamberClient.ts
@@ -0,0 +1,192 @@
+import { elizaLogger, IAgentRuntime } from "@ai16z/eliza";
+import {
+ ChatMessage,
+ ChatRoom,
+ EchoChamberConfig,
+ ModelInfo,
+ ListRoomsResponse,
+ RoomHistoryResponse,
+ MessageResponse,
+} from "./types";
+
+const MAX_RETRIES = 3;
+
+const RETRY_DELAY = 5000;
+
+export class EchoChamberClient {
+ private runtime: IAgentRuntime;
+ private config: EchoChamberConfig;
+ private apiUrl: string;
+ private modelInfo: ModelInfo;
+ private pollInterval: NodeJS.Timeout | null = null;
+ private watchedRoom: string | null = null;
+
+ constructor(runtime: IAgentRuntime, config: EchoChamberConfig) {
+ this.runtime = runtime;
+ this.config = config;
+ this.apiUrl = `${config.apiUrl}/api/rooms`;
+ this.modelInfo = {
+ username: config.username || `agent-${runtime.agentId}`,
+ model: config.model || runtime.modelProvider,
+ };
+ }
+
+ public getUsername(): string {
+ return this.modelInfo.username;
+ }
+
+ public getModelInfo(): ModelInfo {
+ return { ...this.modelInfo };
+ }
+
+ public getConfig(): EchoChamberConfig {
+ return { ...this.config };
+ }
+
+ private getAuthHeaders(): { [key: string]: string } {
+ return {
+ "Content-Type": "application/json",
+ "x-api-key": this.config.apiKey,
+ };
+ }
+
+ public async setWatchedRoom(roomId: string): Promise {
+ try {
+ // Verify room exists
+ const rooms = await this.listRooms();
+ const room = rooms.find((r) => r.id === roomId);
+
+ if (!room) {
+ throw new Error(`Room ${roomId} not found`);
+ }
+
+ // Set new watched room
+ this.watchedRoom = roomId;
+
+ elizaLogger.success(`Now watching room: ${room.name}`);
+ } catch (error) {
+ elizaLogger.error("Error setting watched room:", error);
+ throw error;
+ }
+ }
+
+ public getWatchedRoom(): string | null {
+ return this.watchedRoom;
+ }
+
+ private async retryOperation(
+ operation: () => Promise,
+ retries: number = MAX_RETRIES
+ ): Promise {
+ for (let i = 0; i < retries; i++) {
+ try {
+ return await operation();
+ } catch (error) {
+ if (i === retries - 1) throw error;
+ const delay = RETRY_DELAY * Math.pow(2, i);
+ elizaLogger.warn(`Retrying operation in ${delay}ms...`);
+ await new Promise((resolve) => setTimeout(resolve, delay));
+ }
+ }
+ throw new Error("Max retries exceeded");
+ }
+
+ public async start(): Promise {
+ elizaLogger.log("🚀 Starting EchoChamber client...");
+ try {
+ // Verify connection by listing rooms
+ await this.retryOperation(() => this.listRooms());
+ elizaLogger.success(
+ `✅ EchoChamber client successfully started for ${this.modelInfo.username}`
+ );
+
+ // Join default room if specified and no specific room is being watched
+ if (this.config.defaultRoom && !this.watchedRoom) {
+ await this.setWatchedRoom(this.config.defaultRoom);
+ }
+ } catch (error) {
+ elizaLogger.error("❌ Failed to start EchoChamber client:", error);
+ throw error;
+ }
+ }
+
+ public async stop(): Promise {
+ if (this.pollInterval) {
+ clearInterval(this.pollInterval);
+ this.pollInterval = null;
+ }
+
+ // Leave watched room if any
+ if (this.watchedRoom) {
+ try {
+ this.watchedRoom = null;
+ } catch (error) {
+ elizaLogger.error(
+ `Error leaving room ${this.watchedRoom}:`,
+ error
+ );
+ }
+ }
+
+ elizaLogger.log("Stopping EchoChamber client...");
+ }
+
+ public async listRooms(tags?: string[]): Promise {
+ try {
+ const url = new URL(this.apiUrl);
+ if (tags?.length) {
+ url.searchParams.append("tags", tags.join(","));
+ }
+
+ const response = await fetch(url.toString());
+ if (!response.ok) {
+ throw new Error(`Failed to list rooms: ${response.statusText}`);
+ }
+
+ const data = (await response.json()) as ListRoomsResponse;
+ return data.rooms;
+ } catch (error) {
+ elizaLogger.error("Error listing rooms:", error);
+ throw error;
+ }
+ }
+
+ public async getRoomHistory(roomId: string): Promise {
+ return this.retryOperation(async () => {
+ const response = await fetch(`${this.apiUrl}/${roomId}/history`);
+ if (!response.ok) {
+ throw new Error(
+ `Failed to get room history: ${response.statusText}`
+ );
+ }
+
+ const data = (await response.json()) as RoomHistoryResponse;
+ return data.messages;
+ });
+ }
+
+ public async sendMessage(
+ roomId: string,
+ content: string
+ ): Promise {
+ return this.retryOperation(async () => {
+ const response = await fetch(`${this.apiUrl}/${roomId}/message`, {
+ method: "POST",
+ headers: this.getAuthHeaders(),
+ body: JSON.stringify({
+ content,
+ sender: this.modelInfo,
+ }),
+ });
+
+ if (!response.ok) {
+ throw new Error(
+ `Failed to send message: ${response.statusText}`
+ );
+ }
+
+ const data = (await response.json()) as MessageResponse;
+ return data.message;
+ });
+ }
+}
diff --git a/packages/plugin-echochambers/src/environment.ts b/packages/plugin-echochambers/src/environment.ts
new file mode 100644
index 00000000000..6f444e10611
--- /dev/null
+++ b/packages/plugin-echochambers/src/environment.ts
@@ -0,0 +1,55 @@
+import { IAgentRuntime, elizaLogger } from "@ai16z/eliza";
+
+export async function validateEchoChamberConfig(
+ runtime: IAgentRuntime
+): Promise {
+ const apiUrl = runtime.getSetting("ECHOCHAMBERS_API_URL");
+ const apiKey = runtime.getSetting("ECHOCHAMBERS_API_KEY");
+
+ if (!apiUrl) {
+ elizaLogger.error(
+ "ECHOCHAMBERS_API_URL is required. Please set it in your environment variables."
+ );
+ throw new Error("ECHOCHAMBERS_API_URL is required");
+ }
+
+ if (!apiKey) {
+ elizaLogger.error(
+ "ECHOCHAMBERS_API_KEY is required. Please set it in your environment variables."
+ );
+ throw new Error("ECHOCHAMBERS_API_KEY is required");
+ }
+
+ // Validate API URL format
+ try {
+ new URL(apiUrl);
+ } catch (error) {
+ elizaLogger.error(
+ `Invalid ECHOCHAMBERS_API_URL format: ${apiUrl}. Please provide a valid URL.`
+ );
+ throw new Error("Invalid ECHOCHAMBERS_API_URL format");
+ }
+
+ // Optional settings with defaults
+ const username =
+ runtime.getSetting("ECHOCHAMBERS_USERNAME") ||
+ `agent-${runtime.agentId}`;
+ const defaultRoom =
+ runtime.getSetting("ECHOCHAMBERS_DEFAULT_ROOM") || "general";
+ const pollInterval = Number(
+ runtime.getSetting("ECHOCHAMBERS_POLL_INTERVAL") || 120
+ );
+
+ if (isNaN(pollInterval) || pollInterval < 1) {
+ elizaLogger.error(
+ "ECHOCHAMBERS_POLL_INTERVAL must be a positive number in seconds"
+ );
+ throw new Error("Invalid ECHOCHAMBERS_POLL_INTERVAL");
+ }
+
+ elizaLogger.log("EchoChambers configuration validated successfully");
+ elizaLogger.log(`API URL: ${apiUrl}`);
+ elizaLogger.log(`Username: ${username}`);
+ elizaLogger.log(`Default Room: ${defaultRoom}`);
+ elizaLogger.log(`Poll Interval: ${pollInterval}s`);
+}
diff --git a/packages/plugin-echochambers/src/index.ts b/packages/plugin-echochambers/src/index.ts
new file mode 100644
index 00000000000..42c91decc3b
--- /dev/null
+++ b/packages/plugin-echochambers/src/index.ts
@@ -0,0 +1,93 @@
+import { elizaLogger, Client, IAgentRuntime, Plugin } from "@ai16z/eliza";
+import { EchoChamberClient } from "./echoChamberClient";
+import { InteractionClient } from "./interactions";
+import { EchoChamberConfig } from "./types";
+import { validateEchoChamberConfig } from "./environment";
+
+export const EchoChamberClientInterface: Client = {
+ async start(runtime: IAgentRuntime) {
+ try {
+ // Validate configuration before starting
+ await validateEchoChamberConfig(runtime);
+
+ const apiUrl = runtime.getSetting("ECHOCHAMBERS_API_URL");
+ const apiKey = runtime.getSetting("ECHOCHAMBERS_API_KEY");
+
+ if (!apiKey || !apiUrl) {
+ throw new Error(
+ "ECHOCHAMBERS_API_KEY/ECHOCHAMBERS_API_URL is required"
+ );
+ }
+
+ const config: EchoChamberConfig = {
+ apiUrl,
+ apiKey,
+ username:
+ runtime.getSetting("ECHOCHAMBERS_USERNAME") ||
+ `agent-${runtime.agentId}`,
+ model: runtime.modelProvider,
+ defaultRoom:
+ runtime.getSetting("ECHOCHAMBERS_DEFAULT_ROOM") ||
+ "general",
+ };
+
+ elizaLogger.log("Starting EchoChambers client...");
+
+ // Initialize the API client
+ const client = new EchoChamberClient(runtime, config);
+ await client.start();
+
+ // Initialize the interaction handler
+ const interactionClient = new InteractionClient(client, runtime);
+ await interactionClient.start();
+
+ elizaLogger.success(
+ `✅ EchoChambers client successfully started for character ${runtime.character.name}`
+ );
+
+ return { client, interactionClient };
+ } catch (error) {
+ elizaLogger.error("Failed to start EchoChambers client:", error);
+ throw error;
+ }
+ },
+
+ async stop(runtime: IAgentRuntime) {
+ try {
+ elizaLogger.warn("Stopping EchoChambers client...");
+
+ // Get client instances if they exist
+ const clients = (runtime as any).clients?.filter(
+ (c: any) =>
+ c instanceof EchoChamberClient ||
+ c instanceof InteractionClient
+ );
+
+ for (const client of clients) {
+ await client.stop();
+ }
+
+ elizaLogger.success("EchoChambers client stopped successfully");
+ } catch (error) {
+ elizaLogger.error("Error stopping EchoChambers client:", error);
+ throw error;
+ }
+ },
+};
+
+export const echoChamberPlugin: Plugin = {
+ name: "echochambers",
+ description:
+ "Plugin for interacting with EchoChambers API to enable multi-agent communication",
+ actions: [], // No custom actions needed - core functionality handled by client
+ evaluators: [], // No custom evaluators needed
+ providers: [], // No custom providers needed
+ clients: [EchoChamberClientInterface],
+};
+
+export default echoChamberPlugin;
+
+// Export types and classes
+export * from "./types";
+export { EchoChamberClient } from "./echoChamberClient";
+export { InteractionClient } from "./interactions";
diff --git a/packages/plugin-echochambers/src/interactions.ts b/packages/plugin-echochambers/src/interactions.ts
new file mode 100644
index 00000000000..be824e50ddd
--- /dev/null
+++ b/packages/plugin-echochambers/src/interactions.ts
@@ -0,0 +1,428 @@
+import {
+ composeContext,
+ generateMessageResponse,
+ generateShouldRespond,
+ messageCompletionFooter,
+ shouldRespondFooter,
+ Content,
+ HandlerCallback,
+ IAgentRuntime,
+ Memory,
+ ModelClass,
+ State,
+ stringToUuid,
+ elizaLogger,
+ getEmbeddingZeroVector,
+} from "@ai16z/eliza";
+import { EchoChamberClient } from "./echoChamberClient";
+import { ChatMessage } from "./types";
+
+function createMessageTemplate(currentRoom: string, roomTopic: string) {
+ return (
+ `
+# About {{agentName}}:
+{{bio}}
+{{lore}}
+{{knowledge}}
+
+Current Room: ${currentRoom}
+Room Topic: ${roomTopic}
+
+{{messageDirections}}
+
+Recent conversation history:
+{{recentMessages}}
+
+Thread Context:
+{{formattedConversation}}
+
+# Task: Generate a response in the voice and style of {{agentName}} while:
+1. Staying relevant to the room's topic
+2. Maintaining conversation context
+3. Being helpful but not overly talkative
+4. Responding naturally to direct questions or mentions
+5. Contributing meaningfully to ongoing discussions
+
+Remember:
+- Keep responses concise and focused
+- Stay on topic for the current room
+- Don't repeat information already shared
+- Be natural and conversational
+` + messageCompletionFooter
+ );
+}
+
+function createShouldRespondTemplate(currentRoom: string, roomTopic: string) {
+ return (
+ `
+# About {{agentName}}:
+{{bio}}
+{{knowledge}}
+
+Current Room: ${currentRoom}
+Room Topic: ${roomTopic}
+
+Response options are [RESPOND], [IGNORE] and [STOP].
+
+{{agentName}} should:
+- RESPOND when:
+ * Directly mentioned or asked a question
+ * Can contribute relevant expertise to the discussion
+ * Topic aligns with their knowledge and background
+ * Conversation is active and engaging
+
+- IGNORE when:
+ * Message is not relevant to their expertise
+ * Already responded recently without new information to add
+ * Conversation has moved to a different topic
+ * Message is too short or lacks substance
+ * Other participants are handling the discussion well
+
+- STOP when:
+ * Asked to stop participating
+ * Conversation has concluded
+ * Discussion has completely diverged from their expertise
+ * Room topic has changed significantly
+
+Recent messages:
+{{recentMessages}}
+
+Thread Context:
+{{formattedConversation}}
+
+# Task: Choose whether {{agentName}} should respond to the last message.
+Consider:
+1. Message relevance to {{agentName}}'s expertise
+2. Current conversation context
+3. Time since last response
+4. Value of potential contribution
+` + shouldRespondFooter
+ );
+}
+
+export class InteractionClient {
+ private client: EchoChamberClient;
+ private runtime: IAgentRuntime;
+ private lastCheckedTimestamps: Map = new Map();
+ private lastResponseTimes: Map = new Map();
+ private messageThreads: Map = new Map();
+ private messageHistory: Map<
+ string,
+ { message: ChatMessage; response: ChatMessage | null }[]
+ > = new Map();
+ private pollInterval: NodeJS.Timeout | null = null;
+
+ constructor(client: EchoChamberClient, runtime: IAgentRuntime) {
+ this.client = client;
+ this.runtime = runtime;
+ }
+
+ async start() {
+ const pollInterval = Number(
+ this.runtime.getSetting("ECHOCHAMBERS_POLL_INTERVAL") || 60
+ );
+
+ const handleInteractionsLoop = () => {
+ this.handleInteractions();
+ this.pollInterval = setTimeout(
+ handleInteractionsLoop,
+ pollInterval * 1000
+ );
+ };
+
+ handleInteractionsLoop();
+ }
+
+ async stop() {
+ if (this.pollInterval) {
+ clearTimeout(this.pollInterval);
+ this.pollInterval = null;
+ }
+ }
+
+ private async buildMessageThread(
+ message: ChatMessage,
+ messages: ChatMessage[]
+ ): Promise {
+ const thread: ChatMessage[] = [];
+ const maxThreadLength = Number(
+ this.runtime.getSetting("ECHOCHAMBERS_MAX_MESSAGES") || 10
+ );
+
+ // Start with the current message
+ thread.push(message);
+
+ // Get recent messages in the same room, ordered by timestamp
+ const roomMessages = messages
+ .filter((msg) => msg.roomId === message.roomId)
+ .sort(
+ (a, b) =>
+ new Date(b.timestamp).getTime() -
+ new Date(a.timestamp).getTime()
+ );
+
+ // Add recent messages to provide context
+ for (const msg of roomMessages) {
+ if (thread.length >= maxThreadLength) break;
+ if (msg.id !== message.id) {
+ thread.unshift(msg);
+ }
+ }
+
+ return thread;
+ }
+
+ private shouldProcessMessage(
+ message: ChatMessage,
+ room: { topic: string }
+ ): boolean {
+ const modelInfo = this.client.getModelInfo();
+
+ // Don't process own messages
+ if (message.sender.username === modelInfo.username) {
+ return false;
+ }
+
+ // Check if we've processed this message before
+ const lastChecked =
+ this.lastCheckedTimestamps.get(message.roomId) || "0";
+ if (message.timestamp <= lastChecked) {
+ return false;
+ }
+
+ // Check rate limiting for responses
+ const lastResponseTime =
+ this.lastResponseTimes.get(message.roomId) || 0;
+ const minTimeBetweenResponses = 30000; // 30 seconds
+ if (Date.now() - lastResponseTime < minTimeBetweenResponses) {
+ return false;
+ }
+
+ // Check if message mentions the agent
+ const isMentioned = message.content
+ .toLowerCase()
+ .includes(`${modelInfo.username.toLowerCase()}`);
+
+ // Check if message is relevant to room topic
+ const isRelevantToTopic =
+ room.topic &&
+ message.content.toLowerCase().includes(room.topic.toLowerCase());
+
+ // Always process if mentioned, otherwise check relevance
+ return isMentioned || isRelevantToTopic;
+ }
+
+ private async handleInteractions() {
+ elizaLogger.log("Checking EchoChambers interactions");
+
+ try {
+ const defaultRoom = this.runtime.getSetting(
+ "ECHOCHAMBERS_DEFAULT_ROOM"
+ );
+ const rooms = await this.client.listRooms();
+
+ for (const room of rooms) {
+ // Only process messages from the default room if specified
+ if (defaultRoom && room.id !== defaultRoom) {
+ continue;
+ }
+
+ const messages = await this.client.getRoomHistory(room.id);
+ this.messageThreads.set(room.id, messages);
+
+ // Get only the most recent message that we should process
+ const latestMessages = messages
+ .filter((msg) => !this.shouldProcessMessage(msg, room)) // Fixed: Now filtering out messages we shouldn't process
+ .sort(
+ (a, b) =>
+ new Date(b.timestamp).getTime() -
+ new Date(a.timestamp).getTime()
+ );
+
+ if (latestMessages.length > 0) {
+ const latestMessage = latestMessages[0];
+ await this.handleMessage(latestMessage, room.topic);
+
+ // Update history
+ const roomHistory = this.messageHistory.get(room.id) || [];
+ roomHistory.push({
+ message: latestMessage,
+ response: null, // Will be updated when we respond
+ });
+ this.messageHistory.set(room.id, roomHistory);
+
+ // Update last checked timestamp
+ if (
+ latestMessage.timestamp >
+ (this.lastCheckedTimestamps.get(room.id) || "0")
+ ) {
+ this.lastCheckedTimestamps.set(
+ room.id,
+ latestMessage.timestamp
+ );
+ }
+ }
+ }
+
+ elizaLogger.log("Finished checking EchoChambers interactions");
+ } catch (error) {
+ elizaLogger.error(
+ "Error handling EchoChambers interactions:",
+ error
+ );
+ }
+ }
+
+ private async handleMessage(message: ChatMessage, roomTopic: string) {
+ try {
+ const roomId = stringToUuid(message.roomId);
+ const userId = stringToUuid(message.sender.username);
+
+ // Ensure connection exists
+ await this.runtime.ensureConnection(
+ userId,
+ roomId,
+ message.sender.username,
+ message.sender.username,
+ "echochambers"
+ );
+
+ // Build message thread for context
+ const thread = await this.buildMessageThread(
+ message,
+ this.messageThreads.get(message.roomId) || []
+ );
+
+ // Create memory object
+ const memory: Memory = {
+ id: stringToUuid(message.id),
+ userId,
+ agentId: this.runtime.agentId,
+ roomId,
+ content: {
+ text: message.content,
+ source: "echochambers",
+ thread: thread.map((msg) => ({
+ text: msg.content,
+ sender: msg.sender.username,
+ timestamp: msg.timestamp,
+ })),
+ },
+ createdAt: new Date(message.timestamp).getTime(),
+ embedding: getEmbeddingZeroVector(),
+ };
+
+ // Check if we've already processed this message
+ const existing = await this.runtime.messageManager.getMemoryById(
+ memory.id
+ );
+ if (existing) {
+ elizaLogger.log(
+ `Already processed message ${message.id}, skipping`
+ );
+ return;
+ }
+
+ // Save the message to memory
+ await this.runtime.messageManager.createMemory(memory);
+
+ // Compose state with thread context
+ let state = await this.runtime.composeState(memory);
+ state = await this.runtime.updateRecentMessageState(state);
+
+ // Decide whether to respond
+ const shouldRespondContext = composeContext({
+ state,
+ template:
+ this.runtime.character.templates?.shouldRespondTemplate ||
+ createShouldRespondTemplate(message.roomId, roomTopic),
+ });
+
+ const shouldRespond = await generateShouldRespond({
+ runtime: this.runtime,
+ context: shouldRespondContext,
+ modelClass: ModelClass.SMALL,
+ });
+
+ if (shouldRespond !== "RESPOND") {
+ elizaLogger.log(
+ `Not responding to message ${message.id}: ${shouldRespond}`
+ );
+ return;
+ }
+
+ // Generate response
+ const responseContext = composeContext({
+ state,
+ template:
+ this.runtime.character.templates?.messageHandlerTemplate ||
+ createMessageTemplate(message.roomId, roomTopic),
+ });
+
+ const response = await generateMessageResponse({
+ runtime: this.runtime,
+ context: responseContext,
+ modelClass: ModelClass.SMALL,
+ });
+
+ if (!response || !response.text) {
+ elizaLogger.log("No response generated");
+ return;
+ }
+
+ // Send response
+ const callback: HandlerCallback = async (content: Content) => {
+ const sentMessage = await this.client.sendMessage(
+ message.roomId,
+ content.text
+ );
+
+ // Update last response time
+ this.lastResponseTimes.set(message.roomId, Date.now());
+
+ // Update history with our response
+ const roomHistory =
+ this.messageHistory.get(message.roomId) || [];
+ const lastEntry = roomHistory[roomHistory.length - 1];
+ if (lastEntry && lastEntry.message.id === message.id) {
+ lastEntry.response = sentMessage;
+ }
+
+ const responseMemory: Memory = {
+ id: stringToUuid(sentMessage.id),
+ userId: this.runtime.agentId,
+ agentId: this.runtime.agentId,
+ roomId,
+ content: {
+ text: sentMessage.content,
+ source: "echochambers",
+ action: content.action,
+ thread: thread.map((msg) => ({
+ text: msg.content,
+ sender: msg.sender.username,
+ timestamp: msg.timestamp,
+ })),
+ },
+ createdAt: new Date(sentMessage.timestamp).getTime(),
+ embedding: getEmbeddingZeroVector(),
+ };
+
+ await this.runtime.messageManager.createMemory(responseMemory);
+ return [responseMemory];
+ };
+
+ // Send the response and process any resulting actions
+ const responseMessages = await callback(response);
+ state = await this.runtime.updateRecentMessageState(state);
+ await this.runtime.processActions(
+ memory,
+ responseMessages,
+ state,
+ callback
+ );
+ await this.runtime.evaluate(memory, state, true);
+ } catch (error) {
+ elizaLogger.error("Error handling message:", error);
+ }
+ }
+}
diff --git a/packages/plugin-echochambers/src/types.ts b/packages/plugin-echochambers/src/types.ts
new file mode 100644
index 00000000000..887758813eb
--- /dev/null
+++ b/packages/plugin-echochambers/src/types.ts
@@ -0,0 +1,68 @@
+export interface ModelInfo {
+ username: string; // Unique username for the model/agent
+ model: string; // Type/name of the model being used
+}
+
+export interface ChatMessage {
+ id: string; // Unique message identifier
+ content: string; // Message content/text
+ sender: ModelInfo; // Information about who sent the message
+ timestamp: string; // ISO timestamp of when message was sent
+ roomId: string; // ID of the room this message belongs to
+}
+
+export interface ChatRoom {
+ id: string; // Unique room identifier
+ name: string; // Display name of the room
+ topic: string; // Room's current topic/description
+ tags: string[]; // Tags associated with the room for categorization
+ participants: ModelInfo[]; // List of current room participants
+ createdAt: string; // ISO timestamp of room creation
+ messageCount: number; // Total number of messages in the room
+}
+
+export interface EchoChamberConfig {
+ apiUrl: string; // Base URL for the EchoChambers API
+ apiKey: string; // Required API key for authenticated endpoints
+ defaultRoom?: string; // Optional default room to join on startup
+ username?: string; // Optional custom username (defaults to agent-{agentId})
+ model?: string; // Optional model name (defaults to runtime.modelProvider)
+}
+
+export interface ListRoomsResponse {
+ rooms: ChatRoom[];
+}
+
+export interface RoomHistoryResponse {
+ messages: ChatMessage[];
+}
+
+export interface MessageResponse {
+ message: ChatMessage;
+}
+
+export interface CreateRoomResponse {
+ room: ChatRoom;
+}
+
+export interface ClearMessagesResponse {
+ success: boolean;
+ message: string;
+}
+
+export enum RoomEvent {
+ MESSAGE_CREATED = "message_created",
+ ROOM_CREATED = "room_created",
+ ROOM_UPDATED = "room_updated",
+ ROOM_JOINED = "room_joined",
+ ROOM_LEFT = "room_left",
+}
+
+export interface MessageTransformer {
+ transformIncoming(content: string): Promise;
+ transformOutgoing?(content: string): Promise;
+}
+
+export interface ContentModerator {
+ validateContent(content: string): Promise;
+}
diff --git a/packages/plugin-echochambers/tsconfig.json b/packages/plugin-echochambers/tsconfig.json
new file mode 100644
index 00000000000..b98954f213e
--- /dev/null
+++ b/packages/plugin-echochambers/tsconfig.json
@@ -0,0 +1,10 @@
+{
+ "extends": "../core/tsconfig.json",
+ "compilerOptions": {
+ "outDir": "dist",
+ "rootDir": "./src"
+ },
+ "include": [
+ "src"
+ ]
+}
\ No newline at end of file
diff --git a/packages/plugin-echochambers/tsup.config.ts b/packages/plugin-echochambers/tsup.config.ts
new file mode 100644
index 00000000000..6d705138fb1
--- /dev/null
+++ b/packages/plugin-echochambers/tsup.config.ts
@@ -0,0 +1,19 @@
+import { defineConfig } from "tsup";
+
+export default defineConfig({
+ entry: ["src/index.ts"],
+ outDir: "dist",
+ sourcemap: true,
+ clean: true,
+ format: ["esm"], // Ensure you're targeting CommonJS
+ external: [
+ "dotenv", // Externalize dotenv to prevent bundling
+ "fs", // Externalize fs to use Node.js built-in module
+ "path", // Externalize other built-ins if necessary
+ "@reflink/reflink",
+ "@node-llama-cpp",
+ "https",
+ "http",
+ "agentkeepalive",
+ ],
+});
diff --git a/packages/plugin-evm/README.md b/packages/plugin-evm/README.md
new file mode 100644
index 00000000000..e09e6d6775f
--- /dev/null
+++ b/packages/plugin-evm/README.md
@@ -0,0 +1,93 @@
+# `@ai16z/plugin-evm`
+
+This plugin provides actions and providers for interacting with EVM-compatible chains.
+
+---
+
+## Configuration
+
+### Default Setup
+
+By default, **Ethereum mainnet** is enabled. To use it, simply add your private key to the `.env` file:
+
+```env
+EVM_PRIVATE_KEY=your-private-key-here
+```
+
+### Adding Support for Other Chains
+
+To enable support for additional chains, add them to the character config like this:
+
+```json
+"settings": {
+ "chains": {
+ "evm": [
+ "base", "arbitrum", "iotex"
+ ]
+ }
+}
+```
+
+Note: The chain names must match those in the viem/chains.
+
+### Custom RPC URLs
+
+By default, the RPC URL is inferred from the `viem/chains` config. To use a custom RPC URL for a specific chain, add the following to your `.env` file:
+
+```env
+ETHEREUM_PROVIDER_=https://your-custom-rpc-url
+```
+
+**Example usage:**
+
+```env
+ETHEREUM_PROVIDER_IOTEX=https://iotex-network.rpc.thirdweb.com
+```
+
+#### Custom RPC for Ethereum Mainnet
+
+To set a custom RPC URL for Ethereum mainnet, use:
+
+```env
+EVM_PROVIDER_URL=https://your-custom-mainnet-rpc-url
+```
+
+## Provider
+
+The **Wallet Provider** initializes with the **first chain in the list** as the default (or Ethereum mainnet if none are added). It:
+
+- Provides the **context** of the currently connected address and its balance.
+- Creates **Public** and **Wallet clients** to interact with the supported chains.
+- Allows adding chains dynamically at runtime.
+
+---
+
+## Actions
+
+### Transfer
+
+Transfer tokens from one address to another on any EVM-compatible chain. Just specify the:
+
+- **Amount**
+- **Chain**
+- **Recipient Address**
+
+**Example usage:**
+
+```bash
+Transfer 1 ETH to 0xRecipient on arbitrum.
+```
+
+---
+
+## Contribution
+
+The plugin contains tests. Whether you're using **TDD** or not, please make sure to run the tests before submitting a PR.
+
+### Running Tests
+
+Navigate to the `plugin-evm` directory and run:
+
+```bash
+pnpm test
+```
diff --git a/packages/plugin-evm/package.json b/packages/plugin-evm/package.json
index b24d1045c71..a13be1be776 100644
--- a/packages/plugin-evm/package.json
+++ b/packages/plugin-evm/package.json
@@ -6,7 +6,6 @@
"types": "dist/index.d.ts",
"dependencies": {
"@ai16z/eliza": "workspace:*",
- "@ai16z/plugin-trustdb": "workspace:*",
"@lifi/data-types": "5.15.5",
"@lifi/sdk": "3.4.1",
"@lifi/types": "16.3.0",
diff --git a/packages/plugin-evm/src/actions/transfer.ts b/packages/plugin-evm/src/actions/transfer.ts
index 5c3cb71957b..bf1f57bfabe 100644
--- a/packages/plugin-evm/src/actions/transfer.ts
+++ b/packages/plugin-evm/src/actions/transfer.ts
@@ -1,7 +1,7 @@
import { ByteArray, formatEther, parseEther, type Hex } from "viem";
import {
composeContext,
- generateObjectDEPRECATED,
+ generateObjectDeprecated,
HandlerCallback,
ModelClass,
type IAgentRuntime,
@@ -76,7 +76,7 @@ const buildTransferDetails = async (
chains.toString()
);
- const transferDetails = (await generateObjectDEPRECATED({
+ const transferDetails = (await generateObjectDeprecated({
runtime,
context: contextWithChains,
modelClass: ModelClass.SMALL,
diff --git a/packages/plugin-flow/package.json b/packages/plugin-flow/package.json
index ae0fd2976b6..ca2ea7cc71a 100644
--- a/packages/plugin-flow/package.json
+++ b/packages/plugin-flow/package.json
@@ -6,7 +6,6 @@
"types": "dist/index.d.ts",
"dependencies": {
"@ai16z/eliza": "workspace:*",
- "@ai16z/plugin-trustdb": "workspace:*",
"@onflow/config": "1.5.1",
"@onflow/fcl": "1.13.1",
"@onflow/typedefs": "1.4.0",
@@ -28,7 +27,7 @@
"lines": "find . \\( -name '*.cdc' -o -name '*.ts' \\) -not -path '*/node_modules/*' -not -path '*/tests/*' -not -path '*/deps/*' -not -path '*/dist/*' -not -path '*/imports*' | xargs wc -l",
"build": "tsup --format esm --dts",
"dev": "tsup --format esm --dts --watch",
- "lint": "eslint . --fix",
+ "lint": "eslint --fix --cache .",
"test": "vitest run"
},
"peerDependencies": {
diff --git a/packages/plugin-goat/package.json b/packages/plugin-goat/package.json
index 3581f8343b9..56931db9c6e 100644
--- a/packages/plugin-goat/package.json
+++ b/packages/plugin-goat/package.json
@@ -9,6 +9,7 @@
"@goat-sdk/core": "0.3.8",
"@goat-sdk/plugin-erc20": "0.1.7",
"@goat-sdk/wallet-viem": "0.1.3",
+ "@goat-sdk/plugin-coingecko": "0.1.4",
"tsup": "8.3.5",
"viem": "2.21.53"
},
diff --git a/packages/plugin-goat/src/actions.ts b/packages/plugin-goat/src/actions.ts
index df4f1c906bb..9b767ab154a 100644
--- a/packages/plugin-goat/src/actions.ts
+++ b/packages/plugin-goat/src/actions.ts
@@ -14,7 +14,7 @@ import {
ModelClass,
type State,
composeContext,
- generateObjectV2,
+ generateObject,
} from "@ai16z/eliza";
type GetOnChainActionsParams = {
@@ -125,7 +125,7 @@ async function generateParameters(
context: string,
tool: Tool
): Promise {
- const { object } = await generateObjectV2({
+ const { object } = await generateObject({
runtime,
context,
modelClass: ModelClass.LARGE,
diff --git a/packages/plugin-icp/src/actions/createToken.ts b/packages/plugin-icp/src/actions/createToken.ts
index b33c0f302f5..726c2abffae 100644
--- a/packages/plugin-icp/src/actions/createToken.ts
+++ b/packages/plugin-icp/src/actions/createToken.ts
@@ -2,7 +2,7 @@ import {
composeContext,
generateImage,
generateText,
- generateObjectDEPRECATED,
+ generateObjectDeprecated,
} from "@ai16z/eliza";
import {
ActionExample,
@@ -148,7 +148,7 @@ export const executeCreateToken: Action = {
template: createTokenTemplate,
});
- const response = await generateObjectDEPRECATED({
+ const response = await generateObjectDeprecated({
runtime,
context: createTokenContext,
modelClass: ModelClass.LARGE,
diff --git a/packages/plugin-image-generation/package.json b/packages/plugin-image-generation/package.json
index 950df7ead51..36615554a6b 100644
--- a/packages/plugin-image-generation/package.json
+++ b/packages/plugin-image-generation/package.json
@@ -11,7 +11,7 @@
"scripts": {
"build": "tsup --format esm --dts",
"dev": "tsup --format esm --dts --watch",
- "lint": "eslint . --fix"
+ "lint": "eslint --fix --cache ."
},
"peerDependencies": {
"whatwg-url": "7.1.0"
diff --git a/packages/plugin-image-generation/src/environment.ts b/packages/plugin-image-generation/src/environment.ts
index 3426b4bf346..b5189fe0972 100644
--- a/packages/plugin-image-generation/src/environment.ts
+++ b/packages/plugin-image-generation/src/environment.ts
@@ -8,6 +8,7 @@ export const imageGenEnvSchema = z
HEURIST_API_KEY: z.string().optional(),
FAL_API_KEY: z.string().optional(),
OPENAI_API_KEY: z.string().optional(),
+ VENICE_API_KEY: z.string().optional(),
})
.refine(
(data) => {
@@ -16,12 +17,13 @@ export const imageGenEnvSchema = z
data.TOGETHER_API_KEY ||
data.HEURIST_API_KEY ||
data.FAL_API_KEY ||
- data.OPENAI_API_KEY
+ data.OPENAI_API_KEY ||
+ data.VENICE_API_KEY
);
},
{
message:
- "At least one of ANTHROPIC_API_KEY, TOGETHER_API_KEY, HEURIST_API_KEY, FAL_API_KEY or OPENAI_API_KEY is required",
+ "At least one of ANTHROPIC_API_KEY, TOGETHER_API_KEY, HEURIST_API_KEY, FAL_API_KEY, OPENAI_API_KEY or VENICE_API_KEY is required",
}
);
@@ -46,6 +48,9 @@ export async function validateImageGenConfig(
OPENAI_API_KEY:
runtime.getSetting("OPENAI_API_KEY") ||
process.env.OPENAI_API_KEY,
+ VENICE_API_KEY:
+ runtime.getSetting("VENICE_API_KEY") ||
+ process.env.VENICE_API_KEY,
};
return imageGenEnvSchema.parse(config);
diff --git a/packages/plugin-image-generation/src/index.ts b/packages/plugin-image-generation/src/index.ts
index 4a9a702334a..fd7f8b52467 100644
--- a/packages/plugin-image-generation/src/index.ts
+++ b/packages/plugin-image-generation/src/index.ts
@@ -84,13 +84,15 @@ const imageGeneration: Action = {
const heuristApiKeyOk = !!runtime.getSetting("HEURIST_API_KEY");
const falApiKeyOk = !!runtime.getSetting("FAL_API_KEY");
const openAiApiKeyOk = !!runtime.getSetting("OPENAI_API_KEY");
+ const veniceApiKeyOk = !!runtime.getSetting("VENICE_API_KEY");
return (
anthropicApiKeyOk ||
togetherApiKeyOk ||
heuristApiKeyOk ||
falApiKeyOk ||
- openAiApiKeyOk
+ openAiApiKeyOk ||
+ veniceApiKeyOk
);
},
handler: async (
@@ -205,6 +207,7 @@ const imageGeneration: Action = {
source: "imageGeneration",
description: "...", //caption.title,
text: "...", //caption.description,
+ contentType: "image",
},
],
},
diff --git a/packages/plugin-multiversx/.npmignore b/packages/plugin-multiversx/.npmignore
new file mode 100644
index 00000000000..078562eceab
--- /dev/null
+++ b/packages/plugin-multiversx/.npmignore
@@ -0,0 +1,6 @@
+*
+
+!dist/**
+!package.json
+!readme.md
+!tsup.config.ts
\ No newline at end of file
diff --git a/packages/plugin-multiversx/eslint.config.mjs b/packages/plugin-multiversx/eslint.config.mjs
new file mode 100644
index 00000000000..92fe5bbebef
--- /dev/null
+++ b/packages/plugin-multiversx/eslint.config.mjs
@@ -0,0 +1,3 @@
+import eslintGlobalConfig from "../../eslint.config.mjs";
+
+export default [...eslintGlobalConfig];
diff --git a/packages/plugin-multiversx/package.json b/packages/plugin-multiversx/package.json
new file mode 100644
index 00000000000..1c533df2bb1
--- /dev/null
+++ b/packages/plugin-multiversx/package.json
@@ -0,0 +1,26 @@
+{
+ "name": "@ai16z/plugin-multiversx",
+ "version": "0.1.5-alpha.0",
+ "main": "dist/index.js",
+ "type": "module",
+ "types": "dist/index.d.ts",
+ "dependencies": {
+ "@ai16z/eliza": "workspace:*",
+ "@multiversx/sdk-core": "13.15.0",
+ "bignumber.js": "9.1.2",
+ "browserify": "^17.0.1",
+ "esbuild-plugin-polyfill-node": "^0.3.0",
+ "esmify": "^2.1.1",
+ "tsup": "8.3.5",
+ "vitest": "2.1.5"
+ },
+ "scripts": {
+ "build": "tsup --format esm --dts",
+ "test": "vitest run",
+ "test:watch": "vitest",
+ "lint": "eslint . --fix"
+ },
+ "peerDependencies": {
+ "whatwg-url": "7.1.0"
+ }
+}
diff --git a/packages/plugin-multiversx/readme.md b/packages/plugin-multiversx/readme.md
new file mode 100644
index 00000000000..0c26c8b537b
--- /dev/null
+++ b/packages/plugin-multiversx/readme.md
@@ -0,0 +1,12 @@
+# MultiversX Plugin
+
+## Overview
+
+This plugin aims to be the basis of all interactions with the MultiversX ecosystem.
+
+## Adding a new action
+
+Reuse providers and utilities from the existing actions where possible. Add more utilities if you think they will be useful for other actions.
+
+1. Add the action to the `actions` directory. Try to follow the naming convention of the other actions.
+2. Export the action in the `index.ts` file.
diff --git a/packages/plugin-multiversx/src/actions/createToken.ts b/packages/plugin-multiversx/src/actions/createToken.ts
new file mode 100644
index 00000000000..c84632bff5f
--- /dev/null
+++ b/packages/plugin-multiversx/src/actions/createToken.ts
@@ -0,0 +1,161 @@
+import {
+ elizaLogger,
+ ActionExample,
+ Content,
+ HandlerCallback,
+ IAgentRuntime,
+ Memory,
+ ModelClass,
+ State,
+ generateObject,
+ composeContext,
+ type Action,
+} from "@ai16z/eliza";
+import { WalletProvider } from "../providers/wallet";
+import { validateMultiversxConfig } from "../enviroment";
+
+export interface CreateTokenContent extends Content {
+ tokenName: string;
+ tokenTicker: string;
+ decimals: string;
+ amount: string;
+}
+
+function isCreateTokenContent(
+ runtime: IAgentRuntime,
+ content: any
+): content is CreateTokenContent {
+ console.log("Content for create token", content);
+ return content.tokenName && content.tokenTicker && content.amount;
+}
+
+const createTokenTemplate = `Respond with a JSON markdown block containing only the extracted values. Use null for any values that cannot be determined.
+
+Example response:
+\`\`\`json
+{
+ "tokenName": "TEST",
+ "tokenTicker": "TST",
+ "amount: 100,
+ "decimals": 18
+}
+\`\`\`
+
+{{recentMessages}}
+
+Given the recent messages, extract the following information about the requested token creation:
+- Token name
+- Token ticker
+- Amount
+- Decimals
+
+Respond with a JSON markdown block containing only the extracted values.`;
+
+export default {
+ name: "CREATE_TOKEN",
+ similes: ["DEPLOY_TOKEN"],
+ validate: async (runtime: IAgentRuntime, message: Memory) => {
+ console.log("Validating config for user:", message.userId);
+ await validateMultiversxConfig(runtime);
+ return true;
+ },
+ description: "Create a new token.",
+ handler: async (
+ runtime: IAgentRuntime,
+ message: Memory,
+ state: State,
+ _options: { [key: string]: unknown },
+ callback?: HandlerCallback
+ ) => {
+ elizaLogger.log("Starting CREATE_TOKEN handler...");
+
+ // Initialize or update state
+ if (!state) {
+ state = (await runtime.composeState(message)) as State;
+ } else {
+ state = await runtime.updateRecentMessageState(state);
+ }
+
+ // Compose transfer context
+ const transferContext = composeContext({
+ state,
+ template: createTokenTemplate,
+ });
+
+ // Generate transfer content
+ const content = await generateObject({
+ runtime,
+ context: transferContext,
+ modelClass: ModelClass.SMALL,
+ });
+
+ // Validate transfer content
+ if (!isCreateTokenContent(runtime, content)) {
+ console.error("Invalid content for TRANSFER_TOKEN action.");
+ if (callback) {
+ callback({
+ text: "Unable to process transfer request. Invalid content provided.",
+ content: { error: "Invalid transfer content" },
+ });
+ }
+ return false;
+ }
+
+ try {
+ const privateKey = runtime.getSetting("MVX_PRIVATE_KEY");
+ const network = runtime.getSetting("MVX_NETWORK");
+
+ const walletProvider = new WalletProvider(privateKey, network);
+
+ await walletProvider.createESDT({
+ tokenName: content.tokenName,
+ amount: content.amount,
+ decimals: Number(content.decimals) || 18,
+ tokenTicker: content.tokenTicker,
+ });
+ return true;
+ } catch (error) {
+ console.error("Error during creating token:", error);
+ if (callback) {
+ callback({
+ text: `Error creating token: ${error.message}`,
+ content: { error: error.message },
+ });
+ }
+ return false;
+ }
+ },
+
+ examples: [
+ [
+ {
+ user: "{{user1}}",
+ content: {
+ text: "Create a token XTREME with ticker XTR and supply of 10000",
+ action: "CREATE_TOKEN",
+ },
+ },
+ {
+ user: "{{user2}}",
+ content: {
+ text: "Succesfully created token.",
+ },
+ },
+ ],
+ [
+ {
+ user: "{{user1}}",
+ content: {
+ text: "Create a token TEST with ticker TST, 18 decimals and su of 10000",
+ action: "CREATE_TOKEN",
+ },
+ },
+ {
+ user: "{{user2}}",
+ content: {
+ text: "Succesfully created token.",
+ },
+ },
+ ],
+ ] as ActionExample[][],
+} as Action;
diff --git a/packages/plugin-multiversx/src/actions/transfer.ts b/packages/plugin-multiversx/src/actions/transfer.ts
new file mode 100644
index 00000000000..076cef248ca
--- /dev/null
+++ b/packages/plugin-multiversx/src/actions/transfer.ts
@@ -0,0 +1,177 @@
+import {
+ elizaLogger,
+ ActionExample,
+ Content,
+ HandlerCallback,
+ IAgentRuntime,
+ Memory,
+ ModelClass,
+ State,
+ composeContext,
+ generateObject,
+ type Action,
+} from "@ai16z/eliza";
+import { WalletProvider } from "../providers/wallet";
+import { validateMultiversxConfig } from "../enviroment";
+
+export interface TransferContent extends Content {
+ tokenAddress: string;
+ amount: string;
+ tokenIdentifier?: string;
+}
+
+function isTransferContent(
+ _runtime: IAgentRuntime,
+ content: any
+): content is TransferContent {
+ console.log("Content for transfer", content);
+ return (
+ typeof content.tokenAddress === "string" &&
+ typeof content.amount === "string"
+ );
+}
+
+const transferTemplate = `Respond with a JSON markdown block containing only the extracted values. Use null for any values that cannot be determined.
+
+Example response:
+\`\`\`json
+{
+ "tokenAddress": "erd12r22hx2q4jjt8e0gukxt5shxqjp9ys5nwdtz0gpds25zf8qwtjdqyzfgzm",
+ "amount": "1",
+ "tokenIdentifier": "PEPE-3eca7c"
+}
+\`\`\`
+
+{{recentMessages}}
+
+Given the recent messages, extract the following information about the requested token transfer:
+- Token address
+- Amount to transfer
+- Token identifier
+
+Respond with a JSON markdown block containing only the extracted values.`;
+
+export default {
+ name: "SEND_TOKEN",
+ similes: [
+ "TRANSFER_TOKEN",
+ "TRANSFER_TOKENS",
+ "SEND_TOKENS",
+ "SEND_EGLD",
+ "PAY",
+ ],
+ validate: async (runtime: IAgentRuntime, message: Memory) => {
+ console.log("Validating config for user:", message.userId);
+ await validateMultiversxConfig(runtime);
+ return true;
+ },
+ description: "Transfer tokens from the agent wallet to another address",
+ handler: async (
+ runtime: IAgentRuntime,
+ message: Memory,
+ state: State,
+ _options: { [key: string]: unknown },
+ callback?: HandlerCallback
+ ) => {
+ elizaLogger.log("Starting SEND_TOKEN handler...");
+
+ // Initialize or update state
+ if (!state) {
+ state = (await runtime.composeState(message)) as State;
+ } else {
+ state = await runtime.updateRecentMessageState(state);
+ }
+
+ // Compose transfer context
+ const transferContext = composeContext({
+ state,
+ template: transferTemplate,
+ });
+
+ // Generate transfer content
+ const content = await generateObject({
+ runtime,
+ context: transferContext,
+ modelClass: ModelClass.SMALL,
+ });
+
+ // Validate transfer content
+ if (!isTransferContent(runtime, content)) {
+ console.error("Invalid content for TRANSFER_TOKEN action.");
+ if (callback) {
+ callback({
+ text: "Unable to process transfer request. Invalid content provided.",
+ content: { error: "Invalid transfer content" },
+ });
+ }
+ return false;
+ }
+
+ try {
+ const privateKey = runtime.getSetting("MVX_PRIVATE_KEY");
+ const network = runtime.getSetting("MVX_NETWORK");
+
+ const walletProvider = new WalletProvider(privateKey, network);
+
+ if (
+ content.tokenIdentifier &&
+ content.tokenIdentifier.toLowerCase() !== "egld"
+ ) {
+ await walletProvider.sendESDT({
+ receiverAddress: content.tokenAddress,
+ amount: content.amount,
+ identifier: content.tokenIdentifier,
+ });
+ return true;
+ }
+
+ await walletProvider.sendEGLD({
+ receiverAddress: content.tokenAddress,
+ amount: content.amount,
+ });
+ return true;
+ } catch (error) {
+ console.error("Error during token transfer:", error);
+ if (callback) {
+ callback({
+ text: `Error transferring tokens: ${error.message}`,
+ content: { error: error.message },
+ });
+ }
+ return "";
+ }
+ },
+
+ examples: [
+ [
+ {
+ user: "{{user1}}",
+ content: {
+ text: "Send 1 EGLD to erd12r22hx2q4jjt8e0gukxt5shxqjp9ys5nwdtz0gpds25zf8qwtjdqyzfgzm",
+ },
+ },
+ {
+ user: "{{user2}}",
+ content: {
+ text: "I'll send 1 EGLD tokens now...",
+ action: "SEND_TOKEN",
+ },
+ },
+ ],
+ [
+ {
+ user: "{{user1}}",
+ content: {
+ text: "Send 1 TST-a8b23d to erd12r22hx2q4jjt8e0gukxt5shxqjp9ys5nwdtz0gpds25zf8qwtjdqyzfgzm",
+ },
+ },
+ {
+ user: "{{user2}}",
+ content: {
+ text: "I'll send 1 TST-a8b23d tokens now...",
+ action: "SEND_TOKEN",
+ },
+ },
+ ],
+ ] as ActionExample[][],
+} as Action;
diff --git a/packages/plugin-multiversx/src/enviroment.ts b/packages/plugin-multiversx/src/enviroment.ts
new file mode 100644
index 00000000000..b1d9b2d3e88
--- /dev/null
+++ b/packages/plugin-multiversx/src/enviroment.ts
@@ -0,0 +1,37 @@
+import { IAgentRuntime } from "@ai16z/eliza";
+import { z } from "zod";
+
+export const multiversxEnvSchema = z.object({
+ MVX_PRIVATE_KEY: z
+ .string()
+ .min(1, "MultiversX wallet private key is required"),
+ MVX_NETWORK: z.enum(["mainnet", "devnet", "testnet"]),
+});
+
+export type MultiversxConfig = z.infer;
+
+export async function validateMultiversxConfig(
+ runtime: IAgentRuntime
+): Promise {
+ try {
+ const config = {
+ MVX_PRIVATE_KEY:
+ runtime.getSetting("MVX_PRIVATE_KEY") ||
+ process.env.MVX_PRIVATE_KEY,
+ MVX_NETWORK:
+ runtime.getSetting("MVX_NETWORK") || process.env.MVX_NETWORK,
+ };
+
+ return multiversxEnvSchema.parse(config);
+ } catch (error) {
+ if (error instanceof z.ZodError) {
+ const errorMessages = error.errors
+ .map((err) => `${err.path.join(".")}: ${err.message}`)
+ .join("\n");
+ throw new Error(
+ `MultiversX configuration validation failed:\n${errorMessages}`
+ );
+ }
+ throw error;
+ }
+}
diff --git a/packages/plugin-multiversx/src/index.ts b/packages/plugin-multiversx/src/index.ts
new file mode 100644
index 00000000000..70f60deaae1
--- /dev/null
+++ b/packages/plugin-multiversx/src/index.ts
@@ -0,0 +1,13 @@
+import { Plugin } from "@ai16z/eliza";
+import transfer from "./actions/transfer";
+import createToken from "./actions/createToken";
+
+export const multiversxPlugin: Plugin = {
+ name: "multiversx",
+ description: "MultiversX Plugin for Eliza",
+ actions: [transfer, createToken],
+ evaluators: [],
+ providers: [],
+};
+
+export default multiversxPlugin;
diff --git a/packages/plugin-multiversx/src/providers/wallet.ts b/packages/plugin-multiversx/src/providers/wallet.ts
new file mode 100644
index 00000000000..8d6d2d02a50
--- /dev/null
+++ b/packages/plugin-multiversx/src/providers/wallet.ts
@@ -0,0 +1,301 @@
+import { elizaLogger } from "@ai16z/eliza";
+import {
+ UserSigner,
+ Address,
+ TransactionComputer,
+ ApiNetworkProvider,
+ UserSecretKey,
+ TokenTransfer,
+ TransferTransactionsFactory,
+ TransactionsFactoryConfig,
+ Token,
+ Transaction,
+ TokenManagementTransactionsFactory,
+} from "@multiversx/sdk-core";
+import { denominateAmount } from "../utils/amount";
+
+// Network configuration object for different environments (mainnet, devnet, testnet)
+const MVX_NETWORK_CONFIG = {
+ mainnet: {
+ chainID: "1", // Mainnet chain ID
+ apiURL: "https://api.multiversx.com", // Mainnet API URL
+ explorerURL: "https://explorer.multiversx.com",
+ },
+ devnet: {
+ chainID: "D", // Devnet chain ID
+ apiURL: "https://devnet-api.multiversx.com", // Devnet API URL,
+ explorerURL: "https://devnet-explorer.multiversx.com",
+ },
+ testnet: {
+ chainID: "T", // Testnet chain ID
+ apiURL: "https://testnet-api.multiversx.com", // Testnet API URL
+ explorerURL: "https://testnet-explorer.multiversx.com",
+ },
+};
+
+// WalletProvider class handles wallet-related operations, such as signing transactions, retrieving balance, and sending tokens
+export class WalletProvider {
+ private signer: UserSigner; // Handles cryptographic signing
+ private apiNetworkProvider: ApiNetworkProvider; // Interacts with the MultiversX network
+ private chainID: string; // Current network chain ID
+ private explorerURL: string; // Current network explorer URL
+
+ /**
+ * Constructor to initialize WalletProvider with a private key and network configuration
+ * @param privateKey - User's private key for signing transactions
+ * @param network - Target network (mainnet, devnet, or testnet)
+ */
+ constructor(privateKey: string, network: string) {
+ if (!MVX_NETWORK_CONFIG[network]) {
+ throw new Error(`Unsupported network: ${network}`); // Validate network
+ }
+
+ const networkConfig = MVX_NETWORK_CONFIG[network];
+ this.chainID = networkConfig.chainID;
+ this.explorerURL = networkConfig.explorerURL;
+
+ // Initialize the signer with the user's private key
+ const secretKey = UserSecretKey.fromString(privateKey);
+ this.signer = new UserSigner(secretKey);
+
+ // Set up the network provider for API interactions
+ this.apiNetworkProvider = new ApiNetworkProvider(networkConfig.apiURL, {
+ clientName: "eliza-mvx",
+ });
+ }
+
+ /**
+ * Retrieve the wallet address derived from the private key
+ * @returns Address object
+ */
+ public getAddress(): Address {
+ return this.signer.getAddress();
+ }
+
+ /**
+ * Fetch the wallet's current EGLD balance
+ * @returns Promise resolving to the wallet's balance as a string
+ */
+ public async getBalance(): Promise {
+ const address = new Address(this.getAddress());
+ const account = await this.apiNetworkProvider.getAccount(address);
+ return account.balance.toString(); // Return balance as a string
+ }
+
+ /**
+ * Sign a transaction using the wallet's private key
+ * @param transaction - The transaction object to sign
+ * @returns The transaction signature as a string
+ */
+ public async signTransaction(transaction: Transaction) {
+ const computer = new TransactionComputer();
+ const serializedTx = computer.computeBytesForSigning(transaction); // Prepare transaction for signing
+ const signature = await this.signer.sign(serializedTx); // Sign the transaction
+ return signature;
+ }
+
+ /**
+ * Send EGLD tokens to another wallet
+ * @param receiverAddress - Recipient's wallet address
+ * @param amount - Amount of EGLD to send
+ * @returns Transaction hash as a string
+ */
+ public async sendEGLD({
+ receiverAddress,
+ amount,
+ }: {
+ receiverAddress: string;
+ amount: string;
+ }): Promise {
+ try {
+ const receiver = new Address(receiverAddress);
+ const value = denominateAmount({ amount, decimals: 18 }); // Convert amount to the smallest unit
+ const senderAddress = this.getAddress();
+
+ // Prepare the transaction factory with the current chain ID
+ const factoryConfig = new TransactionsFactoryConfig({
+ chainID: this.chainID,
+ });
+ const factory = new TransferTransactionsFactory({
+ config: factoryConfig,
+ });
+
+ // Create a native EGLD transfer transaction
+ const transaction = factory.createTransactionForNativeTokenTransfer(
+ {
+ sender: this.getAddress(),
+ receiver: receiver,
+ nativeAmount: BigInt(value),
+ }
+ );
+
+ // Get the sender's account details to set the nonce
+ const account =
+ await this.apiNetworkProvider.getAccount(senderAddress);
+ transaction.nonce = BigInt(account.nonce);
+
+ // Sign the transaction
+ const signature = await this.signTransaction(transaction);
+ transaction.signature = signature;
+
+ // Broadcast the transaction to the network
+ const txHash =
+ await this.apiNetworkProvider.sendTransaction(transaction);
+
+ elizaLogger.log(`TxHash: ${txHash}`); // Log transaction hash
+ elizaLogger.log(
+ `Transaction URL: ${this.explorerURL}/transactions/${txHash}`
+ ); // View Transaction
+ return txHash;
+ } catch (error) {
+ console.error("Error sending EGLD transaction:", error);
+ throw new Error(
+ `Failed to send EGLD: ${error.message || "Unknown error"}`
+ );
+ }
+ }
+
+ /**
+ * Send ESDT (eStandard Digital Token) tokens to another wallet
+ * @param receiverAddress - Recipient's wallet address
+ * @param amount - Amount of ESDT to send
+ * @param identifier - ESDT token identifier (e.g., PEPE-3eca7c)
+ * @returns Transaction hash as a string
+ */
+ public async sendESDT({
+ receiverAddress,
+ amount,
+ identifier,
+ }: {
+ receiverAddress: string;
+ amount: string;
+ identifier: string;
+ }): Promise {
+ try {
+ const address = this.getAddress();
+
+ // Set up transaction factory for ESDT transfers
+ const config = new TransactionsFactoryConfig({
+ chainID: this.chainID,
+ });
+ const factory = new TransferTransactionsFactory({ config });
+
+ // Retrieve token details to determine the token's decimals
+ const token =
+ await this.apiNetworkProvider.getFungibleTokenOfAccount(
+ address,
+ identifier
+ );
+
+ // Convert amount to the token's smallest unit
+ const value = denominateAmount({
+ amount,
+ decimals: token.rawResponse.decimals,
+ });
+
+ // Create an ESDT transfer transaction
+ const transaction = factory.createTransactionForESDTTokenTransfer({
+ sender: this.getAddress(),
+ receiver: new Address(receiverAddress),
+ tokenTransfers: [
+ new TokenTransfer({
+ token: new Token({ identifier }),
+ amount: BigInt(value),
+ }),
+ ],
+ });
+
+ // Set the transaction nonce
+ const account = await this.apiNetworkProvider.getAccount(address);
+ transaction.nonce = BigInt(account.nonce);
+
+ // Sign and broadcast the transaction
+ const signature = await this.signTransaction(transaction);
+ transaction.signature = signature;
+ const txHash =
+ await this.apiNetworkProvider.sendTransaction(transaction);
+
+ elizaLogger.log(`TxHash: ${txHash}`); // Log transaction hash
+ elizaLogger.log(
+ `Transaction URL: ${this.explorerURL}/transactions/${txHash}`
+ ); // View Transaction
+ return txHash;
+ } catch (error) {
+ console.error("Error sending ESDT transaction:", error);
+ throw new Error(
+ `Failed to send ESDT: ${error.message || "Unknown error"}`
+ );
+ }
+ }
+
+ /**
+ * Create a new eStandard Digital Token (ESDT).
+ * @param tokenName - The name of the token to be created.
+ * @param tokenTicker - The ticker symbol for the token.
+ * @param amount - The initial supply of the token.
+ * @param decimals - The number of decimal places for the token.
+ * @returns The transaction hash of the created ESDT.
+ */
+ public async createESDT({
+ tokenName,
+ tokenTicker,
+ amount,
+ decimals,
+ }: {
+ tokenName: string;
+ tokenTicker: string;
+ amount: string;
+ decimals: number;
+ }): Promise {
+ try {
+ const address = this.getAddress(); // Retrieve the sender's address
+
+ const factoryConfig = new TransactionsFactoryConfig({
+ chainID: this.chainID, // Set the chain ID for the transaction factory
+ });
+ const factory = new TokenManagementTransactionsFactory({
+ config: factoryConfig, // Initialize the factory with the configuration
+ });
+
+ const totalSupply = denominateAmount({ amount, decimals });
+
+ // Create a transaction for issuing a fungible token
+ const transaction = factory.createTransactionForIssuingFungible({
+ sender: new Address(address), // Specify the sender's address
+ tokenName, // Name of the token
+ tokenTicker: tokenTicker.toUpperCase(), // Token ticker in uppercase
+ initialSupply: BigInt(totalSupply), // Initial supply as a BigInt
+ numDecimals: BigInt(decimals), // Number of decimals as a BigInt
+ canFreeze: false, // Token cannot be frozen
+ canWipe: false, // Token cannot be wiped
+ canPause: false, // Token cannot be paused
+ canChangeOwner: true, // Ownership can be changed
+ canUpgrade: true, // Token can be upgraded
+ canAddSpecialRoles: true, // Special roles can be added
+ });
+
+ // Fetch the account details to set the nonce
+ const account = await this.apiNetworkProvider.getAccount(address);
+ transaction.nonce = BigInt(account.nonce); // Set the nonce for the transaction
+
+ const signature = await this.signTransaction(transaction); // Sign the transaction
+ transaction.signature = signature; // Attach the signature to the transaction
+
+ // Send the transaction to the network and get the transaction hash
+ const txHash =
+ await this.apiNetworkProvider.sendTransaction(transaction);
+
+ elizaLogger.log(`TxHash: ${txHash}`); // Log the transaction hash
+ elizaLogger.log(
+ `Transaction URL: ${this.explorerURL}/transactions/${txHash}`
+ ); // View Transaction
+
+ return txHash; // Return the transaction hash
+ } catch (error) {
+ console.error("Error creating ESDT:", error);
+ throw new Error(
+ `Failed to create ESDT: ${error.message || "Unknown error"}`
+ ); // Throw an error if creation fails
+ }
+ }
+}
diff --git a/packages/plugin-multiversx/src/tests/wallet.test.ts b/packages/plugin-multiversx/src/tests/wallet.test.ts
new file mode 100644
index 00000000000..c147c4dc9a8
--- /dev/null
+++ b/packages/plugin-multiversx/src/tests/wallet.test.ts
@@ -0,0 +1,19 @@
+import { describe, it, expect, beforeEach } from "vitest";
+import { WalletProvider } from "../providers/wallet";
+
+describe("WalletProvider", () => {
+ let walletProvider: WalletProvider;
+
+ beforeEach(() => {
+ // Test wallet private key
+ const privateKey =
+ "b5a356fb7e5563e6b07887f1de0376f9c74f2affaa71d41941dbc002ea13f656";
+ const network = "devnet";
+ walletProvider = new WalletProvider(privateKey, network);
+ });
+
+ it("should retrieve the wallet address", () => {
+ const address = walletProvider.getAddress();
+ expect(address).toBeDefined();
+ });
+});
diff --git a/packages/plugin-multiversx/src/utils/amount.ts b/packages/plugin-multiversx/src/utils/amount.ts
new file mode 100644
index 00000000000..a3f8f901615
--- /dev/null
+++ b/packages/plugin-multiversx/src/utils/amount.ts
@@ -0,0 +1,15 @@
+import BigNumber from "bignumber.js";
+
+BigNumber.config({ ROUNDING_MODE: BigNumber.ROUND_FLOOR });
+
+type PayloadType = {
+ amount: string;
+ decimals: number;
+};
+
+export const denominateAmount = ({ amount, decimals }: PayloadType) => {
+ return new BigNumber(amount)
+ .shiftedBy(decimals)
+ .decimalPlaces(0)
+ .toFixed(0);
+};
diff --git a/packages/plugin-multiversx/tsconfig.json b/packages/plugin-multiversx/tsconfig.json
new file mode 100644
index 00000000000..005fbac9d36
--- /dev/null
+++ b/packages/plugin-multiversx/tsconfig.json
@@ -0,0 +1,8 @@
+{
+ "extends": "../core/tsconfig.json",
+ "compilerOptions": {
+ "outDir": "dist",
+ "rootDir": "src"
+ },
+ "include": ["src/**/*.ts"]
+}
diff --git a/packages/plugin-multiversx/tsup.config.ts b/packages/plugin-multiversx/tsup.config.ts
new file mode 100644
index 00000000000..4f0ac8513de
--- /dev/null
+++ b/packages/plugin-multiversx/tsup.config.ts
@@ -0,0 +1,20 @@
+import { defineConfig } from "tsup";
+import { polyfillNode } from "esbuild-plugin-polyfill-node";
+
+export default defineConfig({
+ entry: ["src/index.ts"],
+ outDir: "dist",
+ sourcemap: true,
+ clean: true,
+ format: ["esm"], // Ensure you're targeting CommonJS
+ external: [
+ "dotenv", // Externalize dotenv to prevent bundling
+ "@reflink/reflink",
+ "@node-llama-cpp",
+ "agentkeepalive",
+ "zod",
+ "zlib",
+ // Add other modules you want to externalize
+ ],
+ esbuildPlugins: [polyfillNode()],
+});
diff --git a/packages/plugin-near/.npmignore b/packages/plugin-near/.npmignore
new file mode 100644
index 00000000000..078562eceab
--- /dev/null
+++ b/packages/plugin-near/.npmignore
@@ -0,0 +1,6 @@
+*
+
+!dist/**
+!package.json
+!readme.md
+!tsup.config.ts
\ No newline at end of file
diff --git a/packages/plugin-near/eslint.config.mjs b/packages/plugin-near/eslint.config.mjs
new file mode 100644
index 00000000000..92fe5bbebef
--- /dev/null
+++ b/packages/plugin-near/eslint.config.mjs
@@ -0,0 +1,3 @@
+import eslintGlobalConfig from "../../eslint.config.mjs";
+
+export default [...eslintGlobalConfig];
diff --git a/packages/plugin-near/package.json b/packages/plugin-near/package.json
new file mode 100644
index 00000000000..0f82fc18034
--- /dev/null
+++ b/packages/plugin-near/package.json
@@ -0,0 +1,25 @@
+{
+ "name": "@ai16z/plugin-near",
+ "version": "0.0.1",
+ "main": "dist/index.js",
+ "type": "module",
+ "types": "dist/index.d.ts",
+ "dependencies": {
+ "@ai16z/eliza": "workspace:*",
+ "@ref-finance/ref-sdk": "^1.4.6",
+ "tsup": "8.3.5",
+ "near-api-js": "5.0.1",
+ "bignumber.js": "9.1.2",
+ "node-cache": "5.1.2"
+ },
+ "scripts": {
+ "build": "tsup --format esm,cjs --dts",
+ "test": "vitest run",
+ "test:watch": "vitest",
+ "lint": "eslint . --fix"
+ },
+ "peerDependencies": {
+ "whatwg-url": "7.1.0",
+ "form-data": "4.0.1"
+ }
+}
diff --git a/packages/plugin-near/src/actions/swap.ts b/packages/plugin-near/src/actions/swap.ts
new file mode 100644
index 00000000000..e12f5774b84
--- /dev/null
+++ b/packages/plugin-near/src/actions/swap.ts
@@ -0,0 +1,336 @@
+import {
+ ActionExample,
+ HandlerCallback,
+ IAgentRuntime,
+ Memory,
+ ModelClass,
+ State,
+ type Action,
+ composeContext,
+ generateObject,
+} from "@ai16z/eliza";
+import { connect, keyStores, utils } from "near-api-js";
+import {
+ init_env,
+ ftGetTokenMetadata,
+ estimateSwap,
+ instantSwap,
+ fetchAllPools,
+ FT_MINIMUM_STORAGE_BALANCE_LARGE,
+ ONE_YOCTO_NEAR,
+} from "@ref-finance/ref-sdk";
+import { walletProvider } from "../providers/wallet";
+import { KeyPairString } from "near-api-js/lib/utils";
+
+async function checkStorageBalance(
+ account: any,
+ contractId: string
+): Promise {
+ try {
+ const balance = await account.viewFunction({
+ contractId,
+ methodName: "storage_balance_of",
+ args: { account_id: account.accountId },
+ });
+ return balance !== null && balance.total !== "0";
+ } catch (error) {
+ console.log(`Error checking storage balance: ${error}`);
+ return false;
+ }
+}
+
+async function swapToken(
+ runtime: IAgentRuntime,
+ inputTokenId: string,
+ outputTokenId: string,
+ amount: string,
+ slippageTolerance: number = Number(
+ runtime.getSetting("SLIPPAGE_TOLERANCE")
+ ) || 0.01
+): Promise {
+ try {
+ // Get token metadata
+ const tokenIn = await ftGetTokenMetadata(inputTokenId);
+ const tokenOut = await ftGetTokenMetadata(outputTokenId);
+ const networkId = runtime.getSetting("NEAR_NETWORK") || "testnet";
+ const nodeUrl =
+ runtime.getSetting("RPC_URL") || "https://rpc.testnet.near.org";
+
+ // Get all pools for estimation
+ // ratedPools, unRatedPools,
+ const { simplePools } = await fetchAllPools();
+ const swapTodos = await estimateSwap({
+ tokenIn,
+ tokenOut,
+ amountIn: amount,
+ simplePools,
+ options: {
+ enableSmartRouting: true,
+ },
+ });
+
+ if (!swapTodos || swapTodos.length === 0) {
+ throw new Error("No valid swap route found");
+ }
+
+ // Get account ID from runtime settings
+ const accountId = runtime.getSetting("NEAR_ADDRESS");
+ if (!accountId) {
+ throw new Error("NEAR_ADDRESS not configured");
+ }
+
+ const secretKey = runtime.getSetting("NEAR_WALLET_SECRET_KEY");
+ const keyStore = new keyStores.InMemoryKeyStore();
+ const keyPair = utils.KeyPair.fromString(secretKey as KeyPairString);
+ await keyStore.setKey(networkId, accountId, keyPair);
+
+ const nearConnection = await connect({
+ networkId,
+ keyStore,
+ nodeUrl,
+ });
+
+ const account = await nearConnection.account(accountId);
+
+ // Check storage balance for both tokens
+ const hasStorageIn = await checkStorageBalance(account, inputTokenId);
+ const hasStorageOut = await checkStorageBalance(account, outputTokenId);
+
+ const transactions = await instantSwap({
+ tokenIn,
+ tokenOut,
+ amountIn: amount,
+ swapTodos,
+ slippageTolerance,
+ AccountId: accountId,
+ });
+
+ // If storage deposit is needed, add it to transactions
+ if (!hasStorageIn) {
+ transactions.unshift({
+ receiverId: inputTokenId,
+ functionCalls: [
+ {
+ methodName: "storage_deposit",
+ args: {
+ account_id: accountId,
+ registration_only: true,
+ },
+ gas: "30000000000000",
+ amount: FT_MINIMUM_STORAGE_BALANCE_LARGE,
+ },
+ ],
+ });
+ }
+
+ if (!hasStorageOut) {
+ transactions.unshift({
+ receiverId: outputTokenId,
+ functionCalls: [
+ {
+ methodName: "storage_deposit",
+ args: {
+ account_id: accountId,
+ registration_only: true,
+ },
+ gas: "30000000000000",
+ amount: FT_MINIMUM_STORAGE_BALANCE_LARGE,
+ },
+ ],
+ });
+ }
+
+ return transactions;
+ } catch (error) {
+ console.error("Error in swapToken:", error);
+ throw error;
+ }
+}
+
+const swapTemplate = `Respond with a JSON markdown block containing only the extracted values. Use null for any values that cannot be determined.
+
+Example response:
+\`\`\`json
+{
+ "inputTokenId": "wrap.testnet",
+ "outputTokenId": "ref.fakes.testnet",
+ "amount": "1.5"
+}
+\`\`\`
+
+{{recentMessages}}
+
+Given the recent messages and wallet information below:
+
+{{walletInfo}}
+
+Extract the following information about the requested token swap:
+- Input token ID (the token being sold)
+- Output token ID (the token being bought)
+- Amount to swap
+
+Respond with a JSON markdown block containing only the extracted values. Use null for any values that cannot be determined. The result should be a valid JSON object with the following schema:
+\`\`\`json
+{
+ "inputTokenId": string | null,
+ "outputTokenId": string | null,
+ "amount": string | null
+}
+\`\`\``;
+
+export const executeSwap: Action = {
+ name: "EXECUTE_SWAP_NEAR",
+ similes: [
+ "SWAP_TOKENS_NEAR",
+ "TOKEN_SWAP_NEAR",
+ "TRADE_TOKENS_NEAR",
+ "EXCHANGE_TOKENS_NEAR",
+ ],
+ validate: async (runtime: IAgentRuntime, message: Memory) => {
+ console.log("Message:", message);
+ return true;
+ },
+ description: "Perform a token swap using Ref Finance.",
+ handler: async (
+ runtime: IAgentRuntime,
+ message: Memory,
+ state: State,
+ _options: { [key: string]: unknown },
+ callback?: HandlerCallback
+ ): Promise => {
+ // Initialize Ref SDK with testnet environment
+ init_env(runtime.getSetting("NEAR_NETWORK") || "testnet");
+ // Compose state
+ if (!state) {
+ state = (await runtime.composeState(message)) as State;
+ } else {
+ state = await runtime.updateRecentMessageState(state);
+ }
+
+ const walletInfo = await walletProvider.get(runtime, message, state);
+ state.walletInfo = walletInfo;
+
+ const swapContext = composeContext({
+ state,
+ template: swapTemplate,
+ });
+
+ const response = await generateObject({
+ runtime,
+ context: swapContext,
+ modelClass: ModelClass.LARGE,
+ });
+
+ console.log("Response:", response);
+
+ if (
+ !response.inputTokenId ||
+ !response.outputTokenId ||
+ !response.amount
+ ) {
+ console.log("Missing required parameters, skipping swap");
+ const responseMsg = {
+ text: "I need the input token ID, output token ID, and amount to perform the swap",
+ };
+ callback?.(responseMsg);
+ return true;
+ }
+
+ try {
+ // Get account credentials
+ const accountId = runtime.getSetting("NEAR_ADDRESS");
+ const secretKey = runtime.getSetting("NEAR_WALLET_SECRET_KEY");
+
+ if (!accountId || !secretKey) {
+ throw new Error("NEAR wallet credentials not configured");
+ }
+
+ // Create keystore and connect to NEAR
+ const keyStore = new keyStores.InMemoryKeyStore();
+ const keyPair = utils.KeyPair.fromString(
+ secretKey as KeyPairString
+ );
+ await keyStore.setKey("testnet", accountId, keyPair);
+
+ const nearConnection = await connect({
+ networkId: runtime.getSetting("NEAR_NETWORK") || "testnet",
+ keyStore,
+ nodeUrl:
+ runtime.getSetting("RPC_URL") ||
+ "https://rpc.testnet.near.org",
+ });
+
+ // Execute swap
+ const swapResult = await swapToken(
+ runtime,
+ response.inputTokenId,
+ response.outputTokenId,
+ response.amount,
+ Number(runtime.getSetting("SLIPPAGE_TOLERANCE")) || 0.01
+ );
+
+ // Sign and send transactions
+ const account = await nearConnection.account(accountId);
+ const results = [];
+
+ for (const tx of swapResult) {
+ for (const functionCall of tx.functionCalls) {
+ const result = await account.functionCall({
+ contractId: tx.receiverId,
+ methodName: functionCall.methodName,
+ args: functionCall.args,
+ gas: functionCall.gas,
+ attachedDeposit: BigInt(
+ functionCall.amount === ONE_YOCTO_NEAR
+ ? "1"
+ : functionCall.amount
+ ),
+ });
+ results.push(result);
+ }
+ }
+
+ console.log("Swap completed successfully!");
+ const txHashes = results.map((r) => r.transaction.hash).join(", ");
+
+ const responseMsg = {
+ text: `Swap completed successfully! Transaction hashes: ${txHashes}`,
+ };
+
+ callback?.(responseMsg);
+ return true;
+ } catch (error) {
+ console.error("Error during token swap:", error);
+ const responseMsg = {
+ text: `Error during swap: ${error instanceof Error ? error.message : String(error)}`,
+ };
+ callback?.(responseMsg);
+ return false;
+ }
+ },
+ examples: [
+ [
+ {
+ user: "{{user1}}",
+ content: {
+ inputTokenId: "wrap.testnet",
+ outputTokenId: "ref.fakes.testnet",
+ amount: "1.0",
+ },
+ },
+ {
+ user: "{{user2}}",
+ content: {
+ text: "Swapping 1.0 NEAR for REF...",
+ action: "TOKEN_SWAP",
+ },
+ },
+ {
+ user: "{{user2}}",
+ content: {
+ text: "Swap completed successfully! Transaction hash: ...",
+ },
+ },
+ ],
+ ] as ActionExample[][],
+} as Action;
diff --git a/packages/plugin-near/src/actions/transfer.ts b/packages/plugin-near/src/actions/transfer.ts
new file mode 100644
index 00000000000..181c5645de6
--- /dev/null
+++ b/packages/plugin-near/src/actions/transfer.ts
@@ -0,0 +1,200 @@
+import {
+ ActionExample,
+ Content,
+ HandlerCallback,
+ IAgentRuntime,
+ Memory,
+ ModelClass,
+ State,
+ type Action,
+ composeContext,
+ generateObject,
+} from "@ai16z/eliza";
+import { connect, keyStores, utils } from "near-api-js";
+import { KeyPairString } from "near-api-js/lib/utils";
+import { utils as nearUtils } from "near-api-js";
+// import BigNumber from "bignumber.js";
+
+export interface TransferContent extends Content {
+ recipient: string;
+ amount: string | number;
+ tokenAddress?: string; // Optional for native NEAR transfers
+}
+
+function isTransferContent(
+ runtime: IAgentRuntime,
+ content: any
+): content is TransferContent {
+ return (
+ typeof content.recipient === "string" &&
+ (typeof content.amount === "string" ||
+ typeof content.amount === "number")
+ );
+}
+
+const transferTemplate = `Respond with a JSON markdown block containing only the extracted values. Use null for any values that cannot be determined.
+
+Example response:
+\`\`\`json
+{
+ "recipient": "bob.near",
+ "amount": "1.5",
+ "tokenAddress": null
+}
+\`\`\`
+
+{{recentMessages}}
+
+Given the recent messages and wallet information below:
+
+{{walletInfo}}
+
+Extract the following information about the requested token transfer:
+- Recipient address (NEAR account)
+- Amount to transfer
+- Token contract address (null for native NEAR transfers)
+
+Respond with a JSON markdown block containing only the extracted values.`;
+
+async function transferNEAR(
+ runtime: IAgentRuntime,
+ recipient: string,
+ amount: string
+): Promise {
+ const networkId = runtime.getSetting("NEAR_NETWORK") || "testnet";
+ const nodeUrl =
+ runtime.getSetting("RPC_URL") || "https://rpc.testnet.near.org";
+ const accountId = runtime.getSetting("NEAR_ADDRESS");
+ const secretKey = runtime.getSetting("NEAR_WALLET_SECRET_KEY");
+
+ if (!accountId || !secretKey) {
+ throw new Error("NEAR wallet credentials not configured");
+ }
+
+ // Convert amount to yoctoNEAR (1 NEAR = 10^24 yoctoNEAR)
+ // const yoctoAmount = new BigNumber(amount).multipliedBy(new BigNumber(10).pow(24)).toFixed(0);
+
+ // Create keystore and connect to NEAR
+ const keyStore = new keyStores.InMemoryKeyStore();
+ const keyPair = utils.KeyPair.fromString(secretKey as KeyPairString);
+ await keyStore.setKey(networkId, accountId, keyPair);
+
+ const nearConnection = await connect({
+ networkId,
+ keyStore,
+ nodeUrl,
+ });
+
+ const account = await nearConnection.account(accountId);
+
+ // Execute transfer
+ const result = await account.sendMoney(
+ recipient,
+ BigInt(nearUtils.format.parseNearAmount(amount)!)
+ );
+
+ return result.transaction.hash;
+}
+
+export const executeTransfer: Action = {
+ name: "SEND_NEAR",
+ similes: ["TRANSFER_NEAR", "SEND_TOKENS", "TRANSFER_TOKENS", "PAY_NEAR"],
+ validate: async (_runtime: IAgentRuntime, _message: Memory) => {
+ return true; // Add your validation logic here
+ },
+ description: "Transfer NEAR tokens to another account",
+ handler: async (
+ runtime: IAgentRuntime,
+ message: Memory,
+ state: State,
+ _options: { [key: string]: unknown },
+ callback?: HandlerCallback
+ ): Promise => {
+ // Initialize or update state
+ if (!state) {
+ state = (await runtime.composeState(message)) as State;
+ } else {
+ state = await runtime.updateRecentMessageState(state);
+ }
+
+ // Compose transfer context
+ const transferContext = composeContext({
+ state,
+ template: transferTemplate,
+ });
+
+ // Generate transfer content
+ const content = await generateObject({
+ runtime,
+ context: transferContext,
+ modelClass: ModelClass.SMALL,
+ });
+
+ // Validate transfer content
+ if (!isTransferContent(runtime, content)) {
+ console.error("Invalid content for TRANSFER_NEAR action.");
+ if (callback) {
+ callback({
+ text: "Unable to process transfer request. Invalid content provided.",
+ content: { error: "Invalid transfer content" },
+ });
+ }
+ return false;
+ }
+
+ try {
+ const txHash = await transferNEAR(
+ runtime,
+ content.recipient,
+ content.amount.toString()
+ );
+
+ if (callback) {
+ callback({
+ text: `Successfully transferred ${content.amount} NEAR to ${content.recipient}\nTransaction: ${txHash}`,
+ content: {
+ success: true,
+ signature: txHash,
+ amount: content.amount,
+ recipient: content.recipient,
+ },
+ });
+ }
+
+ return true;
+ } catch (error) {
+ console.error("Error during NEAR transfer:", error);
+ if (callback) {
+ callback({
+ text: `Error transferring NEAR: ${error}`,
+ content: { error: error },
+ });
+ }
+ return false;
+ }
+ },
+
+ examples: [
+ [
+ {
+ user: "{{user1}}",
+ content: {
+ text: "Send 1.5 NEAR to bob.testnet",
+ },
+ },
+ {
+ user: "{{user2}}",
+ content: {
+ text: "I'll send 1.5 NEAR now...",
+ action: "SEND_NEAR",
+ },
+ },
+ {
+ user: "{{user2}}",
+ content: {
+ text: "Successfully sent 1.5 NEAR to bob.testnet\nTransaction: ABC123XYZ",
+ },
+ },
+ ],
+ ] as ActionExample[][],
+} as Action;
diff --git a/packages/plugin-near/src/environment.ts b/packages/plugin-near/src/environment.ts
new file mode 100644
index 00000000000..cc511870276
--- /dev/null
+++ b/packages/plugin-near/src/environment.ts
@@ -0,0 +1,104 @@
+import { IAgentRuntime } from "@ai16z/eliza";
+import { z } from "zod";
+
+// Add ENV variable at the top
+let ENV: string = "testnet";
+
+export const nearEnvSchema = z.object({
+ NEAR_WALLET_SECRET_KEY: z.string().min(1, "Wallet secret key is required"),
+ NEAR_WALLET_PUBLIC_KEY: z.string().min(1, "Wallet public key is required"),
+ NEAR_ADDRESS: z.string().min(1, "Near address is required"),
+ SLIPPAGE: z.string().min(1, "Slippage is required"),
+ RPC_URL: z.string().min(1, "RPC URL is required"),
+ networkId: z.string(),
+ nodeUrl: z.string(),
+ walletUrl: z.string(),
+ WRAP_NEAR_CONTRACT_ID: z.string(),
+ REF_FI_CONTRACT_ID: z.string(),
+ REF_TOKEN_ID: z.string(),
+ indexerUrl: z.string(),
+ explorerUrl: z.string(),
+ REF_DCL_SWAP_CONTRACT_ID: z.string(),
+});
+
+export type NearConfig = z.infer;
+
+export function getConfig(
+ env: string | undefined | null = ENV ||
+ process.env.NEAR_ENV ||
+ process.env.REACT_APP_REF_SDK_ENV
+) {
+ ENV = env || "testnet";
+ switch (env) {
+ case 'mainnet':
+ return {
+ networkId: 'mainnet',
+ nodeUrl: 'https://rpc.mainnet.near.org',
+ walletUrl: 'https://wallet.near.org',
+ WRAP_NEAR_CONTRACT_ID: 'wrap.near',
+ REF_FI_CONTRACT_ID: 'v2.ref-finance.near',
+ REF_TOKEN_ID: 'token.v2.ref-finance.near',
+ indexerUrl: 'https://indexer.ref.finance',
+ explorerUrl: 'https://testnet.nearblocks.io',
+ REF_DCL_SWAP_CONTRACT_ID: 'dclv2.ref-labs.near',
+ };
+ case 'testnet':
+ return {
+ networkId: 'testnet',
+ nodeUrl: 'https://rpc.testnet.near.org',
+ walletUrl: 'https://wallet.testnet.near.org',
+ indexerUrl: 'https://testnet-indexer.ref-finance.com',
+ WRAP_NEAR_CONTRACT_ID: 'wrap.testnet',
+ REF_FI_CONTRACT_ID: 'ref-finance-101.testnet',
+ REF_TOKEN_ID: 'ref.fakes.testnet',
+ explorerUrl: 'https://testnet.nearblocks.io',
+ REF_DCL_SWAP_CONTRACT_ID: 'dclv2.ref-dev.testnet',
+ };
+ default:
+ return {
+ networkId: 'mainnet',
+ nodeUrl: 'https://rpc.mainnet.near.org',
+ walletUrl: 'https://wallet.near.org',
+ REF_FI_CONTRACT_ID: 'v2.ref-finance.near',
+ WRAP_NEAR_CONTRACT_ID: 'wrap.near',
+ REF_TOKEN_ID: 'token.v2.ref-finance.near',
+ indexerUrl: 'https://indexer.ref.finance',
+ explorerUrl: 'https://nearblocks.io',
+ REF_DCL_SWAP_CONTRACT_ID: 'dclv2.ref-labs.near',
+ };
+ }
+}
+
+export async function validateNearConfig(
+ runtime: IAgentRuntime
+): Promise {
+ try {
+ const envConfig = getConfig(runtime.getSetting("NEAR_ENV") ?? undefined);
+ const config = {
+ NEAR_WALLET_SECRET_KEY:
+ runtime.getSetting("NEAR_WALLET_SECRET_KEY") ||
+ process.env.NEAR_WALLET_SECRET_KEY,
+ NEAR_WALLET_PUBLIC_KEY:
+ runtime.getSetting("NEAR_PUBLIC_KEY") ||
+ runtime.getSetting("NEAR_WALLET_PUBLIC_KEY") ||
+ process.env.NEAR_WALLET_PUBLIC_KEY,
+ NEAR_ADDRESS:
+ runtime.getSetting("NEAR_ADDRESS") || process.env.NEAR_ADDRESS,
+ SLIPPAGE: runtime.getSetting("SLIPPAGE") || process.env.SLIPPAGE,
+ RPC_URL: runtime.getSetting("RPC_URL") || process.env.RPC_URL,
+ ...envConfig // Spread the environment-specific config
+ };
+
+ return nearEnvSchema.parse(config);
+ } catch (error) {
+ if (error instanceof z.ZodError) {
+ const errorMessages = error.errors
+ .map((err) => `${err.path.join(".")}: ${err.message}`)
+ .join("\n");
+ throw new Error(
+ `Near configuration validation failed:\n${errorMessages}`
+ );
+ }
+ throw error;
+ }
+}
diff --git a/packages/plugin-near/src/index.ts b/packages/plugin-near/src/index.ts
new file mode 100644
index 00000000000..6c57aa5ad34
--- /dev/null
+++ b/packages/plugin-near/src/index.ts
@@ -0,0 +1,15 @@
+import { Plugin } from "@ai16z/eliza/src/types";
+import { walletProvider } from "./providers/wallet";
+// import { executeCreateToken } from "./actions/createToken";
+import { executeSwap } from "./actions/swap";
+import { executeTransfer } from './actions/transfer';
+
+export const nearPlugin: Plugin = {
+ name: "NEAR",
+ description: "Near Protocol Plugin for Eliza",
+ providers: [walletProvider],
+ actions: [executeSwap, executeTransfer],
+ evaluators: [],
+};
+
+export default nearPlugin;
diff --git a/packages/plugin-near/src/providers/wallet.ts b/packages/plugin-near/src/providers/wallet.ts
new file mode 100644
index 00000000000..a5fdb149c28
--- /dev/null
+++ b/packages/plugin-near/src/providers/wallet.ts
@@ -0,0 +1,226 @@
+import { IAgentRuntime, Memory, Provider, State } from "@ai16z/eliza";
+import { KeyPair, keyStores, connect, Account, utils } from "near-api-js";
+import BigNumber from "bignumber.js";
+import { KeyPairString } from "near-api-js/lib/utils";
+import NodeCache from "node-cache";
+
+const PROVIDER_CONFIG = {
+ networkId: process.env.NEAR_NETWORK || "testnet",
+ nodeUrl: process.env.RPC_URL || `https://rpc.${process.env.NEAR_NETWORK || "testnet"}.near.org`,
+ walletUrl: `https://${process.env.NEAR_NETWORK || "testnet"}.mynearwallet.com/`,
+ helperUrl: `https://helper.${process.env.NEAR_NETWORK || "testnet"}.near.org`,
+ explorerUrl: `https://${process.env.NEAR_NETWORK || "testnet"}.nearblocks.io`,
+ MAX_RETRIES: 3,
+ RETRY_DELAY: 2000,
+ SLIPPAGE: process.env.SLIPPAGE ? parseInt(process.env.SLIPPAGE) : 1,
+};
+
+export interface NearToken {
+ name: string;
+ symbol: string;
+ decimals: number;
+ balance: string;
+ uiAmount: string;
+ priceUsd: string;
+ valueUsd: string;
+ valueNear?: string;
+}
+
+interface WalletPortfolio {
+ totalUsd: string;
+ totalNear?: string;
+ tokens: Array;
+}
+
+export class WalletProvider implements Provider {
+ private cache: NodeCache;
+ private account: Account | null = null;
+ private keyStore: keyStores.InMemoryKeyStore;
+ constructor(private accountId: string) {
+ this.cache = new NodeCache({ stdTTL: 300 }); // Cache TTL set to 5 minutes
+ this.keyStore = new keyStores.InMemoryKeyStore();
+ }
+
+ async get(
+ runtime: IAgentRuntime,
+ _message: Memory,
+ _state?: State
+ ): Promise {
+ try {
+ return await this.getFormattedPortfolio(runtime);
+ } catch (error) {
+ console.error("Error in wallet provider:", error);
+ return null;
+ }
+ }
+
+ public async connect(runtime: IAgentRuntime) {
+ if (this.account) return this.account;
+
+ const secretKey = runtime.getSetting("NEAR_WALLET_SECRET_KEY");
+ const publicKey = runtime.getSetting("NEAR_WALLET_PUBLIC_KEY");
+
+ if (!secretKey || !publicKey) {
+ throw new Error("NEAR wallet credentials not configured");
+ }
+
+ // Create KeyPair from secret key
+ const keyPair = KeyPair.fromString(secretKey as KeyPairString);
+
+ // Set the key in the keystore
+ await this.keyStore.setKey(PROVIDER_CONFIG.networkId, this.accountId, keyPair);
+
+ const nearConnection = await connect({
+ networkId: PROVIDER_CONFIG.networkId,
+ keyStore: this.keyStore,
+ nodeUrl: PROVIDER_CONFIG.nodeUrl,
+ walletUrl: PROVIDER_CONFIG.walletUrl,
+ helperUrl: PROVIDER_CONFIG.helperUrl,
+ });
+
+ this.account = await nearConnection.account(this.accountId);
+ return this.account;
+ }
+
+ private async fetchWithRetry(
+ url: string,
+ options: RequestInit = {}
+ ): Promise {
+ let lastError: Error;
+
+ for (let i = 0; i < PROVIDER_CONFIG.MAX_RETRIES; i++) {
+ try {
+ const response = await fetch(url, options);
+ if (!response.ok) {
+ throw new Error(`HTTP error! status: ${response.status}`);
+ }
+ return await response.json();
+ } catch (error) {
+ console.error(`Attempt ${i + 1} failed:`, error);
+ lastError = error as Error;
+ if (i < PROVIDER_CONFIG.MAX_RETRIES - 1) {
+ await new Promise(resolve =>
+ setTimeout(resolve, PROVIDER_CONFIG.RETRY_DELAY * Math.pow(2, i))
+ );
+ }
+ }
+ }
+ throw lastError!;
+ }
+
+ async fetchPortfolioValue(runtime: IAgentRuntime): Promise {
+ try {
+ const cacheKey = `portfolio-${this.accountId}`;
+ const cachedValue = this.cache.get(cacheKey);
+
+ if (cachedValue) {
+ console.log("Cache hit for fetchPortfolioValue");
+ return cachedValue;
+ }
+
+ const account = await this.connect(runtime);
+ const balance = await account.getAccountBalance();
+
+ // Convert yoctoNEAR to NEAR
+ const nearBalance = utils.format.formatNearAmount(balance.available);
+
+ // Fetch NEAR price in USD
+ const nearPrice = await this.fetchNearPrice();
+ const valueUsd = new BigNumber(nearBalance).times(nearPrice);
+
+ const portfolio: WalletPortfolio = {
+ totalUsd: valueUsd.toString(),
+ totalNear: nearBalance,
+ tokens: [{
+ name: "NEAR Protocol",
+ symbol: "NEAR",
+ decimals: 24,
+ balance: balance.available,
+ uiAmount: nearBalance,
+ priceUsd: nearPrice.toString(),
+ valueUsd: valueUsd.toString(),
+ }]
+ };
+
+ this.cache.set(cacheKey, portfolio);
+ return portfolio;
+ } catch (error) {
+ console.error("Error fetching portfolio:", error);
+ throw error;
+ }
+ }
+
+ private async fetchNearPrice(): Promise {
+ const cacheKey = "near-price";
+ const cachedPrice = this.cache.get(cacheKey);
+
+ if (cachedPrice) {
+ return cachedPrice;
+ }
+
+ try {
+ const response = await this.fetchWithRetry(
+ "https://api.coingecko.com/api/v3/simple/price?ids=near&vs_currencies=usd"
+ );
+ const price = response.near.usd;
+ this.cache.set(cacheKey, price);
+ return price;
+ } catch (error) {
+ console.error("Error fetching NEAR price:", error);
+ return 0;
+ }
+ }
+
+ formatPortfolio(runtime: IAgentRuntime, portfolio: WalletPortfolio): string {
+ let output = `${runtime.character.system}\n`;
+ output += `Account ID: ${this.accountId}\n\n`;
+
+ const totalUsdFormatted = new BigNumber(portfolio.totalUsd).toFixed(2);
+ const totalNearFormatted = portfolio.totalNear;
+
+ output += `Total Value: $${totalUsdFormatted} (${totalNearFormatted} NEAR)\n\n`;
+ output += "Token Balances:\n";
+
+ for (const token of portfolio.tokens) {
+ output += `${token.name} (${token.symbol}): ${token.uiAmount} ($${new BigNumber(token.valueUsd).toFixed(2)})\n`;
+ }
+
+ output += "\nMarket Prices:\n";
+ output += `NEAR: $${new BigNumber(portfolio.tokens[0].priceUsd).toFixed(2)}\n`;
+
+ return output;
+ }
+
+ async getFormattedPortfolio(runtime: IAgentRuntime): Promise {
+ try {
+ const portfolio = await this.fetchPortfolioValue(runtime);
+ return this.formatPortfolio(runtime, portfolio);
+ } catch (error) {
+ console.error("Error generating portfolio report:", error);
+ return "Unable to fetch wallet information. Please try again later.";
+ }
+ }
+}
+
+const walletProvider: Provider = {
+ get: async (
+ runtime: IAgentRuntime,
+ _message: Memory,
+ _state?: State
+ ): Promise => {
+ try {
+ const accountId = runtime.getSetting("NEAR_ADDRESS");
+ if (!accountId) {
+ throw new Error("NEAR_ADDRESS not configured");
+ }
+ const provider = new WalletProvider(accountId);
+ return await provider.getFormattedPortfolio(runtime);
+ } catch (error) {
+ console.error("Error in wallet provider:", error);
+ return null;
+ }
+ },
+};
+
+
+export { walletProvider };
\ No newline at end of file
diff --git a/packages/plugin-near/tsconfig.json b/packages/plugin-near/tsconfig.json
new file mode 100644
index 00000000000..95cbe371aca
--- /dev/null
+++ b/packages/plugin-near/tsconfig.json
@@ -0,0 +1,21 @@
+{
+ "compilerOptions": {
+ "target": "ES2020",
+ "module": "ESNext",
+ "moduleResolution": "node",
+ "outDir": "dist",
+ "rootDir": "./src",
+ "strict": true,
+ "esModuleInterop": true,
+ "skipLibCheck": true,
+ "forceConsistentCasingInFileNames": true,
+ "typeRoots": [
+ "./node_modules/@types",
+ "./src/types"
+ ],
+ "declaration": true
+ },
+ "include": [
+ "src"
+ ]
+}
\ No newline at end of file
diff --git a/packages/plugin-near/tsup.config.ts b/packages/plugin-near/tsup.config.ts
new file mode 100644
index 00000000000..96d9c237335
--- /dev/null
+++ b/packages/plugin-near/tsup.config.ts
@@ -0,0 +1,12 @@
+import { defineConfig } from "tsup";
+
+export default defineConfig({
+ entry: ["src/index.ts"],
+ format: ["esm", "cjs"],
+ dts: true,
+ splitting: false,
+ sourcemap: true,
+ clean: true,
+ shims: true,
+ treeshake: true
+});
diff --git a/packages/plugin-nft-generation/.npmignore b/packages/plugin-nft-generation/.npmignore
new file mode 100644
index 00000000000..078562eceab
--- /dev/null
+++ b/packages/plugin-nft-generation/.npmignore
@@ -0,0 +1,6 @@
+*
+
+!dist/**
+!package.json
+!readme.md
+!tsup.config.ts
\ No newline at end of file
diff --git a/packages/plugin-nft-generation/eslint.config.mjs b/packages/plugin-nft-generation/eslint.config.mjs
new file mode 100644
index 00000000000..92fe5bbebef
--- /dev/null
+++ b/packages/plugin-nft-generation/eslint.config.mjs
@@ -0,0 +1,3 @@
+import eslintGlobalConfig from "../../eslint.config.mjs";
+
+export default [...eslintGlobalConfig];
diff --git a/packages/plugin-nft-generation/package.json b/packages/plugin-nft-generation/package.json
new file mode 100644
index 00000000000..bea518c9aa6
--- /dev/null
+++ b/packages/plugin-nft-generation/package.json
@@ -0,0 +1,30 @@
+{
+ "name": "@ai16z/plugin-nft-generation",
+ "version": "0.1.5-alpha.5",
+ "main": "dist/index.js",
+ "type": "module",
+ "types": "dist/index.d.ts",
+ "dependencies": {
+ "@ai16z/eliza": "workspace:*",
+ "@ai16z/plugin-image-generation": "workspace:*",
+ "@ai16z/plugin-node": "workspace:*",
+ "@metaplex-foundation/mpl-token-metadata": "^3.3.0",
+ "@metaplex-foundation/mpl-toolbox": "^0.9.4",
+ "@metaplex-foundation/umi": "^0.9.2",
+ "@metaplex-foundation/umi-bundle-defaults": "^0.9.2",
+ "@solana-developers/helpers": "^2.5.6",
+ "@solana/web3.js": "1.95.5",
+ "bs58": "6.0.0",
+ "express": "4.21.1",
+ "node-cache": "5.1.2",
+ "tsup": "8.3.5"
+ },
+ "scripts": {
+ "build": "tsup --format esm --dts",
+ "dev": "tsup --format esm --dts --watch",
+ "lint": "eslint . --fix"
+ },
+ "peerDependencies": {
+ "whatwg-url": "7.1.0"
+ }
+}
diff --git a/packages/plugin-nft-generation/src/api.ts b/packages/plugin-nft-generation/src/api.ts
new file mode 100644
index 00000000000..ed64d86cf34
--- /dev/null
+++ b/packages/plugin-nft-generation/src/api.ts
@@ -0,0 +1,164 @@
+import express from "express";
+
+import { AgentRuntime } from "@ai16z/eliza";
+import { createCollection } from "./handlers/createCollection.ts";
+import { createNFT, createNFTMetadata } from "./handlers/createNFT.ts";
+import { verifyNFT } from "./handlers/verifyNFT.ts";
+
+export function createNFTApiRouter(
+ agents: Map
+): express.Router {
+ const router = express.Router();
+
+ router.post(
+ "/api/nft-generation/create-collection",
+ async (req: express.Request, res: express.Response) => {
+ const agentId = req.body.agentId;
+ const fee = req.body.fee || 0;
+ const runtime = agents.get(agentId);
+ if (!runtime) {
+ res.status(404).send("Agent not found");
+ return;
+ }
+ try {
+ const collectionAddressRes = await createCollection({
+ runtime,
+ collectionName: runtime.character.name,
+ fee,
+ });
+
+ res.json({
+ success: true,
+ data: collectionAddressRes,
+ });
+ } catch (e: any) {
+ console.log(e);
+ res.json({
+ success: false,
+ data: JSON.stringify(e),
+ });
+ }
+ }
+ );
+
+ router.post(
+ "/api/nft-generation/create-nft-metadata",
+ async (req: express.Request, res: express.Response) => {
+ const agentId = req.body.agentId;
+ const collectionName = req.body.collectionName;
+ const collectionAddress = req.body.collectionAddress;
+ const collectionAdminPublicKey = req.body.collectionAdminPublicKey;
+ const collectionFee = req.body.collectionFee;
+ const tokenId = req.body.tokenId;
+ const runtime = agents.get(agentId);
+ if (!runtime) {
+ res.status(404).send("Agent not found");
+ return;
+ }
+
+ try {
+ const nftInfo = await createNFTMetadata({
+ runtime,
+ collectionName,
+ collectionAdminPublicKey,
+ collectionFee,
+ tokenId,
+ });
+
+ res.json({
+ success: true,
+ data: {
+ ...nftInfo,
+ collectionAddress,
+ },
+ });
+ } catch (e: any) {
+ console.log(e);
+ res.json({
+ success: false,
+ data: JSON.stringify(e),
+ });
+ }
+ }
+ );
+
+ router.post(
+ "/api/nft-generation/create-nft",
+ async (req: express.Request, res: express.Response) => {
+ const agentId = req.body.agentId;
+ const collectionName = req.body.collectionName;
+ const collectionAddress = req.body.collectionAddress;
+ const collectionAdminPublicKey = req.body.collectionAdminPublicKey;
+ const collectionFee = req.body.collectionFee;
+ const tokenId = req.body.tokenId;
+ const runtime = agents.get(agentId);
+ if (!runtime) {
+ res.status(404).send("Agent not found");
+ return;
+ }
+
+ try {
+ const nftRes = await createNFT({
+ runtime,
+ collectionName,
+ collectionAddress,
+ collectionAdminPublicKey,
+ collectionFee,
+ tokenId,
+ });
+
+ res.json({
+ success: true,
+ data: nftRes,
+ });
+ } catch (e: any) {
+ console.log(e);
+ res.json({
+ success: false,
+ data: JSON.stringify(e),
+ });
+ }
+ }
+ );
+
+ router.post(
+ "/api/nft-generation/verify-nft",
+ async (req: express.Request, res: express.Response) => {
+ const agentId = req.body.agentId;
+ const collectionAddress = req.body.collectionAddress;
+ const NFTAddress = req.body.nftAddress;
+ const token = req.body.token;
+
+ const runtime = agents.get(agentId);
+ if (!runtime) {
+ res.status(404).send("Agent not found");
+ return;
+ }
+ const verifyToken = runtime.getSetting("SOLANA_VERIFY_TOKEN");
+ if (token !== verifyToken) {
+ res.status(401).send(" Access denied for translation");
+ return;
+ }
+ try {
+ const { success } = await verifyNFT({
+ runtime,
+ collectionAddress,
+ NFTAddress,
+ });
+
+ res.json({
+ success: true,
+ data: success ? "verified" : "unverified",
+ });
+ } catch (e: any) {
+ console.log(e);
+ res.json({
+ success: false,
+ data: JSON.stringify(e),
+ });
+ }
+ }
+ );
+
+ return router;
+}
diff --git a/packages/plugin-nft-generation/src/handlers/createCollection.ts b/packages/plugin-nft-generation/src/handlers/createCollection.ts
new file mode 100644
index 00000000000..21960c2a2d0
--- /dev/null
+++ b/packages/plugin-nft-generation/src/handlers/createCollection.ts
@@ -0,0 +1,118 @@
+import { AwsS3Service } from "@ai16z/plugin-node";
+import {
+ composeContext,
+ elizaLogger,
+ generateImage,
+ getEmbeddingZeroVector,
+ IAgentRuntime,
+ Memory,
+ ServiceType,
+ stringToUuid,
+} from "@ai16z/eliza";
+import {
+ saveBase64Image,
+ saveHeuristImage,
+} from "@ai16z/plugin-image-generation";
+import { PublicKey } from "@solana/web3.js";
+import WalletSolana from "../provider/wallet/walletSolana.ts";
+
+const collectionImageTemplate = `
+Generate a logo with the text "{{collectionName}}", using orange as the main color, with a sci-fi and mysterious background theme
+`;
+
+export async function createCollection({
+ runtime,
+ collectionName,
+ fee,
+}: {
+ runtime: IAgentRuntime;
+ collectionName: string;
+ fee?: number;
+}) {
+ const userId = runtime.agentId;
+ elizaLogger.log("User ID:", userId);
+ const awsS3Service: AwsS3Service = runtime.getService(ServiceType.AWS_S3);
+ const agentName = runtime.character.name;
+ const roomId = stringToUuid("nft_generate_room-" + agentName);
+ // Create memory for the message
+ const memory: Memory = {
+ agentId: userId,
+ userId,
+ roomId,
+ content: {
+ text: "",
+
+ source: "nft-generator",
+ },
+ createdAt: Date.now(),
+ embedding: getEmbeddingZeroVector(),
+ };
+ const state = await runtime.composeState(memory, {
+ collectionName,
+ });
+
+ const prompt = composeContext({
+ state,
+ template: collectionImageTemplate,
+ });
+ const images = await generateImage(
+ {
+ prompt,
+ width: 300,
+ height: 300,
+ },
+ runtime
+ );
+ if (images.success && images.data && images.data.length > 0) {
+ const image = images.data[0];
+ const filename = `collection-image`;
+ if (image.startsWith("http")) {
+ elizaLogger.log("Generating image url:", image);
+ }
+ // Choose save function based on image data format
+ const filepath = image.startsWith("http")
+ ? await saveHeuristImage(image, filename)
+ : saveBase64Image(image, filename);
+
+ const logoPath = await awsS3Service.uploadFile(
+ filepath,
+ `/${collectionName}`,
+ false
+ );
+ const publicKey = runtime.getSetting("SOLANA_PUBLIC_KEY");
+ const privateKey = runtime.getSetting("SOLANA_PRIVATE_KEY");
+ const adminPublicKey = runtime.getSetting("SOLANA_ADMIN_PUBLIC_KEY");
+ const collectionInfo = {
+ name: `${collectionName}`,
+ symbol: `${collectionName.toUpperCase()[0]}`,
+ adminPublicKey,
+ fee: fee || 0,
+ uri: "",
+ };
+ const jsonFilePath = await awsS3Service.uploadJson(
+ {
+ name: collectionInfo.name,
+ description: `${collectionInfo.name}`,
+ image: logoPath.url,
+ },
+ "metadata.json",
+ `${collectionName}`
+ );
+ collectionInfo.uri = jsonFilePath.url;
+
+ const wallet = new WalletSolana(new PublicKey(publicKey), privateKey);
+
+ const collectionAddressRes = await wallet.createCollection({
+ ...collectionInfo,
+ });
+
+ return {
+ network: "solana",
+ address: collectionAddressRes.address,
+ link: collectionAddressRes.link,
+ collectionInfo,
+ };
+ }
+
+ return;
+}
diff --git a/packages/plugin-nft-generation/src/handlers/createNFT.ts b/packages/plugin-nft-generation/src/handlers/createNFT.ts
new file mode 100644
index 00000000000..281b444b168
--- /dev/null
+++ b/packages/plugin-nft-generation/src/handlers/createNFT.ts
@@ -0,0 +1,182 @@
+import { AwsS3Service } from "@ai16z/plugin-node";
+import {
+ composeContext,
+ elizaLogger,
+ generateImage,
+ generateText,
+ getEmbeddingZeroVector,
+ IAgentRuntime,
+ Memory,
+ ModelClass,
+ ServiceType,
+ stringToUuid,
+} from "@ai16z/eliza";
+import {
+ saveBase64Image,
+ saveHeuristImage,
+} from "@ai16z/plugin-image-generation";
+import { PublicKey } from "@solana/web3.js";
+import WalletSolana from "../provider/wallet/walletSolana.ts";
+
+const nftTemplate = `
+# Areas of Expertise
+{{knowledge}}
+
+# About {{agentName}} (@{{twitterUserName}}):
+{{bio}}
+{{lore}}
+{{topics}}
+
+{{providers}}
+
+{{characterPostExamples}}
+
+{{postDirections}}
+# Task: Generate an image to Prompt the {{agentName}}'s appearance, with the total character count MUST be less than 280.
+`;
+
+export async function createNFTMetadata({
+ runtime,
+ collectionName,
+ collectionAdminPublicKey,
+ collectionFee,
+ tokenId,
+}: {
+ runtime: IAgentRuntime;
+ collectionName: string;
+ collectionAdminPublicKey: string;
+ collectionFee: number;
+ tokenId: number;
+}) {
+ const userId = runtime.agentId;
+ elizaLogger.log("User ID:", userId);
+ const awsS3Service: AwsS3Service = runtime.getService(ServiceType.AWS_S3);
+ const agentName = runtime.character.name;
+ const roomId = stringToUuid("nft_generate_room-" + agentName);
+ // Create memory for the message
+ const memory: Memory = {
+ agentId: userId,
+ userId,
+ roomId,
+ content: {
+ text: "",
+ source: "nft-generator",
+ },
+ createdAt: Date.now(),
+ embedding: getEmbeddingZeroVector(),
+ };
+ const state = await runtime.composeState(memory, {
+ collectionName,
+ });
+
+ const context = composeContext({
+ state,
+ template: nftTemplate,
+ });
+
+ let nftPrompt = await generateText({
+ runtime,
+ context,
+ modelClass: ModelClass.MEDIUM,
+ });
+
+ nftPrompt += runtime.character?.nft?.prompt || "";
+ nftPrompt += "The image should only feature one person.";
+
+ const images = await generateImage(
+ {
+ prompt: nftPrompt,
+ width: 1024,
+ height: 1024,
+ },
+ runtime
+ );
+ elizaLogger.log("NFT Prompt:", nftPrompt);
+ if (images.success && images.data && images.data.length > 0) {
+ const image = images.data[0];
+ const filename = `${tokenId}`;
+ if (image.startsWith("http")) {
+ elizaLogger.log("Generating image url:", image);
+ }
+ // Choose save function based on image data format
+ const filepath = image.startsWith("http")
+ ? await saveHeuristImage(image, filename)
+ : saveBase64Image(image, filename);
+ const nftImage = await awsS3Service.uploadFile(
+ filepath,
+ `/${collectionName}/items/${tokenId}`,
+ false
+ );
+ const nftInfo = {
+ name: `${collectionName} #${tokenId}`,
+ description: `${collectionName} #${tokenId}`,
+ symbol: `#${tokenId}`,
+ adminPublicKey: collectionAdminPublicKey,
+ fee: collectionFee,
+ uri: "",
+ };
+ const jsonFilePath = await awsS3Service.uploadJson(
+ {
+ name: nftInfo.name,
+ description: nftInfo.description,
+ image: nftImage.url,
+ },
+ "metadata.json",
+ `/${collectionName}/items/${tokenId}`
+ );
+
+ nftInfo.uri = jsonFilePath.url;
+ return {
+ ...nftInfo,
+ imageUri: nftImage.url
+ };
+ }
+ return null;
+}
+
+export async function createNFT({
+ runtime,
+ collectionName,
+ collectionAddress,
+ collectionAdminPublicKey,
+ collectionFee,
+ tokenId,
+}: {
+ runtime: IAgentRuntime;
+ collectionName: string;
+ collectionAddress: string;
+ collectionAdminPublicKey: string;
+ collectionFee: number;
+ tokenId: number;
+}) {
+ const nftInfo = await createNFTMetadata({
+ runtime,
+ collectionName,
+ collectionAdminPublicKey,
+ collectionFee,
+ tokenId,
+ });
+ if (nftInfo) {
+ const publicKey = runtime.getSetting("SOLANA_PUBLIC_KEY");
+ const privateKey = runtime.getSetting("SOLANA_PRIVATE_KEY");
+
+ const wallet = new WalletSolana(new PublicKey(publicKey), privateKey);
+
+ const nftAddressRes = await wallet.mintNFT({
+ name: nftInfo.name,
+ uri: nftInfo.uri,
+ symbol: nftInfo.symbol,
+ collectionAddress,
+ adminPublicKey: collectionAdminPublicKey,
+ fee: collectionFee,
+ });
+ elizaLogger.log("NFT ID:", nftAddressRes.address);
+ return {
+ network: "solana",
+ address: nftAddressRes.address,
+ link: nftAddressRes.link,
+ nftInfo,
+ };
+ }
+ return;
+}
diff --git a/packages/plugin-nft-generation/src/handlers/verifyNFT.ts b/packages/plugin-nft-generation/src/handlers/verifyNFT.ts
new file mode 100644
index 00000000000..792c38ffc5e
--- /dev/null
+++ b/packages/plugin-nft-generation/src/handlers/verifyNFT.ts
@@ -0,0 +1,27 @@
+import { IAgentRuntime } from "@ai16z/eliza";
+import { PublicKey } from "@solana/web3.js";
+import WalletSolana from "../provider/wallet/walletSolana.ts";
+
+export async function verifyNFT({
+ runtime,
+ collectionAddress,
+ NFTAddress,
+}: {
+ runtime: IAgentRuntime;
+ collectionAddress: string;
+ NFTAddress: string;
+}) {
+ const adminPublicKey = runtime.getSetting("SOLANA_ADMIN_PUBLIC_KEY");
+ const adminPrivateKey = runtime.getSetting("SOLANA_ADMIN_PRIVATE_KEY");
+ const adminWallet = new WalletSolana(
+ new PublicKey(adminPublicKey),
+ adminPrivateKey
+ );
+ await adminWallet.verifyNft({
+ collectionAddress,
+ nftAddress: NFTAddress,
+ });
+ return {
+ success: true,
+ };
+}
diff --git a/packages/plugin-nft-generation/src/index.ts b/packages/plugin-nft-generation/src/index.ts
new file mode 100644
index 00000000000..f5f442b97ee
--- /dev/null
+++ b/packages/plugin-nft-generation/src/index.ts
@@ -0,0 +1,203 @@
+import {
+ Action,
+ elizaLogger,
+ HandlerCallback,
+ IAgentRuntime,
+ Memory,
+ Plugin,
+ State,
+} from "@ai16z/eliza";
+
+import { createCollection } from "./handlers/createCollection.ts";
+import { createNFT } from "./handlers/createNFT.ts";
+import { verifyNFT } from "./handlers/verifyNFT.ts";
+
+export * from "./provider/wallet/walletSolana.ts";
+export * from "./api.ts";
+
+
+export async function sleep(ms: number = 3000) {
+ return new Promise((resolve) => {
+ setTimeout(resolve, ms);
+ });
+}
+
+const nftCollectionGeneration: Action = {
+ name: "GENERATE_COLLECTION",
+ similes: [
+ "COLLECTION_GENERATION",
+ "COLLECTION_GEN",
+ "CREATE_COLLECTION",
+ "MAKE_COLLECTION",
+ "GENERATE_COLLECTION",
+ ],
+ description: "Generate an NFT collection for the message",
+ validate: async (runtime: IAgentRuntime, _message: Memory) => {
+ const AwsAccessKeyIdOk = !!runtime.getSetting("AWS_ACCESS_KEY_ID");
+ const AwsSecretAccessKeyOk = !!runtime.getSetting(
+ "AWS_SECRET_ACCESS_KEY"
+ );
+ const AwsRegionOk = !!runtime.getSetting("AWS_REGION");
+ const AwsS3BucketOk = !!runtime.getSetting("AWS_S3_BUCKET");
+
+ return (
+ AwsAccessKeyIdOk ||
+ AwsSecretAccessKeyOk ||
+ AwsRegionOk ||
+ AwsS3BucketOk
+ );
+ },
+ handler: async (
+ runtime: IAgentRuntime,
+ message: Memory,
+ state: State,
+ options: { [key: string]: unknown },
+ callback: HandlerCallback
+ ) => {
+ try {
+ elizaLogger.log("Composing state for message:", message);
+ const userId = runtime.agentId;
+ elizaLogger.log("User ID:", userId);
+
+ const collectionAddressRes = await createCollection({
+ runtime,
+ collectionName: runtime.character.name,
+ });
+
+ const collectionInfo = collectionAddressRes.collectionInfo;
+
+ elizaLogger.log("Collection Address:", collectionAddressRes);
+
+ const nftRes = await createNFT({
+ runtime,
+ collectionName: collectionInfo.name,
+ collectionAddress: collectionAddressRes.address,
+ collectionAdminPublicKey: collectionInfo.adminPublicKey,
+ collectionFee: collectionInfo.fee,
+ tokenId: 1,
+ });
+
+ elizaLogger.log("NFT Address:", nftRes);
+
+
+ callback({
+ text: `Congratulations to you! 🎉🎉🎉 \nCollection : ${collectionAddressRes.link}\n NFT: ${nftRes.link}`, //caption.description,
+ attachments: [],
+ });
+ await sleep(15000);
+ await verifyNFT({
+ runtime,
+ collectionAddress: collectionAddressRes.address,
+ NFTAddress: nftRes.address,
+ });
+ return [];
+ } catch (e: any) {
+ console.log(e);
+ }
+
+ // callback();
+ },
+ examples: [
+ // TODO: We want to generate images in more abstract ways, not just when asked to generate an image
+
+ [
+ {
+ user: "{{user1}}",
+ content: { text: "Generate a collection" },
+ },
+ {
+ user: "{{agentName}}",
+ content: {
+ text: "Here's the collection you requested.",
+ action: "GENERATE_COLLECTION",
+ },
+ },
+ ],
+ [
+ {
+ user: "{{user1}}",
+ content: { text: "Generate a collection using {{agentName}}" },
+ },
+ {
+ user: "{{agentName}}",
+ content: {
+ text: "We've successfully created a collection.",
+ action: "GENERATE_COLLECTION",
+ },
+ },
+ ],
+ [
+ {
+ user: "{{user1}}",
+ content: { text: "Create a collection using {{agentName}}" },
+ },
+ {
+ user: "{{agentName}}",
+ content: {
+ text: "Here's the collection you requested.",
+ action: "GENERATE_COLLECTION",
+ },
+ },
+ ],
+ [
+ {
+ user: "{{user1}}",
+ content: { text: "Build a Collection" },
+ },
+ {
+ user: "{{agentName}}",
+ content: {
+ text: "The collection has been successfully built.",
+ action: "GENERATE_COLLECTION",
+ },
+ },
+ ],
+ [
+ {
+ user: "{{user1}}",
+ content: { text: "Assemble a collection with {{agentName}}" },
+ },
+ {
+ user: "{{agentName}}",
+ content: {
+ text: "The collection has been assembled",
+ action: "GENERATE_COLLECTION",
+ },
+ },
+ ],
+ [
+ {
+ user: "{{user1}}",
+ content: { text: "Make a collection" },
+ },
+ {
+ user: "{{agentName}}",
+ content: {
+ text: "The collection has been produced successfully.",
+ action: "GENERATE_COLLECTION",
+ },
+ },
+ ],
+ [
+ {
+ user: "{{user1}}",
+ content: { text: "Compile a collection" },
+ },
+ {
+ user: "{{agentName}}",
+ content: {
+ text: "The collection has been compiled.",
+ action: "GENERATE_COLLECTION",
+ },
+ },
+ ],
+ ],
+} as Action;
+
+export const nftGenerationPlugin: Plugin = {
+ name: "nftCollectionGeneration",
+ description: "Generate NFT Collections",
+ actions: [nftCollectionGeneration],
+ evaluators: [],
+ providers: [],
+};
diff --git a/packages/plugin-nft-generation/src/provider/wallet/walletSolana.ts b/packages/plugin-nft-generation/src/provider/wallet/walletSolana.ts
new file mode 100644
index 00000000000..98b2bee2330
--- /dev/null
+++ b/packages/plugin-nft-generation/src/provider/wallet/walletSolana.ts
@@ -0,0 +1,250 @@
+import NodeCache from "node-cache";
+import {
+ Cluster,
+ clusterApiUrl,
+ Connection,
+ LAMPORTS_PER_SOL,
+ PublicKey,
+} from "@solana/web3.js";
+import {
+ createNft,
+ findMetadataPda,
+ mplTokenMetadata,
+ updateV1,
+ verifyCollectionV1,
+} from "@metaplex-foundation/mpl-token-metadata";
+import { createUmi } from "@metaplex-foundation/umi-bundle-defaults";
+import {
+ generateSigner,
+ keypairIdentity,
+ percentAmount,
+ publicKey,
+ sol,
+ TransactionBuilder,
+ Umi,
+} from "@metaplex-foundation/umi";
+import { getExplorerLink } from "@solana-developers/helpers";
+import { transferSol } from "@metaplex-foundation/mpl-toolbox";
+import bs58 from "bs58";
+import { elizaLogger } from "@ai16z/eliza";
+
+export class WalletSolana {
+ private cache: NodeCache;
+ private umi: Umi;
+ private cluster: Cluster;
+
+ constructor(
+ private walletPublicKey: PublicKey,
+ private walletPrivateKeyKey: string,
+ private connection?: Connection
+ ) {
+ this.cache = new NodeCache({ stdTTL: 300 }); // Cache TTL set to 5 minutes
+
+ if (!connection) {
+ this.cluster = (process.env.SOLANA_CLUSTER as Cluster) || "devnet";
+ this.connection = new Connection(clusterApiUrl(this.cluster), {
+ commitment: "finalized",
+ });
+ }
+ const umi = createUmi(this.connection.rpcEndpoint);
+ umi.use(mplTokenMetadata());
+ const umiUser = umi.eddsa.createKeypairFromSecretKey(
+ this.privateKeyUint8Array
+ );
+ umi.use(keypairIdentity(umiUser));
+ this.umi = umi;
+ }
+
+ async getBalance() {
+ let balance = await this.connection.getBalance(this.walletPublicKey);
+ return {
+ value: balance,
+ formater: `${balance / LAMPORTS_PER_SOL} SOL`,
+ };
+ }
+
+ get privateKeyUint8Array() {
+ return bs58.decode(this.walletPrivateKeyKey);
+ }
+
+ async createCollection({
+ name,
+ symbol,
+ adminPublicKey,
+ uri,
+ fee,
+ }: {
+ name: string;
+ symbol: string;
+ adminPublicKey: string;
+ uri: string;
+ fee: number;
+ }): Promise<{
+ success: boolean;
+ link: string;
+ address: string;
+ error?: string | null;
+ }> {
+ try {
+ const collectionMint = generateSigner(this.umi);
+ let transaction = new TransactionBuilder();
+ const info = {
+ name,
+ symbol,
+ uri,
+ };
+ transaction = transaction.add(
+ createNft(this.umi, {
+ ...info,
+ mint: collectionMint,
+ sellerFeeBasisPoints: percentAmount(fee),
+ isCollection: true,
+ })
+ );
+
+ transaction = transaction.add(
+ updateV1(this.umi, {
+ mint: collectionMint.publicKey,
+ newUpdateAuthority: publicKey(adminPublicKey), // updateAuthority's public key
+ })
+ );
+
+ await transaction.sendAndConfirm(this.umi, {
+ confirm: {},
+ });
+
+ const address = collectionMint.publicKey;
+ return {
+ success: true,
+ link: getExplorerLink("address", address, this.cluster),
+ address,
+ error: null,
+ };
+ } catch (e) {
+ return {
+ success: false,
+ link: "",
+ address: "",
+ error: e.message,
+ };
+ }
+ }
+
+ async mintNFT({
+ collectionAddress,
+ adminPublicKey,
+ name,
+ symbol,
+ uri,
+ fee,
+ }: {
+ collectionAddress: string;
+ adminPublicKey: string;
+ name: string;
+ symbol: string;
+ uri: string;
+ fee: number;
+ }): Promise<{
+ success: boolean;
+ link: string;
+ address: string;
+ error?: string | null;
+ }> {
+ try {
+ const umi = this.umi;
+ const mint = generateSigner(umi);
+
+ let transaction = new TransactionBuilder();
+ elizaLogger.log("collection address", collectionAddress);
+ const collectionAddressKey = publicKey(collectionAddress);
+
+ const info = {
+ name,
+ uri,
+ symbol,
+ };
+ transaction = transaction.add(
+ createNft(umi, {
+ mint,
+ ...info,
+ sellerFeeBasisPoints: percentAmount(fee),
+ collection: {
+ key: collectionAddressKey,
+ verified: false,
+ },
+ })
+ );
+
+ transaction = transaction.add(
+ updateV1(umi, {
+ mint: mint.publicKey,
+ newUpdateAuthority: publicKey(adminPublicKey), // updateAuthority's public key
+ })
+ );
+
+ await transaction.sendAndConfirm(umi);
+
+ const address = mint.publicKey;
+ return {
+ success: true,
+ link: getExplorerLink("address", address, this.cluster),
+ address,
+ error: null,
+ };
+ } catch (e) {
+ return {
+ success: false,
+ link: "",
+ address: "",
+ error: e.message,
+ };
+ }
+ }
+
+ async verifyNft({
+ collectionAddress,
+ nftAddress,
+ }: {
+ collectionAddress: string;
+ nftAddress: string;
+ }): Promise<{
+ isVerified: boolean;
+ error: string | null;
+ }> {
+ try {
+ const umi = this.umi;
+ const collectionAddressKey = publicKey(collectionAddress);
+ const nftAddressKey = publicKey(nftAddress);
+
+ let transaction = new TransactionBuilder();
+ transaction = transaction.add(
+ verifyCollectionV1(umi, {
+ metadata: findMetadataPda(umi, { mint: nftAddressKey }),
+ collectionMint: collectionAddressKey,
+ authority: umi.identity,
+ })
+ );
+
+ await transaction.sendAndConfirm(umi);
+
+ elizaLogger.log(
+ `✅ NFT ${nftAddress} verified as member of collection ${collectionAddress}! See Explorer at ${getExplorerLink(
+ "address",
+ nftAddress,
+ this.cluster
+ )}`
+ );
+ return {
+ isVerified: true,
+ error: null,
+ };
+ } catch (e) {
+ return {
+ isVerified: false,
+ error: e.message,
+ };
+ }
+ }
+}
+
+export default WalletSolana;
diff --git a/packages/plugin-nft-generation/tsconfig.json b/packages/plugin-nft-generation/tsconfig.json
new file mode 100644
index 00000000000..834c4dce269
--- /dev/null
+++ b/packages/plugin-nft-generation/tsconfig.json
@@ -0,0 +1,13 @@
+{
+ "extends": "../core/tsconfig.json",
+ "compilerOptions": {
+ "outDir": "dist",
+ "rootDir": "src",
+ "types": [
+ "node"
+ ]
+ },
+ "include": [
+ "src/**/*.ts"
+ ]
+}
\ No newline at end of file
diff --git a/packages/plugin-nft-generation/tsup.config.ts b/packages/plugin-nft-generation/tsup.config.ts
new file mode 100644
index 00000000000..1a96f24afa1
--- /dev/null
+++ b/packages/plugin-nft-generation/tsup.config.ts
@@ -0,0 +1,21 @@
+import { defineConfig } from "tsup";
+
+export default defineConfig({
+ entry: ["src/index.ts"],
+ outDir: "dist",
+ sourcemap: true,
+ clean: true,
+ format: ["esm"], // Ensure you're targeting CommonJS
+ external: [
+ "dotenv", // Externalize dotenv to prevent bundling
+ "fs", // Externalize fs to use Node.js built-in module
+ "path", // Externalize other built-ins if necessary
+ "@reflink/reflink",
+ "@node-llama-cpp",
+ "https",
+ "http",
+ "agentkeepalive",
+ "safe-buffer",
+ // Add other modules you want to externalize
+ ],
+});
diff --git a/packages/plugin-node/package.json b/packages/plugin-node/package.json
index 630305c903d..1040e4a37b5 100644
--- a/packages/plugin-node/package.json
+++ b/packages/plugin-node/package.json
@@ -75,7 +75,7 @@
"scripts": {
"build": "tsup --format esm --dts",
"dev": "tsup --format esm --dts --watch",
- "lint": "eslint . --fix",
+ "lint": "eslint --fix --cache .",
"postinstall": "node scripts/postinstall.js"
},
"peerDependencies": {
diff --git a/packages/plugin-node/src/services/awsS3.ts b/packages/plugin-node/src/services/awsS3.ts
index 57600ada5bd..c8622d13ab0 100644
--- a/packages/plugin-node/src/services/awsS3.ts
+++ b/packages/plugin-node/src/services/awsS3.ts
@@ -63,6 +63,7 @@ export class AwsS3Service extends Service implements IAwsS3Service {
async uploadFile(
filePath: string,
+ subDirectory: string = '',
useSignedUrl: boolean = false,
expiresIn: number = 900
): Promise {
@@ -85,7 +86,7 @@ export class AwsS3Service extends Service implements IAwsS3Service {
const baseFileName = `${Date.now()}-${path.basename(filePath)}`;
// Determine storage path based on public access
- const fileName =`${this.fileUploadPath}/${baseFileName}`.replaceAll('//', '/');
+ const fileName =`${this.fileUploadPath}${subDirectory}/${baseFileName}`.replaceAll('//', '/');
// Set upload parameters
const uploadParams = {
Bucket: this.bucket,
diff --git a/packages/plugin-node/src/services/transcription.ts b/packages/plugin-node/src/services/transcription.ts
index 43bbf373607..35c8546a768 100644
--- a/packages/plugin-node/src/services/transcription.ts
+++ b/packages/plugin-node/src/services/transcription.ts
@@ -14,6 +14,7 @@ import os from "os";
import path from "path";
import { fileURLToPath } from "url";
import { promisify } from "util";
+import { createClient, DeepgramClient } from "@deepgram/sdk";
// const __dirname = path.dirname(new URL(import.meta.url).pathname); #compatibility issues with windows
const __filename = fileURLToPath(import.meta.url);
@@ -25,17 +26,23 @@ export class TranscriptionService
extends Service
implements ITranscriptionService
{
+ private runtime: IAgentRuntime | null = null;
static serviceType: ServiceType = ServiceType.TRANSCRIPTION;
private CONTENT_CACHE_DIR: string;
private DEBUG_AUDIO_DIR: string;
private TARGET_SAMPLE_RATE = 16000; // Common sample rate for speech recognition
private isCudaAvailable: boolean = false;
private openai: OpenAI | null = null;
+ private deepgram?: DeepgramClient;
private queue: { audioBuffer: ArrayBuffer; resolve: Function }[] = [];
private processing: boolean = false;
- async initialize(_runtime: IAgentRuntime): Promise {}
+ async initialize(_runtime: IAgentRuntime): Promise {
+ this.runtime = _runtime;
+ const deepgramKey = this.runtime.getSetting("DEEPGRAM_API_KEY");
+ this.deepgram = deepgramKey ? createClient(deepgramKey) : null;
+ }
constructor() {
super();
@@ -194,8 +201,9 @@ export class TranscriptionService
while (this.queue.length > 0) {
const { audioBuffer, resolve } = this.queue.shift()!;
let result: string | null = null;
-
- if (this.openai) {
+ if (this.deepgram) {
+ result = await this.transcribeWithDeepgram(audioBuffer);
+ } else if (this.openai) {
result = await this.transcribeWithOpenAI(audioBuffer);
} else {
result = await this.transcribeLocally(audioBuffer);
@@ -207,6 +215,23 @@ export class TranscriptionService
this.processing = false;
}
+ private async transcribeWithDeepgram(
+ audioBuffer: ArrayBuffer
+ ): Promise {
+ const buffer = Buffer.from(audioBuffer);
+ const response = await this.deepgram.listen.prerecorded.transcribeFile(
+ buffer,
+ {
+ model: "nova-2",
+ language: "en-US",
+ smart_format: true,
+ }
+ );
+ const result =
+ response.result.results.channels[0].alternatives[0].transcript;
+ return result;
+ }
+
private async transcribeWithOpenAI(
audioBuffer: ArrayBuffer
): Promise {
diff --git a/packages/plugin-solana/package.json b/packages/plugin-solana/package.json
index 2aa671fb7af..30930e41dd9 100644
--- a/packages/plugin-solana/package.json
+++ b/packages/plugin-solana/package.json
@@ -22,7 +22,7 @@
"scripts": {
"build": "tsup --format esm --dts",
"dev": "tsup --format esm --dts --watch",
- "lint": "eslint . --fix",
+ "lint": "eslint --fix --cache .",
"test": "vitest run"
},
"peerDependencies": {
diff --git a/packages/plugin-solana/src/actions/pumpfun.ts b/packages/plugin-solana/src/actions/pumpfun.ts
index 5cc1ebca18b..1299c0cb356 100644
--- a/packages/plugin-solana/src/actions/pumpfun.ts
+++ b/packages/plugin-solana/src/actions/pumpfun.ts
@@ -14,7 +14,7 @@ import {
Memory,
ModelClass,
State,
- generateObjectDEPRECATED,
+ generateObjectDeprecated,
composeContext,
type Action,
} from "@ai16z/eliza";
@@ -302,7 +302,7 @@ export default {
template: pumpfunTemplate,
});
- const content = await generateObjectDEPRECATED({
+ const content = await generateObjectDeprecated({
runtime,
context: pumpContext,
modelClass: ModelClass.LARGE,
diff --git a/packages/plugin-solana/src/actions/swap.ts b/packages/plugin-solana/src/actions/swap.ts
index 434e2cb816d..ffb56183c9d 100644
--- a/packages/plugin-solana/src/actions/swap.ts
+++ b/packages/plugin-solana/src/actions/swap.ts
@@ -1,28 +1,20 @@
-import {
- Connection,
- PublicKey,
- VersionedTransaction,
-} from "@solana/web3.js";
-import BigNumber from "bignumber.js";
-import { v4 as uuidv4 } from "uuid";
-import { TrustScoreDatabase } from "@ai16z/plugin-trustdb";
import {
ActionExample,
+ composeContext,
+ generateObjectDeprecated,
HandlerCallback,
IAgentRuntime,
Memory,
ModelClass,
+ settings,
State,
type Action,
- composeContext,
- generateObjectDEPRECATED,
- settings,
} from "@ai16z/eliza";
-import { TokenProvider } from "../providers/token.ts";
-import { TrustScoreManager } from "../providers/trustScoreProvider.ts";
+import { Connection, PublicKey, VersionedTransaction } from "@solana/web3.js";
+import BigNumber from "bignumber.js";
+import { getWalletKey } from "../keypairUtils.ts";
import { walletProvider, WalletProvider } from "../providers/wallet.ts";
import { getTokenDecimals } from "./swapUtils.ts";
-import { getWalletKey } from "../keypairUtils.ts";
async function swapToken(
connection: Connection,
@@ -206,15 +198,14 @@ export const executeSwap: Action = {
template: swapTemplate,
});
- const response = await generateObjectDEPRECATED({
+ const response = await generateObjectDeprecated({
runtime,
context: swapContext,
modelClass: ModelClass.LARGE,
});
console.log("Response:", response);
- const type =
- response.inputTokenSymbol?.toUpperCase() === "SOL" ? "buy" : "sell";
+ // const type = response.inputTokenSymbol?.toUpperCase() === "SOL" ? "buy" : "sell";
// Add SOL handling logic
if (response.inputTokenSymbol?.toUpperCase() === "SOL") {
@@ -295,7 +286,7 @@ export const executeSwap: Action = {
false
);
- const provider = new WalletProvider(connection, walletPublicKey);
+ // const provider = new WalletProvider(connection, walletPublicKey);
console.log("Wallet Public Key:", walletPublicKey);
console.log("inputTokenSymbol:", response.inputTokenCA);
@@ -366,81 +357,6 @@ export const executeSwap: Action = {
);
}
- if (type === "buy") {
- const tokenProvider = new TokenProvider(
- response.outputTokenCA,
- provider,
- runtime.cacheManager
- );
- const module = await import("better-sqlite3");
- const Database = module.default;
- const trustScoreDb = new TrustScoreDatabase(
- new Database(":memory:")
- );
- // add or get recommender
- const uuid = uuidv4();
- const recommender = await trustScoreDb.getOrCreateRecommender({
- id: uuid,
- address: walletPublicKey.toString(),
- solanaPubkey: walletPublicKey.toString(),
- });
-
- const trustScoreDatabase = new TrustScoreManager(
- runtime,
- tokenProvider,
- trustScoreDb
- );
- // save the trade
- const tradeData = {
- buy_amount: response.amount,
- is_simulation: false,
- };
- await trustScoreDatabase.createTradePerformance(
- runtime,
- response.outputTokenCA,
- recommender.id,
- tradeData
- );
- } else if (type === "sell") {
- const tokenProvider = new TokenProvider(
- response.inputTokenCA,
- provider,
- runtime.cacheManager
- );
- const module = await import("better-sqlite3");
- const Database = module.default;
- const trustScoreDb = new TrustScoreDatabase(
- new Database(":memory:")
- );
- // add or get recommender
- const uuid = uuidv4();
- const recommender = await trustScoreDb.getOrCreateRecommender({
- id: uuid,
- address: walletPublicKey.toString(),
- solanaPubkey: walletPublicKey.toString(),
- });
-
- const trustScoreDatabase = new TrustScoreManager(
- runtime,
- tokenProvider,
- trustScoreDb
- );
- // save the trade
- const sellDetails = {
- sell_amount: response.amount,
- sell_recommender_id: recommender.id,
- };
- const sellTimeStamp = new Date().getTime().toString();
- await trustScoreDatabase.updateSellDetails(
- runtime,
- response.inputTokenCA,
- recommender.id,
- sellTimeStamp,
- sellDetails,
- false
- );
- }
-
console.log("Swap completed successfully!");
console.log(`Transaction ID: ${txid}`);
diff --git a/packages/plugin-solana/src/actions/transfer.ts b/packages/plugin-solana/src/actions/transfer.ts
index b8b0d12aecf..12e9a49eb1e 100644
--- a/packages/plugin-solana/src/actions/transfer.ts
+++ b/packages/plugin-solana/src/actions/transfer.ts
@@ -23,7 +23,7 @@ import {
} from "@ai16z/eliza";
import { composeContext } from "@ai16z/eliza";
import { getWalletKey } from "../keypairUtils";
-import { generateObjectDEPRECATED } from "@ai16z/eliza";
+import { generateObjectDeprecated } from "@ai16z/eliza";
export interface TransferContent extends Content {
tokenAddress: string;
@@ -118,7 +118,7 @@ export default {
});
// Generate transfer content
- const content = await generateObjectDEPRECATED({
+ const content = await generateObjectDeprecated({
runtime,
context: transferContext,
modelClass: ModelClass.LARGE,
diff --git a/packages/plugin-starknet/package.json b/packages/plugin-starknet/package.json
index 39a651ce02a..e8a2dc8a88d 100644
--- a/packages/plugin-starknet/package.json
+++ b/packages/plugin-starknet/package.json
@@ -19,7 +19,7 @@
"dev": "tsup --format esm --dts --watch",
"test": "vitest run",
"test:watch": "vitest",
- "lint": "eslint . --fix"
+ "lint": "eslint --fix --cache ."
},
"peerDependencies": {
"whatwg-url": "7.1.0"
diff --git a/packages/plugin-starknet/src/actions/subdomain.ts b/packages/plugin-starknet/src/actions/subdomain.ts
index d3911b42f91..4f3c50fc011 100644
--- a/packages/plugin-starknet/src/actions/subdomain.ts
+++ b/packages/plugin-starknet/src/actions/subdomain.ts
@@ -9,7 +9,7 @@ import {
State,
type Action,
composeContext,
- generateObjectDEPRECATED,
+ generateObjectDeprecated,
Content,
elizaLogger,
} from "@ai16z/eliza";
@@ -107,7 +107,7 @@ export default {
});
// Generate transfer content
- const content = await generateObjectDEPRECATED({
+ const content = await generateObjectDeprecated({
runtime,
context: transferContext,
modelClass: ModelClass.MEDIUM,
diff --git a/packages/plugin-starknet/src/actions/swap.ts b/packages/plugin-starknet/src/actions/swap.ts
index 84737520c12..92e13d7d8bc 100644
--- a/packages/plugin-starknet/src/actions/swap.ts
+++ b/packages/plugin-starknet/src/actions/swap.ts
@@ -3,7 +3,7 @@ import {
ActionExample,
composeContext,
elizaLogger,
- generateObjectDEPRECATED,
+ generateObjectDeprecated,
HandlerCallback,
IAgentRuntime,
Memory,
@@ -105,7 +105,7 @@ export const executeSwap: Action = {
template: swapTemplate,
});
- const response = await generateObjectDEPRECATED({
+ const response = await generateObjectDeprecated({
runtime,
context: swapContext,
modelClass: ModelClass.MEDIUM,
diff --git a/packages/plugin-starknet/src/actions/transfer.ts b/packages/plugin-starknet/src/actions/transfer.ts
index 8eb8e086070..c761799690c 100644
--- a/packages/plugin-starknet/src/actions/transfer.ts
+++ b/packages/plugin-starknet/src/actions/transfer.ts
@@ -7,7 +7,7 @@ import {
composeContext,
Content,
elizaLogger,
- generateObjectDEPRECATED,
+ generateObjectDeprecated,
HandlerCallback,
IAgentRuntime,
Memory,
@@ -136,7 +136,7 @@ export default {
});
// Generate transfer content
- const content = await generateObjectDEPRECATED({
+ const content = await generateObjectDeprecated({
runtime,
context: transferContext,
modelClass: ModelClass.MEDIUM,
diff --git a/packages/plugin-starknet/src/actions/unruggable.ts b/packages/plugin-starknet/src/actions/unruggable.ts
index 88a5d7e370f..d3e796ce708 100644
--- a/packages/plugin-starknet/src/actions/unruggable.ts
+++ b/packages/plugin-starknet/src/actions/unruggable.ts
@@ -3,7 +3,7 @@ import {
ActionExample,
composeContext,
elizaLogger,
- generateObjectDEPRECATED,
+ generateObjectDeprecated,
HandlerCallback,
IAgentRuntime,
Memory,
@@ -102,7 +102,7 @@ export const deployToken: Action = {
template: deployTemplate,
});
- const response = await generateObjectDEPRECATED({
+ const response = await generateObjectDeprecated({
runtime,
context: deployContext,
modelClass: ModelClass.MEDIUM,
diff --git a/packages/plugin-story/package.json b/packages/plugin-story/package.json
new file mode 100644
index 00000000000..f35d5179214
--- /dev/null
+++ b/packages/plugin-story/package.json
@@ -0,0 +1,26 @@
+{
+ "name": "@ai16z/plugin-story",
+ "version": "0.1.0-alpha.1",
+ "main": "dist/index.js",
+ "type": "module",
+ "types": "dist/index.d.ts",
+ "dependencies": {
+ "@ai16z/eliza": "workspace:*",
+ "@ai16z/plugin-trustdb": "workspace:*",
+ "@story-protocol/core-sdk": "1.2.0-rc.3",
+ "tsup": "8.3.5",
+ "viem": "2.21.54",
+ "@pinata/sdk": "^2.1.0"
+ },
+ "scripts": {
+ "build": "tsup --format esm --dts",
+ "dev": "tsup --format esm --dts --watch",
+ "test": "vitest run"
+ },
+ "peerDependencies": {
+ "whatwg-url": "7.1.0"
+ },
+ "devDependencies": {
+ "@types/node": "^22.10.1"
+ }
+}
diff --git a/packages/plugin-story/src/actions/attachTerms.ts b/packages/plugin-story/src/actions/attachTerms.ts
new file mode 100644
index 00000000000..9c5abf690a3
--- /dev/null
+++ b/packages/plugin-story/src/actions/attachTerms.ts
@@ -0,0 +1,159 @@
+import {
+ composeContext,
+ elizaLogger,
+ generateObjectDeprecated,
+ HandlerCallback,
+ ModelClass,
+ type IAgentRuntime,
+ type Memory,
+ type State,
+} from "@ai16z/eliza";
+import { WalletProvider } from "../providers/wallet";
+import { attachTermsTemplate } from "../templates";
+import {
+ AttachLicenseTermsResponse,
+ LicenseTerms,
+ RegisterPILResponse,
+} from "@story-protocol/core-sdk";
+import { AttachTermsParams } from "../types";
+import { zeroAddress } from "viem";
+
+export { attachTermsTemplate };
+
+export class AttachTermsAction {
+ constructor(private walletProvider: WalletProvider) {}
+
+ async attachTerms(params: AttachTermsParams): Promise<{
+ attachTermsResponse: AttachLicenseTermsResponse;
+ registerPilTermsResponse: RegisterPILResponse;
+ }> {
+ const storyClient = this.walletProvider.getStoryClient();
+
+ console.log("params", params);
+
+ const licenseTerms: LicenseTerms = {
+ transferable: true,
+ royaltyPolicy: params.commercialUse
+ ? "0x28b4F70ffE5ba7A26aEF979226f77Eb57fb9Fdb6"
+ : zeroAddress,
+ defaultMintingFee: params.mintingFee
+ ? BigInt(params.mintingFee)
+ : BigInt(0),
+ expiration: BigInt(0),
+ commercialUse: params.commercialUse || false,
+ commercialAttribution: false,
+ commercializerChecker: zeroAddress,
+ commercializerCheckerData: zeroAddress,
+ commercialRevShare: params.commercialUse
+ ? params.commercialRevShare
+ : 0,
+ commercialRevCeiling: BigInt(0),
+ derivativesAllowed: true,
+ derivativesAttribution: true,
+ derivativesApproval: false,
+ derivativesReciprocal: true,
+ derivativeRevCeiling: BigInt(0),
+ currency: "0xC0F6E387aC0B324Ec18EAcf22EE7271207dCE3d5",
+ uri: "",
+ };
+
+ const registerPilTermsResponse =
+ await storyClient.license.registerPILTerms({
+ ...licenseTerms,
+ txOptions: { waitForTransaction: true },
+ });
+
+ const attachTermsResponse =
+ await storyClient.license.attachLicenseTerms({
+ ipId: params.ipId,
+ licenseTermsId: registerPilTermsResponse.licenseTermsId,
+ txOptions: { waitForTransaction: true },
+ });
+
+ return { attachTermsResponse, registerPilTermsResponse };
+ }
+}
+
+export const attachTermsAction = {
+ name: "ATTACH_TERMS",
+ description: "Attach license terms to an IP Asset on Story",
+ handler: async (
+ runtime: IAgentRuntime,
+ message: Memory,
+ state: State,
+ options: any,
+ callback?: HandlerCallback
+ ): Promise => {
+ elizaLogger.log("Starting ATTACH_TERMS handler...");
+
+ // initialize or update state
+ if (!state) {
+ state = (await runtime.composeState(message)) as State;
+ } else {
+ state = await runtime.updateRecentMessageState(state);
+ }
+
+ const attachTermsContext = composeContext({
+ state,
+ template: attachTermsTemplate,
+ });
+
+ const content = await generateObjectDeprecated({
+ runtime,
+ context: attachTermsContext,
+ modelClass: ModelClass.SMALL,
+ });
+
+ const walletProvider = new WalletProvider(runtime);
+ const action = new AttachTermsAction(walletProvider);
+ try {
+ const response = await action.attachTerms(content);
+ // if license terms were attached
+ if (response.attachTermsResponse.success) {
+ callback?.({
+ text: `Successfully attached license terms: ${response.registerPilTermsResponse.licenseTermsId}. Transaction Hash: ${response.attachTermsResponse.txHash}. View it on the block explorer: https://odyssey.storyscan.xyz/tx/${response.attachTermsResponse.txHash}`,
+ });
+ return true;
+ }
+ // if license terms were already attached
+ callback?.({
+ text: `License terms ${response.registerPilTermsResponse.licenseTermsId} were already attached to IP Asset ${content.ipId}`,
+ });
+ return true;
+ } catch (e) {
+ elizaLogger.error("Error licensing IP:", e.message);
+ callback?.({ text: `Error licensing IP: ${e.message}` });
+ return false;
+ }
+ },
+ template: attachTermsTemplate,
+ validate: async (runtime: IAgentRuntime) => {
+ const privateKey = runtime.getSetting("STORY_PRIVATE_KEY");
+ return typeof privateKey === "string" && privateKey.startsWith("0x");
+ },
+ examples: [
+ [
+ {
+ user: "{{user1}}",
+ content: {
+ text: "I would like to attach license terms to my IP.",
+ action: "ATTACH_TERMS",
+ },
+ },
+ {
+ user: "{{user2}}",
+ content: {
+ text: "Sure! What is the ipId? You should also tell me if you want to add a minting fee, or if you want to enable commercial use of your IP. If so, you can add a revenue share as well.",
+ action: "ATTACH_TERMS",
+ },
+ },
+ {
+ user: "{{user1}}",
+ content: {
+ text: "Attach commercial, 10% rev share license terms to IP Asset 0x2265F2b8e47F98b3Bdf7a1937EAc27282954A4Db",
+ },
+ },
+ ],
+ ],
+ similes: ["ATTACH_TERMS", "ATTACH_TERMS_TO_IP"],
+};
diff --git a/packages/plugin-story/src/actions/getAvailableLicenses.ts b/packages/plugin-story/src/actions/getAvailableLicenses.ts
new file mode 100644
index 00000000000..e3b9030517a
--- /dev/null
+++ b/packages/plugin-story/src/actions/getAvailableLicenses.ts
@@ -0,0 +1,166 @@
+import {
+ composeContext,
+ elizaLogger,
+ generateObjectDeprecated,
+ HandlerCallback,
+ ModelClass,
+ type IAgentRuntime,
+ type Memory,
+ type State,
+} from "@ai16z/eliza";
+import { getAvailableLicensesTemplate, licenseIPTemplate } from "../templates";
+import { Address } from "viem";
+import { IPLicenseDetails, RESOURCE_TYPE } from "../types/api";
+import { API_KEY, API_URL } from "../lib/api";
+import { storyOdyssey } from "viem/chains";
+
+export { licenseIPTemplate };
+
+// Types for request/response
+type GetAvailableLicensesParams = {
+ ipid: Address;
+};
+
+type GetAvailableLicensesResponse = {
+ data: IPLicenseDetails[];
+};
+
+/**
+ * Class to handle fetching available licenses for an IP asset from Story Protocol
+ */
+export class GetAvailableLicensesAction {
+ // Default query options for license terms
+ private readonly defaultQueryOptions = {
+ pagination: { limit: 10, offset: 0 },
+ orderBy: "blockNumber",
+ orderDirection: "desc",
+ };
+
+ async getAvailableLicenses(
+ params: GetAvailableLicensesParams
+ ): Promise {
+ elizaLogger.log(
+ "Fetching from",
+ `${API_URL}/${RESOURCE_TYPE.IP_LICENSE_DETAILS}`
+ );
+
+ const response = await fetch(
+ `${API_URL}/${RESOURCE_TYPE.IP_LICENSE_DETAILS}`,
+ {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ "x-api-key": API_KEY,
+ "x-chain": storyOdyssey.id.toString(),
+ },
+ cache: "no-cache",
+ body: JSON.stringify({
+ ip_ids: [params.ipid],
+ options: this.defaultQueryOptions,
+ }),
+ }
+ );
+
+ if (!response.ok) {
+ throw new Error(`HTTP error! status: ${response.status}`);
+ }
+
+ try {
+ const text = await response.text();
+ const licenseDetailsResponse = JSON.parse(text);
+ elizaLogger.log("licenseDetailsResponse", licenseDetailsResponse);
+ return licenseDetailsResponse;
+ } catch (e) {
+ elizaLogger.error("Failed to parse response");
+ throw new Error(`Failed to parse JSON response: ${e.message}`);
+ }
+ }
+}
+
+/**
+ * Formats a license's terms into a human-readable string
+ */
+const formatLicenseTerms = (license: IPLicenseDetails): string => {
+ const terms = license.terms;
+ return `License ID: ${license.id}
+- Terms:
+ • Commercial Use: ${terms.commercialUse ? "Allowed" : "Not Allowed"}
+ • Commercial Attribution: ${terms.commercialAttribution ? "Required" : "Not Required"}
+ • Derivatives: ${terms.derivativesAllowed ? "Allowed" : "Not Allowed"}
+ • Derivatives Attribution: ${terms.derivativesAttribution ? "Required" : "Not Required"}
+ • Derivatives Approval: ${terms.derivativesApproval ? "Required" : "Not Required"}
+ • Revenue Share: ${terms.commercialRevenueShare ? terms.commercialRevenueShare + "%" : "Not Required"}
+`;
+};
+
+/**
+ * Main action configuration for getting available licenses
+ */
+export const getAvailableLicensesAction = {
+ name: "GET_AVAILABLE_LICENSES",
+ description: "Get available licenses for an IP Asset on Story",
+ handler: async (
+ runtime: IAgentRuntime,
+ message: Memory,
+ state: State,
+ options: any,
+ callback?: HandlerCallback
+ ): Promise => {
+ elizaLogger.log("Starting GET_AVAILABLE_LICENSES handler...");
+
+ // Initialize or update state
+ state = !state
+ ? ((await runtime.composeState(message)) as State)
+ : await runtime.updateRecentMessageState(state);
+
+ // Generate parameters from context
+ const content = await generateObjectDeprecated({
+ runtime,
+ context: composeContext({
+ state,
+ template: getAvailableLicensesTemplate,
+ }),
+ modelClass: ModelClass.SMALL,
+ });
+
+ // Fetch and format license data
+ const action = new GetAvailableLicensesAction();
+ try {
+ const response = await action.getAvailableLicenses(content);
+ const formattedResponse = response.data
+ .map(formatLicenseTerms)
+ .join("\n");
+
+ callback?.({
+ text: formattedResponse,
+ action: "GET_AVAILABLE_LICENSES",
+ source: "Story Protocol API",
+ });
+ return true;
+ } catch (e) {
+ elizaLogger.error("Error fetching available licenses:", e.message);
+ callback?.({
+ text: `Error fetching available licenses: ${e.message}`,
+ });
+ return false;
+ }
+ },
+ template: getAvailableLicensesTemplate,
+ validate: async () => true,
+ examples: [
+ [
+ {
+ user: "{{user1}}",
+ content: {
+ text: "Get available licenses for an IP Asset 0x2265F2b8e47F98b3Bdf7a1937EAc27282954A4Db",
+ action: "GET_AVAILABLE_LICENSES",
+ },
+ },
+ ],
+ ],
+ similes: [
+ "AVAILABLE_LICENSES",
+ "AVAILABLE_LICENSES_FOR_IP",
+ "AVAILABLE_LICENSES_FOR_ASSET",
+ ],
+};
diff --git a/packages/plugin-story/src/actions/getIPDetails.ts b/packages/plugin-story/src/actions/getIPDetails.ts
new file mode 100644
index 00000000000..e5a22cc44f9
--- /dev/null
+++ b/packages/plugin-story/src/actions/getIPDetails.ts
@@ -0,0 +1,125 @@
+import {
+ composeContext,
+ elizaLogger,
+ generateObjectDeprecated,
+ HandlerCallback,
+ ModelClass,
+ type IAgentRuntime,
+ type Memory,
+ type State,
+} from "@ai16z/eliza";
+import { getIPDetailsTemplate } from "../templates";
+import { Address } from "viem";
+import { Asset, RESOURCE_TYPE } from "../types/api";
+import { API_URL, getResource } from "../lib/api";
+
+export { getIPDetailsTemplate };
+
+// Types for the action parameters and response
+type GetIPDetailsParams = {
+ ipId: Address;
+};
+
+type GetIPDetailsResponse = {
+ data: Asset;
+};
+
+/**
+ * Class handling IP details retrieval from Story Protocol
+ */
+class GetIPDetailsAction {
+ async getIPDetails(
+ params: GetIPDetailsParams
+ ): Promise {
+ elizaLogger.log("Fetching from", `${API_URL}/${RESOURCE_TYPE.ASSET}`);
+ return (await getResource(
+ RESOURCE_TYPE.ASSET,
+ params.ipId
+ )) as GetIPDetailsResponse;
+ }
+}
+
+/**
+ * Formats IP asset details into a readable string
+ */
+const formatIPDetails = (data: Asset): string => `IP Asset Details:
+ID: ${data.id}
+NFT Name: ${data.nftMetadata.name}
+Token Contract: ${data.nftMetadata.tokenContract}
+Token ID: ${data.nftMetadata.tokenId}
+Image URL: ${data.nftMetadata.imageUrl}
+
+Relationships:
+• Ancestors: ${data.ancestorCount}
+• Descendants: ${data.descendantCount}
+• Parents: ${data.parentCount || 0}
+• Children: ${data.childCount || 0}
+• Roots: ${data.rootCount || 0}
+
+Created:
+Block #${data.blockNumber}
+Timestamp: ${data.blockTimestamp}`;
+
+/**
+ * Main action configuration for getting IP details
+ */
+export const getIPDetailsAction = {
+ name: "GET_IP_DETAILS",
+ description: "Get details for an IP Asset on Story",
+ handler: async (
+ runtime: IAgentRuntime,
+ message: Memory,
+ state: State,
+ options: any,
+ callback?: HandlerCallback
+ ): Promise => {
+ elizaLogger.log("Starting GET_IP_DETAILS handler...");
+
+ // Initialize or update state
+ state = !state
+ ? ((await runtime.composeState(message)) as State)
+ : await runtime.updateRecentMessageState(state);
+
+ // Generate content using template
+ const content = await generateObjectDeprecated({
+ runtime,
+ context: composeContext({ state, template: getIPDetailsTemplate }),
+ modelClass: ModelClass.SMALL,
+ });
+
+ // Fetch and format IP details
+ const action = new GetIPDetailsAction();
+ try {
+ const response = await action.getIPDetails(content);
+ const formattedResponse = formatIPDetails(response.data);
+
+ callback?.({
+ text: formattedResponse,
+ action: "GET_IP_DETAILS",
+ source: "Story Protocol API",
+ });
+ return true;
+ } catch (e) {
+ elizaLogger.error("Error fetching IP details:", e.message);
+ callback?.({
+ text: `Error fetching IP details: ${e.message}`,
+ });
+ return false;
+ }
+ },
+ template: getIPDetailsTemplate,
+ validate: async () => true,
+ // Example usage of the action
+ examples: [
+ [
+ {
+ user: "{{user1}}",
+ content: {
+ text: "Get details for an IP Asset 0x2265F2b8e47F98b3Bdf7a1937EAc27282954A4Db",
+ action: "GET_IP_DETAILS",
+ },
+ },
+ ],
+ ],
+ similes: ["IP_DETAILS", "IP_DETAILS_FOR_ASSET", "IP_DETAILS_FOR_IP"],
+};
diff --git a/packages/plugin-story/src/actions/licenseIP.ts b/packages/plugin-story/src/actions/licenseIP.ts
new file mode 100644
index 00000000000..f05cedf20d9
--- /dev/null
+++ b/packages/plugin-story/src/actions/licenseIP.ts
@@ -0,0 +1,121 @@
+import {
+ composeContext,
+ elizaLogger,
+ generateObjectDeprecated,
+ HandlerCallback,
+ ModelClass,
+ type IAgentRuntime,
+ type Memory,
+ type State,
+} from "@ai16z/eliza";
+import { WalletProvider } from "../providers/wallet";
+import { licenseIPTemplate } from "../templates";
+import { LicenseIPParams } from "../types";
+import { MintLicenseTokensResponse } from "@story-protocol/core-sdk";
+import { hasIpAttachedLicenseTerms } from "../queries";
+
+export { licenseIPTemplate };
+
+export class LicenseIPAction {
+ constructor(private walletProvider: WalletProvider) {}
+
+ async licenseIP(
+ params: LicenseIPParams
+ ): Promise {
+ const storyClient = this.walletProvider.getStoryClient();
+ const publicClient = this.walletProvider.getPublicClient();
+
+ const hasLicenseTerms = await hasIpAttachedLicenseTerms(publicClient, {
+ ipId: params.licensorIpId,
+ licenseTemplate: "0x58E2c909D557Cd23EF90D14f8fd21667A5Ae7a93",
+ licenseTermsId: BigInt(params.licenseTermsId),
+ });
+ // check if license terms are attached to the ip asset
+ if (!hasLicenseTerms) {
+ throw new Error("License terms are not attached to the IP Asset");
+ }
+ const response = await storyClient.license.mintLicenseTokens({
+ licensorIpId: params.licensorIpId,
+ licenseTermsId: params.licenseTermsId,
+ amount: params.amount || 1,
+ txOptions: { waitForTransaction: true },
+ });
+
+ return response;
+ }
+}
+
+export const licenseIPAction = {
+ name: "LICENSE_IP",
+ description: "License an IP Asset on Story",
+ handler: async (
+ runtime: IAgentRuntime,
+ message: Memory,
+ state: State,
+ options: any,
+ callback?: HandlerCallback
+ ): Promise => {
+ elizaLogger.log("Starting LICENSE_IP handler...");
+
+ // initialize or update state
+ if (!state) {
+ state = (await runtime.composeState(message)) as State;
+ } else {
+ state = await runtime.updateRecentMessageState(state);
+ }
+
+ const licenseIPContext = composeContext({
+ state,
+ template: licenseIPTemplate,
+ });
+
+ const content = await generateObjectDeprecated({
+ runtime,
+ context: licenseIPContext,
+ modelClass: ModelClass.SMALL,
+ });
+
+ const walletProvider = new WalletProvider(runtime);
+ const action = new LicenseIPAction(walletProvider);
+ try {
+ const response = await action.licenseIP(content);
+ callback?.({
+ text: `Successfully minted license tokens: ${response.licenseTokenIds.join(", ")}. Transaction Hash: ${response.txHash}. View it on the block explorer: https://odyssey.storyscan.xyz/tx/${response.txHash}`,
+ });
+ return true;
+ } catch (e) {
+ elizaLogger.error("Error licensing IP:", e.message);
+ callback?.({ text: `Error licensing IP: ${e.message}` });
+ return false;
+ }
+ },
+ template: licenseIPTemplate,
+ validate: async (runtime: IAgentRuntime) => {
+ const privateKey = runtime.getSetting("STORY_PRIVATE_KEY");
+ return typeof privateKey === "string" && privateKey.startsWith("0x");
+ },
+ examples: [
+ [
+ {
+ user: "{{user1}}",
+ content: {
+ text: "I would like to license an IP.",
+ },
+ },
+ {
+ user: "{{user2}}",
+ content: {
+ text: "Sure! Please provide the ipId of the IP you want to license and the license terms id you want to attach.",
+ action: "LICENSE_IP",
+ },
+ },
+ {
+ user: "{{user1}}",
+ content: {
+ text: "License an IP Asset 0x2265F2b8e47F98b3Bdf7a1937EAc27282954A4Db with license terms 1",
+ },
+ },
+ ],
+ ],
+ similes: ["LICENSE", "LICENSE_IP", "LICENSE_IP_ASSET"],
+};
diff --git a/packages/plugin-story/src/actions/registerIP.ts b/packages/plugin-story/src/actions/registerIP.ts
new file mode 100644
index 00000000000..d033c1b4ce4
--- /dev/null
+++ b/packages/plugin-story/src/actions/registerIP.ts
@@ -0,0 +1,148 @@
+import {
+ composeContext,
+ elizaLogger,
+ generateObjectDeprecated,
+ HandlerCallback,
+ ModelClass,
+ type IAgentRuntime,
+ type Memory,
+ type State,
+} from "@ai16z/eliza";
+import pinataSDK from "@pinata/sdk";
+import { RegisterIpResponse } from "@story-protocol/core-sdk";
+import { createHash } from "crypto";
+import { uploadJSONToIPFS } from "../functions/uploadJSONToIPFS";
+import { WalletProvider } from "../providers/wallet";
+import { registerIPTemplate } from "../templates";
+import { RegisterIPParams } from "../types";
+
+export { registerIPTemplate };
+
+export class RegisterIPAction {
+ constructor(private walletProvider: WalletProvider) {}
+
+ async registerIP(
+ params: RegisterIPParams,
+ runtime: IAgentRuntime
+ ): Promise {
+ const storyClient = this.walletProvider.getStoryClient();
+
+ // configure ip metadata
+ const ipMetadata = storyClient.ipAsset.generateIpMetadata({
+ title: params.title,
+ description: params.description,
+ ipType: params.ipType ? params.ipType : undefined,
+ });
+
+ // configure nft metadata
+ const nftMetadata = {
+ name: params.title,
+ description: params.description,
+ };
+
+ const pinataJWT = runtime.getSetting("PINATA_JWT");
+ if (!pinataJWT) throw new Error("PINATA_JWT not configured");
+ const pinata = new pinataSDK({ pinataJWTKey: pinataJWT });
+
+ // upload metadata to ipfs
+ const ipIpfsHash = await uploadJSONToIPFS(pinata, ipMetadata);
+ const ipHash = createHash("sha256")
+ .update(JSON.stringify(ipMetadata))
+ .digest("hex");
+ const nftIpfsHash = await uploadJSONToIPFS(pinata, nftMetadata);
+ const nftHash = createHash("sha256")
+ .update(JSON.stringify(nftMetadata))
+ .digest("hex");
+
+ // register ip
+ const response =
+ await storyClient.ipAsset.mintAndRegisterIpAssetWithPilTerms({
+ spgNftContract: "0xC81B2cbEFD1aA0227bf513729580d3CF40fd61dF",
+ terms: [],
+ ipMetadata: {
+ ipMetadataURI: `https://ipfs.io/ipfs/${ipIpfsHash}`,
+ ipMetadataHash: `0x${ipHash}`,
+ nftMetadataURI: `https://ipfs.io/ipfs/${nftIpfsHash}`,
+ nftMetadataHash: `0x${nftHash}`,
+ },
+ txOptions: { waitForTransaction: true },
+ });
+
+ return response;
+ }
+}
+
+export const registerIPAction = {
+ name: "REGISTER_IP",
+ description: "Register an NFT as an IP Asset on Story",
+ handler: async (
+ runtime: IAgentRuntime,
+ message: Memory,
+ state: State,
+ options: any,
+ callback?: HandlerCallback
+ ): Promise => {
+ elizaLogger.log("Starting REGISTER_IP handler...");
+
+ // initialize or update state
+ if (!state) {
+ state = (await runtime.composeState(message)) as State;
+ } else {
+ state = await runtime.updateRecentMessageState(state);
+ }
+
+ const registerIPContext = composeContext({
+ state,
+ template: registerIPTemplate,
+ });
+
+ const content = await generateObjectDeprecated({
+ runtime,
+ context: registerIPContext,
+ modelClass: ModelClass.SMALL,
+ });
+
+ const walletProvider = new WalletProvider(runtime);
+ const action = new RegisterIPAction(walletProvider);
+ try {
+ const response = await action.registerIP(content, runtime);
+ callback?.({
+ text: `Successfully registered IP ID: ${response.ipId}. Transaction Hash: ${response.txHash}. View it on the explorer: https://explorer.story.foundation/ipa/${response.ipId}`,
+ });
+ return true;
+ } catch (e) {
+ elizaLogger.error("Error registering IP:", e.message);
+ callback?.({ text: `Error registering IP: ${e.message}` });
+ return false;
+ }
+ },
+ template: registerIPTemplate,
+ validate: async (runtime: IAgentRuntime) => {
+ const privateKey = runtime.getSetting("STORY_PRIVATE_KEY");
+ return typeof privateKey === "string" && privateKey.startsWith("0x");
+ },
+ examples: [
+ [
+ {
+ user: "{{user1}}",
+ content: {
+ text: "I would like to register my IP.",
+ },
+ },
+ {
+ user: "{{user2}}",
+ content: {
+ text: "Sure! Please provide the title and description of your IP.",
+ action: "REGISTER_IP",
+ },
+ },
+ {
+ user: "{{user1}}",
+ content: {
+ text: "Register my IP titled 'My IP' with the description 'This is my IP'",
+ },
+ },
+ ],
+ ],
+ similes: ["REGISTER_IP", "REGISTER_NFT"],
+};
diff --git a/packages/plugin-story/src/functions/uploadJSONToIPFS.ts b/packages/plugin-story/src/functions/uploadJSONToIPFS.ts
new file mode 100644
index 00000000000..dc8cb010735
--- /dev/null
+++ b/packages/plugin-story/src/functions/uploadJSONToIPFS.ts
@@ -0,0 +1,9 @@
+import PinataClient from "@pinata/sdk";
+
+export async function uploadJSONToIPFS(
+ pinata: PinataClient,
+ jsonMetadata: any
+): Promise {
+ const { IpfsHash } = await pinata.pinJSONToIPFS(jsonMetadata);
+ return IpfsHash;
+}
diff --git a/packages/plugin-story/src/index.ts b/packages/plugin-story/src/index.ts
new file mode 100644
index 00000000000..2fc566c69bb
--- /dev/null
+++ b/packages/plugin-story/src/index.ts
@@ -0,0 +1,32 @@
+export * from "./actions/registerIP";
+export * from "./actions/licenseIP";
+export * from "./actions/attachTerms";
+export * from "./actions/getAvailableLicenses";
+export * from "./actions/getIPDetails";
+export * from "./providers/wallet";
+export * from "./types";
+
+import type { Plugin } from "@ai16z/eliza";
+import { storyWalletProvider } from "./providers/wallet";
+import { registerIPAction } from "./actions/registerIP";
+import { licenseIPAction } from "./actions/licenseIP";
+import { getAvailableLicensesAction } from "./actions/getAvailableLicenses";
+import { getIPDetailsAction } from "./actions/getIPDetails";
+import { attachTermsAction } from "./actions/attachTerms";
+
+export const storyPlugin: Plugin = {
+ name: "story",
+ description: "Story integration plugin",
+ providers: [storyWalletProvider],
+ evaluators: [],
+ services: [],
+ actions: [
+ registerIPAction,
+ licenseIPAction,
+ attachTermsAction,
+ getAvailableLicensesAction,
+ getIPDetailsAction,
+ ],
+};
+
+export default storyPlugin;
diff --git a/packages/plugin-story/src/lib/api.ts b/packages/plugin-story/src/lib/api.ts
new file mode 100644
index 00000000000..7fd8dc48596
--- /dev/null
+++ b/packages/plugin-story/src/lib/api.ts
@@ -0,0 +1,124 @@
+import {
+ IPLicenseTerms,
+ PILTerms,
+ QUERY_ORDER_BY,
+ QUERY_ORDER_DIRECTION,
+ QueryOptions,
+ RESOURCE_TYPE,
+ ResourceType,
+ Trait,
+} from "../types/api";
+import { elizaLogger } from "@ai16z/eliza";
+
+import { camelize } from "./utils";
+const API_BASE_URL = process.env.STORY_API_BASE_URL;
+const API_VERSION = "v2";
+export const API_URL = `${API_BASE_URL}/${API_VERSION}`;
+export const API_KEY = process.env.STORY_API_KEY || "";
+
+export async function getResource(
+ resourceName: ResourceType,
+ resourceId: string,
+ options?: QueryOptions
+) {
+ try {
+ elizaLogger.log(
+ `Fetching resource ${API_URL}/${resourceName}/${resourceId}`
+ );
+ const res = await fetch(`${API_URL}/${resourceName}/${resourceId}`, {
+ method: "GET",
+ headers: {
+ "Content-Type": "application/json",
+ "x-api-key": API_KEY as string,
+ "x-chain": "1516",
+ },
+ });
+ if (res.ok) {
+ elizaLogger.log("Response is ok");
+ return res.json();
+ } else {
+ elizaLogger.log("Response is not ok");
+ elizaLogger.log(JSON.stringify(res));
+ throw new Error(`HTTP error! status: ${res.status}`);
+ }
+ } catch (error) {
+ console.error(error);
+ }
+}
+
+export async function listResource(
+ resourceName: ResourceType,
+ options?: QueryOptions
+) {
+ try {
+ const _options = {
+ pagination: {
+ limit: 10,
+ offset: 0,
+ },
+ orderBy: QUERY_ORDER_BY.BLOCK_NUMBER,
+ orderDirection: QUERY_ORDER_DIRECTION.DESC,
+ ...options,
+ };
+ elizaLogger.log(`Calling Story API ${resourceName}`);
+ elizaLogger.log(`STORY_API_KEY: ${API_KEY}`);
+ elizaLogger.log(`API_URL: ${API_URL}`);
+ elizaLogger.log(`API_VERSION: ${API_VERSION}`);
+ elizaLogger.log(`_options: ${JSON.stringify(_options)}`);
+ const res = await fetch(`${API_URL}/${resourceName}`, {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ "x-api-key": API_KEY as string,
+ "x-chain": "1516",
+ },
+ cache: "no-cache",
+ ...(_options && { body: JSON.stringify({ options: _options }) }),
+ });
+ if (res.ok) {
+ elizaLogger.log("Response is ok");
+ elizaLogger.log(res.ok);
+ return res.json();
+ } else {
+ elizaLogger.log("Response is not ok");
+ elizaLogger.log(res);
+ return res;
+ }
+ } catch (error) {
+ elizaLogger.log("List resource Error");
+ console.error(error);
+ }
+}
+
+export async function fetchLicenseTermsDetails(data: IPLicenseTerms[]) {
+ const requests = data.map((item) =>
+ getResource(RESOURCE_TYPE.LICENSE_TERMS, item.licenseTermsId)
+ );
+ const results = await Promise.all(requests);
+
+ return results
+ .filter((value) => !!value)
+ .map((result) => {
+ return {
+ ...result.data,
+ licenseTerms: convertLicenseTermObject(
+ result.data.licenseTerms
+ ),
+ };
+ });
+}
+
+type LicenseTerms = Partial;
+
+export function convertLicenseTermObject(licenseTerms: Trait[]): LicenseTerms {
+ return licenseTerms.reduce((acc, option: Trait): LicenseTerms => {
+ const key = camelize(option.trait_type) as keyof PILTerms;
+ acc[key] =
+ option.value === "true"
+ ? true
+ : option.value === "false"
+ ? false
+ : (option.value as any);
+ return acc as LicenseTerms;
+ }, {});
+}
diff --git a/packages/plugin-story/src/lib/utils.ts b/packages/plugin-story/src/lib/utils.ts
new file mode 100644
index 00000000000..f0464e17e51
--- /dev/null
+++ b/packages/plugin-story/src/lib/utils.ts
@@ -0,0 +1,6 @@
+export function camelize(str: string) {
+ return str.replace(/(?:^\w|[A-Z]|\b\w|\s+)/g, function (match, index) {
+ if (+match === 0) return ""; // or if (/\s+/.test(match)) for white spaces
+ return index === 0 ? match.toLowerCase() : match.toUpperCase();
+ });
+}
diff --git a/packages/plugin-story/src/providers/wallet.ts b/packages/plugin-story/src/providers/wallet.ts
new file mode 100644
index 00000000000..f386cf912b6
--- /dev/null
+++ b/packages/plugin-story/src/providers/wallet.ts
@@ -0,0 +1,132 @@
+import type { IAgentRuntime, Provider, Memory, State } from "@ai16z/eliza";
+import {
+ createPublicClient,
+ createWalletClient,
+ http,
+ formatUnits,
+ type PublicClient,
+ type WalletClient,
+ type Chain,
+ type HttpTransport,
+ type Address,
+ Account,
+} from "viem";
+import { storyOdyssey } from "viem/chains";
+import type { SupportedChain, ChainMetadata } from "../types";
+import { privateKeyToAccount } from "viem/accounts";
+import { StoryClient, StoryConfig } from "@story-protocol/core-sdk";
+
+export const DEFAULT_CHAIN_CONFIGS: Record = {
+ odyssey: {
+ chainId: 1516,
+ name: "Odyssey Testnet",
+ chain: storyOdyssey,
+ rpcUrl: "https://odyssey.storyrpc.io/",
+ nativeCurrency: {
+ name: "IP",
+ symbol: "IP",
+ decimals: 18,
+ },
+ blockExplorerUrl: "https://odyssey.storyscan.xyz",
+ },
+} as const;
+
+export class WalletProvider {
+ private storyClient: StoryClient;
+ private publicClient: PublicClient<
+ HttpTransport,
+ Chain,
+ Account | undefined
+ >;
+ private walletClient: WalletClient;
+ private address: Address;
+ runtime: IAgentRuntime;
+
+ constructor(runtime: IAgentRuntime) {
+ const privateKey = runtime.getSetting("STORY_PRIVATE_KEY");
+ if (!privateKey) throw new Error("STORY_PRIVATE_KEY not configured");
+
+ this.runtime = runtime;
+
+ const account = privateKeyToAccount(privateKey as Address);
+ this.address = account.address;
+
+ const config: StoryConfig = {
+ account: account,
+ transport: http(DEFAULT_CHAIN_CONFIGS.odyssey.rpcUrl),
+ chainId: "odyssey",
+ };
+ this.storyClient = StoryClient.newClient(config);
+
+ const baseConfig = {
+ chain: storyOdyssey,
+ transport: http(DEFAULT_CHAIN_CONFIGS.odyssey.rpcUrl),
+ } as const;
+ this.publicClient = createPublicClient(
+ baseConfig
+ ) as PublicClient;
+
+ this.walletClient = createWalletClient({
+ chain: storyOdyssey,
+ transport: http(DEFAULT_CHAIN_CONFIGS.odyssey.rpcUrl),
+ account: account,
+ });
+ }
+
+ getAddress(): Address {
+ return this.address;
+ }
+
+ async getWalletBalance(): Promise {
+ try {
+ const balance = await this.publicClient.getBalance({
+ address: this.address,
+ });
+ return formatUnits(balance, 18);
+ } catch (error) {
+ console.error("Error getting wallet balance:", error);
+ return null;
+ }
+ }
+
+ async connect(): Promise<`0x${string}`> {
+ return this.runtime.getSetting("STORY_PRIVATE_KEY") as `0x${string}`;
+ }
+
+ getPublicClient(): PublicClient {
+ return this.publicClient;
+ }
+
+ getWalletClient(): WalletClient {
+ if (!this.walletClient) throw new Error("Wallet not connected");
+ return this.walletClient;
+ }
+
+ getStoryClient(): StoryClient {
+ if (!this.storyClient) throw new Error("StoryClient not connected");
+ return this.storyClient;
+ }
+}
+
+export const storyWalletProvider: Provider = {
+ async get(
+ runtime: IAgentRuntime,
+ message: Memory,
+ state?: State
+ ): Promise {
+ // Check if the user has a Story wallet
+ if (!runtime.getSetting("STORY_PRIVATE_KEY")) {
+ return null;
+ }
+
+ try {
+ const walletProvider = new WalletProvider(runtime);
+ const address = walletProvider.getAddress();
+ const balance = await walletProvider.getWalletBalance();
+ return `Story Wallet Address: ${address}\nBalance: ${balance} IP`;
+ } catch (error) {
+ console.error("Error in Story wallet provider:", error);
+ return null;
+ }
+ },
+};
diff --git a/packages/plugin-story/src/queries.ts b/packages/plugin-story/src/queries.ts
new file mode 100644
index 00000000000..080b8f12318
--- /dev/null
+++ b/packages/plugin-story/src/queries.ts
@@ -0,0 +1,982 @@
+import { Account, HttpTransport, Chain, Address, PublicClient } from "viem";
+
+type LicenseRegistryHasIpAttachedLicenseTermsRequest = {
+ ipId: Address;
+ licenseTemplate: Address;
+ licenseTermsId: bigint;
+};
+
+export const licenseRegistryAbi = [
+ {
+ type: "constructor",
+ inputs: [
+ {
+ name: "licensingModule",
+ internalType: "address",
+ type: "address",
+ },
+ { name: "disputeModule", internalType: "address", type: "address" },
+ { name: "ipGraphAcl", internalType: "address", type: "address" },
+ ],
+ stateMutability: "nonpayable",
+ },
+ {
+ type: "error",
+ inputs: [
+ { name: "authority", internalType: "address", type: "address" },
+ ],
+ name: "AccessManagedInvalidAuthority",
+ },
+ {
+ type: "error",
+ inputs: [
+ { name: "caller", internalType: "address", type: "address" },
+ { name: "delay", internalType: "uint32", type: "uint32" },
+ ],
+ name: "AccessManagedRequiredDelay",
+ },
+ {
+ type: "error",
+ inputs: [{ name: "caller", internalType: "address", type: "address" }],
+ name: "AccessManagedUnauthorized",
+ },
+ {
+ type: "error",
+ inputs: [{ name: "target", internalType: "address", type: "address" }],
+ name: "AddressEmptyCode",
+ },
+ {
+ type: "error",
+ inputs: [
+ {
+ name: "implementation",
+ internalType: "address",
+ type: "address",
+ },
+ ],
+ name: "ERC1967InvalidImplementation",
+ },
+ { type: "error", inputs: [], name: "ERC1967NonPayable" },
+ { type: "error", inputs: [], name: "FailedInnerCall" },
+ { type: "error", inputs: [], name: "InvalidInitialization" },
+ {
+ type: "error",
+ inputs: [
+ { name: "childIpId", internalType: "address", type: "address" },
+ {
+ name: "parentIpIds",
+ internalType: "address[]",
+ type: "address[]",
+ },
+ ],
+ name: "LicenseRegistry__AddParentIpToIPGraphFailed",
+ },
+ {
+ type: "error",
+ inputs: [],
+ name: "LicenseRegistry__CallerNotLicensingModule",
+ },
+ {
+ type: "error",
+ inputs: [
+ { name: "childIpId", internalType: "address", type: "address" },
+ ],
+ name: "LicenseRegistry__DerivativeAlreadyRegistered",
+ },
+ {
+ type: "error",
+ inputs: [
+ { name: "childIpId", internalType: "address", type: "address" },
+ ],
+ name: "LicenseRegistry__DerivativeIpAlreadyHasChild",
+ },
+ {
+ type: "error",
+ inputs: [
+ { name: "childIpId", internalType: "address", type: "address" },
+ ],
+ name: "LicenseRegistry__DerivativeIpAlreadyHasLicense",
+ },
+ {
+ type: "error",
+ inputs: [{ name: "ipId", internalType: "address", type: "address" }],
+ name: "LicenseRegistry__DerivativeIsParent",
+ },
+ {
+ type: "error",
+ inputs: [
+ { name: "ipId", internalType: "address", type: "address" },
+ {
+ name: "licenseTemplate",
+ internalType: "address",
+ type: "address",
+ },
+ {
+ name: "licenseTermsId",
+ internalType: "uint256",
+ type: "uint256",
+ },
+ ],
+ name: "LicenseRegistry__DuplicateLicense",
+ },
+ {
+ type: "error",
+ inputs: [
+ { name: "ipId", internalType: "address", type: "address" },
+ { name: "index", internalType: "uint256", type: "uint256" },
+ { name: "length", internalType: "uint256", type: "uint256" },
+ ],
+ name: "LicenseRegistry__IndexOutOfBounds",
+ },
+ {
+ type: "error",
+ inputs: [{ name: "ipId", internalType: "address", type: "address" }],
+ name: "LicenseRegistry__IpExpired",
+ },
+ {
+ type: "error",
+ inputs: [
+ { name: "ipId", internalType: "address", type: "address" },
+ {
+ name: "licenseTemplate",
+ internalType: "address",
+ type: "address",
+ },
+ {
+ name: "licenseTermsId",
+ internalType: "uint256",
+ type: "uint256",
+ },
+ ],
+ name: "LicenseRegistry__LicenseTermsAlreadyAttached",
+ },
+ {
+ type: "error",
+ inputs: [
+ {
+ name: "licenseTemplate",
+ internalType: "address",
+ type: "address",
+ },
+ {
+ name: "licenseTermsId",
+ internalType: "uint256",
+ type: "uint256",
+ },
+ ],
+ name: "LicenseRegistry__LicenseTermsNotExists",
+ },
+ {
+ type: "error",
+ inputs: [
+ { name: "ipId", internalType: "address", type: "address" },
+ {
+ name: "licenseTemplate",
+ internalType: "address",
+ type: "address",
+ },
+ {
+ name: "licenseTermsId",
+ internalType: "uint256",
+ type: "uint256",
+ },
+ ],
+ name: "LicenseRegistry__LicensorIpHasNoLicenseTerms",
+ },
+ {
+ type: "error",
+ inputs: [
+ {
+ name: "licenseTemplate",
+ internalType: "address",
+ type: "address",
+ },
+ ],
+ name: "LicenseRegistry__NotLicenseTemplate",
+ },
+ {
+ type: "error",
+ inputs: [{ name: "ipId", internalType: "address", type: "address" }],
+ name: "LicenseRegistry__ParentIpExpired",
+ },
+ {
+ type: "error",
+ inputs: [
+ { name: "ipId", internalType: "address", type: "address" },
+ {
+ name: "licenseTermsId",
+ internalType: "uint256",
+ type: "uint256",
+ },
+ ],
+ name: "LicenseRegistry__ParentIpHasNoLicenseTerms",
+ },
+ {
+ type: "error",
+ inputs: [{ name: "ipId", internalType: "address", type: "address" }],
+ name: "LicenseRegistry__ParentIpTagged",
+ },
+ {
+ type: "error",
+ inputs: [
+ { name: "ipId", internalType: "address", type: "address" },
+ {
+ name: "licenseTemplate",
+ internalType: "address",
+ type: "address",
+ },
+ ],
+ name: "LicenseRegistry__ParentIpUnmatchedLicenseTemplate",
+ },
+ {
+ type: "error",
+ inputs: [
+ { name: "ipId", internalType: "address", type: "address" },
+ {
+ name: "licenseTemplate",
+ internalType: "address",
+ type: "address",
+ },
+ {
+ name: "newLicenseTemplate",
+ internalType: "address",
+ type: "address",
+ },
+ ],
+ name: "LicenseRegistry__UnmatchedLicenseTemplate",
+ },
+ {
+ type: "error",
+ inputs: [
+ {
+ name: "licenseTemplate",
+ internalType: "address",
+ type: "address",
+ },
+ ],
+ name: "LicenseRegistry__UnregisteredLicenseTemplate",
+ },
+ { type: "error", inputs: [], name: "LicenseRegistry__ZeroAccessManager" },
+ { type: "error", inputs: [], name: "LicenseRegistry__ZeroDisputeModule" },
+ { type: "error", inputs: [], name: "LicenseRegistry__ZeroIPGraphACL" },
+ { type: "error", inputs: [], name: "LicenseRegistry__ZeroLicenseTemplate" },
+ { type: "error", inputs: [], name: "LicenseRegistry__ZeroLicensingModule" },
+ {
+ type: "error",
+ inputs: [],
+ name: "LicensingModule__DerivativesCannotAddLicenseTerms",
+ },
+ {
+ type: "error",
+ inputs: [
+ {
+ name: "licenseTemplate",
+ internalType: "address",
+ type: "address",
+ },
+ {
+ name: "licenseTermsId",
+ internalType: "uint256",
+ type: "uint256",
+ },
+ ],
+ name: "LicensingModule__LicenseTermsNotFound",
+ },
+ { type: "error", inputs: [], name: "NotInitializing" },
+ { type: "error", inputs: [], name: "UUPSUnauthorizedCallContext" },
+ {
+ type: "error",
+ inputs: [{ name: "slot", internalType: "bytes32", type: "bytes32" }],
+ name: "UUPSUnsupportedProxiableUUID",
+ },
+ {
+ type: "event",
+ anonymous: false,
+ inputs: [
+ {
+ name: "authority",
+ internalType: "address",
+ type: "address",
+ indexed: false,
+ },
+ ],
+ name: "AuthorityUpdated",
+ },
+ {
+ type: "event",
+ anonymous: false,
+ inputs: [
+ {
+ name: "licenseTemplate",
+ internalType: "address",
+ type: "address",
+ indexed: false,
+ },
+ {
+ name: "licenseTermsId",
+ internalType: "uint256",
+ type: "uint256",
+ indexed: false,
+ },
+ ],
+ name: "DefaultLicenseTermsSet",
+ },
+ {
+ type: "event",
+ anonymous: false,
+ inputs: [
+ {
+ name: "ipId",
+ internalType: "address",
+ type: "address",
+ indexed: true,
+ },
+ {
+ name: "expireTime",
+ internalType: "uint256",
+ type: "uint256",
+ indexed: false,
+ },
+ ],
+ name: "ExpirationTimeSet",
+ },
+ {
+ type: "event",
+ anonymous: false,
+ inputs: [
+ {
+ name: "version",
+ internalType: "uint64",
+ type: "uint64",
+ indexed: false,
+ },
+ ],
+ name: "Initialized",
+ },
+ {
+ type: "event",
+ anonymous: false,
+ inputs: [
+ {
+ name: "licenseTemplate",
+ internalType: "address",
+ type: "address",
+ indexed: true,
+ },
+ ],
+ name: "LicenseTemplateRegistered",
+ },
+ {
+ type: "event",
+ anonymous: false,
+ inputs: [
+ {
+ name: "ipId",
+ internalType: "address",
+ type: "address",
+ indexed: true,
+ },
+ {
+ name: "licensingConfig",
+ internalType: "struct Licensing.LicensingConfig",
+ type: "tuple",
+ components: [
+ { name: "isSet", internalType: "bool", type: "bool" },
+ {
+ name: "mintingFee",
+ internalType: "uint256",
+ type: "uint256",
+ },
+ {
+ name: "licensingHook",
+ internalType: "address",
+ type: "address",
+ },
+ { name: "hookData", internalType: "bytes", type: "bytes" },
+ ],
+ indexed: false,
+ },
+ ],
+ name: "LicensingConfigSetForIP",
+ },
+ {
+ type: "event",
+ anonymous: false,
+ inputs: [
+ {
+ name: "ipId",
+ internalType: "address",
+ type: "address",
+ indexed: true,
+ },
+ {
+ name: "licenseTemplate",
+ internalType: "address",
+ type: "address",
+ indexed: true,
+ },
+ {
+ name: "licenseTermsId",
+ internalType: "uint256",
+ type: "uint256",
+ indexed: true,
+ },
+ ],
+ name: "LicensingConfigSetForLicense",
+ },
+ {
+ type: "event",
+ anonymous: false,
+ inputs: [
+ {
+ name: "implementation",
+ internalType: "address",
+ type: "address",
+ indexed: true,
+ },
+ ],
+ name: "Upgraded",
+ },
+ {
+ type: "function",
+ inputs: [],
+ name: "DISPUTE_MODULE",
+ outputs: [
+ {
+ name: "",
+ internalType: "contract IDisputeModule",
+ type: "address",
+ },
+ ],
+ stateMutability: "view",
+ },
+ {
+ type: "function",
+ inputs: [],
+ name: "EXPIRATION_TIME",
+ outputs: [{ name: "", internalType: "bytes32", type: "bytes32" }],
+ stateMutability: "view",
+ },
+ {
+ type: "function",
+ inputs: [],
+ name: "IP_GRAPH",
+ outputs: [{ name: "", internalType: "address", type: "address" }],
+ stateMutability: "view",
+ },
+ {
+ type: "function",
+ inputs: [],
+ name: "IP_GRAPH_ACL",
+ outputs: [
+ { name: "", internalType: "contract IPGraphACL", type: "address" },
+ ],
+ stateMutability: "view",
+ },
+ {
+ type: "function",
+ inputs: [],
+ name: "LICENSING_MODULE",
+ outputs: [
+ {
+ name: "",
+ internalType: "contract ILicensingModule",
+ type: "address",
+ },
+ ],
+ stateMutability: "view",
+ },
+ {
+ type: "function",
+ inputs: [],
+ name: "UPGRADE_INTERFACE_VERSION",
+ outputs: [{ name: "", internalType: "string", type: "string" }],
+ stateMutability: "view",
+ },
+ {
+ type: "function",
+ inputs: [
+ { name: "ipId", internalType: "address", type: "address" },
+ {
+ name: "licenseTemplate",
+ internalType: "address",
+ type: "address",
+ },
+ {
+ name: "licenseTermsId",
+ internalType: "uint256",
+ type: "uint256",
+ },
+ ],
+ name: "attachLicenseTermsToIp",
+ outputs: [],
+ stateMutability: "nonpayable",
+ },
+ {
+ type: "function",
+ inputs: [],
+ name: "authority",
+ outputs: [{ name: "", internalType: "address", type: "address" }],
+ stateMutability: "view",
+ },
+ {
+ type: "function",
+ inputs: [
+ {
+ name: "licenseTemplate",
+ internalType: "address",
+ type: "address",
+ },
+ {
+ name: "licenseTermsId",
+ internalType: "uint256",
+ type: "uint256",
+ },
+ ],
+ name: "exists",
+ outputs: [{ name: "", internalType: "bool", type: "bool" }],
+ stateMutability: "view",
+ },
+ {
+ type: "function",
+ inputs: [
+ { name: "ipId", internalType: "address", type: "address" },
+ { name: "index", internalType: "uint256", type: "uint256" },
+ ],
+ name: "getAttachedLicenseTerms",
+ outputs: [
+ {
+ name: "licenseTemplate",
+ internalType: "address",
+ type: "address",
+ },
+ {
+ name: "licenseTermsId",
+ internalType: "uint256",
+ type: "uint256",
+ },
+ ],
+ stateMutability: "view",
+ },
+ {
+ type: "function",
+ inputs: [{ name: "ipId", internalType: "address", type: "address" }],
+ name: "getAttachedLicenseTermsCount",
+ outputs: [{ name: "", internalType: "uint256", type: "uint256" }],
+ stateMutability: "view",
+ },
+ {
+ type: "function",
+ inputs: [],
+ name: "getDefaultLicenseTerms",
+ outputs: [
+ {
+ name: "licenseTemplate",
+ internalType: "address",
+ type: "address",
+ },
+ {
+ name: "licenseTermsId",
+ internalType: "uint256",
+ type: "uint256",
+ },
+ ],
+ stateMutability: "view",
+ },
+ {
+ type: "function",
+ inputs: [
+ { name: "parentIpId", internalType: "address", type: "address" },
+ { name: "index", internalType: "uint256", type: "uint256" },
+ ],
+ name: "getDerivativeIp",
+ outputs: [
+ { name: "childIpId", internalType: "address", type: "address" },
+ ],
+ stateMutability: "view",
+ },
+ {
+ type: "function",
+ inputs: [
+ { name: "parentIpId", internalType: "address", type: "address" },
+ ],
+ name: "getDerivativeIpCount",
+ outputs: [{ name: "", internalType: "uint256", type: "uint256" }],
+ stateMutability: "view",
+ },
+ {
+ type: "function",
+ inputs: [{ name: "ipId", internalType: "address", type: "address" }],
+ name: "getExpireTime",
+ outputs: [{ name: "", internalType: "uint256", type: "uint256" }],
+ stateMutability: "view",
+ },
+ {
+ type: "function",
+ inputs: [
+ { name: "ipId", internalType: "address", type: "address" },
+ {
+ name: "licenseTemplate",
+ internalType: "address",
+ type: "address",
+ },
+ {
+ name: "licenseTermsId",
+ internalType: "uint256",
+ type: "uint256",
+ },
+ ],
+ name: "getLicensingConfig",
+ outputs: [
+ {
+ name: "",
+ internalType: "struct Licensing.LicensingConfig",
+ type: "tuple",
+ components: [
+ { name: "isSet", internalType: "bool", type: "bool" },
+ {
+ name: "mintingFee",
+ internalType: "uint256",
+ type: "uint256",
+ },
+ {
+ name: "licensingHook",
+ internalType: "address",
+ type: "address",
+ },
+ { name: "hookData", internalType: "bytes", type: "bytes" },
+ ],
+ },
+ ],
+ stateMutability: "view",
+ },
+ {
+ type: "function",
+ inputs: [
+ { name: "childIpId", internalType: "address", type: "address" },
+ { name: "index", internalType: "uint256", type: "uint256" },
+ ],
+ name: "getParentIp",
+ outputs: [
+ { name: "parentIpId", internalType: "address", type: "address" },
+ ],
+ stateMutability: "view",
+ },
+ {
+ type: "function",
+ inputs: [
+ { name: "childIpId", internalType: "address", type: "address" },
+ ],
+ name: "getParentIpCount",
+ outputs: [{ name: "", internalType: "uint256", type: "uint256" }],
+ stateMutability: "view",
+ },
+ {
+ type: "function",
+ inputs: [
+ { name: "childIpId", internalType: "address", type: "address" },
+ { name: "parentIpId", internalType: "address", type: "address" },
+ ],
+ name: "getParentLicenseTerms",
+ outputs: [
+ {
+ name: "licenseTemplate",
+ internalType: "address",
+ type: "address",
+ },
+ {
+ name: "licenseTermsId",
+ internalType: "uint256",
+ type: "uint256",
+ },
+ ],
+ stateMutability: "view",
+ },
+ {
+ type: "function",
+ inputs: [
+ { name: "parentIpId", internalType: "address", type: "address" },
+ ],
+ name: "hasDerivativeIps",
+ outputs: [{ name: "", internalType: "bool", type: "bool" }],
+ stateMutability: "view",
+ },
+ {
+ type: "function",
+ inputs: [
+ { name: "ipId", internalType: "address", type: "address" },
+ {
+ name: "licenseTemplate",
+ internalType: "address",
+ type: "address",
+ },
+ {
+ name: "licenseTermsId",
+ internalType: "uint256",
+ type: "uint256",
+ },
+ ],
+ name: "hasIpAttachedLicenseTerms",
+ outputs: [{ name: "", internalType: "bool", type: "bool" }],
+ stateMutability: "view",
+ },
+ {
+ type: "function",
+ inputs: [
+ { name: "accessManager", internalType: "address", type: "address" },
+ ],
+ name: "initialize",
+ outputs: [],
+ stateMutability: "nonpayable",
+ },
+ {
+ type: "function",
+ inputs: [],
+ name: "isConsumingScheduledOp",
+ outputs: [{ name: "", internalType: "bytes4", type: "bytes4" }],
+ stateMutability: "view",
+ },
+ {
+ type: "function",
+ inputs: [
+ { name: "childIpId", internalType: "address", type: "address" },
+ ],
+ name: "isDerivativeIp",
+ outputs: [{ name: "", internalType: "bool", type: "bool" }],
+ stateMutability: "view",
+ },
+ {
+ type: "function",
+ inputs: [{ name: "ipId", internalType: "address", type: "address" }],
+ name: "isExpiredNow",
+ outputs: [{ name: "", internalType: "bool", type: "bool" }],
+ stateMutability: "view",
+ },
+ {
+ type: "function",
+ inputs: [
+ { name: "parentIpId", internalType: "address", type: "address" },
+ { name: "childIpId", internalType: "address", type: "address" },
+ ],
+ name: "isParentIp",
+ outputs: [{ name: "", internalType: "bool", type: "bool" }],
+ stateMutability: "view",
+ },
+ {
+ type: "function",
+ inputs: [
+ {
+ name: "licenseTemplate",
+ internalType: "address",
+ type: "address",
+ },
+ ],
+ name: "isRegisteredLicenseTemplate",
+ outputs: [{ name: "", internalType: "bool", type: "bool" }],
+ stateMutability: "view",
+ },
+ {
+ type: "function",
+ inputs: [],
+ name: "proxiableUUID",
+ outputs: [{ name: "", internalType: "bytes32", type: "bytes32" }],
+ stateMutability: "view",
+ },
+ {
+ type: "function",
+ inputs: [
+ { name: "childIpId", internalType: "address", type: "address" },
+ {
+ name: "parentIpIds",
+ internalType: "address[]",
+ type: "address[]",
+ },
+ {
+ name: "licenseTemplate",
+ internalType: "address",
+ type: "address",
+ },
+ {
+ name: "licenseTermsIds",
+ internalType: "uint256[]",
+ type: "uint256[]",
+ },
+ { name: "isUsingLicenseToken", internalType: "bool", type: "bool" },
+ ],
+ name: "registerDerivativeIp",
+ outputs: [],
+ stateMutability: "nonpayable",
+ },
+ {
+ type: "function",
+ inputs: [
+ {
+ name: "licenseTemplate",
+ internalType: "address",
+ type: "address",
+ },
+ ],
+ name: "registerLicenseTemplate",
+ outputs: [],
+ stateMutability: "nonpayable",
+ },
+ {
+ type: "function",
+ inputs: [
+ { name: "newAuthority", internalType: "address", type: "address" },
+ ],
+ name: "setAuthority",
+ outputs: [],
+ stateMutability: "nonpayable",
+ },
+ {
+ type: "function",
+ inputs: [
+ {
+ name: "newLicenseTemplate",
+ internalType: "address",
+ type: "address",
+ },
+ {
+ name: "newLicenseTermsId",
+ internalType: "uint256",
+ type: "uint256",
+ },
+ ],
+ name: "setDefaultLicenseTerms",
+ outputs: [],
+ stateMutability: "nonpayable",
+ },
+ {
+ type: "function",
+ inputs: [
+ { name: "ipId", internalType: "address", type: "address" },
+ {
+ name: "licensingConfig",
+ internalType: "struct Licensing.LicensingConfig",
+ type: "tuple",
+ components: [
+ { name: "isSet", internalType: "bool", type: "bool" },
+ {
+ name: "mintingFee",
+ internalType: "uint256",
+ type: "uint256",
+ },
+ {
+ name: "licensingHook",
+ internalType: "address",
+ type: "address",
+ },
+ { name: "hookData", internalType: "bytes", type: "bytes" },
+ ],
+ },
+ ],
+ name: "setLicensingConfigForIp",
+ outputs: [],
+ stateMutability: "nonpayable",
+ },
+ {
+ type: "function",
+ inputs: [
+ { name: "ipId", internalType: "address", type: "address" },
+ {
+ name: "licenseTemplate",
+ internalType: "address",
+ type: "address",
+ },
+ {
+ name: "licenseTermsId",
+ internalType: "uint256",
+ type: "uint256",
+ },
+ {
+ name: "licensingConfig",
+ internalType: "struct Licensing.LicensingConfig",
+ type: "tuple",
+ components: [
+ { name: "isSet", internalType: "bool", type: "bool" },
+ {
+ name: "mintingFee",
+ internalType: "uint256",
+ type: "uint256",
+ },
+ {
+ name: "licensingHook",
+ internalType: "address",
+ type: "address",
+ },
+ { name: "hookData", internalType: "bytes", type: "bytes" },
+ ],
+ },
+ ],
+ name: "setLicensingConfigForLicense",
+ outputs: [],
+ stateMutability: "nonpayable",
+ },
+ {
+ type: "function",
+ inputs: [
+ {
+ name: "newImplementation",
+ internalType: "address",
+ type: "address",
+ },
+ { name: "data", internalType: "bytes", type: "bytes" },
+ ],
+ name: "upgradeToAndCall",
+ outputs: [],
+ stateMutability: "payable",
+ },
+ {
+ type: "function",
+ inputs: [
+ { name: "licensorIpId", internalType: "address", type: "address" },
+ {
+ name: "licenseTemplate",
+ internalType: "address",
+ type: "address",
+ },
+ {
+ name: "licenseTermsId",
+ internalType: "uint256",
+ type: "uint256",
+ },
+ { name: "isMintedByIpOwner", internalType: "bool", type: "bool" },
+ ],
+ name: "verifyMintLicenseToken",
+ outputs: [
+ {
+ name: "",
+ internalType: "struct Licensing.LicensingConfig",
+ type: "tuple",
+ components: [
+ { name: "isSet", internalType: "bool", type: "bool" },
+ {
+ name: "mintingFee",
+ internalType: "uint256",
+ type: "uint256",
+ },
+ {
+ name: "licensingHook",
+ internalType: "address",
+ type: "address",
+ },
+ { name: "hookData", internalType: "bytes", type: "bytes" },
+ ],
+ },
+ ],
+ stateMutability: "view",
+ },
+] as const;
+
+export async function hasIpAttachedLicenseTerms(
+ publicClient: PublicClient,
+ request: LicenseRegistryHasIpAttachedLicenseTermsRequest
+): Promise {
+ return await publicClient.readContract({
+ abi: licenseRegistryAbi,
+ address: "0xBda3992c49E98392e75E78d82B934F3598bA495f",
+ functionName: "hasIpAttachedLicenseTerms",
+ args: [request.ipId, request.licenseTemplate, request.licenseTermsId],
+ });
+}
diff --git a/packages/plugin-story/src/templates/index.ts b/packages/plugin-story/src/templates/index.ts
new file mode 100644
index 00000000000..e647e389d08
--- /dev/null
+++ b/packages/plugin-story/src/templates/index.ts
@@ -0,0 +1,101 @@
+export const registerIPTemplate = `Given the recent messages below:
+
+{{recentMessages}}
+
+Extract the following information about the requested IP registration:
+- Field "title": The title of your IP
+- Field "description": The description of your IP
+- Field "ipType": The type of your IP. Type of the IP Asset, can be defined arbitrarily by the
+creator. I.e. “character”, “chapter”, “location”, “items”, "music", etc. If a user doesn't provide
+an ipType, you can infer it from the title and description. It should be one word.
+
+Respond with a JSON markdown block containing only the extracted values. A user must explicitly provide a title and description.
+
+\`\`\`json
+{
+ "title": string,
+ "description": string,
+ "ipType": string
+}
+\`\`\`
+`;
+
+export const licenseIPTemplate = `Given the recent messages below:
+
+{{recentMessages}}
+
+Extract the following information about the requested IP licensing:
+- Field "licensorIpId": The IP Asset that you want to mint a license from
+- Field "licenseTermsId": The license terms that you want to mint a license for
+- Field "amount": The amount of licenses to mint
+
+Respond with a JSON markdown block containing only the extracted values. A user must explicitly provide a licensorIpId and licenseTermsId.
+If they don't provide the amount, set it as null.
+
+\`\`\`json
+{
+ "licensorIpId": string,
+ "licenseTermsId": string,
+ "amount": number | null
+}
+\`\`\`
+`;
+
+export const getAvailableLicensesTemplate = `Given the recent messages and wallet information below:
+
+{{recentMessages}}
+
+{{walletInfo}}
+
+Extract the following information about the requested IP licensing:
+- Field "ipid": The IP Asset that you want to mint a license from
+
+Respond with a JSON markdown block containing only the extracted values. A user must provide an ipId.
+
+\`\`\`json
+{
+ "ipid": string
+}
+\`\`\`
+`;
+
+export const getIPDetailsTemplate = `Given the recent messages below:
+
+{{recentMessages}}
+
+Extract the following information about the requested IP details:
+- Field "ipId": The IP Asset that you want to get details for
+
+Respond with a JSON markdown block containing only the extracted values. A user must provide an ipId.
+
+\`\`\`json
+{
+ "ipId": string
+}
+\`\`\`
+`;
+
+export const attachTermsTemplate = `Given the recent messages below:
+
+{{recentMessages}}
+
+Extract the following information about attaching license terms to an IP Asset:
+- Field "ipId": The IP Asset that you want to attach the license terms to
+- Field "mintingFee": The fee to mint this license from the IP Asset.
+- Field "commercialUse": Whether or not the IP Asset can be used commercially.
+- Field "commercialRevShare": The percentage of revenue that the IP Asset owner will receive
+from commercial use of the IP Asset. This must be between 0 and 100. If a user specifies
+a commercialRevShare, then commercialUse must be set to true.
+
+Respond with a JSON markdown block containing only the extracted values. A user must provide an ipId. If they don't provide
+the others fields, set them as null.
+
+\`\`\`json
+{
+ "ipId": string,
+ "mintingFee": number | null,
+ "commercialUse": boolean | null,
+ "commercialRevShare": number | null
+}
+\`\`\`
+`;
diff --git a/packages/plugin-story/src/tests/wallet.test.ts b/packages/plugin-story/src/tests/wallet.test.ts
new file mode 100644
index 00000000000..807282ed8db
--- /dev/null
+++ b/packages/plugin-story/src/tests/wallet.test.ts
@@ -0,0 +1,63 @@
+import { describe, it, expect, beforeEach, vi, afterEach } from "vitest";
+import { WalletProvider } from "../providers/wallet.ts";
+import { defaultCharacter } from "@ai16z/eliza";
+
+// Mock NodeCache
+vi.mock("node-cache", () => {
+ return {
+ default: vi.fn().mockImplementation(() => ({
+ set: vi.fn(),
+ get: vi.fn().mockReturnValue(null),
+ })),
+ };
+});
+
+// Mock path module
+vi.mock("path", async () => {
+ const actual = await vi.importActual("path");
+ return {
+ ...actual,
+ join: vi.fn().mockImplementation((...args) => args.join("/")),
+ };
+});
+
+// Mock the ICacheManager
+const mockCacheManager = {
+ get: vi.fn().mockResolvedValue(null),
+ set: vi.fn(),
+ delete: vi.fn(),
+};
+
+describe("WalletProvider", () => {
+ let walletProvider;
+ let mockedRuntime;
+
+ beforeEach(() => {
+ vi.clearAllMocks();
+ mockCacheManager.get.mockResolvedValue(null);
+
+ mockedRuntime = {
+ character: defaultCharacter,
+ getSetting: vi.fn().mockImplementation((key: string) => {
+ // this is a testnet private key
+ if (key === "STORY_PRIVATE_KEY")
+ return "0x1ad065323caa081ab78d6f4fd2b52181e09cf29a4e60bd7519997b2d03fa44f3";
+ return undefined;
+ }),
+ };
+
+ // Create new instance of WalletProvider with mocked dependencies
+ walletProvider = new WalletProvider(mockedRuntime);
+ });
+
+ afterEach(() => {
+ vi.clearAllTimers();
+ });
+
+ describe("Wallet Integration", () => {
+ it("should check wallet address", async () => {
+ const address = await walletProvider.getAddress();
+ expect(address).toEqual(walletProvider.address);
+ });
+ });
+});
diff --git a/packages/plugin-story/src/types/api.ts b/packages/plugin-story/src/types/api.ts
new file mode 100644
index 00000000000..5ff30f7960e
--- /dev/null
+++ b/packages/plugin-story/src/types/api.ts
@@ -0,0 +1,575 @@
+import { Address, Hash } from "viem";
+
+export enum ACTION_RESPONSE_TYPE {
+ SET = "SET",
+ ATTACH = "ATTACH",
+ CREATE = "CREATE",
+ REGISTER = "REGISTER",
+ REMOVE = "REMOVE",
+}
+
+export enum RESOURCE_TYPE {
+ LICENSE_TOKEN = "licenses/tokens", // new version
+ LICENSE_TEMPLATES = "licenses/templates", // new version
+ LICENSE_TERMS = "licenses/terms", // new version
+ IP_LICENSE_TERMS = "licenses/ip/terms", // new version
+ IP_LICENSE_DETAILS = "detailed-ip-license-terms", // new version
+ ASSET = "assets",
+ COLLECTION = "collections",
+ DISPUTE = "disputes",
+ LICENSE_MINT_FEES = "licenses/mintingfees",
+ MODULE = "modules",
+ PERMISSION = "permissions",
+ ROYALTY = "royalties",
+ ROYALTY_PAY = "royalties/payments",
+ ROYALTY_POLICY = "royalties/policies",
+ ROYALTY_SPLIT = "royalties/splits",
+ TAGS = "tags",
+ TRANSACTION = "transactions",
+ LATEST_TRANSACTIONS = "transactions/latest",
+}
+
+export enum RESPOURCE_REPONSE_TYPE {
+ LICENSE_TOKEN = "LICENSETOKEN", // new version
+ LICENSE_TEMPLATES = "LICENSETEMPLATE", // new version
+ LICENSE_TERMS = "LICENSETERM", // new version
+ IP_LICENSE_TERMS = "licenses/ip/terms", // new version
+ IP_LICENSE_DETAILS = "detailed-ip-license-terms", // new version
+ ASSET = "IPASSET",
+ COLLECTION = "COLLECTION",
+ DISPUTE = "DISPUTE",
+ LICENSE_MINT_FEES = "licenses/mintingfees",
+ MODULE = "modules",
+ PERMISSION = "PERMISSION",
+ ROYALTY = "ROYALTY",
+ ROYALTY_PAY = "royalties/payments",
+ ROYALTY_POLICY = "ROYALTYPOLICY",
+ ROYALTY_SPLIT = "royalties/splits",
+ TAGS = "tags",
+}
+
+export type ResourceType =
+ | RESOURCE_TYPE.ASSET
+ | RESOURCE_TYPE.COLLECTION
+ | RESOURCE_TYPE.TRANSACTION
+ | RESOURCE_TYPE.LATEST_TRANSACTIONS
+ | RESOURCE_TYPE.LICENSE_TOKEN
+ | RESOURCE_TYPE.LICENSE_TERMS
+ | RESOURCE_TYPE.LICENSE_TEMPLATES
+ | RESOURCE_TYPE.IP_LICENSE_TERMS
+ | RESOURCE_TYPE.IP_LICENSE_DETAILS
+ | RESOURCE_TYPE.LICENSE_MINT_FEES
+ | RESOURCE_TYPE.MODULE
+ | RESOURCE_TYPE.PERMISSION
+ | RESOURCE_TYPE.TAGS
+ | RESOURCE_TYPE.ROYALTY
+ | RESOURCE_TYPE.ROYALTY_PAY
+ | RESOURCE_TYPE.ROYALTY_POLICY
+ | RESOURCE_TYPE.ROYALTY_SPLIT
+ | RESOURCE_TYPE.DISPUTE;
+
+export type PaginationOptions = {
+ limit?: number;
+ offset?: number;
+};
+
+export type AssetFilterOptions = {
+ chainId?: string;
+ metadataResolverAddress?: string;
+ tokenContract?: string;
+ tokenId?: string;
+};
+
+export type DisputeFilterOptions = {
+ currentTag?: string;
+ initiator?: string;
+ targetIpId?: string;
+ targetTag?: string;
+};
+
+export type PermissionFilterOptions = {
+ signer?: string;
+ to?: string;
+};
+
+export type PolicyFilterOptions = {
+ policyFrameworkManager?: string;
+};
+
+export type PolicyFrameworkFilterOptions = {
+ address?: string;
+ name?: string;
+};
+
+export type RoyaltyFilterOptions = {
+ ipId?: string | null;
+ royaltyPolicy?: string | null;
+};
+
+export type TagFilterOptions = {
+ ipId?: string;
+ tag?: string;
+};
+export type RoyaltyPayFilterOptions = {
+ ipId?: string;
+ payerIpId?: string;
+ receiverIpId?: string;
+ sender?: string;
+ token?: string;
+};
+
+export type ModuleFilterOptions = {
+ name?: string;
+};
+
+export type LicenseFilterOptions = {
+ licensorIpId?: string;
+ policyId?: string;
+};
+
+export type LicenseFrameworkFilterOptions = {
+ creator?: string;
+};
+
+export type IPAPolicyFilterOptions = {
+ active?: string;
+ inherited?: string;
+ policyId?: string;
+};
+
+export type TransactionFilterOptions = {
+ actionType?: string;
+ resourceId?: string;
+};
+
+export type FilterOptions =
+ | AssetFilterOptions
+ | DisputeFilterOptions
+ | PermissionFilterOptions
+ | PolicyFilterOptions
+ | PolicyFrameworkFilterOptions
+ | RoyaltyFilterOptions
+ | TagFilterOptions
+ | RoyaltyPayFilterOptions
+ | ModuleFilterOptions
+ | LicenseFilterOptions
+ | LicenseFrameworkFilterOptions
+ | IPAPolicyFilterOptions
+ | TransactionFilterOptions;
+
+export type QueryHeaders =
+ | {
+ "x-api-key": string;
+ "x-chain": string;
+ "x-extend-asset"?: string;
+ }
+ | {};
+
+export enum QUERY_ORDER_BY {
+ BLOCK_TIMESTAMP = "blockTimestamp",
+ BLOCK_NUMBER = "blockNumber",
+ TOKEN_ID = "tokenId",
+ ASSET_COUNT = "assetCount",
+ LICENSES_COUNT = "licensesCount",
+ DESCENDANT_COUNT = "descendantCount",
+ // PARENTS = "parentIpIds",
+}
+
+export enum QUERY_ORDER_DIRECTION {
+ ASC = "asc",
+ DESC = "desc",
+}
+
+export type QueryOptions = {
+ chain?: string | number;
+ pagination?: PaginationOptions;
+ where?: FilterOptions;
+ orderBy?: QUERY_ORDER_BY;
+ orderDirection?: QUERY_ORDER_DIRECTION;
+};
+
+export type Transaction = {
+ id: string;
+ createdAt: string;
+ actionType: string;
+ initiator: Address;
+ ipId: Address;
+ resourceId: Address;
+ resourceType: string;
+ blockNumber: string;
+ blockTimestamp: string;
+ logIndex: string;
+ transactionIndex: string;
+ tx_hash: Hash;
+};
+
+export type AssetNFTMetadata = {
+ name: string;
+ chainId: string;
+ tokenContract: Address;
+ tokenId: string;
+ tokenUri: string;
+ imageUrl: string;
+};
+
+export type Permission = {
+ id: string;
+ permission: string;
+ signer: Address;
+ to: Address;
+ func: string;
+ blockNumber: string;
+ blockTimestamp: string;
+};
+
+export type PolicyFramework = {
+ id: string;
+ address: Address;
+ name: string;
+ blockNumber: string;
+ blockTimestamp: string;
+};
+
+export type Module = {
+ id: string;
+ name: string;
+ module: string;
+ blockNumber: string;
+ blockTimestamp: string;
+ deletedAt: string;
+};
+
+export type Tag = {
+ id: string;
+ uuid: string;
+ ipId: Address;
+ tag: string;
+ deletedAt: string;
+ blockNumber: string;
+ blockTimestamp: string;
+};
+
+export type IPAPolicy = {
+ id: string;
+ ipId: Address;
+ policyId: Address;
+ index: string;
+ active: boolean;
+ inherited: boolean;
+ blockNumber: string;
+ blockTimestamp: string;
+};
+
+export type RoyaltyPay = {
+ id: string;
+ receiverIpId: Address;
+ payerIpId: Address;
+ sender: Address;
+ token: Address;
+ amount: string;
+ blockNumber: string;
+ blockTimestamp: string;
+};
+
+export type Royalty = {
+ id: string;
+ ipId: Address;
+ data: string;
+ royaltyPolicy: Address;
+ blockNumber: string;
+ blockTimestamp: string;
+};
+
+export type Dispute = {
+ id: string;
+ targetIpId: Address;
+ targetTag: Address;
+ currentTag: Address;
+ arbitrationPolicy: Address;
+ evidenceLink: string;
+ initiator: Address;
+ data: string;
+ blockNumber: string;
+ blockTimestamp: string;
+};
+
+export type Collection = {
+ id: string;
+ assetCount: string;
+ licensesCount: string;
+ resolvedDisputeCount: string;
+ cancelledDisputeCount: string;
+ raisedDisputeCount: string;
+ judgedDisputeCount: string;
+ blockNumber: string;
+ blockTimestamp: string;
+};
+
+export type Policy = {
+ id: string;
+ policyFrameworkManager: Address;
+ frameworkData: string;
+ royaltyPolicy: Address;
+ royaltyData: string;
+ mintingFee: string;
+ mintingFeeToken: Address;
+ blockNumber: string;
+ blockTimestamp: string;
+ pil: PILType;
+};
+
+export type PILType = {
+ id: Hash;
+ attribution: boolean;
+ commercialUse: boolean;
+ commercialAttribution: boolean;
+ commercializerChecker: Address;
+ commercializerCheckerData: string;
+ commercialRevShare: string;
+ derivativesAllowed: boolean;
+ derivativesAttribution: boolean;
+ derivativesApproval: boolean;
+ derivativesReciprocal: boolean;
+ territories: string[];
+ distributionChannels: string[];
+ contentRestrictions: string[];
+};
+
+export type RoyaltySplit = {
+ id: Address;
+ holders: RoyaltyHolder[];
+ claimFromIPPoolArg: string;
+};
+
+export type RoyaltyHolder = {
+ id: Address;
+ ownership: string;
+};
+
+export type LicenseToken = {
+ id: string;
+ licensorIpId: Address;
+ licenseTemplate: Address;
+ licenseTermsId: string;
+ transferable: boolean;
+ owner: Address;
+ mintedAt: string;
+ expiresAt: string;
+ burntAt: string;
+ blockNumber: string;
+ blockTime: string;
+};
+
+export type LicenseTemplate = {
+ id: string;
+ name: string;
+ metadataUri: string;
+ blockNumber: string;
+ blockTime: string;
+};
+
+export type SocialMedia = {
+ platform?: string;
+ url?: string;
+};
+
+export type Creator = {
+ name?: string;
+ address?: Address;
+ description?: string;
+ contributionPercent?: number;
+ socialMedia?: SocialMedia[];
+};
+
+export interface IPMetadata {
+ title?: string;
+ description?: string;
+ ipType?: string;
+ creators?: Creator[];
+ appInfo?: {
+ id?: string;
+ name?: string;
+ website?: string;
+ }[];
+ relationships?: {
+ parentIpId?: Address;
+ type?: string;
+ }[];
+ robotTerms?: {
+ userAgent?: string;
+ allow?: string;
+ };
+ [key: string]: any;
+}
+
+export interface AssetMetadata {
+ id: Address;
+ metadataHash: string;
+ metadataUri: string;
+ metadataJson: IPMetadata;
+ nftMetadataHash: string;
+ nftTokenUri: string;
+ registrationDate: string;
+}
+
+export type UserCollection = {
+ id?: number;
+ user_id?: number;
+ tx_hash?: Hash;
+ chain?: string;
+ chain_id?: string;
+ collection_address?: Address;
+ collection_name?: string;
+ collection_thumb?: string;
+ collection_banner?: string;
+ collection_description?: string;
+ created_at?: string;
+ updated_at?: string;
+ User?: null;
+};
+
+export enum PIL_FLAVOR {
+ NON_COMMERCIAL_SOCIAL_REMIXING = "Non-Commercial Social Remixing",
+ COMMERCIAL_USE = "Commercial Use",
+ COMMERCIAL_REMIX = "Commercial Remix",
+ CUSTOM = "Custom",
+ // OPEN_DOMAIN = "Open Domain",
+ // NO_DERIVATIVE = "No Derivative",
+}
+
+export type PilFlavor =
+ | PIL_FLAVOR.NON_COMMERCIAL_SOCIAL_REMIXING
+ | PIL_FLAVOR.COMMERCIAL_USE
+ | PIL_FLAVOR.COMMERCIAL_REMIX
+ | PIL_FLAVOR.CUSTOM;
+
+export type Asset = {
+ id: Address;
+ ancestorCount: number;
+ descendantCount: number;
+ parentCount?: number;
+ childCount?: number;
+ rootCount?: number;
+ parentIpIds: Address[] | null;
+ childIpIds: Address[] | null;
+ rootIpIds: Address[] | null;
+ parentIps?: Asset[] | null;
+ rootIps?: Asset[] | null;
+ childIps?: Asset[] | null;
+ nftMetadata: {
+ name: string;
+ chainId: string;
+ tokenContract: Address;
+ tokenId: string;
+ tokenUri: string;
+ imageUrl: string;
+ };
+ blockNumber: string;
+ blockTimestamp: string;
+};
+
+export type AssetEdges = {
+ ipId: Address;
+ parentIpId: Address;
+ blockNumber: string;
+ blockTime: string;
+ licenseTemplate: Address;
+ licenseTermsId: string;
+ licenseTokenId: string;
+ transactionHash: string;
+ transactionIndex: string;
+};
+
+export type License = {
+ id: string;
+ licensorIpId: Address;
+ licenseTemplate: string;
+ licenseTermsId: string;
+ transferable: boolean;
+ owner: Address;
+ mintedAt: string;
+ expiresAt: string;
+ burntAt: string;
+ blockNumber: string;
+ blockTime: string;
+};
+
+export type PILTerms = {
+ commercialAttribution: boolean;
+ commercialRevenueCelling: number;
+ commercialRevenueShare: number;
+ commercialUse: boolean;
+ commercializerCheck: Address;
+ currency: Address;
+ derivativesAllowed: boolean;
+ derivativesApproval: boolean;
+ derivativesAttribution: boolean;
+ derivativesReciprocal: boolean;
+ derivativesRevenueCelling: number;
+ expiration: string;
+ uRI: string;
+};
+
+export type IPLicenseDetails = {
+ id: string;
+ ipId: Address;
+ licenseTemplateId: string;
+ licenseTemplate: {
+ id: string;
+ name: string;
+ metadataUri: string;
+ blockNumber: string;
+ blockTime: string;
+ };
+ terms: PILTerms;
+};
+export type IPLicenseTerms = {
+ id: string;
+ ipId: Address;
+ licenseTemplate: string;
+ licenseTermsId: string;
+ blockNumber: string;
+ blockTime: string;
+};
+
+export type RoyaltyPolicy = {
+ id: Address;
+ ipRoyaltyVault: Address;
+ splitClone: Address;
+ royaltyStack: string;
+ targetAncestors: Address[];
+ targetRoyaltyAmount: string[];
+ blockNumber: string;
+ blockTimestamp: string;
+};
+
+export interface Trait {
+ trait_type: string;
+ value: string | number;
+ max_value?: number;
+}
+
+export type LicenseTerms = {
+ id: string;
+ // json: string
+ licenseTerms: Trait[];
+ licenseTemplate: Address;
+ blockNumber: string;
+ blockTime: string;
+};
+
+export interface AssetMetadata {
+ id: Address;
+ metadataHash: string;
+ metadataUri: string;
+ metadataJson: IPMetadata;
+ nftMetadataHash: string;
+ nftTokenUri: string;
+ registrationDate: string;
+}
+
+export interface Trait {
+ trait_type: string;
+ value: string | number;
+ max_value?: number;
+}
diff --git a/packages/plugin-story/src/types/index.ts b/packages/plugin-story/src/types/index.ts
new file mode 100644
index 00000000000..722985a1cc6
--- /dev/null
+++ b/packages/plugin-story/src/types/index.ts
@@ -0,0 +1,118 @@
+import type { Token } from "@lifi/types";
+import type {
+ Account,
+ Address,
+ Chain,
+ Hash,
+ HttpTransport,
+ PublicClient,
+ WalletClient,
+} from "viem";
+
+export type SupportedChain = "odyssey";
+
+// Transaction types
+export interface Transaction {
+ hash: Hash;
+ from: Address;
+ to: Address;
+ value: bigint;
+ data?: `0x${string}`;
+ chainId?: number;
+}
+
+// Token types
+export interface TokenWithBalance {
+ token: Token;
+ balance: bigint;
+ formattedBalance: string;
+ priceUSD: string;
+ valueUSD: string;
+}
+
+export interface WalletBalance {
+ chain: SupportedChain;
+ address: Address;
+ totalValueUSD: string;
+ tokens: TokenWithBalance[];
+}
+
+// Chain configuration
+export interface ChainMetadata {
+ chainId: number;
+ name: string;
+ chain: Chain;
+ rpcUrl: string;
+ nativeCurrency: {
+ name: string;
+ symbol: string;
+ decimals: number;
+ };
+ blockExplorerUrl: string;
+}
+
+export interface ChainConfig {
+ chain: Chain;
+ publicClient: PublicClient;
+ walletClient?: WalletClient;
+}
+
+// Action parameters
+export interface RegisterIPParams {
+ title: string;
+ description: string;
+ ipType: string;
+}
+
+export interface LicenseIPParams {
+ licensorIpId: Address;
+ licenseTermsId: string;
+ amount: number;
+}
+
+export interface AttachTermsParams {
+ ipId: Address;
+ mintingFee: number;
+ commercialUse: boolean;
+ commercialRevShare: number;
+}
+
+// Plugin configuration
+export interface EvmPluginConfig {
+ rpcUrl?: {
+ ethereum?: string;
+ base?: string;
+ };
+ secrets?: {
+ EVM_PRIVATE_KEY: string;
+ };
+ testMode?: boolean;
+ multicall?: {
+ batchSize?: number;
+ wait?: number;
+ };
+}
+
+// Provider types
+export interface TokenData extends Token {
+ symbol: string;
+ decimals: number;
+ address: Address;
+ name: string;
+ logoURI?: string;
+ chainId: number;
+}
+
+export interface TokenPriceResponse {
+ priceUSD: string;
+ token: TokenData;
+}
+
+export interface TokenListResponse {
+ tokens: TokenData[];
+}
+
+export interface ProviderError extends Error {
+ code?: number;
+ data?: unknown;
+}
diff --git a/packages/plugin-story/tsconfig.json b/packages/plugin-story/tsconfig.json
new file mode 100644
index 00000000000..2d8d3fe8181
--- /dev/null
+++ b/packages/plugin-story/tsconfig.json
@@ -0,0 +1,15 @@
+{
+ "extends": "../core/tsconfig.json",
+ "compilerOptions": {
+ "outDir": "dist",
+ "rootDir": "./src",
+ "typeRoots": [
+ "./node_modules/@types",
+ "./src/types"
+ ],
+ "declaration": true
+ },
+ "include": [
+ "src"
+ ]
+}
\ No newline at end of file
diff --git a/packages/plugin-story/tsup.config.ts b/packages/plugin-story/tsup.config.ts
new file mode 100644
index 00000000000..a68ccd636ad
--- /dev/null
+++ b/packages/plugin-story/tsup.config.ts
@@ -0,0 +1,21 @@
+import { defineConfig } from "tsup";
+
+export default defineConfig({
+ entry: ["src/index.ts"],
+ outDir: "dist",
+ sourcemap: true,
+ clean: true,
+ format: ["esm"], // Ensure you're targeting CommonJS
+ external: [
+ "dotenv", // Externalize dotenv to prevent bundling
+ "fs", // Externalize fs to use Node.js built-in module
+ "path", // Externalize other built-ins if necessary
+ "@reflink/reflink",
+ "@node-llama-cpp",
+ "https",
+ "http",
+ "agentkeepalive",
+ "viem",
+ "@lifi/sdk",
+ ],
+});
diff --git a/packages/plugin-sui/.npmignore b/packages/plugin-sui/.npmignore
new file mode 100644
index 00000000000..078562eceab
--- /dev/null
+++ b/packages/plugin-sui/.npmignore
@@ -0,0 +1,6 @@
+*
+
+!dist/**
+!package.json
+!readme.md
+!tsup.config.ts
\ No newline at end of file
diff --git a/packages/plugin-sui/eslint.config.mjs b/packages/plugin-sui/eslint.config.mjs
new file mode 100644
index 00000000000..92fe5bbebef
--- /dev/null
+++ b/packages/plugin-sui/eslint.config.mjs
@@ -0,0 +1,3 @@
+import eslintGlobalConfig from "../../eslint.config.mjs";
+
+export default [...eslintGlobalConfig];
diff --git a/packages/plugin-sui/package.json b/packages/plugin-sui/package.json
new file mode 100644
index 00000000000..1aee92e29b8
--- /dev/null
+++ b/packages/plugin-sui/package.json
@@ -0,0 +1,26 @@
+{
+ "name": "@ai16z/plugin-sui",
+ "version": "0.1.5-alpha.5",
+ "main": "dist/index.js",
+ "type": "module",
+ "types": "dist/index.d.ts",
+ "dependencies": {
+ "@ai16z/eliza": "workspace:*",
+ "@ai16z/plugin-trustdb": "workspace:*",
+ "@mysten/sui": "^1.16.0",
+ "bignumber": "1.1.0",
+ "bignumber.js": "9.1.2",
+ "node-cache": "5.1.2",
+ "tsup": "8.3.5",
+ "vitest": "2.1.4"
+ },
+ "scripts": {
+ "build": "tsup --format esm --dts",
+ "lint": "eslint . --fix",
+ "test": "vitest run"
+ },
+ "peerDependencies": {
+ "form-data": "4.0.1",
+ "whatwg-url": "7.1.0"
+ }
+}
diff --git a/packages/plugin-sui/src/actions/transfer.ts b/packages/plugin-sui/src/actions/transfer.ts
new file mode 100644
index 00000000000..29e9acf0256
--- /dev/null
+++ b/packages/plugin-sui/src/actions/transfer.ts
@@ -0,0 +1,214 @@
+import {
+ ActionExample,
+ Content,
+ HandlerCallback,
+ IAgentRuntime,
+ Memory,
+ ModelClass,
+ State,
+ composeContext,
+ elizaLogger,
+ generateObject,
+ type Action,
+} from "@ai16z/eliza";
+import { z } from "zod";
+
+import { SuiClient, getFullnodeUrl } from "@mysten/sui/client";
+import { Ed25519Keypair } from "@mysten/sui/keypairs/ed25519";
+import { Transaction } from "@mysten/sui/transactions";
+import { SUI_DECIMALS } from "@mysten/sui/utils";
+
+import { walletProvider } from "../providers/wallet";
+
+type SuiNetwork = "mainnet" | "testnet" | "devnet" | "localnet";
+
+export interface TransferContent extends Content {
+ recipient: string;
+ amount: string | number;
+}
+
+function isTransferContent(content: Content): content is TransferContent {
+ console.log("Content for transfer", content);
+ return (
+ typeof content.recipient === "string" &&
+ (typeof content.amount === "string" ||
+ typeof content.amount === "number")
+ );
+}
+
+const transferTemplate = `Respond with a JSON markdown block containing only the extracted values. Use null for any values that cannot be determined.
+
+Example response:
+\`\`\`json
+{
+ "recipient": "0xaa000b3651bd1e57554ebd7308ca70df7c8c0e8e09d67123cc15c8a8a79342b3",
+ "amount": "1"
+}
+\`\`\`
+
+{{recentMessages}}
+
+Given the recent messages, extract the following information about the requested token transfer:
+- Recipient wallet address
+- Amount to transfer
+
+Respond with a JSON markdown block containing only the extracted values.`;
+
+export default {
+ name: "SEND_TOKEN",
+ similes: [
+ "TRANSFER_TOKEN",
+ "TRANSFER_TOKENS",
+ "SEND_TOKENS",
+ "SEND_SUI",
+ "PAY",
+ ],
+ validate: async (runtime: IAgentRuntime, message: Memory) => {
+ console.log("Validating sui transfer from user:", message.userId);
+ //add custom validate logic here
+ /*
+ const adminIds = runtime.getSetting("ADMIN_USER_IDS")?.split(",") || [];
+ //console.log("Admin IDs from settings:", adminIds);
+
+ const isAdmin = adminIds.includes(message.userId);
+
+ if (isAdmin) {
+ //console.log(`Authorized transfer from user: ${message.userId}`);
+ return true;
+ }
+ else
+ {
+ //console.log(`Unauthorized transfer attempt from user: ${message.userId}`);
+ return false;
+ }
+ */
+ return true;
+ },
+ description: "Transfer tokens from the agent's wallet to another address",
+ handler: async (
+ runtime: IAgentRuntime,
+ message: Memory,
+ state: State,
+ _options: { [key: string]: unknown },
+ callback?: HandlerCallback
+ ): Promise => {
+ elizaLogger.log("Starting SEND_TOKEN handler...");
+
+ const walletInfo = await walletProvider.get(runtime, message, state);
+ state.walletInfo = walletInfo;
+
+ // Initialize or update state
+ if (!state) {
+ state = (await runtime.composeState(message)) as State;
+ } else {
+ state = await runtime.updateRecentMessageState(state);
+ }
+
+ // Define the schema for the expected output
+ const transferSchema = z.object({
+ recipient: z.string(),
+ amount: z.union([z.string(), z.number()]),
+ });
+
+ // Compose transfer context
+ const transferContext = composeContext({
+ state,
+ template: transferTemplate,
+ });
+
+ // Generate transfer content with the schema
+ const content = await generateObject({
+ runtime,
+ context: transferContext,
+ schema: transferSchema,
+ modelClass: ModelClass.SMALL,
+ });
+
+ const transferContent = content.object as TransferContent;
+
+ // Validate transfer content
+ if (!isTransferContent(transferContent)) {
+ console.error("Invalid content for TRANSFER_TOKEN action.");
+ if (callback) {
+ callback({
+ text: "Unable to process transfer request. Invalid content provided.",
+ content: { error: "Invalid transfer content" },
+ });
+ }
+ return false;
+ }
+
+ try {
+ const privateKey = runtime.getSetting("SUI_PRIVATE_KEY");
+ const suiAccount = Ed25519Keypair.deriveKeypair(privateKey);
+ const network = runtime.getSetting("SUI_NETWORK");
+ const suiClient = new SuiClient({
+ url: getFullnodeUrl(network as SuiNetwork),
+ });
+
+ const adjustedAmount = BigInt(
+ Number(transferContent.amount) * Math.pow(10, SUI_DECIMALS)
+ );
+ console.log(
+ `Transferring: ${transferContent.amount} tokens (${adjustedAmount} base units)`
+ );
+ const tx = new Transaction();
+ const [coin] = tx.splitCoins(tx.gas, [adjustedAmount]);
+ tx.transferObjects([coin], transferContent.recipient);
+ const executedTransaction =
+ await suiClient.signAndExecuteTransaction({
+ signer: suiAccount,
+ transaction: tx,
+ });
+
+ console.log("Transfer successful:", executedTransaction.digest);
+
+ if (callback) {
+ callback({
+ text: `Successfully transferred ${transferContent.amount} SUI to ${transferContent.recipient}, Transaction: ${executedTransaction.digest}`,
+ content: {
+ success: true,
+ hash: executedTransaction.digest,
+ amount: transferContent.amount,
+ recipient: transferContent.recipient,
+ },
+ });
+ }
+
+ return true;
+ } catch (error) {
+ console.error("Error during token transfer:", error);
+ if (callback) {
+ callback({
+ text: `Error transferring tokens: ${error.message}`,
+ content: { error: error.message },
+ });
+ }
+ return false;
+ }
+ },
+
+ examples: [
+ [
+ {
+ user: "{{user1}}",
+ content: {
+ text: "Send 1 SUI tokens to 0x4f2e63be8e7fe287836e29cde6f3d5cbc96eefd0c0e3f3747668faa2ae7324b0",
+ },
+ },
+ {
+ user: "{{user2}}",
+ content: {
+ text: "I'll send 1 SUI tokens now...",
+ action: "SEND_TOKEN",
+ },
+ },
+ {
+ user: "{{user2}}",
+ content: {
+ text: "Successfully sent 1 SUI tokens to 0x4f2e63be8e7fe287836e29cde6f3d5cbc96eefd0c0e3f3747668faa2ae7324b0, Transaction: 0x39a8c432d9bdad993a33cc1faf2e9b58fb7dd940c0425f1d6db3997e4b4b05c0",
+ },
+ },
+ ],
+ ] as ActionExample[][],
+} as Action;
diff --git a/packages/plugin-sui/src/enviroment.ts b/packages/plugin-sui/src/enviroment.ts
new file mode 100644
index 00000000000..5c1fadc31af
--- /dev/null
+++ b/packages/plugin-sui/src/enviroment.ts
@@ -0,0 +1,36 @@
+import { IAgentRuntime } from "@ai16z/eliza";
+import { z } from "zod";
+
+export const suiEnvSchema = z.object({
+ SUI_PRIVATE_KEY: z.string().min(1, "Sui private key is required"),
+ SUI_NETWORK: z.enum(["mainnet", "testnet"]),
+});
+
+export type SuiConfig = z.infer;
+
+export async function validateSuiConfig(
+ runtime: IAgentRuntime
+): Promise {
+ try {
+ const config = {
+ SUI_PRIVATE_KEY:
+ runtime.getSetting("SUI_PRIVATE_KEY") ||
+ process.env.SUI_PRIVATE_KEY,
+ SUI_NETWORK:
+ runtime.getSetting("SUI_NETWORK") ||
+ process.env.SUI_NETWORK,
+ };
+
+ return suiEnvSchema.parse(config);
+ } catch (error) {
+ if (error instanceof z.ZodError) {
+ const errorMessages = error.errors
+ .map((err) => `${err.path.join(".")}: ${err.message}`)
+ .join("\n");
+ throw new Error(
+ `Sui configuration validation failed:\n${errorMessages}`
+ );
+ }
+ throw error;
+ }
+}
diff --git a/packages/plugin-sui/src/index.ts b/packages/plugin-sui/src/index.ts
new file mode 100644
index 00000000000..964003a18d0
--- /dev/null
+++ b/packages/plugin-sui/src/index.ts
@@ -0,0 +1,15 @@
+import { Plugin } from "@ai16z/eliza";
+import transferToken from "./actions/transfer.ts";
+import { WalletProvider, walletProvider } from "./providers/wallet.ts";
+
+export { WalletProvider, transferToken as TransferSuiToken };
+
+export const suiPlugin: Plugin = {
+ name: "sui",
+ description: "Sui Plugin for Eliza",
+ actions: [transferToken],
+ evaluators: [],
+ providers: [walletProvider],
+};
+
+export default suiPlugin;
diff --git a/packages/plugin-sui/src/providers/wallet.ts b/packages/plugin-sui/src/providers/wallet.ts
new file mode 100644
index 00000000000..70038c6128f
--- /dev/null
+++ b/packages/plugin-sui/src/providers/wallet.ts
@@ -0,0 +1,246 @@
+import {
+ IAgentRuntime,
+ ICacheManager,
+ Memory,
+ Provider,
+ State,
+} from "@ai16z/eliza";
+
+
+import { getFullnodeUrl, SuiClient } from "@mysten/sui/client";
+import { Ed25519Keypair } from '@mysten/sui/keypairs/ed25519';
+
+import { MIST_PER_SUI } from "@mysten/sui/utils";
+import BigNumber from "bignumber.js";
+import NodeCache from "node-cache";
+import * as path from "path";
+
+// Provider configuration
+const PROVIDER_CONFIG = {
+ MAX_RETRIES: 3,
+ RETRY_DELAY: 2000,
+};
+
+interface WalletPortfolio {
+ totalUsd: string;
+ totalSui: string;
+}
+
+interface Prices {
+ sui: { usd: string };
+}
+
+type SuiNetwork = "mainnet" | "testnet" | "devnet" | "localnet";
+
+export class WalletProvider {
+ private cache: NodeCache;
+ private cacheKey: string = "sui/wallet";
+
+ constructor(
+ private suiClient: SuiClient,
+ private address: string,
+ private cacheManager: ICacheManager
+ ) {
+ this.cache = new NodeCache({ stdTTL: 300 }); // Cache TTL set to 5 minutes
+ }
+
+ private async readFromCache(key: string): Promise {
+ const cached = await this.cacheManager.get