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
121 lines
2.8 KiB
Python
121 lines
2.8 KiB
Python
"""Response contracts without imposing new assumptions on upstream JSON.
|
|
|
|
Only fields constructed by this service have concrete types. Upstream-owned
|
|
values remain Any, and unknown fields remain allowed at every modeled level.
|
|
Responses are validated but serialized from their original payloads so omitted
|
|
fields, numeric representations, key order, and unknown values stay unchanged.
|
|
"""
|
|
|
|
from typing import Any, Literal
|
|
|
|
from pydantic import BaseModel, ConfigDict, RootModel
|
|
|
|
|
|
class OpenResponse(BaseModel):
|
|
model_config = ConfigDict(extra="allow", strict=True)
|
|
|
|
|
|
class ConfigResponse(OpenResponse):
|
|
mcp: bool = False
|
|
search: bool
|
|
lb: bool
|
|
allowDirect: bool
|
|
s2sUrl: str
|
|
primaryPrompt: str
|
|
reasoningType: Literal["PROXY", "OPENAI"]
|
|
injectProfile: bool
|
|
debugPrompt: bool
|
|
startSessionMode: str
|
|
theme: str
|
|
apipilot: bool
|
|
|
|
|
|
class SearchResult(OpenResponse):
|
|
# Serper values have historically passed through, including nulls, numbers,
|
|
# and objects. The service guarantees these keys, not their value types.
|
|
title: Any
|
|
snippet: Any
|
|
url: Any
|
|
|
|
|
|
class SearchResponse(OpenResponse):
|
|
query: str
|
|
answer: Any
|
|
results: list[SearchResult]
|
|
|
|
|
|
class ReportEntry(OpenResponse):
|
|
sourceScheduleLabel: Any
|
|
date: Any
|
|
|
|
|
|
class ReportsResponse(OpenResponse):
|
|
count: int
|
|
wrappedLen: int
|
|
hints: list[str]
|
|
entries: list[ReportEntry]
|
|
rawCount: int | None = None
|
|
cap: int | None = None
|
|
skipped: bool | None = None
|
|
reason: str | None = None
|
|
truncated: bool | None = None
|
|
note: str | None = None
|
|
|
|
|
|
class ToolInvocation(OpenResponse):
|
|
name: str
|
|
success: bool
|
|
result_length: int
|
|
|
|
|
|
class ReasoningResponse(OpenResponse):
|
|
answer: str
|
|
usage_pass2: Any = None
|
|
apipilot_invoked: ToolInvocation | None = None
|
|
|
|
|
|
class ToolDefinition(OpenResponse):
|
|
name: str
|
|
description: str
|
|
parameters: dict[str, Any]
|
|
requires_auth: bool
|
|
|
|
|
|
class ToolCatalog(OpenResponse):
|
|
count: int
|
|
include_unauthorized: bool
|
|
tools: list[ToolDefinition]
|
|
|
|
|
|
class ToolCatalogResponse(OpenResponse):
|
|
success: bool
|
|
data: ToolCatalog | None
|
|
error: Any
|
|
|
|
|
|
class ToolExecutionResponse(OpenResponse):
|
|
success: bool
|
|
# The upstream may omit these fields or return any JSON value for them.
|
|
data: Any = None
|
|
error: Any = None
|
|
|
|
|
|
class AllocationResponse(OpenResponse):
|
|
"""LB-owned object: no new required fields or value-type restrictions."""
|
|
|
|
state: Any = None
|
|
session_id: Any = None
|
|
connect_url: Any = None
|
|
pending_timeout_s: Any = None
|
|
queue_id: Any = None
|
|
position: Any = None
|
|
poll_interval_s: Any = None
|
|
|
|
|
|
class UserProfileResponse(RootModel[Any]):
|
|
"""The profile endpoint relays objects, arrays, scalars, and null verbatim."""
|
|
|
|
|
|
class QueueCancellationResponse(OpenResponse):
|
|
ok: bool
|