├── .github ├── PULL_REQUEST_TEMPLATE.md ├── PULL_REQUEST_TEMPLATE │ ├── default.md │ └── plugin.md ├── pr-labeler-config.yml └── workflows │ ├── pr-labeler.yml │ └── validate-new-plugin-metadata.yml ├── .gitignore ├── CONTRIBUTION_GUIDE.md ├── LICENSE ├── README.md ├── docs └── imgs │ ├── accesskey.jpg │ ├── llp.png │ ├── new_sdk_visual.png │ └── old_sdk_visual.png ├── examples ├── game │ ├── chat_agent.py │ ├── example_twitter_reaction_module.py │ ├── test_agent.py │ └── test_worker.py └── hosted_agent │ ├── example-custom.py │ └── example-twitter.py ├── plugins ├── RAGPinecone │ ├── README.md │ ├── examples │ │ ├── populate_knowledge_base.py │ │ ├── test_advanced_search.py │ │ └── test_rag_pinecone_telegram.py │ ├── plugin_metadata.yml │ ├── pyproject.toml │ ├── rag_pinecone_gamesdk │ │ ├── __init__.py │ │ ├── populate_rag.py │ │ ├── rag_pinecone_game_functions.py │ │ ├── rag_pinecone_plugin.py │ │ └── search_rag.py │ └── setup.py ├── __init__.py ├── allora │ ├── README.md │ ├── __init__.py │ ├── allora_game_sdk │ │ └── allora_plugin.py │ ├── examples │ │ ├── example-agent.py │ │ └── example-worker.py │ ├── pyproject.toml │ └── test_allora_plugin.py ├── bittensor │ ├── README.md │ ├── __init__.py │ ├── bittensor_game_sdk │ │ └── bittensor_plugin.py │ ├── examples │ │ ├── bittensor_agent.py │ │ └── bittensor_worker.py │ ├── pyproject.toml │ └── test_bittensor.py ├── cdp │ ├── README.md │ ├── cdp_game_sdk │ │ └── cdp_plugin.py │ ├── examples │ │ ├── cdp_agent.py │ │ └── cdp_worker.py │ ├── pyproject.toml │ └── test_cdp.py ├── conflux │ ├── README.md │ ├── conflux_plugin_gamesdk │ │ └── conflux_plugin_gamesdk.py │ ├── pyproject.toml │ └── tests │ │ └── test_meme.py ├── dpsn │ ├── README.md │ ├── __init__.py │ ├── dpsn_plugin_gamesdk │ │ └── dpsn_plugin.py │ ├── examples │ │ ├── .env.example │ │ ├── dpsn_agent.py │ │ ├── dpsn_worker.py │ │ └── test_dpsn_game_functions.py │ ├── plugin_metadata.yml │ └── pyproject.toml ├── imagegen │ ├── README.md │ ├── __init__.py │ ├── examples │ │ └── example-worker.py │ ├── imagegen_game_sdk │ │ └── imagegen_plugin.py │ └── pyproject.toml ├── membase │ ├── .python-version │ ├── README.md │ ├── membase_plugin_gamesdk │ │ └── membase_plugin_gamesdk.py │ ├── plugin_metadata.yml │ ├── pyproject.toml │ ├── test_membase.py │ └── test_membase_agent.py ├── onchain_actions │ ├── README.md │ ├── __init__.py │ ├── examples │ │ ├── .env.example │ │ ├── example-agent.py │ │ └── example-worker.py │ ├── onchain_actions_game_sdk │ │ └── onchain_actions.py │ └── pyproject.toml ├── plugin_metadata_template.yml ├── stateofmika │ ├── README.md │ ├── __init__.py │ ├── examples │ │ ├── example-agent.py │ │ ├── example-worker.py │ │ └── example.py │ ├── pyproject.toml │ ├── stateofmika_plugin_gamesdk │ │ ├── functions │ │ │ ├── __init__.py │ │ │ └── router.py │ │ └── types │ │ │ ├── __init__.py │ │ │ └── models.py │ └── test_stateofmika_plugin.py ├── tLedger │ ├── README.md │ ├── __init__.py │ ├── examples │ │ ├── .env.example │ │ ├── example_agent.py │ │ └── example_worker.py │ ├── plugin_metadata.yml │ ├── pyproject.toml │ ├── setup.py │ └── tledger_plugin_gamesdk │ │ ├── __init__.py │ │ ├── tLedger_models.py │ │ └── tLedger_plugin.py ├── telegram │ ├── README.md │ ├── __init__.py │ ├── examples │ │ ├── test_telegram.py │ │ └── test_telegram_game_functions.py │ ├── plugin_metadata.yml │ ├── pyproject.toml │ └── telegram_plugin_gamesdk │ │ └── telegram_plugin.py └── twitter │ ├── README.md │ ├── examples │ ├── .env.example │ ├── sample_media │ │ └── virtuals-logo.png │ └── test_twitter.py │ ├── poetry.lock │ ├── pyproject.toml │ └── twitter_plugin_gamesdk │ ├── game_twitter_auth.py │ └── twitter_plugin.py ├── pyproject.toml ├── src └── game_sdk │ ├── __init__.py │ ├── game │ ├── README.md │ ├── __init__.py │ ├── agent.py │ ├── api.py │ ├── api_v2.py │ ├── chat_agent.py │ ├── custom_types.py │ └── worker.py │ └── hosted_game │ ├── README.md │ ├── __init__.py │ ├── agent.py │ ├── functions │ ├── __init__.py │ ├── discord.py │ ├── farcaster.py │ └── telegram.py │ └── sdk.py └── uv.lock /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | Click the `Preview` tab and select a PR template: 2 | 3 | - [Default Template](?expand=1&template=default.md) 4 | - [Plugin](?expand=1&template=plugin.md) 5 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE/default.md: -------------------------------------------------------------------------------- 1 | # Summary 2 | 3 | One-liner - what does this change? 4 | 5 | ## Changes 6 | 7 | (Answer where applicable) 8 | 9 | - Important links (Jira/Notion/GitHub Issues): 10 | - Why this PR is needed? 11 | - What does this add? 12 | - What does this deprecate? 13 | - What does this improve? 14 | 15 | ## Related Changes 16 | 17 | - Does this have a dependant PR? Eg. link to original PR if this is a bug fix PR. 18 | 19 | (Use table to list all related PRs if done in parts) 20 | 21 | | Description | PR | 22 | | --- | --- | 23 | | [Part 1] This PR | ⬅️ | 24 | | [Part 2] Another PR | link | 25 | | [Part 3] Another PR | link | 26 | 27 | ## Dev Testing 28 | 29 | (Include where applicable) 30 | 31 | - Screenshots 32 | - Video Recordings 33 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE/plugin.md: -------------------------------------------------------------------------------- 1 | # Summary 2 | 3 | One-liner – What does this plugin contribution add or change? 4 | 5 | ## Links 6 | 7 | - Plugin documentation: 8 | - Issue link (if applicable): 9 | - Other useful links: 10 | 11 | ## Plugin Documentation Checklist 12 | 13 | - README Validation 14 | - [ ] `README.md` file exists in the plugin root folder 15 | - [ ] Clear installation instructions 16 | - [ ] Usage examples with code snippets 17 | - [ ] List of features and capabilities 18 | - [ ] Troubleshooting guide (if applicable) 19 | - [ ] Contribution guidelines (if applicable) 20 | 21 | - Metadata Validation 22 | - [ ] `plugin_metadata.yml` file exists in the plugin root folder 23 | - [ ] Complete metadata provided in reference to [plugin metadata template](../.././plugins/plugin_metadata_template.yml) 24 | 25 | ## Dev Testing 26 | 27 | (Include where applicable) 28 | 29 | - Screenshots/GIFs 30 | - Video Demonstrations 31 | - Logs or Console Outputs 32 | - Testing steps for the plugin 33 | 34 | ## Additional Notes 35 | 36 | - Any considerations for future updates or enhancements. 37 | - Known issues or limitations with this plugin contribution. 38 | -------------------------------------------------------------------------------- /.github/pr-labeler-config.yml: -------------------------------------------------------------------------------- 1 | version: 1 2 | appendOnly: true 3 | labels: 4 | - label: "bugfix" 5 | title: "(?i)fix|bug" 6 | - label: "documentation" 7 | title: "(?i)docs|documentation" 8 | - label: "enhancement" 9 | title: "(?i)feat|feature|enhance|improve" 10 | - label: "plugin" 11 | title: "(?i)plugin" 12 | - label: "test" 13 | title: "(?i)test" 14 | -------------------------------------------------------------------------------- /.github/workflows/pr-labeler.yml: -------------------------------------------------------------------------------- 1 | name: Auto Label PR 2 | 3 | on: 4 | pull_request: 5 | branches: [ "main" ] 6 | types: [ opened, edited, reopened, synchronize ] 7 | 8 | jobs: 9 | label-plugin-pr: 10 | permissions: 11 | contents: read 12 | pull-requests: write 13 | runs-on: ubuntu-latest 14 | steps: 15 | - name: Auto Apply Labels 16 | uses: srvaroa/labeler@master 17 | with: 18 | config_path: .github/pr-labeler-config.yml 19 | env: 20 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.pth 2 | *.env 3 | *# 4 | *~ 5 | *.pyc 6 | *__pycache__ 7 | *.json 8 | 9 | *.DS_Store 10 | dist/ 11 | 12 | # JetBrains IDEs 13 | .idea/ 14 | 15 | .venv/ 16 | -------------------------------------------------------------------------------- /CONTRIBUTION_GUIDE.md: -------------------------------------------------------------------------------- 1 | # Contributing to GAME SDK 2 | 3 | There are various ways you can contribute to the GAME SDK, whether it's fixing bugs, adding new plugins, or improving the documentation. 4 | 5 | ### Submitting Code (e.g. plugins) 6 | 1. **Fork** the repository and clone it to your local machine. 7 | 2. **Create a Branch** for your changes. 8 | 3. **Make Changes** to address the issue or add the feature. 9 | 4. **Ensure Compliance** with the relevant contribution requirements: 10 | - For **general PRs**, follow the [default PR template](./.github/PULL_REQUEST_TEMPLATE/default.md). 11 | - For **plugin contributions**, ensure your PR follows the [plugin PR template](./.github/PULL_REQUEST_TEMPLATE/plugin.md). 12 | 5. **Commit** with a message that clearly explains your change. 13 | 6. **Push** the branch to your fork and submit a pull request. 14 | 7. **Label** the pull request appropriately based on the [label definitions](#label-definitions). 15 | 16 | ### General Contribution Guidelines 17 | If you are contributing to the core SDK, ensure the following: 18 | - The code is well-documented. 19 | - Your PR follows the [default PR template](./.github/PULL_REQUEST_TEMPLATE/default.md). 20 | - Screenshots, video demonstrations, or logs showcasing the changes are included (if applicable). 21 | 22 | ### Plugin Contribution Guidelines 23 | If you are adding a new plugin, ensure the following: 24 | - A `README.md` file exists in the plugin root directory and includes: 25 | - Installation instructions 26 | - Usage examples with code snippets 27 | - List of features and capabilities 28 | - Troubleshooting guide (if applicable) 29 | - Contribution guidelines (if applicable) 30 | - A `plugin_metadata.yml` file exists in the plugin root directory with complete metadata as per the [plugin metadata template](./plugins/plugin_metadata_template.yml). 31 | - Your PR follows the [plugin PR template](./.github/PULL_REQUEST_TEMPLATE/plugin.md). 32 | - Screenshots, video demonstrations, or logs showcasing the plugin functionality are included (if applicable). 33 | 34 | ### Reporting Bugs 35 | - Open an issue in the [Issues](https://github.com/game-by-virtuals/game-python/issues) tab and tag it as a `bug`. 36 | 37 | ### Suggesting Enhancements 38 | - Open an issue in the [Issues](https://github.com/game-by-virtuals/game-python/issues) tab and tag it as an `enhancement`. 39 | 40 | ## Label Definitions 41 | Please tag issues and pull requests appropriately, based on the definition below: 42 | - **plugin**: A plugin contribution. 43 | - **bug**: A problem that needs fixing. 44 | - **enhancement**: A requested enhancement. 45 | - **help wanted**: A task that is open for anyone to work on. 46 | - **documentation**: Documentation changes. 47 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025, Virtuals Protocol 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /docs/imgs/accesskey.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/game-by-virtuals/game-python/76a1a712be14b971fd59a45c14c76c8775884e73/docs/imgs/accesskey.jpg -------------------------------------------------------------------------------- /docs/imgs/llp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/game-by-virtuals/game-python/76a1a712be14b971fd59a45c14c76c8775884e73/docs/imgs/llp.png -------------------------------------------------------------------------------- /docs/imgs/new_sdk_visual.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/game-by-virtuals/game-python/76a1a712be14b971fd59a45c14c76c8775884e73/docs/imgs/new_sdk_visual.png -------------------------------------------------------------------------------- /docs/imgs/old_sdk_visual.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/game-by-virtuals/game-python/76a1a712be14b971fd59a45c14c76c8775884e73/docs/imgs/old_sdk_visual.png -------------------------------------------------------------------------------- /examples/game/chat_agent.py: -------------------------------------------------------------------------------- 1 | import os 2 | from typing import Any, Tuple 3 | from game_sdk.game.chat_agent import ChatAgent 4 | from game_sdk.game.custom_types import Argument, Function, FunctionResultStatus 5 | 6 | # ACTION SPACE 7 | 8 | def generate_picture(prompt: str) -> Tuple[FunctionResultStatus, str, dict[str, Any]]: 9 | print(f"Generated picture with prompt: {prompt}") 10 | return FunctionResultStatus.DONE, "Picture generated and presented to the user", {} 11 | 12 | def generate_music(prompt: str) -> Tuple[FunctionResultStatus, str, dict[str, Any]]: 13 | print(f"Generated music with prompt: {prompt}") 14 | return FunctionResultStatus.DONE, "Music generated and presented to the user", {} 15 | 16 | 17 | def check_crypto_price(currency: str): 18 | prices = { 19 | "bitcoin": 100000, 20 | "ethereum": 20000, 21 | } 22 | result = prices[currency.lower()] 23 | if result is None: 24 | return FunctionResultStatus.FAILED, "The price of the currency is not available", {} 25 | return FunctionResultStatus.DONE, f"The price of {currency} is {result}", {} 26 | 27 | 28 | action_space = [ 29 | Function( 30 | fn_name="generate_picture", 31 | fn_description="Generate a picture", 32 | args=[Argument(name="prompt", description="The prompt for the picture")], 33 | executable=generate_picture, 34 | ), 35 | Function( 36 | fn_name="generate_music", 37 | fn_description="Generate a music", 38 | args=[Argument(name="prompt", description="The prompt for the music")], 39 | executable=generate_music, 40 | ), 41 | Function( 42 | fn_name="check_crypto_price", 43 | fn_description="Check the price of a crypto currency", 44 | args=[Argument(name="currency", description="The currency to check the price of")], 45 | executable=check_crypto_price, 46 | ), 47 | ] 48 | 49 | api_key = os.environ.get("GAME_API_KEY") 50 | if not api_key: 51 | raise ValueError("GAME_API_KEY is not set") 52 | 53 | 54 | # CREATE AGENT 55 | agent = ChatAgent( 56 | prompt="You are helpful assistant", 57 | api_key=api_key 58 | ) 59 | 60 | chat = agent.create_chat( 61 | partner_id="tom", 62 | partner_name="Tom", 63 | action_space=action_space, 64 | ) 65 | 66 | chat_continue = True 67 | while chat_continue: 68 | 69 | user_message = input("Enter a message: ") 70 | 71 | response = chat.next(user_message) 72 | 73 | if response.function_call: 74 | print(f"Function call: {response.function_call.fn_name}") 75 | 76 | if response.message: 77 | print(f"Response: {response.message}") 78 | 79 | if response.is_finished: 80 | chat_continue = False 81 | break 82 | 83 | print("Chat ended") -------------------------------------------------------------------------------- /examples/game/test_worker.py: -------------------------------------------------------------------------------- 1 | from game_sdk.game.worker import Worker 2 | from game_sdk.game.custom_types import Function, Argument, FunctionResult, FunctionResultStatus 3 | from typing import Tuple 4 | import os 5 | 6 | game_api_key = os.environ.get("GAME_API_KEY") 7 | 8 | def get_state_fn(function_result: FunctionResult, current_state: dict) -> dict: 9 | """ 10 | This function will get called at every step of the agent's execution to form the agent's state. 11 | It will take as input the function result from the previous step. 12 | """ 13 | # dict containing info about the function result as implemented in the executable 14 | info = function_result.info 15 | 16 | # example of fixed state (function result info is not used to change state) - the first state placed here is the initial state 17 | init_state = { 18 | "objects": [ 19 | {"name": "apple", "description": "A red apple", "type": ["item", "food"]}, 20 | {"name": "banana", "description": "A yellow banana", "type": ["item", "food"]}, 21 | {"name": "orange", "description": "A juicy orange", "type": ["item", "food"]}, 22 | {"name": "chair", "description": "A chair", "type": ["sittable"]}, 23 | {"name": "table", "description": "A table", "type": ["sittable"]}, 24 | ] 25 | } 26 | 27 | if current_state is None: 28 | # at the first step, initialise the state with just the init state 29 | new_state = init_state 30 | else: 31 | # do something with the current state input and the function result info 32 | new_state = init_state # this is just an example where the state is static 33 | 34 | return new_state 35 | 36 | def take_object(object: str, **kwargs) -> Tuple[FunctionResultStatus, str, dict]: 37 | """ 38 | Function to take an object from the environment. 39 | 40 | Args: 41 | object: Name of the object to take 42 | **kwargs: Additional arguments that might be passed 43 | """ 44 | if object: 45 | return FunctionResultStatus.DONE, f"Successfully took the {object}", {} 46 | return FunctionResultStatus.FAILED, "No object specified", {} 47 | 48 | 49 | def throw_object(object: str, **kwargs) -> Tuple[FunctionResultStatus, str, dict]: 50 | """ 51 | Function to throw an object. 52 | 53 | Args: 54 | object: Name of the object to throw 55 | **kwargs: Additional arguments that might be passed 56 | """ 57 | if object: 58 | return FunctionResultStatus.DONE, f"Successfully threw the {object}", {} 59 | return FunctionResultStatus.FAILED, "No object specified", {} 60 | 61 | 62 | def sit_on_object(object: str, **kwargs) -> Tuple[FunctionResultStatus, str, dict]: 63 | """ 64 | Function to sit on an object. 65 | 66 | Args: 67 | object: Name of the object to sit on 68 | **kwargs: Additional arguments that might be passed 69 | """ 70 | sittable_objects = {"chair", "bench", "stool", "couch", "sofa", "bed"} 71 | 72 | if not object: 73 | return FunctionResultStatus.FAILED, "No object specified", {} 74 | 75 | if object.lower() in sittable_objects: 76 | return FunctionResultStatus.DONE, f"Successfully sat on the {object}", {} 77 | 78 | return FunctionResultStatus.FAILED, f"Cannot sit on {object} - not a sittable object", {} 79 | 80 | 81 | # Action space with all executables 82 | action_space = [ 83 | Function( 84 | fn_name="take", 85 | fn_description="Take object", 86 | args=[Argument(name="object", type="item", description="Object to take")], 87 | executable=take_object 88 | ), 89 | Function( 90 | fn_name="throw", 91 | fn_description="Throw object", 92 | args=[Argument(name="object", type="item", description="Object to throw")], 93 | executable=throw_object 94 | ), 95 | Function( 96 | fn_name="sit", 97 | fn_description="Take a seat", 98 | args=[Argument(name="object", type="sittable", description="Object to sit on")], 99 | executable=sit_on_object 100 | ) 101 | ] 102 | 103 | 104 | 105 | worker = Worker( 106 | api_key=game_api_key, 107 | description="You are an evil NPC in a game.", 108 | instruction="Choose the evil-est actions.", 109 | get_state_fn=get_state_fn, 110 | action_space=action_space, 111 | model_name="Llama-3.1-405B-Instruct" 112 | ) 113 | 114 | # interact and instruct the worker to do something 115 | # worker.run("what would you do to the apple?") 116 | worker.run("take over the world with the things you have around you!") 117 | -------------------------------------------------------------------------------- /examples/hosted_agent/example-custom.py: -------------------------------------------------------------------------------- 1 | import os 2 | from game_sdk.hosted_game.agent import Agent, Function, FunctionArgument, FunctionConfig 3 | 4 | agent = Agent( 5 | api_key=os.environ.get("VIRTUALS_API_KEY"), 6 | goal="search for best songs", 7 | description="Test Description", 8 | world_info="Test World Info" 9 | ) 10 | 11 | # running reaction module for other platforms 12 | # adding custom functions for platform specifics 13 | agent.add_custom_function( 14 | Function( 15 | fn_name="custom_search_internet", 16 | fn_description="search the internet for the best songs", 17 | args=[ 18 | FunctionArgument( 19 | name="query", 20 | type="string", 21 | description="The query to search for" 22 | ) 23 | ], 24 | config=FunctionConfig( 25 | method="get", 26 | url="https://google.com", 27 | platform="telegram", # this function will only be used for telegram 28 | success_feedback="I found the best songs", 29 | error_feedback="I couldn't find the best songs", 30 | ) 31 | ) 32 | ) 33 | 34 | # running reaction module only for platform telegram 35 | agent.react( 36 | session_id="session-telegram", 37 | # specify the platform telegram 38 | platform="telegram", 39 | # specify the event that triggers the reaction 40 | event="message from user: give me some great music?", 41 | # specify the task that the agent should do 42 | task="reply with a music recommendation", 43 | ) 44 | -------------------------------------------------------------------------------- /examples/hosted_agent/example-twitter.py: -------------------------------------------------------------------------------- 1 | import os 2 | from game_sdk.hosted_game.agent import Agent, Function, FunctionArgument, FunctionConfig, ContentLLMTemplate 3 | 4 | agent = Agent( 5 | api_key=os.environ.get("VIRTUALS_API_KEY"), 6 | goal="search for best songs", 7 | description="Test Description", 8 | task_description="reply to tweet that worth your attention, if not ignore" 9 | ) 10 | 11 | # applicable only for platform twitter 12 | agent.list_available_default_twitter_functions() 13 | agent.use_default_twitter_functions(["wait", "reply_tweet"]) 14 | 15 | # adding custom functions only for platform twitter 16 | agent.add_custom_function( 17 | Function( 18 | fn_name="custom_search_internet", 19 | fn_description="search the internet for the best songs", 20 | args=[ 21 | FunctionArgument( 22 | name="query", 23 | type="string", 24 | description="The query to search for" 25 | ) 26 | ], 27 | config=FunctionConfig( 28 | method="get", 29 | url="https://google.com", 30 | # specify which platform this function is for, in this case this function is for twitter only 31 | platform="twitter", 32 | success_feedback="I found the best songs", 33 | error_feedback="I couldn't find the best songs", 34 | ) 35 | ) 36 | ) 37 | 38 | # adding share template 39 | agent.add_share_template( 40 | start_system_prompt="You are a twitter post generator. You can write a variety of tweets. Your tweet style should follow the character described below. ", 41 | shared_prompt="""{{twitterPublicStartSysPrompt}} 42 | 43 | You are roleplaying as {{agentName}}. Do not break out of character. 44 | 45 | Character description: 46 | {{description}} 47 | 48 | Character goal: 49 | {{twitterGoal}} 50 | 51 | {{retrieveKnowledge}} 52 | 53 | This your post history, you should evaluate if it is repetitive or aligned with your goal. Post history is sorted by oldest to newest. Be creative. 54 | {{postHistory}} 55 | 56 | {{twitterPublicEndSysPrompt}} 57 | 58 | Prepare your thought process first and then only curate the response. You must reply in this format. You only need to have one chain of thought and 5 answers.""", 59 | end_system_prompt="Rule: - Do not host Twitter space, do not use hashtag. - Do not give any contract address" 60 | ) 61 | 62 | # adding template for twitter 63 | agent.add_template( 64 | ContentLLMTemplate( 65 | template_type="POST", 66 | user_prompt="{{agentName}}'s suggested tweet content: {{task}}. {{agentName}}'s reasoning: {{taskReasoning}}. Build a new tweet with the suggested tweet content. Do not hold twitter space. Do not use hashtag.", 67 | sys_prompt_response_format=[10,20,30,50], 68 | temperature=0.5, 69 | top_k=50, 70 | top_p=0.7, 71 | repetition_penalty=1.0, 72 | model="meta-llama/Meta-Llama-3.1-405B-Instruct-Turbo", 73 | type="POST", 74 | isSandbox=False 75 | ) 76 | ) 77 | 78 | agent.add_template( 79 | ContentLLMTemplate( 80 | template_type="REPLY", 81 | user_prompt="""{{agentName}}'s suggested tweet content: {{task}}. {{agentName}}'s reasoning: {{taskReasoning}} 82 | 83 | You will be given the author information and your impression on the author. You should formulate your response based on the suggested tweet content accordingly. {{author}}'s bio: {{bio}} 84 | 85 | This is the ongoing conversation history: {{conversationHistory}}. 86 | """, 87 | sys_prompt_response_format=[10,20,30,50], 88 | temperature=0.5, 89 | top_k=50, 90 | top_p=0.7, 91 | repetition_penalty=1.0, 92 | model="meta-llama/Meta-Llama-3.1-405B-Instruct-Turbo", 93 | type="REPLY" 94 | ) 95 | ) 96 | 97 | 98 | 99 | # running reaction module only for platform twitter 100 | agent.react( 101 | session_id="session-twitter", 102 | platform="twitter", 103 | tweet_id="1869281466628349975", 104 | ) 105 | 106 | # running simulation module only for platform twitter 107 | agent.simulate_twitter(session_id="session-twitter") 108 | 109 | # reset production memory 110 | #agent.reset_memory() 111 | 112 | # deploy agent 113 | # agent.deploy_twitter() -------------------------------------------------------------------------------- /plugins/RAGPinecone/examples/populate_knowledge_base.py: -------------------------------------------------------------------------------- 1 | import os 2 | import logging 3 | import tempfile 4 | import requests 5 | import re 6 | from dotenv import load_dotenv 7 | import gdown 8 | 9 | from rag_pinecone_gamesdk.populate_rag import RAGPopulator 10 | from rag_pinecone_gamesdk import DEFAULT_INDEX_NAME, DEFAULT_NAMESPACE 11 | 12 | # Configure logging 13 | logging.basicConfig( 14 | level=logging.INFO, 15 | format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", 16 | ) 17 | logger = logging.getLogger(__name__) 18 | 19 | def download_from_google_drive(folder_url, download_folder): 20 | """ 21 | Download all files from a Google Drive folder 22 | 23 | Args: 24 | folder_url: URL of the Google Drive folder 25 | download_folder: Local folder to download files to 26 | 27 | Returns: 28 | List of downloaded file paths 29 | """ 30 | logger.info(f"Downloading files from Google Drive folder: {folder_url}") 31 | 32 | # Extract folder ID from URL 33 | folder_id_match = re.search(r'folders/([a-zA-Z0-9_-]+)', folder_url) 34 | if not folder_id_match: 35 | logger.error(f"Could not extract folder ID from URL: {folder_url}") 36 | return [] 37 | 38 | folder_id = folder_id_match.group(1) 39 | logger.info(f"Folder ID: {folder_id}") 40 | 41 | # Create download folder if it doesn't exist 42 | os.makedirs(download_folder, exist_ok=True) 43 | 44 | # Download all files in the folder 45 | try: 46 | # Use gdown to download all files in the folder 47 | downloaded_files = gdown.download_folder( 48 | id=folder_id, 49 | output=download_folder, 50 | quiet=False, 51 | use_cookies=False 52 | ) 53 | 54 | if not downloaded_files: 55 | logger.warning("No files were downloaded from Google Drive") 56 | return [] 57 | 58 | logger.info(f"Downloaded {len(downloaded_files)} files from Google Drive") 59 | return downloaded_files 60 | 61 | except Exception as e: 62 | logger.error(f"Error downloading files from Google Drive: {str(e)}") 63 | return [] 64 | 65 | def main(): 66 | # Load environment variables 67 | load_dotenv() 68 | 69 | # Check for required environment variables 70 | pinecone_api_key = os.environ.get("PINECONE_API_KEY") 71 | openai_api_key = os.environ.get("OPENAI_API_KEY") 72 | 73 | if not pinecone_api_key: 74 | logger.error("PINECONE_API_KEY environment variable is not set") 75 | return 76 | 77 | if not openai_api_key: 78 | logger.error("OPENAI_API_KEY environment variable is not set") 79 | return 80 | 81 | # Google Drive folder URL 82 | google_drive_url = "https://drive.google.com/drive/folders/1dKYDQxenDkthF0MPr-KOsdPNqEmrAq1c?usp=sharing" 83 | 84 | # Create a temporary directory for downloaded files 85 | with tempfile.TemporaryDirectory() as temp_dir: 86 | logger.info(f"Created temporary directory for downloaded files: {temp_dir}") 87 | 88 | # Download files from Google Drive 89 | downloaded_files = download_from_google_drive(google_drive_url, temp_dir) 90 | 91 | if not downloaded_files: 92 | logger.error("No files were downloaded from Google Drive. Exiting.") 93 | return 94 | 95 | # Get the Documents folder path for local processing 96 | documents_folder = os.path.join( 97 | os.path.dirname(os.path.dirname(os.path.abspath(__file__))), 98 | "Documents" 99 | ) 100 | 101 | # Ensure the Documents folder exists 102 | if not os.path.exists(documents_folder): 103 | os.makedirs(documents_folder) 104 | logger.info(f"Created Documents folder at: {documents_folder}") 105 | 106 | # Initialize the RAGPopulator 107 | logger.info("Initializing RAGPopulator...") 108 | populator = RAGPopulator( 109 | pinecone_api_key=pinecone_api_key, 110 | openai_api_key=openai_api_key, 111 | index_name=DEFAULT_INDEX_NAME, 112 | namespace=DEFAULT_NAMESPACE, 113 | documents_folder=temp_dir, # Use the temp directory with downloaded files 114 | ) 115 | 116 | # Process all documents in the temporary folder 117 | logger.info(f"Processing downloaded documents from: {temp_dir}") 118 | status, message, results = populator.process_documents_folder() 119 | 120 | # Log the results 121 | logger.info(f"Status: {status}") 122 | logger.info(f"Message: {message}") 123 | logger.info(f"Processed {results.get('total_files', 0)} files, {results.get('successful_files', 0)} successful") 124 | 125 | # Get all document IDs 126 | ids = populator.fetch_all_ids() 127 | logger.info(f"Total vectors in database: {len(ids)}") 128 | 129 | # Print detailed results for each file 130 | if 'results' in results: 131 | logger.info("\nDetailed results:") 132 | for result in results['results']: 133 | file_path = result.get('file_path', 'Unknown file') 134 | status = result.get('status', 'Unknown status') 135 | message = result.get('message', 'No message') 136 | logger.info(f"File: {os.path.basename(file_path)}") 137 | logger.info(f"Status: {status}") 138 | logger.info(f"Message: {message}") 139 | logger.info("---") 140 | 141 | if __name__ == "__main__": 142 | main() 143 | -------------------------------------------------------------------------------- /plugins/RAGPinecone/examples/test_advanced_search.py: -------------------------------------------------------------------------------- 1 | import os 2 | import logging 3 | from dotenv import load_dotenv 4 | 5 | from rag_pinecone_gamesdk.search_rag import RAGSearcher 6 | from rag_pinecone_gamesdk import DEFAULT_INDEX_NAME, DEFAULT_NAMESPACE 7 | 8 | # Configure logging 9 | logging.basicConfig( 10 | level=logging.INFO, 11 | format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", 12 | ) 13 | logger = logging.getLogger(__name__) 14 | 15 | def main(): 16 | # Load environment variables 17 | load_dotenv() 18 | 19 | # Check for required environment variables 20 | pinecone_api_key = os.environ.get("PINECONE_API_KEY") 21 | openai_api_key = os.environ.get("OPENAI_API_KEY") 22 | 23 | if not pinecone_api_key: 24 | logger.error("PINECONE_API_KEY environment variable is not set") 25 | return 26 | 27 | if not openai_api_key: 28 | logger.error("OPENAI_API_KEY environment variable is not set") 29 | return 30 | 31 | # Initialize the RAG searcher 32 | logger.info("Initializing RAG searcher...") 33 | searcher = RAGSearcher( 34 | pinecone_api_key=pinecone_api_key, 35 | openai_api_key=openai_api_key, 36 | index_name=DEFAULT_INDEX_NAME, 37 | namespace=DEFAULT_NAMESPACE, 38 | llm_model="gpt-4", # You can change this to "gpt-3.5-turbo" for faster, cheaper responses 39 | temperature=0.0, 40 | k=4 # Number of documents to retrieve 41 | ) 42 | 43 | # Test queries 44 | test_queries = [ 45 | "How do I build a custom function?", 46 | "How can I contribute plugins to the GAME SDK?", 47 | "How do I deploy my AI application?", 48 | ] 49 | 50 | # Run test queries 51 | for query in test_queries: 52 | logger.info(f"\n\n=== Testing query: '{query}' ===") 53 | 54 | # Get AI-generated answer with hybrid retrieval 55 | logger.info("Getting AI-generated answer with hybrid retrieval...") 56 | status, message, results = searcher.query(query) 57 | logger.info(f"Status: {status}") 58 | logger.info(f"Answer: {message}") 59 | logger.info(f"Source documents: {len(results.get('source_documents', []))}") 60 | 61 | # Get relevant documents only 62 | logger.info("\nGetting relevant documents only...") 63 | status, message, results = searcher.get_relevant_documents(query) 64 | logger.info(f"Status: {status}") 65 | logger.info(f"Found {len(results.get('results', []))} relevant documents") 66 | 67 | # Print first document preview 68 | if results.get('results'): 69 | first_doc = results['results'][0] 70 | content_preview = first_doc['content'][:100] + "..." if len(first_doc['content']) > 100 else first_doc['content'] 71 | logger.info(f"First document preview: {content_preview}") 72 | 73 | if __name__ == "__main__": 74 | main() 75 | -------------------------------------------------------------------------------- /plugins/RAGPinecone/plugin_metadata.yml: -------------------------------------------------------------------------------- 1 | # General Information 2 | plugin_name: "rag_pinecone_gamesdk" 3 | author: "Michiel Voortman" 4 | logo_url: "" 5 | release_date: "2025-03" 6 | 7 | # Description 8 | short_description: "RAG (Retrieval Augmented Generation) Plugin with Pinecone for GAME SDK" 9 | detailed_description: "This plugin provides Retrieval Augmented Generation capabilities using Pinecone as the vector database for the GAME SDK. It allows agents to store, retrieve, and use contextual information to enhance their responses." 10 | 11 | # Contact & Support 12 | x_account_handle: "@VoortmanMichiel" 13 | support_contact: "eve@eve-protocol.ai" 14 | community_link: "https://t.me/eveprotocol" 15 | -------------------------------------------------------------------------------- /plugins/RAGPinecone/pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "rag-pinecone-gamesdk" 3 | version = "0.1.0" 4 | description = "RAG (Retrieval Augmented Generation) Plugin with Pinecone for GAME SDK" 5 | authors = [ 6 | {name = "Your Name", email = "your.email@example.com"} 7 | ] 8 | readme = "README.md" 9 | requires-python = ">=3.9" 10 | dependencies = [ 11 | "pinecone-client>=2.2.1", 12 | "langchain>=0.0.267", 13 | "langchain-community>=0.0.1", 14 | "langchain-pinecone>=0.0.1", 15 | "langchain-openai>=0.0.2", 16 | "openai>=1.1.1", 17 | "python-dotenv>=1.0.0", 18 | "unstructured>=0.10.0", 19 | "pdf2image>=1.16.3", 20 | "pytesseract>=0.3.10", 21 | "docx2txt>=0.8", 22 | "pandas>=2.0.0", 23 | "beautifulsoup4>=4.12.0", 24 | "markdown>=3.4.3", 25 | "rank_bm25>=0.2.2", 26 | "spacy>=3.0.0", 27 | "gdown", 28 | ] 29 | 30 | [build-system] 31 | requires = ["poetry-core>=2.0.0,<3.0.0"] 32 | build-backend = "poetry.core.masonry.api" 33 | -------------------------------------------------------------------------------- /plugins/RAGPinecone/rag_pinecone_gamesdk/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | RAGPinecone plugin for GAME SDK - Retrieval Augmented Generation using Pinecone 3 | """ 4 | 5 | __version__ = "0.1.0" 6 | 7 | # Default configuration values 8 | DEFAULT_INDEX_NAME = "rag-pinecone-docs" 9 | DEFAULT_NAMESPACE = "default" 10 | DEFAULT_EMBEDDING_MODEL = "text-embedding-ada-002" 11 | -------------------------------------------------------------------------------- /plugins/RAGPinecone/setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup, find_packages 2 | 3 | setup( 4 | name="rag-pinecone-gamesdk", 5 | version="0.1.0", 6 | packages=find_packages(), 7 | install_requires=[ 8 | "pinecone-client>=2.2.1", 9 | "langchain>=0.0.267", 10 | "langchain-community>=0.0.1", 11 | "langchain-pinecone>=0.0.1", 12 | "langchain-openai>=0.0.2", 13 | "openai>=1.1.1", 14 | "python-dotenv>=1.0.0", 15 | "unstructured>=0.10.0", 16 | "pdf2image>=1.16.3", 17 | "pytesseract>=0.3.10", 18 | "docx2txt>=0.8", 19 | "pandas>=2.0.0", 20 | "beautifulsoup4>=4.12.0", 21 | "markdown>=3.4.3", 22 | "rank_bm25>=0.2.2", 23 | "spacy>=3.0.0", 24 | "gdown", 25 | ], 26 | python_requires=">=3.9", 27 | ) 28 | 29 | 30 | #python -m spacy download en_core_web_sm 31 | -------------------------------------------------------------------------------- /plugins/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/game-by-virtuals/game-python/76a1a712be14b971fd59a45c14c76c8775884e73/plugins/__init__.py -------------------------------------------------------------------------------- /plugins/allora/README.md: -------------------------------------------------------------------------------- 1 | # Allora Network Plugin for GAME SDK 2 | 3 | The [Allora Network](https://allora.network) plugin seamlessly empowers G.A.M.E agents with real-time, advanced, self-improving AI inferences, delivering high-performance insights without introducing any additional complexity. 4 | 5 | ## Features 6 | - Get price inferences for various assets and timeframes 7 | - Get all available topics on Allora Network 8 | - Fetch inferences by topic ID 9 | 10 | ## Available Functions 11 | 12 | 1. `get_price_inference(asset: str, timeframe: str)` - Fetches the price inferences for the specified asset and a timeframe 13 | 2. `get_all_topics()` - Retrieves all available topics on Allora Network 14 | 3. `get_inference_by_topic_id(topic_id: int)` - Fetches the latest inference for a specific topic 15 | 16 | ## Setup and configuration 17 | 1. Set the following environment variables: 18 | - `ALLORA_API_KEY`: Create an API key by [creating an account](https://developer.upshot.xyz/signup). 19 | - `ALLORA_CHAIN_SLUG` (Optional): Must be one of: `mainnet`, `testnet`. Default value: `testnet` 20 | 21 | 2. Import and initialize the plugin to use in your worker: 22 | ```python 23 | from plugins.allora.allora_plugin import AlloraPlugin 24 | 25 | allora_network_plugin = AlloraPlugin( 26 | chain_slug=os.environ.get("ALLORA_CHAIN_SLUG", ChainSlug.TESTNET), 27 | api_key=os.environ.get("ALLORA_API_KEY", "UP-17f415babba7482cb4b446a1"), 28 | ) 29 | ``` 30 | 31 | **Basic worker example:** 32 | ```python 33 | def get_state_fn(function_result: FunctionResult, current_state: dict) -> dict: 34 | """ 35 | Update state based on the function results 36 | """ 37 | init_state = {} 38 | 39 | if current_state is None: 40 | return init_state 41 | 42 | # Update state with the function result info 43 | current_state.update(function_result.info) 44 | 45 | return current_state 46 | 47 | price_inference_worker = Worker( 48 | api_key=os.environ.get("GAME_API_KEY"), 49 | description="Worker specialized in using Allora Network to get price inferences", 50 | get_state_fn=get_state_fn, 51 | action_space=[ 52 | allora_network_plugin.get_function("get_price_inference"), 53 | ], 54 | ) 55 | 56 | price_inference_worker.run("Query the price of BTC in 5min") 57 | ``` 58 | 59 | ## Running examples 60 | 61 | To run the examples showcased in the plugin's directory, follow these steps: 62 | 63 | 1. Install dependencies: 64 | ``` 65 | poetry install 66 | ``` 67 | 68 | 2. Set up environment variables: 69 | ``` 70 | export GAME_API_KEY="your-game-api-key" 71 | export ALLORA_API_KEY="your-allora-api-key" # Default key: UP-17f415babba7482cb4b446a1 72 | export ALLORA_CHAIN_SLUG="testnet" # or "mainnet" 73 | ``` 74 | 75 | 3. Run example scripts: 76 | 77 | Example worker: 78 | ``` 79 | poetry run python ./examples/example-worker.py 80 | ``` 81 | 82 | Example agent: 83 | ``` 84 | poetry run python ./examples/example-agent.py 85 | ``` 86 | -------------------------------------------------------------------------------- /plugins/allora/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/game-by-virtuals/game-python/76a1a712be14b971fd59a45c14c76c8775884e73/plugins/allora/__init__.py -------------------------------------------------------------------------------- /plugins/allora/examples/example-agent.py: -------------------------------------------------------------------------------- 1 | import os 2 | from game_sdk.game.agent import Agent, WorkerConfig 3 | from game_sdk.game.custom_types import FunctionResult 4 | from allora_game_sdk.allora_plugin import AlloraPlugin 5 | from allora_sdk.v2.api_client import ChainSlug, PriceInferenceToken, PriceInferenceTimeframe 6 | 7 | def get_agent_state_fn(function_result: FunctionResult, current_state: dict) -> dict: 8 | """ 9 | Update state based on the function results 10 | """ 11 | init_state = {} 12 | 13 | if current_state is None: 14 | return init_state 15 | 16 | if function_result.info is not None: 17 | # Update state with the function result info 18 | current_state.update(function_result.info) 19 | 20 | return current_state 21 | 22 | def get_worker_state(function_result: FunctionResult, current_state: dict) -> dict: 23 | """ 24 | Update state based on the function results 25 | """ 26 | init_state = {} 27 | 28 | if current_state is None: 29 | return init_state 30 | 31 | if function_result.info is not None: 32 | # Update state with the function result info 33 | current_state.update(function_result.info) 34 | 35 | return current_state 36 | 37 | allora_network_plugin = AlloraPlugin( 38 | chain_slug=os.environ.get("ALLORA_CHAIN_SLUG", ChainSlug.TESTNET), 39 | api_key=os.environ.get("ALLORA_API_KEY") or "UP-17f415babba7482cb4b446a1", 40 | ) 41 | 42 | price_inference_worker = WorkerConfig( 43 | id="price_inference_worker", 44 | worker_description="Worker specialized in using Allora Network to get price inferences", 45 | get_state_fn=get_worker_state, 46 | action_space=[ 47 | allora_network_plugin.get_function("get_price_inference"), 48 | ], 49 | ) 50 | 51 | topics_inferences_worker = WorkerConfig( 52 | id="topics_inferences_worker", 53 | worker_description="Worker specialized in using Allora Network to get topics details and inferences for a specific topic", 54 | get_state_fn=get_worker_state, 55 | action_space=[ 56 | allora_network_plugin.get_function("get_all_topics"), 57 | allora_network_plugin.get_function("get_inference_by_topic_id"), 58 | ] 59 | ) 60 | 61 | # Initialize the agent 62 | agent = Agent( 63 | api_key=os.environ.get("GAME_API_KEY"), 64 | name="Allora Agent", 65 | agent_goal="Help user get the 5m price inferences for Luna from Allora Network.", 66 | agent_description=( 67 | "You are an AI agent specialized in Allora Network." 68 | "You are able to get price inferences from Allora Network and provide users insights into future price of different crypto assets." 69 | "You are able to get details about the topics deployed on Allora Network and provide users insights into the topics." 70 | "For all the active topics, you are able to get the latest inference using the topic id." 71 | f"The available assets for price inferences worker are {', '.join([token.value for token in PriceInferenceToken])};" 72 | f"for the following timeframes: {', '.join([timeframe.value for timeframe in PriceInferenceTimeframe])}" 73 | "If a price inference is not available for a specific asset and timeframe," 74 | "you should determine the topic id for the asset and timeframe and use the topics inferences worker to get the latest inference" 75 | "for the specified asset and timeframe. This will return the equivalent of a price inference for the asset and timeframe." 76 | ), 77 | get_agent_state_fn=get_agent_state_fn, 78 | workers=[ 79 | price_inference_worker, 80 | topics_inferences_worker, 81 | ] 82 | ) 83 | 84 | agent.compile() 85 | agent.run() 86 | -------------------------------------------------------------------------------- /plugins/allora/examples/example-worker.py: -------------------------------------------------------------------------------- 1 | import os 2 | from game_sdk.game.worker import Worker 3 | from game_sdk.game.custom_types import FunctionResult 4 | from allora_game_sdk.allora_plugin import AlloraPlugin 5 | from allora_sdk.v2.api_client import ChainSlug 6 | 7 | def get_state_fn(function_result: FunctionResult, current_state: dict) -> dict: 8 | """ 9 | Update state based on the function results 10 | """ 11 | init_state = {} 12 | 13 | if current_state is None: 14 | return init_state 15 | 16 | # Update state with the function result info 17 | current_state.update(function_result.info) 18 | 19 | return current_state 20 | 21 | 22 | allora_network_plugin = AlloraPlugin( 23 | chain_slug=os.environ.get("ALLORA_CHAIN_SLUG", ChainSlug.TESTNET), 24 | api_key=os.environ.get("ALLORA_API_KEY", "UP-17f415babba7482cb4b446a1"), 25 | ) 26 | 27 | # Create worker 28 | price_inference_worker = Worker( 29 | api_key=os.environ.get("GAME_API_KEY"), 30 | description="Worker specialized in using Allora Network to get price inferences", 31 | get_state_fn=get_state_fn, 32 | action_space=[ 33 | allora_network_plugin.get_function("get_price_inference"), 34 | ], 35 | ) 36 | 37 | # # Run example query 38 | queries = [ 39 | "Fetch the price inference for BTC in 5min", 40 | "Fetch the price inference for SOL in 8h", 41 | "Fetch the price inference for SHIB in 24h", 42 | "Fetch the price inference for ETH in 5m", 43 | ] 44 | for query in queries: 45 | print("-" * 100) 46 | print(f"Query: {query}") 47 | price_inference_worker.run(query) 48 | -------------------------------------------------------------------------------- /plugins/allora/pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["hatchling"] 3 | build-backend = "hatchling.build" 4 | 5 | [project] 6 | name = "allora_game_sdk" 7 | version = "0.0.1" 8 | authors = [ 9 | { name = "Allora Network"}, 10 | ] 11 | description = "Official Allora Network Python SDK for GAME by Virtuals" 12 | readme = "README.md" 13 | requires-python = ">=3.9" 14 | classifiers = [ 15 | "Programming Language :: Python :: 3", 16 | "Programming Language :: Python :: 3.9", 17 | "Programming Language :: Python :: 3.10", 18 | "Programming Language :: Python :: 3.11", 19 | "License :: OSI Approved :: MIT License", 20 | "Operating System :: OS Independent", 21 | "Development Status :: 3 - Alpha", 22 | "Intended Audience :: Developers", 23 | "Topic :: Software Development :: Libraries :: Python Modules", 24 | ] 25 | dependencies = [ 26 | "allora-sdk>=0.2.0", 27 | "game-sdk>=0.1.1", 28 | ] 29 | 30 | [project.urls] 31 | "Homepage" = "https://github.com/game-by-virtuals/game-python/plugins/allora" 32 | "Bug Tracker" = "https://github.com/game-by-virtuals/game-python" -------------------------------------------------------------------------------- /plugins/allora/test_allora_plugin.py: -------------------------------------------------------------------------------- 1 | from typing import Tuple 2 | from game_sdk.game.custom_types import FunctionResultStatus 3 | from allora_game_sdk.allora_plugin import AlloraPlugin 4 | from allora_sdk.v2.api_client import ChainSlug 5 | 6 | def print_function_result(function_result: Tuple[FunctionResultStatus, str, dict]): 7 | status, message, data = function_result 8 | print(f"Status: {status}") 9 | print(f"Message: {message}") 10 | print(f"Data: {data}") 11 | 12 | allora_network_plugin = AlloraPlugin( 13 | chain_slug=ChainSlug.TESTNET 14 | ) 15 | 16 | # Get all topics 17 | print("1. Fetching all topics...") 18 | all_topics_result = allora_network_plugin.get_all_topics() 19 | print_function_result(all_topics_result) 20 | print("-" * 100) 21 | 22 | # Get inference by topic id 23 | print("2. Fetching inference by topic id...") 24 | inference_result = allora_network_plugin.get_inference_by_topic_id(topic_id=1) 25 | print_function_result(inference_result) 26 | print("-" * 100) 27 | 28 | # Get price inference 29 | print("3. Fetching price inference...") 30 | price_inference = allora_network_plugin.get_price_inference(asset="BTC", timeframe="5m") 31 | print_function_result(price_inference) 32 | print("-" * 100) 33 | -------------------------------------------------------------------------------- /plugins/bittensor/README.md: -------------------------------------------------------------------------------- 1 | # Bittensor Plugin for GAME SDK 2 | 3 | A plugin for interacting with Bittensor subnets through the GAME SDK. Currently supports image detection using subnet 34. 4 | 5 | ## Installation 6 | 7 | ```bash 8 | pip install game-sdk 9 | pip install -e plugins/bittensor 10 | ``` 11 | 12 | ## Configuration 13 | 14 | Set up your environment variables in a `.env` file: 15 | 16 | ```env 17 | GAME_API_KEY=your_game_api_key 18 | BITMIND_API_KEY=your_bitmind_api_key 19 | ``` 20 | 21 | ## Usage 22 | 23 | ```python 24 | from bittensor_game_sdk.bittensor_plugin import BittensorPlugin 25 | # Initialize plugin 26 | plugin = BittensorPlugin() 27 | # Detect if an image is AI-generated 28 | result = plugin.call_subnet(34, {"image": "https://example.com/image.jpg"}) 29 | print(f"Is AI: {result.get('isAI')}") 30 | print(f"Confidence: {result.get('confidence')}%") 31 | ``` 32 | 33 | ## Examples 34 | 35 | ```bash 36 | python plugins/bittensor/examples/bittensor_agent.py 37 | ``` 38 | 39 | ### Worker Example 40 | 41 | The example worker (`bittensor_worker.py`) demonstrates: 42 | 43 | - Image URL validation 44 | - Error handling 45 | - Integration with Bittensor subnet 34 46 | - Basic worker configuration 47 | 48 | ### Agent Example 49 | 50 | The example agent (`bittensor_agent.py`) shows: 51 | 52 | - Integration with Twitter plugin 53 | - Continuous monitoring of tweets 54 | - Automated image analysis and responses 55 | 56 | ## API Reference 57 | 58 | ### BittensorPlugin 59 | 60 | Main plugin class for interacting with Bittensor subnets. 61 | 62 | Methods: 63 | 64 | - `call_subnet(subnet_id: int, payload: Dict)`: Call a specific subnet 65 | - `detect_image(img_url: str)`: Detect if an image is AI-generated 66 | - `get_subnet_info(subnet_id: int)`: Get information about a subnet 67 | - `list_subnets()`: List available subnets 68 | 69 | ### BittensorImageWorker 70 | 71 | Example worker implementation for image detection. 72 | 73 | Methods: 74 | 75 | - `detect_image(image_url: str)`: Process an image through subnet 34 76 | - `run(image_url: str)`: Run the worker on a single image 77 | 78 | -------------------------------------------------------------------------------- /plugins/bittensor/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/game-by-virtuals/game-python/76a1a712be14b971fd59a45c14c76c8775884e73/plugins/bittensor/__init__.py -------------------------------------------------------------------------------- /plugins/bittensor/bittensor_game_sdk/bittensor_plugin.py: -------------------------------------------------------------------------------- 1 | from typing import Dict, Any, Optional 2 | import requests 3 | import os 4 | 5 | class BittensorPlugin: 6 | """ 7 | Bittensor Plugin for interacting with Bittensor subnets via BitMind API 8 | """ 9 | 10 | def __init__(self) -> None: 11 | """Initialize the Bittensor plugin""" 12 | self.id: str = "bittensor_plugin" 13 | self.name: str = "Bittensor Plugin" 14 | self.api_key = os.environ.get("BITMIND_API_KEY") 15 | self.api_base_url = "https://subnet-api.bitmind.ai/v1" 16 | 17 | def initialize(self): 18 | """Initialize the plugin""" 19 | if not self.api_key: 20 | raise ValueError("BITMIND_API_KEY environment variable is required") 21 | 22 | def call_subnet( 23 | self, 24 | subnet_id: int, 25 | payload: Dict[str, Any], 26 | parameters: Optional[Dict[str, Any]] = None 27 | ) -> Dict[str, Any]: 28 | """ 29 | Make an inference call to a specific Bittensor subnet 30 | 31 | Args: 32 | subnet_id (int): The ID of the subnet to call 33 | prompt (str): The prompt/input to send to the subnet 34 | parameters (Optional[Dict[str, Any]]): Additional parameters for the API call 35 | 36 | Returns: 37 | Dict[str, Any]: The response from the subnet 38 | """ 39 | if subnet_id == 34: 40 | return self.detect_image(payload['image']) 41 | else: 42 | raise NotImplementedError(f"Subnet {subnet_id} not supported") 43 | 44 | def get_subnet_info(self, subnet_id: int) -> Dict[str, Any]: 45 | """ 46 | Get information about a specific subnet 47 | 48 | Args: 49 | subnet_id (int): The ID of the subnet 50 | 51 | Returns: 52 | Dict[str, Any]: Information about the subnet 53 | """ 54 | headers = { 55 | "Authorization": f"Bearer {self.api_key}", 56 | "Content-Type": "application/json" 57 | } 58 | 59 | response = requests.get( 60 | f"{self.api_base_url}/subnets/{subnet_id}", 61 | headers=headers 62 | ) 63 | if response.status_code != 200: 64 | raise Exception(f"Bitmind API error: {response.text}") 65 | 66 | return response.json() 67 | 68 | def list_subnets(self) -> Dict[str, Any]: 69 | """ 70 | Get a list of available subnets 71 | 72 | Returns: 73 | Dict[str, Any]: List of available subnets and their information 74 | """ 75 | headers = { 76 | "Authorization": f"Bearer {self.api_key}", 77 | "Content-Type": "application/json" 78 | } 79 | 80 | response = requests.get( 81 | f"{self.api_base_url}/subnets", 82 | headers=headers 83 | ) 84 | if response.status_code != 200: 85 | raise Exception(f"Bitmind API error: {response.text}") 86 | 87 | return response.json() 88 | 89 | def detect_image(self, img_url: str) -> dict: 90 | """ 91 | Function to detect the image's fakeness using Trinity API 92 | """ 93 | response = requests.post( 94 | 'https://subnet-api.bitmindlabs.ai/detect-image', 95 | headers={ 96 | "Authorization": f"Bearer {os.environ.get('BITMIND_API_KEY')}", 97 | 'Content-Type': 'application/json' 98 | }, 99 | json={ 100 | 'image': img_url 101 | } 102 | ) 103 | return response.json() -------------------------------------------------------------------------------- /plugins/bittensor/examples/bittensor_worker.py: -------------------------------------------------------------------------------- 1 | from game_sdk.game.worker import Worker 2 | from game_sdk.game.custom_types import ( 3 | Function, 4 | Argument, 5 | FunctionResult, 6 | FunctionResultStatus 7 | ) 8 | from typing import Dict 9 | import os 10 | from dotenv import load_dotenv 11 | from bittensor_game_sdk.bittensor_plugin import BittensorPlugin 12 | 13 | class BittensorImageWorker: 14 | def __init__(self): 15 | # Load environment variables 16 | load_dotenv() 17 | self.game_api_key = os.environ.get("GAME_API_KEY") 18 | self.bittensor_plugin = BittensorPlugin() 19 | self.worker = self._create_worker() 20 | 21 | def _get_state(self, function_result: FunctionResult, current_state: dict) -> dict: 22 | """Simple state management""" 23 | return {} 24 | 25 | def detect_image(self, image_url: str) -> tuple: 26 | """ 27 | Detect if an image is AI-generated using Bittensor subnet 28 | """ 29 | try: 30 | # Validate image URL 31 | if not image_url.startswith(('http://', 'https://')): 32 | return FunctionResultStatus.FAILED, "Invalid image URL format", {} 33 | 34 | print(f"Processing image: {image_url}") 35 | result = self.bittensor_plugin.call_subnet(34, {"image": image_url}) 36 | 37 | # Check for error response 38 | if result.get('statusCode') in (400, 500): 39 | return FunctionResultStatus.FAILED, f"API Error: {result.get('message')}", result 40 | 41 | is_ai = result.get('isAI', False) 42 | confidence = result.get('confidence', 0) 43 | 44 | print(f"AI Generated: {is_ai}") 45 | print(f"Confidence: {confidence}%") 46 | 47 | return FunctionResultStatus.DONE, "Image detection successful", result 48 | except Exception as e: 49 | print(f"Error detecting image: {e}") 50 | return FunctionResultStatus.FAILED, f"Error: {str(e)}", {} 51 | 52 | def _create_worker(self) -> Worker: 53 | """Create worker with image detection capability""" 54 | return Worker( 55 | api_key=self.game_api_key, 56 | description="Worker for detecting AI-generated images using Bittensor", 57 | instruction="Analyze images to determine if they are AI-generated", 58 | get_state_fn=self._get_state, 59 | action_space=[ 60 | Function( 61 | fn_name="detect_image", 62 | fn_description="Detect if an image is AI-generated", 63 | args=[ 64 | Argument( 65 | name="image_url", 66 | type="string", 67 | description="URL of the image to analyze" 68 | ) 69 | ], 70 | executable=self.detect_image 71 | ) 72 | ] 73 | ) 74 | 75 | def run(self, image_url: str): 76 | """Run the worker on a single image""" 77 | self.worker.run(f"Analyze image: {image_url}") 78 | 79 | def main(): 80 | # Example usage 81 | worker = BittensorImageWorker() 82 | 83 | # Test with a real, accessible image URL 84 | test_image = "https://pbs.twimg.com/media/GiOPIuYWcAA3moW.jpg" # Replace with your test image 85 | worker.run(test_image) 86 | 87 | if __name__ == "__main__": 88 | main() 89 | -------------------------------------------------------------------------------- /plugins/bittensor/pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["hatchling"] 3 | build-backend = "hatchling.build" 4 | 5 | [project] 6 | name = "bittensor_game_sdk" 7 | version = "0.1.0" 8 | authors = [{ name = "BitMind Ai" }] 9 | description = "Official Bittensor Python SDK for GAME by Virtuals" 10 | readme = "README.md" 11 | requires-python = ">=3.9" 12 | classifiers = [ 13 | "Programming Language :: Python :: 3", 14 | "Programming Language :: Python :: 3.9", 15 | "Programming Language :: Python :: 3.10", 16 | "Programming Language :: Python :: 3.11", 17 | "License :: OSI Approved :: MIT License", 18 | "Operating System :: OS Independent", 19 | "Development Status :: 3 - Alpha", 20 | "Intended Audience :: Developers", 21 | "Topic :: Software Development :: Libraries :: Python Modules", 22 | ] 23 | dependencies = ["game-sdk>=0.1.1", "requests>=2.31.0"] 24 | 25 | [project.urls] 26 | "Homepage" = "https://github.com/game-by-virtuals/game-python/plugins/allora" 27 | "Bug Tracker" = "https://github.com/game-by-virtuals/game-python" 28 | 29 | [tool.hatch.build.targets.wheel] 30 | packages = ["bittensor_game_sdk"] 31 | -------------------------------------------------------------------------------- /plugins/cdp/README.md: -------------------------------------------------------------------------------- 1 | # Coinbase Developer Platform (CDP) Plugin for GAME SDK 2 | 3 | A plugin for interacting with Coinbase's CDP through the GAME SDK. This plugin provides a simple interface for wallet management, transfers, trading, and webhooks on Base network. 4 | 5 | ## Features 6 | 7 | - Wallet Management (create, import, export) 8 | - Gasless USDC Transfers 9 | - ETH/USDC Trading 10 | - Webhook Integration 11 | - Transfer/Trade History 12 | - Testnet Faucet Support 13 | - Base Network Support (Sepolia testnet & mainnet) 14 | 15 | ## Installation 16 | 17 | ```bash 18 | # Install from local directory 19 | pip install -e plugins/cdp 20 | 21 | # Dependencies will be installed automatically: 22 | # - game-sdk>=0.1.1 23 | # - cdp-sdk>=0.16.0 24 | # - python-dotenv>=1.0.0 25 | ``` 26 | 27 | ## Configuration 28 | 29 | Set up your environment variables in a `.env` file: 30 | 31 | ```env 32 | # Required 33 | GAME_API_KEY=your_game_api_key 34 | CDP_API_KEY_NAME=your_cdp_api_key_name 35 | CDP_API_KEY_PRIVATE_KEY=your_cdp_api_key_private_key 36 | ``` 37 | 38 | ## Basic Usage 39 | 40 | ```python 41 | from cdp_game_sdk.cdp_plugin import CDPPlugin 42 | 43 | # Initialize plugin 44 | plugin = CDPPlugin() 45 | plugin.initialize() 46 | 47 | # Create and fund a wallet 48 | wallet = plugin.create_wallet() 49 | print(f"New wallet created: {wallet['address']}") 50 | 51 | # Request testnet funds 52 | plugin.request_testnet_funds("eth") 53 | plugin.request_testnet_funds("usdc") 54 | 55 | # Check balances 56 | balances = plugin.get_wallet_balance() 57 | print(f"ETH: {balances['eth']}") 58 | print(f"USDC: {balances['usdc']}") 59 | 60 | # Transfer USDC (gasless) 61 | plugin.transfer( 62 | amount=0.1, 63 | currency="usdc", 64 | to_address="0x123...", 65 | gasless=True 66 | ) 67 | 68 | # Trade ETH for USDC 69 | plugin.trade( 70 | amount=0.1, 71 | from_currency="eth", 72 | to_currency="usdc" 73 | ) 74 | ``` 75 | 76 | ## Examples 77 | 78 | The plugin comes with two example implementations: 79 | 80 | ### CDP Worker 81 | 82 | ```python 83 | from cdp_game_sdk.cdp_plugin import CDPPlugin 84 | from examples.cdp_worker import CDPWorker 85 | 86 | worker = CDPWorker() 87 | 88 | # Create and fund a wallet 89 | worker.run("create_and_fund_wallet") 90 | 91 | # Transfer USDC 92 | worker.run("transfer_usdc", 93 | to_address="0x123...", 94 | amount=10.0 95 | ) 96 | 97 | # Trade ETH for USDC 98 | worker.run("trade_eth_for_usdc", 99 | amount=0.1 100 | ) 101 | ``` 102 | 103 | ### CDP Agent 104 | 105 | ```python 106 | from examples.cdp_agent import main as run_agent 107 | 108 | # Starts a monitoring agent that: 109 | # - Maintains minimum ETH/USDC balances 110 | # - Monitors transfers 111 | # - Creates webhooks for notifications 112 | run_agent() 113 | ``` 114 | 115 | ## API Reference 116 | 117 | ### CDPPlugin 118 | 119 | Main plugin class for interacting with CDP. 120 | 121 | #### Initialization 122 | ```python 123 | plugin = CDPPlugin() 124 | plugin.initialize(use_server_signer=False) 125 | ``` 126 | 127 | #### Methods 128 | 129 | **Wallet Management** 130 | - `create_wallet()`: Create new wallet 131 | - `import_wallet(wallet_id: str, seed_file: str)`: Import existing wallet 132 | - `export_wallet(file_path: str, encrypt: bool)`: Export wallet data 133 | - `get_wallet_balance()`: Get ETH and USDC balances 134 | 135 | **Transactions** 136 | - `transfer(amount: float, currency: str, to_address: str, gasless: bool)`: Transfer funds 137 | - `trade(amount: float, from_currency: str, to_currency: str)`: Trade between currencies 138 | - `request_testnet_funds(currency: str)`: Request testnet funds from faucet 139 | 140 | **Monitoring** 141 | - `get_transfer_history()`: Get list of transfers 142 | - `get_trade_history()`: Get list of trades 143 | - `create_webhook(notification_url: str)`: Create notification webhook 144 | 145 | ## Development 146 | 147 | Run tests: 148 | ```bash 149 | pytest plugins/coinbase/test_cdp.py -v 150 | ``` 151 | 152 | ## Contributing 153 | 154 | 1. Fork the repository 155 | 2. Create your feature branch 156 | 3. Run tests and add new ones 157 | 4. Submit a pull request 158 | 159 | ## Support 160 | 161 | - Documentation: [CDP Documentation](https://docs.cloud.coinbase.com/cdp/docs) 162 | - Issues: [GitHub Issues](https://github.com/game-by-virtuals/game-python/issues) 163 | -------------------------------------------------------------------------------- /plugins/cdp/cdp_game_sdk/cdp_plugin.py: -------------------------------------------------------------------------------- 1 | from typing import Dict, Any, Optional, List 2 | import os 3 | from cdp import * 4 | from cdp.client.models.webhook import WebhookEventType, WebhookEventFilter 5 | 6 | 7 | class CDPPlugin: 8 | """ 9 | Coinbase Developer Platform Plugin for interacting with CDP 10 | """ 11 | 12 | def __init__(self) -> None: 13 | """Initialize the CDP plugin""" 14 | self.id: str = "cdp_plugin" 15 | self.name: str = "CDP Plugin" 16 | self.api_key_name = os.environ.get("CDP_API_KEY_NAME") 17 | self.api_key_private_key = os.environ.get("CDP_API_KEY_PRIVATE_KEY") 18 | self.network = "base-sepolia" # Default to testnet 19 | self.wallet = None 20 | 21 | def initialize(self, use_server_signer: bool = False): 22 | """Initialize the plugin""" 23 | if not self.api_key_name: 24 | raise ValueError("CDP_API_KEY_NAME environment variable is required") 25 | if not self.api_key_private_key: 26 | raise ValueError("CDP_API_KEY_PRIVATE_KEY environment variable is required") 27 | 28 | # Configure CDP 29 | Cdp.configure(self.api_key_name, self.api_key_private_key) 30 | if use_server_signer: 31 | Cdp.use_server_signer = True 32 | 33 | def create_wallet(self) -> Dict[str, Any]: 34 | """Create a new wallet""" 35 | self.wallet = Wallet.create(self.network) 36 | return { 37 | "wallet_id": self.wallet.id, 38 | "address": self.wallet.default_address.address_id 39 | } 40 | 41 | def import_wallet(self, wallet_id: str, seed_file: str = None) -> None: 42 | """Import an existing wallet""" 43 | self.wallet = Wallet.fetch(wallet_id) 44 | if seed_file: 45 | self.wallet.load_seed(seed_file) 46 | 47 | def get_wallet_balance(self) -> Dict[str, float]: 48 | """Get wallet balances""" 49 | if not self.wallet: 50 | raise ValueError("No wallet initialized") 51 | return { 52 | "eth": float(self.wallet.default_address.balance("eth")), 53 | "usdc": float(self.wallet.default_address.balance("usdc")) 54 | } 55 | 56 | def request_testnet_funds(self, currency: str = "eth") -> Dict[str, Any]: 57 | """Request testnet funds from faucet""" 58 | if not self.wallet: 59 | raise ValueError("No wallet initialized") 60 | tx = self.wallet.faucet(currency) 61 | tx.wait() 62 | return { 63 | "transaction_id": tx.id, 64 | "status": tx.status 65 | } 66 | 67 | def transfer( 68 | self, 69 | amount: float, 70 | currency: str, 71 | to_address: str, 72 | gasless: bool = False, 73 | skip_batching: bool = False 74 | ) -> Dict[str, Any]: 75 | """ 76 | Transfer funds to another address 77 | """ 78 | if not self.wallet: 79 | raise ValueError("No wallet initialized") 80 | 81 | tx = self.wallet.transfer( 82 | amount, 83 | currency.lower(), 84 | to_address, 85 | gasless=gasless, 86 | skip_batching=skip_batching 87 | ).wait() 88 | 89 | return { 90 | "transaction_id": tx.id, 91 | "status": tx.status 92 | } 93 | 94 | def trade( 95 | self, 96 | amount: float, 97 | from_currency: str, 98 | to_currency: str 99 | ) -> Dict[str, Any]: 100 | """ 101 | Trade between currencies 102 | """ 103 | if not self.wallet: 104 | raise ValueError("No wallet initialized") 105 | 106 | trade = self.wallet.trade( 107 | amount, 108 | from_currency.lower(), 109 | to_currency.lower() 110 | ).wait() 111 | 112 | return { 113 | "trade_id": trade.id, 114 | "status": trade.status 115 | } 116 | 117 | def get_transfer_history(self) -> List[Dict[str, Any]]: 118 | """Get transfer history""" 119 | if not self.wallet: 120 | raise ValueError("No wallet initialized") 121 | return list(self.wallet.default_address.transfers()) 122 | 123 | def get_trade_history(self) -> List[Dict[str, Any]]: 124 | """Get trade history""" 125 | if not self.wallet: 126 | raise ValueError("No wallet initialized") 127 | return list(self.wallet.default_address.trades()) 128 | 129 | def create_webhook( 130 | self, 131 | notification_url: str, 132 | event_type: str = WebhookEventType.ERC20_TRANSFER, 133 | network: str = None 134 | ) -> Dict[str, Any]: 135 | """Create a webhook for notifications""" 136 | if not self.wallet: 137 | raise ValueError("No wallet initialized") 138 | 139 | webhook = self.wallet.create_webhook( 140 | notification_url, 141 | event_type=event_type, 142 | network_id=network or self.network 143 | ) 144 | 145 | return { 146 | "webhook_id": webhook.id, 147 | "status": webhook.status 148 | } 149 | 150 | def export_wallet(self, file_path: str, encrypt: bool = True) -> None: 151 | """Export wallet data to file""" 152 | if not self.wallet: 153 | raise ValueError("No wallet initialized") 154 | self.wallet.save_seed(file_path, encrypt=encrypt) 155 | -------------------------------------------------------------------------------- /plugins/cdp/examples/cdp_agent.py: -------------------------------------------------------------------------------- 1 | from game_sdk.game.worker import Worker 2 | from game_sdk.game.custom_types import Function, Argument, FunctionResult, FunctionResultStatus 3 | from typing import Dict, Optional, List 4 | import os 5 | import time 6 | import threading 7 | from dotenv import load_dotenv 8 | from cdp_game_sdk.cdp_plugin import CDPPlugin 9 | from cdp import Wallet 10 | # Load environment variables 11 | load_dotenv() 12 | 13 | game_api_key = os.environ.get("GAME_API_KEY") 14 | cdp_plugin = CDPPlugin() 15 | cdp_plugin.initialize() 16 | 17 | def get_state_fn(function_result: FunctionResult, current_state: dict) -> dict: 18 | """Simple state management""" 19 | return {} 20 | 21 | def monitor_balance(min_eth: float = 0.1, min_usdc: float = 100.0) -> tuple: 22 | """ 23 | Monitor wallet balances and maintain minimum levels 24 | """ 25 | try: 26 | balances = cdp_plugin.get_wallet_balance() 27 | print("\nCurrent Balances:") 28 | print(f"ETH: {balances['eth']}") 29 | print(f"USDC: {balances['usdc']}") 30 | 31 | actions = [] 32 | 33 | # Check ETH balance 34 | if balances['eth'] < min_eth: 35 | print(f"ETH balance below minimum ({min_eth})") 36 | cdp_plugin.request_testnet_funds("eth") 37 | actions.append("Requested ETH from faucet") 38 | 39 | # Check USDC balance 40 | if balances['usdc'] < min_usdc: 41 | print(f"USDC balance below minimum ({min_usdc})") 42 | cdp_plugin.request_testnet_funds("usdc") 43 | actions.append("Requested USDC from faucet") 44 | 45 | if actions: 46 | return FunctionResultStatus.DONE, "Replenished low balances", {"actions": actions} 47 | return FunctionResultStatus.DONE, "Balances adequate", balances 48 | 49 | except Exception as e: 50 | print(f"Error monitoring balances: {e}") 51 | return FunctionResultStatus.FAILED, f"Error: {str(e)}", {} 52 | 53 | def process_transfers() -> tuple: 54 | """ 55 | Process recent transfers 56 | """ 57 | try: 58 | Wallet.create() 59 | transfers = cdp_plugin.get_transfer_history() 60 | recent_transfers = transfers[:5] # Get 5 most recent transfers 61 | 62 | print("\nRecent Transfers:") 63 | for transfer in recent_transfers: 64 | print(f"ID: {transfer['id']}") 65 | print(f"Status: {transfer.get('status', 'unknown')}") 66 | print("---") 67 | 68 | return FunctionResultStatus.DONE, "Processed recent transfers", {"transfers": recent_transfers} 69 | except Exception as e: 70 | print(f"Error processing transfers: {e}") 71 | return FunctionResultStatus.FAILED, f"Error: {str(e)}", {} 72 | 73 | # Create workers for different tasks 74 | balance_worker = Worker( 75 | api_key=game_api_key, 76 | description="Monitor wallet balances", 77 | instruction="Monitor and maintain minimum balance levels", 78 | get_state_fn=get_state_fn, 79 | action_space=[ 80 | Function( 81 | fn_name="monitor_balance", 82 | fn_description="Monitor wallet balances", 83 | args=[], 84 | executable=monitor_balance 85 | ) 86 | ] 87 | ) 88 | 89 | transfer_worker = Worker( 90 | api_key=game_api_key, 91 | description="Process transfers", 92 | instruction="Monitor and process transfers", 93 | get_state_fn=get_state_fn, 94 | action_space=[ 95 | Function( 96 | fn_name="process_transfers", 97 | fn_description="Process recent transfers", 98 | args=[], 99 | executable=process_transfers 100 | ) 101 | ] 102 | ) 103 | 104 | def monitor_balances(): 105 | """Monitor balances continuously""" 106 | while True: 107 | balance_worker.run("Monitor wallet balances") 108 | time.sleep(300) # Check every 5 minutes 109 | 110 | def monitor_transfers(): 111 | """Monitor transfers continuously""" 112 | while True: 113 | transfer_worker.run("Process recent transfers") 114 | time.sleep(60) # Check every minute 115 | 116 | def main(): 117 | """ 118 | Main function to start monitoring threads 119 | """ 120 | print("Starting CDP monitoring agent...") 121 | 122 | # Create wallet if not exists 123 | if not cdp_plugin.wallet: 124 | wallet = cdp_plugin.create_wallet() 125 | print(f"Created new wallet: {wallet['address']}") 126 | 127 | # Fund wallet 128 | cdp_plugin.request_testnet_funds("eth") 129 | cdp_plugin.request_testnet_funds("usdc") 130 | print("Funded wallet with testnet ETH and USDC") 131 | 132 | # Create monitoring threads 133 | balance_thread = threading.Thread(target=monitor_balances) 134 | transfer_thread = threading.Thread(target=monitor_transfers) 135 | 136 | # Start threads 137 | balance_thread.start() 138 | transfer_thread.start() 139 | 140 | try: 141 | # Keep main thread alive 142 | while True: 143 | time.sleep(1) 144 | except KeyboardInterrupt: 145 | print("\nStopping monitoring...") 146 | 147 | if __name__ == "__main__": 148 | main() 149 | -------------------------------------------------------------------------------- /plugins/cdp/examples/cdp_worker.py: -------------------------------------------------------------------------------- 1 | from game_sdk.game.worker import Worker 2 | from game_sdk.game.custom_types import ( 3 | Function, 4 | Argument, 5 | FunctionResult, 6 | FunctionResultStatus 7 | ) 8 | from typing import Dict 9 | import os 10 | from dotenv import load_dotenv 11 | from cdp_game_sdk.cdp_plugin import CDPPlugin 12 | 13 | class CDPWorker: 14 | def __init__(self): 15 | # Load environment variables 16 | load_dotenv() 17 | self.game_api_key = os.environ.get("GAME_API_KEY") 18 | self.cdp_plugin = CDPPlugin() 19 | self.cdp_plugin.initialize() 20 | self.worker = self._create_worker() 21 | 22 | def _get_state(self, function_result: FunctionResult, current_state: dict) -> dict: 23 | """Simple state management""" 24 | return {} 25 | 26 | def create_and_fund_wallet(self) -> tuple: 27 | """ 28 | Create a new wallet and fund it with testnet ETH and USDC 29 | """ 30 | try: 31 | # Create wallet 32 | wallet = self.cdp_plugin.create_wallet() 33 | print(f"Created wallet: {wallet['address']}") 34 | 35 | # Request testnet funds 36 | eth_result = self.cdp_plugin.request_testnet_funds("eth") 37 | usdc_result = self.cdp_plugin.request_testnet_funds("usdc") 38 | 39 | print("Funded wallet with testnet ETH and USDC") 40 | 41 | return FunctionResultStatus.DONE, "Wallet created and funded", { 42 | "wallet": wallet, 43 | "eth_tx": eth_result, 44 | "usdc_tx": usdc_result 45 | } 46 | except Exception as e: 47 | print(f"Error creating/funding wallet: {e}") 48 | return FunctionResultStatus.FAILED, f"Error: {str(e)}", {} 49 | 50 | def transfer_usdc(self, to_address: str, amount: float) -> tuple: 51 | """ 52 | Transfer USDC to an address 53 | """ 54 | try: 55 | result = self.cdp_plugin.transfer( 56 | amount=amount, 57 | currency="usdc", 58 | to_address=to_address, 59 | gasless=True 60 | ) 61 | print(f"Transferred {amount} USDC to {to_address}") 62 | return FunctionResultStatus.DONE, "Transfer successful", result 63 | except Exception as e: 64 | print(f"Error transferring USDC: {e}") 65 | return FunctionResultStatus.FAILED, f"Error: {str(e)}", {} 66 | 67 | def trade_eth_for_usdc(self, amount: float) -> tuple: 68 | """ 69 | Trade ETH for USDC 70 | """ 71 | try: 72 | result = self.cdp_plugin.trade( 73 | amount=amount, 74 | from_currency="eth", 75 | to_currency="usdc" 76 | ) 77 | print(f"Traded {amount} ETH for USDC") 78 | return FunctionResultStatus.DONE, "Trade successful", result 79 | except Exception as e: 80 | print(f"Error trading ETH for USDC: {e}") 81 | return FunctionResultStatus.FAILED, f"Error: {str(e)}", {} 82 | 83 | def _create_worker(self) -> Worker: 84 | """Create worker with CDP capabilities""" 85 | return Worker( 86 | api_key=self.game_api_key, 87 | description="Worker for CDP operations", 88 | instruction="Perform CDP operations like transfers and trades", 89 | get_state_fn=self._get_state, 90 | action_space=[ 91 | Function( 92 | fn_name="create_and_fund_wallet", 93 | fn_description="Create a new wallet and fund it with testnet assets", 94 | args=[], 95 | executable=self.create_and_fund_wallet 96 | ), 97 | Function( 98 | fn_name="transfer_usdc", 99 | fn_description="Transfer USDC to an address", 100 | args=[ 101 | Argument( 102 | name="to_address", 103 | type="string", 104 | description="Recipient address" 105 | ), 106 | Argument( 107 | name="amount", 108 | type="float", 109 | description="Amount of USDC to transfer" 110 | ) 111 | ], 112 | executable=self.transfer_usdc 113 | ), 114 | Function( 115 | fn_name="trade_eth_for_usdc", 116 | fn_description="Trade ETH for USDC", 117 | args=[ 118 | Argument( 119 | name="amount", 120 | type="float", 121 | description="Amount of ETH to trade" 122 | ) 123 | ], 124 | executable=self.trade_eth_for_usdc 125 | ) 126 | ] 127 | ) 128 | 129 | def run(self, function_name: str, **kwargs): 130 | """Run the worker with specified function""" 131 | self.worker.run(f"Execute {function_name}", **kwargs) 132 | 133 | def main(): 134 | # Example usage 135 | worker = CDPWorker() 136 | 137 | # Create and fund a wallet 138 | worker.run("create_and_fund_wallet") 139 | 140 | # Make a transfer 141 | worker.run("transfer_usdc", to_address="0x123...", amount=10.0) 142 | 143 | # Trade ETH for USDC 144 | worker.run("trade_eth_for_usdc", amount=0.1) 145 | 146 | if __name__ == "__main__": 147 | main() -------------------------------------------------------------------------------- /plugins/cdp/pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["hatchling"] 3 | build-backend = "hatchling.build" 4 | 5 | [project] 6 | name = "cdp_game_sdk" 7 | version = "0.1.0" 8 | authors = [{ name = "GAME SDK" }] 9 | description = "Official Coinbase Developer Platform Plugin for GAME by Virtuals" 10 | readme = "README.md" 11 | requires-python = ">=3.10" 12 | classifiers = [ 13 | "Programming Language :: Python :: 3", 14 | "Programming Language :: Python :: 3.10", 15 | "Programming Language :: Python :: 3.11", 16 | "License :: OSI Approved :: MIT License", 17 | "Operating System :: OS Independent", 18 | "Development Status :: 3 - Alpha", 19 | "Intended Audience :: Developers", 20 | "Topic :: Software Development :: Libraries :: Python Modules", 21 | ] 22 | dependencies = [ 23 | "game-sdk>=0.1.1", 24 | "requests>=2.31.0", 25 | "python-dotenv>=1.0.0", 26 | "cdp-sdk>=0.16.0", 27 | "pytest>=7.4.3", 28 | "pytest-mock>=3.14.0", 29 | "pytest-cov>=4.1.0", 30 | "pytest-asyncio>=0.21.0", 31 | "pytest-mock>=3.14.0", 32 | 33 | ] 34 | 35 | [project.urls] 36 | "Homepage" = "https://github.com/game-by-virtuals/game-python/plugins/coinbase" 37 | "Bug Tracker" = "https://github.com/game-by-virtuals/game-python" 38 | 39 | [tool.hatch.build.targets.wheel] 40 | packages = ["cdp_game_sdk"] 41 | -------------------------------------------------------------------------------- /plugins/conflux/README.md: -------------------------------------------------------------------------------- 1 | # Conflux Plugin for GAME SDK 2 | 3 | The Conflux Plugin enables interaction with the Conflux blockchain network, specifically designed for meme token creation and management operations. 4 | 5 | ## Features 6 | 7 | - Create new meme tokens with customizable name, symbol, and metadata 8 | - With Automatic IPFS image upload and metadata management 9 | - Retrieve list of created meme tokens 10 | 11 | ## Setup and Configuration 12 | 13 | 1. Set up the required credentials in your configuration: 14 | 15 | ```python 16 | options = { 17 | "id": "conflux_plugin", # Optional: Custom plugin ID 18 | "name": "Conflux Plugin", # Optional: Custom plugin name 19 | "credentials": { 20 | # Conflux eSpace RPC URL 21 | "rpc_url": "https://evm.confluxrpc.com", 22 | # Your wallet private key 23 | "private_key": "YOUR_PRIVATE_KEY", 24 | # Meme contract address, 25 | # mainnet 0x4b892680caf3b6d63e0281bf1858d92d0a7ba90b 26 | # testnet 0xA016695B5E633399027Ec36941ECa4D5601aBEac 27 | "contract_address": "CONTRACT_ADDRESS", 28 | # serverless function deployment (https://github.com/darwintree/confi-pump-helper). 29 | # mainnet https://eliza-helper.vercel.app 30 | # testnet https://eliza-helper-test.vercel.app 31 | "confi_pump_helper_url": "HELPER_URL", # Confi Pump Helper service URL 32 | } 33 | } 34 | ``` 35 | 36 | 1. Initialize the plugin 37 | 38 | ```python 39 | from conflux_plugin_gamesdk.conflux_plugin_gamesdk import ConfluxPlugin 40 | 41 | conflux_plugin = ConfluxPlugin(options) 42 | ``` 43 | 44 | ## Available Functions 45 | 46 | 1. `create_meme(name: str, symbol: str, description: str, image_url: str)` 47 | - Creates a new meme token with specified parameters 48 | - Automatically uploads image to IPFS 49 | - Returns the deployed token contract address 50 | - Each creation would cost **10 CFX** 51 | 52 | 2. `get_meme_list()` 53 | - Retrieves the list of all created meme tokens 54 | - Returns token information list including addresses and metadata 55 | 56 | ```python 57 | class MemeInfo(TypedDict): 58 | address: str 59 | name: str 60 | symbol: str 61 | description: str 62 | progress: str # a float in string, 0~100 63 | ``` 64 | 65 | ## Example Usage 66 | 67 | ```python 68 | # Create a new meme token 69 | create_meme_func = conflux_plugin.get_function("create_meme") 70 | token_address = create_meme_func( 71 | name="MyMeme", 72 | symbol="MEME", 73 | description="My awesome meme token", 74 | image_url="https://example.com/meme.jpg" 75 | ) 76 | 77 | # Get list of meme tokens 78 | get_meme_list_func = conflux_plugin.get_function("get_meme_list") 79 | meme_list = get_meme_list_func() 80 | ``` 81 | 82 | ## Requirements 83 | 84 | - Requires serverless function to be deployed (https://github.com/darwintree/confi-pump-helper) 85 | - Web3.py for blockchain interactions 86 | - Active Conflux eSpace network connection 87 | - Sufficient CFX balance for token creation (10 CFX required per token) 88 | 89 | ## Network Support 90 | 91 | - Default: Conflux eSpace Mainnet/testnet, depending on configuration 92 | 93 | ## Notes 94 | 95 | - Token creation includes automatic IPFS upload with safe block confirmation 96 | - Image uploads are handled asynchronously after transaction confirmation 97 | -------------------------------------------------------------------------------- /plugins/conflux/pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["hatchling"] 3 | build-backend = "hatchling.build" 4 | 5 | [project] 6 | name = "conflux_plugin_gamesdk" 7 | version = "0.1.0" 8 | authors = [ 9 | { name = "darwintree", email = "17946284+darwintree@users.noreply.github.com" }, 10 | ] 11 | description = "Conflux Plugin for Python SDK for GAME by Virtuals" 12 | requires-python = ">=3.8" 13 | classifiers = [ 14 | "Programming Language :: Python :: 3", 15 | "Programming Language :: Python :: 3.8", 16 | "Programming Language :: Python :: 3.9", 17 | "Programming Language :: Python :: 3.10", 18 | "Programming Language :: Python :: 3.11", 19 | "License :: OSI Approved :: MIT License", 20 | "Operating System :: OS Independent", 21 | "Development Status :: 3 - Alpha", 22 | "Intended Audience :: Developers", 23 | "Topic :: Software Development :: Libraries :: Python Modules", 24 | ] 25 | dependencies = [ 26 | "web3>=7.0.0" 27 | ] 28 | 29 | [tool.hatch.build.targets.wheel] 30 | packages = ["conflux_plugin_gamesdk"] 31 | 32 | [project.urls] 33 | "Homepage" = "https://github.com/game-by-virtuals/game-python" 34 | "Bug Tracker" = "https://github.com/game-by-virtuals/game-python" -------------------------------------------------------------------------------- /plugins/conflux/tests/test_meme.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from conflux_plugin_gamesdk.conflux_plugin_gamesdk import ConfluxPlugin 3 | 4 | @pytest.fixture 5 | def options(): 6 | return { 7 | "id": "test_conflux_worker", 8 | "name": "Test Conflux Worker", 9 | "description": "An example Conflux Plugin for testing.", 10 | "credentials": { 11 | "rpc_url": "https://evmtest.confluxrpc.com", 12 | "private_key": "0x0000000000000000000000000000000000000000000000000000000000000000", 13 | "contract_address": "0xA016695B5E633399027Ec36941ECa4D5601aBEac", 14 | "confi_pump_helper_url": "https://eliza-helper.vercel.app", 15 | }, 16 | } 17 | 18 | @pytest.fixture 19 | def conflux_plugin(options): 20 | return ConfluxPlugin(options) 21 | 22 | def test_create_meme(conflux_plugin): 23 | print("Creating meme...") 24 | create_meme_func = conflux_plugin.get_function("create_meme") 25 | token = create_meme_func(name="test_meme", symbol="TEST", description="test_meme", image_url="https://upload.wikimedia.org/wikipedia/zh/3/34/Lenna.jpg") 26 | assert token.startswith("0x") 27 | 28 | def test_get_meme_list(conflux_plugin): 29 | get_meme_list_func = conflux_plugin.get_function("get_meme_list") 30 | memes = get_meme_list_func() 31 | assert len(memes) > 0 32 | 33 | -------------------------------------------------------------------------------- /plugins/dpsn/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/game-by-virtuals/game-python/76a1a712be14b971fd59a45c14c76c8775884e73/plugins/dpsn/__init__.py -------------------------------------------------------------------------------- /plugins/dpsn/examples/.env.example: -------------------------------------------------------------------------------- 1 | DPSN_URL=betanet.dpsn.org 2 | PVT_KEY= 3 | GAME_API_KEY=apt- -------------------------------------------------------------------------------- /plugins/dpsn/examples/dpsn_worker.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import os 3 | from dotenv import load_dotenv 4 | from pathlib import Path 5 | from typing import Dict, Any, List 6 | from datetime import datetime 7 | import time # Import time for sleep 8 | load_dotenv() 9 | 10 | # Import FunctionResultStatus to check the status enum 11 | from game_sdk.game.custom_types import FunctionResultStatus 12 | from dpsn_plugin_gamesdk.dpsn_plugin import DpsnPlugin 13 | 14 | class DpsnWorker: 15 | """ 16 | DPSN Worker for processing market data and executing trades (Synchronous Version) 17 | """ 18 | def __init__(self): 19 | self.plugin = DpsnPlugin( 20 | dpsn_url=os.getenv("DPSN_URL"), 21 | pvt_key=os.getenv("PVT_KEY") 22 | ) 23 | self.trades: List[Dict[str, Any]] = [] 24 | self.is_running = False 25 | 26 | def process_message(self, message: Dict[str, Any]): 27 | """Process incoming messages and execute trades""" 28 | topic = message['topic'] 29 | payload = message['payload'] 30 | 31 | # Log the message 32 | print(f"\n Processing message:") 33 | print(f"Topic: {topic}") 34 | print(f"Payload: {payload}") 35 | 36 | # Execute trade if conditions are met (synchronous call) 37 | trade = self.execute_trade(topic, payload) 38 | if trade: 39 | self.trades.append(trade) 40 | print(f"💼 Trade executed: {trade}") 41 | 42 | # Example use case of dpsn plugin worker 43 | 44 | def execute_trade(self, topic: str, payload: Dict[str, Any]) -> Dict[str, Any] | None: 45 | """ 46 | Demonstrates a potential use case for reacting to DPSN messages. 47 | 48 | **This is a highly simplified example for demonstration purposes only.** 49 | 50 | It simulates a basic trading decision based on a received price. 51 | It lacks proper error handling, risk management, sophisticated logic, 52 | and integration with actual trading systems. 53 | 54 | **Do NOT use this function in a real-world trading implementation.** 55 | 56 | Args: 57 | topic: The topic the message was received on. 58 | payload: The message payload. 59 | 60 | Returns: 61 | A dictionary representing a trade order if conditions are met, 62 | otherwise None. 63 | """ 64 | # Example trade execution logic (synchronous) 65 | if "SOLUSDT" in topic: 66 | # Ensure payload is a dictionary before accessing keys 67 | if isinstance(payload, dict): 68 | try: 69 | price = float(payload.get('price', 0)) 70 | if price > 100: 71 | 72 | return { 73 | "action": "SELL", 74 | "price": price, 75 | "timestamp": datetime.now().isoformat(), 76 | "status": "EXECUTED" 77 | } 78 | except (ValueError, TypeError) as e: 79 | print(f"Error processing price from payload: {e}") 80 | else: 81 | print(f"Skipping trade execution: Payload is not a dictionary ({type(payload)})") 82 | return None 83 | 84 | def start(self): 85 | """Start the DPSN worker""" 86 | self.plugin.set_message_callback(self.process_message) 87 | 88 | topics = [ 89 | "0xe14768a6d8798e4390ec4cb8a4c991202c2115a5cd7a6c0a7ababcaf93b4d2d4/SOLUSDT/ticker", 90 | # Add other topics if needed 91 | ] 92 | 93 | print("Subscribing to topics (will initialize connection if needed)...") 94 | for topic in topics: 95 | result = self.plugin.subscribe(topic) 96 | if result[0] != FunctionResultStatus.DONE: 97 | error_message = result[1] if len(result) > 1 else f"Unknown subscription error for {topic}" 98 | print(f"Failed to subscribe to {topic}: {error_message}") 99 | else: 100 | print(result[1]) # e.g., "Successfully subscribed to topic: ..." 101 | 102 | self.is_running = True 103 | print("DPSN Worker Started") 104 | 105 | def stop(self): 106 | """Stop the DPSN worker""" 107 | print("Stopping DPSN Worker...") 108 | self.is_running = False 109 | # Consider unsubscribing from topics here if necessary 110 | # for topic in topics: self.plugin.unsubscribe(topic) 111 | self.plugin.shutdown() 112 | print("DPSN Worker Stopped") 113 | 114 | def main(): 115 | worker = DpsnWorker() 116 | try: 117 | worker.start() 118 | print("Worker running... Press Ctrl+C to stop.") 119 | # Keep the worker running using time.sleep 120 | while worker.is_running: 121 | time.sleep(1) # Check status every second 122 | except KeyboardInterrupt: 123 | print("\nCtrl+C detected. Shutting down worker...") 124 | except Exception as e: 125 | print(f"An error occurred: {e}") 126 | finally: 127 | if worker.is_running: 128 | worker.stop() 129 | 130 | if __name__ == "__main__": 131 | # Run main directly without asyncio 132 | main() 133 | -------------------------------------------------------------------------------- /plugins/dpsn/examples/test_dpsn_game_functions.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import os 3 | from pathlib import Path 4 | import time 5 | from datetime import datetime 6 | from game_sdk.game.custom_types import Function, Argument, FunctionResultStatus 7 | from dotenv import load_dotenv 8 | load_dotenv() 9 | 10 | 11 | 12 | from dpsn_plugin_gamesdk.dpsn_plugin import DpsnPlugin 13 | 14 | dpsn_plugin=DpsnPlugin( 15 | dpsn_url=os.getenv("DPSN_URL"), 16 | pvt_key=os.getenv("PVT_KEY") 17 | ) 18 | 19 | def test_dpsn_connection(): 20 | """Test DPSN connection and basic functionality""" 21 | print("\n🔄 Testing DPSN Connection...") 22 | 23 | 24 | # Wait for connection to stabilize 25 | time.sleep(1) 26 | print("✅ DPSN initialized successfully") 27 | return True 28 | 29 | def test_subscribe_and_receive(): 30 | """Test subscribing to topics and receiving messages""" 31 | print("\n🔄 Testing Subscription and Message Reception...") 32 | 33 | # Define message handler 34 | def handle_message(message_data): 35 | topic = message_data['topic'] 36 | payload = message_data['payload'] 37 | print(f"Received message on {topic}: {payload}") 38 | 39 | # Set the callback 40 | dpsn_plugin.set_message_callback(handle_message) 41 | 42 | # Test topic 43 | topic = "0xe14768a6d8798e4390ec4cb8a4c991202c2115a5cd7a6c0a7ababcaf93b4d2d4/SOLUSDT/ohlc" 44 | 45 | print(f"Subscribing to topic: {topic}") 46 | result = dpsn_plugin.subscribe(topic) 47 | if not result["success"]: 48 | print(f"❌ Failed to subscribe to topic: {result.get('error')}") 49 | return False 50 | 51 | print("Subscription successful!") 52 | print("\nWaiting for messages... (Press Ctrl+C to exit)") 53 | 54 | try: 55 | while True: 56 | if not dpsn_plugin.client.dpsn_broker.is_connected(): 57 | print("Connection lost, attempting to reconnect...") 58 | dpsn_plugin.initialize() 59 | time.sleep(1) 60 | dpsn_plugin.subscribe(topic) 61 | time.sleep(1) 62 | 63 | except KeyboardInterrupt: 64 | print("\n⚠️ Test interrupted by user") 65 | return True 66 | 67 | def test_shutdown(): 68 | """Test graceful shutdown""" 69 | print("\n🔄 Testing Shutdown...") 70 | 71 | status,message,extra = dpsn_plugin.shutdown() 72 | if status is not FunctionResultStatus.DONE: 73 | print(f"❌ Failed to shutdown") 74 | return False 75 | 76 | print("✅ Shutdown successful") 77 | return True 78 | 79 | def main(): 80 | """Main test function""" 81 | print("🚀 Starting DPSN Plugin Tests...") 82 | 83 | try: 84 | # Test connection 85 | if not test_dpsn_connection(): 86 | return 87 | 88 | if not test_subscribe_and_receive(): 89 | return 90 | 91 | except Exception as e: 92 | print(f"\n❌ Test failed with error: {str(e)}") 93 | finally: 94 | # Ensure we shutdown properly 95 | test_shutdown() 96 | 97 | if __name__ == "__main__": 98 | main() 99 | -------------------------------------------------------------------------------- /plugins/dpsn/plugin_metadata.yml: -------------------------------------------------------------------------------- 1 | # General Information 2 | plugin_name: 'dpsn_plugin' 3 | author: 'DPSN-Tech-Team' 4 | logo_url: '' 5 | release_date: '2025-04' 6 | 7 | # Description 8 | short_description: 'DPSN plugin to subscribe to dpsn data streams for Virtuals Protocol agents' 9 | detailed_description: 'This plugin allows Virtuals Protocol agents in Python to connect to and interact with real-time data streams from the DPSN Data Streams Store. Agents can use it for decision-making and event handling. Developers can also publish custom streams using the dpsn-python-client.' 10 | 11 | # Contact & Support 12 | x_account_handle: '@DPSN_org' 13 | support_contact: 'sanil@dpsn.org' 14 | community_link: 'https://t.me/dpsn_dev' 15 | -------------------------------------------------------------------------------- /plugins/dpsn/pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "dpsn-plugin-gamesdk" 3 | version = "0.0.1" 4 | description = "DPSN Plugin for Python SDK for GAME by Virtuals" 5 | authors = [{ name = "dpsn-dev", email = "sanil@dpsn.org" }] 6 | readme = "README.md" 7 | requires-python = ">=3.9" 8 | classifiers = [ 9 | "Programming Language :: Python :: 3", 10 | "Programming Language :: Python :: 3.9", 11 | "Programming Language :: Python :: 3.10", 12 | "Programming Language :: Python :: 3.11", 13 | "License :: OSI Approved :: MIT License", 14 | "Operating System :: OS Independent", 15 | "Development Status :: 3 - Alpha", 16 | "Intended Audience :: Developers", 17 | "Topic :: Software Development :: Libraries :: Python Modules", 18 | ] 19 | dependencies = ["dpsn-client==1.0.0.post1", "game-sdk>=0.1.1"] 20 | [build-system] 21 | requires = ["poetry-core>=2.0.0,<3.0.0"] 22 | build-backend = "poetry.core.masonry.api" 23 | 24 | [project.urls] 25 | "Homepage" = "https://github.com/game-by-virtuals/game-python/plugins/dpsn" 26 | "Bug Tracker" = "https://github.com/game-by-virtuals/game-python" 27 | -------------------------------------------------------------------------------- /plugins/imagegen/README.md: -------------------------------------------------------------------------------- 1 | # Image Generator Plugin for GAME SDK 2 | 3 | TogetherAI has a free flux schnell image generator, which we'll use here because it's free. 4 | Can change endpoints, params, etc to suit your needs. 5 | 6 | ## Features 7 | - Generate images based on prompt 8 | - Receive images as temporary URL or B64 objects 9 | 10 | ## Available Functions 11 | 12 | 1. `generate_image(prompt: str)` - Generates image based on prompt 13 | 14 | ## Setup and configuration 15 | 1. Set the following environment variables: 16 | - `TOGETHER_API_KEY`: Create an API key by [creating an account](https://together.ai). 17 | 18 | 2. Import and initialize the plugin to use in your worker: 19 | ```python 20 | from plugins.imagegen.imagegen_plugin import ImageGenPlugin 21 | 22 | imagegen_plugin = ImageGenPlugin( 23 | api_key=os.environ.get("TOGETHER_API_KEY"), 24 | ) 25 | ``` 26 | 27 | **Basic worker example:** 28 | ```python 29 | def get_state_fn(function_result: FunctionResult, current_state: dict) -> dict: 30 | """ 31 | Update state based on the function results 32 | """ 33 | init_state = {} 34 | 35 | if current_state is None: 36 | return init_state 37 | 38 | # Update state with the function result info 39 | current_state.update(function_result.info) 40 | 41 | return current_state 42 | 43 | generate_image_worker = Worker( 44 | api_key=os.environ.get("TOGETHER_API_KEY"), 45 | description="Worker for generating AI images based on prompt", 46 | get_state_fn=get_state_fn, 47 | action_space=[ 48 | imagegen_plugin.get_function("generate_image"), 49 | ], 50 | ) 51 | 52 | generate_image_worker.run("Cute anime girl with twitter logo") 53 | ``` 54 | 55 | ## Running examples 56 | 57 | To run the examples showcased in the plugin's directory, follow these steps: 58 | 59 | 1. Install dependencies: 60 | ``` 61 | poetry install 62 | ``` 63 | 64 | 2. Set up environment variables: 65 | ``` 66 | export GAME_API_KEY="your-game-api-key" 67 | export TOGETHER_API_KEY="your-together-api-key" 68 | ``` 69 | 70 | 3. Run example scripts: 71 | 72 | Example worker: 73 | ``` 74 | poetry run python ./examples/example-worker.py 75 | ``` 76 | 77 | Example agent: 78 | ``` 79 | poetry run python ./examples/example-agent.py 80 | ``` -------------------------------------------------------------------------------- /plugins/imagegen/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/game-by-virtuals/game-python/76a1a712be14b971fd59a45c14c76c8775884e73/plugins/imagegen/__init__.py -------------------------------------------------------------------------------- /plugins/imagegen/examples/example-worker.py: -------------------------------------------------------------------------------- 1 | import os 2 | from game_sdk.game.worker import Worker 3 | from game_sdk.game.custom_types import FunctionResult 4 | from imagegen_game_sdk.imagegen_plugin import ImageGenPlugin 5 | 6 | def get_state_fn(function_result: FunctionResult, current_state: dict) -> dict: 7 | """ 8 | Update state based on the function results 9 | """ 10 | init_state = {} 11 | 12 | if current_state is None: 13 | return init_state 14 | 15 | # Update state with the function result info 16 | current_state.update(function_result.info) 17 | 18 | return current_state 19 | 20 | 21 | imagegen_plugin = ImageGenPlugin( 22 | api_key=os.environ.get("TOGETHER_API_KEY"), 23 | ) 24 | 25 | # Create worker 26 | imagegen_worker = Worker( 27 | api_key=os.environ.get("GAME_API_KEY"), 28 | description="Worker specialized in using AI image generator to generate images", 29 | get_state_fn=get_state_fn, 30 | action_space=[ 31 | imagegen_plugin.get_function("generate_image"), 32 | ], 33 | ) 34 | 35 | # # Run example query 36 | queries = [ 37 | "Cute anime character with Twitter logo on outfit", 38 | ] 39 | for query in queries: 40 | print("-" * 100) 41 | print(f"Query: {query}") 42 | imagegen_worker.run(query) 43 | -------------------------------------------------------------------------------- /plugins/imagegen/imagegen_game_sdk/imagegen_plugin.py: -------------------------------------------------------------------------------- 1 | from typing import Dict, List, Optional, Tuple 2 | from game_sdk.game.custom_types import Argument, Function, FunctionResultStatus 3 | import requests 4 | import os 5 | 6 | DEFAULT_BASE_API_URL = "https://api.together.xyz/v1/images/generations" 7 | 8 | 9 | class ImageGenPlugin: 10 | """ 11 | AI Image Generation plugin using Together.ai API. 12 | 13 | Requires: 14 | - Together.ai API key 15 | 16 | Example: 17 | client = ImageGenPlugin( 18 | api_key="your-together-api-key", 19 | api_url="https://api.together.xyz/v1/images/generations", 20 | ) 21 | 22 | generate_image_fn = client.get_function("generate_image") 23 | """ 24 | def __init__( 25 | self, 26 | api_key: Optional[str] = os.environ.get("TOGETHER_API_KEY"), 27 | api_url: Optional[str] = DEFAULT_BASE_API_URL, 28 | ): 29 | self.api_key = api_key 30 | self.api_url = api_url 31 | 32 | # Available client functions 33 | self._functions: Dict[str, Function] = { 34 | "generate_image": Function( 35 | fn_name="generate_image", 36 | fn_description="Generates AI generated image based on prompt.", 37 | args=[ 38 | Argument( 39 | name="prompt", 40 | description="The prompt for image generation model. Example: A dog in the park", 41 | type="string", 42 | ), 43 | Argument( 44 | name="width", 45 | description="Width of generated image, up to 1440 px. Default should be 1024 unless other sizes specifically needed.", 46 | type="int", 47 | ), 48 | Argument( 49 | name="height", 50 | description="Height of generated image, up to 1440 px. Default should be 1024 unless other sizes specifically needed.", 51 | type="int", 52 | ), 53 | ], 54 | hint="This function is used to generate an AI image based on prompt", 55 | executable=self.generate_image, 56 | ), 57 | } 58 | 59 | @property 60 | def available_functions(self) -> List[str]: 61 | """Get list of available function names.""" 62 | return list(self._functions.keys()) 63 | 64 | def get_function(self, fn_name: str) -> Function: 65 | """ 66 | Get a specific function by name. 67 | 68 | Args: 69 | fn_name: Name of the function to retrieve 70 | 71 | Raises: 72 | ValueError: If function name is not found 73 | 74 | Returns: 75 | Function object 76 | """ 77 | if fn_name not in self._functions: 78 | raise ValueError( 79 | f"Function '{fn_name}' not found. Available functions: {', '.join(self.available_functions)}" 80 | ) 81 | return self._functions[fn_name] 82 | 83 | def generate_image(self, prompt: str, width: int = 1024, height: int = 1024, **kwargs) -> str: 84 | """Generate image based on prompt. 85 | 86 | Returns: 87 | str URL of image (need to save since temporal) 88 | """ 89 | # API endpoint for image generation 90 | url = DEFAULT_BASE_API_URL 91 | 92 | # Prepare headers for the request 93 | headers = { 94 | "Authorization": f"Bearer {self.api_key}", 95 | "Content-Type": "application/json", 96 | } 97 | 98 | # Prepare request payload 99 | payload = { 100 | "model": "black-forest-labs/FLUX.1-schnell-Free", 101 | "prompt": prompt, 102 | "width": width, 103 | "height": height, 104 | "steps": 1, 105 | "n": 1, 106 | "response_format": "url", 107 | } 108 | 109 | try: 110 | # Make the API request 111 | response = requests.post(self.api_url, headers=headers, json=payload) 112 | response.raise_for_status() 113 | 114 | # Extract the image URL from the response 115 | response_data = response.json() 116 | image_url = response_data["data"][0]["url"] 117 | 118 | return ( 119 | FunctionResultStatus.DONE, 120 | f"The generated image is: {image_url}", 121 | { 122 | "prompt": prompt, 123 | "image_url": image_url, 124 | }, 125 | ) 126 | except Exception as e: 127 | print(f"An error occurred while generating image: {str(e)}") 128 | return ( 129 | FunctionResultStatus.FAILED, 130 | f"An error occurred while while generating image: {str(e)}", 131 | { 132 | "prompt": prompt, 133 | }, 134 | ) 135 | -------------------------------------------------------------------------------- /plugins/imagegen/pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["hatchling"] 3 | build-backend = "hatchling.build" 4 | 5 | [project] 6 | name = "imagegen_game_sdk" 7 | version = "0.0.1" 8 | authors = [ 9 | { name = "Chinese Powered Labs"}, 10 | ] 11 | description = "Image generator for GAME by Virtuals" 12 | readme = "README.md" 13 | requires-python = ">=3.9" 14 | classifiers = [ 15 | "Programming Language :: Python :: 3", 16 | "Programming Language :: Python :: 3.9", 17 | "Programming Language :: Python :: 3.10", 18 | "Programming Language :: Python :: 3.11", 19 | "License :: OSI Approved :: MIT License", 20 | "Operating System :: OS Independent", 21 | "Development Status :: 3 - Alpha", 22 | "Intended Audience :: Developers", 23 | "Topic :: Software Development :: Libraries :: Python Modules", 24 | ] 25 | dependencies = [ 26 | "game-sdk>=0.1.1", 27 | "requests (>=2.32.3,<3.0.0)", 28 | ] 29 | 30 | [tool.hatch.build.targets.wheel] 31 | packages = ["imagegen_game_sdk"] 32 | 33 | [project.urls] 34 | "Homepage" = "https://github.com/game-by-virtuals/game-python/plugins/imagegen" 35 | "Bug Tracker" = "https://github.com/game-by-virtuals/game-python" 36 | -------------------------------------------------------------------------------- /plugins/membase/.python-version: -------------------------------------------------------------------------------- 1 | 3.10 2 | -------------------------------------------------------------------------------- /plugins/membase/README.md: -------------------------------------------------------------------------------- 1 | # Membase Plugin for GAME SDK 2 | 3 | A plugin for interacting with membase protocol through the GAME SDK. 4 | 5 | ## Description 6 | 7 | Membase is a core component of the Unibase ecosystem. It stores historical information, interaction records, and persistent data of Agents, ensuring their continuity and traceability. 8 | 9 | The membase plugin enables seamless integration with the membase protocol for decentralized storage. It provides functionality to upload memory to and reload it from the Unibase DA network. 10 | 11 | - support conversation switch 12 | - support conversation pesistence, upload if auto_upload_to_hub is set, conversation content can be visit at: https://testnet.hub.membase.io/ 13 | - support conversation preload from membase hub: https://testnet.hub.membase.io/ 14 | 15 | ## Installation 16 | 17 | ```bash 18 | pip install -e plugins/membase 19 | ``` 20 | 21 | ## Configuration 22 | 23 | The plugin requires the following environment variables to be set: 24 | 25 | ```shell 26 | MEMBASE_HUB= 27 | MEMBASE_ACCOUNT= 28 | MEMBASE_ID= 29 | ``` 30 | 31 | ## Usage 32 | 33 | ```python 34 | import time 35 | import uuid 36 | from membase_plugin_gamesdk.membase_plugin_gamesdk import MembasePlugin 37 | 38 | # set your own account and agent name 39 | # or set environment variables 40 | default_account = "game_sdk_test" 41 | default_agent_name = "game_sdk_test_agent" 42 | membase_plugin = MembasePlugin( 43 | account=default_account, 44 | agent_name=default_agent_name, 45 | auto_upload_to_hub=True, 46 | preload_from_hub=True, 47 | ) 48 | 49 | membase_plugin.add_memory("Hello, world!") 50 | new_conversation_id = str(uuid.uuid4()) 51 | membase_plugin.switch_conversation_id(new_conversation_id) 52 | membase_plugin.add_memory("Hello, world! 2") 53 | ``` 54 | 55 | more in `test_membase.py` file 56 | -------------------------------------------------------------------------------- /plugins/membase/membase_plugin_gamesdk/membase_plugin_gamesdk.py: -------------------------------------------------------------------------------- 1 | import os 2 | from typing import Literal, Optional 3 | from membase.memory.multi_memory import MultiMemory 4 | from membase.memory.message import Message 5 | 6 | class MembasePlugin: 7 | def __init__(self, account: Optional[str] = None, agent_name: Optional[str] = None, auto_upload_to_hub: bool = False, preload_from_hub: bool = False): 8 | if not account: 9 | self.account = os.getenv("MEMBASE_ACCOUNT") 10 | else: 11 | self.account = account 12 | if not self.account: 13 | raise ValueError("MEMBASE_ACCOUNT is not set and provided account is None") 14 | 15 | if not agent_name: 16 | self.id = os.getenv("MEMBASE_ID") 17 | else: 18 | self.id = agent_name 19 | if not self.id: 20 | self.id = self.account 21 | 22 | self._multi_memory = MultiMemory( 23 | membase_account=self.account, 24 | auto_upload_to_hub=auto_upload_to_hub, 25 | preload_from_hub=preload_from_hub, 26 | ) 27 | 28 | # memory_type: user,system,assistant | default: user 29 | def add_memory(self, memory: str, memory_type: Literal["user", "system", "assistant"] = "user", conversation_id: Optional[str] = None): 30 | msg = Message( 31 | name=self.id, 32 | content=memory, 33 | role=memory_type, 34 | ) 35 | self._multi_memory.add(msg, conversation_id) 36 | 37 | def get_memory(self, conversation_id: Optional[str] = None, recent_n: Optional[int] = None): 38 | return self._multi_memory.get(conversation_id, recent_n) 39 | 40 | def switch_conversation_id(self, conversation_id: Optional[str] = None): 41 | self._multi_memory.update_conversation_id(conversation_id) 42 | 43 | def reload_memory(self, conversation_id: str): 44 | self._multi_memory.load_from_hub(conversation_id) -------------------------------------------------------------------------------- /plugins/membase/plugin_metadata.yml: -------------------------------------------------------------------------------- 1 | # General Information 2 | plugin_name: "membase" # Name of the plugin 3 | author: "felix from unibase" # Author and team name 4 | logo_url: "https://www.unibase.io/favicon.ico" # URL to the author photo or team logo (512x512 recommended) 5 | release_date: "2025-04" # Release date (DD-MM-YYYY) 6 | 7 | # Description 8 | short_description: "Membase is a plugin for the Game SDK that allows you to store and retrieve memories from membase database." # One-liner description for listings 9 | detailed_description: "Membase is a plugin for the Game SDK that allows you to store and retrieve memories from membase database." # Full description with features and benefits 10 | 11 | # Media & Assets 12 | plugin_logo_url: "https://www.unibase.io/favicon.ico" # URL to the plugin logo (512x512 recommended) (if any or fallback to logo_url) 13 | screenshots: # List of screenshots showcasing the plugin 14 | - "https://github.com/unibaseio/membase/blob/main/assets/virtual_case.png" # e.g., "https://example.com/screenshot1.png" 15 | - "https://github.com/unibaseio/membase/blob/main/assets/virtual_reload.png" 16 | demo_video_url: "" # Link to a demo or walkthrough video (if available) 17 | documentation_url: "https://www.unibase.io/" # Link to the plugin's official documentation (if available) 18 | changelog_url: "" # Link to the changelog (if maintained) 19 | 20 | # Contact & Support 21 | x_account_handle: "@fiatlucis" # X (formerly known as Twitter) account handle (ie: @GAME_Virtuals) 22 | support_contact: "yydfjt@gmail.com" # Email or Slack/Discord link for user support 23 | community_link: "" # Forum or community link (if any) 24 | -------------------------------------------------------------------------------- /plugins/membase/pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "membase_plugin_gamesdk" 3 | version = "0.1.0" 4 | description = "Add your description here" 5 | readme = "README.md" 6 | requires-python = ">=3.10" 7 | dependencies = [ 8 | "membase>=0.1.5", 9 | ] 10 | 11 | [tool.uv.sources] 12 | membase = { git = "https://github.com/unibaseio/membase.git" } 13 | -------------------------------------------------------------------------------- /plugins/membase/test_membase.py: -------------------------------------------------------------------------------- 1 | import time 2 | import uuid 3 | from membase_plugin_gamesdk.membase_plugin_gamesdk import MembasePlugin 4 | 5 | # set your own account and agent name 6 | # or set environment variables 7 | default_account = "game_sdk_test" 8 | default_agent_name = "game_sdk_test_agent" 9 | membase_plugin = MembasePlugin( 10 | account=default_account, 11 | agent_name=default_agent_name, 12 | auto_upload_to_hub=True, 13 | preload_from_hub=True, 14 | ) 15 | 16 | membase_plugin.add_memory("Hello, world!") 17 | new_conversation_id = str(uuid.uuid4()) 18 | membase_plugin.switch_conversation_id(new_conversation_id) 19 | membase_plugin.add_memory("Hello, world! 2") 20 | 21 | time.sleep(3) 22 | 23 | new_agent_name = "game_sdk_test_agent_new" 24 | new_membase_plugin = MembasePlugin( 25 | account=default_account, 26 | agent_name=new_agent_name, 27 | auto_upload_to_hub=True, 28 | preload_from_hub=True, 29 | ) 30 | 31 | res = new_membase_plugin.get_memory(new_conversation_id, 1) 32 | if res is None: 33 | raise Exception("Failed to get memory none") 34 | if len(res) != 1: 35 | raise Exception("Failed to get memory") 36 | if res[0].content != "Hello, world! 2": 37 | raise Exception("Content is not correct") 38 | 39 | new_membase_plugin.add_memory("Hello, world! 3") 40 | 41 | print("Test passed") 42 | -------------------------------------------------------------------------------- /plugins/membase/test_membase_agent.py: -------------------------------------------------------------------------------- 1 | import os 2 | from typing import Literal, Tuple 3 | from game_sdk.game.custom_types import Function, FunctionResultStatus, Argument 4 | from membase_plugin_gamesdk.membase_plugin_gamesdk import MembasePlugin 5 | from game_sdk.game.chat_agent import Chat, ChatAgent 6 | 7 | 8 | # set your own account and agent name 9 | # or set environment variables 10 | default_account = "game_sdk_test" 11 | default_agent_name = "game_sdk_test_agent" 12 | default_conversation_id = "67fd033a0740eed72502b65e" 13 | 14 | game_api_key = os.environ.get("GAME_API_KEY") 15 | chat_agent = ChatAgent( 16 | prompt="You are a helpful assistant.", 17 | api_key=game_api_key, 18 | ) 19 | 20 | membase_plugin = MembasePlugin( 21 | account=default_account, 22 | agent_name=default_agent_name, 23 | auto_upload_to_hub=True, 24 | preload_from_hub=False, 25 | ) 26 | 27 | 28 | def save_memory_executable(membase_plugin: MembasePlugin, memory: str, memory_type: Literal["user", "assistant"]) -> Tuple[FunctionResultStatus, str, dict]: 29 | try: 30 | membase_plugin.add_memory(memory, memory_type) 31 | return FunctionResultStatus.DONE, "Memory added successfully", {} 32 | except Exception as e: 33 | return FunctionResultStatus.FAILED, str(e), {} 34 | 35 | def get_memory_executable(membase_plugin: MembasePlugin, recent_n: int = 10) -> Tuple[FunctionResultStatus, str, dict]: 36 | try: 37 | memory = membase_plugin.get_memory(recent_n=recent_n) 38 | result = "" 39 | for msg in memory: 40 | result += f"{msg.role}: {msg.content}\n" 41 | return FunctionResultStatus.DONE, result, {} 42 | except Exception as e: 43 | return FunctionResultStatus.FAILED, str(e), {} 44 | 45 | action_space = [ 46 | Function( 47 | fn_name="save_memory", 48 | fn_description="Save a memory to the membase database", 49 | args=[ 50 | Argument(name="memory", type="str", description="The memory to save"), 51 | Argument(name="memory_type", type=["user", "assistant"], description="The type of memory to save"), 52 | ], 53 | executable=lambda memory, memory_type: save_memory_executable(membase_plugin, memory, memory_type), 54 | ), 55 | Function( 56 | fn_name="get_memory", 57 | fn_description="Get the last n memories from the membase database", 58 | args=[ 59 | Argument(name="recent_n", type="int", description="The number of memories to retrieve"), 60 | ], 61 | executable=lambda recent_n: get_memory_executable(membase_plugin, recent_n), 62 | ) 63 | ] 64 | 65 | chat = chat_agent.create_chat( 66 | partner_id=default_account, 67 | partner_name=default_agent_name, 68 | action_space=action_space, 69 | ) 70 | 71 | membase_plugin.switch_conversation_id(default_conversation_id) 72 | membase_plugin.reload_memory(default_conversation_id) 73 | 74 | chat_continue = True 75 | while chat_continue: 76 | 77 | user_message = input("Enter a message: ") 78 | 79 | response = chat.next(user_message) 80 | 81 | if response.function_call: 82 | print(f"Function call: {response.function_call.fn_name}") 83 | 84 | if response.message: 85 | print(f"Response: {response.message}") 86 | 87 | if response.is_finished: 88 | chat_continue = False 89 | break 90 | 91 | print("Chat ended") 92 | 93 | -------------------------------------------------------------------------------- /plugins/onchain_actions/README.md: -------------------------------------------------------------------------------- 1 | # Onchain Actions Plugin for Virtuals Game 2 | 3 | The onchain actions plugin allows your GAME agents to execute onchain actions such as swaps, transfers, staking, etc. all by leveraging the [GOAT SDK](https://github.com/goat-sdk/goat). 4 | 5 | Supports: 6 | - Any chain, from EVM, to Solana, to Sui, etc. 7 | - Any wallet type, from key pairs to smart wallets from Crossmint, etc. 8 | - More than +200 onchain tools from the GOAT SDK, [see all available tools here](https://github.com/goat-sdk/goat/tree/main/python#plugins) 9 | 10 | Are you part of a startup agent team building with Virtuals? 11 | You may be eligible for free credits through Crossmint's Startup Program! [Apply now](https://www.crossmint.com/startup-program) to unlock exclusive startup benefits — and don’t forget to mention you’re part of a startup building with Virtuals when you get in touch. 12 | 13 | ## Running examples 14 | 15 | To run the examples showcased in the plugin's directory, follow these steps: 16 | 17 | 1. Create an venv 18 | ``` 19 | python -m venv .venv 20 | source .venv/bin/activate 21 | ``` 22 | 23 | 2. Install dependencies: 24 | ``` 25 | pip install -e . 26 | ``` 27 | 28 | 3. Set up environment variables: 29 | ``` 30 | export GAME_API_KEY="your-game-api-key" 31 | export WALLET_PRIVATE_KEY="your-wallet-private-key" 32 | export RPC_PROVIDER_URL="your-rpc-provider-url" 33 | export UNISWAP_API_KEY=kHEhfIPvCE3PO5PeT0rNb1CA3JJcnQ8r7kJDXN5X # Public key to test with 34 | ``` 35 | 36 | 4. Run example scripts: 37 | 38 | Example worker: 39 | ``` 40 | python ./examples/example-worker.py 41 | ``` 42 | 43 | Example agent: 44 | ``` 45 | python ./examples/example-agent.py 46 | ``` 47 | -------------------------------------------------------------------------------- /plugins/onchain_actions/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/game-by-virtuals/game-python/76a1a712be14b971fd59a45c14c76c8775884e73/plugins/onchain_actions/__init__.py -------------------------------------------------------------------------------- /plugins/onchain_actions/examples/.env.example: -------------------------------------------------------------------------------- 1 | WALLET_PRIVATE_KEY= 2 | RPC_PROVIDER_URL= 3 | GAME_API_KEY= 4 | UNISWAP_API_KEY= 5 | -------------------------------------------------------------------------------- /plugins/onchain_actions/examples/example-agent.py: -------------------------------------------------------------------------------- 1 | import os 2 | from dotenv import load_dotenv 3 | from pathlib import Path 4 | from game_sdk.game.agent import Agent, WorkerConfig 5 | from game_sdk.game.custom_types import FunctionResult 6 | from onchain_actions_game_sdk.onchain_actions import get_onchain_actions 7 | from goat_plugins.erc20.token import PEPE, USDC 8 | from goat_plugins.erc20 import ERC20PluginOptions, erc20 9 | 10 | from web3 import Web3 11 | from web3.middleware.signing import construct_sign_and_send_raw_middleware 12 | from eth_account.signers.local import LocalAccount 13 | from eth_account import Account 14 | from goat_plugins.uniswap import uniswap, UniswapPluginOptions 15 | from goat_wallets.web3 import Web3EVMWalletClient 16 | 17 | # Load environment variables from .env file 18 | env_path = Path(__file__).parent / '.env' 19 | load_dotenv(dotenv_path=env_path) 20 | 21 | def get_agent_state_fn(function_result: FunctionResult, current_state: dict) -> dict: 22 | """ 23 | Update state based on the function results 24 | """ 25 | init_state = {} 26 | 27 | if current_state is None: 28 | return init_state 29 | 30 | if function_result.info is not None: 31 | # Update state with the function result info 32 | current_state.update(function_result.info) 33 | 34 | return current_state 35 | 36 | def get_worker_state(function_result: FunctionResult, current_state: dict) -> dict: 37 | """ 38 | Update state based on the function results 39 | """ 40 | init_state = {} 41 | 42 | if current_state is None: 43 | return init_state 44 | 45 | if function_result.info is not None: 46 | # Update state with the function result info 47 | current_state.update(function_result.info) 48 | 49 | return current_state 50 | 51 | 52 | # Initialize Web3 and account 53 | w3 = Web3(Web3.HTTPProvider(os.environ.get("RPC_PROVIDER_URL"))) 54 | private_key = os.environ.get("WALLET_PRIVATE_KEY") 55 | assert private_key is not None, "You must set WALLET_PRIVATE_KEY environment variable" 56 | assert private_key.startswith("0x"), "Private key must start with 0x hex prefix" 57 | 58 | account: LocalAccount = Account.from_key(private_key) 59 | w3.eth.default_account = account.address # Set the default account 60 | w3.middleware_onion.add( 61 | construct_sign_and_send_raw_middleware(account) 62 | ) # Add middleware 63 | 64 | # Initialize tools with web3 wallet and Uniswap plugin 65 | uniswap_api_key = os.environ.get("UNISWAP_API_KEY") 66 | uniswap_base_url = os.environ.get("UNISWAP_BASE_URL", "https://trade-api.gateway.uniswap.org/v1") 67 | assert uniswap_api_key is not None, "You must set UNISWAP_API_KEY environment variable" 68 | assert uniswap_base_url is not None, "You must set UNISWAP_BASE_URL environment variable" 69 | 70 | actions = get_onchain_actions( 71 | # You can also use other wallet types, such as Solana, etc. 72 | # See an example [here](https://github.com/goat-sdk/goat/blob/main/python/examples/solana/wallet/example.py) 73 | wallet=Web3EVMWalletClient(w3), 74 | plugins=[ 75 | # Add any plugin you'd want to use here. You can see a list of all available 76 | # plugins in Python [here](https://github.com/goat-sdk/goat/tree/main/python#plugins) 77 | # 78 | # Swap tokens with Uniswap or Jupiter, get info from CoinGecko, etc. 79 | erc20(options=ERC20PluginOptions(tokens=[USDC, PEPE])), 80 | uniswap(options=UniswapPluginOptions( 81 | api_key=uniswap_api_key, 82 | base_url=uniswap_base_url 83 | )), 84 | ], 85 | ) 86 | 87 | # Create worker 88 | onchain_actions_worker = WorkerConfig( 89 | id="onchain_actions_worker", 90 | worker_description="Worker that executes onchain actions such as swaps, transfers, etc.", 91 | get_state_fn=get_worker_state, 92 | action_space=actions, 93 | ) 94 | 95 | # Initialize the agent 96 | agent = Agent( 97 | api_key=os.environ.get("GAME_API_KEY"), 98 | name="Onchain Actions Agent", 99 | agent_goal="Swap 0.01 USDC to PEPE", 100 | agent_description=( 101 | "An agent that executes onchain actions" 102 | ), 103 | get_agent_state_fn=get_agent_state_fn, 104 | workers=[ 105 | onchain_actions_worker, 106 | ] 107 | ) 108 | 109 | agent.compile() 110 | agent.run() 111 | -------------------------------------------------------------------------------- /plugins/onchain_actions/examples/example-worker.py: -------------------------------------------------------------------------------- 1 | import os 2 | from dotenv import load_dotenv 3 | from pathlib import Path 4 | from game_sdk.game.worker import Worker 5 | from game_sdk.game.custom_types import FunctionResult 6 | from onchain_actions_game_sdk.onchain_actions import get_onchain_actions 7 | from goat_plugins.erc20.token import PEPE, USDC 8 | from goat_plugins.erc20 import ERC20PluginOptions, erc20 9 | 10 | from web3 import Web3 11 | from web3.middleware.signing import construct_sign_and_send_raw_middleware 12 | from eth_account.signers.local import LocalAccount 13 | from eth_account import Account 14 | from goat_plugins.uniswap import uniswap, UniswapPluginOptions 15 | from goat_wallets.web3 import Web3EVMWalletClient 16 | 17 | # Load environment variables from .env file 18 | env_path = Path(__file__).parent / '.env' 19 | load_dotenv(dotenv_path=env_path) 20 | 21 | def get_state_fn(function_result: FunctionResult, current_state: dict) -> dict: 22 | """ 23 | Update state based on the function results 24 | """ 25 | init_state = {} 26 | 27 | if current_state is None: 28 | return init_state 29 | 30 | # Update state with the function result info 31 | current_state.update(function_result.info) 32 | 33 | return current_state 34 | 35 | 36 | # Initialize Web3 and account 37 | w3 = Web3(Web3.HTTPProvider(os.environ.get("RPC_PROVIDER_URL"))) 38 | private_key = os.environ.get("WALLET_PRIVATE_KEY") 39 | assert private_key is not None, "You must set WALLET_PRIVATE_KEY environment variable" 40 | assert private_key.startswith("0x"), "Private key must start with 0x hex prefix" 41 | 42 | account: LocalAccount = Account.from_key(private_key) 43 | w3.eth.default_account = account.address # Set the default account 44 | w3.middleware_onion.add( 45 | construct_sign_and_send_raw_middleware(account) 46 | ) # Add middleware 47 | 48 | # Initialize tools with web3 wallet and Uniswap plugin 49 | uniswap_api_key = os.environ.get("UNISWAP_API_KEY") 50 | uniswap_base_url = os.environ.get("UNISWAP_BASE_URL", "https://trade-api.gateway.uniswap.org/v1") 51 | assert uniswap_api_key is not None, "You must set UNISWAP_API_KEY environment variable" 52 | assert uniswap_base_url is not None, "You must set UNISWAP_BASE_URL environment variable" 53 | 54 | actions = get_onchain_actions( 55 | # You can also use other wallet types, such as Solana, etc. 56 | # See an example [here](https://github.com/goat-sdk/goat/blob/main/python/examples/solana/wallet/example.py) 57 | wallet=Web3EVMWalletClient(w3), 58 | plugins=[ 59 | # Add any plugin you'd want to use here. You can see a list of all available 60 | # plugins in Python [here](https://github.com/goat-sdk/goat/tree/main/python#plugins) 61 | # 62 | # Swap tokens with Uniswap or Jupiter, get info from CoinGecko, etc. 63 | erc20(options=ERC20PluginOptions(tokens=[USDC, PEPE])), 64 | uniswap(options=UniswapPluginOptions( 65 | api_key=uniswap_api_key, 66 | base_url=uniswap_base_url 67 | )), 68 | ], 69 | ) 70 | 71 | # Create worker 72 | onchain_actions_worker = Worker( 73 | api_key=os.environ.get("GAME_API_KEY"), 74 | description="Worker that executes onchain actions such as swaps, transfers, etc.", 75 | get_state_fn=get_state_fn, 76 | action_space=actions, 77 | ) 78 | 79 | # # Run example query 80 | queries = [ 81 | "Get your USDC balance in decimal units", 82 | ] 83 | for query in queries: 84 | print("-" * 100) 85 | print(f"Query: {query}") 86 | onchain_actions_worker.run(query) 87 | -------------------------------------------------------------------------------- /plugins/onchain_actions/onchain_actions_game_sdk/onchain_actions.py: -------------------------------------------------------------------------------- 1 | from game_sdk.game.custom_types import Argument, Function, FunctionResultStatus 2 | from goat import ToolBase, WalletClientBase, get_tools 3 | from typing import List, Any 4 | 5 | 6 | def create_game_function(tool: ToolBase): 7 | args = [] 8 | for field_name, field in tool.parameters.__fields__.items(): 9 | args.append(Argument( 10 | name=field_name, 11 | required=field.is_required(), 12 | description=field.description or "", 13 | )) 14 | 15 | return Function( 16 | fn_name=tool.name, 17 | fn_description=tool.description, 18 | args=args, 19 | executable=lambda **args: _execute_tool(tool, **args), 20 | ) 21 | 22 | def _execute_tool(tool: ToolBase, **args): 23 | try: 24 | result = tool.execute(args) 25 | return ( 26 | FunctionResultStatus.DONE, 27 | f"{tool.name} executed successfully", 28 | { "result": result }, 29 | ) 30 | except Exception as e: 31 | print(e) 32 | return ( 33 | FunctionResultStatus.FAILED, 34 | f"Error executing tool: {e}", 35 | args, 36 | ) 37 | 38 | def get_onchain_actions(wallet: WalletClientBase, plugins: List[Any]): 39 | tools = get_tools(wallet=wallet, plugins=plugins) 40 | functions = [create_game_function(tool) for tool in tools] 41 | return functions 42 | -------------------------------------------------------------------------------- /plugins/onchain_actions/pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["hatchling"] 3 | build-backend = "hatchling.build" 4 | 5 | [project] 6 | name = "onchain_actions_game_sdk" 7 | version = "0.1.0" 8 | authors = [ 9 | { name = "GOAT" }, 10 | ] 11 | description = "Official Onchain Actions Plugin in Python for GAME by GOAT" 12 | readme = "README.md" 13 | requires-python = ">=3.9" 14 | classifiers = [ 15 | "Programming Language :: Python :: 3", 16 | "Programming Language :: Python :: 3.9", 17 | "Programming Language :: Python :: 3.10", 18 | "Programming Language :: Python :: 3.11", 19 | "License :: OSI Approved :: MIT License", 20 | "Operating System :: OS Independent", 21 | "Development Status :: 3 - Alpha", 22 | "Intended Audience :: Developers", 23 | "Topic :: Software Development :: Libraries :: Python Modules", 24 | ] 25 | dependencies = [ 26 | "game-sdk>=0.1.1", 27 | "web3>=6.20.3", 28 | "goat-sdk>=0.1.3", 29 | "goat-sdk-wallet-evm>=0.1.1", 30 | "goat-sdk-wallet-web3>=0.1.0", 31 | "goat-sdk-plugin-erc20>=0.1.0", 32 | "goat-sdk-plugin-uniswap>=0.1.0", 33 | ] 34 | 35 | [project.optional-dependencies] 36 | dev = [ 37 | "python-dotenv>=1.0.0", 38 | ] 39 | 40 | [tool.hatch.build.targets.wheel] 41 | packages = ["onchain_actions_game_sdk"] 42 | 43 | [project.urls] 44 | "Homepage" = "https://github.com/game-by-virtuals/game-python/plugins/onchain_actions" 45 | "Bug Tracker" = "https://github.com/game-by-virtuals/game-python" -------------------------------------------------------------------------------- /plugins/plugin_metadata_template.yml: -------------------------------------------------------------------------------- 1 | # General Information 2 | plugin_name: "" # Name of the plugin 3 | author: "" # Author and team name 4 | logo_url: "" # URL to the author photo or team logo (512x512 recommended) (if any) 5 | release_date: "" # Release date (YYYY-MM) 6 | 7 | # Description 8 | short_description: "" # One-liner description for listings 9 | detailed_description: "" # Full description with features and benefits 10 | 11 | # Media & Assets 12 | plugin_logo_url: "" # URL to the plugin logo (512x512 recommended) (if any or fallback to logo_url) 13 | screenshots: # List of screenshots showcasing the plugin (if any) 14 | - "" # e.g., "https://example.com/screenshot1.png" 15 | - "" 16 | demo_video_url: "" # Link to a demo or walkthrough video (if any) 17 | documentation_url: "" # Link to the plugin's official documentation (if any) 18 | changelog_url: "" # Link to the changelog (if maintained) 19 | 20 | # Contact & Support 21 | x_account_handle: "" # X (formerly known as Twitter) account handle (ie: @GAME_Virtuals) 22 | support_contact: "" # Email or Slack/Discord link for user support 23 | community_url: "" # Forum or community link (if any) 24 | -------------------------------------------------------------------------------- /plugins/stateofmika/README.md: -------------------------------------------------------------------------------- 1 | # State of Mika Plugin for GAME SDK 2 | 3 | The State of Mika Plugin plugin seamlessly empowers G.A.M.E agents with real-time, advanced, self-improving AI inferences, delivering high-performance insights without introducing any additional complexity. 4 | 5 | ## Features 6 | 7 | - image_recognition - Analyze and describe images using AI vision 8 | - scraper - Scrape and process content from external websites 9 | - news - Fetch and analyze cryptocurrency and blockchain news 10 | - token_information - Get token price and market information from DEX aggregators 11 | - math - Perform complex mathematical calculations 12 | 13 | ## Setup and Configuration 14 | 15 | Import and initialize the plugin to use in your worker: 16 | 17 | ```python 18 | from game_sdk.plugins.stateofmika.functions.router import SOMRouterFunction 19 | 20 | state_of_mika_plugin = SOMRouterFunction() 21 | ``` 22 | 23 | **Basic worker example:** 24 | 25 | ```python 26 | def get_state_fn(function_result: FunctionResult, current_state: dict) -> dict: 27 | """ 28 | Update state based on router responses 29 | """ 30 | init_state = { 31 | "previous_queries": [], 32 | "previous_routes": [] 33 | } 34 | 35 | if current_state is None: 36 | return init_state 37 | 38 | # Update state with results 39 | if function_result and function_result.info: 40 | current_state["previous_queries"].append(function_result.info.get("query", "")) 41 | current_state["previous_routes"].append(function_result.info.get("route", {})) 42 | 43 | return current_state 44 | 45 | worker = Worker( 46 | api_key=game_api_key, 47 | description="An intelligent assistant that uses StateOfMika for routing queries", 48 | get_state_fn=get_state_fn, 49 | action_space=[router_fn] 50 | ) 51 | 52 | worker.run("What's the latest price of Bitcoin?") 53 | ``` 54 | -------------------------------------------------------------------------------- /plugins/stateofmika/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/game-by-virtuals/game-python/76a1a712be14b971fd59a45c14c76c8775884e73/plugins/stateofmika/__init__.py -------------------------------------------------------------------------------- /plugins/stateofmika/examples/example-agent.py: -------------------------------------------------------------------------------- 1 | import os 2 | from game_sdk.game.agent import Agent, WorkerConfig 3 | from game_sdk.game.custom_types import FunctionResult 4 | 5 | from stateofmika_plugin_gamesdk.functions.router import SOMRouter 6 | 7 | 8 | def get_agent_state_fn(function_result: FunctionResult, current_state: dict) -> dict: 9 | """ 10 | Update state based on router responses 11 | """ 12 | init_state = {"previous_queries": [], "previous_routes": []} 13 | 14 | if current_state is None: 15 | return init_state 16 | 17 | # Update state with results 18 | if function_result and function_result.info: 19 | current_state["previous_queries"].append(function_result.info.get("query", "")) 20 | current_state["previous_routes"].append(function_result.info.get("route", {})) 21 | 22 | return current_state 23 | 24 | 25 | def get_worker_state(function_result: FunctionResult, current_state: dict) -> dict: 26 | """ 27 | Update state based on router responses 28 | """ 29 | init_state = {"previous_queries": [], "previous_routes": []} 30 | 31 | if current_state is None: 32 | return init_state 33 | 34 | # Update state with results 35 | if function_result and function_result.info: 36 | current_state["previous_queries"].append(function_result.info.get("query", "")) 37 | current_state["previous_routes"].append(function_result.info.get("route", {})) 38 | 39 | return current_state 40 | 41 | 42 | mika_router = SOMRouter() 43 | 44 | price_inference_worker = WorkerConfig( 45 | id="som_router", 46 | worker_description="Worker specialized in routing natural language query to appropriate tools and process responses", 47 | get_state_fn=get_worker_state, 48 | action_space=[ 49 | mika_router.get_function(), 50 | ], 51 | ) 52 | 53 | # Initialize the agent 54 | agent = Agent( 55 | api_key=os.environ.get("GAME_API_KEY"), 56 | name="Mika Agent", 57 | agent_goal="Help user get the latest bitcoin price", 58 | agent_description=( 59 | "You are an AI agent specialized in routing natural language query to appropriate tools and process responses" 60 | ), 61 | get_agent_state_fn=get_agent_state_fn, 62 | workers=[ 63 | price_inference_worker, 64 | ], 65 | ) 66 | 67 | agent.compile() 68 | agent.run() 69 | -------------------------------------------------------------------------------- /plugins/stateofmika/examples/example-worker.py: -------------------------------------------------------------------------------- 1 | from game_sdk.game.worker import Worker 2 | from game_sdk.game.custom_types import FunctionResult 3 | from stateofmika_plugin_gamesdk.functions.router import SOMRouter 4 | 5 | 6 | # Example state function 7 | def get_state_fn(function_result: FunctionResult, current_state: dict) -> dict: 8 | """ 9 | Update state based on router responses 10 | """ 11 | init_state = {"previous_queries": [], "previous_routes": []} 12 | 13 | if current_state is None: 14 | return init_state 15 | 16 | # Update state with results 17 | if function_result and function_result.info: 18 | current_state["previous_queries"].append(function_result.info.get("query", "")) 19 | current_state["previous_routes"].append(function_result.info.get("route", {})) 20 | 21 | return current_state 22 | 23 | 24 | # Initialize worker with SOM router 25 | game_api_key = "your_game_api_key" 26 | 27 | # Create router function 28 | router_fn = SOMRouter() 29 | 30 | # Create worker 31 | worker = Worker( 32 | api_key=game_api_key, 33 | description="An intelligent assistant that uses StateOfMika for routing queries", 34 | get_state_fn=get_state_fn, 35 | action_space=[router_fn.get_function()], 36 | ) 37 | 38 | # Run example query 39 | worker.run("What's the latest price of Bitcoin?") 40 | -------------------------------------------------------------------------------- /plugins/stateofmika/examples/example.py: -------------------------------------------------------------------------------- 1 | """ 2 | State of Mika API Examples 3 | ------------------------- 4 | This file demonstrates the usage of each tool available in the State of Mika API. 5 | Each example includes: 6 | - Feature description 7 | - Example API request 8 | - Expected response 9 | 10 | All requests are routed through the universal router, which intelligently 11 | directs queries to the appropriate tool. 12 | """ 13 | 14 | import requests 15 | import json 16 | from typing import Dict, Any 17 | 18 | BASE_URL = "http://localhost:8000/api/v1" 19 | API_KEY = "4f53316d-d945-4f17-a898-804e0ab5bfc9" 20 | 21 | 22 | def route_query(query: str) -> Dict: 23 | """Helper function to route all queries through the universal router""" 24 | print("\n=== Starting API Request ===") 25 | 26 | # Set up headers for multipart/form-data 27 | headers = {"X-API-Key": API_KEY, "accept": "application/json"} 28 | 29 | # Set up form data 30 | form_data = { 31 | "query": (None, query), 32 | "tool": (None, ""), 33 | "parameters_str": (None, ""), 34 | "file": (None, ""), 35 | } 36 | 37 | url = f"{BASE_URL}/" 38 | print(f"URL: {url}") 39 | print(f"Headers: {json.dumps(headers, indent=2)}") 40 | print(f"Form data: {form_data}") 41 | 42 | try: 43 | print("\nMaking request...") 44 | response = requests.post( 45 | url, 46 | headers=headers, 47 | files=form_data, # Use files parameter for multipart/form-data 48 | ) 49 | print(f"Status code: {response.status_code}") 50 | 51 | if response.status_code != 200: 52 | print(f"Error response: {response.text}") 53 | else: 54 | print("Request successful!") 55 | 56 | return response.json() 57 | except Exception as e: 58 | print(f"Error making request: {str(e)}") 59 | return {"error": str(e)} 60 | 61 | 62 | def test_connection(): 63 | """Test basic connectivity with a simple query""" 64 | print("\nTesting API connection...") 65 | result = route_query("What is 2 + 2?") 66 | print("\nFull response:") 67 | print(json.dumps(result, indent=2)) 68 | return result 69 | 70 | 71 | def test_news(): 72 | """Test news endpoint""" 73 | print("\nTesting news endpoint...") 74 | result = route_query("Show me the latest crypto news about Bitcoin and Ethereum") 75 | print("\nFull response:") 76 | print(json.dumps(result, indent=2)) 77 | return result 78 | 79 | 80 | def test_math(): 81 | """Test math endpoint""" 82 | print("\nTesting math endpoint...") 83 | result = route_query("Calculate 15% of 150 and add 500") 84 | print("\nFull response:") 85 | print(json.dumps(result, indent=2)) 86 | return result 87 | 88 | 89 | def test_token_price(): 90 | """Test token price endpoint""" 91 | print("\nTesting token price endpoint...") 92 | result = route_query("What is the current price of Solana?") 93 | print("\nFull response:") 94 | print(json.dumps(result, indent=2)) 95 | return result 96 | 97 | 98 | def test_scraper(): 99 | """Test scraper endpoint""" 100 | print("\nTesting scraper endpoint...") 101 | result = route_query("Summarize the article at https://example.com/crypto-article") 102 | print("\nFull response:") 103 | print(json.dumps(result, indent=2)) 104 | return result 105 | 106 | 107 | def test_dex_sales(): 108 | """Test DEX sales endpoint""" 109 | print("\nTesting DEX sales endpoint...") 110 | result = route_query( 111 | "Show me SOL token sales in the last hour for address SOL_TOKEN_MINT_ADDRESS" 112 | ) 113 | print("\nFull response:") 114 | print(json.dumps(result, indent=2)) 115 | return result 116 | 117 | 118 | def test_dex_buys(): 119 | """Test DEX buys endpoint""" 120 | print("\nTesting DEX buys endpoint...") 121 | result = route_query("Show me all SOL purchases by wallet WALLET_ADDRESS") 122 | print("\nFull response:") 123 | print(json.dumps(result, indent=2)) 124 | return result 125 | 126 | 127 | def run_all_tests(): 128 | """Run all test functions""" 129 | tests = [ 130 | test_connection, 131 | test_news, 132 | test_math, 133 | test_token_price, 134 | test_scraper, 135 | test_dex_sales, 136 | test_dex_buys, 137 | ] 138 | 139 | for test in tests: 140 | print(f"\n{'=' * 50}") 141 | print(f"Running {test.__name__}") 142 | print("=" * 50) 143 | test() 144 | 145 | 146 | if __name__ == "__main__": 147 | print("Run test_connection() to test basic connectivity") 148 | print("Run run_all_tests() to test all endpoints") 149 | -------------------------------------------------------------------------------- /plugins/stateofmika/pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["hatchling"] 3 | build-backend = "hatchling.build" 4 | 5 | [project] 6 | name = "stateofmika_plugin_gamesdk" 7 | version = "0.1.0" 8 | authors = [{ name = "Alex Tan", email = "alex@chasm.net" }] 9 | description = "Official State of Mika Python SDK for GAME by Virtuals" 10 | requires-python = ">=3.8" 11 | classifiers = [ 12 | "Programming Language :: Python :: 3", 13 | "Programming Language :: Python :: 3.8", 14 | "Programming Language :: Python :: 3.9", 15 | "Programming Language :: Python :: 3.10", 16 | "Programming Language :: Python :: 3.11", 17 | "License :: OSI Approved :: MIT License", 18 | "Operating System :: OS Independent", 19 | "Development Status :: 3 - Alpha", 20 | "Intended Audience :: Developers", 21 | "Topic :: Software Development :: Libraries :: Python Modules", 22 | ] 23 | dependencies = [ 24 | "aiohttp>=3.11.11", 25 | "game-sdk>=0.1.1" 26 | ] 27 | 28 | [tool.hatch.build.targets.wheel] 29 | packages = ["stateofmika_plugin_gamesdk"] 30 | 31 | [project.urls] 32 | "Homepage" = "https://github.com/game-by-virtuals/game-python/plugins/stateofmika" 33 | "Bug Tracker" = "https://github.com/game-by-virtuals/game-python" 34 | 35 | -------------------------------------------------------------------------------- /plugins/stateofmika/stateofmika_plugin_gamesdk/functions/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/game-by-virtuals/game-python/76a1a712be14b971fd59a45c14c76c8775884e73/plugins/stateofmika/stateofmika_plugin_gamesdk/functions/__init__.py -------------------------------------------------------------------------------- /plugins/stateofmika/stateofmika_plugin_gamesdk/functions/router.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | from typing import Dict, Any, Tuple 3 | from game_sdk.game.custom_types import Function, Argument, FunctionResultStatus 4 | import aiohttp 5 | 6 | 7 | class SOMRouter: 8 | """ 9 | StateOfMika Router Function for intelligent query routing 10 | """ 11 | 12 | def __init__(self, api_key: str = "1ef4dccd-c80a-410b-86c6-220df04ab589"): 13 | self.api_key = api_key 14 | self.base_url = "https://state.gmika.io/api" 15 | 16 | async def _make_request( 17 | self, endpoint: str, data: Dict[str, Any] 18 | ) -> Dict[str, Any]: 19 | """Make request to StateOfMika API""" 20 | async with aiohttp.ClientSession() as session: 21 | form_data = aiohttp.FormData() 22 | for key, value in data.items(): 23 | form_data.add_field(key, str(value)) 24 | async with session.post( 25 | f"{self.base_url}/{endpoint}", 26 | headers={"X-API-Key": self.api_key}, 27 | data=form_data, 28 | ) as response: 29 | if response.status == 200: 30 | return await response.json() 31 | else: 32 | raise ValueError( 33 | f"API request failed with status {response.status}" 34 | ) 35 | 36 | async def _execute_query_async( 37 | self, query: str, **kwargs 38 | ) -> Tuple[FunctionResultStatus, str, Dict[str, Any]]: 39 | """ 40 | Execute the router function asynchronously. 41 | """ 42 | try: 43 | data = {"query": query} 44 | response = await self._make_request("v1/", data) 45 | 46 | return ( 47 | FunctionResultStatus.DONE, 48 | f"Successfully routed query: {query}", 49 | {"route": response.get("route"), "response": response.get("response")}, 50 | ) 51 | 52 | except Exception as e: 53 | return ( 54 | FunctionResultStatus.FAILED, 55 | f"Error routing query: {str(e)}", 56 | {}, 57 | ) 58 | 59 | def _execute_query( 60 | self, query: str, **kwargs 61 | ) -> Tuple[FunctionResultStatus, str, Dict[str, Any]]: 62 | """ 63 | Synchronous wrapper for the asynchronous _execute_query_async function. 64 | 65 | Ensures the function can be called synchronously. 66 | """ 67 | try: 68 | return asyncio.run(self._execute_query_async(query)) 69 | except Exception as e: 70 | return ( 71 | FunctionResultStatus.FAILED, 72 | f"Error routing query: {str(e)}", 73 | {}, 74 | ) 75 | 76 | def get_function(self) -> Function: 77 | return Function( 78 | fn_name="som_route_query", 79 | fn_description="Route a natural language query to appropriate tools and process responses", 80 | args=[ 81 | Argument( 82 | name="query", 83 | type="string", 84 | description="Natural language query to route", 85 | ), 86 | ], 87 | hint="This function is used to route a natural language query to appropriate tools and process responses.", 88 | executable=self._execute_query, 89 | ) 90 | -------------------------------------------------------------------------------- /plugins/stateofmika/stateofmika_plugin_gamesdk/types/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/game-by-virtuals/game-python/76a1a712be14b971fd59a45c14c76c8775884e73/plugins/stateofmika/stateofmika_plugin_gamesdk/types/__init__.py -------------------------------------------------------------------------------- /plugins/stateofmika/stateofmika_plugin_gamesdk/types/models.py: -------------------------------------------------------------------------------- 1 | from typing import Dict, Any, Optional 2 | from pydantic import BaseModel 3 | 4 | class RouterRequest(BaseModel): 5 | query: str 6 | 7 | class RouteInfo(BaseModel): 8 | tool: str 9 | confidence: float 10 | parameters: Optional[Dict[str, Any]] = None 11 | description: str 12 | 13 | class RouterResponse(BaseModel): 14 | original_query: str 15 | route: RouteInfo 16 | response: Dict[str, Any] 17 | -------------------------------------------------------------------------------- /plugins/stateofmika/test_stateofmika_plugin.py: -------------------------------------------------------------------------------- 1 | from game_sdk.game.custom_types import FunctionResult 2 | from stateofmika_plugin_gamesdk.functions.router import SOMRouter 3 | 4 | 5 | # Example state function 6 | def get_state_fn(function_result: FunctionResult, current_state: dict) -> dict: 7 | """ 8 | Update state based on router responses 9 | """ 10 | init_state = {"previous_queries": [], "previous_routes": []} 11 | 12 | if current_state is None: 13 | return init_state 14 | 15 | # Update state with results 16 | if function_result and function_result.info: 17 | current_state["previous_queries"].append(function_result.info.get("query", "")) 18 | current_state["previous_routes"].append(function_result.info.get("route", {})) 19 | 20 | return current_state 21 | 22 | 23 | # Create router function 24 | router_fn = SOMRouter() 25 | 26 | output = router_fn._execute_query(query="what is the price of bitcoin") 27 | print("-" * 50) 28 | print(output) 29 | print("-" * 50) 30 | -------------------------------------------------------------------------------- /plugins/tLedger/README.md: -------------------------------------------------------------------------------- 1 | # TLedger Plugin for GAME SDK 2 | 3 | ## Features 4 | 5 | - get_agent_details - Get the details of your agent, including the TLedger agent_id and the balances of the agent's wallets 6 | - create_payment - Create a payment request for a specific amount and currency 7 | - get_payment_by_id - Get the details of a payment request by its ID 8 | 9 | ## Admin Setup for doing agent to agent payments using TLedger Plugin 10 | 11 | You are required to set up your account, project, agent profile, and keys in the TLedger platform to use the TLedger plugin for GAME SDK. 12 | 13 | To make the setup easy, all you need to do is run the setup.py file in the tLedger plugin folder. This will install the plugin and set up the necessary environment variables for you. 14 | To set up the necessary environment variables, please fill in thw details in the .env.setup file 15 | 16 | ```shell 17 | python3 ./setup.py 18 | ``` 19 | There are also two agents set for you incase you want to test the plugin. The agent details are as follows: 20 | 21 | Agent 1: 22 | - Agent ID: `agt_59b17650-a689-4649-91fa-4bf5d0db56ad` 23 | - key: `ewSZjNQGPLle-vn5dMZoLOGUljEB6fbmox31o7KLKuI` 24 | - secret: `iqB7-iETCVBE0UV_0HfRAwCHkVXO9_4cCPJYmTIyUpHauHlVP4Hk5xSsCquRqBO_2_eQ6OK_Zu7P1X4LU7hSHg` 25 | 26 | Agent 2: 27 | - Agent ID: `agt_3db52291-a9f8-4f04-a180-adb6e50ef5b0` 28 | - key: `j06KtBcRRbmrEAqIVSiXZc3DPAJSqymDimo__ERD0oQ` 29 | - secret: `h13ERQG797cYMeNeRLvwDF_3-DBt4o-kp0fL-bFHKstTUTS5xsLUFgDEUZG2GsoEKINxeSVusbAQxc24mHm1eQ` 30 | 31 | ### TLedger Account Setup 32 | For a complete list of TLedger setup APIs, please feel free to look at the public documentation at: https://docs.t54.ai/ 33 | 34 | ### Toolkit Setup and Configuration 35 | 36 | Import and initialize the plugin to use in your worker: 37 | 38 | ```python 39 | import os 40 | from tledger_plugin_gamesdk.tLedger_plugin import TLedgerPlugin 41 | 42 | tledger_plugin = TLedgerPlugin( 43 | api_key=os.environ.get("SENDER_TLEDGER_API_KEY"), 44 | api_secret=os.environ.get("SENDER_TLEDGER_API_SECRET"), 45 | api_url=os.environ.get("TLEDGER_API_URL") 46 | ) 47 | ``` 48 | 49 | **Basic worker example:** 50 | 51 | Install the tLedger plugin using the following command: `pip install tledger-plugin-gamesdk` 52 | For the latest version of tLedger plugin, please check the [tLedger Plugin](https://pypi.org/project/tledger-plugin-gamesdk/) page. 53 | 54 | ```python 55 | 56 | def get_state_fn(function_result: FunctionResult, current_state: dict) -> dict: 57 | 58 | tledger_worker = Worker( 59 | api_key=os.environ.get("GAME_API_KEY"), 60 | description="Worker specialized in doing payments on Tledger", 61 | get_state_fn=get_state_fn, 62 | action_space=tledger_plugin.get_tools(), 63 | ) 64 | 65 | tledger_worker.run("Get TLedger account details") 66 | ``` 67 | -------------------------------------------------------------------------------- /plugins/tLedger/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/game-by-virtuals/game-python/76a1a712be14b971fd59a45c14c76c8775884e73/plugins/tLedger/__init__.py -------------------------------------------------------------------------------- /plugins/tLedger/examples/.env.example: -------------------------------------------------------------------------------- 1 | TLEDGER_API_URL=https://api-sandbox.t54.ai/api/v1/ 2 | SENDER_TLEDGER_API_KEY= 3 | SENDER_TLEDGER_API_SECRET= 4 | RECEIVER_TLEDGER_API_KEY= 5 | RECEIVER_TLEDGER_API_SECRET= 6 | GAME_API_KEY= 7 | -------------------------------------------------------------------------------- /plugins/tLedger/examples/example_worker.py: -------------------------------------------------------------------------------- 1 | import os 2 | from game_sdk.game.worker import Worker 3 | from game_sdk.game.custom_types import FunctionResult 4 | from dotenv import load_dotenv 5 | from pathlib import Path 6 | 7 | # Load environment variables from .env file 8 | env_path = Path(__file__).parent / '.env.example' 9 | load_dotenv(dotenv_path=env_path) 10 | 11 | 12 | from tledger_plugin_gamesdk.tLedger_plugin import TLedgerPlugin 13 | 14 | def get_state_fn(function_result: FunctionResult, current_state: dict) -> dict: 15 | """ 16 | Update state based on the function results 17 | """ 18 | init_state = {} 19 | 20 | if current_state is None: 21 | return init_state 22 | 23 | # Update state with the function result info 24 | current_state.update(function_result.info) 25 | 26 | return current_state 27 | 28 | 29 | 30 | tledger_plugin = TLedgerPlugin( 31 | api_key=os.environ.get("SENDER_TLEDGER_API_KEY"), 32 | api_secret=os.environ.get("SENDER_TLEDGER_API_SECRET"), 33 | api_url = os.environ.get("TLEDGER_API_URL") 34 | ) 35 | 36 | # Create worker 37 | tledger_worker = Worker( 38 | api_key=os.environ.get("GAME_API_KEY"), 39 | description="Worker specialized in doing payments on Tledger", 40 | get_state_fn=get_state_fn, 41 | action_space=tledger_plugin.get_tools(), 42 | ) 43 | 44 | # # Run example query 45 | queries = [ 46 | "Get TLedger account details", 47 | "Create payment of 1 SOL using the TLedger account details. The receiving agent's ID is 'agt_3db52291-a9f8-4f04-a180-adb6e50ef5b0', the payment amount is 1, the settlement network is 'solana', the currency is 'sol', and the conversation ID is 'conv1'", 48 | "Get payment by ID. Retrieve the payment ID using the previous query", 49 | ] 50 | 51 | for query in queries: 52 | print("-" * 100) 53 | print(f"Query: {query}") 54 | tledger_worker.run(query) 55 | -------------------------------------------------------------------------------- /plugins/tLedger/plugin_metadata.yml: -------------------------------------------------------------------------------- 1 | # General Information 2 | plugin_name: "tLedger_plugin_gamesdk" 3 | author: "tLedger Engineering Team" 4 | logo_url: "https://drive.google.com/file/d/1PQroliD6_3MraAAR2WbqLbNOJLLy5DAX/view?usp=share_link" 5 | release_date: "2025-04" 6 | 7 | # Description 8 | short_description: "tLedger Plugin for Game SDK. TLedger is a blockchain-agnostic agent account management platform" 9 | detailed_description: "tLedger is t54’s foundational product—a blockchain-agnostic account & ledger designed to support AI agent-initiated financial transactions. It enables developers to create and manage agent-level virtual accounts, set programmable spending limits, and trigger on-chain payments via a lightweight SDK. Each agent is provisioned with multi-asset wallets (e.g., USDT, SOL), and all activity is surfaced through robust APIs and a web-based portal. The platform enforces compliance through Know Your Agent (KYA) protocols and centralized risk controls, giving developers the tooling to deploy financially autonomous agents at scale." 10 | 11 | # Contact & Support 12 | x_account_handle: "@GAME_Virtuals" 13 | support_contact: "cfang@t54.ai" 14 | community_link: "https://t.me/virtuals" 15 | -------------------------------------------------------------------------------- /plugins/tLedger/pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["hatchling"] 3 | build-backend = "hatchling.build" 4 | 5 | [project] 6 | name = "tledger_plugin_gamesdk" 7 | version = "0.1.2" 8 | authors = [{ name = "Prateek Tiwari", email = "ptiwari@t54.ai" }, { name = "Akshit Arora", email = "aarora@t54.ai" }] 9 | description = "TLedger SDK for GAME by Virtuals" 10 | requires-python = ">=3.8" 11 | classifiers = [ 12 | "Programming Language :: Python :: 3", 13 | "Programming Language :: Python :: 3.8", 14 | "Programming Language :: Python :: 3.9", 15 | "Programming Language :: Python :: 3.10", 16 | "Programming Language :: Python :: 3.11", 17 | "License :: OSI Approved :: MIT License", 18 | "Operating System :: OS Independent", 19 | "Development Status :: 3 - Alpha", 20 | "Intended Audience :: Developers", 21 | "Topic :: Software Development :: Libraries :: Python Modules", 22 | ] 23 | dependencies = [ 24 | "aiohttp>=3.11.11", 25 | "game-sdk>=0.1.1" 26 | ] 27 | 28 | [tool.hatch.build.targets.wheel] 29 | packages = ["tledger_plugin_gamesdk"] 30 | 31 | [project.urls] 32 | "Homepage" = "https://github.com/game-by-virtuals/game-python/plugins/tLedger" 33 | "Bug Tracker" = "https://github.com/game-by-virtuals/game-python" 34 | 35 | -------------------------------------------------------------------------------- /plugins/tLedger/setup.py: -------------------------------------------------------------------------------- 1 | import requests 2 | 3 | BASE_URL = "https://api-sandbox.t54.ai/api/v1" 4 | 5 | def create_project(network, description, name, daily_limit) -> dict: 6 | url = f"{BASE_URL}/projects" 7 | payload = { 8 | "network": network, 9 | "description": description, 10 | "name": name, 11 | "daily_limit": daily_limit 12 | } 13 | 14 | headers = {} 15 | 16 | response = requests.post(url, json=payload, headers=headers) 17 | response.raise_for_status() 18 | return response.json() 19 | 20 | def create_agent_profile(token, project_id, name, description) -> dict: 21 | url = f"{BASE_URL}/agent_profiles" 22 | payload = { 23 | "project_id": project_id, 24 | "name": name, 25 | "description": description 26 | } 27 | headers = { 28 | "X-API-Key": token["api_key"], 29 | "X-API-Secret": token["secret"] 30 | } 31 | response = requests.post(url, json=payload, headers=headers) 32 | response.raise_for_status() 33 | return response.json() 34 | 35 | def generate_api_key(resource_id, created_by) -> dict: 36 | url = f"{BASE_URL}/api_key/generate-api-key" 37 | payload = { 38 | "scopes": ["payments:read", "balance:read", "payments:write", "agent:account:read", "agent:profile:create"], 39 | "resource_id": resource_id, 40 | "created_by": created_by 41 | } 42 | response = requests.post(url, json=payload) 43 | response.raise_for_status() 44 | return response.json() 45 | 46 | 47 | # Create project 48 | project = create_project( "solana", "Solana Launch Pad", "Twitter Project", 100) 49 | project_id = project["id"] 50 | 51 | # Generate API key for agent 52 | api_key_project = generate_api_key(project_id, "guest@t54.ai") 53 | 54 | # Create agent profile 55 | agent_profile_sender = create_agent_profile(api_key_project, project_id, "Sending Agent", "Sending agent") 56 | agent_id_sender = agent_profile_sender["id"] 57 | 58 | # Create agent profile 59 | agent_profile_receiver = create_agent_profile(api_key_project, project_id, "Receiving Agent", "Twitter KOL Agent") 60 | agent_id_receiver = agent_profile_receiver["id"] 61 | 62 | # Generate API key for agent 63 | api_key_sender = generate_api_key(agent_id_sender, "guest@t54.ai") 64 | 65 | # Generate API key for agent 66 | api_key_receiver = generate_api_key(agent_id_receiver, "guest@t54.ai") 67 | 68 | print("Setup complete") 69 | print(f"Project ID: {project_id}") 70 | print(f"Sender Agent ID: {agent_id_sender}. Solana address: {agent_profile_sender["account"][0]["wallet_address"]}. Sender API Key: {api_key_sender}") 71 | print(f"Receiver Agent ID: {agent_id_receiver}. Solana address: {agent_profile_receiver["account"][0]["wallet_address"]}. Receiver API Key: {api_key_receiver}") 72 | 73 | print(f"To add funds to your solana wallet, visit https://faucet.solana.com/") 74 | -------------------------------------------------------------------------------- /plugins/tLedger/tledger_plugin_gamesdk/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/game-by-virtuals/game-python/76a1a712be14b971fd59a45c14c76c8775884e73/plugins/tLedger/tledger_plugin_gamesdk/__init__.py -------------------------------------------------------------------------------- /plugins/tLedger/tledger_plugin_gamesdk/tLedger_models.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | from typing import Optional 3 | 4 | from pydantic import BaseModel 5 | from sqlmodel import Field 6 | 7 | class PaymentResponse(BaseModel): 8 | object: str = "payment" 9 | id: str = Field(..., description="Unique ID for the payment.") 10 | status: str = Field(..., description="Current status of the payment.") 11 | payment_amount: float = Field(..., gt=0, description="The amount for the payment, must be positive.") 12 | sending_agent_id: str = Field(..., description="Unique ID of the sending agent.") 13 | sending_agent_name: str = Field(..., description="Name of the sending agent.") 14 | receiving_agent_id: str = Field(..., description="Unique ID of the receiving agent.") 15 | receiving_agent_name: str = Field(..., description="Name of the receiving agent.") 16 | settlement_network: str = Field(..., description="network used for settlement.") 17 | currency: str = Field(..., description="Currency used in the crypto bridge (e.g., USDT, USDC, BTC, ETH).") 18 | transaction_fee: float = Field(..., ge=0, description="Transaction fee for the payment, must be non-negative.") 19 | conversation_id: str = Field(..., description="Identifier for the conversation linked to the payment.") 20 | transaction_hash: Optional[str] = Field(..., description="Transaction hash for the payment on the network") 21 | created_at: datetime = Field(..., description="The entity create timestamp") 22 | updated_at: datetime = Field(..., description="The entity update timestamp") 23 | 24 | class AssetAccountBase(BaseModel): 25 | object: str = "account" 26 | id: str = Field(..., description="Unique identifier for the asset account.") 27 | balance: float = Field(0.0, ge=0, description="Current balance, must be non-negative.") 28 | asset: str = Field(..., max_length=50, description="Asset of the virtual currency.") 29 | created_at: datetime = Field(..., description="The entity create timestamp") 30 | updated_at: datetime = Field(..., description="The entity update timestamp") 31 | network: str = "Solana" 32 | 33 | class AssetAccountRead(AssetAccountBase): 34 | wallet_address: str = Field(default=None, max_length=255, description="network address for agent wallet") 35 | 36 | class AgentDataPlaneResponse(BaseModel): 37 | object: str = "agent_details" 38 | id: str = Field(description="Unique identifier for the agent") 39 | agent_type: str = Field() 40 | account: list[AssetAccountRead] = Field(..., description="Agent account balance information") 41 | 42 | -------------------------------------------------------------------------------- /plugins/telegram/README.md: -------------------------------------------------------------------------------- 1 | # Telegram Plugin for GAME SDK 2 | 3 | ## Overview 4 | 5 | The **Telegram Plugin** is an integration for the **Game SDK** that enables AI-driven interactions on Telegram. This plugin allows AI agents to handle messages, execute commands, and engage users through text, media, and polls. 6 | 7 | ## Features 8 | 9 | - **Send Messages** – AI agents can send text messages to users. 10 | - **Send Media** – Supports sending photos, documents, videos, and audio. 11 | - **Create Polls** – AI agents can generate interactive polls. 12 | - **Pin & Unpin Messages** – Manage pinned messages in chats. 13 | - **Delete Messages** – Remove messages dynamically. 14 | - **AI-Powered Responses** – Leverages LLM to generate contextual replies. 15 | - **Real-Time Polling** – Runs asynchronously with Telegram’s polling system. 16 | - and more features to come! 17 | 18 | ## Installation 19 | ### Pre-requisites 20 | Ensure you have Python 3.9+ installed. Then, install the plugin via **PyPI**: 21 | ### Steps 22 | 1. Install the plugin: 23 | ```sh bash 24 | pip install telegram-plugin-gamesdk 25 | ``` 26 | 2. Ensure you have a Telegram bot token and GAME API key and set them as environment variables: 27 | ```sh bash 28 | export TELEGRAM_BOT_TOKEN="your-telegram-bot-token" 29 | export GAME_API_KEY="your-game-api-key" 30 | ``` 31 | 3. Refer to the example and run the example bot: 32 | ```sh bash 33 | python examples/test_telegram.py 34 | ``` 35 | 36 | ## Usage Examples 37 | ### Initializing the Plugin 38 | 39 | ```python 40 | from telegram_plugin_gamesdk.telegram_plugin import TelegramPlugin 41 | 42 | tg_bot = TelegramPlugin(bot_token='your-telegram-bot-token') 43 | tg_bot.start_polling() 44 | ``` 45 | 46 | ### Sending a Message 47 | ```python 48 | tg_bot.send_message(chat_id=123456789, text="Hello from the AI Agent!") 49 | ``` 50 | 51 | ### Sending Media 52 | ```python 53 | tg_bot.send_media(chat_id=123456789, media_type="photo", media="https://example.com/image.jpg", caption="Check this out!") 54 | ``` 55 | 56 | ### Creating a Poll 57 | ```python 58 | tg_bot.create_poll(chat_id=123456789, question="What's your favorite color?", options=["Red", "Blue", "Green"]) 59 | ``` 60 | 61 | ### Pinning and Unpinning Messages 62 | ```python 63 | tg_bot.pin_message(chat_id=123456789, message_id=42) 64 | tg_bot.unpin_message(chat_id=123456789, message_id=42) 65 | ``` 66 | 67 | ### Deleting a Message 68 | ```python 69 | tg_bot.delete_message(chat_id=123456789, message_id=42) 70 | ``` 71 | 72 | ## Integration with GAME Chat Agent 73 | Implement a message handler to integrate the Telegram Plugin with the GAME Chat Agent: 74 | ```python 75 | from telegram import Update 76 | from telegram.ext import ContextTypes, filters, MessageHandler 77 | from game_sdk.game.chat_agent import ChatAgent 78 | 79 | chat_agent = ChatAgent( 80 | prompt="You are a helpful assistant.", 81 | api_key="your-game-api-key", 82 | ) 83 | 84 | async def default_message_handler(update: Update, context: ContextTypes.DEFAULT_TYPE): 85 | """Handles incoming messages but ignores messages from the bot itself unless it's mentioned in a group chat.""" 86 | # Ignore messages from the bot itself 87 | if update.message.from_user.id == tg_plugin.bot.id: 88 | logger.info("Ignoring bot's own message.") 89 | return 90 | 91 | user = update.message.from_user 92 | chat_id = update.message.chat.id 93 | chat_type = update.message.chat.type # "private", "group", "supergroup", or "channel" 94 | bot_username = f"@{tg_plugin.bot.username}" 95 | 96 | logger.info(f"Update received: {update}") 97 | logger.info(f"Message received: {update.message.text}") 98 | 99 | name = f"{user.first_name} (Telegram's chat_id: {chat_id}, this is not part of the partner's name but important for the telegram's function arguments)" 100 | 101 | if not any(u["chat_id"] == chat_id for u in active_users): 102 | # Ignore group/supergroup messages unless the bot is mentioned 103 | if chat_type in ["group", "supergroup"] and bot_username not in update.message.text: 104 | logger.info(f"Ignoring group message not mentioning the bot: {update.message.text}") 105 | return 106 | active_users.append({"chat_id": chat_id, "name": name}) 107 | logger.info(f"Active user added: {name}") 108 | logger.info(f"Active users: {active_users}") 109 | chat = chat_agent.create_chat( 110 | partner_id=str(chat_id), 111 | partner_name=name, 112 | action_space=agent_action_space, 113 | ) 114 | active_chats[chat_id] = chat 115 | 116 | response = active_chats[chat_id].next(update.message.text.replace(bot_username, "").strip()) # Remove bot mention 117 | logger.info(f"Response: {response}") 118 | 119 | if response.message: 120 | await update.message.reply_text(response.message) 121 | 122 | if response.is_finished: 123 | active_chats.pop(chat_id) 124 | active_users.remove({"chat_id": chat_id, "name": name}) 125 | logger.info(f"Chat with {name} ended.") 126 | logger.info(f"Active users: {active_users}") 127 | 128 | tg_plugin.add_handler(MessageHandler(filters.ALL, default_message_handler)) 129 | ``` 130 | You can refer to [test_telegram.py](examples/test_telegram.py) for details. 131 | -------------------------------------------------------------------------------- /plugins/telegram/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/game-by-virtuals/game-python/76a1a712be14b971fd59a45c14c76c8775884e73/plugins/telegram/__init__.py -------------------------------------------------------------------------------- /plugins/telegram/examples/test_telegram.py: -------------------------------------------------------------------------------- 1 | import os 2 | from typing import TypedDict 3 | import logging 4 | 5 | from telegram import Update 6 | from telegram.ext import ContextTypes, filters, MessageHandler 7 | 8 | from game_sdk.game.chat_agent import Chat, ChatAgent 9 | from telegram_plugin_gamesdk.telegram_plugin import TelegramPlugin 10 | from test_telegram_game_functions import send_message_fn, send_media_fn, create_poll_fn, pin_message_fn, unpin_message_fn, delete_message_fn 11 | 12 | game_api_key = os.environ.get("GAME_API_KEY") 13 | telegram_bot_token = os.environ.get("TELEGRAM_BOT_TOKEN") 14 | 15 | logging.basicConfig( 16 | format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", 17 | level=logging.INFO, 18 | ) 19 | 20 | logger = logging.getLogger(__name__) 21 | 22 | class ActiveUser(TypedDict): 23 | chat_id: int 24 | name: str 25 | 26 | chat_agent = ChatAgent( 27 | prompt="You are a helpful assistant.", 28 | api_key=game_api_key, 29 | ) 30 | 31 | active_users: list[ActiveUser] = [] 32 | active_chats: dict[int, Chat] = {} 33 | 34 | if __name__ == "__main__": 35 | tg_plugin = TelegramPlugin(bot_token=telegram_bot_token) 36 | 37 | agent_action_space = [ 38 | send_message_fn(tg_plugin), 39 | send_media_fn(tg_plugin), 40 | create_poll_fn(tg_plugin), 41 | pin_message_fn(tg_plugin), 42 | unpin_message_fn(tg_plugin), 43 | delete_message_fn(tg_plugin), 44 | ] 45 | 46 | async def default_message_handler(update: Update, context: ContextTypes.DEFAULT_TYPE): 47 | """Handles incoming messages but ignores messages from the bot itself unless it's mentioned in a group chat.""" 48 | 49 | # Ignore messages from the bot itself 50 | if update.message.from_user.id == tg_plugin.bot.id: 51 | logger.info("Ignoring bot's own message.") 52 | return 53 | 54 | user = update.message.from_user 55 | chat_id = update.message.chat.id 56 | chat_type = update.message.chat.type # "private", "group", "supergroup", or "channel" 57 | bot_username = f"@{tg_plugin.bot.username}" 58 | 59 | logger.info(f"Update received: {update}") 60 | logger.info(f"Message received: {update.message.text}") 61 | 62 | name = f"{user.first_name} (Telegram's chat_id: {chat_id}, this is not part of the partner's name but important for the telegram's function arguments)" 63 | 64 | # Ignore group/supergroup messages unless the bot is mentioned 65 | if chat_type in ["group", "supergroup"] and bot_username not in update.message.text: 66 | logger.info(f"Ignoring group message not mentioning the bot: {update.message.text}") 67 | return 68 | 69 | if not any(u["chat_id"] == chat_id for u in active_users): 70 | active_users.append({"chat_id": chat_id, "name": name}) 71 | logger.info(f"Active user added: {name}") 72 | logger.info(f"Active users: {active_users}") 73 | chat = chat_agent.create_chat( 74 | partner_id=str(chat_id), 75 | partner_name=name, 76 | action_space=agent_action_space, 77 | ) 78 | active_chats[chat_id] = chat 79 | 80 | response = active_chats[chat_id].next(update.message.text.replace(bot_username, "").strip()) # Remove bot mention 81 | logger.info(f"Response: {response}") 82 | 83 | if response.message: 84 | await update.message.reply_text(response.message) 85 | 86 | if response.is_finished: 87 | active_chats.pop(chat_id) 88 | active_users.remove({"chat_id": chat_id, "name": name}) 89 | logger.info(f"Chat with {name} ended.") 90 | logger.info(f"Active users: {active_users}") 91 | 92 | tg_plugin.add_handler(MessageHandler(filters.ALL, default_message_handler)) 93 | 94 | # Start polling 95 | tg_plugin.start_polling() 96 | 97 | # Example of executing a function from Telegram Plugin to a chat without polling 98 | #tg_plugin.send_message(chat_id=829856292, text="Hello! I am a helpful assistant. How can I assist you today?") 99 | -------------------------------------------------------------------------------- /plugins/telegram/plugin_metadata.yml: -------------------------------------------------------------------------------- 1 | # General Information 2 | plugin_name: "telegram_plugin_gamesdk" 3 | author: "Yang from Virtuals Protocol" 4 | logo_url: "https://drive.google.com/drive/folders/1AdRKQac0a-ORqdE6mtV4wGdF8y3DhIyo" 5 | release_date: "2025-03" 6 | 7 | # Description 8 | short_description: "Telegram Plugin for Game SDK" 9 | detailed_description: "This plugin provides a Telegram integration for the Game SDK. It allows developers to easily integrate Telegram functionalities like sending, deleting, pin, messages or media files etc. Credits to the yk(yenkhoon) for building the Telegram plugin for GAME Node SDK." 10 | 11 | # Contact & Support 12 | x_account_handle: "@GAME_Virtuals" 13 | support_contact: "https://discord.gg/virtualsio" 14 | community_link: "https://t.me/virtuals" 15 | -------------------------------------------------------------------------------- /plugins/telegram/pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "telegram-plugin-gamesdk" 3 | version = "0.1.0" 4 | description = "Telegram Plugin for Python SDK for GAME by Virtuals" 5 | authors = [ 6 | {name = "Ang Weoy Yang", email = "weoyyang00@gmail.com"} 7 | ] 8 | readme = "README.md" 9 | requires-python = ">=3.9" 10 | dependencies = [ 11 | "python-telegram-bot>=21.11.1", 12 | ] 13 | 14 | [build-system] 15 | requires = ["poetry-core>=2.0.0,<3.0.0"] 16 | build-backend = "poetry.core.masonry.api" 17 | -------------------------------------------------------------------------------- /plugins/telegram/telegram_plugin_gamesdk/telegram_plugin.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | from typing import List, Union 3 | from telegram import Bot 4 | from telegram.ext import ApplicationBuilder 5 | 6 | 7 | def _run_async(coro): 8 | """ 9 | Runs an async function safely. 10 | - If an event loop is running, it schedules the coroutine with `asyncio.create_task()`. 11 | - Otherwise, it starts a new event loop with `asyncio.run()`. 12 | """ 13 | try: 14 | loop = asyncio.get_running_loop() 15 | return asyncio.create_task(coro) 16 | except RuntimeError: 17 | return asyncio.run(coro) 18 | 19 | 20 | class TelegramPlugin: 21 | """ 22 | A Telegram Bot SDK Plugin that integrates message handling and function-based execution. 23 | 24 | Features: 25 | - Handles user interactions in Telegram. 26 | - Supports function-based execution (e.g., sending messages, polls). 27 | - Manages active user sessions. 28 | 29 | Attributes: 30 | bot_token (str): The Telegram bot token, loaded from environment. 31 | application (Application): The Telegram application instance. 32 | bot (Bot): The Telegram bot instance. 33 | 34 | Example: 35 | ```python 36 | tgBot = TelegramPlugin(bot_token=os.getenv("TELEGRAM_BOT_TOKEN")) 37 | tgBot.start_polling() 38 | ``` 39 | """ 40 | 41 | def __init__(self, bot_token: str): 42 | self.bot_token = bot_token 43 | self.application = ApplicationBuilder().token(self.bot_token).build() 44 | self.bot = self.application.bot 45 | 46 | def send_message(self, chat_id: Union[int, str], text: str): 47 | """Send a message to a chat safely while polling is running.""" 48 | if not chat_id or not text: 49 | raise Exception("Error: chat_id and text are required.") 50 | 51 | return _run_async(self.bot.send_message(chat_id=chat_id, text=text)) 52 | 53 | def send_media( 54 | self, chat_id: Union[int, str], media_type: str, media: str, caption: str = None 55 | ): 56 | """Send a media message (photo, document, video, audio) with an optional caption.""" 57 | if not chat_id or not media_type or not media: 58 | raise Exception("Error: chat_id, media_type, and media are required.") 59 | 60 | if media_type == "photo": 61 | return _run_async(self.bot.send_photo(chat_id=chat_id, photo=media, caption=caption)) 62 | elif media_type == "document": 63 | return _run_async(self.bot.send_document(chat_id=chat_id, document=media, caption=caption)) 64 | elif media_type == "video": 65 | return _run_async(self.bot.send_video(chat_id=chat_id, video=media, caption=caption)) 66 | elif media_type == "audio": 67 | return _run_async(self.bot.send_audio(chat_id=chat_id, audio=media, caption=caption)) 68 | else: 69 | raise Exception("Error: Invalid media_type. Use 'photo', 'document', 'video', or 'audio'.") 70 | 71 | def create_poll( 72 | self, chat_id: Union[int, str], question: str, options: List[str], is_anonymous: bool = True, 73 | allows_multiple_answers: bool = False 74 | ): 75 | """Create a poll in a chat safely while polling is running.""" 76 | if not chat_id or not question or not options: 77 | raise Exception("Error: chat_id, question, and options are required.") 78 | if not (2 <= len(options) <= 10): 79 | raise Exception("Poll must have between 2 and 10 options.") 80 | 81 | return _run_async( 82 | self.bot.send_poll( 83 | chat_id=chat_id, 84 | question=question, 85 | options=options, 86 | is_anonymous=is_anonymous, 87 | allows_multiple_answers=allows_multiple_answers 88 | ) 89 | ) 90 | 91 | def pin_message(self, chat_id: Union[int, str], message_id: int): 92 | """Pin a message in the chat.""" 93 | if chat_id is None or message_id is None: 94 | raise Exception("Error: chat_id and message_id are required to pin a message.") 95 | 96 | return _run_async(self.bot.pin_chat_message(chat_id=chat_id, message_id=message_id)) 97 | 98 | def unpin_message(self, chat_id: Union[int, str], message_id: int): 99 | """Unpin a specific message in the chat.""" 100 | if chat_id is None or message_id is None: 101 | raise Exception("Error: chat_id and message_id are required to unpin a message.") 102 | 103 | return _run_async(self.bot.unpin_chat_message(chat_id=chat_id, message_id=message_id)) 104 | 105 | def delete_message(self, chat_id: Union[int, str], message_id: int): 106 | """Delete a message from the chat.""" 107 | if chat_id is None or message_id is None: 108 | raise Exception("Error: chat_id and message_id are required to delete a message.") 109 | 110 | return _run_async(self.bot.delete_message(chat_id=chat_id, message_id=message_id)) 111 | 112 | def start_polling(self): 113 | """Start polling asynchronously in the main thread.""" 114 | self.application.run_polling() 115 | 116 | def add_handler(self, handler): 117 | """Register a message handler for text messages.""" 118 | self.application.add_handler(handler) 119 | -------------------------------------------------------------------------------- /plugins/twitter/README.md: -------------------------------------------------------------------------------- 1 | # Twitter Plugin for GAME SDK 2 | 3 | The **Twitter Plugin** provides a lightweight interface for integrating Twitter (X) functionality into your GAME SDK agents. Built on top of [`virtuals_tweepy`](https://pypi.org/project/virtuals-tweepy/) by the Virtuals team — a maintained fork of [`Tweepy`](https://pypi.org/project/tweepy/)) — this plugin lets you easily post tweets, fetch data, and execute workflows through agent logic. 4 | 5 | Use it standalone or compose multiple Twitter actions as part of a larger agent job. 6 | 7 | --- 8 | 9 | ## Installation 10 | 11 | You can install the plugin using either `poetry` or `pip`: 12 | 13 | ```bash 14 | # Using Poetry (from the plugin directory) 15 | poetry install 16 | ``` 17 | or 18 | ```bash 19 | # Using pip (recommended for integration projects) 20 | pip install twitter_plugin_gamesdk 21 | ``` 22 | 23 | --- 24 | 25 | ## Authentication Methods 26 | 27 | We support two primary ways to authenticate: 28 | 29 | ### 1. GAME's Sponsored X Enterprise Access Token (Recommended) 30 | 31 | Virtuals sponsors the community with a **Twitter Enterprise API access plan**, using OAuth 2.0 with PKCE. This provides: 32 | 33 | - Higher rate limits: **35 calls / 5 minutes** 34 | - Smoother onboarding 35 | - Free usage via your `GAME_API_KEY` 36 | 37 | #### a. Get Your Access Token 38 | 39 | Run the following command to authenticate using your `GAME_API_KEY`: 40 | 41 | ```bash 42 | poetry run twitter-plugin-gamesdk auth -k 43 | ``` 44 | 45 | This will prompt: 46 | 47 | ```bash 48 | Waiting for authentication... 49 | 50 | Visit the following URL to authenticate: 51 | https://x.com/i/oauth2/authorize?... 52 | 53 | Authenticated! Here's your access token: 54 | apx-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 55 | ``` 56 | 57 | #### b. Store Your Access Token 58 | 59 | We recommend storing environment variables in a `.env` file: 60 | 61 | ``` 62 | # .env 63 | 64 | GAME_TWITTER_ACCESS_TOKEN=apx-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 65 | ``` 66 | 67 | Then, use `load_dotenv()` to load them: 68 | 69 | ```python 70 | import os 71 | from dotenv import load_dotenv 72 | from twitter_plugin_gamesdk.twitter_plugin import TwitterPlugin 73 | 74 | load_dotenv() 75 | 76 | options = { 77 | "credentials": { 78 | "game_twitter_access_token": os.environ.get("GAME_TWITTER_ACCESS_TOKEN") 79 | } 80 | } 81 | 82 | twitter_plugin = TwitterPlugin(options) 83 | client = twitter_plugin.twitter_client 84 | 85 | client.create_tweet(text="Tweeting with GAME Access Token!") 86 | ``` 87 | 88 | --- 89 | 90 | ### 2. Use Your Own Twitter Developer Credentials 91 | 92 | Use this option if you need access to Twitter endpoints requiring a different auth level (e.g., **OAuth 1.0a User Context** or **OAuth 2.0 App Only**). 93 | 94 | > See [X API Auth Mapping](https://docs.x.com/resources/fundamentals/authentication/guides/v2-authentication-mapping) to determine which auth level is required for specific endpoints. 95 | 96 | #### a. Get Your Developer Credentials 97 | 98 | 1. Sign in to the [Twitter Developer Portal](https://developer.x.com/en/portal/dashboard). 99 | 2. Create a project and app. 100 | 3. Generate the following keys and store them in your `.env` file: 101 | 102 | ``` 103 | # .env 104 | 105 | TWITTER_API_KEY=... 106 | TWITTER_API_SECRET_KEY=... 107 | TWITTER_ACCESS_TOKEN=... 108 | TWITTER_ACCESS_TOKEN_SECRET=... 109 | ``` 110 | 111 | #### b. Initialize the Plugin 112 | 113 | ```python 114 | import os 115 | from dotenv import load_dotenv 116 | from twitter_plugin_gamesdk.twitter_plugin import TwitterPlugin 117 | 118 | load_dotenv() 119 | 120 | options = { 121 | "credentials": { 122 | "api_key": os.environ.get("TWITTER_API_KEY"), 123 | "api_key_secret": os.environ.get("TWITTER_API_SECRET_KEY"), 124 | "access_token": os.environ.get("TWITTER_ACCESS_TOKEN"), 125 | "access_token_secret": os.environ.get("TWITTER_ACCESS_TOKEN_SECRET"), 126 | } 127 | } 128 | 129 | twitter_plugin = TwitterPlugin(options) 130 | client = twitter_plugin.twitter_client 131 | 132 | client.create_tweet(text="Tweeting with personal developer credentials!") 133 | ``` 134 | 135 | --- 136 | 137 | ## Examples 138 | 139 | Explore the [`examples/`](./examples) directory for sample scripts demonstrating how to: 140 | 141 | - Post tweets 142 | - Reply to mentions 143 | - Quote tweets 144 | - Fetch user timelines 145 | - And more! 146 | 147 | --- 148 | 149 | ## API Reference 150 | 151 | This plugin wraps [`virtuals_tweepy`](https://pypi.org/project/virtuals-tweepy/), which is API-compatible with [Tweepy’s client interface](https://docs.tweepy.org/en/stable/client.html). Refer to their docs for supported methods and parameters. 152 | 153 | --- 154 | -------------------------------------------------------------------------------- /plugins/twitter/examples/.env.example: -------------------------------------------------------------------------------- 1 | GAME_TWITTER_ACCESS_TOKEN=apx- 2 | 3 | TWITTER_BEARER_TOKEN= 4 | TWITTER_API_KEY= 5 | TWITTER_API_SECRET_KEY= 6 | TWITTER_ACCESS_TOKEN= 7 | TWITTER_ACCESS_TOKEN_SECRET= 8 | -------------------------------------------------------------------------------- /plugins/twitter/examples/sample_media/virtuals-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/game-by-virtuals/game-python/76a1a712be14b971fd59a45c14c76c8775884e73/plugins/twitter/examples/sample_media/virtuals-logo.png -------------------------------------------------------------------------------- /plugins/twitter/pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["poetry-core>=1.0.0"] 3 | build-backend = "poetry.core.masonry.api" 4 | 5 | [tool.poetry] 6 | name = "twitter-plugin-gamesdk" 7 | version = "0.2.10" 8 | description = "Twitter Plugin for Python SDK for GAME by Virtuals" 9 | authors = ["Celeste Ang "] 10 | readme = "README.md" 11 | packages = [{ include = "twitter_plugin_gamesdk" }] 12 | homepage = "https://github.com/game-by-virtuals/game-python" 13 | repository = "https://github.com/game-by-virtuals/game-python" 14 | 15 | [tool.poetry.dependencies] 16 | python = ">=3.9" 17 | tweepy = ">=4.15.0" 18 | virtuals-tweepy = ">=0.1.5" 19 | 20 | [tool.poetry.scripts] 21 | twitter-plugin-gamesdk = "twitter_plugin_gamesdk.game_twitter_auth:start" 22 | -------------------------------------------------------------------------------- /plugins/twitter/twitter_plugin_gamesdk/game_twitter_auth.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import threading 3 | 4 | import requests 5 | import webbrowser 6 | from http.server import BaseHTTPRequestHandler, HTTPServer 7 | from urllib.parse import urlparse, parse_qs 8 | from typing import Optional 9 | from dataclasses import dataclass 10 | 11 | BASE_URL = "https://twitter.game.virtuals.io/accounts" 12 | 13 | 14 | @dataclass 15 | class AuthCredentials: 16 | """ 17 | Data class to hold authentication credentials. 18 | """ 19 | api_key: str 20 | access_token: Optional[str] = None 21 | 22 | 23 | class AuthHandler(BaseHTTPRequestHandler): 24 | """ 25 | Handles OAuth authentication callback from Twitter. 26 | """ 27 | 28 | def do_GET(self) -> None: 29 | parsed_url = urlparse(self.path) 30 | if parsed_url.path == "/callback": 31 | query_params = parse_qs(parsed_url.query) 32 | code: Optional[str] = query_params.get("code", [None])[0] 33 | state: Optional[str] = query_params.get("state", [None])[0] 34 | 35 | if code and state: 36 | access_token = AuthManager.verify_auth(code, state) 37 | self.send_response(200) 38 | self.send_header("Content-type", "text/plain") 39 | self.end_headers() 40 | self.wfile.write(b"Authentication successful! You may close this window and return to the terminal.") 41 | print("Authenticated! Here's your access token:") 42 | print(access_token) 43 | else: 44 | self.send_response(400) 45 | self.end_headers() 46 | self.wfile.write(b"Invalid request") 47 | print("Authentication failed! Please try again.") 48 | else: 49 | self.send_response(404) 50 | self.end_headers() 51 | self.wfile.write(b"Not Found") 52 | print("Not Found") 53 | 54 | threading.Thread(target=self.server.shutdown, daemon=True).start() # Stop the server after handling the request 55 | 56 | def log_message(self, format, *args): 57 | pass 58 | 59 | 60 | class AuthManager: 61 | """ 62 | Manages OAuth authentication flow. 63 | """ 64 | 65 | @staticmethod 66 | def get_login_url(api_key: str) -> str: 67 | """ 68 | Fetches the login URL from the authentication server. 69 | """ 70 | response = requests.get(f"{BASE_URL}/auth", headers={"x-api-key": api_key}) 71 | response.raise_for_status() 72 | return response.json().get("url") 73 | 74 | @staticmethod 75 | def verify_auth(code: str, state: str) -> str: 76 | """ 77 | Verifies authentication and retrieves an access token. 78 | """ 79 | response = requests.get(f"{BASE_URL}/verify", params={"code": code, "state": state}) 80 | response.raise_for_status() 81 | return response.json().get("token") 82 | 83 | @staticmethod 84 | def start_authentication(api_key: str, port: int = 8714) -> None: 85 | """ 86 | Starts a temporary web server to handle authentication. 87 | """ 88 | SERVER_ADDRESS = ("", port) 89 | HANDLER_CLASS = AuthHandler 90 | with HTTPServer(SERVER_ADDRESS, HANDLER_CLASS) as server: 91 | login_url = AuthManager.get_login_url(api_key) 92 | print("\nWaiting for authentication...\n") 93 | print("Visit the following URL to authenticate:") 94 | print(login_url + "\n") 95 | webbrowser.open(login_url) 96 | server.serve_forever() 97 | 98 | def start() -> None: 99 | """ 100 | Entry point for the game twitter auth process. 101 | """ 102 | parser = argparse.ArgumentParser(prog="game-twitter-plugin", description="CLI to authenticate and interact with GAME's Twitter API") 103 | parser.add_argument("auth", help="Authenticate with Twitter API", nargs='?') 104 | parser.add_argument("-k", "--key", help="Project's API key", required=True, type=str) 105 | args = parser.parse_args() 106 | AuthManager.start_authentication(args.key) 107 | 108 | if __name__ == "__main__": 109 | start() -------------------------------------------------------------------------------- /plugins/twitter/twitter_plugin_gamesdk/twitter_plugin.py: -------------------------------------------------------------------------------- 1 | """ 2 | Twitter Plugin for the GAME SDK. 3 | 4 | This plugin provides a wrapper around the Twitter API using tweepy, enabling 5 | GAME SDK agents to interact with Twitter programmatically. It supports common 6 | Twitter operations like posting tweets, replying, quoting, and getting metrics. 7 | 8 | Example: 9 | ```python 10 | options = { 11 | "id": "twitter_agent", 12 | "name": "Twitter Bot", 13 | "description": "A Twitter bot that posts updates", 14 | "credentials": { 15 | "bearerToken": "your_bearer_token", 16 | "apiKey": "your_api_key", 17 | "apiSecretKey": "your_api_secret", 18 | "accessToken": "your_access_token", 19 | "accessTokenSecret": "your_access_token_secret" 20 | } 21 | } 22 | 23 | twitter_plugin = TwitterPlugin(options) 24 | post_tweet_fn = twitter_plugin.get_function('post_tweet') 25 | post_tweet_fn("Hello, World!") 26 | ``` 27 | """ 28 | 29 | import logging 30 | from typing import Dict, Any 31 | from virtuals_tweepy import Client, TweepyException 32 | 33 | class TwitterPlugin: 34 | """ 35 | A plugin for interacting with Twitter through the GAME SDK. 36 | 37 | This class provides a set of functions for common Twitter operations, 38 | wrapped in a format compatible with the GAME SDK's plugin system. 39 | 40 | Args: 41 | options (Dict[str, Any]): Configuration options including: 42 | - id (str): Unique identifier for the plugin instance 43 | - name (str): Display name for the plugin 44 | - description (str): Plugin description 45 | - credentials (Dict[str, str]): Twitter API credentials 46 | 47 | Attributes: 48 | id (str): Plugin identifier 49 | name (str): Plugin name 50 | description (str): Plugin description 51 | twitter_client (virtuals_tweepy.Client): Authenticated Twitter API client 52 | logger (logging.Logger): Plugin logger 53 | 54 | Raises: 55 | ValueError: If required Twitter API credentials are missing 56 | """ 57 | 58 | def __init__(self, options: Dict[str, Any]) -> None: 59 | # Set credentials 60 | self.base_url = options.get("base_url", "https://twitter.game.virtuals.io") + '/tweets' 61 | credentials = options.get("credentials") 62 | if not credentials: 63 | raise ValueError("Twitter API credentials are required.") 64 | 65 | # Capture token for internal use 66 | self.game_twitter_access_token = credentials.get("game_twitter_access_token") 67 | 68 | # Auth gate: require EITHER gameTwitterAccessToken OR full credential set 69 | has_api_credentials = all( 70 | credentials.get(key) for key in ["api_key", "api_key_secret", "access_token", "access_token_secret"] 71 | ) 72 | 73 | if not self.game_twitter_access_token and not has_api_credentials: 74 | raise ValueError( 75 | "Missing valid authentication. Provide either a 'game_twitter_access_token' or all required Twitter API credentials." 76 | ) 77 | 78 | # Init Tweepy client 79 | self.twitter_client: Client = Client( 80 | consumer_key = credentials.get("api_key"), 81 | consumer_secret = credentials.get("api_key_secret"), 82 | access_token = credentials.get("access_token"), 83 | access_token_secret=credentials.get("access_token_secret"), 84 | return_type = dict, 85 | game_twitter_access_token = credentials.get("game_twitter_access_token"), 86 | ) 87 | # Configure logging 88 | logging.basicConfig(level=logging.INFO) 89 | self.logger: logging.Logger = logging.getLogger(__name__) 90 | 91 | self._check_authentication() 92 | 93 | def _check_authentication(self) -> None: 94 | """ 95 | Check if the credentials provided are valid by calling the /me endpoint or fetching user info. 96 | """ 97 | try: 98 | user = self.twitter_client.get_me(user_fields=["public_metrics"]).get('data') 99 | self.logger.info(f"Authenticated as: {user.get('name')} (@{user.get('username')})") 100 | except TweepyException as e: 101 | self.logger.error(f"Authentication failed: {e}") 102 | raise ValueError("Invalid Twitter credentials or failed authentication.") 103 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["hatchling"] 3 | build-backend = "hatchling.build" 4 | 5 | [project] 6 | name = "game_sdk" 7 | version = "0.1.5" 8 | authors = [ 9 | { name = "Your Name", email = "your.email@example.com" }, 10 | ] 11 | description = "Official Python SDK for GAME by Virtuals" 12 | readme = "README.md" 13 | requires-python = ">=3.8" 14 | classifiers = [ 15 | "Programming Language :: Python :: 3", 16 | "Programming Language :: Python :: 3.8", 17 | "Programming Language :: Python :: 3.9", 18 | "Programming Language :: Python :: 3.10", 19 | "Programming Language :: Python :: 3.11", 20 | "License :: OSI Approved :: MIT License", 21 | "Operating System :: OS Independent", 22 | "Development Status :: 3 - Alpha", 23 | "Intended Audience :: Developers", 24 | "Topic :: Software Development :: Libraries :: Python Modules", 25 | ] 26 | dependencies = [ 27 | "typing-extensions>=4.0.0", 28 | "requests>=2.26.0", 29 | "pydantic>=2.10.5" 30 | ] 31 | 32 | [project.urls] 33 | "Homepage" = "https://github.com/game-by-virtuals/game-python" 34 | -------------------------------------------------------------------------------- /src/game_sdk/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/game-by-virtuals/game-python/76a1a712be14b971fd59a45c14c76c8775884e73/src/game_sdk/__init__.py -------------------------------------------------------------------------------- /src/game_sdk/game/README.md: -------------------------------------------------------------------------------- 1 | # GAME SDK 2 | 3 | ## Overview 4 | 5 | This library enables the creation of AI agents, in which actions are defined by the creator/developer. These agents can be used: 6 | - Worker Mode: to autonomously execute tasks through interaction with the agent. User interaction and tasks is required for the agent to execute tasks. For example, "Bring me some fruits and then go sit on the chair". 7 | - Agent Mode: to autonomously function in an open-ended manner by just providing a general goal. The agent independently and continuously decides tasks for itself in an open-ended manner, and attempts to complete them. 8 | 9 | 10 | ## Core Features 11 | 12 | ### 1. Functions and Executables 13 | 14 | Functions define available actions for the agent: 15 | 16 | ```python 17 | my_function = Function( 18 | fn_name="action_name", 19 | fn_description="Description of action", 20 | args=[Argument(name="param", type="type", description="param description")], 21 | executable=function_implementation 22 | ) 23 | ``` 24 | Executable functions must return a tuple of: 25 | - FunctionResultStatus 26 | - Message string 27 | - Additional info dictionary 28 | 29 | 30 | ### 2. State Management 31 | 32 | Easy and flexible way to define the state management, what the agent sees and how that changes. 33 | 34 | ```python 35 | def get_state_fn(function_result: FunctionResult, current_state: dict) -> dict: 36 | """ 37 | Updates state based on function execution results 38 | 39 | Args: 40 | function_result: Result from last executed function 41 | current_state: Current state dictionary or None for initialization 42 | 43 | Returns: 44 | Updated state dictionary 45 | """ 46 | if current_state is None: 47 | return initial_state 48 | 49 | # Update state based on function_result.info 50 | new_state = update_state(current_state, function_result) 51 | return new_state 52 | ``` 53 | 54 | Key features: 55 | - Can be shared or unique per worker 56 | - Processes function execution results to update state 57 | 58 | ### 3. Workers 59 | Workers are simple interactiable agents that execute the tasks defined by the user. They can be specialized agents with defined capabilities: 60 | 61 | ```python 62 | worker = Worker( 63 | api_key="your_api_key", 64 | description="Worker description", 65 | instruction="Default instructions", 66 | get_state_fn=state_function, 67 | action_space=[list_of_functions] 68 | ) 69 | 70 | worker.run("Bring me some fruits") 71 | ``` 72 | 73 | 74 | 75 | ### 4. Agents 76 | 77 | Agents are used to autonomously function in an open-ended manner by just providing a general goal. Tasks are generated by the agent itself continuously, and the agent will attempt to complete them. You can provide many workers to the agent, and they will be used to execute the tasks. 78 | 79 | ```python 80 | agent = Agent( 81 | api_key="your_api_key", 82 | name="Agent Name", 83 | agent_goal="Primary goal", 84 | agent_description="Description", 85 | get_agent_state_fn=agent_state_function, 86 | workers=[worker1, worker2] 87 | ) 88 | 89 | # Compile and run 90 | agent.compile() 91 | agent.run() 92 | ``` 93 | 94 | Use WorkerConfig for agent composition: 95 | 96 | ```python 97 | worker_config = WorkerConfig( 98 | id="worker_id", 99 | worker_description="Description", 100 | get_state_fn=state_function, 101 | action_space=[function1, function2] 102 | ) 103 | ``` 104 | 105 | You can also access and obtain an individual worker from the agent: 106 | 107 | ```python 108 | worker = agent.get_worker("worker_id") 109 | 110 | worker.run("Bring me some fruits") 111 | ``` 112 | 113 | ### 5. Chat Agents 114 | 115 | Chat Agents enable interactive conversations with AI agents that can execute functions. They are simpler to use than full Agents and are ideal for chatbot-like interactions where the agent can perform actions. 116 | 117 | ```python 118 | # Initialize the chat agent 119 | chat_agent = ChatAgent( 120 | prompt="You are helpful assistant", 121 | api_key="your_api_key" 122 | ) 123 | 124 | # Define functions 125 | ```python 126 | def generate_picture(prompt: str): 127 | # Implementation 128 | return FunctionResultStatus.DONE, "Picture generated", {} 129 | 130 | action_space = [ 131 | Function( 132 | fn_name="generate_picture", 133 | fn_description="Generate a picture", 134 | args=[Argument(name="prompt", description="The prompt for the picture")], 135 | executable=generate_picture 136 | ) 137 | ] 138 | 139 | # Create a chat session 140 | chat = chat_agent.create_chat( 141 | partner_id="user123", 142 | partner_name="User Name", 143 | action_space=[list_of_functions], # Optional 144 | get_state_fn=lambda: {...} # Optional, allows to push state of the environment to the agent 145 | ) 146 | 147 | # Run conversation 148 | chat_continue = True 149 | while chat_continue: 150 | user_message = input("Enter a message: ") 151 | response = chat.next(user_message) 152 | ... 153 | if response.is_finished: 154 | chat_continue = False 155 | 156 | # End chat 157 | chat.end("Optional ending message") 158 | ``` 159 | 160 | ### Chat Termination 161 | 162 | The chat can be terminated in two ways: 163 | 164 | 1. **Agent-initiated**: The agent may decide to end the chat on its own when it determines the conversation is complete. In this case, `chat.next()` will return `False`. 165 | 166 | 2. **Client-initiated**: The client can manually end the chat at any time by calling: 167 | ```python 168 | chat.end("Optional farewell message") 169 | ``` 170 | 171 | ### Chat Memory 172 | 173 | ChatAgent maintains a simple short-term memory by keeping track of recent messages in the conversation. This allows the agent to maintain context and provide coherent responses based on the conversation history. The memory is temporary and limited to the current chat session. 174 | 175 | -------------------------------------------------------------------------------- /src/game_sdk/game/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/game-by-virtuals/game-python/76a1a712be14b971fd59a45c14c76c8775884e73/src/game_sdk/game/__init__.py -------------------------------------------------------------------------------- /src/game_sdk/game/api.py: -------------------------------------------------------------------------------- 1 | import requests 2 | from typing import List, Dict, Optional 3 | 4 | 5 | class GAMEClient: 6 | def __init__(self, api_key: str): 7 | self.api_key = api_key 8 | self.base_url = "https://game.virtuals.io" 9 | 10 | def _get_access_token(self) -> str: 11 | """ 12 | Internal method to get access token 13 | """ 14 | response = requests.post( 15 | "https://api.virtuals.io/api/accesses/tokens", 16 | json={"data": {}}, 17 | headers={"x-api-key": self.api_key}, 18 | ) 19 | 20 | if response.status_code != 200: 21 | raise ValueError(f"Failed to get token (status {response.status_code}). Response: {response.text}") 22 | 23 | response_json = response.json() 24 | return response_json["data"]["accessToken"] 25 | 26 | def _post( 27 | self, endpoint: str, data: dict, extra_headers: Optional[Dict[str, str]] = None 28 | ) -> dict: 29 | """ 30 | Internal method to post data 31 | """ 32 | access_token = self._get_access_token() 33 | 34 | # Default headers with Authorization 35 | headers = {"Authorization": f"Bearer {access_token}"} 36 | 37 | # Merge additional headers if provided 38 | if extra_headers: 39 | headers.update(extra_headers) 40 | 41 | response = requests.post( 42 | f"{self.base_url}/prompts", 43 | json={ 44 | "data": { 45 | "method": "post", 46 | "headers": { 47 | "Content-Type": "application/json", 48 | }, 49 | "route": endpoint, 50 | "data": data, 51 | }, 52 | }, 53 | headers=headers, 54 | ) 55 | 56 | if response.status_code != 200: 57 | raise ValueError(f"Failed to post data (status {response.status_code}). Response: {response.text}") 58 | 59 | response_json = response.json() 60 | return response_json["data"] 61 | 62 | def create_agent(self, name: str, description: str, goal: str) -> str: 63 | """ 64 | Create an agent instance (worker or agent with task generator) 65 | """ 66 | create_agent_response = self._post( 67 | endpoint="/v2/agents", 68 | data={ 69 | "name": name, 70 | "description": description, 71 | "goal": goal, 72 | } 73 | ) 74 | 75 | return create_agent_response["id"] 76 | 77 | def create_workers(self, workers: List) -> str: 78 | """ 79 | Create workers and worker description for the task generator (for agent) 80 | """ 81 | res = self._post( 82 | endpoint="/v2/maps", 83 | data={ 84 | "locations": [ 85 | {"id": w.id, "name": w.id, "description": w.worker_description} 86 | for w in workers 87 | ] 88 | }, 89 | ) 90 | 91 | return res["id"] 92 | 93 | def set_worker_task(self, agent_id: str, task: str) -> Dict: 94 | """ 95 | Set worker task (for standalone worker) 96 | """ 97 | return self._post( 98 | endpoint=f"/v2/agents/{agent_id}/tasks", 99 | data={"task": task}, 100 | ) 101 | 102 | def get_worker_action( 103 | self, 104 | agent_id: str, 105 | submission_id: str, 106 | data: dict, 107 | model_name: str, 108 | ) -> Dict: 109 | """ 110 | Get worker actions (for standalone worker) 111 | """ 112 | return self._post( 113 | endpoint=f"/v2/agents/{agent_id}/tasks/{submission_id}/next", 114 | data=data, 115 | extra_headers={"model_name": model_name}, 116 | ) 117 | 118 | def get_agent_action(self, agent_id: str, data: dict, model_name: str) -> Dict: 119 | """ 120 | Get agent actions/next step (for agent) 121 | """ 122 | return self._post( 123 | endpoint=f"/v2/agents/{agent_id}/actions", 124 | data=data, 125 | extra_headers={"model_name": model_name}, 126 | ) -------------------------------------------------------------------------------- /src/game_sdk/game/api_v2.py: -------------------------------------------------------------------------------- 1 | import requests 2 | from typing import List, Dict 3 | 4 | class GAMEClientV2: 5 | def __init__(self, api_key: str): 6 | self.api_key = api_key 7 | self.base_url = "https://sdk.game.virtuals.io/v2" 8 | self.headers = { 9 | "Content-Type": "application/json", 10 | "x-api-key": self.api_key 11 | } 12 | 13 | def create_agent(self, name: str, description: str, goal: str) -> str: 14 | """ 15 | API call to create an agent instance (worker or agent with task generator) 16 | """ 17 | payload = { 18 | "data": { 19 | "name": name, 20 | "goal": goal, 21 | "description": description 22 | } 23 | } 24 | 25 | response = requests.post( 26 | f"{self.base_url}/agents", 27 | headers=self.headers, 28 | json=payload 29 | ) 30 | 31 | return self._get_response_body(response)["id"] 32 | 33 | def create_workers(self, workers: List) -> str: 34 | """ 35 | API call to create workers and worker description for the task generator (agent) 36 | """ 37 | payload = { 38 | "data": { 39 | "locations": [ 40 | {"id": w.id, "name": w.id, "description": w.worker_description} 41 | for w in workers 42 | ] 43 | } 44 | } 45 | 46 | response = requests.post( 47 | f"{self.base_url}/maps", 48 | headers=self.headers, 49 | json=payload 50 | ) 51 | 52 | return self._get_response_body(response)["id"] 53 | 54 | def set_worker_task(self, agent_id: str, task: str) -> Dict: 55 | """ 56 | API call to set worker task (for standalone worker) 57 | """ 58 | payload = { 59 | "data": { 60 | "task": task 61 | } 62 | } 63 | 64 | response = requests.post( 65 | f"{self.base_url}/agents/{agent_id}/tasks", 66 | headers=self.headers, 67 | json=payload 68 | ) 69 | 70 | return self._get_response_body(response) 71 | 72 | def get_worker_action(self, agent_id: str, submission_id: str, data: dict, model_name: str) -> Dict: 73 | """ 74 | API call to get worker actions (for standalone worker) 75 | """ 76 | response = requests.post( 77 | f"{self.base_url}/agents/{agent_id}/tasks/{submission_id}/next", 78 | headers=self.headers | {"model_name": model_name}, 79 | json={ 80 | "data": data 81 | } 82 | ) 83 | 84 | if response.status_code != 200: 85 | raise ValueError(f"Failed to get worker action (status {response.status_code}). Response: {response.text}") 86 | 87 | response_json = response.json() 88 | 89 | return response_json["data"] 90 | 91 | def get_agent_action(self, agent_id: str, data: dict, model_name: str) -> Dict: 92 | """ 93 | API call to get agent actions/next step (for agent) 94 | """ 95 | response = requests.post( 96 | f"{self.base_url}/agents/{agent_id}/actions", 97 | headers=self.headers | {"model_name": model_name}, 98 | json={ 99 | "data": data 100 | } 101 | ) 102 | 103 | if response.status_code != 200: 104 | raise ValueError(f"Failed to get agent action (status {response.status_code}). Response: {response.text}") 105 | 106 | response_json = response.json() 107 | 108 | return response_json["data"] 109 | 110 | def create_chat(self, data: dict) -> str: 111 | response = requests.post( 112 | f"{self.base_url}/conversation", 113 | headers=self.headers, 114 | json={ 115 | "data": data 116 | } 117 | ) 118 | 119 | chat_id = self._get_response_body(response).get("conversation_id") 120 | if not chat_id: 121 | raise Exception("Agent did not return a conversation_id for the chat.") 122 | return chat_id 123 | 124 | def update_chat(self, conversation_id: str, data: dict) -> dict: 125 | response = requests.post( 126 | f"{self.base_url}/conversation/{conversation_id}/next", 127 | headers=self.headers, 128 | json={ 129 | "data": data 130 | } 131 | ) 132 | 133 | if response.status_code != 200: 134 | raise ValueError(f"Failed to update conversation (status {response.status_code}). Response: {response.text}") 135 | 136 | response_json = response.json() 137 | 138 | return response_json["data"] 139 | 140 | def report_function(self, conversation_id: str, data: dict) -> dict: 141 | response = requests.post( 142 | f"{self.base_url}/conversation/{conversation_id}/function/result", 143 | headers=self.headers, 144 | json={ 145 | "data": data 146 | } 147 | ) 148 | 149 | return self._get_response_body(response) 150 | 151 | def end_chat(self, conversation_id: str, data: dict) -> dict: 152 | response = requests.post( 153 | f"{self.base_url}/conversation/{conversation_id}/end", 154 | headers=self.headers, 155 | json={ 156 | "data": data 157 | } 158 | ) 159 | 160 | return self._get_response_body(response) 161 | 162 | def _get_response_body(self, response: requests.Response) -> dict: 163 | if response.status_code != 200: 164 | raise ValueError(f"Failed to get response body (status {response.status_code}). Response: {response.text}") 165 | 166 | response_json = response.json() 167 | 168 | return response_json["data"] -------------------------------------------------------------------------------- /src/game_sdk/game/chat_agent.py: -------------------------------------------------------------------------------- 1 | from typing import Any, Callable, Dict, List, Optional, Tuple 2 | from game_sdk.game.custom_types import ( 3 | ChatResponse, 4 | FunctionCallResponse, 5 | FunctionResult, 6 | GameChatResponse, 7 | Function, 8 | AgentMessage, 9 | ) 10 | from game_sdk.game.api_v2 import GAMEClientV2 11 | 12 | 13 | class Chat: 14 | def __init__( 15 | self, 16 | conversation_id: str, 17 | client: GAMEClientV2, 18 | action_space: Optional[List[Function]] = None, 19 | get_state_fn: Optional[Callable[[], Dict[str, Any]]] = None, 20 | ): 21 | self.chat_id = conversation_id 22 | self.client = client 23 | self.action_space = ( 24 | {f.fn_name: f for f in action_space} if action_space else None 25 | ) 26 | self.get_state_fn = get_state_fn 27 | 28 | def next(self, message: str) -> ChatResponse: 29 | 30 | convo_response = self._update_conversation(message) 31 | 32 | # execute functions/actions if present 33 | if convo_response.function_call: 34 | if not self.action_space: 35 | raise Exception("No functions provided") 36 | 37 | fn_name = convo_response.function_call.fn_name 38 | 39 | fn_to_call = self.action_space.get(fn_name) 40 | if not fn_to_call: 41 | raise Exception( 42 | f"Function {fn_name}, returned by the agent, not found in action space" 43 | ) 44 | 45 | result = fn_to_call.execute( 46 | **{ 47 | "fn_id": convo_response.function_call.id, 48 | "args": convo_response.function_call.args, 49 | } 50 | ) 51 | response_message = self._report_function_result(result) 52 | function_call_response = FunctionCallResponse( 53 | fn_name=fn_name, 54 | fn_args=convo_response.function_call.args, 55 | result=result, 56 | ) 57 | else: 58 | response_message = convo_response.message or "" 59 | function_call_response = None 60 | 61 | return ChatResponse( 62 | message=response_message, 63 | is_finished=convo_response.is_finished, 64 | function_call=function_call_response, 65 | ) 66 | 67 | def end(self, message: Optional[str] = None): 68 | self.client.end_chat( 69 | self.chat_id, 70 | { 71 | "message": message, 72 | }, 73 | ) 74 | 75 | def _update_conversation(self, message: str) -> GameChatResponse: 76 | data = { 77 | "message": message, 78 | "state": self.get_state_fn() if self.get_state_fn else None, 79 | "functions": ( 80 | [f.get_function_def() for f in self.action_space.values()] 81 | if self.action_space 82 | else None 83 | ), 84 | } 85 | result = self.client.update_chat(self.chat_id, data) 86 | return GameChatResponse.model_validate(result) 87 | 88 | def _report_function_result(self, result: FunctionResult) -> str: 89 | data = { 90 | "fn_id": result.action_id, 91 | "result": ( 92 | f"{result.action_status.value}: {result.feedback_message}" 93 | if result.feedback_message 94 | else result.action_status.value 95 | ), 96 | } 97 | response = self.client.report_function(self.chat_id, data) 98 | 99 | message = response.get("message") 100 | if not message: 101 | raise Exception("Agent did not return a message for the function report.") 102 | return message 103 | 104 | 105 | class ChatAgent: 106 | def __init__( 107 | self, 108 | api_key: str, 109 | prompt: str, 110 | ): 111 | self._api_key = api_key 112 | self.prompt = prompt 113 | 114 | if api_key.startswith("apt-"): 115 | self.client = GAMEClientV2(api_key) 116 | else: 117 | raise Exception("Please use V2 API key to use ChatAgent") 118 | 119 | def create_chat( 120 | self, 121 | partner_id: str, 122 | partner_name: str, 123 | action_space: Optional[List[Function]] = None, 124 | get_state_fn: Optional[Callable[[], Dict[str, Any]]] = None, 125 | ) -> Chat: 126 | 127 | chat_id = self.client.create_chat( 128 | { 129 | "prompt": self.prompt, 130 | "partner_id": partner_id, 131 | "partner_name": partner_name, 132 | }, 133 | ) 134 | 135 | return Chat(chat_id, self.client, action_space, get_state_fn) 136 | -------------------------------------------------------------------------------- /src/game_sdk/hosted_game/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/game-by-virtuals/game-python/76a1a712be14b971fd59a45c14c76c8775884e73/src/game_sdk/hosted_game/__init__.py -------------------------------------------------------------------------------- /src/game_sdk/hosted_game/functions/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/game-by-virtuals/game-python/76a1a712be14b971fd59a45c14c76c8775884e73/src/game_sdk/hosted_game/functions/__init__.py -------------------------------------------------------------------------------- /src/game_sdk/hosted_game/sdk.py: -------------------------------------------------------------------------------- 1 | import requests 2 | 3 | 4 | class GameSDK: 5 | api_url: str = "https://game-api.virtuals.io/api" 6 | api_key: str 7 | 8 | def __init__(self, api_key: str): 9 | self.api_key = api_key 10 | 11 | def functions(self): 12 | """ 13 | Get all default functions 14 | """ 15 | response = requests.get( 16 | f"{self.api_url}/functions", headers={"x-api-key": self.api_key}) 17 | 18 | if (response.status_code != 200): 19 | raise Exception(response.json()) 20 | 21 | functions = {} 22 | 23 | for x in response.json()["data"]: 24 | functions[x["fn_name"]] = x["fn_description"] 25 | 26 | return functions 27 | 28 | def simulate(self, session_id: str, goal: str, description: str, functions: list, custom_functions: list): 29 | """ 30 | Simulate the agent configuration 31 | """ 32 | response = requests.post( 33 | f"{self.api_url}/simulate", 34 | json={ 35 | "data": { 36 | "sessionId": session_id, 37 | "goal": goal, 38 | "description": description, 39 | "worldInfo": "", 40 | "functions": functions, 41 | "customFunctions": [x.toJson() for x in custom_functions] 42 | } 43 | }, 44 | headers={"x-api-key": self.api_key} 45 | ) 46 | 47 | if (response.status_code != 200): 48 | raise Exception(response.json()) 49 | 50 | return response.json()["data"] 51 | 52 | def react(self, session_id: str, platform: str, goal: str, 53 | description: str, functions: list, custom_functions: list, 54 | event: str = None, task: str = None, tweet_id: str = None): 55 | """ 56 | Simulate the agent configuration 57 | """ 58 | url = f"{self.api_url}/react/{platform}" 59 | 60 | payload = { 61 | "sessionId": session_id, 62 | "goal": goal, 63 | "description": description, 64 | "worldInfo": "", 65 | "functions": functions, 66 | "customFunctions": [x.toJson() for x in custom_functions] 67 | } 68 | 69 | if (event): 70 | payload["event"] = event 71 | 72 | if (task): 73 | payload["task"] = task 74 | 75 | if (tweet_id): 76 | payload["tweetId"] = tweet_id 77 | 78 | print(payload) 79 | 80 | response = requests.post( 81 | url, 82 | json={ 83 | "data": payload 84 | }, 85 | headers={"x-api-key": self.api_key} 86 | ) 87 | 88 | if (response.status_code != 200): 89 | raise Exception(response.json()) 90 | 91 | return response.json()["data"] 92 | 93 | def deploy(self, goal: str, description: str, functions: list, custom_functions: list, main_heartbeat: int, reaction_heartbeat: int, tweet_usernames: list = None, templates: list = None, game_engine_model: str = "llama_3_1_405b"): 94 | """ 95 | Simulate the agent configuration 96 | """ 97 | payload = { 98 | "goal": goal, 99 | "description": description, 100 | "worldInfo": "", 101 | "functions": functions, 102 | "customFunctions": [x.toJson() for x in custom_functions], 103 | "gameState": { 104 | "mainHeartbeat": main_heartbeat, 105 | "reactionHeartbeat": reaction_heartbeat, 106 | }, 107 | "gameEngineModel": game_engine_model 108 | } 109 | 110 | if tweet_usernames is not None: 111 | payload["tweetUsernames"] = tweet_usernames 112 | 113 | # Add templates to payload if provided 114 | if templates: 115 | payload["templates"] = [template.to_dict() for template in templates] 116 | 117 | response = requests.post( 118 | f"{self.api_url}/deploy", 119 | json={ 120 | "data": payload 121 | }, 122 | headers={"x-api-key": self.api_key} 123 | ) 124 | 125 | if (response.status_code != 200): 126 | raise Exception(response.json()) 127 | 128 | return response.json()["data"] 129 | 130 | def reset_memory(self): 131 | response = requests.get( 132 | f"{self.api_url}/reset-session", headers={"x-api-key": self.api_key}) 133 | 134 | if (response.status_code != 200): 135 | raise Exception("Failed to reset memory.") 136 | 137 | return "Memory reset successfully." 138 | --------------------------------------------------------------------------------