Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions cdp-agentkit-core/python/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,19 @@

## Unreleased

## [0.0.10] - 2025-01-24

### Added

- Added `hyperbolic` module.
- Added `get_spend_history` action.
- Added `get_available_gpus` action.
- Added `get_spend_history` action.
- Added `get_gpu_status` action.
- Added `rent_compute` action.
- Added `terminate_compute` action.
- Added `utils` module for Hyperbolic API key handling.

## [0.0.9] - 2025-01-17

### Added
Expand Down
12 changes: 12 additions & 0 deletions cdp-agentkit-core/python/cdp_agentkit_core/actions/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@
from cdp_agentkit_core.actions.get_balance import GetBalanceAction
from cdp_agentkit_core.actions.get_balance_nft import GetBalanceNftAction
from cdp_agentkit_core.actions.get_wallet_details import GetWalletDetailsAction
from cdp_agentkit_core.actions.hyperbolic.get_available_gpus import GetAvailableGpusAction
from cdp_agentkit_core.actions.hyperbolic.get_current_balance import GetCurrentBalanceAction
from cdp_agentkit_core.actions.hyperbolic.get_gpu_status import GetGpuStatusAction
from cdp_agentkit_core.actions.hyperbolic.get_spend_history import GetSpendHistoryAction
from cdp_agentkit_core.actions.hyperbolic.rent_compute import RentComputeAction
from cdp_agentkit_core.actions.hyperbolic.terminate_compute import TerminateComputeAction
from cdp_agentkit_core.actions.mint_nft import MintNftAction
from cdp_agentkit_core.actions.pyth.fetch_price import PythFetchPriceAction
from cdp_agentkit_core.actions.pyth.fetch_price_feed_id import PythFetchPriceFeedIDAction
Expand Down Expand Up @@ -50,4 +56,10 @@ def get_all_cdp_actions() -> list[type[CdpAction]]:
"WrapEthAction",
"PythFetchPriceFeedIDAction",
"PythFetchPriceAction",
"GetAvailableGpusAction",
"GetCurrentBalanceAction",
"GetGpuStatusAction",
"GetSpendHistoryAction",
"RentComputeAction",
"TerminateComputeAction",
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
from collections.abc import Callable

import requests
from pydantic import BaseModel

from cdp_agentkit_core.actions import CdpAction
from cdp_agentkit_core.actions.hyperbolic.utils import get_api_key

GET_AVAILABLE_GPUS_PROMPT = """
This tool will get all the available GPU machines on the Hyperbolic platform.

It does not take any following inputs

Important notes:
- Authorization key is required for this operation
- The GPU prices are in CENTS per hour
"""


class GetAvailableGpusInput(BaseModel):
"""Input argument schema for getting available GPU machines."""


def get_available_gpus() -> str:
"""Get all the available GPU machines on the Hyperbolic platform.

Returns:
A string representing the response from the API.

"""
# Get API key from environment
api_key = get_api_key()

url = "https://api.hyperbolic.xyz/v1/marketplace"
headers = {"Content-Type": "application/json", "Authorization": f"Bearer {api_key}"}
data = {"filters": {}}
response = requests.post(url, headers=headers, json=data)
return response.json()


class GetAvailableGpusAction(CdpAction):
"""Get available GPUs action."""

name: str = "get_available_gpus"
description: str = GET_AVAILABLE_GPUS_PROMPT
args_schema: type[BaseModel] | None = GetAvailableGpusInput
func: Callable[..., str] = get_available_gpus
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
from collections.abc import Callable
from datetime import datetime

import requests
from pydantic import BaseModel

from cdp_agentkit_core.actions import CdpAction
from cdp_agentkit_core.actions.hyperbolic.utils import get_api_key

GET_CURRENT_BALANCE_PROMPT = """
This tool retrieves your current Hyperbolic platform credit balance.
It shows:
- Available Hyperbolic platform credits in your account (in USD)
- Recent credit purchase history
Note: This is NOT for checking cryptocurrency wallet balances (ETH/USDC).
For crypto wallet balances, please use a different command.
No input parameters required.
"""


class GetCurrentBalanceInput(BaseModel):
"""Input argument schema for getting current balance."""

pass


def get_current_balance() -> str:
"""Retrieve current balance and purchase history from the account.

Returns:
str: Formatted current balance and purchase history information

"""
api_key = get_api_key()
headers = {"Content-Type": "application/json", "Authorization": f"Bearer {api_key}"}

try:
# Get current balance
balance_url = "https://api.hyperbolic.xyz/billing/get_current_balance"
balance_response = requests.get(balance_url, headers=headers)
balance_response.raise_for_status()
balance_data = balance_response.json()

# Get purchase history
history_url = "https://api.hyperbolic.xyz/billing/purchase_history"
history_response = requests.get(history_url, headers=headers)
history_response.raise_for_status()
history_data = history_response.json()

# Format the output
credits = balance_data.get("credits", 0)
balance_usd = credits / 100 # Convert tokens to dollars

output = [f"Your current Hyperbolic platform balance is ${balance_usd:.2f}."]

purchases = history_data.get("purchase_history", [])
if purchases:
output.append("\nPurchase History:")
for purchase in purchases:
amount = float(purchase["amount"]) / 100
timestamp = datetime.fromisoformat(purchase["timestamp"])
formatted_date = timestamp.strftime("%B %d, %Y")
output.append(f"- ${amount:.2f} on {formatted_date}")
else:
output.append("\nNo previous purchases found.")

return "\n".join(output)

except requests.exceptions.RequestException as e:
return f"Error retrieving balance information: {e!s}"


class GetCurrentBalanceAction(CdpAction):
"""Get current balance action."""

