115 lines
4 KiB
Python
115 lines
4 KiB
Python
|
|
from __future__ import annotations
|
||
|
|
|
||
|
|
from datetime import datetime, timezone
|
||
|
|
|
||
|
|
from fastapi import APIRouter, Depends, Request
|
||
|
|
from fastapi.responses import JSONResponse, StreamingResponse
|
||
|
|
|
||
|
|
from app_dependencies import get_runtime
|
||
|
|
from message_pipeline import (
|
||
|
|
context_label_from_message_metadata,
|
||
|
|
encode_sse_data,
|
||
|
|
typed_message_from_api_notification,
|
||
|
|
)
|
||
|
|
from nanobot_api_client import stream_nanobot_agent_turn
|
||
|
|
from route_helpers import read_json_request
|
||
|
|
from session_store import normalize_session_chat_id
|
||
|
|
from web_runtime import WebAppRuntime
|
||
|
|
|
||
|
|
router = APIRouter()
|
||
|
|
|
||
|
|
|
||
|
|
@router.post("/message")
|
||
|
|
async def post_message(
|
||
|
|
request: Request,
|
||
|
|
runtime: WebAppRuntime = Depends(get_runtime),
|
||
|
|
) -> JSONResponse:
|
||
|
|
try:
|
||
|
|
payload = await read_json_request(request)
|
||
|
|
except ValueError as exc:
|
||
|
|
return JSONResponse({"error": str(exc)}, status_code=400)
|
||
|
|
text = str(payload.get("text", "")).strip()
|
||
|
|
metadata = payload.get("metadata", {})
|
||
|
|
chat_id = normalize_session_chat_id(str(payload.get("chat_id", "web") or "web"))
|
||
|
|
if not text:
|
||
|
|
return JSONResponse({"error": "empty message"}, status_code=400)
|
||
|
|
if not chat_id:
|
||
|
|
return JSONResponse({"error": "invalid chat id"}, status_code=400)
|
||
|
|
if not isinstance(metadata, dict):
|
||
|
|
metadata = {}
|
||
|
|
try:
|
||
|
|
await runtime.gateway.send_user_message(text, metadata=metadata, chat_id=chat_id)
|
||
|
|
except RuntimeError as exc:
|
||
|
|
return JSONResponse({"error": str(exc)}, status_code=503)
|
||
|
|
return JSONResponse({"status": "ok"})
|
||
|
|
|
||
|
|
|
||
|
|
@router.post("/message/stream")
|
||
|
|
async def stream_message(
|
||
|
|
request: Request,
|
||
|
|
runtime: WebAppRuntime = Depends(get_runtime),
|
||
|
|
):
|
||
|
|
try:
|
||
|
|
payload = await read_json_request(request)
|
||
|
|
except ValueError as exc:
|
||
|
|
return JSONResponse({"error": str(exc)}, status_code=400)
|
||
|
|
|
||
|
|
text = str(payload.get("text", "")).strip()
|
||
|
|
metadata = payload.get("metadata", {})
|
||
|
|
chat_id = normalize_session_chat_id(str(payload.get("chat_id", "web") or "web"))
|
||
|
|
if not text:
|
||
|
|
return JSONResponse({"error": "empty message"}, status_code=400)
|
||
|
|
if not chat_id:
|
||
|
|
return JSONResponse({"error": "invalid chat id"}, status_code=400)
|
||
|
|
if not isinstance(metadata, dict):
|
||
|
|
metadata = {}
|
||
|
|
context_label = context_label_from_message_metadata(metadata)
|
||
|
|
|
||
|
|
async def stream_turn():
|
||
|
|
try:
|
||
|
|
yield ": stream-open\n\n"
|
||
|
|
yield encode_sse_data(
|
||
|
|
{
|
||
|
|
"type": "message",
|
||
|
|
"role": "user",
|
||
|
|
"content": text,
|
||
|
|
"is_progress": False,
|
||
|
|
"is_tool_hint": False,
|
||
|
|
"timestamp": datetime.now(timezone.utc).isoformat(),
|
||
|
|
"context_label": context_label or None,
|
||
|
|
}
|
||
|
|
)
|
||
|
|
|
||
|
|
async for typed_event in stream_nanobot_agent_turn(
|
||
|
|
text,
|
||
|
|
chat_id=chat_id,
|
||
|
|
metadata=metadata,
|
||
|
|
timeout_seconds=60.0,
|
||
|
|
notification_to_event=lambda obj: typed_message_from_api_notification(
|
||
|
|
obj,
|
||
|
|
default_chat_id=chat_id,
|
||
|
|
),
|
||
|
|
):
|
||
|
|
if typed_event.get("type") == "card":
|
||
|
|
await runtime.publish_cards_changed(
|
||
|
|
typed_event.get("chat_id") if isinstance(typed_event, dict) else chat_id
|
||
|
|
)
|
||
|
|
elif typed_event.get("type") == "workbench":
|
||
|
|
await runtime.publish_workbench_changed(
|
||
|
|
typed_event.get("chat_id") if isinstance(typed_event, dict) else chat_id
|
||
|
|
)
|
||
|
|
yield encode_sse_data(typed_event)
|
||
|
|
await runtime.publish_sessions_changed()
|
||
|
|
except RuntimeError as exc:
|
||
|
|
yield encode_sse_data({"type": "error", "error": str(exc)})
|
||
|
|
|
||
|
|
return StreamingResponse(
|
||
|
|
stream_turn(),
|
||
|
|
media_type="text/event-stream",
|
||
|
|
headers={
|
||
|
|
"Cache-Control": "no-cache",
|
||
|
|
"Connection": "keep-alive",
|
||
|
|
"X-Accel-Buffering": "no",
|
||
|
|
},
|
||
|
|
)
|