refactor(api): scope uv to s2s-client and validate compatible responses

This commit is contained in:
2026-10-06 18:23:08 -03:00
parent 92c53dc7ca
commit f8d5b7857a
39 changed files with 316 additions and 67 deletions
+47
View File
@@ -0,0 +1,47 @@
from __future__ import annotations
from typing import Any
from pydantic import BaseModel, ConfigDict
class SearchRequest(BaseModel):
query: str
# Optional user-supplied key (fallback when the deploy has no server key).
# Used for this request only; never stored.
key: str | None = None
class ReasoningRequest(BaseModel):
transcript: str
instructions: str = ""
language: str = ""
user_context: str = ""
# Optional phone number. When S2S_INJECT_PROFILE is true the proxy fetches
# the caller profile from VOCERO_USER_PHONE_AUTH_URL and:
# 1. prepends the serialized profile to user_context before feeding the
# engine (NODE_RED or OPENAI);
# 2. appends the profile context to the LLM instructions prompt.
phoneNumber: str | None = None
# Optional podcast episode ID. When set the proxy fetches the episode from
# the Aquachile AzurianLab API and injects it as the PRIMARY context into
# both user_context (prepended) and LLM instructions prompt (appended first).
episode_id: str | None = None
class ApipilotExecuteRequest(BaseModel):
"""Body for the ApiPilot execute proxy endpoint. Mirrors §2.2 request shape."""
name: str
arguments: dict = {}
class QueueEndPayload(BaseModel):
"""A view of a decoded beacon object, preserving the legacy loose value.
Missing/invalid JSON and non-object bodies are still handled by the route.
In particular, a truthy non-string ID must retain its existing behavior.
"""
model_config = ConfigDict(extra="allow")
queueId: Any = ""
+125
View File
@@ -0,0 +1,125 @@
"""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):
search: bool
lb: bool
allowDirect: bool
s2sUrl: str
primaryPrompt: str
reasoningType: Literal["PROXY", "OPENAI", "NODE_RED"]
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
log_only: bool | None = None
usage: Any = None
usage_pass2: Any = None
latency_ms: Any = None
intent: Any = None
caller_profile_keys: Any = None
debug: 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