feat: 完善 N7 智能座舱交互与控制链路

This commit is contained in:
vee
2026-09-07 15:34:59 +08:00
parent 32d179143f
commit c96970f068
100 changed files with 30088 additions and 1174 deletions
+609 -13
View File
@@ -12,6 +12,7 @@ import re
import socket
import threading
import time
from collections.abc import Callable, Mapping
from copy import deepcopy
from http import HTTPStatus
from http.cookies import CookieError, SimpleCookie
@@ -27,12 +28,20 @@ REPOSITORY_ROOT = Path(__file__).resolve().parents[2]
load_local_env(REPOSITORY_ROOT / ".env", override=True)
load_local_env(REPOSITORY_ROOT / ".env.auth", override=True)
from cognitive_agent.application.assistant_capabilities import ( # noqa: E402
AssistantCapabilityCatalog,
)
from cognitive_agent.application.calibration.service import ( # noqa: E402
ApiError as CalibrationApiError,
)
from cognitive_agent.application.calibration.service import (
from cognitive_agent.application.calibration.service import ( # noqa: E402
CalibrationService,
)
from cognitive_agent.application.codex_assistant import ( # noqa: E402
CodexAssistantError,
CodexAssistantService,
CodexVehicleToolError,
)
from cognitive_agent.application.gesture_bridge import GestureCapabilityBridge # noqa: E402
from cognitive_agent.application.seat_service import SeatDemoService # noqa: E402
from cognitive_agent.application.self_extension import ( # noqa: E402
@@ -44,15 +53,25 @@ from cognitive_agent.application.self_extension import ( # noqa: E402
resolve_active_peripheral_command,
)
from cognitive_agent.application.self_repair import SelfRepairService # noqa: E402
from cognitive_agent.auth import AuthConfig, AuthRuntime, verify_password # noqa: E402
from cognitive_agent.auth import ( # noqa: E402
AuthConfig,
AuthRuntime,
is_json_content_type,
origin_matches_host,
verify_password,
)
from cognitive_agent.infrastructure.can.access import ( # noqa: E402
HardwareAccessRequiredError,
)
from cognitive_agent.infrastructure.can.gateway import CanGatewayError # noqa: E402
from cognitive_agent.infrastructure.can.hardware import ( # noqa: E402
HardwareBusyError,
HardwareSafetyError,
)
from cognitive_agent.infrastructure.can.gateway import CanGatewayError # noqa: E402
from cognitive_agent.infrastructure.codex.app_server import ( # noqa: E402
CodexAppServerClient,
CodexAppServerError,
)
from cognitive_agent.infrastructure.llm.openai_responses import OpenAIIntentError # noqa: E402
from cognitive_agent.infrastructure.nfc import NfcReaderService # noqa: E402
from cognitive_agent.infrastructure.speech import ( # noqa: E402
@@ -175,6 +194,13 @@ def _lan_ipv4_address() -> str | None:
class SeatDemoHandler(BaseHTTPRequestHandler):
service = SeatDemoService()
# Codex defaults to its own empty temporary workspace. A deployment may
# opt into another isolated directory through CODEX_ASSISTANT_WORKSPACE,
# but it must never inherit access to this application's source tree.
assistant = CodexAssistantService(
CodexAppServerClient(),
fast_implicit_controls=True,
)
gesture = GestureCapabilityBridge(REPOSITORY_ROOT)
extension = SelfExtensionService()
nfc = NfcReaderService()
@@ -184,8 +210,12 @@ class SeatDemoHandler(BaseHTTPRequestHandler):
)
repair = SelfRepairService()
speech = SpeechTranscriber(REPOSITORY_ROOT)
# Reject excess live-preview work before reading request bodies. The
# transcriber owns a second, stricter gate so non-HTTP callers are covered too.
speech_preview_http_slots = threading.BoundedSemaphore(6)
auth = AuthRuntime(AuthConfig.from_env())
vehicle_display_sync = VehicleDisplaySync()
assistant_capability_catalog: AssistantCapabilityCatalog | None = None
server_version = "CognitiveAgentCabinDemo/2.0"
def do_GET(self) -> None: # noqa: N802 - stdlib handler API
@@ -208,6 +238,22 @@ class SeatDemoHandler(BaseHTTPRequestHandler):
}
)
return
if path == "/api/health":
assistant_status = self.assistant.status()
assistant_available = bool(assistant_status.get("available"))
self._json(
{
"status": "ok" if assistant_available else "unavailable",
"service": "cabin-cognitive-orchestrator",
"assistant": {
"engine": str(assistant_status.get("engine") or "codex-app-server"),
"status": str(assistant_status.get("status") or "unknown"),
"available": assistant_available,
},
},
HTTPStatus.OK if assistant_available else HTTPStatus.SERVICE_UNAVAILABLE,
)
return
if path in {"/login", "/login.html"}:
if self.auth.config.enabled and self._session_username() is not None:
self._redirect("/")
@@ -245,8 +291,11 @@ class SeatDemoHandler(BaseHTTPRequestHandler):
HTTPStatus.BAD_REQUEST,
)
return
if path == "/api/health":
self._json({"status": "ok", "service": "cabin-cognitive-orchestrator"})
if path == "/api/assistant/status":
self._json(self.assistant.status())
return
if path == "/api/assistant/capabilities":
self._json(self._assistant_capability_catalog().snapshot())
return
if path == "/api/runtime":
self._json(self.service.runtime_info())
@@ -273,7 +322,7 @@ class SeatDemoHandler(BaseHTTPRequestHandler):
vehicle_id,
control_transport=control_transport,
hardware_client_id=hardware_client_id,
hardware_access_bypass=self._is_unproxied_loopback_request(),
hardware_access_bypass=self._hardware_access_bypass_allowed(),
)
)
except HardwareAccessRequiredError as exc:
@@ -389,7 +438,16 @@ class SeatDemoHandler(BaseHTTPRequestHandler):
def do_POST(self) -> None: # noqa: N802 - stdlib handler API
path = unquote(urlparse(self.path).path)
if not self._require_same_origin():
return
if path == "/api/internal/codex/vehicle-control":
if not self._require_json_content_type():
return
self._handle_codex_vehicle_control()
return
if path == "/api/auth/login":
if not self._require_json_content_type():
return
self._login()
return
if path == "/api/auth/logout":
@@ -397,9 +455,19 @@ class SeatDemoHandler(BaseHTTPRequestHandler):
return
if not self._authorize(path):
return
if path == "/api/assistant/stream":
if not self._require_json_content_type():
return
self._handle_assistant_stream()
return
if path == "/api/speech/transcribe":
self._handle_speech_transcription()
return
if path == "/api/speech/preview":
self._handle_speech_preview()
return
if not self._require_json_content_type():
return
if path in {
"/api/hardware/access/request",
"/api/hardware/access/release",
@@ -523,7 +591,7 @@ class SeatDemoHandler(BaseHTTPRequestHandler):
payload.get("simulation_fault", "normal")
),
"hardware_client_id": hardware_client_id,
"hardware_access_bypass": self._is_unproxied_loopback_request(),
"hardware_access_bypass": self._hardware_access_bypass_allowed(),
"reasoning_effort": reasoning_effort,
"control_transport": str(
payload.get("control_transport", "wired_can")
@@ -606,7 +674,7 @@ class SeatDemoHandler(BaseHTTPRequestHandler):
payload.get("simulation_fault", "normal")
),
hardware_client_id=hardware_client_id,
hardware_access_bypass=self._is_unproxied_loopback_request(),
hardware_access_bypass=self._hardware_access_bypass_allowed(),
control_transport=str(
payload.get("control_transport", "wired_can")
),
@@ -633,7 +701,7 @@ class SeatDemoHandler(BaseHTTPRequestHandler):
"COGNITIVE_AGENT_HARDWARE_APPROVAL_TOKEN"
),
hardware_client_id=hardware_client_id,
hardware_access_bypass=self._is_unproxied_loopback_request(),
hardware_access_bypass=self._hardware_access_bypass_allowed(),
reasoning_effort=reasoning_effort,
)
else:
@@ -1447,7 +1515,7 @@ class SeatDemoHandler(BaseHTTPRequestHandler):
if payload.get("hardware_client_id")
else None
),
hardware_access_bypass=self._is_unproxied_loopback_request(),
hardware_access_bypass=self._hardware_access_bypass_allowed(),
reasoning_effort=(
str(payload["reasoning_effort"])
if payload.get("reasoning_effort") is not None
@@ -1505,10 +1573,324 @@ class SeatDemoHandler(BaseHTTPRequestHandler):
detail=str(exc),
)
def _assistant_capability_catalog(self) -> AssistantCapabilityCatalog:
"""Bind the assistant to the same live registries used by every service."""
if self.assistant_capability_catalog is not None:
return self.assistant_capability_catalog
vehicle_service = (
self.service
if hasattr(self.service, "capability_registry")
else SeatDemoHandler.service
)
return AssistantCapabilityCatalog(
vehicle_service,
peripheral_provider=self.extension.list_peripheral_capabilities,
nfc_provider=self.nfc.snapshot,
speech_provider=self.speech.status,
gesture_provider=self.gesture.runtime_info,
calibration_provider=self.calibration.snapshot,
self_repair_provider=self.repair.health,
)
def _handle_assistant_stream(self) -> None:
"""Serve one unified answer/control turn as incremental NDJSON."""
try:
username = self._session_username()
principal = username or f"anonymous:{self._client_key()}"
retry_after = self.auth.api_requests.retry_after(principal)
if retry_after:
self._json(
{"error": "rate_limited", "message": "请求过于频繁,请稍后再试。"},
HTTPStatus.TOO_MANY_REQUESTS,
{"Retry-After": str(retry_after)},
)
return
self.auth.api_requests.record(principal)
payload = self._read_json_body(max_bytes=64 * 1024)
except (ValueError, json.JSONDecodeError) as exc:
self._json(
{"error": "invalid_request", "message": str(exc)},
HTTPStatus.BAD_REQUEST,
)
return
message = str(payload.get("message") or payload.get("utterance") or "").strip()
conversation_id = str(payload.get("conversation_id") or "").strip()
if not conversation_id:
conversation_id = f"conv-{os.urandom(16).hex()}"
history_payload = payload.get("history")
history = history_payload if isinstance(history_payload, list) else []
source = str(payload.get("client_type") or "web_console_assistant")
hardware_client_id = (
str(payload["hardware_client_id"]) if payload.get("hardware_client_id") else None
)
reasoning_effort = (
str(payload["reasoning_effort"])
if payload.get("reasoning_effort") is not None
else "low"
)
common_arguments: dict[str, object] = {
"utterance": message,
# Codex has already converted an implicit request into one explicit
# vehicle instruction. Keep the safety-critical execution pass
# deterministic and avoid paying for a second model invocation.
"provider": "local",
"conversation_id": conversation_id,
"execution_mode": str(payload.get("execution_mode") or "dry_run"),
"approval_token": os.getenv("COGNITIVE_AGENT_HARDWARE_APPROVAL_TOKEN"),
"vehicle_id": str(payload["vehicle_id"]) if payload.get("vehicle_id") else None,
"simulation_fault": str(payload.get("simulation_fault") or "normal"),
"hardware_client_id": hardware_client_id,
"hardware_access_bypass": self._hardware_access_bypass_allowed(),
"reasoning_effort": reasoning_effort,
"control_transport": str(payload.get("control_transport") or "wired_can"),
}
capability_catalog = self._assistant_capability_catalog()
capability_snapshot = capability_catalog.snapshot()
capability_context = capability_catalog.compact_context(capability_snapshot)
fast_answer = capability_catalog.fast_answer(message, capability_snapshot)
self.send_response(HTTPStatus.OK)
self.send_header("Content-Type", "application/x-ndjson; charset=utf-8")
self.send_header("Cache-Control", "no-store")
self.send_header("X-Accel-Buffering", "no")
self._send_security_headers()
self.end_headers()
stream_open = True
def emit(event: dict[str, object]) -> bool:
nonlocal stream_open
if not stream_open:
return False
content = (json.dumps(event, ensure_ascii=False) + "\n").encode("utf-8")
try:
self.wfile.write(content)
self.wfile.flush()
return True
except (BrokenPipeError, ConnectionResetError):
stream_open = False
return False
def record_control_failure(code: str, public_message: str) -> None:
self.vehicle_display_sync.update_task(
status="failed",
utterance=message,
source=source,
error={"code": code, "message": public_message},
)
def execute_control(
instruction: str,
on_commands_validated: Callable[[dict[str, object]], None],
assistant_plan: Mapping[str, object] | None = None,
) -> dict[str, object]:
self.vehicle_display_sync.update_task(
status="running",
utterance=message,
source=source,
)
turn_arguments = {
**common_arguments,
"utterance": instruction,
"provider": "local",
**({"assistant_plan": dict(assistant_plan)} if assistant_plan is not None else {}),
}
try:
result = self._execute_request_with_peripherals(
"/api/requests/preview",
turn_arguments,
on_commands_validated=on_commands_validated,
)
if result is None:
result = self.service.preview_native_can(
**turn_arguments,
on_commands_validated=on_commands_validated,
)
if result.get("supplement_required"):
result = self.service.supplement_native_can(
str(result["request_id"]),
provider="local",
approval_token=os.getenv("COGNITIVE_AGENT_HARDWARE_APPROVAL_TOKEN"),
hardware_client_id=hardware_client_id,
hardware_access_bypass=self._hardware_access_bypass_allowed(),
reasoning_effort=reasoning_effort,
on_commands_validated=on_commands_validated,
)
except HardwareAccessRequiredError:
record_control_failure("hardware_access_required", "真实车控当前不可用。")
raise
except HardwareBusyError:
record_control_failure("hardware_busy", "车辆控制通道正在使用中。")
raise
except HardwareSafetyError:
record_control_failure("hardware_safety_gate", "操作未通过车辆安全校验。")
raise
except CanGatewayError:
record_control_failure("can_transport_failed", "车辆控制通道当前不可用。")
raise
except (GatewayError, ExtensionIntegrationError):
record_control_failure("device_control_failed", "外设控制没有完成。")
raise
except Exception:
record_control_failure("vehicle_control_failed", "车辆控制没有完成。")
raise
self.vehicle_display_sync.update_task(
status=str(result.get("status") or "succeeded"),
utterance=message,
source=source,
result=result,
)
return result
try:
self.assistant.stream(
message,
conversation_id=conversation_id,
principal=principal,
history=history,
emit=emit,
execute_control=execute_control,
capability_snapshot=capability_snapshot,
capability_context=capability_context,
control_terms=capability_catalog.control_terms(capability_snapshot),
query_capabilities=capability_catalog.query,
authoritative_answer=fast_answer.answer if fast_answer else None,
)
except HardwareAccessRequiredError as exc:
self.vehicle_display_sync.update_task(
status="failed",
utterance=message,
source=source,
error={"code": "hardware_access_required", "message": str(exc)},
)
emit(
{
"type": "error",
"error": "hardware_access_required",
"message": f"暂时不能执行真实车控:{exc}",
"access_state": exc.state,
}
)
except HardwareBusyError as exc:
record_control_failure("hardware_busy", "车辆控制通道正在使用中。")
emit(
{
"type": "error",
"error": "hardware_busy",
"message": f"车辆控制通道正在使用中:{exc}",
"retry_after_seconds": exc.retry_after_seconds,
}
)
except HardwareSafetyError as exc:
record_control_failure("hardware_safety_gate", "操作未通过车辆安全校验。")
emit(
{
"type": "error",
"error": "hardware_safety_gate",
"message": f"该操作未通过车辆安全校验:{exc}",
}
)
except CanGatewayError as exc:
record_control_failure("can_transport_failed", "车辆控制通道当前不可用。")
emit(
{
"type": "error",
"error": "can_transport_failed",
"message": f"车辆控制通道当前不可用:{exc}",
}
)
except (GatewayError, ExtensionIntegrationError) as exc:
record_control_failure("device_control_failed", "外设控制没有完成。")
emit(
{
"type": "error",
"error": "device_control_failed",
"message": f"外设控制没有完成:{exc}",
}
)
except (CodexAppServerError, CodexAssistantError, OpenAIIntentError) as exc:
self.log_error("assistant model request failed: %s", exc)
emit(
{
"type": "error",
"error": "assistant_model_failed",
"message": "Codex AI 助手暂时没有响应,请稍后再试。",
}
)
except (ValueError, json.JSONDecodeError) as exc:
emit({"type": "error", "error": "invalid_request", "message": str(exc)})
except Exception as exc: # noqa: BLE001 - stream one safe terminal event
self.log_error("assistant request failed: %s", exc)
emit(
{
"type": "error",
"error": "internal_error",
"message": "请求处理失败,请稍后再试。",
}
)
def _handle_codex_vehicle_control(self) -> None:
"""Run an allowlisted MCP tool over the authenticated loopback bridge."""
if not self._is_unproxied_loopback_request():
self._json(
{"error": "local_access_required", "message": "仅允许本机 Codex 工具调用。"},
HTTPStatus.FORBIDDEN,
)
return
if not self.assistant.authenticate_internal_request(
self.headers.get("X-N7-Internal-Token", "")
):
self._json(
{"error": "invalid_internal_token", "message": "Codex 工具认证失败。"},
HTTPStatus.FORBIDDEN,
)
return
try:
payload = self._read_json_body(max_bytes=32 * 1024)
tool_name = str(payload.get("tool") or "execute_cockpit_request")
request_token = str(payload.get("request_token") or "")
if tool_name == "get_n7web_capabilities":
result = self.assistant.get_n7web_capabilities(
request_token,
str(payload.get("query") or ""),
)
elif tool_name == "execute_cockpit_request":
plan = payload.get("plan")
if plan is not None and not isinstance(plan, dict):
raise ValueError("plan must be an object")
result = self.assistant.execute_cockpit_request(
request_token,
plan,
)
else:
raise ValueError("unsupported Codex application tool")
self._json({"success": True, "result": result})
except (ValueError, json.JSONDecodeError) as exc:
self._json(
{"error": "invalid_request", "message": str(exc)},
HTTPStatus.BAD_REQUEST,
)
except CodexVehicleToolError as exc:
self._json(
{"error": "vehicle_control_failed", "message": str(exc)},
HTTPStatus.CONFLICT,
)
except Exception as exc: # noqa: BLE001 - return a safe MCP failure
self.log_error("Codex vehicle tool failed: %s", exc)
self._json(
{"error": "vehicle_control_failed", "message": str(exc)},
HTTPStatus.CONFLICT,
)
def do_PUT(self) -> None: # noqa: N802 - stdlib handler API
path = unquote(urlparse(self.path).path)
if not self._authorize(path):
return
if not self._require_same_origin() or not self._require_json_content_type():
return
match = re.fullmatch(r"/api/extension/gscu/slots/(?P<slot>\d+)", path)
if match is None:
self._json({"error": "not_found"}, HTTPStatus.NOT_FOUND)
@@ -1530,6 +1912,8 @@ class SeatDemoHandler(BaseHTTPRequestHandler):
path = unquote(parsed_url.path)
if not self._authorize(path):
return
if not self._require_same_origin():
return
slot_match = re.fullmatch(r"/api/extension/gscu/slots/(?P<slot>\d+)", path)
if slot_match is not None:
try:
@@ -1743,7 +2127,7 @@ class SeatDemoHandler(BaseHTTPRequestHandler):
vehicle_id=vehicle_id,
simulation_fault=str(payload.get("simulation_fault", "normal")),
hardware_client_id=hardware_client_id,
hardware_access_bypass=self._is_unproxied_loopback_request(),
hardware_access_bypass=self._hardware_access_bypass_allowed(),
control_transport=control_transport,
)
if entries:
@@ -1898,7 +2282,7 @@ class SeatDemoHandler(BaseHTTPRequestHandler):
result = self.extension.execute_vehicle(
payload,
self.service,
hardware_access_bypass=self._is_unproxied_loopback_request(),
hardware_access_bypass=self._hardware_access_bypass_allowed(),
)
self.vehicle_display_sync.update_task(
status=str(result.get("status", "succeeded")),
@@ -2257,6 +2641,36 @@ class SeatDemoHandler(BaseHTTPRequestHandler):
raise ValueError("request body must be a JSON object")
return payload
def _require_json_content_type(self) -> bool:
if is_json_content_type(self.headers.get("Content-Type", "")):
return True
self._json(
{
"error": "unsupported_media_type",
"message": "请求 Content-Type 必须为 application/json。",
},
HTTPStatus.UNSUPPORTED_MEDIA_TYPE,
)
return False
def _require_same_origin(self) -> bool:
"""Reject browser cross-origin mutations while retaining local CLI calls."""
origin = self.headers.get("Origin", "").strip()
if not origin:
return True
scheme = "https" if self._is_https() else "http"
if origin_matches_host(origin, self.headers.get("Host", ""), scheme):
return True
self._json(
{
"error": "cross_origin_request",
"message": "拒绝跨来源的状态变更请求。",
},
HTTPStatus.FORBIDDEN,
)
return False
def _handle_speech_transcription(self) -> None:
content_type = self.headers.get("Content-Type", "").split(";", 1)[0].strip().lower()
if content_type not in {"audio/wav", "audio/wave", "audio/x-wav"}:
@@ -2276,10 +2690,38 @@ class SeatDemoHandler(BaseHTTPRequestHandler):
"录音数据为空或超过大小限制。",
status=413,
)
transcription_arguments: dict[str, object] = {
"utterance_id": self.headers.get("X-Speech-Utterance-Id")
}
requested_mode = (
self.headers.get("X-Speech-Input-Mode", "").strip().lower()
)
legacy_wake_required = (
self.headers.get("X-Speech-Wake-Word-Required", "").strip() == "1"
)
input_mode = requested_mode or (
"wake" if legacy_wake_required else "manual"
)
if input_mode not in {"manual", "wake"}:
raise SpeechRecognitionError(
"invalid_speech_input_mode",
"语音输入模式仅支持 manual 或 wake。",
status=400,
)
if requested_mode == "manual" and legacy_wake_required:
raise SpeechRecognitionError(
"speech_input_mode_conflict",
"手动语音模式不能同时要求唤醒词。",
status=400,
)
transcription_arguments["require_wake_word"] = input_mode == "wake"
if bool(getattr(self.speech, "supports_input_modes", False)):
transcription_arguments["input_mode"] = input_mode
result = self.speech.transcribe_wav(
self.rfile.read(content_length),
utterance_id=self.headers.get("X-Speech-Utterance-Id"),
**transcription_arguments,
)
result = {**result, "input_mode": input_mode}
raw_text = str(result.get("raw_text") or "").strip()
peripheral_routing = self._speech_peripheral_routing(raw_text)
if peripheral_routing.get("execution_eligible"):
@@ -2300,6 +2742,145 @@ class SeatDemoHandler(BaseHTTPRequestHandler):
HTTPStatus.BAD_REQUEST,
)
def _handle_speech_preview(self) -> None:
"""Return semantic-free partial text for an in-progress microphone session."""
content_type = self.headers.get("Content-Type", "").split(";", 1)[0].strip().lower()
if content_type not in {"audio/wav", "audio/wave", "audio/x-wav"}:
self._json(
{
"error": "unsupported_media_type",
"message": "实时语音接口仅接受 16 位 PCM WAV 音频。",
},
HTTPStatus.UNSUPPORTED_MEDIA_TYPE,
)
return
slot_acquired = False
content_length = 0
request_body_consumed = False
try:
content_length = int(self.headers.get("Content-Length", "0"))
if content_length <= 0 or content_length > 2 * 1024 * 1024:
raise SpeechRecognitionError(
"invalid_audio_size",
"实时录音数据为空或超过大小限制。",
status=413,
)
sequence = int(self.headers.get("X-Speech-Preview-Sequence", "-1"))
if sequence < 0 or sequence > 100_000:
raise SpeechRecognitionError(
"invalid_preview_sequence",
"实时转写序号无效。",
status=400,
)
if not self.speech_preview_http_slots.acquire(blocking=False):
raise SpeechRecognitionError(
"preview_overloaded",
"实时语音请求过多,请稍后重试。",
status=429,
retry_after_seconds=1,
)
slot_acquired = True
preview_arguments: dict[str, object] = {
"utterance_id": self.headers.get("X-Speech-Utterance-Id"),
"sequence": sequence,
}
input_mode = (
self.headers.get("X-Speech-Input-Mode", "wake").strip().lower()
or "wake"
)
if input_mode not in {"manual", "wake"}:
raise SpeechRecognitionError(
"invalid_speech_input_mode",
"语音输入模式仅支持 manual 或 wake。",
status=400,
)
supports_input_modes = bool(
getattr(self.speech, "supports_input_modes", False)
)
if supports_input_modes:
preview_arguments["input_mode"] = input_mode
content = self.rfile.read(content_length)
request_body_consumed = True
result = self.speech.transcribe_preview_wav(
content,
**preview_arguments,
)
if input_mode == "manual" and not supports_input_modes:
raw_text = str(result.get("raw_text") or result.get("text") or "")
wake_word = dict(result.get("wake_word") or {})
wake_word.update({"required": False, "remainder": raw_text})
result = {
**result,
"raw_text": raw_text,
"text": raw_text,
"live_text": raw_text,
"wake_word": wake_word,
}
result = {**result, "input_mode": input_mode}
self._json(result)
except SpeechRecognitionError as exc:
error_payload: dict[str, object] = {
"error": exc.code,
"message": str(exc),
}
error_headers: dict[str, str] = {}
if exc.retry_after_seconds is not None:
error_payload["retry_after_seconds"] = exc.retry_after_seconds
error_headers["Retry-After"] = str(exc.retry_after_seconds)
self._json(
error_payload,
HTTPStatus(exc.status),
error_headers,
)
if not request_body_consumed:
self._drain_rejected_request_body(content_length)
except (TypeError, ValueError):
self._json(
{"error": "invalid_preview", "message": "实时语音请求格式无效。"},
HTTPStatus.BAD_REQUEST,
)
if not request_body_consumed:
self._drain_rejected_request_body(content_length)
finally:
if slot_acquired:
self.speech_preview_http_slots.release()
def _drain_rejected_request_body(self, content_length: int) -> None:
"""Gracefully finish an early-rejected POST without doing speech work.
Closing a Windows socket while its peer's request body is still unread can
replace an already-written HTTP error with TCP RST. Flush the rejection
first, then briefly discard a valid-sized body so clients reliably receive
the 4xx response. The preview/transcriber concurrency gates still run
before any audio is read or decoded.
"""
if content_length <= 0 or content_length > 2 * 1024 * 1024:
return
self.close_connection = True
try:
self.wfile.flush()
except OSError:
return
previous_timeout = self.connection.gettimeout()
try:
self.connection.settimeout(0.25)
remaining = content_length
while remaining > 0:
chunk = self.rfile.read(min(remaining, 64 * 1024))
if not chunk:
break
remaining -= len(chunk)
except (OSError, ValueError):
pass
finally:
try:
self.connection.settimeout(previous_timeout)
except OSError:
pass
def _authorize(self, path: str) -> bool:
if not self.auth.config.enabled or path in PUBLIC_AUTH_PATHS:
return True
@@ -2442,6 +3023,15 @@ class SeatDemoHandler(BaseHTTPRequestHandler):
self.headers.get(name, "").strip() for name in proxy_headers
)
def _hardware_access_bypass_allowed(self) -> bool:
"""Allow implicit hardware access only for authenticated local sessions."""
return (
self.auth.config.enabled
and self._is_unproxied_loopback_request()
and self._session_username() is not None
)
def _require_local_decision_point(self) -> bool:
if self._is_unproxied_loopback_request():
return True
@@ -2598,6 +3188,10 @@ def main() -> None:
parser.add_argument("--port", type=int, default=int(os.getenv("PORT", "8000")))
args = parser.parse_args()
server = ThreadingHTTPServer((args.host, args.port), SeatDemoHandler)
SeatDemoHandler.speech.start()
SeatDemoHandler.assistant.start(
f"http://127.0.0.1:{server.server_port}/api/internal/codex/vehicle-control"
)
SeatDemoHandler.service.start()
SeatDemoHandler.extension.start()
SeatDemoHandler.nfc.start()
@@ -2637,6 +3231,8 @@ def main() -> None:
pass
finally:
server.server_close()
SeatDemoHandler.speech.close()
SeatDemoHandler.assistant.close()
SeatDemoHandler.service.close()
SeatDemoHandler.extension.close()
SeatDemoHandler.nfc.close()