Vocero needs to query AquaPortal on behalf of the caller selected by the existing phone link. This adds server-owned conversations and MCP tool execution for voice and HTTP reasoning while leaving the speech pipeline unchanged. Release notes: - Added browser-bound conversations that keep backend credentials server-side and isolate callers. - Connected the eight-tool AquaPortal catalog through authenticated Streamable HTTP with full input schemas and structured results. - Added bounded multi-step tool execution with cancellation, expiry, and stale-result protection. - Updated browser bootstrap, tool progress, reasoning headers, and conversation cleanup. - Disabled legacy unscoped content preloads in MCP mode and documented configuration and deployment constraints. - Preserved disabled-mode contracts and added local SDK, WebSocket, and browser regression coverage. Reviewed-on: AquaPortal/s2s#1
63 lines
2.7 KiB
Python
63 lines
2.7 KiB
Python
from __future__ import annotations
|
|
|
|
from fastapi import APIRouter, HTTPException, Request
|
|
from fastapi.responses import JSONResponse
|
|
|
|
from vocero_api.api.responses import validated_json_response
|
|
from vocero_api.core import config as settings
|
|
from vocero_api.core import logging as app_logging
|
|
from vocero_api.core import urls as core_urls
|
|
from vocero_api.models.responses import ConfigResponse
|
|
|
|
router = APIRouter()
|
|
|
|
|
|
def _effective_s2s_url(request: Request) -> str:
|
|
return settings.SPEECH_TO_SPEECH_URL or core_urls._proxy_public_url(request)
|
|
|
|
|
|
@router.get("/api/config", response_model=ConfigResponse)
|
|
async def config(request: Request) -> JSONResponse:
|
|
"""Client bootstrap: whether web search is available, whether the deploy runs
|
|
behind a load balancer (so the browser uses the /api/session proxy),
|
|
and whether the user may instead set a direct
|
|
s2s server URL. The LB address itself is intentionally NOT included.
|
|
|
|
2026-09-24: new top-level `apipilot` flag. When True the deploy exposes the
|
|
new `/api/apipilot/tools` and `/api/apipilot/execute` proxy endpoints (see
|
|
docs/client/tool-calling.md) that the browser may call directly instead of
|
|
hardcoding upstream tool URLs / names."""
|
|
app_logging.logger.debug(
|
|
"[INTERNAL] /api/config requested client=%s", request.client.host if request.client else "?"
|
|
)
|
|
primary_prompt = settings.S2S_PRIMARY_PROMPT
|
|
out = {
|
|
"search": bool(settings.SERPER_KEY),
|
|
"lb": bool(settings.LOAD_BALANCER_URL),
|
|
"allowDirect": not settings.LOAD_BALANCER_URL,
|
|
"s2sUrl": _effective_s2s_url(request),
|
|
"primaryPrompt": primary_prompt,
|
|
"reasoningType": settings.S2S_REASONING_TYPE,
|
|
"injectProfile": settings.S2S_INJECT_PROFILE,
|
|
"debugPrompt": settings.S2S_DEBUG_PROMPT,
|
|
"startSessionMode": settings.S2S_START_SESSION_MODE,
|
|
"theme": settings.S2S_THEME,
|
|
"apipilot": bool(settings.S2S_APIPILOT_TOOLS_URL),
|
|
}
|
|
if settings.S2S_MCP_URL:
|
|
if settings.S2S_REASONING_TYPE == "PROXY" and (
|
|
not settings.S2S_PROXY_TARGET or settings.SPEECH_TO_SPEECH_URL or settings.LOAD_BALANCER_URL
|
|
):
|
|
raise HTTPException(503, "MCP en modo PROXY requiere la conexión de voz a través de Vocero.")
|
|
out["mcp"] = True
|
|
out["apipilot"] = False
|
|
app_logging.logger.debug(
|
|
"[INTERNAL] /api/config returned promptLen=%d reasoning=%s startSessionMode=%s theme=%s apipilot=%s",
|
|
len(primary_prompt),
|
|
settings.S2S_REASONING_TYPE,
|
|
settings.S2S_START_SESSION_MODE or "-",
|
|
settings.S2S_THEME or "-",
|
|
out["apipilot"],
|
|
)
|
|
return validated_json_response(out, ConfigResponse)
|