This commit is contained in:
+285
-3
@@ -10,6 +10,7 @@ from traderai.cornerstone_client import CornerstoneClient, parse_cornerstone_ite
|
||||
from traderai.memory import MemoryStore
|
||||
from traderai.scheduler import WakeScheduler
|
||||
from traderai.scmdb_client import SCMDBClient
|
||||
from traderai.starcitizen_wiki_client import StarCitizenWikiClient
|
||||
from traderai.uex_client import UEXClient
|
||||
|
||||
|
||||
@@ -58,10 +59,14 @@ UEX_GET_RESOURCES: dict[str, dict[str, Any]] = {
|
||||
"marketplace_averages": {"params": ["id_item", "item_name", "item_slug"], "auth": False, "group": "marketplace"},
|
||||
"marketplace_averages_all": {"params": [], "auth": False, "group": "marketplace", "heavy": True},
|
||||
"marketplace_favorites": {"params": ["id_listing"], "auth": True, "group": "marketplace"},
|
||||
"marketplace_listings": {"params": ["id", "slug", "username"], "auth": False, "group": "marketplace"},
|
||||
"marketplace_listings": {"params": ["id", "slug", "username", "id_item", "operation"], "auth": False, "group": "marketplace"},
|
||||
"marketplace_negotiations": {"params": ["id", "id_listing", "hash"], "auth": True, "group": "marketplace"},
|
||||
"marketplace_negotiations_messages": {"params": ["hash", "id_negotiation"], "auth": True, "group": "marketplace"},
|
||||
"marketplace_prices_averages": {"params": ["id_item", "item_name", "item_slug"], "auth": False, "group": "marketplace"},
|
||||
"marketplace_prices_averages": {
|
||||
"params": ["id_item", "item_name", "item_slug", "id_category", "currency", "quality_tier"],
|
||||
"auth": False,
|
||||
"group": "marketplace",
|
||||
},
|
||||
"marketplace_prices_averages_all": {"params": [], "auth": False, "group": "marketplace", "heavy": True},
|
||||
"marketplace_prices_history": {
|
||||
"params": [
|
||||
@@ -83,7 +88,11 @@ UEX_GET_RESOURCES: dict[str, dict[str, Any]] = {
|
||||
"group": "marketplace",
|
||||
"history": True,
|
||||
},
|
||||
"marketplace_trends": {"params": ["id_item", "item_name", "item_slug"], "auth": False, "group": "marketplace"},
|
||||
"marketplace_trends": {
|
||||
"params": ["id_item", "item_name", "item_slug", "id_category", "currency", "quality_tier"],
|
||||
"auth": False,
|
||||
"group": "marketplace",
|
||||
},
|
||||
"moons": {"params": ["id", "id_planet", "id_star_system", "name", "slug"], "auth": False, "group": "locations"},
|
||||
"orbits": {"params": ["id", "id_star_system", "name", "slug"], "auth": False, "group": "locations"},
|
||||
"orbits_distances": {"params": ["id_origin", "id_destination"], "auth": False, "group": "locations"},
|
||||
@@ -162,12 +171,14 @@ class ToolRegistry:
|
||||
scheduler: WakeScheduler | None = None,
|
||||
scmdb: SCMDBClient | None = None,
|
||||
cornerstone: CornerstoneClient | None = None,
|
||||
scwiki: StarCitizenWikiClient | None = None,
|
||||
plan_store: Any | None = None,
|
||||
plan_runner: Any | None = None,
|
||||
) -> None:
|
||||
self.uex = uex
|
||||
self.scmdb = scmdb or SCMDBClient()
|
||||
self.cornerstone = cornerstone or CornerstoneClient()
|
||||
self.scwiki = scwiki or StarCitizenWikiClient()
|
||||
self.require_write_approval = require_write_approval
|
||||
self.memory = memory
|
||||
self.scheduler = scheduler
|
||||
@@ -178,6 +189,7 @@ class ToolRegistry:
|
||||
self.handlers: dict[str, ToolHandler] = {
|
||||
"search_marketplace_listings": self.search_marketplace_listings,
|
||||
"get_marketplace_listing": self.get_marketplace_listing,
|
||||
"get_marketplace_trends": self.get_marketplace_trends,
|
||||
"list_marketplace_negotiations": self.list_marketplace_negotiations,
|
||||
"get_negotiation_messages": self.get_negotiation_messages,
|
||||
"draft_negotiation_message": self.draft_negotiation_message,
|
||||
@@ -192,11 +204,16 @@ class ToolRegistry:
|
||||
"pause_continual_plan": self.pause_continual_plan,
|
||||
"resume_continual_plan": self.resume_continual_plan,
|
||||
"cancel_continual_plan": self.cancel_continual_plan,
|
||||
"delete_continual_plan": self.delete_continual_plan,
|
||||
"run_continual_plan_now": self.run_continual_plan_now,
|
||||
"check_uex_notifications": self.check_uex_notifications,
|
||||
"list_scmdb_versions": self.list_scmdb_versions,
|
||||
"search_scmdb_missions": self.search_scmdb_missions,
|
||||
"get_scmdb_mission_rewards": self.get_scmdb_mission_rewards,
|
||||
"search_scwiki_pages": self.search_scwiki_pages,
|
||||
"get_scwiki_page": self.get_scwiki_page,
|
||||
"search_scwiki_vehicles": self.search_scwiki_vehicles,
|
||||
"get_scwiki_vehicle": self.get_scwiki_vehicle,
|
||||
"search_cornerstone_items": self.search_cornerstone_items,
|
||||
"get_cornerstone_item_locations": self.get_cornerstone_item_locations,
|
||||
"get_cornerstone_item_media": self.get_cornerstone_item_media,
|
||||
@@ -226,6 +243,7 @@ class ToolRegistry:
|
||||
*self._uex_post_schemas(),
|
||||
*self._uex_delete_schemas(),
|
||||
*self._scmdb_schemas(),
|
||||
*self._scwiki_schemas(),
|
||||
*self._cornerstone_schemas(),
|
||||
{
|
||||
"type": "function",
|
||||
@@ -261,6 +279,24 @@ class ToolRegistry:
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "get_marketplace_trends",
|
||||
"description": "Fetch current UEX marketplace trend metrics for an item, including WTS and WTB averages plus negotiation counts.",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id_item": {"type": "integer"},
|
||||
"item_name": {"type": "string"},
|
||||
"item_slug": {"type": "string"},
|
||||
"id_category": {"type": "integer"},
|
||||
"currency": {"type": "string", "description": "Optional currency filter such as UEC, WIF, or MGS."},
|
||||
"quality_tier": {"type": "integer", "minimum": 0, "maximum": 7},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
@@ -480,6 +516,14 @@ class ToolRegistry:
|
||||
"parameters": {"type": "object", "required": ["plan_id"], "properties": {"plan_id": {"type": "string"}}},
|
||||
},
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "delete_continual_plan",
|
||||
"description": "Delete a continual plan and all of its stored checklist items, candidates, negotiations, and event history.",
|
||||
"parameters": {"type": "object", "required": ["plan_id"], "properties": {"plan_id": {"type": "string"}}},
|
||||
},
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
@@ -965,6 +1009,68 @@ class ToolRegistry:
|
||||
},
|
||||
]
|
||||
|
||||
@classmethod
|
||||
def _scwiki_schemas(cls) -> list[dict[str, Any]]:
|
||||
return [
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "search_scwiki_pages",
|
||||
"description": "Search Star Citizen Wiki pages on starcitizen.tools and return concise summaries for general game knowledge.",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"query": {"type": "string", "description": "Page title or topic to search for."},
|
||||
"limit": {"type": "integer", "minimum": 1, "maximum": 10, "default": 5},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "get_scwiki_page",
|
||||
"description": "Fetch one Star Citizen Wiki page summary by title or page id.",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"title": {"type": "string"},
|
||||
"pageid": {"type": "integer"},
|
||||
"chars": {"type": "integer", "minimum": 120, "maximum": 1200, "default": 700},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "search_scwiki_vehicles",
|
||||
"description": "Search Star Citizen Wiki structured vehicle data for ships and vehicles.",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"query": {"type": "string", "description": "Ship or vehicle name to search for."},
|
||||
"limit": {"type": "integer", "minimum": 1, "maximum": 10, "default": 5},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "get_scwiki_vehicle",
|
||||
"description": "Fetch one Star Citizen Wiki vehicle summary, including MSRP and in-game purchase locations when available.",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"slug": {"type": "string", "description": "Vehicle slug such as anvl-carrack."},
|
||||
"query": {"type": "string", "description": "Vehicle name if the slug is not known."},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
@classmethod
|
||||
def _cornerstone_schemas(cls) -> list[dict[str, Any]]:
|
||||
return [
|
||||
@@ -1213,6 +1319,49 @@ class ToolRegistry:
|
||||
response = await self.uex.get("marketplace_listings", {"id": id, "slug": slug})
|
||||
return {"listing": response.get("data")}
|
||||
|
||||
async def get_marketplace_trends(
|
||||
self,
|
||||
id_item: int | None = None,
|
||||
item_name: str | None = None,
|
||||
item_slug: str | None = None,
|
||||
id_category: int | None = None,
|
||||
currency: str | None = None,
|
||||
quality_tier: int | None = None,
|
||||
) -> dict[str, Any]:
|
||||
response = await self.uex.get(
|
||||
"marketplace_trends",
|
||||
{
|
||||
"id_item": id_item,
|
||||
"item_name": item_name,
|
||||
"item_slug": item_slug,
|
||||
"id_category": id_category,
|
||||
"currency": currency,
|
||||
"quality_tier": quality_tier,
|
||||
},
|
||||
)
|
||||
trends = [
|
||||
self._summarize_marketplace_trend(item)
|
||||
for item in self._as_list(response.get("data"))
|
||||
if isinstance(item, dict)
|
||||
]
|
||||
return {
|
||||
"status": response.get("status"),
|
||||
"count": len(trends),
|
||||
"filters": {
|
||||
key: value
|
||||
for key, value in {
|
||||
"id_item": id_item,
|
||||
"item_name": item_name,
|
||||
"item_slug": item_slug,
|
||||
"id_category": id_category,
|
||||
"currency": currency,
|
||||
"quality_tier": quality_tier,
|
||||
}.items()
|
||||
if value is not None
|
||||
},
|
||||
"trends": trends,
|
||||
}
|
||||
|
||||
async def list_marketplace_negotiations(
|
||||
self,
|
||||
id: int | None = None,
|
||||
@@ -1405,6 +1554,19 @@ class ToolRegistry:
|
||||
self.scheduler.unschedule_plan(plan_id)
|
||||
return {"plan": self.plan_store.set_status(plan_id, "canceled")}
|
||||
|
||||
async def delete_continual_plan(self, plan_id: str) -> dict[str, Any]:
|
||||
if self.plan_store is None:
|
||||
return {"error": "Continual plan store is not configured."}
|
||||
plan = self.plan_store.get_plan(plan_id)
|
||||
if not plan:
|
||||
return {"error": f"Plan not found: {plan_id}"}
|
||||
if self.scheduler is not None:
|
||||
self.scheduler.unschedule_plan(plan_id)
|
||||
deleted = self.plan_store.delete_plan(plan_id)
|
||||
if not deleted:
|
||||
return {"error": f"Plan not found: {plan_id}"}
|
||||
return {"deleted": True, "plan_id": plan_id, "summary": f"Deleted plan {plan.get('title') or plan_id}."}
|
||||
|
||||
async def run_continual_plan_now(self, plan_id: str) -> dict[str, Any]:
|
||||
if self.plan_runner is None:
|
||||
return {"error": "Continual plan runner is not configured."}
|
||||
@@ -1535,6 +1697,49 @@ class ToolRegistry:
|
||||
"mission": self._summarize_scmdb_mission(data, mission, source=source, detailed=True),
|
||||
}
|
||||
|
||||
async def search_scwiki_pages(self, query: str, limit: int = 5) -> dict[str, Any]:
|
||||
pages = await self.scwiki.search_pages(query, limit=limit)
|
||||
return {"source": self.scwiki.base_url, "query": query, "matched": len(pages), "pages": pages}
|
||||
|
||||
async def get_scwiki_page(
|
||||
self,
|
||||
title: str | None = None,
|
||||
pageid: int | None = None,
|
||||
chars: int = 700,
|
||||
) -> dict[str, Any]:
|
||||
page = await self.scwiki.get_page_summary(title=title, pageid=pageid, chars=chars)
|
||||
if not page:
|
||||
return {"error": "No Star Citizen Wiki page matched."}
|
||||
return {"source": self.scwiki.base_url, "page": page}
|
||||
|
||||
async def search_scwiki_vehicles(self, query: str, limit: int = 5) -> dict[str, Any]:
|
||||
groups = await self.scwiki.search_verse(query)
|
||||
vehicles_group = next((item for item in groups if item.get("type") == "vehicles"), None)
|
||||
results = [
|
||||
self._summarize_scwiki_vehicle_search(item)
|
||||
for item in (vehicles_group or {}).get("results", [])[: max(1, min(limit, 10))]
|
||||
if isinstance(item, dict)
|
||||
]
|
||||
return {"source": self.scwiki.api_base_url, "query": query, "matched": len(results), "vehicles": results}
|
||||
|
||||
async def get_scwiki_vehicle(self, slug: str | None = None, query: str | None = None) -> dict[str, Any]:
|
||||
resolved_slug = slug
|
||||
if not resolved_slug:
|
||||
if not query:
|
||||
return {"error": "Provide slug or query."}
|
||||
groups = await self.scwiki.search_verse(query)
|
||||
vehicles_group = next((item for item in groups if item.get("type") == "vehicles"), None)
|
||||
candidates = [
|
||||
item
|
||||
for item in (vehicles_group or {}).get("results", [])
|
||||
if isinstance(item, dict) and item.get("api_url")
|
||||
]
|
||||
if not candidates:
|
||||
return {"error": "No Star Citizen Wiki vehicle matched."}
|
||||
resolved_slug = str(candidates[0]["api_url"]).rstrip("/").rsplit("/", 1)[-1]
|
||||
vehicle = await self.scwiki.get_vehicle(resolved_slug)
|
||||
return {"source": self.scwiki.api_base_url, "vehicle": self._summarize_scwiki_vehicle(vehicle)}
|
||||
|
||||
async def search_cornerstone_items(
|
||||
self,
|
||||
query: str = "",
|
||||
@@ -2210,6 +2415,83 @@ class ToolRegistry:
|
||||
"expires_at": listing.get("date_expiration"),
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def _summarize_marketplace_trend(trend: dict[str, Any]) -> dict[str, Any]:
|
||||
return {
|
||||
"id_item": trend.get("id_item"),
|
||||
"item_name": trend.get("item_name"),
|
||||
"item_slug": trend.get("item_slug"),
|
||||
"currency": trend.get("currency"),
|
||||
"sell": {
|
||||
"avg_price": trend.get("price_avg_sell"),
|
||||
"avg_price_month": trend.get("price_avg_month_sell"),
|
||||
"min_price": trend.get("price_min_sell"),
|
||||
"max_price": trend.get("price_max_sell"),
|
||||
"listings_count": trend.get("listings_count_sell"),
|
||||
},
|
||||
"buy": {
|
||||
"avg_price": trend.get("price_avg_buy"),
|
||||
"avg_price_month": trend.get("price_avg_month_buy"),
|
||||
"min_price": trend.get("price_min_buy"),
|
||||
"max_price": trend.get("price_max_buy"),
|
||||
"listings_count": trend.get("listings_count_buy"),
|
||||
},
|
||||
"total_listings_count": trend.get("total_listings_count"),
|
||||
"negotiations_count": trend.get("negotiations_count"),
|
||||
"negotiations_open": trend.get("negotiations_open"),
|
||||
"negotiations_success": trend.get("negotiations_success"),
|
||||
"link_prices": trend.get("link_prices"),
|
||||
"link_prices_history": trend.get("link_prices_history"),
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def _summarize_scwiki_vehicle_search(vehicle: dict[str, Any]) -> dict[str, Any]:
|
||||
return {
|
||||
"name": vehicle.get("name"),
|
||||
"class_name": vehicle.get("class_name"),
|
||||
"career": vehicle.get("extra_label"),
|
||||
"api_url": vehicle.get("api_url"),
|
||||
"web_url": vehicle.get("web_url"),
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def _summarize_scwiki_vehicle(vehicle: dict[str, Any]) -> dict[str, Any]:
|
||||
purchases = []
|
||||
for entry in ((vehicle.get("uex_prices") or {}).get("purchase") or []):
|
||||
if not isinstance(entry, dict):
|
||||
continue
|
||||
location = entry.get("starmap_location") or {}
|
||||
purchases.append(
|
||||
{
|
||||
"price_buy": entry.get("price_buy"),
|
||||
"terminal_name": entry.get("terminal_name"),
|
||||
"location": location.get("name"),
|
||||
"parent_location": location.get("parent_name"),
|
||||
"star_system": location.get("star_system_name"),
|
||||
"game_version": entry.get("game_version"),
|
||||
"date_updated": entry.get("date_updated"),
|
||||
"uex_link": entry.get("uex_link"),
|
||||
}
|
||||
)
|
||||
return {
|
||||
"name": vehicle.get("name") or vehicle.get("game_name"),
|
||||
"game_name": vehicle.get("game_name"),
|
||||
"slug": vehicle.get("slug"),
|
||||
"manufacturer": (vehicle.get("manufacturer") or {}).get("name"),
|
||||
"career": vehicle.get("career"),
|
||||
"role": vehicle.get("role"),
|
||||
"size_class": vehicle.get("size_class"),
|
||||
"cargo_capacity": vehicle.get("cargo_capacity"),
|
||||
"crew": vehicle.get("crew"),
|
||||
"msrp": vehicle.get("msrp"),
|
||||
"pledge_url": vehicle.get("pledge_url"),
|
||||
"purchase_locations": purchases,
|
||||
"description": ((vehicle.get("description") or {}).get("en_EN") or (vehicle.get("game_description") or {}).get("en_EN")),
|
||||
"web_url": vehicle.get("web_url"),
|
||||
"updated_at": vehicle.get("updated_at"),
|
||||
"version": vehicle.get("version"),
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def _summarize_negotiation(cls, negotiation: dict[str, Any]) -> dict[str, Any]:
|
||||
summary = cls._project_item(negotiation, mode="summary")
|
||||
|
||||
Reference in New Issue
Block a user