This commit is contained in:
@@ -12,6 +12,7 @@ from traderai.scheduler import WakeScheduler
|
||||
from traderai.scmdb_client import SCMDBClient
|
||||
from traderai.starcitizen_wiki_client import StarCitizenWikiClient
|
||||
from traderai.uex_client import UEXClient
|
||||
from traderai.wikelo_projects_client import WikeloProjectsClient
|
||||
|
||||
|
||||
ToolHandler = Callable[..., Awaitable[dict[str, Any]]]
|
||||
@@ -172,6 +173,7 @@ class ToolRegistry:
|
||||
scmdb: SCMDBClient | None = None,
|
||||
cornerstone: CornerstoneClient | None = None,
|
||||
scwiki: StarCitizenWikiClient | None = None,
|
||||
wikelo: WikeloProjectsClient | None = None,
|
||||
plan_store: Any | None = None,
|
||||
plan_runner: Any | None = None,
|
||||
) -> None:
|
||||
@@ -179,6 +181,7 @@ class ToolRegistry:
|
||||
self.scmdb = scmdb or SCMDBClient()
|
||||
self.cornerstone = cornerstone or CornerstoneClient()
|
||||
self.scwiki = scwiki or StarCitizenWikiClient()
|
||||
self.wikelo = wikelo or WikeloProjectsClient()
|
||||
self.require_write_approval = require_write_approval
|
||||
self.memory = memory
|
||||
self.scheduler = scheduler
|
||||
@@ -214,6 +217,8 @@ class ToolRegistry:
|
||||
"get_scwiki_page": self.get_scwiki_page,
|
||||
"search_scwiki_vehicles": self.search_scwiki_vehicles,
|
||||
"get_scwiki_vehicle": self.get_scwiki_vehicle,
|
||||
"search_wikelo_ship_projects": self.search_wikelo_ship_projects,
|
||||
"get_wikelo_ship_project": self.get_wikelo_ship_project,
|
||||
"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,
|
||||
@@ -244,6 +249,7 @@ class ToolRegistry:
|
||||
*self._uex_delete_schemas(),
|
||||
*self._scmdb_schemas(),
|
||||
*self._scwiki_schemas(),
|
||||
*self._wikelo_schemas(),
|
||||
*self._cornerstone_schemas(),
|
||||
{
|
||||
"type": "function",
|
||||
@@ -1071,6 +1077,39 @@ class ToolRegistry:
|
||||
},
|
||||
]
|
||||
|
||||
@classmethod
|
||||
def _wikelo_schemas(cls) -> list[dict[str, Any]]:
|
||||
return [
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "search_wikelo_ship_projects",
|
||||
"description": "Search Wikelo ship projects and their required materials from wikelo-projects.com. Use this when the user asks for Wikelo ship requirements or build materials.",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"query": {"type": "string", "description": "Ship or project name to search for, such as Polaris, Idris, Zeus, or Guardian."},
|
||||
"limit": {"type": "integer", "minimum": 1, "maximum": 10, "default": 5},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "get_wikelo_ship_project",
|
||||
"description": "Fetch one Wikelo ship project with its required materials and contribution progress.",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"project_id": {"type": "string", "description": "Wikelo ship project id."},
|
||||
"ship_name": {"type": "string", "description": "Ship or project name if the project id is not known."},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
@classmethod
|
||||
def _cornerstone_schemas(cls) -> list[dict[str, Any]]:
|
||||
return [
|
||||
@@ -1740,6 +1779,51 @@ class ToolRegistry:
|
||||
vehicle = await self.scwiki.get_vehicle(resolved_slug)
|
||||
return {"source": self.scwiki.api_base_url, "vehicle": self._summarize_scwiki_vehicle(vehicle)}
|
||||
|
||||
async def search_wikelo_ship_projects(self, query: str, limit: int = 5) -> dict[str, Any]:
|
||||
projects = await self.wikelo.list_ship_projects()
|
||||
q = (query or "").casefold().strip()
|
||||
matches = []
|
||||
for project in projects:
|
||||
score = self._wikelo_ship_match_score(q, project)
|
||||
if q and score <= 0:
|
||||
continue
|
||||
matches.append((score, project))
|
||||
matches.sort(
|
||||
key=lambda match: (
|
||||
-match[0],
|
||||
str(match[1].get("ship_name") or "").casefold(),
|
||||
str(match[1].get("id") or ""),
|
||||
)
|
||||
)
|
||||
limit = max(1, min(limit, 10))
|
||||
return {
|
||||
"source": f"{self.wikelo.base_url}/Ships",
|
||||
"query": query,
|
||||
"matched": len(matches),
|
||||
"projects": [self._summarize_wikelo_ship_project(item) for _, item in matches[:limit]],
|
||||
}
|
||||
|
||||
async def get_wikelo_ship_project(self, project_id: str | None = None, ship_name: str | None = None) -> dict[str, Any]:
|
||||
projects = await self.wikelo.list_ship_projects()
|
||||
if project_id:
|
||||
for project in projects:
|
||||
if str(project.get("id") or "").strip() == str(project_id).strip():
|
||||
return {"source": f"{self.wikelo.base_url}/Ships", "project": self._summarize_wikelo_ship_project(project, detailed=True)}
|
||||
return {"error": "No Wikelo ship project matched that id."}
|
||||
|
||||
if not ship_name:
|
||||
return {"error": "Provide project_id or ship_name."}
|
||||
|
||||
ranked = [
|
||||
(self._wikelo_ship_match_score(ship_name.casefold().strip(), project), project)
|
||||
for project in projects
|
||||
]
|
||||
ranked = [match for match in ranked if match[0] > 0]
|
||||
ranked.sort(key=lambda match: (-match[0], str(match[1].get("ship_name") or "").casefold()))
|
||||
if not ranked:
|
||||
return {"error": "No Wikelo ship project matched."}
|
||||
return {"source": f"{self.wikelo.base_url}/Ships", "project": self._summarize_wikelo_ship_project(ranked[0][1], detailed=True)}
|
||||
|
||||
async def search_cornerstone_items(
|
||||
self,
|
||||
query: str = "",
|
||||
@@ -2492,6 +2576,62 @@ class ToolRegistry:
|
||||
"version": vehicle.get("version"),
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def _wikelo_ship_match_score(query: str, project: dict[str, Any]) -> int:
|
||||
if not query:
|
||||
return 1
|
||||
ship_name = str(project.get("ship_name") or "").casefold()
|
||||
description = str(project.get("description") or "").casefold()
|
||||
materials = " ".join(
|
||||
str(item.get("material_name") or "").casefold()
|
||||
for item in (project.get("required_materials") or [])
|
||||
if isinstance(item, dict)
|
||||
)
|
||||
haystack = " ".join(part for part in [ship_name, description, materials] if part)
|
||||
if ship_name == query:
|
||||
return 10000
|
||||
if query in ship_name:
|
||||
return 9000 - ship_name.index(query)
|
||||
if query in description:
|
||||
return 7000 - description.index(query)
|
||||
if query in materials:
|
||||
return 5000 - materials.index(query)
|
||||
tokens = [token for token in query.split() if token]
|
||||
if tokens and all(token in haystack for token in tokens):
|
||||
return 3000 - len(haystack)
|
||||
return 0
|
||||
|
||||
@classmethod
|
||||
def _summarize_wikelo_ship_project(cls, project: dict[str, Any], detailed: bool = False) -> dict[str, Any]:
|
||||
materials = []
|
||||
for item in (project.get("required_materials") or []):
|
||||
if not isinstance(item, dict):
|
||||
continue
|
||||
quantity_needed = item.get("quantity_needed")
|
||||
quantity_collected = item.get("quantity_collected")
|
||||
materials.append(
|
||||
{
|
||||
"material_name": item.get("material_name"),
|
||||
"quantity_needed": int(quantity_needed) if isinstance(quantity_needed, (int, float)) and float(quantity_needed).is_integer() else quantity_needed,
|
||||
"quantity_collected": int(quantity_collected) if isinstance(quantity_collected, (int, float)) and float(quantity_collected).is_integer() else quantity_collected,
|
||||
}
|
||||
)
|
||||
summary = {
|
||||
"id": project.get("id"),
|
||||
"ship_name": project.get("ship_name"),
|
||||
"description": project.get("description"),
|
||||
"status": project.get("status"),
|
||||
"privacy": project.get("privacy"),
|
||||
"owner_name": project.get("owner_name"),
|
||||
"org_name": project.get("org_name"),
|
||||
"home_port": project.get("home_port"),
|
||||
"ship_image": project.get("ship_image"),
|
||||
"materials_count": len(materials),
|
||||
"required_materials": materials if detailed else materials[:12],
|
||||
"source_url": f"https://wikelo-projects.com/Ships",
|
||||
}
|
||||
return {key: value for key, value in summary.items() if value not in (None, "", [], {})}
|
||||
|
||||
@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