feat: chat

This commit is contained in:
2026-06-09 11:24:15 -04:00
parent 454bb57484
commit 8fac3d2bae
15 changed files with 2015 additions and 38 deletions
+166
View File
@@ -8,6 +8,7 @@ from typing import Any, Awaitable, Callable
from traderai.cornerstone_client import CornerstoneClient, parse_cornerstone_item_page
from traderai.memory import MemoryStore
from traderai.negotiations import UEX_NEGOTIATION_CLOSE_ENDPOINT
from traderai.scheduler import WakeScheduler
from traderai.scmdb_client import SCMDBClient
from traderai.starcitizen_wiki_client import StarCitizenWikiClient
@@ -150,6 +151,7 @@ UEX_RESOURCE_DESCRIPTIONS = {
UEX_PRODUCTION_WRITE_RESOURCES = {
"marketplace_advertise",
"marketplace_negotiations_messages",
UEX_NEGOTIATION_CLOSE_ENDPOINT,
}
@@ -176,6 +178,7 @@ class ToolRegistry:
wikelo: WikeloProjectsClient | None = None,
plan_store: Any | None = None,
plan_runner: Any | None = None,
negotiation_sync: Any | None = None,
) -> None:
self.uex = uex
self.scmdb = scmdb or SCMDBClient()
@@ -187,6 +190,7 @@ class ToolRegistry:
self.scheduler = scheduler
self.plan_store = plan_store
self.plan_runner = plan_runner
self.negotiation_sync = negotiation_sync
self.pending_actions: dict[str, PendingAction] = {}
self._chat_images_var: ContextVar[list[dict[str, Any]]] = ContextVar("chat_images", default=[])
self.handlers: dict[str, ToolHandler] = {
@@ -196,6 +200,11 @@ class ToolRegistry:
"list_marketplace_negotiations": self.list_marketplace_negotiations,
"get_negotiation_messages": self.get_negotiation_messages,
"draft_negotiation_message": self.draft_negotiation_message,
"list_local_negotiations": self.list_local_negotiations,
"get_local_negotiation": self.get_local_negotiation,
"search_local_negotiation_messages": self.search_local_negotiation_messages,
"draft_negotiation_close": self.draft_negotiation_close,
"draft_negotiation_rating": self.draft_negotiation_rating,
"draft_marketplace_listing": self.draft_marketplace_listing,
"remember_user_fact": self.remember_user_fact,
"recall_memory": self.recall_memory,
@@ -354,6 +363,97 @@ class ToolRegistry:
},
},
},
{
"type": "function",
"function": {
"name": "list_local_negotiations",
"description": "List locally synced UEX negotiations with unread and status details.",
"parameters": {
"type": "object",
"properties": {
"status": {"type": "string", "enum": ["all", "open", "closed"]},
"unread_only": {"type": "boolean"},
"search": {"type": "string"},
"limit": {"type": "integer", "minimum": 1, "maximum": 50},
},
},
},
},
{
"type": "function",
"function": {
"name": "get_local_negotiation",
"description": "Get a locally synced UEX negotiation with compact metadata and recent messages.",
"parameters": {
"type": "object",
"properties": {
"hash": {"type": "string"},
},
"required": ["hash"],
},
},
},
{
"type": "function",
"function": {
"name": "search_local_negotiation_messages",
"description": "Search locally cached negotiation message text so the assistant can reference prior UEX conversations without re-fetching them.",
"parameters": {
"type": "object",
"properties": {
"query": {"type": "string"},
"limit": {"type": "integer", "minimum": 1, "maximum": 20},
},
"required": ["query"],
},
},
},
{
"type": "function",
"function": {
"name": "draft_negotiation_close",
"description": "Draft closing or rating a UEX negotiation. This creates a pending action that must be approved before sending.",
"parameters": {
"type": "object",
"properties": {
"hash": {"type": "string"},
"id_negotiation": {"type": "integer"},
"deal_closed": {"type": "boolean"},
"deal_value": {"type": "number"},
"currency": {"type": "string"},
"clarity_rating": {"type": "integer", "minimum": 1, "maximum": 5},
"speed_rating": {"type": "integer", "minimum": 1, "maximum": 5},
"respect_rating": {"type": "integer", "minimum": 1, "maximum": 5},
"fairness_rating": {"type": "integer", "minimum": 1, "maximum": 5},
"comment": {"type": "string"},
},
"required": ["deal_closed"],
},
},
},
{
"type": "function",
"function": {
"name": "draft_negotiation_rating",
"description": "Alias for drafting a UEX negotiation close/rating action.",
"parameters": {
"type": "object",
"properties": {
"hash": {"type": "string"},
"id_negotiation": {"type": "integer"},
"deal_closed": {"type": "boolean"},
"deal_value": {"type": "number"},
"currency": {"type": "string"},
"clarity_rating": {"type": "integer", "minimum": 1, "maximum": 5},
"speed_rating": {"type": "integer", "minimum": 1, "maximum": 5},
"respect_rating": {"type": "integer", "minimum": 1, "maximum": 5},
"fairness_rating": {"type": "integer", "minimum": 1, "maximum": 5},
"comment": {"type": "string"},
},
"required": ["deal_closed"],
},
},
},
{
"type": "function",
"function": {
@@ -1418,6 +1518,37 @@ class ToolRegistry:
async def get_negotiation_messages(self, hash: str | None = None, id_negotiation: int | None = None) -> dict[str, Any]:
return await self.uex.get("marketplace_negotiations_messages", {"hash": hash, "id_negotiation": id_negotiation}, authenticated=True)
async def list_local_negotiations(
self,
status: str = "all",
unread_only: bool = False,
search: str = "",
limit: int = 10,
) -> dict[str, Any]:
if self.negotiation_sync is None:
return {"error": "Negotiation sync is not configured."}
negotiations = self.negotiation_sync.list_negotiations(
status=status,
unread_only=unread_only,
search=search,
limit=limit,
)
return {"count": len(negotiations), "negotiations": negotiations}
async def get_local_negotiation(self, hash: str) -> dict[str, Any]:
if self.negotiation_sync is None:
return {"error": "Negotiation sync is not configured."}
negotiation = self.negotiation_sync.get_negotiation(hash, mark_read=False)
if not negotiation:
return {"error": f"Negotiation not found: {hash}"}
return {"negotiation": negotiation}
async def search_local_negotiation_messages(self, query: str, limit: int = 8) -> dict[str, Any]:
if self.negotiation_sync is None:
return {"error": "Negotiation sync is not configured."}
matches = self.negotiation_sync.search_messages(query, limit=limit)
return {"count": len(matches), "matches": matches}
async def draft_negotiation_message(
self,
message: str,
@@ -1453,6 +1584,41 @@ class ToolRegistry:
metadata=attached_image.get("metadata"),
)
async def draft_negotiation_close(
self,
deal_closed: bool,
hash: str | None = None,
id_negotiation: int | None = None,
deal_value: float | None = None,
currency: str | None = None,
clarity_rating: int | None = None,
speed_rating: int | None = None,
respect_rating: int | None = None,
fairness_rating: int | None = None,
comment: str | None = None,
) -> dict[str, Any]:
payload = {
"hash": hash,
"id_negotiation": id_negotiation,
"deal_closed": 1 if deal_closed else 0,
"deal_value": deal_value,
"currency": currency,
"clarity_rating": clarity_rating,
"speed_rating": speed_rating,
"respect_rating": respect_rating,
"fairness_rating": fairness_rating,
"comment": comment,
}
metadata = {
"hash": hash,
"id_negotiation": id_negotiation,
"kind": "negotiation_close",
}
return self._pending("Close negotiation", UEX_NEGOTIATION_CLOSE_ENDPOINT, payload, metadata=metadata)
async def draft_negotiation_rating(self, **payload: Any) -> dict[str, Any]:
return await self.draft_negotiation_close(**payload)
async def draft_marketplace_listing_with_cornerstone_image(
self,
item_query: str,