versioning: 0.0.4, feat: create listing, source image
Build Release EXE / build-windows-exe (release) Successful in 52s
Build Release EXE / build-windows-exe (release) Successful in 52s
This commit is contained in:
+156
-4
@@ -196,6 +196,8 @@ class ToolRegistry:
|
||||
"get_scmdb_mission_rewards": self.get_scmdb_mission_rewards,
|
||||
"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,
|
||||
"draft_marketplace_listing_with_cornerstone_image": self.draft_marketplace_listing_with_cornerstone_image,
|
||||
}
|
||||
self.handlers["uex_api_catalog"] = self.uex_api_catalog
|
||||
self.handlers["uex_get"] = self.uex_get
|
||||
@@ -311,7 +313,7 @@ class ToolRegistry:
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "draft_marketplace_listing",
|
||||
"description": "Draft a new UEX marketplace listing. Listing prices are in-game aUEC/UEC credits, not real-world dollars. This creates a pending action that must be approved before posting.",
|
||||
"description": "Draft a new UEX marketplace listing. Listing prices are in-game aUEC/UEC credits, not real-world dollars. This creates a pending action that must be approved before posting. Prefer draft_marketplace_listing_with_cornerstone_image for item posts when a Cornerstone image is useful.",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"required": ["id_category", "operation", "type", "unit", "title", "description", "price", "currency", "language"],
|
||||
@@ -332,8 +334,12 @@ class ToolRegistry:
|
||||
"source": {"type": "string"},
|
||||
"availability": {"type": "string"},
|
||||
"in_stock": {"type": "integer"},
|
||||
"durability": {"type": "integer", "minimum": 0, "maximum": 100},
|
||||
"video_url": {"type": "string"},
|
||||
"image_data": {"type": "string", "description": "Base64 JPG or PNG image data for UEX upload."},
|
||||
"hours_expiration": {"type": "integer"},
|
||||
"is_hidden": {"type": "integer", "enum": [0, 1]},
|
||||
"is_tv_allowed": {"type": "integer", "enum": [0, 1]},
|
||||
"is_production": {"type": "integer", "enum": [0, 1], "default": 1},
|
||||
},
|
||||
},
|
||||
@@ -512,7 +518,7 @@ class ToolRegistry:
|
||||
"label": action.label,
|
||||
"method": action.method,
|
||||
"endpoint": action.endpoint,
|
||||
"payload": action.payload,
|
||||
"payload": self._display_payload(action.payload),
|
||||
"metadata": action.metadata or {},
|
||||
},
|
||||
}
|
||||
@@ -973,6 +979,60 @@ class ToolRegistry:
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "get_cornerstone_item_media",
|
||||
"description": "Fetch Cornerstone item page media, especially image URLs that can be used when drafting UEX marketplace listings.",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {"type": "string", "description": "Cornerstone item id from search_cornerstone_items."},
|
||||
"query": {"type": "string", "description": "Item name if id is not known."},
|
||||
"limit": {"type": "integer", "minimum": 1, "maximum": 10, "default": 5},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "draft_marketplace_listing_with_cornerstone_image",
|
||||
"description": "Draft a UEX marketplace listing and source the listing image from Cornerstone. The image is downloaded as base64 image_data and included in the pending action. Nothing is posted until user approval.",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"required": ["item_query", "id_category", "operation", "type", "unit", "title", "description", "price", "currency", "language"],
|
||||
"properties": {
|
||||
"item_query": {"type": "string", "description": "Cornerstone item name to source an image from."},
|
||||
"cornerstone_id": {"type": "string", "description": "Cornerstone item id, if already known."},
|
||||
"id_item": {"type": "integer"},
|
||||
"id_star_system": {"type": "integer"},
|
||||
"id_terminal": {"type": "integer"},
|
||||
"id_organization": {"type": "integer"},
|
||||
"id_category": {"type": "integer"},
|
||||
"operation": {"type": "string", "enum": ["buy", "sell", "rent", "trade"]},
|
||||
"type": {"type": "string", "enum": ["item", "service", "contract"]},
|
||||
"unit": {"type": "string"},
|
||||
"title": {"type": "string"},
|
||||
"description": {"type": "string"},
|
||||
"price": {"type": "number"},
|
||||
"currency": {"type": "string", "enum": ["UEC"]},
|
||||
"language": {"type": "string", "default": "en_US"},
|
||||
"location": {"type": "string"},
|
||||
"source": {"type": "string", "enum": ["looted", "pledged", "purchased_in_game", "pirated", "gifted"]},
|
||||
"availability": {"type": "string"},
|
||||
"in_stock": {"type": "integer"},
|
||||
"durability": {"type": "integer", "minimum": 0, "maximum": 100},
|
||||
"video_url": {"type": "string"},
|
||||
"hours_expiration": {"type": "integer"},
|
||||
"is_hidden": {"type": "integer", "enum": [0, 1]},
|
||||
"is_tv_allowed": {"type": "integer", "enum": [0, 1]},
|
||||
"is_production": {"type": "integer", "enum": [0, 1], "default": 1},
|
||||
"require_image": {"type": "boolean", "default": False, "description": "Return an error instead of drafting if no Cornerstone JPG/PNG image can be sourced."},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
@classmethod
|
||||
@@ -1167,6 +1227,55 @@ class ToolRegistry:
|
||||
async def draft_marketplace_listing(self, **payload: Any) -> dict[str, Any]:
|
||||
return self._pending("Post marketplace listing", "marketplace_advertise", payload)
|
||||
|
||||
async def draft_marketplace_listing_with_cornerstone_image(
|
||||
self,
|
||||
item_query: str,
|
||||
cornerstone_id: str | None = None,
|
||||
**payload: Any,
|
||||
) -> dict[str, Any]:
|
||||
require_image = bool(payload.pop("require_image", False))
|
||||
item = await self._resolve_cornerstone_item(id=cornerstone_id, query=item_query)
|
||||
if not item:
|
||||
return {"error": "No Cornerstone item matched. Provide cornerstone_id or a more specific item_query."}
|
||||
|
||||
page = await self.cornerstone.get_item_page(str(item["id"]))
|
||||
parsed = parse_cornerstone_item_page(page["html"], page["url"])
|
||||
media = parsed.get("media") or []
|
||||
image_result: dict[str, Any] | None = None
|
||||
image_error = ""
|
||||
for media_item in media:
|
||||
try:
|
||||
image_result = await self.cornerstone.get_image_data(media_item["url"])
|
||||
break
|
||||
except Exception as exc:
|
||||
image_error = str(exc)
|
||||
|
||||
if image_result:
|
||||
payload["image_data"] = image_result["image_data"]
|
||||
elif require_image:
|
||||
return {
|
||||
"error": "Cornerstone item matched, but no usable JPG/PNG image could be sourced.",
|
||||
"cornerstone": {
|
||||
"item": {"id": item.get("id"), "name": parsed.get("name") or item.get("name")},
|
||||
"url": page["url"],
|
||||
"media": media,
|
||||
"image_error": image_error,
|
||||
},
|
||||
}
|
||||
|
||||
payload.setdefault("id_item", self._int_or_none(item.get("id")))
|
||||
metadata = {
|
||||
"cornerstone_item_id": item.get("id"),
|
||||
"cornerstone_item_name": parsed.get("name") or item.get("name"),
|
||||
"cornerstone_url": page["url"],
|
||||
"cornerstone_image_url": image_result.get("url") if image_result else None,
|
||||
"cornerstone_image_content_type": image_result.get("content_type") if image_result else None,
|
||||
"cornerstone_image_size_bytes": image_result.get("size_bytes") if image_result else None,
|
||||
"cornerstone_image_status": "included" if image_result else "not_found",
|
||||
"cornerstone_image_error": image_error or None,
|
||||
}
|
||||
return self._pending("Post marketplace listing with Cornerstone image", "marketplace_advertise", payload, metadata=metadata)
|
||||
|
||||
async def remember_user_fact(self, content: str, kind: str = "note", importance: int = 3) -> dict[str, Any]:
|
||||
if self.memory is None:
|
||||
return {"error": "Memory store is not configured."}
|
||||
@@ -1430,7 +1539,7 @@ class ToolRegistry:
|
||||
return {"error": "No Cornerstone item matched. Provide an id or a more specific query."}
|
||||
|
||||
page = await self.cornerstone.get_item_page(str(item["id"]))
|
||||
parsed = parse_cornerstone_item_page(page["html"])
|
||||
parsed = parse_cornerstone_item_page(page["html"], page["url"])
|
||||
locations = parsed.get("locations") or []
|
||||
location_filter = (location or "").casefold().strip()
|
||||
if location_filter:
|
||||
@@ -1455,6 +1564,34 @@ class ToolRegistry:
|
||||
"locations": locations[:limit],
|
||||
}
|
||||
|
||||
async def get_cornerstone_item_media(
|
||||
self,
|
||||
id: str | None = None,
|
||||
query: str | None = None,
|
||||
limit: int = 5,
|
||||
) -> dict[str, Any]:
|
||||
item = await self._resolve_cornerstone_item(id=id, query=query)
|
||||
if not item:
|
||||
return {"error": "No Cornerstone item matched. Provide an id or a more specific query."}
|
||||
|
||||
page = await self.cornerstone.get_item_page(str(item["id"]))
|
||||
parsed = parse_cornerstone_item_page(page["html"], page["url"])
|
||||
media = parsed.get("media") or []
|
||||
limit = max(1, min(limit, 10))
|
||||
return {
|
||||
"source": self.cornerstone.base_url,
|
||||
"url": page["url"],
|
||||
"item": {
|
||||
"id": item.get("id"),
|
||||
"name": parsed.get("name") or item.get("name"),
|
||||
"sold": bool(item.get("sold")),
|
||||
"general": parsed.get("general") or {},
|
||||
},
|
||||
"returned": min(len(media), limit),
|
||||
"truncated": len(media) > limit,
|
||||
"media": media[:limit],
|
||||
}
|
||||
|
||||
def _pending(
|
||||
self,
|
||||
label: str,
|
||||
@@ -1474,12 +1611,27 @@ class ToolRegistry:
|
||||
"label": label,
|
||||
"method": method,
|
||||
"endpoint": endpoint,
|
||||
"payload": payload,
|
||||
"payload": self._display_payload(payload),
|
||||
"metadata": metadata,
|
||||
"approval_required": self.require_write_approval,
|
||||
}
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def _display_payload(payload: dict[str, Any]) -> dict[str, Any]:
|
||||
display = dict(payload)
|
||||
image_data = display.get("image_data")
|
||||
if isinstance(image_data, str) and image_data:
|
||||
display["image_data"] = f"<base64 image data redacted; {len(image_data)} characters>"
|
||||
return display
|
||||
|
||||
@staticmethod
|
||||
def _int_or_none(value: Any) -> int | None:
|
||||
try:
|
||||
return int(value)
|
||||
except (TypeError, ValueError):
|
||||
return None
|
||||
|
||||
def _record_pending_action_result(self, action: PendingAction, result_kind: str, result: dict[str, Any]) -> None:
|
||||
metadata = action.metadata or {}
|
||||
plan_id = metadata.get("plan_id")
|
||||
|
||||
Reference in New Issue
Block a user