"""Codex App Server orchestration for the web-console assistant.""" from __future__ import annotations import hmac import json import logging import os import queue import re import secrets import threading import time import uuid from collections import OrderedDict from collections.abc import Callable, Mapping, Sequence from copy import deepcopy from dataclasses import dataclass, field from pathlib import Path from typing import Protocol from cognitive_agent.application.assistant_plan_guard import MAX_PLAN_REQUESTS from cognitive_agent.application.mode_profile_intent import ( is_mode_profile_query, mode_creation_source_name, normalize_mode_profile_name, parse_mode_profile_create, parse_mode_profile_delete, parse_mode_profile_update, ) from cognitive_agent.application.mode_creation_seat_policy import ( ModeCreationSeatPolicyError, project_seed_plan, ) from cognitive_agent.application.mode_schedule import ModeSchedule from cognitive_agent.application.mode_schedule_service import public_schedule_status from cognitive_agent.application.scene_composer import ( SCENE_COMPOSER_INSTRUCTIONS, SCENE_PLANNER_VERSION, SCENE_TEMPLATE_AUGMENTATION_INSTRUCTIONS, SCHEDULE_COMPOSER_INSTRUCTIONS, SceneCompositionError, build_scene_output_schema, normalize_scene_output, scene_context_for_mode_creation, scene_has_explicit_control_object, scene_required_native_fallbacks, scene_required_peripheral_refs, scene_semantic_guidance, ) from cognitive_agent.application.scene_templates import ( PRESERVE_BASE_EXTERNAL_LIGHT_TEMPLATE_IDS, normalize_scene_template_name, ) from cognitive_agent.application.schedule_intent import is_schedule_stop, needs_mode_schedule LOGGER = logging.getLogger(__name__) INTERNAL_SCENE_BUILDER_MARKER = "根据该场景名称使用当前可控原子能力" class CodexAssistantError(RuntimeError): """Safe failure raised by the embedded Codex assistant.""" class CodexVehicleToolError(CodexAssistantError): """Safe failure returned to the vehicle MCP bridge.""" @dataclass(frozen=True) class ActiveCodexTurn: thread_id: str turn_id: str events: queue.Queue[dict[str, object]] class CodexClient(Protocol): @property def available(self) -> bool: ... def start(self, callback_url: str, callback_token: str) -> None: ... def close(self) -> None: ... @property def status(self) -> str: ... def start_thread( self, *, developer_instructions: str, ephemeral: bool = True, replace_developer_instructions: bool = False, timeout: float | None = None, ) -> str: ... def start_turn( self, thread_id: str, text: str, request_token: str, *, application_context: Mapping[str, object] | None = None, ) -> ActiveCodexTurn: ... def start_structured_turn( self, thread_id: str, text: str, *, application_context: Mapping[str, object], output_schema: Mapping[str, object], timeout: float | None = None, ) -> ActiveCodexTurn: ... def finish_turn( self, turn: ActiveCodexTurn, timeout: float | None = None, ) -> None: ... def interrupt( self, turn: ActiveCodexTurn, timeout: float | None = None, ) -> None: ... def unsubscribe_thread( self, thread_id: str, timeout: float | None = None, ) -> None: ... def abandon_turn(self, turn: ActiveCodexTurn) -> None: ... EmitEvent = Callable[[dict[str, object]], bool | None] # The third argument is an optional, untrusted structured plan. Legacy/local # calls omit it; the application validates it against the original request # before it can reach the normal orchestration pipeline. ControlExecutor = Callable[..., dict[str, object]] CapabilityQuery = Callable[[Mapping[str, object], str], dict[str, object]] @dataclass class _Conversation: thread_id: str touched_at: float = field(default_factory=time.monotonic) lock: threading.Lock = field(default_factory=threading.Lock) in_use: int = 0 @dataclass class _SceneFlight: ready: threading.Event = field(default_factory=threading.Event) plan: dict[str, object] | None = None error: BaseException | None = None @dataclass class _VehicleTurnContext: original_message: str execute_control: ControlExecutor authorization: _VehicleAuthorization capability_snapshot: Mapping[str, object] query_capabilities: CapabilityQuery events: queue.Queue[dict[str, object]] = field(default_factory=queue.Queue) lock: threading.Lock = field(default_factory=threading.Lock) called: bool = False capability_queries: int = 0 action_succeeded: bool | None = None action_state: str | None = None action_result: Mapping[str, object] | None = None @dataclass(frozen=True) class _VehicleAuthorization: instruction: str | None reason: str class _ScenePlannerFallback(RuntimeError): """Internal signal that the general Codex path should handle this request.""" class _ScenePlannerTimeout(RuntimeError): """Internal signal that the bounded scene-planning deadline expired.""" class _ScenePlannerOverloaded(RuntimeError): """Internal signal that all bounded scene-planning slots are busy.""" class _ScenePlannerCancelled(RuntimeError): """Internal signal that the browser stopped accepting streamed events.""" _CONTROL_VERB_PATTERN = re.compile( r"打开|开启|关闭|关掉|关上|启动|停止|设置|调节|调整|调高|调低|升高|降低|" r"增加|减少|切换|进入|退出|复位|移动|伸出|收回|开到|关到|调到|降到|升到|" r"拉开|拉上|锁车|上锁|解锁|执行|运行|启用|停用|保存|记住|恢复|召回|调用|" r"前移|后退|前进|调直|放倒|抬高|放低|半开|标定|抬到|降下|升起|" r"帮我|替我|给我|请把|请将" ) _CONTROL_VALUE_PATTERN = re.compile(r"-?\d+(?:\.\d+)?(?:%|%|度|档|秒|毫秒|ms)", re.IGNORECASE) _SUPPORT_QUERY_PATTERN = re.compile( r"支持|能否|能不能|可不可以|有没有|是否|哪些(?:设备|能力|功能)|能做什么" ) _DENIAL_PATTERN = re.compile( r"不要|无需|不用|禁止|不许|不得|取消操作|" r"(?:不|别)(?:执行|下发|发送|操作|控制|调整)|" r"(?:只|仅)(?:需|要)?(?:做)?(?:解析|预览|展示|显示)|" r"(?:只|仅)(?:需|要)?(?:给出|返回)(?:解析(?:结果)?|控制(?:指令|命令))|" r"只回答|仅回答" ) _KNOWLEDGE_PATTERN = re.compile( r"为什么|什么是|解释|介绍|分析|科普|教程|如何|怎么做|" r"如果|假如|假设|举例|示例|翻译|改写|总结|复述|引用|造句|" r"故事|剧本|小说|作文|角色|主角|这句话|以下内容|" r"忽略.{0,12}(规则|指令|提示)|系统提示|调用工具" ) _NON_VEHICLE_WORK_PATTERN = re.compile( r"文件|代码|源码|网页|浏览器|文档|数据库|目录|仓库|项目|终端|命令行|日志" ) _MODE_EDITOR_REQUEST_ID_PATTERN = re.compile(r"^req-[a-z0-9-]{6,100}$") _HOT_DISCOMFORT_PATTERN = re.compile(r"热|闷热|好热|太晒") _COLD_DISCOMFORT_PATTERN = re.compile(r"冷|好冷") _IMPLICIT_HOT_REQUEST_PATTERN = re.compile( r"^(?:(?:我|这里|车里|车内|车上|座舱|舱内)(?:感觉|觉得)?(?:有点|太|很|好)?|" r"(?:有点|太|很|好))(?:闷热|热|晒)(?:了|啊|呀)?" r"(?:(?:请)?(?:帮我)?(?:处理|调节|调整|解决)(?:一下)?|怎么办)?$" ) _IMPLICIT_COLD_REQUEST_PATTERN = re.compile( r"^(?:(?:我|这里|车里|车内|车上|座舱|舱内)(?:感觉|觉得)?(?:有点|太|很|好)?|" r"(?:有点|太|很|好))冷(?:了|啊|呀)?" r"(?:(?:请)?(?:帮我)?(?:处理|调节|调整|解决)(?:一下)?|怎么办)?$" ) _COOLING_INSTRUCTION = "打开空调,将全车温度设置为23度,风量设置为2档" _WARMING_INSTRUCTION = "打开空调,将全车温度设置为26度,风量设置为2档" _FAST_COMFORT_REASONS = frozenset({"implicit_hot_comfort", "implicit_cold_comfort"}) def _normalize_instruction(value: object) -> str: return re.sub(r"[\s,。!?!?、;;::]", "", str(value).lower()) def _safe_authoritative_suggestions( value: Sequence[Mapping[str, object]], ) -> list[dict[str, object]]: """Bound server-owned quick controls before placing them on the stream.""" suggestions: list[dict[str, object]] = [] seen_ids: set[str] = set() for raw in value[:6]: suggestion_id = str(raw.get("id") or "").strip() label = " ".join(str(raw.get("label") or "").strip().split()) command = " ".join(str(raw.get("command") or "").strip().split()) domain = str(raw.get("domain") or "").strip() if ( not re.fullmatch(r"[a-z0-9][a-z0-9-]{0,63}", suggestion_id) or suggestion_id in seen_ids or not label or len(label) > 80 or not command or len(command) > 160 or not re.fullmatch(r"[a-z][a-z0-9_]{0,39}", domain) ): continue raw_capability_ids = raw.get("capability_ids") capability_ids = ( [ capability_id for item in raw_capability_ids[:8] for capability_id in [str(item or "").strip()] if re.fullmatch(r"[a-z][a-z0-9_]*\.[a-z][a-z0-9_]*", capability_id) ] if isinstance(raw_capability_ids, Sequence) and not isinstance(raw_capability_ids, (str, bytes, bytearray)) else [] ) if not capability_ids: continue seen_ids.add(suggestion_id) suggestions.append( { "id": suggestion_id, "label": label, "command": command, "domain": domain, "capability_ids": list(dict.fromkeys(capability_ids)), } ) return suggestions def _mode_profile_create_parts(message: str) -> tuple[str, str] | None: """Return the requested scene name and optional explicit settings.""" return parse_mode_profile_create(message) def is_mode_profile_create_request(message: str) -> bool: """Return whether the original user text is a bounded mode-create request.""" return _mode_profile_create_parts(message) is not None def _mode_profile_update_parts( message: str, *, expected_mode_name: object | None = None, ) -> tuple[str, str] | None: """Return a bounded named-mode update without interpreting its action.""" return parse_mode_profile_update( message, expected_mode_name=expected_mode_name, ) def _validated_mode_profile_draft_context( value: Mapping[str, object] | None, *, message: str, ) -> dict[str, str] | None: """Validate the browser's opaque draft reference, never client actions.""" if value is None: return None update = _mode_profile_update_parts( message, expected_mode_name=(value.get("name") if isinstance(value, Mapping) else None), ) if update is None: return None if not isinstance(value, Mapping): raise ValueError("mode_profile_draft_context must be an object") referenced_name = " ".join(str(value.get("name") or "").strip().split()) if normalize_mode_profile_name(update[0]) != normalize_mode_profile_name( referenced_name ): return None required = {"name", "source_request_id", "vehicle_id"} if set(value) != required: raise ValueError("mode_profile_draft_context fields are invalid") clean = {key: " ".join(str(value.get(key) or "").strip().split()) for key in required} if ( not clean["name"] or len(clean["name"]) > 64 or not clean["vehicle_id"] or len(clean["vehicle_id"]) > 80 or not _MODE_EDITOR_REQUEST_ID_PATTERN.fullmatch(clean["source_request_id"]) ): raise ValueError("mode_profile_draft_context values are invalid") # The UI keeps the latest card selected while ordinary questions are # asked. A reference is only a routing hint when this exact sentence edits # that exact draft; all non-matching references returned above are inert. return clean def _catalog_exact_mode_match_count( catalog: Mapping[str, object], requested_name: str, ) -> int | None: """Count exact visible mode matches; ``None`` means catalog unavailable.""" needle = "".join(str(requested_name).casefold().split()) source_status = catalog.get("source_status") if "modes" not in catalog or ( isinstance(source_status, Mapping) and source_status.get("modes") != "ready" ): return None matched_ids: set[str] = set() raw_modes = catalog.get("modes", []) if not isinstance(raw_modes, Sequence) or isinstance( raw_modes, (str, bytes, bytearray), ): return None for raw_mode in raw_modes: if not isinstance(raw_mode, Mapping): continue candidates = [raw_mode.get("id"), raw_mode.get("name")] aliases = raw_mode.get("aliases", []) if isinstance(aliases, Sequence) and not isinstance( aliases, (str, bytes, bytearray), ): candidates.extend(aliases) if needle in { "".join(str(candidate or "").casefold().split()) for candidate in candidates if str(candidate or "").strip() }: matched_ids.add(str(raw_mode.get("id") or "")) matched_ids.discard("") subject = mode_creation_source_name(requested_name) if not matched_ids and normalize_mode_profile_name(subject) != needle: return _catalog_exact_mode_match_count(catalog, subject) return len(matched_ids) def _catalog_exact_mode_execution_id( catalog: Mapping[str, object], message: str, ) -> str | None: """Resolve an explicit saved-mode run without asking the model to route it.""" source_status = catalog.get("source_status") if not isinstance(source_status, Mapping) or source_status.get("modes") != "ready": return None normalized = re.sub(r"[\s,。!?!?、;;::]", "", str(message).casefold()) if not normalized: return None prefixes = ("", "请", "请帮我", "帮我", "麻烦", "麻烦帮我", "现在", "立即", "马上") verbs = ("进入", "启动", "执行", "开启", "打开", "切换到") suffixes = ("", "吧", "一下", "现在", "立即") matched_ids: set[str] = set() raw_modes = catalog.get("modes", []) if not isinstance(raw_modes, Sequence) or isinstance( raw_modes, (str, bytes, bytearray), ): return None for raw_mode in raw_modes: if not isinstance(raw_mode, Mapping) or raw_mode.get("assistant_callable") is not True: continue mode_id = str(raw_mode.get("id") or "").strip() if not mode_id: continue aliases = raw_mode.get("aliases", []) candidates = [ raw_mode.get("name"), *( aliases if isinstance(aliases, Sequence) and not isinstance(aliases, (str, bytes, bytearray)) else [] ), ] for candidate in candidates: mode_name = re.sub( r"[\s,。!?!?、;;::]", "", str(candidate or "").casefold(), ) if not mode_name: continue accepted = { f"{prefix}{verb}{mode_name}{suffix}" for prefix in prefixes for verb in verbs for suffix in suffixes } if normalized in accepted: matched_ids.add(mode_id) break return next(iter(matched_ids)) if len(matched_ids) == 1 else None def _catalog_exact_scene_template_plan( catalog: Mapping[str, object], requested_name: str, ) -> dict[str, object] | None: """Return one exact, callable template plan from the server-owned catalog.""" source_status = catalog.get("source_status") if ( not isinstance(source_status, Mapping) or source_status.get("scene_templates") != "ready" ): return None raw_templates = catalog.get("scene_templates") if not isinstance(raw_templates, Sequence) or isinstance( raw_templates, (str, bytes, bytearray) ): return None needle = normalize_scene_template_name(requested_name) matches: dict[str, Mapping[str, object]] = {} for raw_template in raw_templates: if ( not isinstance(raw_template, Mapping) or raw_template.get("assistant_callable") is not True ): continue candidates = [raw_template.get("id"), raw_template.get("name")] aliases = raw_template.get("aliases", []) if isinstance(aliases, Sequence) and not isinstance( aliases, (str, bytes, bytearray) ): candidates.extend(aliases) if needle and needle in { normalize_scene_template_name(candidate) for candidate in candidates if str(candidate or "").strip() }: template_id = str(raw_template.get("id") or "").strip() if template_id: matches[template_id] = raw_template if not matches: subject = mode_creation_source_name(requested_name) if normalize_scene_template_name(subject) != needle: return _catalog_exact_scene_template_plan(catalog, subject) if len(matches) != 1: return None template = next(iter(matches.values())) plan = template.get("plan") metadata = plan.get("metadata") if isinstance(plan, Mapping) else None if ( not isinstance(plan, Mapping) or not isinstance(metadata, Mapping) or metadata.get("source") != "scene_template" or metadata.get("template_id") != template.get("id") or metadata.get("template_revision") != template.get("template_revision") ): return None try: return json.loads( json.dumps(dict(plan), ensure_ascii=False, allow_nan=False) ) except (TypeError, ValueError): return None def _vehicle_result_succeeded(result: Mapping[str, object]) -> bool: """Only a terminal, fully successful service result completes a tool call.""" schedule = result.get("schedule_execution") return str(result.get("status") or "").lower() == "succeeded" or ( result.get("status") == "running" and result.get("orchestration_route") == "mode_schedule_execute" and isinstance(schedule, Mapping) and schedule.get("status") == "running" ) def _mode_profile_result_succeeded(result: Mapping[str, object]) -> bool: """Require a complete, non-physical mode preview before claiming success.""" if ( not _vehicle_result_succeeded(result) or str(result.get("execution_mode") or "").lower() != "dry_run" or result.get("hardware_execution_allowed") is not False or result.get("orchestration_route") != "mode_profile_create_preview" ): return False draft = result.get("mode_profile_draft") if not isinstance(draft, Mapping): return False operation_count = draft.get("operation_count") if ( not isinstance(operation_count, int) or isinstance(operation_count, bool) or operation_count <= 0 ): return False intent_ir = result.get("intent_ir") spec = intent_ir.get("spec") if isinstance(intent_ir, Mapping) else None actions = spec.get("actions") if isinstance(spec, Mapping) else None if not isinstance(actions, Sequence) or isinstance( actions, (str, bytes, bytearray), ): return False if len(actions) != operation_count or not all( isinstance(action, Mapping) for action in actions ): return False validation = result.get("validation", []) return not ( isinstance(validation, Sequence) and not isinstance(validation, (str, bytes, bytearray)) and any( isinstance(issue, Mapping) and issue.get("severity") == "error" for issue in validation ) ) def _mode_profile_management_result_succeeded( result: Mapping[str, object], route: str, ) -> bool: """Require complete control-plane evidence for a saved-mode mutation.""" if ( not _vehicle_result_succeeded(result) or str(result.get("execution_mode") or "").lower() != "dry_run" or result.get("hardware_execution_allowed") is not False or result.get("orchestration_route") != route ): return False evidence_key = ( "deleted_mode_profile" if route == "mode_profile_delete" else "mode_profile" ) evidence = result.get(evidence_key) return isinstance(evidence, Mapping) and bool(evidence.get("id")) def _vehicle_verification_status(result: Mapping[str, object]) -> str: """Normalize the service's execution evidence without inventing feedback.""" execution_mode = str(result.get("execution_mode") or "").strip().lower() if execution_mode in {"dry_run", "simulated_hardware"}: return "simulated" for key in ("execution_outcome", "verification_status"): value = str(result.get(key) or "") if value: return value execution_report = result.get("execution_report") if isinstance(execution_report, Mapping): spec = execution_report.get("spec") if isinstance(spec, Mapping): value = str(spec.get("verification_status") or "") if value: return value if result.get("physical_feedback_confirmed") is True: return "verified_success" return "unknown" def _vehicle_action_state(result: Mapping[str, object]) -> tuple[str, bool, str]: """Describe accepted, simulated and physically verified outcomes distinctly.""" if not _vehicle_result_succeeded(result): return "failed", False, "车辆控制未完成" if result.get("orchestration_route") == "mode_schedule_stop": return ("simulated" if result.get("execution_mode") != "hardware" else "cancelled"), True, "动态模式停止请求已处理" if result.get("status") == "running": return "running", True, "时间轴已启动,尚未完成所有阶段" execution_mode = str(result.get("execution_mode") or "").lower() if execution_mode == "dry_run": return "simulated", True, "车辆控制模拟编排已完成" if execution_mode == "simulated_hardware": return "simulated", True, "车辆控制已在模拟硬件完成" if _vehicle_verification_status(result) == "verified_success": return "verified", True, "车辆控制已完成并通过状态确认" return "submitted", True, "控制指令已提交,等待车辆状态确认" def _authorize_vehicle_request( message: str, control_terms: Sequence[str] = (), mode_profile_draft_context: Mapping[str, object] | None = None, ) -> _VehicleAuthorization: """Create a server-owned capability envelope from the original user text.""" expected_draft_name = ( mode_profile_draft_context.get("name") if isinstance(mode_profile_draft_context, Mapping) else None ) mode_profile_draft_context = _validated_mode_profile_draft_context( mode_profile_draft_context, message=message, ) normalized = "".join(str(message).lower().split()) if is_schedule_stop(message): return _VehicleAuthorization(message, "explicit_schedule_stop") mode_profile_create = _mode_profile_create_parts(message) mode_profile_update = _mode_profile_update_parts(message) mode_profile_delete = parse_mode_profile_delete(message) if ( mode_profile_create is None and mode_profile_update is None and mode_profile_delete is None and _DENIAL_PATTERN.search(normalized) ): return _VehicleAuthorization(None, "denied_by_user") if ( mode_profile_create is not None and not _KNOWLEDGE_PATTERN.search(normalized) and not _NON_VEHICLE_WORK_PATTERN.search(normalized) ): # Performative questions such as ``能不能帮我创建……`` are still # concrete configuration requests. The canonical parser has already # rejected bare capability questions and quoted examples, so handle # this before the broader question detector below. return _VehicleAuthorization(message, "explicit_mode_profile_create") if ( "模式" in normalized and re.search(r"构建|创建|新建|新增|生成|添加", normalized) and _KNOWLEDGE_PATTERN.search(normalized) ): return _VehicleAuthorization(None, "knowledge_or_non_vehicle_request") if is_mode_profile_query( normalized, expected_mode_name=expected_draft_name, ): # A question about a named mode may contain device names, control # verbs and numeric targets. It remains a question and must never # fall through to the registered-control executor. return _VehicleAuthorization(None, "mode_profile_query") if mode_profile_update is not None: return _VehicleAuthorization( message, ( "explicit_mode_profile_draft_update" if mode_profile_draft_context is not None else "explicit_mode_profile_update" ), ) if mode_profile_delete is not None: return _VehicleAuthorization(message, "explicit_mode_profile_delete") implicit_request = re.sub(r"[,。!?!?、]", "", normalized) if not _KNOWLEDGE_PATTERN.search(normalized): if _IMPLICIT_HOT_REQUEST_PATTERN.fullmatch(implicit_request): return _VehicleAuthorization(_COOLING_INSTRUCTION, "implicit_hot_comfort") if _IMPLICIT_COLD_REQUEST_PATTERN.fullmatch(implicit_request): return _VehicleAuthorization(_WARMING_INSTRUCTION, "implicit_cold_comfort") if _KNOWLEDGE_PATTERN.search(normalized) or _NON_VEHICLE_WORK_PATTERN.search( normalized ): return _VehicleAuthorization(None, "knowledge_or_non_vehicle_request") if _SUPPORT_QUERY_PATTERN.search(normalized): return _VehicleAuthorization(None, "knowledge_or_non_vehicle_request") normalized_terms = { "".join(str(term).lower().split()) for term in control_terms if str(term).strip() } matched = any(term and term in normalized for term in normalized_terms) if matched and ( _CONTROL_VERB_PATTERN.search(normalized) or _CONTROL_VALUE_PATTERN.search(normalized) ): # The callback executes this exact server-owned original instruction. # The model can choose whether to request execution, but cannot broaden it. return _VehicleAuthorization(message, "explicit_registered_control") return _VehicleAuthorization(None, "not_a_registered_control") class _StreamingSecretRedactor: """Redact a token even when it is split across streamed model deltas.""" def __init__(self, secret: str) -> None: self._secret = secret self._buffer = "" def feed(self, text: str) -> str: self._buffer += text self._buffer = self._buffer.replace(self._secret, "[内部令牌已隐藏]") keep = 0 maximum = min(len(self._secret) - 1, len(self._buffer)) for length in range(maximum, 0, -1): if self._buffer.endswith(self._secret[:length]): keep = length break if keep: ready, self._buffer = self._buffer[:-keep], self._buffer[-keep:] return ready ready, self._buffer = self._buffer, "" return ready def finish(self) -> str: ready = self._buffer.replace(self._secret, "[内部令牌已隐藏]") self._buffer = "" return ready class CodexAssistantService: """Map browser conversations onto Codex threads and trusted vehicle tools.""" _conversation_pattern = re.compile(r"^conv-[a-z0-9-]{8,100}$") def __init__( self, client: CodexClient, *, prompt_path: Path | None = None, max_sessions: int = 128, fast_implicit_controls: bool = False, ) -> None: self.client = client self._prompt_path = prompt_path or ( Path(__file__).resolve().parents[1] / "agent" / "prompts" / "codex_cockpit_assistant.md" ) self._instructions = self._prompt_path.read_text(encoding="utf-8") self._max_sessions = max(8, max_sessions) self._fast_implicit_controls = bool(fast_implicit_controls) self._sessions: OrderedDict[tuple[str, str], _Conversation] = OrderedDict() self._sessions_lock = threading.Lock() self._tool_contexts: dict[str, _VehicleTurnContext] = {} self._tool_contexts_lock = threading.Lock() self._scene_cache: OrderedDict[ tuple[str, str, str, str], tuple[float, dict[str, object]] ] = OrderedDict() self._scene_flights: dict[tuple[str, str, str, str], _SceneFlight] = {} self._scene_cache_lock = threading.Lock() self._scene_cache_ttl = max( 1.0, min(float(os.getenv("CODEX_ASSISTANT_SCENE_CACHE_TTL_SECONDS", "3600")), 86400.0), ) self._scene_cache_limit = max( 8, min(int(os.getenv("CODEX_ASSISTANT_SCENE_CACHE_MAX_ENTRIES", "256")), 4096), ) self._scene_turn_timeout = max( 5.0, min(float(os.getenv("CODEX_ASSISTANT_SCENE_TIMEOUT_SECONDS", "45")), 300.0), ) self._scene_max_concurrent_plans = max( 1, min( int(os.getenv("CODEX_ASSISTANT_SCENE_MAX_CONCURRENT_PLANS", "8")), 64, ), ) self._scene_planner_slots = threading.BoundedSemaphore( self._scene_max_concurrent_plans ) self._scene_progress_interval = 2.0 self._internal_token = secrets.token_urlsafe(32) self._turn_timeout = max( 10.0, min(float(os.getenv("CODEX_ASSISTANT_TURN_TIMEOUT_SECONDS", "300")), 1800.0), ) @property def internal_token(self) -> str: return self._internal_token def start(self, callback_url: str) -> None: self.client.start(callback_url, self._internal_token) def close(self) -> None: with self._sessions_lock: conversations = list(self._sessions.values()) self._sessions.clear() for conversation in conversations: self._unsubscribe_thread(conversation.thread_id) self.client.close() def status(self) -> dict[str, object]: client_status = self.client.status payload: dict[str, object] = { "status": str(client_status), "available": self.client.available, } with self._sessions_lock: payload["active_conversations"] = len(self._sessions) with self._scene_cache_lock: payload["scene_composer"] = { "planner_version": SCENE_PLANNER_VERSION, "cached_plans": len(self._scene_cache), "in_flight": len(self._scene_flights), "max_concurrent_plans": self._scene_max_concurrent_plans, } payload["engine"] = "codex-app-server" return payload def stream( self, message: str, *, conversation_id: str, principal: str, history: Sequence[dict[str, str]], emit: EmitEvent, execute_control: ControlExecutor, preview_control: ControlExecutor | None = None, supplement_control: ControlExecutor | None = None, capability_snapshot: Mapping[str, object] | None = None, capability_context: Mapping[str, object] | None = None, scene_capability_context: Mapping[str, object] | None = None, control_terms: Sequence[str] = (), query_capabilities: CapabilityQuery | None = None, authoritative_answer: str | None = None, authoritative_topic: str | None = None, authoritative_suggestions: Sequence[Mapping[str, object]] = (), quick_control_commands: Sequence[str] = (), mode_profile_draft_context: Mapping[str, object] | None = None, ) -> dict[str, object]: del history # Codex owns conversation history after the thread is created. message = " ".join(str(message).strip().split()) if not message: raise ValueError("message must not be empty") if len(message) > 4000: raise ValueError("message must not exceed 4000 characters") if not self._conversation_pattern.fullmatch(conversation_id): raise ValueError("invalid conversation_id") catalog = json.loads( json.dumps(dict(capability_snapshot or {}), ensure_ascii=False, default=str) ) model_context = json.loads( json.dumps(dict(capability_context or {}), ensure_ascii=False, default=str) ) scene_context = json.loads( json.dumps(dict(scene_capability_context or {}), ensure_ascii=False, default=str) ) catalog_query = query_capabilities or (lambda snapshot, _query: dict(snapshot)) authoritative_answer = " ".join(str(authoritative_answer or "").strip().split()) if len(authoritative_answer) > 16_000: raise ValueError("authoritative_answer must not exceed 16000 characters") authoritative_topic = str(authoritative_topic or "").strip() if not re.fullmatch(r"[a-z][a-z0-9_]{0,63}", authoritative_topic): authoritative_topic = "" authoritative_suggestions = _safe_authoritative_suggestions( authoritative_suggestions ) normalized_quick_controls = { normalized for raw in quick_control_commands[:6] for normalized in [_normalize_instruction(str(raw)[:160])] if normalized } draft_context = _validated_mode_profile_draft_context( mode_profile_draft_context, message=message, ) authorization = _authorize_vehicle_request( message, control_terms, draft_context, ) saved_mode_execution_id = _catalog_exact_mode_execution_id(catalog, message) if saved_mode_execution_id is not None: authorization = _VehicleAuthorization( message, "explicit_saved_mode_execute", ) fast_comfort_control = ( self._fast_implicit_controls and authorization.reason in _FAST_COMFORT_REASONS ) exact_quick_control = ( authorization.reason == "explicit_registered_control" and _normalize_instruction(message) in normalized_quick_controls ) explicit_local_control = ( authorization.reason == "explicit_registered_control" ) local_first_control = bool( explicit_local_control and preview_control is not None and supplement_control is not None ) mode_profile_preview = authorization.reason in { "explicit_mode_profile_create", "explicit_mode_profile_draft_update", } mode_parts = _mode_profile_create_parts(message) if mode_profile_preview else None if mode_parts: scene_context = scene_context_for_mode_creation(scene_context, message) model_context = scene_context_for_mode_creation(model_context, message) semantic_guidance = scene_semantic_guidance(mode_parts[0]) live_scene_capabilities = { str(item.get("id") or "") for item in scene_context.get("capabilities", []) if isinstance(item, Mapping) } raw_preferred_capabilities = semantic_guidance.get( "preferred_capability_ids", [] ) semantic_guidance["preferred_capability_ids"] = [ str(capability_id) for capability_id in ( raw_preferred_capabilities if isinstance(raw_preferred_capabilities, Sequence) and not isinstance( raw_preferred_capabilities, (str, bytes, bytearray), ) else [] ) if str(capability_id) in live_scene_capabilities ] include_default_bundle = ( not needs_mode_schedule(message) and not scene_has_explicit_control_object( message, scene_context, ) ) required_peripheral_refs = scene_required_peripheral_refs( mode_parts[0], scene_context, include_default_bundle=include_default_bundle, ) required_native_fallbacks = scene_required_native_fallbacks( mode_parts[0], scene_context, include_default_bundle=include_default_bundle, ) semantic_guidance["required_native_domains"] = list(dict.fromkeys( str(item.get("domain") or "") for item in required_native_fallbacks if str(item.get("domain") or "") )) semantic_guidance["required_native_controls"] = [ { "capability": str(item.get("capability") or ""), "target_scope": str(item.get("target_scope") or ""), } for item in required_native_fallbacks ] semantic_guidance["native_fallback_requests"] = ( required_native_fallbacks ) semantic_guidance["default_control_bundle_applied"] = ( include_default_bundle ) raw_peripheral_actions = scene_context.get("peripheral_actions", []) semantic_guidance["peripheral_policy"] = { "available_action_count": ( len(raw_peripheral_actions) if isinstance(raw_peripheral_actions, list) else 0 ), "required_refs": required_peripheral_refs, "selection_goal": ( "Include as many semantically relevant scene-safe peripheral " "devices as are useful; normally choose activation plus at most " "one stable state action per device." ), "required_reason": ( "Name-only scenes must include the available authorized " "flashing-light, ambient-light and light-strip controls." if include_default_bundle and required_peripheral_refs else "Romantic and Mid-Autumn related scenes must include the " "authorized flashing-light and light-strip controls." if required_peripheral_refs else "No peripheral action is mandatory for this name." ), } scene_context["scene_semantics"] = semantic_guidance model_context["scene_semantics"] = deepcopy(semantic_guidance) exact_mode_matches = ( _catalog_exact_mode_match_count(catalog, mode_parts[0]) if mode_parts else None ) candidate_scene_template_plan = ( _catalog_exact_scene_template_plan(catalog, mode_parts[0]) if mode_parts and not mode_parts[1] else None ) candidate_scene_template_metadata = ( candidate_scene_template_plan.get("metadata") if isinstance(candidate_scene_template_plan, Mapping) else None ) candidate_scene_template_id = ( str(candidate_scene_template_metadata.get("template_id") or "") if isinstance(candidate_scene_template_metadata, Mapping) else "" ) scene_template_plan = ( candidate_scene_template_plan if exact_mode_matches == 0 or candidate_scene_template_id in PRESERVE_BASE_EXTERNAL_LIGHT_TEMPLATE_IDS else None ) scene_template_metadata = ( scene_template_plan.get("metadata") if isinstance(scene_template_plan, Mapping) else None ) preserve_base_external_lights = bool( isinstance(scene_template_metadata, Mapping) and str(scene_template_metadata.get("template_id") or "") in PRESERVE_BASE_EXTERNAL_LIGHT_TEMPLATE_IDS ) if preserve_base_external_lights: # The template already owns every native cinema action. Do not let # generic romantic-scene defaults replace or append native actions; # only the deterministic, NFC-authorized light refs may be added. scene_semantics = scene_context.get("scene_semantics") if isinstance(scene_semantics, dict): scene_semantics["required_native_domains"] = [] scene_semantics["required_native_controls"] = [] scene_semantics["native_fallback_requests"] = [] scene_semantics["default_control_bundle_applied"] = False dynamic_scene_request = bool(mode_parts and needs_mode_schedule(message)) scene_peripheral_actions = scene_context.get("peripheral_actions", []) template_augmentation_preview = bool( scene_template_plan is not None and not preserve_base_external_lights and not dynamic_scene_request and isinstance(scene_peripheral_actions, list) and scene_peripheral_actions and self.client.available and callable(getattr(self.client, "start_structured_turn", None)) ) direct_scene_template_plan = ( self._with_required_scene_peripherals( scene_template_plan, scene_context, ) if scene_template_plan is not None else None ) direct_mode_profile_preview = bool( mode_parts and ( (mode_parts[1] and not dynamic_scene_request) # Ambiguous or unavailable mode catalogs fail closed in the # local builder. Only a known-zero match is safe to hand to # semantic scene composition. or exact_mode_matches != 0 or ( scene_template_plan is not None and not template_augmentation_preview ) ) ) direct_mode_profile_update = ( authorization.reason == "explicit_mode_profile_draft_update" ) saved_mode_management_route = { "explicit_mode_profile_update": "mode_profile_update", "explicit_mode_profile_delete": "mode_profile_delete", }.get(authorization.reason) scene_composition_preview = bool( mode_parts and (not mode_parts[1] or dynamic_scene_request) and exact_mode_matches == 0 and scene_template_plan is None and scene_context.get("composition_revision") and callable(getattr(self.client, "start_structured_turn", None)) ) # Explicit device settings and repository-backed scenes remain on the # zero-model-latency path. A genuinely new name-only scene needs Codex # to compose a candidate plan from the live capability catalog; the # vehicle service still limits it to dry-run and requires confirmation. direct_vehicle_control = ( fast_comfort_control or authorization.reason == "explicit_schedule_stop" or exact_quick_control or local_first_control or saved_mode_execution_id is not None or direct_mode_profile_preview or authorization.reason == "explicit_mode_profile_draft_update" or saved_mode_management_route is not None ) route = ( "unsupported_control" if authoritative_answer and authoritative_topic == "unsupported_vehicle_control" else "capability_catalog" if authoritative_answer else "scene_template" if scene_template_plan is not None else saved_mode_management_route if saved_mode_management_route is not None else "mode_profile_execute" if saved_mode_execution_id is not None else "vehicle_control" if direct_vehicle_control else "scene_composer" if scene_composition_preview else "agent" ) engine = ( "n7web-capability-registry" if authoritative_answer else "n7web-scene-template" if scene_template_plan is not None else "n7web-mode-builder" if direct_mode_profile_preview or direct_mode_profile_update else "n7web-mode-profile-manager" if saved_mode_management_route is not None else "n7web-mode-profile-runner" if saved_mode_execution_id is not None else "codex-app-server-scene-composer" if scene_composition_preview else "n7web-control-router" if fast_comfort_control or exact_quick_control or local_first_control else "codex-app-server" ) message_id = f"msg-{uuid.uuid4().hex}" if ( emit( { "type": "message.accepted", "message_id": message_id, "conversation_id": conversation_id, "route": route, "engine": engine, } ) is False ): return {"cancelled": True} if ( emit( { "type": "work.started", "message_id": message_id, "display_text": ( "正在核对车端控制能力……" if route == "unsupported_control" else "正在读取工程能力目录……" if authoritative_answer else "正在载入并校验常用模式模板……" if scene_template_plan is not None else "正在构建并校验功能模式……" if direct_mode_profile_preview or direct_mode_profile_update else "正在更新已保存的功能模式……" if saved_mode_management_route == "mode_profile_update" else "正在删除自定义功能模式……" if saved_mode_management_route == "mode_profile_delete" else "正在载入并执行已保存的功能模式……" if saved_mode_execution_id is not None else "正在根据实时能力目录快速组合模式……" if scene_composition_preview else "小迪正在理解场景并组合工程能力……" if mode_profile_preview else "正在快速处理座舱舒适需求……" if fast_comfort_control else "正在执行快捷车控……" if exact_quick_control else "正在本地解析并下发明确控制指令……" if local_first_control else "小迪正在理解并处理……" ), } ) is False ): return {"cancelled": True} if authoritative_answer: if ( emit( { "type": "answer.delta", "message_id": message_id, "delta": authoritative_answer, } ) is False ): return {"cancelled": True} if authoritative_suggestions and ( emit( { "type": "control.suggestions", "message_id": message_id, "suggestions": authoritative_suggestions, } ) is False ): return {"cancelled": True} emit( { "type": "work.completed", "message_id": message_id, "success": True, "display_text": ( "已核对当前车端能力" if route == "unsupported_control" else "已按工程能力目录核对" ), } ) emit( { "type": "answer.completed", "message_id": message_id, "route": route, "answer": authoritative_answer, **({"topic": authoritative_topic} if authoritative_topic else {}), **( {"suggestions": authoritative_suggestions} if authoritative_suggestions else {} ), } ) return { "route": route, "answer": authoritative_answer, **({"topic": authoritative_topic} if authoritative_topic else {}), **( {"suggestions": authoritative_suggestions} if authoritative_suggestions else {} ), } if ( local_first_control and preview_control is not None and supplement_control is not None ): return self._stream_local_first_vehicle_control( authorization, message_id=message_id, emit=emit, preview_control=preview_control, supplement_control=supplement_control, conversation_id=conversation_id, principal=principal, capability_snapshot=catalog, capability_context=model_context, control_terms=control_terms, query_capabilities=catalog_query, ) if template_augmentation_preview and scene_template_plan is not None: try: return self._stream_scene_template_augmentation( message, message_id=message_id, emit=emit, execute_control=execute_control, scene_context=scene_context, template_plan=scene_template_plan, ) except _ScenePlannerCancelled: return {"cancelled": True} except Exception as exc: # noqa: BLE001 - trusted base remains usable LOGGER.warning( "Scene template peripheral augmentation failed; using base: %s", exc, ) return self._stream_direct_vehicle_control( authorization, message_id=message_id, emit=emit, execute_control=execute_control, assistant_plan=direct_scene_template_plan, response_route="scene_template", ) if direct_vehicle_control: return self._stream_direct_vehicle_control( ( _VehicleAuthorization( authorization.instruction, "exact_quick_control", ) if exact_quick_control else authorization ), message_id=message_id, emit=emit, execute_control=execute_control, assistant_plan=direct_scene_template_plan, response_route=( "scene_template" if scene_template_plan is not None else saved_mode_management_route if saved_mode_management_route is not None else "mode_profile_execute" if saved_mode_execution_id is not None else "vehicle_control" ), ) if not self.client.available: raise CodexAssistantError("Codex App Server is not available") if scene_composition_preview and mode_parts is not None: try: return self._stream_scene_composition( message, mode_name=mode_parts[0], message_id=message_id, emit=emit, execute_control=execute_control, scene_context=scene_context, ) except _ScenePlannerCancelled: return {"cancelled": True} except _ScenePlannerTimeout: return self._stream_scene_planner_failure( message_id=message_id, emit=emit, display_text="功能模式规划超时", answer=( f"功能模式暂未生成:场景规划超时,已超过" f"{self._scene_turn_timeout:g}秒。未执行或保存任何车辆操作," "请重试。" ), ) except _ScenePlannerOverloaded: return self._stream_scene_planner_failure( message_id=message_id, emit=emit, display_text="功能模式规划任务较多", answer=( "功能模式暂未生成:当前场景规划任务较多。" "未执行或保存任何车辆操作,请稍后重试。" ), ) except _ScenePlannerFallback as exc: LOGGER.warning("Scene Composer fell back to the general agent: %s", exc) if ( emit( { "type": "work.progress", "message_id": message_id, "display_text": "快速规划暂不可用,正在切换完整理解链路……", } ) is False ): return {"cancelled": True} conversation, conversation_key = self._conversation(principal, conversation_id) try: with conversation.lock: return self._stream_locked( conversation, message, message_id=message_id, emit=emit, execute_control=execute_control, capability_snapshot=catalog, capability_context=model_context, control_terms=control_terms, query_capabilities=catalog_query, ) finally: self._release_conversation(conversation_key, conversation) @staticmethod def _stream_scene_planner_failure( *, message_id: str, emit: EmitEvent, display_text: str, answer: str, ) -> dict[str, object]: if ( emit( { "type": "work.completed", "message_id": message_id, "success": False, "display_text": display_text, } ) is False ): return {"cancelled": True} if ( emit( { "type": "answer.delta", "message_id": message_id, "delta": answer, } ) is False ): return {"cancelled": True} emit( { "type": "answer.completed", "message_id": message_id, "route": "scene_composer", "answer": answer, } ) return { "route": "scene_composer", "success": False, "answer": answer, } @staticmethod def _copy_scene_plan(plan: Mapping[str, object]) -> dict[str, object]: return json.loads(json.dumps(dict(plan), ensure_ascii=False, allow_nan=False)) @staticmethod def _required_scene_peripheral_refs( scene_context: Mapping[str, object], ) -> list[str]: semantics = scene_context.get("scene_semantics") policy = ( semantics.get("peripheral_policy") if isinstance(semantics, Mapping) else None ) raw_refs = policy.get("required_refs", []) if isinstance(policy, Mapping) else [] available_refs = { str(item.get("ref") or "").strip() for item in scene_context.get("peripheral_actions", []) if isinstance(item, Mapping) and str(item.get("ref") or "").strip() } return list( dict.fromkeys( str(value).strip() for value in ( raw_refs if isinstance(raw_refs, Sequence) and not isinstance(raw_refs, (str, bytes, bytearray)) else [] ) if str(value).strip() in available_refs ) ) @classmethod def _with_required_scene_peripherals( cls, plan: Mapping[str, object], scene_context: Mapping[str, object], *, maximum_requests: int = MAX_PLAN_REQUESTS, ) -> dict[str, object]: """Enforce required native domains and opaque peripheral references.""" normalized = cls._copy_scene_plan(plan) requests = normalized.get("requests") if not isinstance(requests, list): raise SceneCompositionError("scene plan requests must be an array") existing_refs = { str(parameter.get("value") or "").strip() for request in requests if isinstance(request, Mapping) and request.get("domain") == "self_extension" and request.get("capability") == "infrared.emit" for parameter in ( request.get("parameters", []) if isinstance(request.get("parameters"), list) else [] ) if isinstance(parameter, Mapping) and parameter.get("name") == "capability_ref" } required_refs = cls._required_scene_peripheral_refs(scene_context) missing_refs = [ref for ref in required_refs if ref not in existing_refs] semantics = scene_context.get("scene_semantics") raw_native_fallbacks = ( semantics.get("native_fallback_requests", []) if isinstance(semantics, Mapping) else [] ) native_fallbacks = [ deepcopy(dict(item)) for item in ( raw_native_fallbacks if isinstance(raw_native_fallbacks, Sequence) and not isinstance(raw_native_fallbacks, (str, bytes, bytearray)) else [] ) if isinstance(item, Mapping) ] flexible_native_values = { "climate.set_temperature", "climate.set_fan_level", "climate.set_cycle_mode", "roof.set_shade_position", "seat.set_backrest_recline", "seat.set_legrest_height", } def parameter_map(request: Mapping[str, object]) -> dict[str, object]: raw_parameters = request.get("parameters", []) return { str(parameter.get("name") or ""): parameter.get("value") for parameter in ( raw_parameters if isinstance(raw_parameters, Sequence) and not isinstance(raw_parameters, (str, bytes, bytearray)) else [] ) if isinstance(parameter, Mapping) and str(parameter.get("name") or "") } def native_requirement_satisfied( request: Mapping[str, object], fallback: Mapping[str, object], ) -> bool: capability = str(fallback.get("capability") or "") target_scope = str(fallback.get("target_scope") or "") if ( request.get("capability") != capability or request.get("target_scope") != target_scope ): return False if capability in flexible_native_values: return True required_parameters = parameter_map(fallback) actual_parameters = parameter_map(request) return all( actual_parameters.get(name) == value for name, value in required_parameters.items() ) missing_native: list[dict[str, object]] = [] replaced_native = 0 for fallback in native_fallbacks: matching_indexes = [ index for index, request in enumerate(requests) if isinstance(request, Mapping) and request.get("capability") == fallback.get("capability") and request.get("target_scope") == fallback.get("target_scope") ] if any( native_requirement_satisfied(requests[index], fallback) for index in matching_indexes ): continue if matching_indexes: requests[matching_indexes[0]] = deepcopy(fallback) replaced_native += 1 continue missing_native.append(fallback) native_changed = bool(missing_native or replaced_native) metadata = normalized.get("metadata") if ( native_changed and isinstance(metadata, Mapping) and metadata.get("source") == "scene_template" ): scope_policy = scene_context.get("seat_scope_policy") raw_selected_scopes = ( scope_policy.get("selected_scopes", []) if isinstance(scope_policy, Mapping) else [] ) selected_scopes = frozenset( str(scope) for scope in ( raw_selected_scopes if isinstance(raw_selected_scopes, Sequence) and not isinstance( raw_selected_scopes, (str, bytes, bytearray), ) else [] ) if str(scope) ) if selected_scopes: try: normalized = project_seed_plan(normalized, selected_scopes) except ModeCreationSeatPolicyError as exc: raise SceneCompositionError(str(exc)) from exc projected_requests = normalized.get("requests") if not isinstance(projected_requests, list): raise SceneCompositionError( "projected scene template requests must be an array" ) requests = projected_requests if ( len(requests) + len(missing_native) + len(missing_refs) > maximum_requests ): raise SceneCompositionError( "required scene controls exceed the scene action limit" ) requests.extend(missing_native) requests.extend( { "domain": "self_extension", "capability": "infrared.emit", "target_scope": capability_ref, "operation": "emit", "duration_ms": None, "parameters": [ {"name": "capability_ref", "value": capability_ref} ], "reason": ( "Mode semantics require this currently authorized learned-device " "action; runtime binding resolution is still required." ), } for capability_ref in missing_refs ) if native_changed or missing_refs: if native_changed: # The trusted template remains the source seed, but adding a # native domain makes this an ordinary fully guarded semantic # composition. First project its seat variants to the user's # selected scope above, then remove metadata because template # metadata requires byte-identical base requests. normalized.pop("metadata", None) domains = normalized.get("detected_domains") if isinstance(domains, list): for fallback in missing_native: domain = str(fallback.get("domain") or "") if domain and domain not in domains: domains.append(domain) if missing_refs and "self_extension" not in domains: domains.append("self_extension") summaries = normalized.get("decision_summary") if isinstance(summaries, list): summaries.append( "已按模式语义补齐 " f"{len(missing_native) + replaced_native} 项原生控制和 " f"{len(missing_refs)} 项已授权外设动作。" ) return normalized def _scene_cache_key( self, *, mode_name: str, scene_context: Mapping[str, object], ) -> tuple[str, str, str, str]: vehicle = scene_context.get("vehicle") vehicle_id = ( str(vehicle.get("id") or "default") if isinstance(vehicle, Mapping) else "default" ) revision = str(scene_context.get("composition_revision") or "") if not revision: raise _ScenePlannerFallback("scene capability revision is unavailable") scope_policy = scene_context.get("seat_scope_policy") selected_scopes = ( scope_policy.get("selected_scopes", []) if isinstance(scope_policy, Mapping) else [] ) scope_key = ",".join(sorted(str(scope) for scope in selected_scopes)) return ( vehicle_id, revision, normalize_mode_profile_name(mode_name), f"{SCENE_PLANNER_VERSION}:seats={scope_key}", ) def _begin_scene_plan( self, key: tuple[str, str, str, str], ) -> tuple[dict[str, object] | None, _SceneFlight | None, bool]: now = time.monotonic() with self._scene_cache_lock: expired = [ cached_key for cached_key, (expires_at, _plan) in self._scene_cache.items() if expires_at <= now ] for cached_key in expired: self._scene_cache.pop(cached_key, None) cached = self._scene_cache.get(key) if cached is not None: self._scene_cache.move_to_end(key) return self._copy_scene_plan(cached[1]), None, False flight = self._scene_flights.get(key) if flight is not None: return None, flight, False flight = _SceneFlight() self._scene_flights[key] = flight return None, flight, True def _publish_scene_plan( self, key: tuple[str, str, str, str], flight: _SceneFlight, *, plan: Mapping[str, object] | None = None, error: BaseException | None = None, ) -> None: with self._scene_cache_lock: if plan is not None: flight.plan = self._copy_scene_plan(plan) flight.error = error if error is not None: self._scene_flights.pop(key, None) flight.ready.set() def _release_scene_flight( self, key: tuple[str, str, str, str], flight: _SceneFlight, ) -> None: with self._scene_cache_lock: if self._scene_flights.get(key) is flight: self._scene_flights.pop(key, None) def _cache_validated_scene_plan( self, key: tuple[str, str, str, str], plan: Mapping[str, object], ) -> None: with self._scene_cache_lock: self._scene_cache[key] = ( time.monotonic() + self._scene_cache_ttl, self._copy_scene_plan(plan), ) self._scene_cache.move_to_end(key) while len(self._scene_cache) > self._scene_cache_limit: self._scene_cache.popitem(last=False) def _stream_scene_template_augmentation( self, message: str, *, message_id: str, emit: EmitEvent, execute_control: ControlExecutor, scene_context: Mapping[str, object], template_plan: Mapping[str, object], ) -> dict[str, object]: """Add only model-selected opaque IR refs to an immutable template base.""" peripheral_actions = scene_context.get("peripheral_actions", []) if not isinstance(peripheral_actions, list) or not peripheral_actions: return self._stream_direct_vehicle_control( _VehicleAuthorization(message, "explicit_mode_profile_create"), message_id=message_id, emit=emit, execute_control=execute_control, assistant_plan=self._with_required_scene_peripherals( template_plan, scene_context, ), response_route="scene_template", ) base_requests = template_plan.get("requests", []) if not isinstance(base_requests, list) or not base_requests: raise _ScenePlannerFallback("scene template base is empty") augmentation_context = { **dict(scene_context), "capabilities": [], "scene_semantics": deepcopy(scene_context.get("scene_semantics")), "device_inventory": [ deepcopy(item) for item in scene_context.get("device_inventory", []) if isinstance(item, Mapping) and item.get("kind") == "learned_infrared" ], "template_base": { "immutable": True, "actions": [ { "capability": request.get("capability"), "target_scope": request.get("target_scope"), "parameters": request.get("parameters"), } for request in base_requests if isinstance(request, Mapping) ], }, } augmentation_context["maximum_generated_action_count"] = max( 0, MAX_PLAN_REQUESTS - len(base_requests) - len(self._required_scene_peripheral_refs(scene_context)) - len( ( scene_context.get("scene_semantics") or {} ).get("native_fallback_requests", []) if isinstance(scene_context.get("scene_semantics"), Mapping) else [] ), ) augmentation_semantics = augmentation_context.get("scene_semantics") if isinstance(augmentation_semantics, dict): augmentation_semantics["grounded"] = False augmentation_semantics["minimum_action_count"] = 0 augmentation_semantics["ambiguities_allowed"] = True if ( emit( { "type": "work.progress", "message_id": message_id, "display_text": "正在根据已授权外设补充常用模式……", } ) is False ): raise _ScenePlannerCancelled() deadline = time.monotonic() + self._scene_turn_timeout augmentation = self._request_structured_scene_plan( message, message_id=message_id, emit=emit, scene_context=augmentation_context, deadline=deadline, developer_instructions=SCENE_TEMPLATE_AUGMENTATION_INSTRUCTIONS, dynamic_override=False, enforce_required_controls=False, ) proposed = augmentation.get("requests", []) if not isinstance(proposed, list) or any( not isinstance(request, Mapping) or request.get("domain") != "self_extension" or request.get("capability") != "infrared.emit" for request in proposed ): raise _ScenePlannerFallback("template augmentation returned a native mutation") if len(base_requests) + len(proposed) > MAX_PLAN_REQUESTS: raise _ScenePlannerFallback("template augmentation exceeds the scene action limit") combined = self._copy_scene_plan(template_plan) combined["requests"] = [ *deepcopy(base_requests), *deepcopy(proposed), ] combined = self._with_required_scene_peripherals( combined, scene_context, ) return self._stream_direct_vehicle_control( _VehicleAuthorization(message, "explicit_mode_profile_create"), message_id=message_id, emit=emit, execute_control=execute_control, assistant_plan=combined, response_route="scene_template", ) def _stream_scene_composition( self, message: str, *, mode_name: str, message_id: str, emit: EmitEvent, execute_control: ControlExecutor, scene_context: Mapping[str, object], planning_deadline: float | None = None, ) -> dict[str, object]: started_at = time.monotonic() deadline = planning_deadline or (started_at + self._scene_turn_timeout) key = self._scene_cache_key( mode_name=mode_name, scene_context=scene_context, ) plan, flight, leader = self._begin_scene_plan(key) cache_hit = plan is not None if plan is not None: if ( emit( { "type": "work.progress", "message_id": message_id, "display_text": "已复用场景组合,正在按实时能力重新校验……", } ) is False ): raise _ScenePlannerCancelled() elif not leader: assert flight is not None if ( emit( { "type": "work.progress", "message_id": message_id, "display_text": "相同场景正在组合,完成后将立即复用……", } ) is False ): raise _ScenePlannerCancelled() next_progress_at = time.monotonic() + self._scene_progress_interval while not flight.ready.is_set(): remaining = deadline - time.monotonic() if remaining <= 0: raise _ScenePlannerTimeout( "timed out waiting for an identical scene plan" ) flight.ready.wait(min(0.25, remaining)) if not flight.ready.is_set() and time.monotonic() >= next_progress_at: if ( emit( { "type": "work.progress", "message_id": message_id, "display_text": "相同场景仍在组合,请稍候……", } ) is False ): raise _ScenePlannerCancelled() next_progress_at = time.monotonic() + self._scene_progress_interval if flight.error is not None: if isinstance(flight.error, _ScenePlannerCancelled): # The planner belongs to request scope, not to the browser # connection that happened to become singleflight leader. # Re-elect one surviving follower instead of forcing every # waiter onto the slower general-agent path. return self._stream_scene_composition( message, mode_name=mode_name, message_id=message_id, emit=emit, execute_control=execute_control, scene_context=scene_context, planning_deadline=deadline, ) if isinstance(flight.error, _ScenePlannerTimeout): raise _ScenePlannerTimeout(str(flight.error)) from flight.error if isinstance(flight.error, _ScenePlannerOverloaded): raise _ScenePlannerOverloaded(str(flight.error)) from flight.error raise _ScenePlannerFallback(str(flight.error)) from flight.error if flight.plan is None: raise _ScenePlannerFallback("identical scene planning returned no plan") plan = self._copy_scene_plan(flight.plan) cache_hit = True else: assert flight is not None if not self._scene_planner_slots.acquire(blocking=False): error = _ScenePlannerOverloaded("all Scene Composer slots are busy") self._publish_scene_plan(key, flight, error=error) raise error planner_error: Exception | None = None try: plan = self._request_structured_scene_plan( message, message_id=message_id, emit=emit, scene_context=scene_context, deadline=deadline, ) except Exception as exc: planner_error = exc finally: self._scene_planner_slots.release() if planner_error is not None: self._publish_scene_plan(key, flight, error=planner_error) if isinstance( planner_error, ( _ScenePlannerCancelled, _ScenePlannerTimeout, _ScenePlannerOverloaded, _ScenePlannerFallback, ), ): raise planner_error raise _ScenePlannerFallback(str(planner_error)) from planner_error self._publish_scene_plan(key, flight, plan=plan) assert plan is not None try: if ( emit( { "type": "work.progress", "message_id": message_id, "display_text": "模式候选动作已生成,正在进行参数与安全校验……", } ) is False ): raise _ScenePlannerCancelled() validation_started_at = time.monotonic() response = self._stream_direct_vehicle_control( _VehicleAuthorization(message, "explicit_mode_profile_create"), message_id=message_id, emit=emit, execute_control=execute_control, assistant_plan=plan, response_route="scene_composer", ) if response.get("success") is True: self._cache_validated_scene_plan(key, plan) LOGGER.info( "Scene Composer finished revision=%s cache_hit=%s success=%s " "validation_ms=%d elapsed_ms=%d", key[1], cache_hit, response.get("success") is True, round((time.monotonic() - validation_started_at) * 1000), round((time.monotonic() - started_at) * 1000), ) return response finally: if leader and flight is not None: self._release_scene_flight(key, flight) def _request_structured_scene_plan( self, message: str, *, message_id: str, emit: EmitEvent, scene_context: Mapping[str, object], deadline: float, developer_instructions: str | None = None, dynamic_override: bool | None = None, enforce_required_controls: bool = True, ) -> dict[str, object]: start_structured_turn = getattr(self.client, "start_structured_turn", None) if not callable(start_structured_turn): raise _ScenePlannerFallback("structured turns are not supported") planning_started_at = time.monotonic() dynamic = ( needs_mode_schedule(message) if dynamic_override is None else dynamic_override ) planner_context = deepcopy(dict(scene_context)) if dynamic: # Timed IR playback is not yet phase-aware. Keep learned devices in # inventory so the model can explain availability, but do not offer # them as selectable actions in a schedule. planner_context["peripheral_actions"] = [] dynamic_semantics = planner_context.get("scene_semantics") dynamic_policy = ( dynamic_semantics.get("peripheral_policy") if isinstance(dynamic_semantics, dict) else None ) if isinstance(dynamic_policy, dict): dynamic_policy["available_action_count"] = 0 dynamic_policy["required_refs"] = [] dynamic_policy["required_reason"] = ( "Timed learned-infrared actions are not supported." ) for item in planner_context.get("device_inventory", []): if ( isinstance(item, dict) and item.get("kind") == "learned_infrared" and item.get("availability") == "available" ): item["availability"] = "unavailable" item["availability_reason"] = "timed_ir_not_supported" output_schema = build_scene_output_schema(planner_context, dynamic=dynamic) context_bytes = len( json.dumps( planner_context, ensure_ascii=False, separators=(",", ":"), ).encode("utf-8") ) schema_bytes = len( json.dumps( output_schema, ensure_ascii=False, separators=(",", ":"), ).encode("utf-8") ) thread_id: str | None = None active: ActiveCodexTurn | None = None final_text = "" streamed_text = "" progress_emitted = False next_heartbeat_at = time.monotonic() + self._scene_progress_interval thread_ready_at: float | None = None turn_ready_at: float | None = None first_event_at: float | None = None terminal_received = False interrupt_requested = False try: try: thread_id = self.client.start_thread( developer_instructions=( developer_instructions or ( SCHEDULE_COMPOSER_INSTRUCTIONS if dynamic else SCENE_COMPOSER_INSTRUCTIONS ) ), ephemeral=True, replace_developer_instructions=True, timeout=self._remaining_scene_time(deadline), ) except Exception as exc: self._raise_if_scene_timeout(exc, deadline, "thread/start") raise thread_ready_at = time.monotonic() try: active = start_structured_turn( thread_id, message, application_context={ **planner_context, "natural_language_routing": { "original_utterance": message, "parsed_clauses": [], "resolved_clauses": [], "unresolved_clauses": [message], "cloud_input_utterance": message, }, }, output_schema=output_schema, timeout=self._remaining_scene_time(deadline), ) except Exception as exc: self._raise_if_scene_timeout(exc, deadline, "turn/start") raise turn_ready_at = time.monotonic() terminal: dict[str, object] | None = None while terminal is None: remaining = deadline - time.monotonic() if remaining <= 0: self._interrupt_scene_turn(active) interrupt_requested = True raise _ScenePlannerTimeout("Scene Composer turn timed out") try: event = active.events.get(timeout=min(0.2, remaining)) except queue.Empty: if time.monotonic() >= next_heartbeat_at: if ( emit( { "type": "work.progress", "message_id": message_id, "display_text": "小迪正在生成结构化模式方案……", } ) is False ): self._interrupt_scene_turn(active) interrupt_requested = True raise _ScenePlannerCancelled() next_heartbeat_at = ( time.monotonic() + self._scene_progress_interval ) continue if first_event_at is None: first_event_at = time.monotonic() method = str(event.get("method") or "") params = event.get("params") params = params if isinstance(params, dict) else {} if not progress_emitted and method in { "turn/started", "item/started", "item/agentMessage/delta", }: progress_emitted = True if ( emit( { "type": "work.progress", "message_id": message_id, "display_text": "小迪已开始生成结构化模式方案……", } ) is False ): self._interrupt_scene_turn(active) interrupt_requested = True raise _ScenePlannerCancelled() if method == "transport/error": raise _ScenePlannerFallback( str(params.get("message") or "Codex disconnected") ) if method == "item/agentMessage/delta": streamed_text += str(params.get("delta") or "") if len(streamed_text.encode("utf-8")) > 64 * 1024: self._interrupt_scene_turn(active) interrupt_requested = True raise SceneCompositionError("scene composer response is too large") elif method == "item/completed": item = params.get("item") if isinstance(item, Mapping) and item.get("type") == "agentMessage": final_text = str(item.get("text") or final_text) elif method == "error": detail = params.get("error") if isinstance(detail, Mapping): detail = detail.get("message") or detail.get("codexErrorInfo") raise _ScenePlannerFallback( str(detail or params.get("message") or "Scene Composer turn failed") ) elif method == "turn/completed": terminal = params terminal_received = True if terminal is None and time.monotonic() >= next_heartbeat_at: if ( emit( { "type": "work.progress", "message_id": message_id, "display_text": "小迪正在生成结构化模式方案……", } ) is False ): self._interrupt_scene_turn(active) interrupt_requested = True raise _ScenePlannerCancelled() next_heartbeat_at = time.monotonic() + self._scene_progress_interval turn = terminal.get("turn") if terminal else None turn = turn if isinstance(turn, Mapping) else {} status = str(turn.get("status") or "failed") if status != "completed": detail = turn.get("error") if isinstance(detail, Mapping): detail = detail.get("message") or detail.get("codexErrorInfo") raise _ScenePlannerFallback(str(detail or f"Scene Composer turn {status}")) output_text = final_text or streamed_text if not output_text: raise SceneCompositionError("scene composer produced an empty response") try: raw_output = json.loads(output_text) except json.JSONDecodeError as exc: raise SceneCompositionError("scene composer output was not valid JSON") from exc if not isinstance(raw_output, Mapping): raise SceneCompositionError("scene composer output must be an object") normalized = normalize_scene_output(raw_output, planner_context, dynamic=dynamic) if not dynamic and enforce_required_controls: normalized = self._with_required_scene_peripherals( normalized, planner_context, ) finished_at = time.monotonic() LOGGER.info( "Scene Composer model completed context_bytes=%d schema_bytes=%d " "thread_start_ms=%d turn_start_ms=%d first_event_ms=%d model_ms=%d", context_bytes, schema_bytes, round(((thread_ready_at or finished_at) - planning_started_at) * 1000), round( ( (turn_ready_at or finished_at) - (thread_ready_at or planning_started_at) ) * 1000 ), ( round((first_event_at - planning_started_at) * 1000) if first_event_at is not None else -1 ), round((finished_at - planning_started_at) * 1000), ) return normalized finally: if active is not None: if not terminal_received and not interrupt_requested: self._interrupt_scene_turn(active) if not self._finish_scene_turn(active): abandon = getattr(self.client, "abandon_turn", None) if callable(abandon): abandon(active) if thread_id is not None: self._unsubscribe_scene_thread(thread_id) @staticmethod def _remaining_scene_time(deadline: float) -> float: remaining = deadline - time.monotonic() if remaining <= 0: raise _ScenePlannerTimeout("Scene Composer deadline expired") return remaining @staticmethod def _raise_if_scene_timeout( exc: BaseException, deadline: float, stage: str, ) -> None: if ( exc.__class__.__name__ == "CodexAppServerTimeout" or time.monotonic() >= deadline ): raise _ScenePlannerTimeout( f"Scene Composer {stage} exceeded its deadline" ) from exc def _interrupt_scene_turn(self, active: ActiveCodexTurn) -> None: try: self.client.interrupt(active, timeout=0.25) except (RuntimeError, TypeError) as exc: LOGGER.debug("Scene Composer turn interrupt failed", exc_info=exc) def _finish_scene_turn(self, active: ActiveCodexTurn) -> bool: try: self.client.finish_turn(active, timeout=0.25) return True except (RuntimeError, TypeError) as exc: LOGGER.debug("Scene Composer turn cleanup failed", exc_info=exc) return False def _unsubscribe_scene_thread(self, thread_id: str) -> None: unsubscribe = getattr(self.client, "unsubscribe_thread", None) if not callable(unsubscribe): return try: unsubscribe(thread_id, timeout=0.25) except (RuntimeError, TypeError) as exc: LOGGER.debug("Scene Composer thread cleanup failed", exc_info=exc) def _stream_local_first_vehicle_control( self, authorization: _VehicleAuthorization, *, message_id: str, emit: EmitEvent, preview_control: ControlExecutor, supplement_control: ControlExecutor, conversation_id: str, principal: str, capability_snapshot: Mapping[str, object], capability_context: Mapping[str, object], control_terms: Sequence[str], query_capabilities: CapabilityQuery, ) -> dict[str, object]: """Publish deterministic controls, then delegate only unresolved text.""" instruction = authorization.instruction if instruction is None: raise CodexAssistantError("local-first vehicle control was not authorized") if ( emit( { "type": "action.started", "message_id": message_id, "display_text": "正在通过本地规则解析明确控制指令……", } ) is False ): return {"cancelled": True} stream_open = True def on_progress(payload: dict[str, object]) -> None: nonlocal stream_open if not stream_open: return validated_payload = self._validated_control_payload(payload) if validated_payload is not None: stream_open = ( emit( { "type": "validated_control_commands", "message_id": message_id, "payload": validated_payload, } ) is not False ) if not stream_open: return stream_open = ( emit( { "type": "action.progress", "message_id": message_id, "display_text": "本地控制指令已解析并进入下发链路……", "payload": self._safe_vehicle_result(payload), } ) is not False ) try: preview = preview_control(instruction, on_progress) if not isinstance(preview, dict): raise TypeError("vehicle preview returned an invalid result") except Exception: # noqa: BLE001 - stable browser-facing failure answer = "车辆控制未完成,本地安全执行链没有返回有效结果。" for event in ( { "type": "action.completed", "message_id": message_id, "success": False, "state": "failed", "display_text": "本地控制未完成", }, { "type": "answer.delta", "message_id": message_id, "delta": answer, }, { "type": "work.completed", "message_id": message_id, "success": False, "display_text": "车辆控制未完成", }, { "type": "answer.completed", "message_id": message_id, "route": "vehicle_control", "answer": answer, }, ): if emit(event) is False: return {"cancelled": True, "answer": answer} return { "route": "vehicle_control", "answer": answer, "success": False, } if not stream_open: return {"cancelled": True} routing_value = preview.get("natural_language_routing") routing = dict(routing_value) if isinstance(routing_value, Mapping) else {} raw_unresolved = routing.get("unresolved_clauses") if not isinstance(raw_unresolved, Sequence) or isinstance( raw_unresolved, (str, bytes, bytearray), ): raw_unresolved = preview.get("unresolved_clauses") unresolved_clauses = list( dict.fromkeys( text for item in ( list(raw_unresolved)[:64] if isinstance(raw_unresolved, Sequence) and not isinstance(raw_unresolved, (str, bytes, bytearray)) else [] ) for text in [str(item).strip()[:1000]] if text ) ) safe_preview = self._safe_vehicle_result(preview) if not unresolved_clauses: action_state, successful, completion_text = _vehicle_action_state(preview) answer = self._fast_comfort_answer( "explicit_registered_control", preview, successful, ) if not stream_open: return {"cancelled": True, "answer": answer} for event in ( { "type": "action.completed", "message_id": message_id, "success": successful, "state": action_state, "display_text": completion_text, "payload": safe_preview, }, { "type": "answer.delta", "message_id": message_id, "delta": answer, }, { "type": "work.completed", "message_id": message_id, "success": successful, "state": action_state, "display_text": completion_text, }, { "type": "answer.completed", "message_id": message_id, "route": "vehicle_control", "answer": answer, }, ): if emit(event) is False: return {"cancelled": True, "answer": answer} return { "route": "vehicle_control", "answer": answer, "success": successful, "result": safe_preview, } local_has_commands = bool( preview.get("function_configs") and preview.get("command_plans") ) local_status = str(preview.get("status") or "") local_succeeded = local_has_commands and local_status in { "succeeded", "partially_succeeded", "no_action", } if stream_open and emit( { "type": "action.completed", "message_id": message_id, "success": local_succeeded, "state": "submitted" if local_succeeded else "failed", "display_text": ( "本地明确控制已先行处理" if local_succeeded else "本地阶段未生成控制指令" ), "payload": safe_preview, } ) is False: return {"cancelled": True} if emit( { "type": "work.progress", "message_id": message_id, "display_text": "本地明确子句已处理,正在补充未解析子句……", } ) is False: return {"cancelled": True} cloud_message = ",".join(unresolved_clauses) routing.update( { "original_utterance": str( routing.get("original_utterance") or instruction )[:1000], "parsed_clauses": list(routing.get("parsed_clauses") or []), "resolved_clauses": list(routing.get("resolved_clauses") or []), "unresolved_clauses": unresolved_clauses, "cloud_input_utterance": cloud_message, } ) if not self.client.available: answer = ( "已先行处理本地可解析的控制;其余子句暂无法连接云端模型," "未生成额外控制。" ) emit( { "type": "answer.delta", "message_id": message_id, "delta": answer, } ) emit( { "type": "work.completed", "message_id": message_id, "success": local_succeeded, "display_text": "本地控制已处理,云端补充不可用", } ) emit( { "type": "answer.completed", "message_id": message_id, "route": "vehicle_control", "answer": answer, } ) return { "route": "vehicle_control", "answer": answer, "success": local_succeeded, "result": safe_preview, } conversation, conversation_key = self._conversation( principal, conversation_id, ) try: with conversation.lock: return self._stream_locked( conversation, cloud_message, message_id=message_id, emit=emit, execute_control=supplement_control, capability_snapshot=capability_snapshot, capability_context={ **dict(capability_context), "natural_language_routing": routing, }, control_terms=control_terms, query_capabilities=query_capabilities, ) finally: self._release_conversation(conversation_key, conversation) def _stream_direct_vehicle_control( self, authorization: _VehicleAuthorization, *, message_id: str, emit: EmitEvent, execute_control: ControlExecutor, assistant_plan: Mapping[str, object] | None = None, response_route: str = "vehicle_control", ) -> dict[str, object]: """Execute deterministic high-confidence routes without model latency.""" instruction = authorization.instruction if instruction is None: raise CodexAssistantError("direct vehicle control was not authorized") mode_profile_preview = authorization.reason in { "explicit_mode_profile_create", "explicit_mode_profile_draft_update", } mode_profile_draft_update = ( authorization.reason == "explicit_mode_profile_draft_update" ) exact_quick_control = authorization.reason == "exact_quick_control" explicit_local_control = authorization.reason in { "exact_quick_control", "explicit_registered_control", } saved_mode_execution = authorization.reason == "explicit_saved_mode_execute" mode_profile_management_route = { "explicit_mode_profile_update": "mode_profile_update", "explicit_mode_profile_delete": "mode_profile_delete", }.get(authorization.reason) if ( emit( { "type": "action.started", "message_id": message_id, "display_text": ( "正在生成模式动作预览……" if mode_profile_preview and not mode_profile_draft_update else "正在调整待保存模式动作……" if mode_profile_draft_update else "正在更新已保存的功能模式……" if mode_profile_management_route == "mode_profile_update" else "正在删除自定义功能模式……" if mode_profile_management_route == "mode_profile_delete" else "正在通过安全执行链进入功能模式……" if saved_mode_execution else "正在通过车辆安全链执行快捷控制……" if exact_quick_control else "正在通过本地规则解析并下发控制……" if explicit_local_control else "正在通过车辆安全链执行舒适性调节……" ), } ) is False ): return {"cancelled": True} stream_open = True def on_progress(payload: dict[str, object]) -> None: nonlocal stream_open if not stream_open: return validated_payload = self._validated_control_payload(payload) if validated_payload is not None: stream_open = ( emit( { "type": "validated_control_commands", "message_id": message_id, "payload": validated_payload, } ) is not False ) if not stream_open: return stream_open = ( emit( { "type": "action.progress", "message_id": message_id, "display_text": ( "模式动作已解析,正在进行安全校验……" if mode_profile_preview else "已保存模式动作已载入,正在校验并执行……" if saved_mode_execution else "功能模式存储请求已解析,正在校验……" if mode_profile_management_route is not None else "本地控制指令已通过校验并进入下发链路……" if explicit_local_control else "控制指令已通过校验,正在执行……" ), "payload": self._safe_vehicle_result(payload), } ) is not False ) try: result = ( execute_control(instruction, on_progress, self._copy_scene_plan(assistant_plan)) if assistant_plan is not None else execute_control(instruction, on_progress) ) if not isinstance(result, dict): raise TypeError("vehicle control returned an invalid result") safe_result = self._safe_vehicle_result(result) action_state, successful, completion_text = _vehicle_action_state(result) if ( saved_mode_execution and result.get("status") == "partially_succeeded" ): successful = True action_state = ( "simulated" if result.get("execution_mode") != "hardware" else "submitted" ) completion_text = ( "功能模式模拟编排已完成" if action_state == "simulated" else "功能模式指令已提交" ) elif mode_profile_preview: successful = _mode_profile_result_succeeded(result) action_state = "simulated" if successful else "failed" completion_text = ( "功能模式预览已生成" if successful else "功能模式预览生成失败" ) elif mode_profile_management_route is not None: successful = _mode_profile_management_result_succeeded( result, mode_profile_management_route, ) action_state = "simulated" if successful else "failed" completion_text = ( "功能模式已更新" if successful and mode_profile_management_route == "mode_profile_update" else "自定义功能模式已删除" if successful else "功能模式更新未完成" if mode_profile_management_route == "mode_profile_update" else "功能模式删除未完成" ) except Exception: # noqa: BLE001 - return a stable public failure result = {} safe_result = {"status": "failed", "execution_mode": "unknown", "verified": False} action_state = "failed" completion_text = "车辆控制未完成" successful = False answer = ( self._mode_profile_preview_answer(result, successful) if mode_profile_preview else self._mode_profile_management_answer( result, successful, mode_profile_management_route, ) if mode_profile_management_route is not None else self._fast_comfort_answer(authorization.reason, result, successful) ) if not stream_open: return { "cancelled": True, "route": response_route, "answer": answer, "success": successful, } events = ( { "type": "action.completed", "message_id": message_id, "success": successful, "state": action_state, "display_text": completion_text, "payload": safe_result, }, { "type": "answer.delta", "message_id": message_id, "delta": answer, }, { "type": "work.completed", "message_id": message_id, "success": successful, "state": action_state, "display_text": ( completion_text if action_state == "running" or result.get("orchestration_route") == "mode_schedule_stop" else "功能模式预览已生成" if mode_profile_preview and successful else "功能模式预览生成失败" if mode_profile_preview else completion_text if mode_profile_management_route is not None else "功能模式已提交,等待车辆状态确认" if saved_mode_execution and action_state == "submitted" else "功能模式模拟编排已完成" if saved_mode_execution and action_state == "simulated" else "功能模式执行已完成" if saved_mode_execution and successful else "功能模式执行未完成" if saved_mode_execution else "快捷控制已提交,等待车辆状态确认" if exact_quick_control and action_state == "submitted" else "快捷控制模拟编排已完成" if exact_quick_control and action_state == "simulated" else "快捷控制已完成" if exact_quick_control and successful else "快捷控制未完成" if exact_quick_control else "明确控制已提交,等待车辆状态确认" if explicit_local_control and action_state == "submitted" else "明确控制模拟编排已完成" if explicit_local_control and action_state == "simulated" else "明确控制已完成" if explicit_local_control and successful else "明确控制未完成" if explicit_local_control else "舒适性调节已提交,等待车辆状态确认" if action_state == "submitted" else "舒适性调节模拟编排已完成" if action_state == "simulated" else "舒适性调节已完成" if successful else "舒适性调节未完成" ), }, { "type": "answer.completed", "message_id": message_id, "route": response_route, "answer": answer, }, ) for event in events: if emit(event) is False: return { "cancelled": True, "route": response_route, "answer": answer, "success": successful, } return { "route": response_route, "answer": answer, "success": successful, "result": safe_result, } @staticmethod def _mode_profile_failure_feedback( result: Mapping[str, object], ) -> tuple[str, str]: def clean(value: object, *, limit: int = 420) -> str: text = re.sub(r"[\x00-\x1f\x7f]+", " ", str(value or "")) return re.sub(r"\s+", " ", text).strip(" ,,。;;::")[:limit] failure = result.get("mode_creation_failure") if isinstance(failure, Mapping): reason = clean(failure.get("reason")) next_step = clean(failure.get("next_step")) if reason and next_step: if ( INTERNAL_SCENE_BUILDER_MARKER in reason or INTERNAL_SCENE_BUILDER_MARKER in next_step ): return ( "未能根据场景名称生成通过工程能力校验的座舱动作", "请稍后重试;若该场景尚未登记,可补充希望联动的设备状态", ) return reason, next_step unresolved = result.get("unresolved_clauses") if isinstance(unresolved, Sequence) and not isinstance( unresolved, (str, bytes, bytearray), ): clauses = list( dict.fromkeys( clean(item, limit=120) for item in unresolved[:3] if clean(item, limit=120) and INTERNAL_SCENE_BUILDER_MARKER not in clean(item, limit=120) ) ) if clauses: rendered = "、".join(f"“{item}”" for item in clauses) return ( f"以下设置未能转换为可保存的座舱动作:{rendered}", ( "请按“设备 + 动作 + 明确目标”重述,例如“副驾腿托调整到80%”" "或“空调调整到24度”,然后重试" ), ) validation = result.get("validation") if isinstance(validation, Sequence) and not isinstance( validation, (str, bytes, bytearray), ): safe_codes = { "AMBIGUOUS_INTENT", "MODE_PROFILE_DRAFT_INCOMPLETE", "MODE_PROFILE_DRAFT_EMPTY", "NO_EXECUTABLE_OPERATION", "UNRESOLVED_UTTERANCE_SPAN", } for item in validation[:12]: if not isinstance(item, Mapping): continue if str(item.get("severity") or "") != "error": continue if str(item.get("code") or "") not in safe_codes: continue message = clean(item.get("message"), limit=240) if not message or re.search( r"(?:CAN|CH[01]|JSON|schema|0x[0-9a-f]+|\\|/[A-Za-z])", message, re.IGNORECASE, ): continue return ( message, "请根据上述原因改为当前支持且目标唯一的座舱设置,然后重新提交", ) status = clean(result.get("status"), limit=60) if status in {"needs_confirmation", "validation_failed"}: return ( "场景设置存在未识别、冲突或超出支持范围的内容", "请逐项明确设备、动作和单一目标值后重试;仍失败时可将设置分条提交", ) return ( "模式构建服务未返回可验证的成功结果", "请稍后重试;若持续失败,请将各项设置按“设备 + 动作 + 目标值”分条输入", ) @staticmethod def _mode_profile_preview_answer( result: Mapping[str, object], successful: bool ) -> str: if not successful: reason, next_step = CodexAssistantService._mode_profile_failure_feedback( result ) return ( f"功能模式未生成。失败原因:{reason}。" f"下一步:{next_step}。" ) draft = result.get("mode_profile_draft") name = ( str(draft.get("name") or "新功能模式") if isinstance(draft, Mapping) else "新功能模式" ) if isinstance(draft, Mapping) and isinstance(draft.get("schedule"), dict): schedule = ModeSchedule.from_dict(draft["schedule"]) return (f"已为你生成“{name}”的分阶段时间轴:{len(schedule.phases)}个阶段," f"每轮{schedule.operation_count}项动作,重复{schedule.repeat_count}次," f"总时长{schedule.total_duration_ms // 1000}秒。" "每个阶段的设备目标和持续时间见卡片。当前未保存或执行;确认后保存,进入模式才开始计时。" "执行中可输入“停止动态模式”取消后续阶段,已下发目标不会自动回滚。") if ( isinstance(draft, Mapping) and draft.get("generation_source") == "draft_revision" ): raw_operation_count = draft.get("operation_count") operation_count = ( raw_operation_count if isinstance(raw_operation_count, int) and not isinstance(raw_operation_count, bool) and raw_operation_count > 0 else 0 ) persistence_hint = ( "确认后可更新现有模式。" if draft.get("existing_mode_id") else "确认后可保存为功能模式。" ) requested_operation_count = draft.get("requested_operation_count") changed_operation_count = draft.get("changed_operation_count") no_effective_change = ( isinstance(requested_operation_count, int) and not isinstance(requested_operation_count, bool) and requested_operation_count > 0 and isinstance(changed_operation_count, int) and not isinstance(changed_operation_count, bool) and changed_operation_count == 0 ) if no_effective_change: return ( f"待保存的“{name}”模式中,该项原本已是目标状态," f"已保持不变并重新校验全部{operation_count}项组合动作。" f"原草稿未被保存或执行,可以继续调整;{persistence_hint}" ) return ( f"已调整待保存的“{name}”模式,并重新校验全部" f"{operation_count}项组合动作。原草稿未被保存或执行," f"可以继续调整;{persistence_hint}" ) if isinstance(draft, Mapping) and draft.get("existing_mode_id"): raw_operation_count = draft.get("operation_count") operation_count = ( raw_operation_count if isinstance(raw_operation_count, int) and not isinstance(raw_operation_count, bool) and raw_operation_count >= 0 else 0 ) if draft.get("generation_source") == "registered_mode_profile": return ( f"工程中已有“{name}”,已按当前车型载入预览并验证" f"{operation_count}项设备动作。创建阶段未下发,确认后可更新现有模式。" ) return ( f"已按你提供的设置为现有“{name}”生成预览并验证" f"{operation_count}项设备动作。创建阶段未下发,确认后可更新现有模式。" ) if ( isinstance(draft, Mapping) and draft.get("generation_source") == "scene_template_copy" ): raw_operation_count = draft.get("operation_count") operation_count = ( raw_operation_count if isinstance(raw_operation_count, int) and not isinstance(raw_operation_count, bool) and raw_operation_count > 0 else 0 ) return ( f"已从常用模式模板快速生成“{name}”,并按当前工程能力验证了" f"{operation_count}项设备动作。当前只是独立草稿,可以继续调整;" "创建阶段不会下发,确认预览后可保存为新的功能模式。" ) if ( isinstance(draft, Mapping) and draft.get("generation_source") == "codex_scene_composition" ): raw_operation_count = draft.get("operation_count") operation_count = ( raw_operation_count if isinstance(raw_operation_count, int) and not isinstance(raw_operation_count, bool) and raw_operation_count > 0 else 0 ) return ( f"已理解“{name}”的场景意图,并从当前工程能力中组合、验证了" f"{operation_count}项设备动作。创建阶段不会下发,确认预览后可保存。" ) return f"已为你生成“{name}”预览。创建阶段不会下发,确认动作后可保存。" @staticmethod def _mode_profile_management_answer( result: Mapping[str, object], successful: bool, route: str, ) -> str: """Render saved-mode mutations from service evidence, not model claims.""" deleting = route == "mode_profile_delete" evidence = result.get( "deleted_mode_profile" if deleting else "mode_profile" ) if successful and isinstance(evidence, Mapping): name = str(evidence.get("name") or "该模式") if deleting: return ( f"已删除自定义模式“{name}”。本次只修改模式库," "没有向车辆下发控制动作。" ) operation_count = evidence.get("operation_count") count_text = ( f",当前共包含{operation_count}项设备动作" if isinstance(operation_count, int) and not isinstance(operation_count, bool) else "" ) change_text = CodexAssistantService._mode_profile_change_summary( result.get("mode_profile_changes") ) return ( f"已更新并保存“{name}”{count_text}。" f"{change_text}" "本次只修改模式配置," "没有向车辆下发控制动作。" ) issue_code = "" issue_message = "" validation = result.get("validation") if isinstance(validation, Sequence) and not isinstance( validation, (str, bytes, bytearray), ): issue = next( (item for item in validation if isinstance(item, Mapping)), None, ) if isinstance(issue, Mapping): issue_code = str(issue.get("code") or "") issue_message = re.sub( r"\s+", " ", str(issue.get("message") or ""), ).strip()[:300] if deleting and issue_code == "MODE_PROFILE_DELETE_PROTECTED": return issue_message or "工程预设模式受保护,不能删除。" if issue_code == "MODE_PROFILE_NOT_FOUND": return issue_message or "未找到指定的功能模式,未修改模式库。" action = "删除" if deleting else "更新" if issue_message: return f"功能模式{action}未完成:{issue_message}" return f"功能模式{action}未完成,模式库没有发生变化。" @staticmethod def _mode_profile_change_summary(value: object) -> str: if not isinstance(value, Sequence) or isinstance( value, (str, bytes, bytearray), ): return "" target_labels = { "driver": "主驾", "passenger": "副驾", "rear": "后排", "vehicle": "全车", "all": "全部区域", } summaries: list[str] = [] for item in list(value)[:4]: if not isinstance(item, Mapping): continue capability = str(item.get("capability") or "") target = target_labels.get(str(item.get("target_scope") or ""), "") parameters = item.get("parameters") if not isinstance(parameters, Mapping): continue if capability == "climate.set_temperature" and isinstance( parameters.get("temperature_c"), (int, float), ): summaries.append( f"{target}空调温度调整为{parameters['temperature_c']}℃" ) elif capability == "climate.set_fan_level" and isinstance( parameters.get("level"), int, ): summaries.append(f"{target}空调风量调整为{parameters['level']}档") elif capability == "roof.set_shade_position" and isinstance( parameters.get("position_pct"), (int, float), ): summaries.append( f"遮阳帘位置调整为{parameters['position_pct']}%" ) return "已应用:" + ";".join(summaries) + "。" if summaries else "" @staticmethod def _fast_comfort_answer( reason: str, result: Mapping[str, object], successful: bool, ) -> str: if result.get("orchestration_route") == "mode_schedule_stop": if result.get("execution_mode") == "hardware" and (result.get("schedule_execution") or {}).get("status") != "cancelled": return "当前没有正在执行的动态时间轴。" return ("已停止动态模式后续阶段。已下发的设备目标保持,不自动回滚。" if result.get("execution_mode") == "hardware" else "已模拟停止动态模式,未改变正在运行的车辆任务。") if result.get("orchestration_route") == "mode_schedule_execute": if not successful: return "时间轴未能完成,后续阶段已停止。请检查车辆状态后重试。" return ("动态模式时间轴已启动,将按卡片中的时长逐阶段调节。尚未完成所有阶段;输入“停止动态模式”可取消后续阶段。" if result.get("status") == "running" else "已按虚拟时钟模拟验证动态模式的全部阶段,未向实际车辆下发操作。") if reason in { "exact_quick_control", "explicit_registered_control", "explicit_saved_mode_execute", }: instruction = re.sub( r"\s+", " ", str( result.get("utterance") or ( "功能模式" if reason == "explicit_saved_mode_execute" else "控制指令" ) ), ).strip()[:160] if not successful: return f"未能完成“{instruction}”,请检查车辆状态后重试。" execution_mode = str(result.get("execution_mode") or "dry_run") if execution_mode == "dry_run": return f"已完成模拟编排:{instruction}。" if execution_mode == "simulated_hardware": return f"已在模拟硬件环境完成:{instruction}。" physically_confirmed = ( result.get("physical_feedback_confirmed") is True or _vehicle_verification_status(result) == "verified_success" ) if physically_confirmed: return f"已完成:{instruction}。" return f"已提交:{instruction},正在等待车辆状态确认。" cooling = reason == "implicit_hot_comfort" task = "降温调节" if cooling else "升温调节" temperature = 23 if cooling else 26 if not successful: return f"{task}未完成,请检查车辆状态后重试。" detail = f"空调已开启,全车温度设置为{temperature}℃,风量设置为2档。" execution_mode = str(result.get("execution_mode") or "dry_run") if execution_mode == "dry_run": return f"已完成模拟编排:{detail}" if execution_mode == "simulated_hardware": return f"已在模拟硬件环境完成{task}:{detail}" physically_confirmed = ( result.get("physical_feedback_confirmed") is True or _vehicle_verification_status(result) == "verified_success" ) if physically_confirmed: return f"已完成{task}:{detail}" return f"已提交{task},等待车辆状态确认。" def _stream_locked( self, conversation: _Conversation, message: str, *, message_id: str, emit: EmitEvent, execute_control: ControlExecutor, capability_snapshot: Mapping[str, object], capability_context: Mapping[str, object], control_terms: Sequence[str], query_capabilities: CapabilityQuery, ) -> dict[str, object]: request_token = secrets.token_urlsafe(24) tool_context = _VehicleTurnContext( message, execute_control, _authorize_vehicle_request(message, control_terms), capability_snapshot, query_capabilities, ) vehicle_control_expected = tool_context.authorization.instruction is not None mode_profile_expected = ( tool_context.authorization.reason == "explicit_mode_profile_create" ) with self._tool_contexts_lock: self._tool_contexts[request_token] = tool_context active: ActiveCodexTurn | None = None item_phases: dict[str, str] = {} final_answer = "" streamed_answer = "" answer_redactor = _StreamingSecretRedactor(request_token) cancelled = False deadline = time.monotonic() + self._turn_timeout application_context = dict(capability_context) if not isinstance( application_context.get("natural_language_routing"), Mapping, ): application_context["natural_language_routing"] = { "original_utterance": message, "parsed_clauses": [], "resolved_clauses": [], "unresolved_clauses": [message], "cloud_input_utterance": message, } try: active = self.client.start_turn( conversation.thread_id, message, request_token, application_context=application_context, ) terminal: dict[str, object] | None = None while terminal is None: if time.monotonic() >= deadline: self.client.interrupt(active) raise CodexAssistantError("Codex turn timed out") while True: try: tool_event = tool_context.events.get_nowait() except queue.Empty: break if emit(tool_event) is False: cancelled = True self.client.interrupt(active) break if cancelled: break try: event = active.events.get(timeout=0.2) except queue.Empty: continue method = str(event.get("method") or "") params = event.get("params") params = params if isinstance(params, dict) else {} if method == "transport/error": raise CodexAssistantError(str(params.get("message") or "Codex disconnected")) if method == "item/started": item = params.get("item") if isinstance(item, dict): item_id = str(item.get("id") or "") item_phases[item_id] = str(item.get("phase") or "") ui_event = self._item_started_event(item, message_id) if ui_event and emit(ui_event) is False: cancelled = True elif method == "item/agentMessage/delta": delta = str(params.get("delta") or "") item_id = str(params.get("itemId") or "") phase = item_phases.get(item_id, "") if delta and phase == "commentary": if ( emit( { "type": "work.progress", "message_id": message_id, "display_text": "小迪正在处理……", } ) is False ): cancelled = True elif delta: safe_delta = answer_redactor.feed(delta) if safe_delta: streamed_answer += safe_delta if ( not vehicle_control_expected and emit( { "type": "answer.delta", "message_id": message_id, "delta": safe_delta, } ) is False ): cancelled = True elif method == "item/mcpToolCall/progress": if ( emit( { "type": "action.progress", "message_id": message_id, # App Server progress text is model/tool # controlled and may split secrets across # multiple notifications. Keep this UI event # fixed instead of attempting per-event # redaction. "display_text": "正在执行受控座舱操作……", } ) is False ): cancelled = True elif method == "item/completed": item = params.get("item") if isinstance(item, dict): if item.get("type") == "agentMessage": phase = str( item.get("phase") or item_phases.get(str(item.get("id") or ""), "") ) if phase != "commentary": final_answer = str(item.get("text") or final_answer).replace( request_token, "[内部令牌已隐藏]" ) ui_event = self._item_completed_event(item, message_id) if ui_event and emit(ui_event) is False: cancelled = True elif method == "error": if bool(params.get("willRetry")): if ( emit( { "type": "work.progress", "message_id": message_id, "display_text": "小迪正在重新连接……", } ) is False ): cancelled = True else: detail = params.get("error") if isinstance(detail, dict): detail = detail.get("message") or detail.get("codexErrorInfo") if not detail: detail = params.get("message") raise CodexAssistantError(str(detail or "Codex turn failed")) elif method == "turn/completed": terminal = params if cancelled: self.client.interrupt(active) break trailing_answer = answer_redactor.finish() if trailing_answer: streamed_answer += trailing_answer if ( not cancelled and not vehicle_control_expected and emit( { "type": "answer.delta", "message_id": message_id, "delta": trailing_answer, } ) is False ): cancelled = True self.client.interrupt(active) if cancelled: return {"cancelled": True, "answer": final_answer or streamed_answer} turn = terminal.get("turn") if terminal else None turn = turn if isinstance(turn, dict) else {} status = str(turn.get("status") or "failed") if status != "completed": detail = turn.get("error") if isinstance(detail, dict): detail = detail.get("message") or detail.get("codexErrorInfo") raise CodexAssistantError(str(detail or f"Codex turn {status}")) if not final_answer: final_answer = streamed_answer.strip() if vehicle_control_expected: if not tool_context.called: tool_context.action_succeeded = False final_answer = ( "车辆控制未完成:AI 助手没有调用受控执行链,未向车辆下发任何操作。" ) emit( { "type": "action.completed", "message_id": message_id, "success": False, "display_text": "车辆控制未执行", } ) elif tool_context.action_succeeded is not True: final_answer = ( self._mode_profile_preview_answer( tool_context.action_result or {}, False, ) if mode_profile_expected else "车辆控制未完成,受控执行链未返回成功结果。" ) elif mode_profile_expected: final_answer = self._mode_profile_preview_answer( tool_context.action_result or {}, True, ) elif tool_context.action_state == "submitted": final_answer = "控制指令已提交,正在等待车辆状态确认。" elif not final_answer and tool_context.action_state == "simulated": final_answer = "车辆控制模拟编排已完成。" elif not final_answer and tool_context.action_state == "verified": final_answer = "车辆控制已完成并通过状态确认。" if ( emit( { "type": "answer.delta", "message_id": message_id, "delta": final_answer, } ) is False ): return {"cancelled": True, "answer": final_answer} if not final_answer: raise CodexAssistantError("Codex produced an empty response") work_succeeded = ( tool_context.called and tool_context.action_succeeded is True if vehicle_control_expected else tool_context.action_succeeded is not False ) emit( { "type": "work.completed", "message_id": message_id, "success": work_succeeded, "display_text": ( "功能模式预览已生成" if mode_profile_expected and work_succeeded else "功能模式预览生成失败" if mode_profile_expected else "车辆控制模拟编排已完成" if vehicle_control_expected and work_succeeded and tool_context.action_state == "simulated" else "控制指令已提交,等待车辆状态确认" if vehicle_control_expected and work_succeeded and tool_context.action_state == "submitted" else "车辆控制已完成并通过状态确认" if vehicle_control_expected and work_succeeded else "小迪已完成" if work_succeeded else "车辆控制未完成" ), } ) emit( { "type": "answer.completed", "message_id": message_id, "route": "agent", "answer": final_answer, } ) conversation.touched_at = time.monotonic() return {"route": "agent", "answer": final_answer} finally: if active is not None: try: self.client.finish_turn(active) except RuntimeError as exc: # The stream error has already been translated for the # browser. Cleanup must not replace it with a second error. LOGGER.debug("Codex turn cleanup failed", exc_info=exc) with self._tool_contexts_lock: self._tool_contexts.pop(request_token, None) def execute_vehicle_control( self, request_token: str, instruction: str, *, assistant_plan: Mapping[str, object] | None = None, ) -> dict[str, object]: request_token = str(request_token).strip() instruction = " ".join(str(instruction).strip().split()) if not request_token or not instruction: raise ValueError("request_token and instruction are required") if len(instruction) > 1000: raise ValueError("instruction must not exceed 1000 characters") with self._tool_contexts_lock: context = self._tool_contexts.get(request_token) if context is None: raise CodexVehicleToolError("vehicle control context is missing or expired") with context.lock: if context.called: raise CodexVehicleToolError("vehicle control was already requested in this turn") context.called = True authorization = context.authorization if authorization.instruction is None or not hmac.compare_digest( _normalize_instruction(instruction).encode("utf-8"), _normalize_instruction(authorization.instruction).encode("utf-8"), ): context.action_succeeded = False context.events.put( { "type": "action.completed", "success": False, "display_text": "已阻止未获授权的车辆控制", } ) raise CodexVehicleToolError( "vehicle control was not authorized by the original user request" ) context.events.put( { "type": "action.started", "display_text": "正在通过车辆安全链执行控制……", } ) def on_progress(payload: dict[str, object]) -> None: validated_payload = self._validated_control_payload( payload, secret=request_token, ) if validated_payload is not None: context.events.put( { "type": "validated_control_commands", "payload": validated_payload, } ) context.events.put( { "type": "action.progress", "display_text": "控制指令已通过校验,正在执行……", "payload": self._safe_vehicle_result( payload, secret=request_token, ), } ) try: # Execute the server-owned instruction, never a model-expanded # variant. This binds the capability token to the user's intent. if assistant_plan is None: result = context.execute_control( authorization.instruction, on_progress, ) else: result = context.execute_control( authorization.instruction, on_progress, assistant_plan, ) except Exception as exc: context.action_succeeded = False context.events.put( { "type": "action.completed", "success": False, "display_text": "车辆控制未完成", } ) raise CodexVehicleToolError(self._safe_control_error(exc)) from exc safe_result = self._safe_vehicle_result( result, secret=request_token, ) action_state, successful, completion_text = _vehicle_action_state(result) if context.authorization.reason == "explicit_mode_profile_create": successful = _mode_profile_result_succeeded(result) action_state = "simulated" if successful else "failed" completion_text = ( "功能模式预览已生成" if successful else "功能模式预览生成失败" ) context.action_result = result context.action_succeeded = successful context.action_state = action_state context.events.put( { "type": "action.completed", "success": successful, "state": action_state, "display_text": completion_text, "payload": safe_result, } ) return safe_result def execute_cockpit_request( self, request_token: str, assistant_plan: Mapping[str, object] | None = None, ) -> dict[str, object]: """Execute the bound request, optionally using a guarded semantic proposal.""" request_token = str(request_token).strip() with self._tool_contexts_lock: context = self._tool_contexts.get(request_token) if context is None: raise CodexVehicleToolError("vehicle control context is missing or expired") instruction = context.authorization.instruction if instruction is None: raise CodexVehicleToolError( "vehicle control was not authorized by the original user request" ) normalized_plan: dict[str, object] | None = None if assistant_plan is not None: if not isinstance(assistant_plan, Mapping): raise ValueError("assistant_plan must be an object") try: encoded_plan = json.dumps( dict(assistant_plan), ensure_ascii=False, allow_nan=False, separators=(",", ":"), ) except (TypeError, ValueError) as exc: raise ValueError("assistant_plan must contain JSON values") from exc if len(encoded_plan.encode("utf-8")) > 24 * 1024: raise ValueError("assistant_plan is too large") normalized_plan = json.loads(encoded_plan.replace(request_token, "[内部令牌已隐藏]")) # Free-form model prose is neither needed for execution nor safe # to persist. Keep only structural intent and replace narrative # fields before the plan reaches artifacts or progress payloads. if "decision_summary" in normalized_plan: normalized_plan["decision_summary"] = ["小迪生成的结构化计划已提交应用安全链复核。"] if "ambiguities" in normalized_plan: normalized_plan["ambiguities"] = ( [ { "field": "assistant_plan", "question": "小迪生成的候选计划包含未解决歧义。", } ] if isinstance(normalized_plan["ambiguities"], list) and normalized_plan["ambiguities"] else [] ) requests = normalized_plan.get("requests") if isinstance(requests, list): for request in requests: if isinstance(request, dict) and "reason" in request: request["reason"] = "原始用户请求的结构化候选动作。" return self.execute_vehicle_control( request_token, instruction, assistant_plan=normalized_plan, ) def get_n7web_capabilities( self, request_token: str, query: str = "", ) -> dict[str, object]: """Return the turn-bound authoritative catalog through the read-only MCP tool.""" request_token = str(request_token).strip() query = " ".join(str(query).strip().split()) if not request_token: raise ValueError("request_token is required") if len(query) > 160: raise ValueError("query must not exceed 160 characters") with self._tool_contexts_lock: context = self._tool_contexts.get(request_token) if context is None: raise CodexVehicleToolError("capability context is missing or expired") with context.lock: if context.capability_queries >= 4: raise CodexVehicleToolError("capability query limit exceeded for this turn") context.capability_queries += 1 try: result = context.query_capabilities(context.capability_snapshot, query) serialized = json.dumps(result, ensure_ascii=False, default=str) except Exception as exc: raise CodexVehicleToolError( "capability catalog is temporarily unavailable" ) from exc if len(serialized.encode("utf-8")) > 512 * 1024: raise CodexVehicleToolError("capability catalog response is too large") normalized = json.loads(serialized) if not isinstance(normalized, dict): raise CodexVehicleToolError("capability catalog returned an invalid result") return { "ok": True, "source": "n7web_runtime_registry", "catalog": normalized, } def authenticate_internal_request(self, candidate: str) -> bool: return hmac.compare_digest( str(candidate).encode("utf-8"), self._internal_token.encode("utf-8"), ) def _conversation( self, principal: str, conversation_id: str, ) -> tuple[_Conversation, tuple[str, str]]: key = (str(principal or "anonymous")[:128], conversation_id) with self._sessions_lock: existing = self._sessions.pop(key, None) if existing is not None: existing.in_use += 1 self._sessions[key] = existing evicted = self._prune_sessions_locked(protected=key) if existing is not None: for conversation in evicted: self._unsubscribe_thread(conversation.thread_id) return existing, key # App Server startup can involve model-provider I/O. Keep that latency # outside the global session lock so unrelated conversations continue. created = _Conversation( self.client.start_thread( developer_instructions=self._instructions, ephemeral=True, ) ) redundant: _Conversation | None = None with self._sessions_lock: existing = self._sessions.pop(key, None) if existing is None: existing = created else: # Another request won the same-conversation creation race. Both # callers share its per-conversation lock; release our extra # App Server subscription after leaving the global lock. redundant = created existing.in_use += 1 self._sessions[key] = existing evicted = self._prune_sessions_locked(protected=key) if redundant is not None: self._unsubscribe_thread(redundant.thread_id) for conversation in evicted: self._unsubscribe_thread(conversation.thread_id) return existing, key def _release_conversation( self, key: tuple[str, str], conversation: _Conversation, ) -> None: with self._sessions_lock: current = self._sessions.get(key) if current is conversation: conversation.in_use = max(0, conversation.in_use - 1) conversation.touched_at = time.monotonic() evicted = self._prune_sessions_locked() for stale in evicted: self._unsubscribe_thread(stale.thread_id) def _prune_sessions_locked( self, *, protected: tuple[str, str] | None = None, ) -> list[_Conversation]: evicted: list[_Conversation] = [] while len(self._sessions) > self._max_sessions: stale_key = next( ( candidate_key for candidate_key, candidate in self._sessions.items() if candidate_key != protected and candidate.in_use == 0 ), None, ) if stale_key is None: break evicted.append(self._sessions.pop(stale_key)) return evicted def _unsubscribe_thread(self, thread_id: str) -> None: unsubscribe = getattr(self.client, "unsubscribe_thread", None) if not callable(unsubscribe): return try: unsubscribe(thread_id) except RuntimeError as exc: LOGGER.debug("Codex thread unsubscribe failed", exc_info=exc) @staticmethod def _item_started_event( item: dict[str, object], message_id: str, ) -> dict[str, object] | None: item_type = str(item.get("type") or "") if item_type in {"agentMessage", "reasoning", "mcpToolCall"}: return None labels = { "commandExecution": "正在执行受限工作步骤……", "webSearch": "正在检索信息……", "fileChange": "正在处理文件变更……", } return { "type": "work.started", "message_id": message_id, "work_type": item_type, "display_text": labels.get(item_type, "小迪正在处理……"), } @staticmethod def _item_completed_event( item: dict[str, object], message_id: str, ) -> dict[str, object] | None: item_type = str(item.get("type") or "") if item_type in {"agentMessage", "reasoning", "mcpToolCall"}: return None if item_type not in {"commandExecution", "webSearch", "fileChange"}: return None status = str(item.get("status") or "completed") success = status not in {"failed", "declined", "cancelled"} return { "type": "work.completed", "message_id": message_id, "work_type": item_type, "success": success, "display_text": "工作步骤已完成" if success else "工作步骤未完成", } @staticmethod def _safe_result_value( value: object, *, secret: str, depth: int = 0, ) -> object: """Project bounded JSON data and remove tool-boundary secrets.""" if value is None or isinstance(value, (bool, int, float)): return value if isinstance(value, str): text = value.replace(secret, "[内部令牌已隐藏]") if secret else value return text[:1000] if depth >= 2: return None if isinstance(value, Mapping): safe: dict[str, object] = {} for raw_key, raw_value in list(value.items())[:24]: key = str(raw_key)[:80] if re.search( r"token|secret|password|authorization|approval|api.?key|" r"credential|cookie|bearer|private.?key|access.?key|" r"raw.?can|can.?frame|data.?hex|payload.?hex", key, re.IGNORECASE, ): continue safe[key] = CodexAssistantService._safe_result_value( raw_value, secret=secret, depth=depth + 1, ) return safe if isinstance(value, Sequence) and not isinstance( value, (str, bytes, bytearray), ): return [ CodexAssistantService._safe_result_value( item, secret=secret, depth=depth + 1, ) for item in list(value)[:24] ] text = str(value) if secret: text = text.replace(secret, "[内部令牌已隐藏]") return text[:240] @staticmethod def _validated_control_payload( result: Mapping[str, object], *, secret: str = "", ) -> dict[str, object] | None: """Build a browser-only allowlist projection of compiled controls. MCP tool results intentionally exclude command plans and raw CAN material. The web console still needs a deterministic view after validation, so copy only semantic fields, canonical debug commands, reconstructed IR payloads, display-safe CAN bytes, and stage timings. """ raw_configurations = result.get("function_configs") configuration_entries: list[tuple[str, Mapping[str, object]]] = [] if isinstance(raw_configurations, Mapping): configuration_entries.extend( (str(key), value) for key, value in raw_configurations.items() if isinstance(value, Mapping) ) if not configuration_entries and isinstance( result.get("function_config"), Mapping ): configuration_entries.append(("default", result["function_config"])) raw_plans = result.get("command_plans") plan_entries: list[tuple[str, Mapping[str, object]]] = [] if isinstance(raw_plans, Mapping): plan_entries.extend( (str(key), value) for key, value in raw_plans.items() if isinstance(value, Mapping) ) if not plan_entries and isinstance(result.get("command_plan"), Mapping): plan_entries.append(("default", result["command_plan"])) if not configuration_entries or not plan_entries: return None def safe_text(value: object, limit: int) -> str: """Return bounded display text without copying the request secret.""" text = str(value or "") if secret and secret in text: return "" return text[:limit] def command_refs(command: Mapping[str, object]) -> tuple[str, ...]: raw_refs = command.get("operation_ref") if isinstance(raw_refs, Sequence) and not isinstance( raw_refs, (str, bytes, bytearray) ): values = raw_refs else: values = str(raw_refs or "").split(",") return tuple(str(value).strip() for value in values if str(value).strip()) def matching_commands( entries: Sequence[tuple[str, Mapping[str, object]]], operation_id: str, ) -> list[tuple[Mapping[str, object], Mapping[str, object]]]: matches: list[tuple[Mapping[str, object], Mapping[str, object]]] = [] for _plan_key, plan in entries: spec = plan.get("spec") if not isinstance(spec, Mapping): continue commands = spec.get("commands") if not isinstance(commands, Sequence) or isinstance( commands, (str, bytes, bytearray) ): continue matches.extend( (command, plan) for command in commands if isinstance(command, Mapping) and operation_id in command_refs(command) ) return matches presentations: list[dict[str, object]] = [] for config_key, configuration in configuration_entries[:16]: spec = configuration.get("spec") if not isinstance(spec, Mapping): continue operations = spec.get("operations") if not isinstance(operations, Sequence) or isinstance( operations, (str, bytes, bytearray) ): continue raw_domain = str(spec.get("domain") or "") domain = safe_text(raw_domain, 80) preferred_plans = [ entry for entry in plan_entries if entry[0] == config_key ] for operation in operations[:24]: if not isinstance(operation, Mapping): continue raw_operation_id = str(operation.get("id") or "").strip()[:160] operation_id = safe_text(raw_operation_id, 160).strip() capability = safe_text(operation.get("capability"), 160).strip() if not raw_operation_id or not operation_id or not capability: continue compiled = matching_commands(preferred_plans, raw_operation_id) if ( not preferred_plans and len(configuration_entries) == 1 and len(plan_entries) == 1 ): # Keep the legacy function_config/command_plan pair working, # but never search globally across multi-config results. A # repeated operation id must fail closed instead of borrowing # a compiled command from another domain. compiled = matching_commands(plan_entries, raw_operation_id) if not compiled: continue channel = "IR" if raw_domain == "self_extension" else "CAN1" actual_commands: list[str] = [] frames: list[dict[str, str]] = [] for command, plan in compiled[:16]: signals = command.get("signals") signals = signals if isinstance(signals, Mapping) else {} plan_spec = plan.get("spec") plan_domain = ( str(plan_spec.get("domain") or "") if isinstance(plan_spec, Mapping) else "" ) message = str(command.get("message") or "") bus = str(command.get("bus") or "").lower() if message == "infrared.emit" or plan_domain == "self_extension": command_channel = "IR" elif "seat" in bus or "can0" in bus: command_channel = "CAN0" else: command_channel = "CAN1" channel = command_channel debug_command = " ".join( safe_text(signals.get("debug_command"), 256).split() ) if re.fullmatch(r"[A-Za-z0-9_.:+% -]{1,256}", debug_command): if debug_command not in actual_commands: actual_commands.append(debug_command) slot_value = signals.get("slot") slot = ( int(slot_value) if isinstance(slot_value, int) and not isinstance(slot_value, bool) and 1 <= slot_value <= 248 else None ) if message == "infrared.emit" and slot is not None: actual = f"infrared emit {slot}" if actual not in actual_commands: actual_commands.append(actual) frame = { "label": "GSCU1B TCP", "value": json.dumps( { "action": "emit", "data": {"no": slot}, "type": "infrared", }, ensure_ascii=False, separators=(",", ":"), ), } if frame not in frames: frames.append(frame) continue frame_id = str(signals.get("frame_id") or "").strip() data_bytes = " ".join( str(signals.get("data_hex") or "").strip().split() ).upper() if re.fullmatch( r"0[xX][0-9A-Fa-f]{1,8}", frame_id ) and re.fullmatch( r"[0-9A-F]{2}(?: [0-9A-F]{2}){0,63}", data_bytes ): frame = { "label": f"{command_channel} 帧", "value": f"0x{frame_id[2:].upper()} · {data_bytes}", } if frame not in frames: frames.append(frame) safe_target = CodexAssistantService._safe_result_value( operation.get("target") or {}, secret=secret ) if raw_domain == "self_extension" and isinstance(safe_target, dict): safe_target = { key: safe_target[key] for key in ("device", "name") if key in safe_target } safe_parameters = CodexAssistantService._safe_result_value( operation.get("parameters") or {}, secret=secret ) presentations.append( { "operation": { "id": operation_id, "capability": capability, "target": safe_target if isinstance(safe_target, dict) else {}, "parameters": ( safe_parameters if isinstance(safe_parameters, dict) else {} ), **( { "parse_source": safe_text( operation.get("parse_source"), 80 ) } if safe_text(operation.get("parse_source"), 80) else {} ), }, "domain": domain, "channel": channel, "actual_command": ";".join(actual_commands), "frames": frames, } ) if not presentations: return None allowed_trace_stages = { "intent_parse", "context_resolve", "domain_route", "function_generate", "schema_validate", "domain_validate", "compile", "nfc_peripheral_route", "local_peripheral_plan", "nfc_peripheral_sequence", } safe_trace: list[dict[str, object]] = [] raw_trace = result.get("trace_events") if isinstance(raw_trace, Sequence) and not isinstance( raw_trace, (str, bytes, bytearray) ): for item in raw_trace[:32]: if not isinstance(item, Mapping): continue stage = str(item.get("stage") or "") duration = item.get("duration_ms") if ( stage in allowed_trace_stages and isinstance(duration, (int, float)) and not isinstance(duration, bool) and duration >= 0 ): safe_trace.append({"stage": stage, "duration_ms": duration}) payload: dict[str, object] = { "validated_control_commands": presentations, "trace_events": safe_trace, } for key in ( "request_id", "preview_request_id", "supplement_of", "utterance", "provider_used", "execution_mode", "control_transport", "local_control_budget_ms", "local_control_elapsed_ms", "local_control_within_budget", "natural_language_routing", "parallel_dispatch", ): if result.get(key) is not None: payload[key] = CodexAssistantService._safe_result_value( result[key], secret=secret ) return payload @staticmethod def _safe_vehicle_result( result: dict[str, object], *, secret: str = "", ) -> dict[str, object]: safe: dict[str, object] = { "status": str(result.get("status") or "unknown"), "execution_mode": str(result.get("execution_mode") or "dry_run"), } if isinstance(result.get("hardware_execution_allowed"), bool): safe["hardware_execution_allowed"] = result[ "hardware_execution_allowed" ] for key in ("request_id", "utterance", "decision_summary"): if result.get(key) is not None: safe[key] = CodexAssistantService._safe_result_value( result[key], secret=secret, ) actions: list[dict[str, object]] = [] intent_ir = result.get("intent_ir") if isinstance(intent_ir, dict): spec = intent_ir.get("spec") if isinstance(spec, dict) and isinstance(spec.get("actions"), list): for item in spec["actions"][ :96 if result.get("schedule") else MAX_PLAN_REQUESTS ]: if not isinstance(item, dict): continue safe_action = { key: CodexAssistantService._safe_result_value( item[key], secret=secret, ) for key in ("domain", "capability", "target", "parameters") if key in item } if ( item.get("domain") == "self_extension" and isinstance(item.get("target"), Mapping) ): safe_action["target"] = { key: CodexAssistantService._safe_result_value( item["target"][key], secret=secret, ) for key in ("device", "name") if key in item["target"] } actions.append(safe_action) safe["actions"] = actions infrared_actions: list[dict[str, object]] = [] raw_infrared = result.get("infrared_actions") if isinstance(raw_infrared, list): for item in raw_infrared[:MAX_PLAN_REQUESTS]: if not isinstance(item, dict): continue infrared_actions.append( { key: CodexAssistantService._safe_result_value( item[key], secret=secret, ) for key in ( "capability_id", "device_id", "device_name", "action", "slot", "transport", "parse_source", ) if key in item } ) if infrared_actions: safe["infrared_actions"] = infrared_actions mode_profile = result.get("mode_profile") if isinstance(mode_profile, dict): safe["mode_profile"] = { key: CodexAssistantService._safe_result_value( mode_profile[key], secret=secret, ) for key in ( "id", "name", "description", "domains", "operation_count", "origin", ) if key in mode_profile } mode_profile_changes = result.get("mode_profile_changes") if isinstance(mode_profile_changes, list): safe["mode_profile_changes"] = [ { key: CodexAssistantService._safe_result_value( item[key], secret=secret, ) for key in ("domain", "capability", "target_scope", "parameters") if key in item } for item in mode_profile_changes[:MAX_PLAN_REQUESTS] if isinstance(item, Mapping) ] deleted_mode_profile = result.get("deleted_mode_profile") if isinstance(deleted_mode_profile, dict): safe["deleted_mode_profile"] = { key: CodexAssistantService._safe_result_value( deleted_mode_profile[key], secret=secret, ) for key in ( "id", "name", "description", "domains", "operation_count", "origin", ) if key in deleted_mode_profile } mode_profile_draft = result.get("mode_profile_draft") if isinstance(mode_profile_draft, dict): safe["mode_profile_draft"] = { key: CodexAssistantService._safe_result_value( mode_profile_draft[key], secret=secret, ) for key in ( "name", "instruction", "source_request_id", "vehicle_id", "domains", "operation_count", "existing_mode_id", "existing_mode_revision", "source_template_id", "source_template_revision", "generation_source", "draft_revision", "revises_source_request_id", "requested_operation_count", "changed_operation_count", ) if key in mode_profile_draft } for key in ("mode_profile_draft", "mode_profile"): source = result.get(key) if isinstance(source, dict) and isinstance(source.get("schedule"), dict): try: safe[key]["schedule"] = ModeSchedule.from_dict(source["schedule"]).to_dict() except ValueError: pass if isinstance(result.get("schedule"), dict): try: safe["schedule"] = ModeSchedule.from_dict(result["schedule"]).to_dict() except ValueError: pass if isinstance(result.get("schedule_execution"), dict): safe["schedule_execution"] = public_schedule_status(result["schedule_execution"]) if isinstance(result.get("vehicle"), dict): safe["vehicle"] = {"id": str(result["vehicle"].get("id") or "")[:128]} failure = result.get("mode_creation_failure") if isinstance(failure, Mapping): reason, next_step = CodexAssistantService._mode_profile_failure_feedback( result ) raw_unresolved = failure.get("unresolved_clauses") unresolved_items = ( list(raw_unresolved)[:3] if isinstance(raw_unresolved, Sequence) and not isinstance(raw_unresolved, (str, bytes, bytearray)) else [] ) safe["failure_feedback"] = { "reason": reason, "next_step": next_step, "unresolved_clauses": [ CodexAssistantService._safe_result_value( item, secret=secret, ) for item in unresolved_items if isinstance(item, str) and INTERNAL_SCENE_BUILDER_MARKER not in item ], } peripheral = result.get("peripheral") if isinstance(peripheral, dict): safe["peripheral"] = { key: CodexAssistantService._safe_result_value( peripheral[key], secret=secret, ) for key in ("id", "device_id", "device_name", "action", "slot") if key in peripheral } if result.get("orchestration_route") is not None: safe["orchestration_route"] = str(result["orchestration_route"]) if isinstance(result.get("parallel_dispatch"), Mapping): safe["parallel_dispatch"] = CodexAssistantService._safe_result_value( result["parallel_dispatch"], secret=secret, ) validation = result.get("validation") if isinstance(validation, Sequence) and not isinstance( validation, (str, bytes, bytearray), ): safe["validation"] = [ { key: CodexAssistantService._safe_result_value( item[key], secret=secret, ) for key in ("severity", "code", "message") if key in item } for item in list(validation)[:8] if isinstance(item, Mapping) ] deferred = result.get("deferred_requests") if isinstance(deferred, list): safe["deferred_count"] = len(deferred) verification_status = _vehicle_verification_status(result) safe["verification_status"] = verification_status safe["verified"] = verification_status == "verified_success" return safe @staticmethod def _safe_control_error(error: Exception) -> str: names = { "HardwareAccessRequiredError": "vehicle hardware access is not available", "HardwareBusyError": "the vehicle control channel is busy", "HardwareSafetyError": "the request did not pass the vehicle safety gate", "CanGatewayError": "the vehicle control channel is unavailable", } return names.get(type(error).__name__, "vehicle control failed safely")