From 4381ec8fee2f36cf97050063eb867406ecd52c4b Mon Sep 17 00:00:00 2001 From: zyssyz123 <916125788@qq.com> Date: Thu, 16 Jul 2026 11:11:08 +0800 Subject: [PATCH] fix(agent): accept legacy missing asset placeholders (#39039) Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> --- api/models/agent_config_entities.py | 18 +++++++++ .../services/agent/test_agent_dsl_service.py | 37 +++++++++++++++++++ 2 files changed, 55 insertions(+) diff --git a/api/models/agent_config_entities.py b/api/models/agent_config_entities.py index 7ce6a103046..c4428b716bf 100644 --- a/api/models/agent_config_entities.py +++ b/api/models/agent_config_entities.py @@ -188,6 +188,14 @@ def validate_config_skill_name(name: str) -> str: return normalized +def _normalize_legacy_missing_asset_file_id(value: Any) -> Any: + """Canonicalize the null placeholder emitted by early portable Agent DSLs.""" + + if isinstance(value, dict) and value.get("is_missing") is True and value.get("file_id") is None: + return {**value, "file_id": ""} + return value + + class AgentConfigFileRefConfig(BaseModel): """Stable Agent Soul reference to one config file payload.""" @@ -201,6 +209,11 @@ class AgentConfigFileRefConfig(BaseModel): hash: str | None = None mime_type: str | None = None + @model_validator(mode="before") + @classmethod + def _normalize_legacy_file_id(cls, value: Any) -> Any: + return _normalize_legacy_missing_asset_file_id(value) + @field_validator("name") @classmethod def _validate_name(cls, value: str) -> str: @@ -231,6 +244,11 @@ class AgentConfigSkillRefConfig(BaseModel): hash: str | None = None mime_type: str | None = "application/zip" + @model_validator(mode="before") + @classmethod + def _normalize_legacy_file_id(cls, value: Any) -> Any: + return _normalize_legacy_missing_asset_file_id(value) + @field_validator("name") @classmethod def _validate_name(cls, value: str) -> str: diff --git a/api/tests/unit_tests/services/agent/test_agent_dsl_service.py b/api/tests/unit_tests/services/agent/test_agent_dsl_service.py index 00aecefbaa6..c80c7a788a7 100644 --- a/api/tests/unit_tests/services/agent/test_agent_dsl_service.py +++ b/api/tests/unit_tests/services/agent/test_agent_dsl_service.py @@ -153,6 +153,43 @@ def test_agent_package_round_trips_as_strict_dsl_dto() -> None: assert restored == package +def test_agent_package_normalizes_legacy_null_missing_asset_file_ids() -> None: + package = make_portable_agent_package( + _agent(), + AgentSoulConfig.model_validate( + { + "config_skills": [{"name": "research", "file_id": "skill-file"}], + "config_files": [{"name": "guide.md", "file_kind": "tool_file", "file_id": "config-file"}], + } + ), + ).model_dump(mode="json") + package["soul"]["config_skills"][0]["file_id"] = None + package["soul"]["config_files"][0]["file_id"] = None + + restored = AgentPackage.model_validate(package) + + assert restored.soul.config_skills[0].file_id == "" + assert restored.soul.config_files[0].file_id == "" + assert restored.model_dump(mode="json")["soul"]["config_skills"][0]["file_id"] == "" + assert restored.model_dump(mode="json")["soul"]["config_files"][0]["file_id"] == "" + + +@pytest.mark.parametrize( + "asset", + [ + {"name": "research", "file_id": None, "is_missing": False}, + {"name": "guide.md", "file_kind": "tool_file", "file_id": None, "is_missing": False}, + ], +) +def test_agent_package_rejects_null_file_id_for_available_assets(asset: dict) -> None: + package = make_portable_agent_package(_agent(), AgentSoulConfig()).model_dump(mode="json") + target = "config_files" if "file_kind" in asset else "config_skills" + package["soul"][target] = [asset] + + with pytest.raises(ValidationError): + AgentPackage.model_validate(package) + + def test_import_warnings_cover_runtime_setup_removed_from_package(monkeypatch) -> None: soul = AgentSoulConfig.model_validate( {