Files
Cognitive_Agent/src/cognitive_agent/server.py
T

4064 lines
166 KiB
Python
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Dependency-light HTTP server for the cabin orchestration demo."""
from __future__ import annotations
import argparse
import hmac
import ipaddress
import json
import mimetypes
import os
import re
import socket
import sys
import threading
import time
from collections.abc import Callable, Mapping
from copy import deepcopy
from http import HTTPStatus
from http.cookies import CookieError, SimpleCookie
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
from pathlib import Path
from urllib.parse import ParseResult, parse_qs, quote, unquote, urlparse
from cognitive_agent.config_loader import load_local_env
REPOSITORY_ROOT = Path(__file__).resolve().parents[2]
# Repository-local launch configuration is deliberate and must not be shadowed
# by stale variables inherited from an IDE or an earlier model experiment.
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 ( # noqa: E402
CalibrationService,
)
from cognitive_agent.application.codex_assistant import ( # noqa: E402
CodexAssistantError,
CodexAssistantService,
CodexVehicleToolError,
)
from cognitive_agent.application.firmware_workbench import ( # noqa: E402
FirmwareWorkbenchError,
FirmwareWorkbenchService,
)
from cognitive_agent.application.gesture_bridge import GestureCapabilityBridge # noqa: E402
from cognitive_agent.application.mode_profile_intent import ( # noqa: E402
normalize_mode_profile_name,
parse_mode_profile_update,
)
from cognitive_agent.application.mode_schedule_service import public_schedule_status # noqa: E402
from cognitive_agent.application.seat_service import ( # noqa: E402
LOCAL_CONTROL_BUDGET_MS,
SeatDemoService,
)
from cognitive_agent.application.self_extension import ( # noqa: E402
ExtensionIntegrationError,
GatewayError,
SelfExtensionService,
active_peripheral_plan_catalog,
partition_active_peripheral_intent,
resolve_active_peripheral_command,
)
from cognitive_agent.application.self_repair import SelfRepairService # 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.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
SpeechRecognitionError,
SpeechTranscriber,
)
STATIC_ROOT = REPOSITORY_ROOT / "apps" / "web-console"
DEFAULT_MODE_SCENE_CATALOG_PATH = (
REPOSITORY_ROOT.parent / "scenses" / "demo-scenes.json"
)
mimetypes.add_type("application/manifest+json", ".webmanifest")
mimetypes.add_type("model/gltf-binary", ".glb")
REQUEST_ID_PATTERN = re.compile(r"^req-[a-z0-9-]+$")
PUBLIC_AUTH_PATHS = {
"/login",
"/login.html",
"/login.css",
"/login.js",
"/network.js",
"/api/auth/status",
"/api/auth/login",
"/api/firmware-workbench/releases/latest",
}
PUBLIC_OTA_DOWNLOAD_PATH = re.compile(
r"/api/firmware-workbench/releases/[a-z0-9-]+/download"
)
NON_DISPATCH_DISPLAY_SOURCES = frozenset({"mode_editor_compile"})
class VehicleDisplaySync:
"""Process-local snapshot consumed by passive vehicle display clients."""
def __init__(self) -> None:
self._lock = threading.Lock()
self._revision = 0
self._snapshot: dict[str, object] = {
"revision": 0,
"updated_at": None,
"task": {
"status": "idle",
"utterance": "",
"source": "head_unit",
"result": None,
"error": None,
},
"extension": {
"catalog": [],
"last_action": None,
"result": None,
},
}
@staticmethod
def _safe_copy(value: object) -> object:
return json.loads(json.dumps(value, ensure_ascii=False, default=str))
def update_task(
self,
*,
status: str,
utterance: str,
source: str = "head_unit",
result: object = None,
error: object = None,
) -> None:
with self._lock:
self._revision += 1
self._snapshot["revision"] = self._revision
self._snapshot["updated_at"] = time.time()
self._snapshot["task"] = {
"status": status,
"utterance": utterance,
"source": source or "head_unit",
"result": self._safe_copy(result),
"error": self._safe_copy(error),
}
def update_extension(
self,
*,
catalog: object | None = None,
action: object | None = None,
result: object | None = None,
) -> None:
with self._lock:
self._revision += 1
current = dict(self._snapshot.get("extension", {}))
if catalog is not None:
current["catalog"] = self._safe_copy(catalog)
if action is not None:
current["last_action"] = self._safe_copy(action)
if result is not None:
current["result"] = self._safe_copy(result)
self._snapshot["revision"] = self._revision
self._snapshot["updated_at"] = time.time()
self._snapshot["extension"] = current
def snapshot(self, health: dict[str, object]) -> dict[str, object]:
with self._lock:
payload = self._safe_copy(self._snapshot)
assert isinstance(payload, dict)
payload["extension_health"] = self._safe_copy(health)
return payload
def extension_catalog(self) -> list[dict[str, object]]:
with self._lock:
extension = self._snapshot.get("extension", {})
catalog = extension.get("catalog", []) if isinstance(extension, dict) else []
payload = self._safe_copy(catalog)
if not isinstance(payload, list):
return []
return [item for item in payload if isinstance(item, dict)]
def _lan_ipv4_address() -> str | None:
"""Return the IPv4 address selected by the host's default route."""
probe = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
try:
probe.connect(("192.0.2.1", 80))
address = str(probe.getsockname()[0])
return None if address.startswith("127.") else address
except OSError:
return None
finally:
probe.close()
class SeatDemoHandler(BaseHTTPRequestHandler):
service = SeatDemoService()
firmware_workbench = FirmwareWorkbenchService(REPOSITORY_ROOT)
# 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()
calibration = CalibrationService(
REPOSITORY_ROOT / "artifacts" / "tmp" / "self-calibration",
repository_root=REPOSITORY_ROOT,
)
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
parsed_url = urlparse(self.path)
path = unquote(parsed_url.path)
if path == "/favicon.ico":
self.send_response(HTTPStatus.NO_CONTENT)
self._send_security_headers()
self.end_headers()
return
if path == "/api/auth/status":
username = self._session_username()
self._json(
{
"enabled": self.auth.config.enabled,
"authenticated": not self.auth.config.enabled or username is not None,
"username": username,
"session_ttl_seconds": self.auth.config.session_ttl_seconds,
"requests_per_minute": self.auth.config.requests_per_minute,
}
)
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 == "/n7/scenes":
vehicle_id = str(parse_qs(parsed_url.query).get("vehicle_id", [""])[0]) or None
self._json(
self.service.mode_scene_catalog(vehicle_id),
headers={"Access-Control-Allow-Origin": "*"},
)
return
if path in {"/login", "/login.html"}:
if self.auth.config.enabled and self._session_username() is not None:
self._redirect("/")
return
self._serve_static_file("login.html")
return
if not self._authorize(path):
return
if path in {"/hardware-control", "/hardware-control.html"}:
if not self._require_local_decision_point():
return
self._serve_static_file("hardware-control.html")
return
if path in {"/hardware-control.js", "/hardware-control.css"}:
if not self._require_local_decision_point():
return
self._serve_static_file(path.lstrip("/"))
return
if path == "/api/local/hardware-access":
if not self._require_local_decision_point():
return
self._json(self.service.hardware_access.admin_snapshot())
return
if path == "/api/hardware/access/status":
query = parse_qs(parsed_url.query)
try:
self._json(
self.service.hardware_access.status(
str(query.get("client_id", [""])[0])
)
)
except ValueError as exc:
self._json(
{"error": "invalid_request", "message": str(exc)},
HTTPStatus.BAD_REQUEST,
)
return
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/firmware-workbench/state":
self._json(self.firmware_workbench.snapshot())
return
if path == "/api/firmware-workbench/releases/latest":
try:
self._json(self.firmware_workbench.latest_release())
except FirmwareWorkbenchError as exc:
self._json(
{"error": exc.code, "message": str(exc)}, HTTPStatus(exc.status)
)
return
firmware_task_match = re.fullmatch(
r"/api/firmware-workbench/tasks/(?P<task_id>[a-z0-9-]+)", path
)
if firmware_task_match:
try:
self._json(
self.firmware_workbench.task(firmware_task_match.group("task_id"))
)
except FirmwareWorkbenchError as exc:
self._json(
{"error": exc.code, "message": str(exc)}, HTTPStatus(exc.status)
)
return
firmware_artifact_download_match = re.fullmatch(
r"/api/firmware-workbench/artifacts/(?P<artifact_id>[a-z0-9-]+)/download",
path,
)
if firmware_artifact_download_match:
self._serve_firmware_download(
"artifact", firmware_artifact_download_match.group("artifact_id")
)
return
firmware_release_download_match = re.fullmatch(
r"/api/firmware-workbench/releases/(?P<release_id>[a-z0-9-]+)/download",
path,
)
if firmware_release_download_match:
self._serve_firmware_download(
"release", firmware_release_download_match.group("release_id")
)
return
firmware_artifact_match = re.fullmatch(
r"/api/firmware-workbench/artifacts/(?P<artifact_id>[a-z0-9-]+)", path
)
if firmware_artifact_match:
try:
self._json(
self.firmware_workbench.artifact(
firmware_artifact_match.group("artifact_id")
)
)
except FirmwareWorkbenchError as exc:
self._json(
{"error": exc.code, "message": str(exc)}, HTTPStatus(exc.status)
)
return
if path == "/api/runtime":
self._json(self.service.runtime_info())
return
if path == "/api/modes/schedule/status":
query = parse_qs(parsed_url.query)
try:
vehicle = self.service.vehicle_profiles.get(str(query.get("vehicle_id", [""])[0]) or None)
self._json(public_schedule_status(self.service.mode_schedule_runner.status(vehicle.id)))
except ValueError as exc:
self._json({"error": "invalid_request", "message": str(exc)}, HTTPStatus.BAD_REQUEST)
return
if path == "/api/vehicle-display-sync":
self._json(self.vehicle_display_sync.snapshot(self._extension_health()))
return
if path in {"/api/hardware/seat-state", "/api/hardware/vehicle-state"}:
query = parse_qs(parsed_url.query)
conversation_id = str(query.get("conversation_id", [""])[0])
vehicle_id = str(query.get("vehicle_id", [""])[0]) or None
control_transport = str(
query.get("control_transport", ["wired_can"])[0]
)
hardware_client_id = str(query.get("hardware_client_id", [""])[0]) or None
try:
self._json(
(
self.service.synchronize_vehicle_state
if path == "/api/hardware/vehicle-state"
else self.service.synchronize_hardware_state
)(
conversation_id,
vehicle_id,
control_transport=control_transport,
hardware_client_id=hardware_client_id,
hardware_access_bypass=self._hardware_access_bypass_allowed(),
)
)
except HardwareAccessRequiredError as exc:
self._json(
{
"error": "hardware_access_required",
"message": str(exc),
"access_state": exc.state,
},
HTTPStatus.FORBIDDEN,
)
except HardwareBusyError as exc:
self._json(
{
"error": "hardware_busy",
"message": str(exc),
"retry_after_seconds": exc.retry_after_seconds,
},
HTTPStatus.CONFLICT,
{"Retry-After": str(exc.retry_after_seconds)},
)
except ValueError as exc:
self._json(
{"error": "invalid_request", "message": str(exc)},
HTTPStatus.BAD_REQUEST,
)
except Exception as exc: # noqa: BLE001 - surface passive CAN availability
self.log_error("physical CAN state synchronization failed: %s", exc)
self._json(
{
"error": "hardware_state_unavailable",
"message": str(exc),
},
HTTPStatus.SERVICE_UNAVAILABLE,
)
return
if path == "/api/gesture/runtime":
self._json(self.gesture.runtime_info())
return
if path == "/api/gesture/model":
self._serve_gesture_model()
return
if path == "/api/extension/health":
self._json(self._extension_health())
return
if path == "/api/extension/nfc/status":
self._json({"success": True, **self.nfc.snapshot()})
return
if path == "/api/extension/gscu/slots":
try:
self._json(self.extension.list_infrared_slots())
except GatewayError as exc:
self._json(
{"success": False, "error": "catalog_unavailable", "message": str(exc)},
HTTPStatus.SERVICE_UNAVAILABLE,
)
return
if path == "/api/extension/peripherals":
try:
catalog = self.extension.list_peripheral_capabilities()
self._json(
{
"success": True,
"catalog": catalog,
"count": len(catalog),
}
)
except GatewayError as exc:
self._json(
{
"success": False,
"error": "catalog_unavailable",
"message": str(exc),
},
HTTPStatus.SERVICE_UNAVAILABLE,
)
return
if path == "/api/extension/gscu/info":
self._handle_extension_gateway("info", {})
return
if path.startswith("/api/calibration/"):
if not self._require_calibration_admin(parsed_url):
return
if path == "/api/calibration/state":
self._json(self.calibration.snapshot())
return
if path == "/api/calibration/events":
self._serve_calibration_events()
return
if path == "/api/calibration/video/stream":
self._serve_calibration_video()
return
self._json({"error": "not_found"}, HTTPStatus.NOT_FOUND)
return
if path.startswith("/api/self-repair/"):
if not self._require_self_repair_admin(parsed_url):
return
if path == "/api/self-repair/state":
self._json(self.repair.snapshot())
return
if path == "/api/self-repair/health":
self._json(self.repair.health())
return
self._json({"error": "not_found"}, HTTPStatus.NOT_FOUND)
return
if path == "/api/speech/status":
self._json(self.speech.status())
return
if path.startswith("/api/requests/"):
self._serve_request(path.removeprefix("/api/requests/"))
return
self._serve_static(path, parsed_url.query)
def do_OPTIONS(self) -> None: # noqa: N802 - stdlib handler API
path = unquote(urlparse(self.path).path)
if path != "/n7/scenes":
self._json({"error": "not_found"}, HTTPStatus.NOT_FOUND)
return
self.send_response(HTTPStatus.NO_CONTENT)
self.send_header("Access-Control-Allow-Origin", "*")
self.send_header("Access-Control-Allow-Methods", "GET, OPTIONS")
self.send_header("Access-Control-Allow-Headers", "Accept, Content-Type")
self.send_header("Content-Length", "0")
self.send_header("Cache-Control", "no-store")
self._send_security_headers()
self.end_headers()
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":
self._logout()
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 == "/api/firmware-workbench/tasks" or path.startswith(
"/api/firmware-workbench/"
):
self._handle_firmware_workbench_post(path)
return
if path in {
"/api/hardware/access/request",
"/api/hardware/access/release",
"/api/local/hardware-access/approve",
"/api/local/hardware-access/deny",
"/api/local/hardware-access/revoke",
}:
self._handle_hardware_access_post(path)
return
if path.startswith("/api/extension/"):
self._handle_extension_post(path)
return
if path.startswith("/api/calibration/"):
self._handle_calibration_post(path)
return
if path.startswith("/api/self-repair/"):
self._handle_self_repair_post(path)
return
if path == "/api/mode-profile-drafts/trial":
self._handle_mode_profile_draft_trial()
return
if path == "/api/mode-profiles" or path.startswith("/api/mode-profiles/"):
self._handle_mode_profile_post(path)
return
supplement_match = re.fullmatch(
r"/api/requests/(?P<request_id>req-[a-z0-9-]+)/supplement",
path,
)
if (
path not in {
"/api/requests",
"/api/requests/preview",
"/api/can/commands",
}
and not supplement_match
):
self._json({"error": "not_found"}, HTTPStatus.NOT_FOUND)
return
utterance = ""
source = "head_unit"
sync_vehicle_task = True
def fail_display_sync(code: str, error: Exception) -> None:
if not utterance or not sync_vehicle_task:
return
self.vehicle_display_sync.update_task(
status="failed",
utterance=utterance,
source=source,
error={
"code": code,
"message": str(error),
"outcome": "failed_or_blocked",
},
)
try:
username = self._session_username() or "local"
retry_after = self.auth.api_requests.retry_after(username)
if self.auth.config.enabled and retry_after:
self._json(
{
"error": "rate_limited",
"message": "请求过于频繁,请稍后再试。",
},
HTTPStatus.TOO_MANY_REQUESTS,
{"Retry-After": str(retry_after)},
)
return
if self.auth.config.enabled:
self.auth.api_requests.record(username)
payload = self._read_json_body()
direct_can_commands = (
payload.get("commands") if path == "/api/can/commands" else None
)
utterance = (
";".join(
item if isinstance(item, str) else repr(item)
for item in direct_can_commands
)
if isinstance(direct_can_commands, list)
else str(payload.get("utterance", ""))
)
source = str(payload.get("client_type", "head_unit"))
sync_vehicle_task = source not in NON_DISPATCH_DISPLAY_SOURCES
if utterance and sync_vehicle_task:
self.vehicle_display_sync.update_task(
status="running",
utterance=utterance,
source=source,
)
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 None
)
provider = str(payload.get("provider", "auto"))
execution_mode = str(payload.get("execution_mode", "dry_run"))
conversation_id = (
str(payload["conversation_id"])
if payload.get("conversation_id")
else None
)
common_arguments = {
"utterance": utterance,
"provider": provider,
"conversation_id": conversation_id,
"execution_mode": execution_mode,
# Hardware authorization is a server deployment policy. The
# browser never receives or transmits this secret.
"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", "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", "wired_can")
),
}
speech_utterance_id = str(
payload.get("speech_utterance_id", "")
).strip()
speech_understanding: dict[str, object] | None = None
speech_peripheral_route = False
if speech_utterance_id:
if path != "/api/requests":
raise ValueError(
"speech semantic execution is only accepted by /api/requests"
)
speech_understanding = self.speech.cached_result(
speech_utterance_id
)
clean_text = str(
speech_understanding.get("clean_text", "")
).strip()
raw_text = str(
speech_understanding.get("raw_text", "")
).strip()
peripheral_routing = self._speech_peripheral_routing(raw_text)
peripheral_transcript = bool(
peripheral_routing.get("execution_eligible") and raw_text
)
expected_text = raw_text if peripheral_transcript else clean_text
if utterance != expected_text:
raise SpeechRecognitionError(
"speech_text_changed",
"语音文本已被修改,请确认修改后的内容再执行。",
status=409,
)
speech_peripheral_route = peripheral_transcript
if (
speech_understanding.get("execution_eligible") is not True
and not speech_peripheral_route
):
raise SpeechRecognitionError(
"speech_not_executable",
"语音意图尚未通过能力或参数校验,已拒绝执行。",
status=422,
)
provider = "local"
common_arguments["provider"] = provider
if not speech_peripheral_route:
common_arguments["_speech_understanding"] = speech_understanding
if (
path != "/api/can/commands"
and "application/x-ndjson" in self.headers.get("Accept", "")
):
self._stream_control_request(
path,
supplement_match,
payload,
common_arguments,
source=source,
)
return
if path == "/api/can/commands":
result = self.service.process_can_commands(
direct_can_commands,
conversation_id=(
str(payload["conversation_id"])
if payload.get("conversation_id")
else None
),
execution_mode=str(payload.get("execution_mode", "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", "normal")
),
hardware_client_id=hardware_client_id,
hardware_access_bypass=self._hardware_access_bypass_allowed(),
control_transport=str(
payload.get("control_transport", "wired_can")
),
)
else:
result = None
if path in {"/api/requests", "/api/requests/preview"}:
result = self._execute_request_with_peripherals(
path,
common_arguments,
)
if result is None:
if path == "/api/requests/preview":
result = self.service.preview_native_can(**common_arguments)
elif supplement_match:
result = self.service.supplement_native_can(
supplement_match.group("request_id"),
provider=(
str(payload["provider"])
if payload.get("provider")
else None
),
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,
)
else:
result = self.service.process(**common_arguments)
if speech_peripheral_route and speech_understanding is not None:
result["speech_understanding"] = {
"utterance_id": speech_utterance_id,
"raw_text": speech_understanding.get("raw_text"),
"clean_text": speech_understanding.get("clean_text"),
"semantic_status": speech_understanding.get("semantic_status"),
"routing": "validated_peripheral_text",
}
utterance = str(result.get("utterance") or utterance)
if sync_vehicle_task:
self.vehicle_display_sync.update_task(
status=str(result.get("status", "succeeded")),
utterance=utterance,
source=source,
result=result,
)
self._json(result, HTTPStatus.CREATED)
except HardwareAccessRequiredError as exc:
fail_display_sync("hardware_access_required", exc)
self._json(
{
"error": "hardware_access_required",
"message": str(exc),
"outcome": "failed_or_blocked",
"access_state": exc.state,
},
HTTPStatus.FORBIDDEN,
)
except HardwareBusyError as exc:
fail_display_sync("hardware_busy", exc)
self._json(
{
"error": "hardware_busy",
"message": str(exc),
"outcome": "failed_or_blocked",
"retry_after_seconds": exc.retry_after_seconds,
},
HTTPStatus.CONFLICT,
{"Retry-After": str(exc.retry_after_seconds)},
)
except HardwareSafetyError as exc:
fail_display_sync("hardware_safety_gate", exc)
self._json(
{
"error": "hardware_safety_gate",
"message": str(exc),
"outcome": "failed_or_blocked",
},
HTTPStatus.FORBIDDEN,
)
except CanGatewayError as exc:
fail_display_sync("can_transport_failed", exc)
self._json(
{
"error": "can_transport_failed",
"message": str(exc),
"outcome": "failed_or_blocked",
},
HTTPStatus.SERVICE_UNAVAILABLE,
)
except GatewayError as exc:
fail_display_sync("gscu_unavailable", exc)
self._json(
{"error": "gscu_unavailable", "message": str(exc)},
HTTPStatus.SERVICE_UNAVAILABLE,
)
except ExtensionIntegrationError as exc:
fail_display_sync("extension_request_ambiguous", exc)
self._json(
{"error": "extension_request_ambiguous", "message": str(exc)},
HTTPStatus.UNPROCESSABLE_ENTITY,
)
except SpeechRecognitionError as exc:
fail_display_sync(exc.code, exc)
self._json(
{"error": exc.code, "message": str(exc)},
HTTPStatus(exc.status),
)
except (ValueError, json.JSONDecodeError) as exc:
fail_display_sync("invalid_request", exc)
self._json(
{
"error": "invalid_request",
"message": str(exc),
"outcome": "failed_or_blocked",
},
HTTPStatus.BAD_REQUEST,
)
except OpenAIIntentError as exc:
fail_display_sync("openai_request_failed", exc)
self.log_error("OpenAI intent request failed: %s", exc)
self._json(
{
"error": "openai_request_failed",
"message": "GPT 意图解析失败,请检查 API Key、模型权限或网络状态。",
"outcome": "failed_or_blocked",
},
HTTPStatus.BAD_GATEWAY,
)
except Exception as exc: # noqa: BLE001 - keep the demo API responsive
fail_display_sync("internal_error", exc)
self.log_error("request failed: %s", exc)
self._json(
{
"error": "internal_error",
"message": "请求处理失败,请查看服务端日志。",
"outcome": "failed_or_blocked",
"detail": str(exc),
},
HTTPStatus.INTERNAL_SERVER_ERROR,
)
def _execute_active_peripheral_request(
self,
utterance: str,
*,
provider: str,
conversation_id: str | None,
execution_mode: str,
on_commands_validated: Callable[[dict[str, object]], None] | None = None,
natural_language_routing: Mapping[str, object] | None = None,
) -> dict[str, object] | None:
"""Route a command to the NFC-bound peripheral when one is present."""
# The browser may disappear or reload between learning and execution.
# Natural-language routing therefore reads the durable, validated
# whitelist instead of trusting the display-sync snapshot.
extension_catalog = self.extension.list_peripheral_capabilities()
nfc_snapshot = self.nfc.snapshot()
plan_catalog = active_peripheral_plan_catalog(
utterance,
extension_catalog,
nfc_snapshot,
)
peripheral = None
if not plan_catalog:
peripheral = resolve_active_peripheral_command(
utterance,
extension_catalog,
nfc_snapshot,
)
if not plan_catalog and peripheral is None:
return None
if conversation_id and not re.fullmatch(
r"conv-[a-z0-9-]+", conversation_id
):
raise ValueError(
"conversation_id must start with conv- and contain only "
"lowercase letters, numbers, or hyphens"
)
conversation_turn = 1
if conversation_id:
context = self.service.conversations.snapshot(conversation_id)
conversation_turn = int(context.get("turn_count", 0)) + 1
if plan_catalog:
plan_result = self.extension.plan(
{
"utterance": utterance,
"peripherals": plan_catalog,
"vehicle_devices": [],
"provider": provider,
**(
{
"natural_language_routing": dict(
natural_language_routing
)
}
if natural_language_routing is not None
else {}
),
}
)
if plan_result.get("needs_confirmation"):
raise ExtensionIntegrationError(
str(plan_result.get("question") or "请明确要控制的外设。")
)
result = self.extension.execute_peripheral_plan_request(
utterance,
plan_result,
plan_catalog,
conversation_id=conversation_id,
conversation_turn=conversation_turn,
execution_mode=execution_mode,
provider_requested=provider,
on_commands_validated=on_commands_validated,
)
self.vehicle_display_sync.update_extension(
action={
"type": "sequence_emit",
"source": "main_orchestration",
"step_count": len(plan_result.get("steps", [])),
},
result=result,
)
return result
assert peripheral is not None
result = self.extension.execute_peripheral_request(
utterance,
peripheral,
conversation_id=conversation_id,
conversation_turn=conversation_turn,
execution_mode=execution_mode,
provider_requested=provider,
on_commands_validated=on_commands_validated,
)
self.vehicle_display_sync.update_extension(
action={
"type": "emit",
"source": "main_orchestration",
"slot": peripheral["slot"],
"capability_id": peripheral.get("id"),
},
result=result,
)
return result
def _speech_peripheral_routing(self, utterance: str) -> dict[str, object]:
"""Validate whether an ASR transcript may enter peripheral routing.
Vehicle speech keeps using its cached structured semantic payload. A
learned peripheral cannot be represented by the static vehicle
capability catalog, so it may instead reuse the exact server-owned ASR
transcript only after the live NFC binding and learned action resolve.
"""
text = str(utterance or "").strip()
if not text:
return {"execution_eligible": False, "action_count": 0}
try:
catalog = self.extension.list_peripheral_capabilities()
nfc_snapshot = self.nfc.snapshot()
partition = partition_active_peripheral_intent(
text,
catalog,
nfc_snapshot,
)
entries = list(partition.get("peripheral_actions") or [])
unresolved = list(
partition.get("unresolved_peripheral_clauses") or []
)
denied = list(partition.get("denied_peripheral_actions") or [])
plan_catalog = active_peripheral_plan_catalog(
text,
catalog,
nfc_snapshot,
)
direct = None
if not entries and not plan_catalog and not unresolved and not denied:
direct = resolve_active_peripheral_command(
text,
catalog,
nfc_snapshot,
)
except (ExtensionIntegrationError, GatewayError, ValueError):
return {"execution_eligible": False, "action_count": 0}
action_count = len(entries) or (1 if direct is not None else 0)
if plan_catalog:
action_count = max(action_count, 1)
return {
"execution_eligible": bool(action_count and not unresolved and not denied),
"action_count": action_count,
"has_ordered_plan": bool(plan_catalog),
}
@staticmethod
def _residual_has_vehicle_language(utterance: str) -> bool:
"""Recognize only obvious vehicle-domain text in a claimed mixed request."""
return bool(
re.search(
r"空调|温度|风量|循环|主驾|副驾|座椅|车窗|车门|后门|"
r"遮阳帘|阅读灯|氛围灯|室内灯|出风口|风口|"
r"\b(?:ac|seat|window|door|shade|light|outlet)\b",
utterance,
flags=re.IGNORECASE,
)
)
def _execute_local_peripheral_actions(
self,
utterance: str,
entries: list[dict[str, object]],
*,
provider: str,
conversation_id: str | None,
execution_mode: str,
on_commands_validated: Callable[[dict[str, object]], None] | None = None,
) -> dict[str, object]:
"""Execute already validated infrared capabilities."""
capabilities = [
{
**capability,
"parse_source": str(capability.get("parse_source") or "local_rule"),
}
for entry in entries
if isinstance(entry, dict) and isinstance(entry.get("capability"), dict)
for capability in [dict(entry["capability"])]
]
if not capabilities:
raise ExtensionIntegrationError("没有可执行的已学习红外能力。")
conversation_turn = 1
if conversation_id:
context = self.service.conversations.snapshot(conversation_id)
conversation_turn = int(context.get("turn_count", 0)) + 1
if len(capabilities) == 1:
result = self.extension.execute_peripheral_request(
utterance,
capabilities[0],
conversation_id=conversation_id,
conversation_turn=conversation_turn,
execution_mode=execution_mode,
provider_requested=provider,
on_commands_validated=on_commands_validated,
)
else:
result = self.extension.execute_peripheral_plan_request(
utterance,
{
"summary": f"本地规则命中 {len(capabilities)} 个已学习红外动作。",
"provider_used": "infrared_local_rules",
"steps": [
{
"kind": "peripheral",
"capability_id": capability["id"],
}
for capability in capabilities
],
},
capabilities,
conversation_id=conversation_id,
conversation_turn=conversation_turn,
execution_mode=execution_mode,
provider_requested=provider,
on_commands_validated=on_commands_validated,
)
result["provider_used"] = "infrared_local_rules"
self.vehicle_display_sync.update_extension(
action={
"type": "emit_sequence" if len(capabilities) > 1 else "emit",
"source": "main_orchestration_local_rule",
"capability_ids": [item["id"] for item in capabilities],
"slots": [item["slot"] for item in capabilities],
},
result=result,
)
return result
@staticmethod
def _merge_can_and_peripheral_results(
utterance: str,
can_result: dict[str, object],
infrared_result: dict[str, object],
entries: list[dict[str, object]],
) -> dict[str, object]:
"""Create one response while retaining each transport's evidence."""
merged = deepcopy(can_result)
can_status = str(can_result.get("status") or "failed")
infrared_status = str(infrared_result.get("status") or "failed")
successful = {"succeeded", "partially_succeeded", "no_action"}
can_ok = can_status in successful
infrared_ok = infrared_status in successful
function_configs = deepcopy(can_result.get("function_configs") or {})
function_configs.update(
deepcopy(infrared_result.get("function_configs") or {})
)
command_plans = deepcopy(can_result.get("command_plans") or {})
command_plans.update(deepcopy(infrared_result.get("command_plans") or {}))
trace_events = [
*deepcopy(list(infrared_result.get("trace_events") or [])),
*deepcopy(list(can_result.get("trace_events") or [])),
]
can_operations = list(can_result.get("operation_results") or [])
infrared_operations = list(infrared_result.get("operation_results") or [])
operation_results = [*can_operations, *infrared_operations]
can_report = deepcopy(can_result.get("execution_report"))
infrared_report = deepcopy(infrared_result.get("execution_report"))
execution_report = can_report or infrared_report
if isinstance(execution_report, dict):
spec = execution_report.setdefault("spec", {})
if execution_report is can_report and isinstance(infrared_report, dict):
spec["results"] = [
*list(spec.get("results") or []),
*list((infrared_report.get("spec") or {}).get("results") or []),
]
spec["status"] = (
"succeeded" if can_ok and infrared_ok else "partially_succeeded"
)
metadata = execution_report.setdefault("metadata", {})
labels = metadata.setdefault("labels", {})
labels["route"] = "unified_can_infrared"
infrared_actions = [
{
"index": entry.get("index"),
"clause": entry.get("clause"),
"capability_id": capability.get("id"),
"device_id": capability.get("device_id"),
"device_name": capability.get("device_name"),
"action": capability.get("action"),
"slot": capability.get("slot"),
"parse_source": str(
capability.get("parse_source") or "local_rule"
),
"transport": str(infrared_result.get("control_transport") or "gscu_tcp"),
}
for entry in entries
for capability in [entry.get("capability")]
if isinstance(capability, dict)
]
merged.update(
{
"utterance": utterance,
"status": (
"succeeded"
if can_ok and infrared_ok
else "partially_succeeded"
if can_ok or infrared_ok
else "failed"
),
"success": can_ok and infrared_ok,
"orchestration_route": "unified_can_infrared",
"provider_used": (
f"{can_result.get('provider_used') or 'vehicle'}+"
f"{infrared_result.get('provider_used') or 'infrared_local_rules'}"
),
"decision_summary": [
*list(can_result.get("decision_summary") or []),
*list(infrared_result.get("decision_summary") or []),
"CAN 与红外动作已通过独立白名单校验并按各自传输链路执行。",
],
"function_configs": function_configs,
"command_plans": command_plans,
"function_config": next(iter(function_configs.values()), None),
"command_plan": next(iter(command_plans.values()), None),
"trace_events": trace_events,
"execution_report": execution_report,
"operation_results": operation_results,
"infrared_actions": infrared_actions,
"peripheral": infrared_result.get("peripheral"),
"control_transports": {
"vehicle": can_result.get("control_transport"),
"infrared": infrared_result.get("control_transport"),
},
"phase_execution_reports": {
**dict(can_result.get("phase_execution_reports") or {}),
"vehicle": can_report,
"infrared": infrared_report,
},
}
)
return merged
@staticmethod
def _merge_peripheral_phase_results(
utterance: str,
local_result: dict[str, object],
cloud_result: dict[str, object],
) -> dict[str, object]:
"""Merge local and hosted IR phases without replaying local actions."""
merged = deepcopy(cloud_result)
def phase_documents(
local_documents: object,
cloud_documents: object,
) -> dict[str, object]:
local_mapping = local_documents if isinstance(local_documents, Mapping) else {}
cloud_mapping = cloud_documents if isinstance(cloud_documents, Mapping) else {}
return {
**{
f"local_{key}": deepcopy(value)
for key, value in local_mapping.items()
},
**{
f"cloud_{key}": deepcopy(value)
for key, value in cloud_mapping.items()
},
}
function_configs = phase_documents(
local_result.get("function_configs"),
cloud_result.get("function_configs"),
)
command_plans = phase_documents(
local_result.get("command_plans"),
cloud_result.get("command_plans"),
)
successful = {"succeeded", "no_action"}
local_ok = str(local_result.get("status") or "") in successful
cloud_ok = str(cloud_result.get("status") or "") in successful
local_request_id = str(local_result.get("request_id") or "")
cloud_request_id = str(cloud_result.get("request_id") or "")
merged.update(
{
"utterance": utterance,
"status": (
"succeeded"
if local_ok and cloud_ok
else "partially_succeeded"
if local_ok or cloud_ok
else "failed"
),
"success": local_ok and cloud_ok,
"orchestration_route": "two_stage_nfc_peripheral",
"provider_used": (
f"{local_result.get('provider_used') or 'infrared_local_rules'}+"
f"{cloud_result.get('provider_used') or 'cloud_model'}"
),
"preview_request_id": local_request_id,
"supplement_of": local_request_id,
"supplement_request_id": cloud_request_id,
"decision_summary": [
*list(local_result.get("decision_summary") or []),
*list(cloud_result.get("decision_summary") or []),
"本地红外动作先行下发;云端只补充未解析子句。",
],
"function_configs": function_configs,
"command_plans": command_plans,
"function_config": next(iter(function_configs.values()), None),
"command_plan": next(iter(command_plans.values()), None),
"trace_events": [
*deepcopy(list(local_result.get("trace_events") or [])),
*deepcopy(list(cloud_result.get("trace_events") or [])),
],
"validation": [
*deepcopy(list(local_result.get("validation") or [])),
*deepcopy(list(cloud_result.get("validation") or [])),
],
"operation_results": [
*deepcopy(list(local_result.get("operation_results") or [])),
*deepcopy(list(cloud_result.get("operation_results") or [])),
],
"phase_execution_reports": {
"infrared_local": deepcopy(local_result.get("execution_report")),
"infrared_cloud": deepcopy(cloud_result.get("execution_report")),
},
"execution_report": deepcopy(cloud_result.get("execution_report"))
or deepcopy(local_result.get("execution_report")),
"peripheral": deepcopy(
cloud_result.get("peripheral") or local_result.get("peripheral")
),
}
)
for key in (
"execution_mode",
"control_transport",
"local_control_budget_ms",
"local_control_elapsed_ms",
"local_control_within_budget",
"natural_language_routing",
):
if local_result.get(key) is not None:
merged[key] = deepcopy(local_result[key])
return merged
@staticmethod
def _apply_nfc_command_denials(
result: dict[str, object],
entries: list[dict[str, object]],
) -> dict[str, object]:
"""Expose command-level NFC denials without discarding allowed work."""
if not entries:
return result
denied_actions = [
{
"index": entry.get("index"),
"clause": entry.get("clause"),
"capability_id": capability.get("id"),
"device_id": capability.get("device_id"),
"device_name": capability.get("device_name"),
"action": capability.get("action"),
"slot": capability.get("slot"),
"reason": str(entry.get("reason") or "nfc_command_disabled"),
}
for entry in entries
for capability in [entry.get("capability")]
if isinstance(entry, dict) and isinstance(capability, dict)
]
if not denied_actions:
return result
result["denied_peripheral_actions"] = denied_actions
result["authorization_filtered"] = True
summaries = list(result.get("decision_summary") or [])
labels = "、".join(
str(item.get("clause") or item.get("action") or "未命名动作")
for item in denied_actions
)
summaries.append(
f"当前 NFC 未授权 {len(denied_actions)} 条红外命令,已过滤:{labels}。"
)
result["decision_summary"] = summaries
prior_status = str(result.get("status") or "failed")
if prior_status in {"succeeded", "partially_succeeded", "no_action"}:
result["status"] = "partially_succeeded"
result["success"] = False
execution_report = result.get("execution_report")
if isinstance(execution_report, dict):
spec = execution_report.get("spec")
if isinstance(spec, dict):
spec["status"] = "partially_succeeded"
return result
def _execute_request_with_peripherals(
self,
path: str,
common_arguments: dict[str, object],
*,
on_commands_validated: object | None = None,
) -> dict[str, object] | None:
"""Route IR-only or mixed IR/CAN requests without changing CAN parsing."""
local_stage_started = time.perf_counter()
utterance = str(common_arguments.get("utterance") or "").strip()
if self.service.is_mode_profile_configuration_request(utterance):
# Creating, saving, updating, or restoring a reusable mode is
# configuration, never a live-control command. Bypass the
# NFC/peripheral executor so a learned device name embedded in the
# mode sentence cannot claim it before SeatDemoService handles it.
mode_arguments = {
**common_arguments,
"execution_mode": "dry_run",
}
callback = (
on_commands_validated
if callable(on_commands_validated)
else None
)
if path == "/api/requests/preview":
return self.service.preview_native_can(
**mode_arguments,
on_commands_validated=callback,
)
return self.service.process(
**mode_arguments,
_on_commands_validated=callback,
)
catalog = self.extension.list_peripheral_capabilities()
nfc_snapshot = self.nfc.snapshot()
partition = partition_active_peripheral_intent(
utterance,
catalog,
nfc_snapshot,
)
entries = list(partition.get("peripheral_actions") or [])
residual = str(partition.get("residual_utterance") or "").strip()
unresolved = list(partition.get("unresolved_peripheral_clauses") or [])
denied = list(partition.get("denied_peripheral_actions") or [])
parsed_peripheral_clauses = list(
dict.fromkeys(
str(entry.get("clause") or "").strip()
for entry in entries
if isinstance(entry, dict) and str(entry.get("clause") or "").strip()
)
)
unresolved_peripheral_clauses = list(
dict.fromkeys(str(item).strip() for item in unresolved if str(item).strip())
)
natural_language_routing: dict[str, object] = {
"original_utterance": utterance,
"parsed_clauses": parsed_peripheral_clauses,
"resolved_clauses": parsed_peripheral_clauses,
"unresolved_clauses": unresolved_peripheral_clauses,
}
provider = str(common_arguments.get("provider") or "auto")
conversation_id = (
str(common_arguments["conversation_id"])
if common_arguments.get("conversation_id")
else None
)
execution_mode = str(common_arguments.get("execution_mode") or "dry_run")
callback = on_commands_validated if callable(on_commands_validated) else None
peripheral_validation_published = False
peripheral_validation_elapsed_ms: float | None = None
def publish_peripheral_validation(result: dict[str, object]) -> None:
nonlocal peripheral_validation_elapsed_ms, peripheral_validation_published
peripheral_validation_published = True
peripheral_validation_elapsed_ms = round(
(time.perf_counter() - local_stage_started) * 1000,
3,
)
if callback is None:
return
validated = deepcopy(result)
validated["status"] = "processing"
validated["phase"] = "commands_validated"
validated.update(
{
"local_control_budget_ms": LOCAL_CONTROL_BUDGET_MS,
"local_control_elapsed_ms": peripheral_validation_elapsed_ms,
"local_control_within_budget": (
peripheral_validation_elapsed_ms < LOCAL_CONTROL_BUDGET_MS
),
"natural_language_routing": deepcopy(
natural_language_routing
),
}
)
callback(validated)
infrared_result: dict[str, object] | None = None
can_result: dict[str, object] | None = None
cloud_supplement_error: str | None = None
# Preserve the legacy all-peripheral planner when there is no hosted
# ambiguity to resolve. It retains explicit delay/order semantics.
if (
entries
and residual
and not unresolved
and not self._residual_has_vehicle_language(residual)
and bool(
re.search(
r"然后|随后|接着|之后|"
r"(?:\d+(?:\.\d+)?|[一二三四五六七八九十两半]+)"
r"(?:毫秒|秒(?:钟)?|分钟|小时|ms(?![A-Za-z])|s(?![A-Za-z]))",
residual,
flags=re.IGNORECASE,
)
)
):
planned = self._execute_active_peripheral_request(
utterance,
provider=provider,
conversation_id=conversation_id,
execution_mode=execution_mode,
on_commands_validated=publish_peripheral_validation,
natural_language_routing=natural_language_routing,
)
if planned is not None:
if not peripheral_validation_published:
publish_peripheral_validation(planned)
if peripheral_validation_elapsed_ms is not None:
planned.update(
{
"local_control_budget_ms": LOCAL_CONTROL_BUDGET_MS,
"local_control_elapsed_ms": (
peripheral_validation_elapsed_ms
),
"local_control_within_budget": (
peripheral_validation_elapsed_ms
< LOCAL_CONTROL_BUDGET_MS
),
"natural_language_routing": deepcopy(
natural_language_routing
),
}
)
return planned
if not entries and not denied and not unresolved:
result = self._execute_active_peripheral_request(
utterance,
provider=provider,
conversation_id=conversation_id,
execution_mode=execution_mode,
on_commands_validated=publish_peripheral_validation,
natural_language_routing=natural_language_routing,
)
if result is not None:
if not peripheral_validation_published:
publish_peripheral_validation(result)
if peripheral_validation_elapsed_ms is not None:
result.update(
{
"local_control_budget_ms": LOCAL_CONTROL_BUDGET_MS,
"local_control_elapsed_ms": (
peripheral_validation_elapsed_ms
),
"local_control_within_budget": (
peripheral_validation_elapsed_ms
< LOCAL_CONTROL_BUDGET_MS
),
"natural_language_routing": deepcopy(
natural_language_routing
),
}
)
return result
# Compile, publish and execute every deterministic IR action before
# any hosted planner is contacted.
if entries:
peripheral_validation_published = False
infrared_result = self._execute_local_peripheral_actions(
utterance,
entries,
provider=provider,
conversation_id=conversation_id,
execution_mode=execution_mode,
on_commands_validated=publish_peripheral_validation,
)
if not peripheral_validation_published:
publish_peripheral_validation(infrared_result)
if peripheral_validation_elapsed_ms is not None:
infrared_result.update(
{
"local_control_budget_ms": LOCAL_CONTROL_BUDGET_MS,
"local_control_elapsed_ms": (
peripheral_validation_elapsed_ms
),
"local_control_within_budget": (
peripheral_validation_elapsed_ms
< LOCAL_CONTROL_BUDGET_MS
),
"natural_language_routing": deepcopy(
natural_language_routing
),
}
)
# The native preview is also strictly local. Its own manifest records
# unresolved CAN clauses for the later supplement endpoint.
if residual:
can_arguments = {
**common_arguments,
"utterance": residual,
"_cloud_instruction_context": natural_language_routing,
}
can_validation_callback = callback
if callback is not None and infrared_result is not None:
def publish_combined_validation(
validated_can: dict[str, object],
) -> None:
validated = self._merge_can_and_peripheral_results(
utterance,
validated_can,
infrared_result,
entries,
)
validated = self._apply_nfc_command_denials(validated, denied)
validated["status"] = "processing"
validated["phase"] = "commands_validated"
callback(validated)
can_validation_callback = publish_combined_validation
if path == "/api/requests/preview":
can_result = self.service.preview_native_can(
**can_arguments,
on_commands_validated=can_validation_callback,
)
else:
can_result = self.service.process(
**can_arguments,
_on_commands_validated=can_validation_callback,
)
routed = can_result.get("natural_language_routing")
if isinstance(routed, dict):
natural_language_routing = deepcopy(routed)
# Hosted IR planning is deliberately last. Only the unresolved IR
# text is submitted, while the trusted context retains the full
# sentence and the clauses already handled above.
if unresolved and provider != "local":
try:
candidates = list(partition.get("unresolved_candidates") or [])
if not candidates:
raise ExtensionIntegrationError(
"没有可供云端补充选择的已学习红外能力。"
)
cloud_utterance = ",".join(unresolved_peripheral_clauses)
plan_result = self.extension.plan(
{
"utterance": cloud_utterance,
"peripherals": candidates,
"vehicle_devices": [],
"provider": provider,
"natural_language_routing": {
**natural_language_routing,
"cloud_input_utterance": cloud_utterance,
},
}
)
if plan_result.get("needs_confirmation"):
raise ExtensionIntegrationError(
str(
plan_result.get("question")
or "请明确要控制的红外动作。"
)
)
cloud_steps = list(plan_result.get("steps") or [])
if not cloud_steps:
raise ExtensionIntegrationError(
"云端模型没有命中已学习红外能力。"
)
candidate_by_id = {
str(item.get("id")): {
**item,
"parse_source": "cloud_model",
}
for item in candidates
if isinstance(item, dict) and item.get("id")
}
selected_cloud = [
candidate_by_id[str(step.get("capability_id"))]
for step in cloud_steps
if isinstance(step, dict)
and step.get("kind") == "peripheral"
and str(step.get("capability_id")) in candidate_by_id
]
if not selected_cloud:
raise ExtensionIntegrationError(
"云端模型没有返回允许执行的已学习红外能力。"
)
conversation_turn = 1
if conversation_id:
context = self.service.conversations.snapshot(conversation_id)
conversation_turn = int(context.get("turn_count", 0)) + 1
cloud_infrared_result = (
self.extension.execute_peripheral_plan_request(
utterance,
plan_result,
selected_cloud,
conversation_id=conversation_id,
conversation_turn=conversation_turn,
execution_mode=execution_mode,
provider_requested=provider,
)
)
next_index = max(
[
int(entry.get("index", 0))
for entry in entries
if isinstance(entry, dict)
],
default=-1,
) + 1
entries.extend(
{
"index": next_index + index,
"clause": cloud_utterance,
"capability": capability,
}
for index, capability in enumerate(selected_cloud)
)
infrared_result = (
self._merge_peripheral_phase_results(
utterance,
infrared_result,
cloud_infrared_result,
)
if infrared_result is not None
else cloud_infrared_result
)
except ExtensionIntegrationError as exc:
cloud_supplement_error = str(exc)
if infrared_result is None and can_result is None:
raise
if can_result is not None and infrared_result is not None:
result = self._merge_can_and_peripheral_results(
utterance,
can_result,
infrared_result,
entries,
)
elif can_result is not None:
result = can_result
result["utterance"] = utterance
elif infrared_result is not None:
result = infrared_result
elif unresolved and provider == "local":
local_elapsed_ms = round(
(time.perf_counter() - local_stage_started) * 1000,
3,
)
request_id = f"req-local-preview-{os.urandom(8).hex()}"
result = {
"request_id": request_id,
"preview_request_id": request_id,
"utterance": utterance,
"conversation_id": conversation_id,
"status": "no_action",
"success": False,
"phase": "deterministic_preview",
"provider_requested": provider,
"provider_used": "local_rules",
"execution_mode": execution_mode,
"decision_summary": [
"本地规则未生成控制;仅未解析子句等待云端补充。"
],
"function_config": None,
"function_configs": {},
"command_plan": None,
"command_plans": {},
"trace_events": [],
"validation": [],
"execution_report": None,
"operation_results": [],
"parsed_control_signatures": [],
"preview_elapsed_ms": local_elapsed_ms,
"local_control_budget_ms": LOCAL_CONTROL_BUDGET_MS,
"local_control_elapsed_ms": local_elapsed_ms,
"local_control_within_budget": (
local_elapsed_ms < LOCAL_CONTROL_BUDGET_MS
),
"supplement_required": True,
}
else:
labels = "、".join(
str(item.get("clause") or "未命名动作")
for item in denied
if isinstance(item, dict)
)
raise ExtensionIntegrationError(
f"当前 NFC 未授权执行以下红外命令:{labels}。"
)
result["natural_language_routing"] = deepcopy(natural_language_routing)
for key in ("parsed_clauses", "resolved_clauses", "unresolved_clauses"):
value = natural_language_routing.get(key)
if isinstance(value, list):
result[key] = deepcopy(value)
if cloud_supplement_error:
result["cloud_supplement_error"] = {
"stage": "unresolved_peripheral_clauses",
"message": cloud_supplement_error,
}
result["status"] = "partially_succeeded"
result["success"] = False
result["decision_summary"] = [
*list(result.get("decision_summary") or []),
"明确控制已由本地规则先行处理;未解析的红外子句云端补充失败。",
]
result = self._apply_nfc_command_denials(result, denied)
self.service.persist_composed_result(result)
if callback is not None:
callback(result)
return result
def _stream_control_request(
self,
path: str,
supplement_match: re.Match[str] | None,
payload: dict[str, object],
common_arguments: dict[str, object],
*,
source: str,
) -> None:
"""Stream validated command plans before physical execution finishes."""
sync_vehicle_task = source not in NON_DISPATCH_DISPLAY_SOURCES
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]) -> None:
nonlocal stream_open
if not stream_open:
return
content = (json.dumps(event, ensure_ascii=False) + "\n").encode(
"utf-8"
)
try:
self.wfile.write(content)
self.wfile.flush()
except (BrokenPipeError, ConnectionResetError):
# The submitted control request must continue even if the
# browser closes while the physical execution is in progress.
stream_open = False
def on_commands_validated(result: dict[str, object]) -> None:
emit({"type": "validated_commands", "payload": result})
def emit_terminal_error(
code: str,
message: str,
*,
detail: str | None = None,
extra: dict[str, object] | None = None,
) -> None:
"""Publish one terminal failure using can_read_trans outcome terms."""
error = {
"code": code,
"message": message,
"outcome": "failed_or_blocked",
**({"detail": detail} if detail else {}),
}
if sync_vehicle_task:
self.vehicle_display_sync.update_task(
status="failed",
utterance=utterance,
source=source,
error=error,
)
emit({
"type": "error",
"error": code,
"message": message,
"outcome": "failed_or_blocked",
**({"detail": detail} if detail else {}),
**(extra or {}),
})
utterance = str(common_arguments.get("utterance") or "")
try:
result = None
if path in {"/api/requests", "/api/requests/preview"}:
result = self._execute_request_with_peripherals(
path,
common_arguments,
on_commands_validated=on_commands_validated,
)
if result is None:
if path == "/api/requests/preview":
result = self.service.preview_native_can(
**common_arguments,
on_commands_validated=on_commands_validated,
)
elif supplement_match:
result = self.service.supplement_native_can(
supplement_match.group("request_id"),
provider=(
str(payload["provider"])
if payload.get("provider")
else None
),
approval_token=os.getenv(
"COGNITIVE_AGENT_HARDWARE_APPROVAL_TOKEN"
),
hardware_client_id=(
str(payload["hardware_client_id"])
if payload.get("hardware_client_id")
else None
),
hardware_access_bypass=self._hardware_access_bypass_allowed(),
reasoning_effort=(
str(payload["reasoning_effort"])
if payload.get("reasoning_effort") is not None
else None
),
on_commands_validated=on_commands_validated,
)
else:
result = self.service.process(
**common_arguments,
_on_commands_validated=on_commands_validated,
)
utterance = str(result.get("utterance") or utterance)
if sync_vehicle_task:
self.vehicle_display_sync.update_task(
status=str(result.get("status", "succeeded")),
utterance=utterance,
source=source,
result=result,
)
emit({"type": "result", "payload": result})
except HardwareAccessRequiredError as exc:
emit_terminal_error(
"hardware_access_required",
str(exc),
extra={"access_state": exc.state},
)
except HardwareBusyError as exc:
emit_terminal_error(
"hardware_busy",
str(exc),
extra={"retry_after_seconds": exc.retry_after_seconds},
)
except HardwareSafetyError as exc:
emit_terminal_error("hardware_safety_gate", str(exc))
except CanGatewayError as exc:
emit_terminal_error("can_transport_failed", str(exc))
except GatewayError as exc:
emit_terminal_error("gscu_unavailable", str(exc))
except ExtensionIntegrationError as exc:
emit_terminal_error("extension_request_ambiguous", str(exc))
except (ValueError, json.JSONDecodeError) as exc:
emit_terminal_error("invalid_request", str(exc))
except OpenAIIntentError as exc:
self.log_error("OpenAI intent request failed: %s", exc)
emit_terminal_error(
"openai_request_failed",
"GPT 意图解析失败,请检查 API Key、模型权限或网络状态。",
detail=str(exc),
)
except Exception as exc: # noqa: BLE001 - stream a terminal event
self.log_error("streaming request failed: %s", exc)
emit_terminal_error(
"internal_error",
"请求处理失败,请查看服务端日志。",
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"
)
mode_profile_draft_context = payload.get("mode_profile_draft_context")
parsed_mode_update = parse_mode_profile_update(
message,
expected_mode_name=(
mode_profile_draft_context.get("name")
if isinstance(mode_profile_draft_context, Mapping)
else None
),
)
matching_mode_profile_draft_context = (
dict(mode_profile_draft_context)
if isinstance(mode_profile_draft_context, Mapping)
and parsed_mode_update is not None
and normalize_mode_profile_name(parsed_mode_update[0])
== normalize_mode_profile_name(
mode_profile_draft_context.get("name")
)
else None
)
requested_vehicle_id = (
str(payload["vehicle_id"]) if payload.get("vehicle_id") else None
)
common_arguments: dict[str, object] = {
"utterance": message,
# Explicit controls take the deterministic preview path first.
# ``auto`` permits a second phase only when that preview reports
# genuinely unresolved clauses; fully local requests never invoke
# a hosted model.
"provider": "auto",
"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": requested_vehicle_id,
"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"),
**(
{
"mode_profile_draft_context": (
matching_mode_profile_draft_context
)
}
if matching_mode_profile_draft_context is not None
else {}
),
}
capability_catalog = self._assistant_capability_catalog()
try:
capability_snapshot = capability_catalog.snapshot(
vehicle_id=requested_vehicle_id
)
except ValueError as exc:
self._json(
{"error": "invalid_request", "message": str(exc)},
HTTPStatus.BAD_REQUEST,
)
return
capability_context = capability_catalog.compact_context(capability_snapshot)
scene_capability_context = capability_catalog.scene_composition_context(
capability_snapshot
)
fast_answer = capability_catalog.fast_answer(message, capability_snapshot)
quick_control_provider = getattr(capability_catalog, "quick_controls", None)
quick_controls = (
quick_control_provider(capability_snapshot)
if callable(quick_control_provider)
else ()
)
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},
)
local_preview_result: dict[str, object] | None = None
def preview_control(
instruction: str,
on_commands_validated: Callable[[dict[str, object]], None],
) -> dict[str, object]:
"""Run only deterministic phases and retain their merge evidence."""
nonlocal local_preview_result
self.vehicle_display_sync.update_task(
status="running",
utterance=message,
source=source,
)
preview_arguments = {
**common_arguments,
"utterance": instruction,
"provider": "local",
}
result = self._execute_request_with_peripherals(
"/api/requests/preview",
preview_arguments,
on_commands_validated=on_commands_validated,
)
if result is None:
result = self.service.preview_native_can(
**preview_arguments,
on_commands_validated=on_commands_validated,
)
local_preview_result = result
self.vehicle_display_sync.update_task(
status=str(result.get("status") or "succeeded"),
utterance=message,
source=source,
result=result,
)
return result
def supplement_control(
instruction: str,
on_commands_validated: Callable[[dict[str, object]], None],
assistant_plan: Mapping[str, object] | None = None,
) -> dict[str, object]:
"""Execute a Codex proposal for unresolved text without replay."""
preview = local_preview_result
if preview is None:
raise CodexAssistantError("local control preview is missing")
routing_value = preview.get("natural_language_routing")
routing = (
deepcopy(routing_value)
if isinstance(routing_value, Mapping)
else {
"original_utterance": message,
"parsed_clauses": [],
"resolved_clauses": [],
"unresolved_clauses": [instruction],
}
)
routing["cloud_input_utterance"] = instruction
excluded_signatures = {
tuple(item)
for item in preview.get("parsed_control_signatures", [])
if isinstance(item, list)
}
def publish_supplement(validated: dict[str, object]) -> None:
merged_event = self.service._merge_native_can_phases(
preview,
validated,
)
merged_event.update(
{
"phase": "supplement_commands_validated",
"preview_request_id": preview.get("request_id"),
"supplement_of": preview.get("request_id"),
"natural_language_routing": deepcopy(routing),
}
)
on_commands_validated(merged_event)
process_arguments = {
key: value
for key, value in common_arguments.items()
if key
not in {
"utterance",
"provider",
"mode_profile_draft_context",
}
}
if assistant_plan is not None:
supplement = self.service.process(
instruction,
provider="local",
**process_arguments,
_excluded_control_signatures=excluded_signatures,
_model_parse_source="codex_supplement",
_allow_empty_control_result=True,
_on_commands_validated=publish_supplement,
_assistant_plan=dict(assistant_plan),
_cloud_instruction_context=routing,
)
else:
supplement_arguments = {
**common_arguments,
"utterance": instruction,
"provider": "local",
}
supplement = self._execute_request_with_peripherals(
"/api/requests",
supplement_arguments,
on_commands_validated=publish_supplement,
)
if supplement is None:
supplement = self.service.process(
instruction,
provider="local",
**process_arguments,
_excluded_control_signatures=excluded_signatures,
_model_parse_source="codex_supplement",
_allow_empty_control_result=True,
_on_commands_validated=publish_supplement,
_cloud_instruction_context=routing,
)
merged = self.service._merge_native_can_phases(preview, supplement)
merged.update(
{
"phase": "two_stage_complete",
"utterance": message,
"preview_request_id": preview.get("request_id"),
"supplement_of": preview.get("request_id"),
"supplement_request_id": supplement.get("request_id"),
"supplement_required": False,
"natural_language_routing": routing,
}
)
self.service.persist_composed_result(merged)
self.vehicle_display_sync.update_task(
status=str(merged.get("status") or "succeeded"),
utterance=message,
source=source,
result=merged,
)
return merged
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,
# Codex has already normalized this agent-tool request. Keep
# the safety execution pass deterministic; explicit user text
# uses preview_control/supplement_control above instead.
"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,
preview_control=preview_control,
supplement_control=supplement_control,
capability_snapshot=capability_snapshot,
capability_context=capability_context,
scene_capability_context=scene_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,
authoritative_topic=(
getattr(fast_answer, "topic", None) if fast_answer else None
),
authoritative_suggestions=(
[
item.as_dict()
for item in getattr(fast_answer, "suggestions", ())
]
if fast_answer
else []
),
quick_control_commands=[item.command for item in quick_controls],
mode_profile_draft_context=mode_profile_draft_context,
)
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": "小迪暂时没有响应,请稍后再试。",
}
)
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."""
try:
content_length = int(self.headers.get("Content-Length", "0"))
except ValueError:
content_length = 0
if not self._is_unproxied_loopback_request():
self._json(
{"error": "local_access_required", "message": "仅允许本机 Codex 工具调用。"},
HTTPStatus.FORBIDDEN,
)
self._drain_rejected_request_body(content_length, max_bytes=32 * 1024)
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,
)
self._drain_rejected_request_body(content_length, max_bytes=32 * 1024)
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)
return
try:
payload = self._read_json_body(max_bytes=4096)
self._json(
self.extension.save_infrared_slot(match.group("slot"), payload),
HTTPStatus.OK,
)
except (GatewayError, ValueError, json.JSONDecodeError) as exc:
self._json(
{"success": False, "error": "invalid_request", "message": str(exc)},
HTTPStatus.BAD_REQUEST,
)
def do_DELETE(self) -> None: # noqa: N802 - stdlib handler API
parsed_url = urlparse(self.path)
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:
self._json(
self.extension.delete_infrared_slot(slot_match.group("slot")),
HTTPStatus.OK,
)
except GatewayError as exc:
self._json(
{"success": False, "error": "invalid_request", "message": str(exc)},
HTTPStatus.BAD_REQUEST,
)
return
firmware_artifact_match = re.fullmatch(
r"/api/firmware-workbench/artifacts/(?P<artifact_id>[a-z0-9-]+)",
path,
)
if firmware_artifact_match is not None:
try:
self._json(
self.firmware_workbench.delete_artifact(
firmware_artifact_match.group("artifact_id")
),
HTTPStatus.OK,
)
except FirmwareWorkbenchError as exc:
self._json(
{"error": exc.code, "message": str(exc)},
HTTPStatus(exc.status),
)
return
prefix = "/api/mode-profiles/"
if not path.startswith(prefix):
self._json({"error": "not_found"}, HTTPStatus.NOT_FOUND)
return
mode_id = path.removeprefix(prefix).strip()
vehicle_id = str(parse_qs(parsed_url.query).get("vehicle_id", [""])[0]) or None
try:
self._json(
self.service.delete_user_mode_profile(mode_id, vehicle_id),
HTTPStatus.OK,
)
except KeyError:
self._json(
{"error": "not_found", "message": "未找到可删除的用户自定义模式。"},
HTTPStatus.NOT_FOUND,
)
except PermissionError as exc:
self._json(
{"error": "preset_mode_protected", "message": str(exc)},
HTTPStatus.FORBIDDEN,
)
except ValueError as exc:
self._json(
{"error": "invalid_request", "message": str(exc)},
HTTPStatus.BAD_REQUEST,
)
def _handle_mode_profile_post(self, path: str) -> None:
execute_match = re.fullmatch(
r"/api/mode-profiles/(?P<mode_id>[a-z0-9][a-z0-9_-]{0,63})/execute",
path,
)
if execute_match is not None:
self._handle_mode_profile_execute(execute_match.group("mode_id"))
return
create_mode = path == "/api/mode-profiles"
mode_id = path.removeprefix("/api/mode-profiles/").strip()
if not create_mode and not mode_id:
self._json(
{"error": "invalid_request", "message": "模式编号不能为空。"},
HTTPStatus.BAD_REQUEST,
)
return
try:
payload = self._read_json_body(max_bytes=16 * 1024)
arguments = (
str(payload.get("name", "")),
str(payload.get("instruction", "")),
str(payload.get("source_request_id", "")),
str(payload.get("vehicle_id", "")) or None,
)
result = (
self.service.create_mode_profile_card(*arguments)
if create_mode
else self.service.save_mode_profile_card(mode_id, *arguments)
)
self._json(result, HTTPStatus.CREATED if create_mode else HTTPStatus.OK)
except KeyError:
self._json(
{"error": "not_found", "message": "未找到需要编辑的模式卡片。"},
HTTPStatus.NOT_FOUND,
)
except (ValueError, json.JSONDecodeError) as exc:
self._json(
{"error": "invalid_request", "message": str(exc)},
HTTPStatus.BAD_REQUEST,
)
def _handle_mode_profile_draft_trial(self) -> None:
try:
payload = self._read_json_body(max_bytes=16 * 1024)
result = self.service.trial_mode_profile_draft(
str(payload.get("name", "")),
str(payload.get("instruction", "")),
str(payload.get("source_request_id", "")),
str(payload.get("vehicle_id", "")) or None,
)
self._json(result, HTTPStatus.OK)
except (ValueError, json.JSONDecodeError) as exc:
self._json(
{"error": "invalid_request", "message": str(exc)},
HTTPStatus.BAD_REQUEST,
)
def _saved_mode_infrared_entries(
self,
profile: object,
) -> list[dict[str, object]]:
command_flow = getattr(profile, "command_flow", None)
if not isinstance(command_flow, dict):
return []
configurations = command_flow.get("function_configs") or {}
extension_config = None
if isinstance(configurations, dict):
extension_config = configurations.get("self_extension")
if not isinstance(extension_config, dict):
extension_config = next(
(
config
for config in configurations.values()
if isinstance(config, dict)
and str((config.get("spec") or {}).get("domain"))
== "self_extension"
),
None,
)
operations = (
(extension_config.get("spec") or {}).get("operations") or []
if isinstance(extension_config, dict)
else []
)
if not operations:
return []
snapshot = self.nfc.snapshot()
card = snapshot.get("card") if isinstance(snapshot, dict) else None
active_uid = re.sub(
r"[^0-9A-Fa-f]",
"",
str((card or {}).get("uid") or ""),
).upper()
if not active_uid or str((card or {}).get("action") or "present") != "present":
raise ExtensionIntegrationError(
"该模式包含 NFC 红外指令,请先放置生成此模式时绑定的 NFC 卡。"
)
catalog = self.extension.list_peripheral_capabilities()
entries: list[dict[str, object]] = []
for index, operation in enumerate(operations):
if not isinstance(operation, dict) or operation.get("capability") != "infrared.emit":
continue
parameters = operation.get("parameters") or {}
target = operation.get("target") or {}
try:
slot = int(parameters.get("slot"))
except (TypeError, ValueError) as exc:
raise ExtensionIntegrationError(
"已保存模式包含无效的红外槽位。"
) from exc
device_id = str(target.get("device") or "").strip()
capability = next(
(
item
for item in catalog
if item.get("status") == "learned"
and item.get("enabled", True) is True
and int(item.get("slot") or 0) == slot
and str(item.get("device_id") or "") == device_id
and re.sub(
r"[^0-9A-Fa-f]",
"",
str(item.get("card_uid") or ""),
).upper()
== active_uid
),
None,
)
if capability is None:
raise ExtensionIntegrationError(
f"当前 NFC 未授权模式中的红外命令:{device_id} / IR {slot}。"
)
entries.append(
{
"index": index,
"clause": str(parameters.get("action") or f"IR {slot}"),
"capability": {
**capability,
"parse_source": "saved_mode_command_flow",
},
}
)
return entries
def _handle_mode_profile_execute(self, mode_id: str) -> None:
utterance = ""
source = "web_console_mode_card"
try:
payload = self._read_json_body(max_bytes=16 * 1024)
vehicle_id = str(payload.get("vehicle_id", "")) or None
profile = self.service.get_mode_profile_card(mode_id, vehicle_id)
utterance = profile.name
source = str(payload.get("client_type") or source)
execution_mode = str(payload.get("execution_mode", "dry_run"))
control_transport = str(
payload.get("control_transport", "wired_can")
)
conversation_id = (
str(payload["conversation_id"])
if payload.get("conversation_id")
else None
)
hardware_client_id = (
str(payload["hardware_client_id"])
if payload.get("hardware_client_id")
else None
)
self.vehicle_display_sync.update_task(
status="running",
utterance=utterance,
source=source,
)
# Validate the complete saved flow, including the live NFC
# binding, before any transport is allowed to execute.
entries = self._saved_mode_infrared_entries(profile)
can_result = self.service.execute_mode_profile_card(
mode_id,
conversation_id=conversation_id,
execution_mode=execution_mode,
approval_token=os.getenv(
"COGNITIVE_AGENT_HARDWARE_APPROVAL_TOKEN"
),
vehicle_id=vehicle_id,
simulation_fault=str(payload.get("simulation_fault", "normal")),
hardware_client_id=hardware_client_id,
hardware_access_bypass=self._hardware_access_bypass_allowed(),
control_transport=control_transport,
)
if entries:
infrared_result = self._execute_local_peripheral_actions(
utterance,
entries,
provider="saved_mode_command_flow",
conversation_id=conversation_id,
execution_mode=execution_mode,
)
result = self._merge_can_and_peripheral_results(
utterance,
can_result,
infrared_result,
entries,
)
result["provider_used"] = "saved_mode_command_flow"
result["orchestration_route"] = "saved_mode_command_flow"
result["mode_profile"] = profile.public_dict()
self.service.persist_composed_result(result)
else:
result = can_result
self.vehicle_display_sync.update_task(
status=str(result.get("status") or "succeeded"),
utterance=utterance,
source=source,
result=result,
)
self._json(result, HTTPStatus.OK)
except KeyError:
self._json(
{"error": "not_found", "message": "未找到需要执行的模式卡片。"},
HTTPStatus.NOT_FOUND,
)
except HardwareAccessRequiredError as exc:
self._json(
{
"error": "hardware_access_required",
"message": str(exc),
"access_state": exc.state,
},
HTTPStatus.FORBIDDEN,
)
except HardwareBusyError as exc:
self._json(
{
"error": "hardware_busy",
"message": str(exc),
"retry_after_seconds": exc.retry_after_seconds,
},
HTTPStatus.CONFLICT,
)
except (HardwareSafetyError, CanGatewayError, GatewayError) as exc:
if utterance:
self.vehicle_display_sync.update_task(
status="failed",
utterance=utterance,
source=source,
error={"code": "mode_execution_failed", "message": str(exc)},
)
self._json(
{"error": "mode_execution_failed", "message": str(exc)},
HTTPStatus.SERVICE_UNAVAILABLE,
)
except (ExtensionIntegrationError, ValueError, json.JSONDecodeError) as exc:
if utterance:
self.vehicle_display_sync.update_task(
status="failed",
utterance=utterance,
source=source,
error={"code": "invalid_mode_command_flow", "message": str(exc)},
)
self._json(
{"error": "invalid_mode_command_flow", "message": str(exc)},
HTTPStatus.BAD_REQUEST,
)
def _handle_extension_post(self, path: str) -> None:
action_map = {
"/api/extension/gscu/info": "info",
"/api/extension/gscu/learn": "learn",
"/api/extension/gscu/cancel": "cancel",
"/api/extension/gscu/emit": "emit",
"/api/extension/gscu/read": "read",
"/api/extension/gscu/write": "write",
"/api/extension/gscu/restart": "restart",
"/api/extension/gscu/reset": "reset",
"/api/extension/gscu/wifi-lock": "wifi_lock",
"/api/extension/gscu/raw": "raw",
}
try:
payload = self._read_json_body(max_bytes=256 * 1024)
if path in {
"/api/extension/gscu/erase",
"/api/extension/gscu/erase-all",
}:
result = self.extension.erase_all_infrared(dict(payload))
self.vehicle_display_sync.update_extension(
action={"type": "erase_all"},
result=result,
)
self._json(result)
return
action = action_map.get(path)
if action:
self._handle_extension_gateway(action, payload)
return
if path == "/api/extension/gscu/config":
result = self.extension.configure_tcp_gateway(dict(payload))
self.vehicle_display_sync.update_extension(
action={
"type": "gateway_config",
"listen_host": result.get("listen_host"),
"port": result.get("port"),
},
result=result,
)
self._json(result)
return
if path == "/api/extension/display-sync":
catalog = payload.get("peripherals", payload.get("catalog", []))
if not isinstance(catalog, list) or len(catalog) > 248:
raise ValueError("外设能力目录必须是至多 248 项的数组。")
durable_sync = self.extension.sync_peripheral_catalog(catalog)
sync_result = {
"status": "ready",
"count": len(catalog),
"learned_count": durable_sync["count"],
}
self.vehicle_display_sync.update_extension(
catalog=catalog,
action={
"type": "hardware_detected",
"device": payload.get("device"),
},
result=sync_result,
)
self._json({"success": True, **sync_result})
return
if path == "/api/extension/assistant/plan":
self.vehicle_display_sync.update_extension(
catalog=payload.get("peripherals", payload.get("catalog", [])),
action={"type": "plan", "command": payload.get("utterance", "")},
)
result = self.extension.plan(payload)
self.vehicle_display_sync.update_extension(
result=result,
)
self._json(result)
return
if path == "/api/extension/vehicle/execute":
result = self.extension.execute_vehicle(
payload,
self.service,
hardware_access_bypass=self._hardware_access_bypass_allowed(),
)
self.vehicle_display_sync.update_task(
status=str(result.get("status", "succeeded")),
utterance=str(payload.get("command", payload.get("utterance", ""))),
source=str(payload.get("client_type", "head_unit")),
result=result,
)
self.vehicle_display_sync.update_extension(
catalog=payload.get("catalog") if "catalog" in payload else None,
action={"type": "vehicle_execute"},
result=result,
)
self._json(result)
return
self._json({"error": "not_found"}, HTTPStatus.NOT_FOUND)
except GatewayError as exc:
self._json(
{"success": False, "error": "gscu_unavailable", "message": str(exc)},
HTTPStatus.SERVICE_UNAVAILABLE,
)
except ExtensionIntegrationError as exc:
self._json(
{"success": False, "error": "extension_unavailable", "message": str(exc)},
HTTPStatus.SERVICE_UNAVAILABLE,
)
except HardwareAccessRequiredError as exc:
self._json(
{
"success": False,
"error": "hardware_access_required",
"message": str(exc),
"access_state": exc.state,
},
HTTPStatus.FORBIDDEN,
)
except HardwareBusyError as exc:
self._json(
{
"success": False,
"error": "hardware_busy",
"message": str(exc),
"retry_after_seconds": exc.retry_after_seconds,
},
HTTPStatus.CONFLICT,
{"Retry-After": str(exc.retry_after_seconds)},
)
except HardwareSafetyError as exc:
self._json(
{"success": False, "error": "hardware_safety_gate", "message": str(exc)},
HTTPStatus.FORBIDDEN,
)
except OpenAIIntentError as exc:
self.log_error("extension vehicle intent request failed: %s", exc)
self._json(
{
"success": False,
"error": "openai_request_failed",
"message": "车端 GPT 意图解析失败,请检查模型配置。",
},
HTTPStatus.BAD_GATEWAY,
)
except (ValueError, json.JSONDecodeError) as exc:
self._json(
{"success": False, "error": "invalid_request", "message": str(exc)},
HTTPStatus.BAD_REQUEST,
)
except Exception as exc: # noqa: BLE001 - keep extension APIs responsive
self.log_error("self-extension request failed: %s", exc)
self._json(
{
"success": False,
"error": "internal_error",
"message": "自扩展服务处理失败,请查看服务端日志。",
},
HTTPStatus.INTERNAL_SERVER_ERROR,
)
def _handle_calibration_post(self, path: str) -> None:
if not self._require_calibration_admin():
return
try:
payload = self._read_json_body(max_bytes=256 * 1024)
action_map = {
"/api/calibration/can/connect": lambda: self.calibration.connect_can(
dict(payload.get("can", payload))
),
"/api/calibration/start": lambda: self.calibration.calibrate(
None if payload.get("axis", "all") == "all" else int(payload["axis"])
),
"/api/calibration/move": lambda: self.calibration.move(
int(payload["axis"]), int(payload["permille"])
),
"/api/calibration/seek": lambda: self.calibration.seek(
int(payload["axis"]), bool(payload.get("far"))
),
"/api/calibration/stop": self.calibration.stop,
"/api/calibration/session/reset": self.calibration.reset_virtual_session,
"/api/calibration/settings": lambda: self.calibration.update_settings(
dict(payload)
),
"/api/calibration/mqtt/connect": self.calibration.connect_mqtt,
"/api/calibration/mqtt/disconnect": self.calibration.disconnect_mqtt,
"/api/calibration/mqtt/test": self.calibration.mqtt_test,
"/api/calibration/video/start": self.calibration.start_video,
"/api/calibration/video/stop": self.calibration.stop_video,
}
action = action_map.get(path)
if action is None:
self._json({"error": "not_found"}, HTTPStatus.NOT_FOUND)
return
requested_mode = str(
dict(payload.get("can", {})).get(
"mode", self.calibration.snapshot()["transport"]["mode"]
)
)
motion_paths = {
"/api/calibration/can/connect",
"/api/calibration/start",
"/api/calibration/move",
"/api/calibration/seek",
"/api/calibration/stop",
}
if path in motion_paths and requested_mode == "hardware":
if not self._require_local_decision_point():
return
self._json({"ok": True, "state": action()})
except CalibrationApiError as exc:
self._json(
{"ok": False, "error": "calibration_error", "message": str(exc)},
HTTPStatus(exc.status),
)
except (KeyError, TypeError, ValueError, json.JSONDecodeError) as exc:
self._json(
{"ok": False, "error": "invalid_request", "message": str(exc)},
HTTPStatus.BAD_REQUEST,
)
except Exception as exc: # noqa: BLE001 - keep calibration APIs responsive
self.log_error("self-calibration request failed: %s", exc)
self._json(
{
"ok": False,
"error": "internal_error",
"message": "自标定服务处理失败,请查看服务端日志。",
},
HTTPStatus.INTERNAL_SERVER_ERROR,
)
def _handle_self_repair_post(self, path: str) -> None:
if not self._require_self_repair_admin():
return
try:
payload = self._read_json_body(max_bytes=256 * 1024)
physical_paths = {
"/api/self-repair/hardware/start",
"/api/self-repair/hardware/stop",
"/api/self-repair/emergency-stop",
"/api/self-repair/firmware/build-flash",
"/api/self-repair/open-log",
}
if (
path in physical_paths
or (
path in {
"/api/self-repair/runtime",
"/api/self-repair/healing/start",
"/api/self-repair/healing/reset",
}
and str(payload.get("execution_mode", "shadow")) == "bench"
)
) and not self._require_local_decision_point():
return
if path == "/api/self-repair/healing/start":
self._json({"ok": True, "healing": self.repair.start_healing(payload)})
return
if path == "/api/self-repair/runtime":
self._json({"ok": True, "runtime": self.repair.set_runtime(payload)})
return
if path == "/api/self-repair/runtime/reset-statistics":
self._json({
"ok": True,
"runtime": self.repair.reset_runtime_statistics(),
})
return
if path == "/api/self-repair/healing/run-all":
self._json({"ok": True, "batch": self.repair.start_batch(payload)})
return
if path == "/api/self-repair/healing/reset":
self._json({"ok": True, **self.repair.reset_healing(payload)})
return
if path == "/api/self-repair/hardware/start":
self._json({"ok": True, "session": self.repair.start_hardware()})
return
if path == "/api/self-repair/hardware/stop":
self._json({"ok": True, "session": self.repair.stop_hardware()})
return
if path == "/api/self-repair/hardware/self-test":
self._json(
{"ok": True, "self_test": self.repair.run_hardware_self_test()}
)
return
if path == "/api/self-repair/emergency-stop":
self._json({"ok": True, "request": self.repair.emergency_stop()})
return
if path == "/api/self-repair/snapshot":
self._json({"ok": True, "path": str(self.repair.save_snapshot())})
return
if path == "/api/self-repair/firmware/build-flash":
self._json(
{
"ok": True,
"firmware": self.repair.build_and_flash_firmware(payload),
}
)
return
if path == "/api/self-repair/open-log":
opened = self.repair.open_log_dir()
self._json({"ok": opened})
return
self._json({"error": "not_found"}, HTTPStatus.NOT_FOUND)
except (KeyError, TypeError, ValueError, json.JSONDecodeError) as exc:
self._json(
{"ok": False, "error": "invalid_request", "message": str(exc)},
HTTPStatus.BAD_REQUEST,
)
except Exception as exc: # noqa: BLE001 - keep self-repair API responsive
self.log_error("self-repair request failed: %s", exc)
self._json(
{
"ok": False,
"error": "internal_error",
"message": "自修复服务处理失败,请查看服务端日志。",
},
HTTPStatus.INTERNAL_SERVER_ERROR,
)
def _require_self_repair_admin(self, parsed_url: ParseResult | None = None) -> bool:
role = self.headers.get("X-Cognitive-Agent-Role", "").strip().lower()
if not role and parsed_url is not None:
role = str(parse_qs(parsed_url.query).get("role", [""])[0]).lower()
if hmac.compare_digest(role, "admin"):
return True
self._json(
{"error": "admin_required", "message": "自修复仅对 admin 开放。"},
HTTPStatus.FORBIDDEN,
)
return False
def _require_calibration_admin(self, parsed_url: ParseResult | None = None) -> bool:
role = self.headers.get("X-Cognitive-Agent-Role", "").strip().lower()
if not role and parsed_url is not None:
role = str(parse_qs(parsed_url.query).get("role", [""])[0]).lower()
if hmac.compare_digest(role, "admin"):
return True
self._json(
{
"error": "admin_required",
"message": "自标定仅对 admin 开放。",
},
HTTPStatus.FORBIDDEN,
)
return False
def _serve_calibration_events(self) -> None:
self.send_response(HTTPStatus.OK)
self.send_header("Content-Type", "text/event-stream; charset=utf-8")
self.send_header("Cache-Control", "no-cache")
self.send_header("Connection", "keep-alive")
self.send_header("X-Accel-Buffering", "no")
self._send_security_headers()
self.end_headers()
version = -1
try:
while True:
state = self.calibration.wait_for_version(version, 12.0)
version = state["version"]
data = json.dumps(state, ensure_ascii=False, separators=(",", ":"))
self.wfile.write(f"data: {data}\n\n".encode("utf-8"))
self.wfile.flush()
except (BrokenPipeError, ConnectionResetError, ConnectionAbortedError):
pass
def _serve_calibration_video(self) -> None:
self._json(
{
"error": "legacy_video_transport_removed",
"message": "ZED 画面已改为浏览器直连 WebRTC/WHEP。",
},
HTTPStatus.GONE,
)
def _handle_extension_gateway(self, action: str, payload: dict[str, object]) -> None:
try:
result = self.extension.execute_gateway(action, dict(payload))
if action != "info":
self.vehicle_display_sync.update_extension(
action={"type": action, **dict(payload)},
result=result,
)
self._json(result)
except GatewayError as exc:
self._json(
{"success": False, "error": "gscu_unavailable", "message": str(exc)},
HTTPStatus.SERVICE_UNAVAILABLE,
)
def _extension_health(self) -> dict[str, object]:
return {**self.extension.health(), "nfc": self.nfc.snapshot()}
def _handle_hardware_access_post(self, path: str) -> None:
local_path = path.startswith("/api/local/")
if local_path and not self._require_local_decision_point():
return
try:
payload = self._read_json_body(max_bytes=4096)
client_id = str(payload.get("client_id", ""))
if path == "/api/hardware/access/request":
status = self.service.hardware_access.request_access(
client_id,
str(payload.get("label", "")),
self._client_key(),
)
self._json(
status,
HTTPStatus.OK if status["state"] == "approved" else HTTPStatus.ACCEPTED,
)
return
if path == "/api/hardware/access/release":
self._json(self.service.hardware_access.release(client_id))
return
if path == "/api/local/hardware-access/approve":
self._json(self.service.hardware_access.approve(client_id))
return
if path == "/api/local/hardware-access/deny":
self._json(self.service.hardware_access.deny(client_id))
return
if path == "/api/local/hardware-access/revoke":
self._json(
self.service.hardware_access.revoke(client_id or None)
)
return
except (ValueError, json.JSONDecodeError) as exc:
self._json(
{"error": "invalid_request", "message": str(exc)},
HTTPStatus.BAD_REQUEST,
)
def _read_json_body(self, max_bytes: int = 32768) -> dict[str, object]:
content_length = int(self.headers.get("Content-Length", "0"))
if content_length <= 0 or content_length > max_bytes:
raise ValueError(f"request body must be between 1 and {max_bytes} bytes")
payload = json.loads(self.rfile.read(content_length).decode("utf-8"))
if not isinstance(payload, dict):
raise ValueError("request body must be a JSON object")
return payload
def _handle_firmware_workbench_post(self, path: str) -> None:
try:
payload = self._read_json_body(max_bytes=16 * 1024)
if path == "/api/firmware-workbench/tasks":
result = self.firmware_workbench.start_task(
str(payload.get("instruction", "")),
str(payload.get("baseline_id", "time_only")),
)
self._json(result, HTTPStatus.ACCEPTED)
return
task_action_match = re.fullmatch(
r"/api/firmware-workbench/tasks/(?P<task_id>[a-z0-9-]+)/(?P<action>cancel|retry)",
path,
)
if task_action_match:
task_id = task_action_match.group("task_id")
if task_action_match.group("action") == "cancel":
self._json(self.firmware_workbench.cancel_task(task_id))
else:
self._json(
self.firmware_workbench.retry_task(task_id), HTTPStatus.ACCEPTED
)
return
artifact_publish_match = re.fullmatch(
r"/api/firmware-workbench/artifacts/(?P<artifact_id>[a-z0-9-]+)/publish",
path,
)
if artifact_publish_match:
self._json(
self.firmware_workbench.publish_artifact(
artifact_publish_match.group("artifact_id")
)
)
return
self._json({"error": "not_found"}, HTTPStatus.NOT_FOUND)
except (ValueError, json.JSONDecodeError) as exc:
self._json(
{"error": "invalid_request", "message": str(exc)},
HTTPStatus.BAD_REQUEST,
)
except FirmwareWorkbenchError as exc:
self._json({"error": exc.code, "message": str(exc)}, HTTPStatus(exc.status))
def _serve_firmware_download(self, kind: str, item_id: str) -> None:
try:
if kind == "release":
path, filename = self.firmware_workbench.resolve_release_download(item_id)
else:
path, filename = self.firmware_workbench.resolve_artifact_download(item_id)
content = path.read_bytes()
self.send_response(HTTPStatus.OK)
self.send_header("Content-Type", "application/zip")
self.send_header("Content-Length", str(len(content)))
self.send_header("Cache-Control", "private, no-store")
self.send_header(
"Content-Disposition",
f"attachment; filename*=UTF-8''{quote(filename, safe='.-_')}",
)
self._send_security_headers()
self.end_headers()
self.wfile.write(content)
except FirmwareWorkbenchError as exc:
self._json({"error": exc.code, "message": str(exc)}, HTTPStatus(exc.status))
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"}:
self._json(
{
"error": "unsupported_media_type",
"message": "语音听写接口仅接受 16 位 PCM WAV 音频。",
},
HTTPStatus.UNSUPPORTED_MEDIA_TYPE,
)
return
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,
)
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),
**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"):
result = {
**result,
"routing_text": raw_text,
"peripheral_routing": peripheral_routing,
}
self._json(result)
except SpeechRecognitionError as exc:
self._json(
{"error": exc.code, "message": str(exc)},
HTTPStatus(exc.status),
)
except (TypeError, ValueError):
self._json(
{"error": "invalid_audio", "message": "录音请求格式无效。"},
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,
*,
max_bytes: int = 2 * 1024 * 1024,
) -> None:
"""Gracefully finish an early-rejected POST without processing its payload.
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. Callers choose a strict size ceiling, and authorization or
concurrency gates still run before any payload is read or decoded.
"""
if content_length <= 0 or content_length > max_bytes:
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
or PUBLIC_OTA_DOWNLOAD_PATH.fullmatch(path)
):
return True
if self._session_username() is not None:
return True
if path.startswith("/api/"):
self._json(
{
"error": "authentication_required",
"message": "请登录后访问。",
},
HTTPStatus.UNAUTHORIZED,
)
else:
requested = path if path.startswith("/") else "/"
self._redirect(f"/login?next={quote(requested, safe='/')}")
return False
def _login(self) -> None:
if not self.auth.config.enabled:
self._json({"enabled": False, "authenticated": True})
return
client_key = self._client_key()
retry_after = self.auth.login_failures.retry_after(client_key)
if retry_after:
self._json(
{
"error": "login_rate_limited",
"message": "登录尝试过多,请稍后再试。",
},
HTTPStatus.TOO_MANY_REQUESTS,
{"Retry-After": str(retry_after)},
)
return
try:
content_length = int(self.headers.get("Content-Length", "0"))
if content_length <= 0 or content_length > 4096:
raise ValueError("invalid login body")
payload = json.loads(self.rfile.read(content_length).decode("utf-8"))
if not isinstance(payload, dict):
raise ValueError("invalid login body")
username = str(payload.get("username", ""))[:128]
password = str(payload.get("password", ""))[:1024]
except (ValueError, json.JSONDecodeError):
self._json(
{"error": "invalid_request", "message": "登录请求格式无效。"},
HTTPStatus.BAD_REQUEST,
)
return
password_valid = verify_password(password, self.auth.config.password_hash)
username_valid = hmac.compare_digest(
username.encode("utf-8"),
self.auth.config.username.encode("utf-8"),
)
if not (username_valid and password_valid):
self.auth.login_failures.record(client_key)
self._json(
{"error": "invalid_credentials", "message": "用户名或密码错误。"},
HTTPStatus.UNAUTHORIZED,
)
return
self.auth.login_failures.clear(client_key)
token = self.auth.sessions.issue(self.auth.config.username)
attributes = [
f"{self.auth.config.cookie_name}={token}",
"Path=/",
"HttpOnly",
"SameSite=Strict",
f"Max-Age={self.auth.config.session_ttl_seconds}",
]
if self._is_https():
attributes.append("Secure")
self._json(
{
"authenticated": True,
"username": self.auth.config.username,
},
headers={"Set-Cookie": "; ".join(attributes)},
)
def _logout(self) -> None:
attributes = [
f"{self.auth.config.cookie_name}=",
"Path=/",
"HttpOnly",
"SameSite=Strict",
"Max-Age=0",
]
if self._is_https():
attributes.append("Secure")
self._json(
{"authenticated": False},
headers={"Set-Cookie": "; ".join(attributes)},
)
def _session_username(self) -> str | None:
if not self.auth.config.enabled:
return None
cookie_header = self.headers.get("Cookie", "")
if not cookie_header or len(cookie_header) > 4096:
return None
try:
cookies = SimpleCookie()
cookies.load(cookie_header)
morsel = cookies.get(self.auth.config.cookie_name)
return self.auth.sessions.verify(morsel.value if morsel else "")
except (CookieError, ValueError):
return None
def _client_key(self) -> str:
address = self.client_address[0]
direct_address = ipaddress.ip_address(address)
if self.auth.config.trust_proxy_headers and direct_address.is_loopback:
forwarded = (
self.headers.get("CF-Connecting-IP")
or self.headers.get("X-Forwarded-For", "").split(",", 1)[0]
).strip()
try:
address = str(ipaddress.ip_address(forwarded))
except ValueError:
pass
return address
def _is_unproxied_loopback_request(self) -> bool:
"""Distinguish the local decision console from traffic via the tunnel."""
try:
direct_address = ipaddress.ip_address(self.client_address[0])
except ValueError:
return False
proxy_headers = (
"Forwarded",
"X-Forwarded-For",
"X-Real-IP",
"CF-Connecting-IP",
)
return direct_address.is_loopback and not any(
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
self._json(
{
"error": "local_access_required",
"message": "真实台架审批与停止权限仅在台架本机开放。",
},
HTTPStatus.FORBIDDEN,
)
return False
def _is_https(self) -> bool:
if not self.auth.config.trust_proxy_headers:
return False
if self.headers.get("X-Forwarded-Proto", "").lower() == "https":
return True
return '"scheme":"https"' in self.headers.get("CF-Visitor", "").replace(" ", "")
def _serve_request(self, request_id: str) -> None:
if not REQUEST_ID_PATTERN.fullmatch(request_id):
self._json({"error": "invalid_request_id"}, HTTPStatus.BAD_REQUEST)
return
result_path = self.service.artifact_root / request_id / "result.json"
if not result_path.is_file():
self._json({"error": "not_found"}, HTTPStatus.NOT_FOUND)
return
self._json(json.loads(result_path.read_text(encoding="utf-8")))
def _serve_static(self, path: str, query: str = "") -> None:
relative_path = "index.html" if path in {"", "/"} else path.lstrip("/")
versioned = bool(parse_qs(query).get("v"))
self._serve_static_file(relative_path, versioned=versioned)
def _serve_static_file(self, relative_path: str, *, versioned: bool = False) -> None:
candidate = (STATIC_ROOT / relative_path).resolve()
try:
candidate.relative_to(STATIC_ROOT.resolve())
except ValueError:
self._json({"error": "not_found"}, HTTPStatus.NOT_FOUND)
return
if not candidate.is_file() and (
Path(relative_path).suffix
or relative_path.startswith(("assets/", "vendor/", "icons/"))
):
self._json({"error": "not_found"}, HTTPStatus.NOT_FOUND)
return
if not candidate.is_file():
candidate = STATIC_ROOT / "index.html"
content_type, _ = mimetypes.guess_type(candidate.name)
content = candidate.read_bytes()
self.send_response(HTTPStatus.OK)
resolved_content_type = content_type or "application/octet-stream"
if resolved_content_type.startswith("text/") or resolved_content_type in {
"application/javascript",
"application/json",
"application/manifest+json",
"image/svg+xml",
}:
resolved_content_type = f"{resolved_content_type}; charset=utf-8"
self.send_header(
"Content-Type",
resolved_content_type,
)
self.send_header("Content-Length", str(len(content)))
cache_control = "no-cache"
if relative_path.startswith(("vendor/", "icons/")) or (
relative_path.startswith("assets/") and versioned
):
cache_control = "public, max-age=31536000, immutable"
elif relative_path.startswith("assets/"):
cache_control = "no-cache"
elif candidate.name == "service-worker.js":
cache_control = "no-store"
elif candidate.name in {"index.html", "login.html"}:
cache_control = "no-cache"
self.send_header("Cache-Control", cache_control)
if candidate.name == "service-worker.js":
self.send_header("Service-Worker-Allowed", "/")
self._send_security_headers()
self.end_headers()
self.wfile.write(content)
def _serve_gesture_model(self) -> None:
path = self.gesture.model_path
if not path.is_file():
self._json(
{"error": "gesture_model_unavailable", "message": "手势模型文件不可用。"},
HTTPStatus.NOT_FOUND,
)
return
content = path.read_bytes()
self.send_response(HTTPStatus.OK)
self.send_header("Content-Type", "application/octet-stream")
self.send_header("Content-Length", str(len(content)))
self.send_header("Cache-Control", "private, max-age=3600")
self._send_security_headers()
self.end_headers()
self.wfile.write(content)
def _json(
self,
payload: object,
status: HTTPStatus = HTTPStatus.OK,
headers: dict[str, str] | None = None,
) -> None:
content = json.dumps(payload, ensure_ascii=False).encode("utf-8")
self.send_response(status)
self.send_header("Content-Type", "application/json; charset=utf-8")
self.send_header("Content-Length", str(len(content)))
self.send_header("Cache-Control", "no-store")
for name, value in (headers or {}).items():
self.send_header(name, value)
self._send_security_headers()
self.end_headers()
self.wfile.write(content)
def _redirect(self, location: str) -> None:
self.send_response(HTTPStatus.SEE_OTHER)
self.send_header("Location", location)
self.send_header("Cache-Control", "no-store")
self._send_security_headers()
self.end_headers()
def _send_security_headers(self) -> None:
allow_same_origin_frame = (
unquote(urlparse(self.path).path) == "/firmware-workbench.html"
)
self.send_header("X-Content-Type-Options", "nosniff")
self.send_header(
"X-Frame-Options", "SAMEORIGIN" if allow_same_origin_frame else "DENY"
)
self.send_header("Referrer-Policy", "no-referrer")
self.send_header(
"Permissions-Policy",
"camera=(self), microphone=(self), geolocation=(), payment=(), usb=()",
)
self.send_header(
"Content-Security-Policy",
"default-src 'self'; "
"script-src 'self' 'wasm-unsafe-eval'; "
"style-src 'self'; "
"img-src 'self' data: blob:; "
"media-src 'self' blob:; "
"frame-src 'self' http://*:3001; "
"connect-src 'self' blob: http://172.20.10.6:8889; "
"object-src 'none'; base-uri 'none'; frame-ancestors "
+ ("'self'" if allow_same_origin_frame else "'none'"),
)
if self._is_https():
self.send_header(
"Strict-Transport-Security",
"max-age=31536000; includeSubDomains",
)
def main() -> None:
parser = argparse.ArgumentParser(description="Run the cabin orchestration demo")
parser.add_argument("--host", default=os.getenv("HOST", "127.0.0.1"))
parser.add_argument("--port", type=int, default=int(os.getenv("PORT", "8000")))
parser.add_argument(
"--scene-catalog",
type=Path,
default=Path(
os.getenv("N7_SCENE_CATALOG_PATH", "").strip()
or DEFAULT_MODE_SCENE_CATALOG_PATH
),
help="JSON path synchronized from preset and user-created mode cards",
)
parser.add_argument(
"--scene-server-id",
default=os.getenv("N7_SCENE_SERVER_ID", "n7-cognitive-agent"),
help="server_id written to the N7 scene catalog",
)
args = parser.parse_args()
scene_catalog_status = SeatDemoHandler.service.configure_mode_scene_catalog(
args.scene_catalog,
server_id=args.scene_server_id,
)
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()
SeatDemoHandler.calibration.start()
SeatDemoHandler.repair.start()
if args.host in {"0.0.0.0", "::"}:
lan_address = _lan_ipv4_address()
print(f"Cabin orchestration demo (local): http://127.0.0.1:{args.port}", flush=True)
if lan_address:
print(
f"Cabin orchestration demo (LAN): http://{lan_address}:{args.port}",
flush=True,
)
print(
"LAN mode: devices on reachable networks can use the GPT-backed API.",
flush=True,
)
else:
print(f"Cabin orchestration demo: http://{args.host}:{args.port}", flush=True)
if scene_catalog_status["state"] == "ready":
print(
"N7 mode scene catalog: "
f"{scene_catalog_status['path']} "
f"({scene_catalog_status['scene_count']} scenes)",
flush=True,
)
else:
print(
"N7 mode scene catalog synchronization unavailable: "
f"{scene_catalog_status.get('last_error') or 'unknown error'}",
file=sys.stderr,
flush=True,
)
hardware_execution_ready = any(
(
SeatDemoHandler.service.hardware_settings.configured,
SeatDemoHandler.service.vehicle_can_settings.configured,
SeatDemoHandler.service.vehicle_mqtt_settings.configured,
)
)
print(
"Execution modes: dry_run + simulated_hardware "
f"({'enabled' if SeatDemoHandler.service.simulated_hardware_enabled else 'locked'}) "
"+ hardware "
f"({'configured' if hardware_execution_ready else 'locked'})",
flush=True,
)
try:
server.serve_forever()
except KeyboardInterrupt:
pass
finally:
server.server_close()
SeatDemoHandler.speech.close()
SeatDemoHandler.assistant.close()
SeatDemoHandler.service.close()
SeatDemoHandler.extension.close()
SeatDemoHandler.nfc.close()
SeatDemoHandler.calibration.close()
SeatDemoHandler.repair.close()
SeatDemoHandler.firmware_workbench.close()
if __name__ == "__main__":
main()