versioning: 0.0.6, ux: move buttons, feat: add cloud providers, feat: increese tool call limit
Build Release EXE / build-windows-exe (release) Successful in 51s
Build Release EXE / build-windows-exe (release) Successful in 51s
This commit is contained in:
+98
-13
@@ -1,6 +1,8 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import uuid
|
||||
from contextlib import contextmanager
|
||||
from contextvars import ContextVar
|
||||
from dataclasses import dataclass
|
||||
from typing import Any, Awaitable, Callable
|
||||
|
||||
@@ -172,6 +174,7 @@ class ToolRegistry:
|
||||
self.plan_store = plan_store
|
||||
self.plan_runner = plan_runner
|
||||
self.pending_actions: dict[str, PendingAction] = {}
|
||||
self._chat_images_var: ContextVar[list[dict[str, Any]]] = ContextVar("chat_images", default=[])
|
||||
self.handlers: dict[str, ToolHandler] = {
|
||||
"search_marketplace_listings": self.search_marketplace_listings,
|
||||
"get_marketplace_listing": self.get_marketplace_listing,
|
||||
@@ -334,10 +337,19 @@ 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"},
|
||||
"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."},
|
||||
"use_attached_image": {
|
||||
"type": "boolean",
|
||||
"description": "When true, reuse an image pasted into the current chat as the listing image_data.",
|
||||
},
|
||||
"attached_image_index": {
|
||||
"type": "integer",
|
||||
"minimum": 0,
|
||||
"description": "Zero-based pasted image index to reuse when use_attached_image is true.",
|
||||
},
|
||||
"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},
|
||||
@@ -495,6 +507,14 @@ class ToolRegistry:
|
||||
except Exception as exc:
|
||||
return {"error": str(exc)}
|
||||
|
||||
@contextmanager
|
||||
def chat_image_scope(self, images: list[dict[str, Any]] | None):
|
||||
token = self._chat_images_var.set(self._normalize_chat_images(images))
|
||||
try:
|
||||
yield
|
||||
finally:
|
||||
self._chat_images_var.reset(token)
|
||||
|
||||
async def approve(self, action_id: str) -> dict[str, Any]:
|
||||
action = self.pending_actions.pop(action_id, None)
|
||||
if not action:
|
||||
@@ -1020,11 +1040,21 @@ class ToolRegistry:
|
||||
"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"},
|
||||
"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."},
|
||||
"use_attached_image": {
|
||||
"type": "boolean",
|
||||
"description": "When true, reuse an image pasted into the current chat as the listing image_data instead of sourcing from Cornerstone.",
|
||||
},
|
||||
"attached_image_index": {
|
||||
"type": "integer",
|
||||
"minimum": 0,
|
||||
"description": "Zero-based pasted image index to reuse when use_attached_image is true.",
|
||||
},
|
||||
"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},
|
||||
@@ -1225,7 +1255,15 @@ class ToolRegistry:
|
||||
return self._pending("Send negotiation message", "marketplace_negotiations_messages", payload, metadata=metadata)
|
||||
|
||||
async def draft_marketplace_listing(self, **payload: Any) -> dict[str, Any]:
|
||||
return self._pending("Post marketplace listing", "marketplace_advertise", payload)
|
||||
attached_image = self._attach_chat_image(payload)
|
||||
if attached_image.get("error"):
|
||||
return {"error": attached_image["error"]}
|
||||
return self._pending(
|
||||
"Post marketplace listing",
|
||||
"marketplace_advertise",
|
||||
payload,
|
||||
metadata=attached_image.get("metadata"),
|
||||
)
|
||||
|
||||
async def draft_marketplace_listing_with_cornerstone_image(
|
||||
self,
|
||||
@@ -1234,6 +1272,9 @@ class ToolRegistry:
|
||||
**payload: Any,
|
||||
) -> dict[str, Any]:
|
||||
require_image = bool(payload.pop("require_image", False))
|
||||
attached_image = self._attach_chat_image(payload)
|
||||
if attached_image.get("error"):
|
||||
return {"error": attached_image["error"]}
|
||||
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."}
|
||||
@@ -1250,9 +1291,9 @@ class ToolRegistry:
|
||||
except Exception as exc:
|
||||
image_error = str(exc)
|
||||
|
||||
if image_result:
|
||||
if image_result and not payload.get("image_data"):
|
||||
payload["image_data"] = image_result["image_data"]
|
||||
elif require_image:
|
||||
elif require_image and not payload.get("image_data"):
|
||||
return {
|
||||
"error": "Cornerstone item matched, but no usable JPG/PNG image could be sourced.",
|
||||
"cornerstone": {
|
||||
@@ -1271,9 +1312,11 @@ class ToolRegistry:
|
||||
"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_status": "user_attached" if attached_image.get("metadata") else ("included" if image_result else "not_found"),
|
||||
"cornerstone_image_error": image_error or None,
|
||||
}
|
||||
if attached_image.get("metadata"):
|
||||
metadata.update(attached_image["metadata"])
|
||||
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]:
|
||||
@@ -1625,6 +1668,48 @@ class ToolRegistry:
|
||||
display["image_data"] = f"<base64 image data redacted; {len(image_data)} characters>"
|
||||
return display
|
||||
|
||||
def _attach_chat_image(self, payload: dict[str, Any]) -> dict[str, Any]:
|
||||
attached_index = payload.pop("attached_image_index", None)
|
||||
use_attached_image = bool(payload.pop("use_attached_image", False) or attached_index is not None)
|
||||
if payload.get("image_data") or not use_attached_image:
|
||||
return {}
|
||||
image = self._chat_image(attached_index or 0)
|
||||
if not image:
|
||||
return {"error": "No pasted chat image is available at the requested attached_image_index."}
|
||||
payload["image_data"] = image["image_data"]
|
||||
return {
|
||||
"metadata": {
|
||||
"attached_chat_image_name": image.get("name"),
|
||||
"attached_chat_image_content_type": image.get("content_type"),
|
||||
"attached_chat_image_index": attached_index or 0,
|
||||
"attached_chat_image_status": "included",
|
||||
}
|
||||
}
|
||||
|
||||
def _chat_image(self, index: int) -> dict[str, Any] | None:
|
||||
images = self._chat_images_var.get()
|
||||
if 0 <= index < len(images):
|
||||
return images[index]
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def _normalize_chat_images(images: list[dict[str, Any]] | None) -> list[dict[str, Any]]:
|
||||
normalized: list[dict[str, Any]] = []
|
||||
for image in images or []:
|
||||
if not isinstance(image, dict):
|
||||
continue
|
||||
image_data = str(image.get("image_data") or "").strip()
|
||||
if not image_data:
|
||||
continue
|
||||
normalized.append(
|
||||
{
|
||||
"name": str(image.get("name") or "").strip() or "pasted-image.png",
|
||||
"content_type": str(image.get("content_type") or "image/png").strip() or "image/png",
|
||||
"image_data": image_data,
|
||||
}
|
||||
)
|
||||
return normalized
|
||||
|
||||
@staticmethod
|
||||
def _int_or_none(value: Any) -> int | None:
|
||||
try:
|
||||
|
||||
Reference in New Issue
Block a user