name: str = "get_current_balance"
description: str = GET_CURRENT_BALANCE_PROMPT
args_schema: type[BaseModel] | None = GetCurrentBalanceInput
func: Callable[..., str] = get_current_balance
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
from collections.abc import Callable

import requests
from pydantic import BaseModel

from cdp_agentkit_core.actions.cdp_action import CdpAction
from cdp_agentkit_core.actions.hyperbolic.utils import get_api_key

GET_GPU_STATUS_PROMPT = """
This tool will get all the the status and ssh commands of you currently rented GPUs on the Hyperbolic platform.

It does not take any inputs

Important notes:
- Authorization key is required for this operation
- The GPU prices are in CENTS per hour
- If the status is "starting", it means the GPU is not ready yet. You can use the GetGPUStatus Action to check the status again after 5 seconds.
- You can access it through the SSHAccess Action and run commands on it through the RemoteShell Action.
"""


class GetGpuStatusInput(BaseModel):
"""Input argument schema for getting available GPUs."""


def get_gpu_status() -> str:
"""Return a string representation of the response from the Hyperbolic API.

Returns:
A string representing the response from the API.

"""
# Get API key from environment
api_key = get_api_key()

url = "https://api.hyperbolic.xyz/v1/marketplace/instances"
headers = {"Content-Type": "application/json", "Authorization": f"Bearer {api_key}"}
response = requests.get(url, headers=headers)
return response.json()


class GetGpuStatusAction(CdpAction):
"""Get status for my GPUs action."""

name: str = "get_gpu_status"
description: str = GET_GPU_STATUS_PROMPT
args_schema: type[BaseModel] | None = GetGpuStatusInput
func: Callable[..., str] = get_gpu_status
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
from collections import defaultdict
from collections.abc import Callable
from datetime import datetime

import requests
from pydantic import BaseModel

from cdp_agentkit_core.actions.cdp_action import CdpAction
from cdp_agentkit_core.actions.hyperbolic.utils import get_api_key

GET_SPEND_HISTORY_PROMPT = """
This tool retrieves and analyzes your GPU rental spending history from the Hyperbolic platform.
It provides information about:
- List of all instances rented
- Duration of each rental in seconds
- Cost per rental
- Total spending per GPU type
- Overall total spending
No input parameters required.
"""


class GetSpendHistoryInput(BaseModel):
"""Input argument schema for getting spend history."""

pass


def calculate_duration_seconds(start_time: str, end_time: str) -> float:
"""Calculate duration in seconds between two timestamps."""
start = datetime.fromisoformat(start_time.replace("Z", "+00:00"))
end = datetime.fromisoformat(end_time.replace("Z", "+00:00"))
duration = end - start
return duration.total_seconds()


def get_spend_history() -> str:
"""Retrieve and analyze instance rental spending history.

Returns:
str: Formatted analysis of GPU rental spending

"""
api_key = get_api_key()

url = "https://api.hyperbolic.xyz/v1/marketplace/instances/history"
headers = {"Content-Type": "application/json", "Authorization": f"Bearer {api_key}"}

try:
response = requests.get(url, headers=headers)
response.raise_for_status()
data = response.json()

if not data.get("instance_history"):
return "No rental history found."

# Initialize analysis variables
total_cost = 0
gpu_stats = defaultdict(lambda: {"count": 0, "total_cost": 0, "total_seconds": 0})
instances_summary = []

# Analyze each instance
for instance in data["instance_history"]:
duration_seconds = calculate_duration_seconds(
instance["started_at"], instance["terminated_at"]
)
# Convert seconds to hours for cost calculation
duration_hours = duration_seconds / 3600.0
# Calculate cost: (hours) * (cents/hour) / (100 cents/dollar)
cost = (duration_hours * instance["price"]["amount"]) / 100.0
total_cost += cost

# Get GPU model and count from this instance - with validation
gpus = instance["hardware"].get("gpus", [])

gpu_model = "Unknown GPU" if not gpus else gpus[0].get("model", "Unknown GPU") # Safely get GPU model

gpu_count = instance["gpu_count"]
gpu_stats[gpu_model]["count"] += gpu_count
gpu_stats[gpu_model]["total_cost"] += cost
gpu_stats[gpu_model]["total_seconds"] += duration_seconds

# Create instance summary
instances_summary.append(
{
"name": instance["instance_name"],
"gpu_model": gpu_model,
"gpu_count": gpu_count,
"duration_seconds": int(duration_seconds),
"cost": round(cost, 2),
}
)

# Format the output
output = ["=== GPU Rental Spending Analysis ===\n"]

output.append("Instance Rentals:")
for instance in instances_summary:
output.append(f"- {instance['name']}:")
output.append(f" GPU: {instance['gpu_model']} (Count: {instance['gpu_count']})")
output.append(f" Duration: {instance['duration_seconds']} seconds")
output.append(f" Cost: ${instance['cost']:.2f}")

output.append("\nGPU Type Statistics:")
for gpu_model, stats in gpu_stats.items():
output.append(f"\n{gpu_model}:")
output.append(f" Total Rentals: {stats['count']}")
output.append(f" Total Time: {int(stats['total_seconds'])} seconds")
output.append(f" Total Cost: ${stats['total_cost']:.2f}")

output.append(f"\nTotal Spending: ${total_cost:.2f}")

return "\n".join(output)

except requests.exceptions.RequestException as e:
return f"Error retrieving spend history: {e!s}"


class GetSpendHistoryAction(CdpAction):
"""Get spend history action."""

name: str = "get_spend_history"
description: str = GET_SPEND_HISTORY_PROMPT
args_schema: type[BaseModel] | None = GetSpendHistoryInput
func: Callable[..., str] = get_spend_history
Loading