Support direct CF and CFE inputs in AI structure flow
CI / python (push) Has been cancelled
CI / rust (push) Has been cancelled

This commit is contained in:
2026-05-22 00:45:57 +03:00
parent b85bff6e06
commit de8b0eb795
3 changed files with 187 additions and 19 deletions
+13 -17
View File
@@ -587,7 +587,14 @@ async def _start_ai_structure_agent_job(*, project_id: str, effective_project_id
if not binary_files:
raise HTTPException(status_code=400, detail="Во входном пути не найдены файлы .cf или .cfe.")
source = ImportSourceKind.CF_FILE if any(path.suffix.casefold() == ".cf" for path in binary_files) else ImportSourceKind.CFE_FILE
cf_files = [path for path in binary_files if path.suffix.casefold() == ".cf"]
cfe_files = [path for path in binary_files if path.suffix.casefold() == ".cfe"]
if cf_files and cfe_files:
raise HTTPException(
status_code=400,
detail="Во входной папке одновременно лежат .cf и .cfe. Укажите конкретный файл, который нужно подготовить для ИИ.",
)
source = ImportSourceKind.CF_FILE if cf_files else ImportSourceKind.CFE_FILE
agent_id = _agent_id_for_source(settings, ImportSourceKind.CF_FILE)
if not agent_id:
raise HTTPException(status_code=400, detail="В настройках проекта не выбран Windows Agent для CF/CFE.")
@@ -603,26 +610,14 @@ async def _start_ai_structure_agent_job(*, project_id: str, effective_project_id
local_path: str | None = None
if source == ImportSourceKind.CF_FILE:
one_c_server = _agent_string_value(agent, "one_c_server") or _agent_string_value(agent, "published_1c_server") or _agent_string_value(agent, "published_server_url")
one_c_infobase = _agent_string_value(agent, "one_c_infobase") or _agent_string_value(agent, "published_infobase")
if one_c_server.startswith(("http://", "https://")):
one_c_server = urlsplit(one_c_server).hostname or one_c_server
if not one_c_server or not one_c_infobase:
if len(cf_files) != 1:
raise HTTPException(
status_code=400,
detail="Для разбора .cf нужен сервер 1С и имя информационной базы в настройках проекта. Сейчас они не заполнены.",
detail="Для прямого разбора .cf укажите один конкретный файл .cf, а не папку с несколькими конфигурациями.",
)
metadata.update(
{
"one_c_server": one_c_server,
"one_c_infobase": one_c_infobase,
"one_c_user": _agent_string_value(agent, "one_c_user") or None,
"one_c_password": _agent_string_value(agent, "one_c_password") or None,
"include_extensions": True,
}
)
local_path = str(cf_files[0])
metadata["input_mode"] = "cf_file"
else:
cfe_files = [path for path in binary_files if path.suffix.casefold() == ".cfe"]
if len(cfe_files) != 1:
raise HTTPException(
status_code=400,
@@ -631,6 +626,7 @@ async def _start_ai_structure_agent_job(*, project_id: str, effective_project_id
cfe_file = cfe_files[0]
local_path = str(cfe_file)
metadata["one_c_extension"] = cfe_file.stem
metadata["input_mode"] = "cfe_file"
return await create_agent_import_job(
effective_project_id,
+74
View File
@@ -1862,6 +1862,7 @@ def test_html5_ai_structure_routes_binary_cf_through_windows_agent(tmp_path: Pat
assert claimed.status_code == 200
assert claimed.json()["job_id"] == job_id
assert claimed.json()["source"] == "CF_FILE"
assert claimed.json()["local_path"] == str(cf_input)
completed = client.post(
f"/agent/jobs/{job_id}/result",
@@ -1887,6 +1888,79 @@ def test_html5_ai_structure_routes_binary_cf_through_windows_agent(tmp_path: Pat
assert (output / f"codex-1c-context-{project_id}" / "AGENTS.md").exists()
def test_html5_ai_structure_routes_binary_cfe_through_windows_agent(tmp_path: Path):
metadata_root = tmp_path / "extension"
metadata_root.mkdir()
(metadata_root / "metadata.xml").write_text(
"""
<Configuration>
<Catalog name="Контрагенты" qualifiedName="Справочник.Контрагенты" />
</Configuration>
""",
encoding="utf-8",
)
cfe_input = tmp_path / "MyExtension.cfe"
cfe_input.write_bytes(b"binary-cfe")
output = tmp_path / "ai-out-cfe"
client = TestClient(app)
project_id = f"ai-agent-cfe-{uuid4()}"
agent_id = f"win-agent-{uuid4()}"
indexed = client.post("/projects/index", json={"path": str(metadata_root), "project_id": project_id})
assert indexed.status_code == 200
settings = client.post(
f"/projects/{project_id}/settings",
json={
"name": "AI Agent Extension Demo",
"structure_source": "CFE_FILE",
"agent": {
"cf_agent_id": agent_id,
},
},
)
assert settings.status_code == 200
heartbeat = client.post("/agent/heartbeat", json={"agent_id": agent_id, "host": "test-host"})
assert heartbeat.status_code == 200
queued = client.post(
f"/html5/projects/{project_id}/ai-structure/run",
data={"project_id": project_id, "input_path": str(cfe_input), "output_path": str(output)},
)
assert queued.status_code == 200
match = re.search(r"/html5/projects/[^/]+/ai-structure/jobs/([A-Za-z0-9-]+)", queued.text)
assert match is not None
job_id = match.group(1)
claimed = client.get("/agent/jobs/next", params={"agent_id": agent_id})
assert claimed.status_code == 200
assert claimed.json()["job_id"] == job_id
assert claimed.json()["source"] == "CFE_FILE"
assert claimed.json()["local_path"] == str(cfe_input)
assert claimed.json()["metadata"]["one_c_extension"] == "MyExtension"
completed = client.post(
f"/agent/jobs/{job_id}/result",
json={
"status": "SUCCEEDED",
"server_path": str(metadata_root),
"logs": ["Выгрузка расширения завершена."],
},
)
assert completed.status_code == 200
deadline = time.monotonic() + 10
fragment = ""
while time.monotonic() < deadline:
polled = client.get(f"/html5/projects/{project_id}/ai-structure/jobs/{job_id}")
assert polled.status_code == 200
fragment = polled.text
if "готово" in fragment:
break
time.sleep(0.05)
assert "готово" in fragment
assert (output / f"codex-1c-context-{project_id}" / "AGENTS.md").exists()
def test_import_full_replace_replaces_current_normalized_project(tmp_path: Path):
first = tmp_path / "first"
second = tmp_path / "second"