feat: chat
This commit is contained in:
+159
-12
@@ -24,6 +24,7 @@ from traderai.config import save_settings, settings_payload
|
||||
from traderai.config import get_settings
|
||||
from traderai.cornerstone_client import CornerstoneClient
|
||||
from traderai.memory import DEFAULT_THREAD_ID, MemoryStore
|
||||
from traderai.negotiations import NegotiationSyncService
|
||||
from traderai.plans import ContinualPlanRunner, ContinualPlanStore
|
||||
from traderai.scheduler import WakeScheduler
|
||||
from traderai.scmdb_client import SCMDBClient
|
||||
@@ -63,6 +64,21 @@ class DirectNegotiationMessageRequest(BaseModel):
|
||||
message: str
|
||||
|
||||
|
||||
class NegotiationDraftMessageRequest(BaseModel):
|
||||
message: str
|
||||
|
||||
|
||||
class NegotiationCloseRequest(BaseModel):
|
||||
deal_closed: bool
|
||||
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
|
||||
|
||||
|
||||
class ClearMemoryRequest(BaseModel):
|
||||
include_memories: bool = True
|
||||
include_conversations: bool = True
|
||||
@@ -120,22 +136,42 @@ def create_app() -> FastAPI:
|
||||
runtime: dict[str, Any] = {}
|
||||
|
||||
def configure_runtime(current_settings: Any) -> None:
|
||||
uex = UEXClient(current_settings.uex_base_url, current_settings.uex_secret_key, current_settings.uex_bearer_token)
|
||||
uex = UEXClient(
|
||||
current_settings.uex_base_url,
|
||||
current_settings.uex_secret_key,
|
||||
current_settings.uex_bearer_token,
|
||||
negotiation_close_endpoint=current_settings.uex_negotiation_close_endpoint,
|
||||
)
|
||||
negotiation_sync = NegotiationSyncService(memory, uex)
|
||||
scmdb = SCMDBClient(current_settings.scmdb_base_url)
|
||||
cornerstone = CornerstoneClient(current_settings.cornerstone_base_url)
|
||||
scwiki = StarCitizenWikiClient(current_settings.scwiki_base_url, current_settings.scwiki_api_base_url)
|
||||
wikelo = WikeloProjectsClient()
|
||||
tools = ToolRegistry(
|
||||
uex,
|
||||
current_settings.require_write_approval,
|
||||
memory=memory,
|
||||
scheduler=scheduler,
|
||||
scmdb=scmdb,
|
||||
cornerstone=cornerstone,
|
||||
scwiki=scwiki,
|
||||
wikelo=wikelo,
|
||||
plan_store=plan_store,
|
||||
)
|
||||
try:
|
||||
tools = ToolRegistry(
|
||||
uex,
|
||||
current_settings.require_write_approval,
|
||||
memory=memory,
|
||||
scheduler=scheduler,
|
||||
scmdb=scmdb,
|
||||
cornerstone=cornerstone,
|
||||
scwiki=scwiki,
|
||||
wikelo=wikelo,
|
||||
plan_store=plan_store,
|
||||
negotiation_sync=negotiation_sync,
|
||||
)
|
||||
except TypeError:
|
||||
tools = ToolRegistry(
|
||||
uex,
|
||||
current_settings.require_write_approval,
|
||||
memory=memory,
|
||||
scheduler=scheduler,
|
||||
scmdb=scmdb,
|
||||
cornerstone=cornerstone,
|
||||
scwiki=scwiki,
|
||||
wikelo=wikelo,
|
||||
plan_store=plan_store,
|
||||
)
|
||||
plan_runner = ContinualPlanRunner(plan_store, tools, memory)
|
||||
tools.plan_runner = plan_runner
|
||||
provider_base_url, provider_model, provider_api_key = provider_settings(current_settings)
|
||||
@@ -154,6 +190,8 @@ def create_app() -> FastAPI:
|
||||
scheduler.bind_agent(agent)
|
||||
scheduler.bind_plan_runner(plan_runner)
|
||||
scheduler.bind_uex_notifications(uex, current_settings.uex_notification_poll_seconds)
|
||||
if hasattr(scheduler, "bind_negotiation_sync"):
|
||||
scheduler.bind_negotiation_sync(negotiation_sync)
|
||||
runtime.update(
|
||||
{
|
||||
"settings": current_settings,
|
||||
@@ -161,6 +199,7 @@ def create_app() -> FastAPI:
|
||||
"tools": tools,
|
||||
"plan_runner": plan_runner,
|
||||
"agent": agent,
|
||||
"negotiation_sync": negotiation_sync,
|
||||
}
|
||||
)
|
||||
|
||||
@@ -173,6 +212,10 @@ def create_app() -> FastAPI:
|
||||
@app.on_event("startup")
|
||||
async def startup() -> None:
|
||||
await refresh_user_profile()
|
||||
try:
|
||||
await runtime["negotiation_sync"].startup_sync()
|
||||
except Exception:
|
||||
memory.set_profile("uex_last_negotiation_sync_error", "startup_sync_failed")
|
||||
scheduler.start()
|
||||
|
||||
@app.on_event("shutdown")
|
||||
@@ -468,8 +511,78 @@ def create_app() -> FastAPI:
|
||||
deleted = memory.delete_outbox(inbox_id)
|
||||
return {"deleted": deleted, "inbox": memory.list_outbox()}
|
||||
|
||||
@app.get("/api/negotiations")
|
||||
async def negotiations(status: str = "all", unread_only: bool = False, search: str = "", limit: int = 50) -> dict:
|
||||
negotiation_sync = runtime["negotiation_sync"]
|
||||
return {
|
||||
"negotiations": negotiation_sync.list_negotiations(
|
||||
status=status,
|
||||
unread_only=unread_only,
|
||||
search=search,
|
||||
limit=limit,
|
||||
)
|
||||
}
|
||||
|
||||
@app.get("/api/negotiations/unread-count")
|
||||
async def negotiations_unread_count() -> dict:
|
||||
return {"unread_count": runtime["negotiation_sync"].unread_count()}
|
||||
|
||||
@app.post("/api/negotiations/refresh-all")
|
||||
async def negotiations_refresh_all() -> dict:
|
||||
result = await runtime["negotiation_sync"].refresh_negotiations(seed_open_messages=True)
|
||||
return result
|
||||
|
||||
@app.get("/api/negotiations/{identifier}")
|
||||
async def negotiation_detail(identifier: str) -> dict:
|
||||
negotiation_sync = runtime["negotiation_sync"]
|
||||
negotiation = negotiation_sync.get_negotiation(identifier, mark_read=True)
|
||||
if not negotiation:
|
||||
raise HTTPException(status_code=404, detail="Negotiation not found.")
|
||||
return {"negotiation": negotiation}
|
||||
|
||||
@app.post("/api/negotiations/{identifier}/refresh")
|
||||
async def refresh_negotiation(identifier: str) -> dict:
|
||||
negotiation_sync = runtime["negotiation_sync"]
|
||||
result = await negotiation_sync.refresh_negotiation(identifier, mark_read=False)
|
||||
return {"negotiation": negotiation_sync.get_negotiation(identifier, mark_read=False), "refreshed": result.refreshed}
|
||||
|
||||
@app.post("/api/negotiations/{identifier}/open-chat")
|
||||
async def open_negotiation_chat(identifier: str) -> dict:
|
||||
negotiation_sync = runtime["negotiation_sync"]
|
||||
negotiation = negotiation_sync.get_negotiation(identifier, mark_read=False)
|
||||
if not negotiation:
|
||||
raise HTTPException(status_code=404, detail="Negotiation not found.")
|
||||
thread = memory.create_thread(negotiation.get("title") or f"Negotiation {identifier}")
|
||||
context = {
|
||||
"hash": negotiation.get("hash"),
|
||||
"title": negotiation.get("title"),
|
||||
"counterparty_username": negotiation.get("counterparty_username"),
|
||||
"status": negotiation.get("status"),
|
||||
"unread_count": negotiation.get("unread_count"),
|
||||
"last_message_at": negotiation.get("last_message_at"),
|
||||
"recent_messages": [
|
||||
{
|
||||
"author_username": item.get("author_username"),
|
||||
"is_me": item.get("is_me"),
|
||||
"body": item.get("body"),
|
||||
"sent_at": item.get("sent_at"),
|
||||
}
|
||||
for item in (negotiation.get("messages") or [])[-8:]
|
||||
],
|
||||
}
|
||||
memory.add_conversation(
|
||||
"assistant",
|
||||
"Negotiation context loaded:\n" + json.dumps(context, ensure_ascii=True, indent=2),
|
||||
thread["id"],
|
||||
)
|
||||
return {"chat": thread, "negotiation": negotiation}
|
||||
|
||||
@app.get("/api/negotiations/{identifier}/messages")
|
||||
async def negotiation_messages(identifier: str) -> dict:
|
||||
negotiation_sync = runtime["negotiation_sync"]
|
||||
negotiation = negotiation_sync.get_negotiation(identifier, mark_read=True)
|
||||
if negotiation:
|
||||
return {"messages": negotiation.get("messages", []), "negotiation": negotiation}
|
||||
uex = runtime["uex"]
|
||||
params = negotiation_identifier_params(identifier)
|
||||
return await uex.get("marketplace_negotiations_messages", params, authenticated=True)
|
||||
@@ -481,6 +594,40 @@ def create_app() -> FastAPI:
|
||||
payload = {**params, "message": request.message, "is_production": 1}
|
||||
return await uex.post("marketplace_negotiations_messages", payload, authenticated=True)
|
||||
|
||||
@app.post("/api/negotiations/{identifier}/messages/manual")
|
||||
async def send_negotiation_message_manual(identifier: str, request: DirectNegotiationMessageRequest) -> dict:
|
||||
result = await runtime["negotiation_sync"].manual_send_message(identifier, request.message)
|
||||
return {
|
||||
**result,
|
||||
"message": "Sent",
|
||||
"negotiation": runtime["negotiation_sync"].get_negotiation(identifier, mark_read=True),
|
||||
}
|
||||
|
||||
@app.post("/api/negotiations/{identifier}/messages/draft")
|
||||
async def draft_negotiation_message(identifier: str, request: NegotiationDraftMessageRequest) -> dict:
|
||||
tools = runtime["tools"]
|
||||
result = await tools.draft_negotiation_message(hash=identifier, message=request.message)
|
||||
if result.get("error"):
|
||||
raise HTTPException(status_code=400, detail=result["error"])
|
||||
return result
|
||||
|
||||
@app.post("/api/negotiations/{identifier}/close/draft")
|
||||
async def draft_negotiation_close(identifier: str, request: NegotiationCloseRequest) -> dict:
|
||||
tools = runtime["tools"]
|
||||
result = await tools.draft_negotiation_close(hash=identifier, **request.model_dump())
|
||||
if result.get("error"):
|
||||
raise HTTPException(status_code=400, detail=result["error"])
|
||||
return result
|
||||
|
||||
@app.post("/api/negotiations/{identifier}/close/manual")
|
||||
async def close_negotiation_manual(identifier: str, request: NegotiationCloseRequest) -> dict:
|
||||
result = await runtime["negotiation_sync"].manual_close_negotiation(identifier, request.model_dump())
|
||||
return {
|
||||
**result,
|
||||
"message": "Deal submitted",
|
||||
"negotiation": runtime["negotiation_sync"].get_negotiation(identifier, mark_read=True),
|
||||
}
|
||||
|
||||
@app.get("/api/wake-jobs")
|
||||
async def wake_jobs() -> dict:
|
||||
return {"scheduled_jobs": scheduler.list_jobs()}
|
||||
|
||||
Reference in New Issue
Block a user