refactor: unify skills+cli runtime and streamline ag-ui flow
This commit is contained in:
@@ -38,12 +38,12 @@ async def test_attachment_storage_rejects_unexpected_bucket(
|
||||
|
||||
storage = SupabaseService()
|
||||
monkeypatch.setattr(
|
||||
app_config.storage,
|
||||
app_config.storage.attachment,
|
||||
"bucket",
|
||||
"allowed-bucket",
|
||||
)
|
||||
|
||||
with pytest.raises(RuntimeError, match="Invalid attachment bucket"):
|
||||
with pytest.raises(RuntimeError, match="Invalid storage bucket"):
|
||||
await storage.upload_bytes(
|
||||
bucket="other-bucket",
|
||||
path="agent-inputs/u/t/r/file.png",
|
||||
@@ -62,7 +62,7 @@ async def test_attachment_storage_accepts_configured_bucket(
|
||||
fake_bucket = _FakeBucket()
|
||||
fake_client = SimpleNamespace(storage=_FakeStorage(fake_bucket))
|
||||
monkeypatch.setattr(
|
||||
app_config.storage,
|
||||
app_config.storage.attachment,
|
||||
"bucket",
|
||||
"allowed-bucket",
|
||||
)
|
||||
|
||||
@@ -3,7 +3,7 @@ from __future__ import annotations
|
||||
from uuid import uuid4
|
||||
|
||||
import pytest
|
||||
from fastapi import HTTPException
|
||||
from core.http.errors import ApiProblemError
|
||||
|
||||
from core.auth.models import CurrentUser
|
||||
from v1.agent.service import ensure_session_owner
|
||||
@@ -12,5 +12,5 @@ from v1.agent.service import ensure_session_owner
|
||||
def test_owner_guard_denies_non_owner() -> None:
|
||||
user = CurrentUser(id=uuid4(), phone="self@example.com")
|
||||
|
||||
with pytest.raises(HTTPException):
|
||||
with pytest.raises(ApiProblemError):
|
||||
ensure_session_owner(owner_id="other-user", current_user=user)
|
||||
|
||||
@@ -6,10 +6,10 @@ from urllib.parse import quote
|
||||
from uuid import UUID
|
||||
|
||||
from ag_ui.core import RunAgentInput
|
||||
from fastapi import HTTPException
|
||||
import pytest
|
||||
|
||||
import v1.agent.service as agent_service_module
|
||||
from core.http.errors import ApiProblemError
|
||||
from core.auth.models import CurrentUser
|
||||
from core.config.settings import config
|
||||
from schemas.domain.chat_message import AgentChatMessageMetadata
|
||||
@@ -25,7 +25,7 @@ class _FakeRepository:
|
||||
async def get_session_owner(self, *, session_id: str) -> str:
|
||||
if session_id == "00000000-0000-0000-0000-000000000001":
|
||||
return "00000000-0000-0000-0000-000000000001"
|
||||
raise HTTPException(status_code=404, detail="Session not found")
|
||||
raise ApiProblemError(status_code=404, detail="Session not found")
|
||||
|
||||
async def create_session_for_user(
|
||||
self, *, user_id: str, session_id: str | None = None
|
||||
@@ -92,7 +92,7 @@ class _FakeRepository:
|
||||
"timeout_seconds": 30,
|
||||
"visibility_consumer_bit": bit,
|
||||
"context_messages": {"mode": "number", "count": 20},
|
||||
"enabled_tools": [],
|
||||
"enabled_skills": [],
|
||||
},
|
||||
}
|
||||
|
||||
@@ -201,7 +201,7 @@ def _build_run_input(*, urls: list[str], runtime_mode: str = "chat") -> RunAgent
|
||||
@pytest.mark.asyncio
|
||||
async def test_enqueue_run_rejects_non_project_host_signed_url(monkeypatch) -> None:
|
||||
monkeypatch.setattr(
|
||||
agent_service_module.config.storage, "bucket", "agent-test-bucket"
|
||||
agent_service_module.config.storage.attachment, "bucket", "agent-test-bucket"
|
||||
)
|
||||
service = AgentService(
|
||||
repository=_FakeRepository(),
|
||||
@@ -215,11 +215,11 @@ async def test_enqueue_run_rejects_non_project_host_signed_url(monkeypatch) -> N
|
||||
]
|
||||
)
|
||||
|
||||
with pytest.raises(HTTPException) as exc_info:
|
||||
with pytest.raises(ApiProblemError) as exc_info:
|
||||
await service.enqueue_run(run_input=run_input, current_user=_user())
|
||||
|
||||
assert exc_info.value.status_code == 422
|
||||
assert exc_info.value.detail == "INVALID_BINARY_URL_HOST"
|
||||
assert exc_info.value.detail == "Invalid binary url host"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@@ -227,7 +227,7 @@ async def test_enqueue_run_persists_attachment_and_queue_without_user_token(
|
||||
monkeypatch,
|
||||
) -> None:
|
||||
monkeypatch.setattr(
|
||||
agent_service_module.config.storage, "bucket", "agent-test-bucket"
|
||||
agent_service_module.config.storage.attachment, "bucket", "agent-test-bucket"
|
||||
)
|
||||
repository = _FakeRepository()
|
||||
queue = _FakeQueue()
|
||||
@@ -274,7 +274,7 @@ async def test_enqueue_run_persists_attachment_and_queue_without_user_token(
|
||||
@pytest.mark.asyncio
|
||||
async def test_enqueue_run_rejects_unknown_agent_type(monkeypatch) -> None:
|
||||
monkeypatch.setattr(
|
||||
agent_service_module.config.storage, "bucket", "agent-test-bucket"
|
||||
agent_service_module.config.storage.attachment, "bucket", "agent-test-bucket"
|
||||
)
|
||||
service = AgentService(
|
||||
repository=_FakeRepository(),
|
||||
@@ -294,7 +294,7 @@ async def test_enqueue_run_rejects_unknown_agent_type(monkeypatch) -> None:
|
||||
runtime_mode="planner",
|
||||
)
|
||||
|
||||
with pytest.raises(HTTPException) as exc_info:
|
||||
with pytest.raises(ApiProblemError) as exc_info:
|
||||
await service.enqueue_run(run_input=run_input, current_user=_user())
|
||||
|
||||
assert exc_info.value.status_code == 422
|
||||
@@ -303,7 +303,7 @@ async def test_enqueue_run_rejects_unknown_agent_type(monkeypatch) -> None:
|
||||
@pytest.mark.asyncio
|
||||
async def test_enqueue_run_rejects_invalid_runtime_mode(monkeypatch) -> None:
|
||||
monkeypatch.setattr(
|
||||
agent_service_module.config.storage, "bucket", "agent-test-bucket"
|
||||
agent_service_module.config.storage.attachment, "bucket", "agent-test-bucket"
|
||||
)
|
||||
repository = _FakeRepository()
|
||||
service = AgentService(
|
||||
@@ -314,7 +314,7 @@ async def test_enqueue_run_rejects_invalid_runtime_mode(monkeypatch) -> None:
|
||||
)
|
||||
run_input = _build_run_input(urls=[], runtime_mode="planner")
|
||||
|
||||
with pytest.raises(HTTPException) as exc_info:
|
||||
with pytest.raises(ApiProblemError) as exc_info:
|
||||
await service.enqueue_run(run_input=run_input, current_user=_user())
|
||||
|
||||
assert exc_info.value.status_code == 422
|
||||
@@ -324,7 +324,7 @@ async def test_enqueue_run_rejects_invalid_runtime_mode(monkeypatch) -> None:
|
||||
@pytest.mark.asyncio
|
||||
async def test_create_attachment_signed_url_returns_url(monkeypatch) -> None:
|
||||
monkeypatch.setattr(
|
||||
agent_service_module.config.storage, "bucket", "agent-test-bucket"
|
||||
agent_service_module.config.storage.attachment, "bucket", "agent-test-bucket"
|
||||
)
|
||||
service = AgentService(
|
||||
repository=_FakeRepository(),
|
||||
@@ -349,7 +349,7 @@ async def test_create_attachment_signed_url_rejects_out_of_scope_path(
|
||||
monkeypatch,
|
||||
) -> None:
|
||||
monkeypatch.setattr(
|
||||
agent_service_module.config.storage, "bucket", "agent-test-bucket"
|
||||
agent_service_module.config.storage.attachment, "bucket", "agent-test-bucket"
|
||||
)
|
||||
service = AgentService(
|
||||
repository=_FakeRepository(),
|
||||
@@ -358,7 +358,7 @@ async def test_create_attachment_signed_url_rejects_out_of_scope_path(
|
||||
attachment_storage=_FakeAttachmentStorage(),
|
||||
)
|
||||
|
||||
with pytest.raises(HTTPException) as exc_info:
|
||||
with pytest.raises(ApiProblemError) as exc_info:
|
||||
await service.create_attachment_signed_url(
|
||||
bucket="agent-test-bucket",
|
||||
path="agent-inputs/other-user/thread-x/uploads/a.png",
|
||||
@@ -371,7 +371,7 @@ async def test_create_attachment_signed_url_rejects_out_of_scope_path(
|
||||
@pytest.mark.asyncio
|
||||
async def test_enqueue_run_rejects_too_many_attachments(monkeypatch) -> None:
|
||||
monkeypatch.setattr(
|
||||
agent_service_module.config.storage, "bucket", "agent-test-bucket"
|
||||
agent_service_module.config.storage.attachment, "bucket", "agent-test-bucket"
|
||||
)
|
||||
service = AgentService(
|
||||
repository=_FakeRepository(),
|
||||
@@ -405,7 +405,7 @@ async def test_enqueue_run_rejects_too_many_attachments(monkeypatch) -> None:
|
||||
]
|
||||
)
|
||||
|
||||
with pytest.raises(HTTPException) as exc_info:
|
||||
with pytest.raises(ApiProblemError) as exc_info:
|
||||
await service.enqueue_run(run_input=run_input, current_user=_user())
|
||||
|
||||
assert exc_info.value.status_code == 422
|
||||
@@ -461,8 +461,6 @@ async def test_get_history_snapshot_filters_out_tool_messages() -> None:
|
||||
"agent_output": {
|
||||
"status": "success",
|
||||
"answer": "今天共有 3 条日程。",
|
||||
"key_points": [],
|
||||
"result_type": "summary",
|
||||
"suggested_actions": [],
|
||||
},
|
||||
},
|
||||
@@ -529,7 +527,7 @@ async def test_cancel_run_rejects_non_owner() -> None:
|
||||
phone="+8613812340000",
|
||||
)
|
||||
|
||||
with pytest.raises(HTTPException) as exc_info:
|
||||
with pytest.raises(ApiProblemError) as exc_info:
|
||||
await service.cancel_run(
|
||||
thread_id="00000000-0000-0000-0000-000000000001",
|
||||
run_id="run-cancel-2",
|
||||
|
||||
@@ -30,7 +30,7 @@ def test_convert_message_to_history_does_not_attach_ui_schema_for_tool_message()
|
||||
assert "uiSchema" not in result
|
||||
|
||||
|
||||
def test_convert_message_to_history_uses_ui_schema_key_for_assistant_message() -> None:
|
||||
def test_convert_message_to_history_does_not_attach_ui_schema_for_assistant_message() -> None:
|
||||
message = _FakeMessage(
|
||||
role="assistant",
|
||||
metadata={
|
||||
@@ -40,9 +40,8 @@ def test_convert_message_to_history_uses_ui_schema_key_for_assistant_message() -
|
||||
|
||||
result = convert_message_to_history(message) # type: ignore[arg-type]
|
||||
|
||||
assert "ui_schema" in result
|
||||
assert "ui_schema" not in result
|
||||
assert "uiSchema" not in result
|
||||
assert result["ui_schema"] == {"version": "2.0", "root": {"type": "stack"}}
|
||||
|
||||
|
||||
def test_convert_message_to_history_returns_multiple_user_attachments() -> None:
|
||||
|
||||
@@ -5,6 +5,7 @@ from unittest.mock import MagicMock
|
||||
|
||||
import pytest
|
||||
from core.http.errors import ApiProblemError
|
||||
from core.config.settings import config
|
||||
|
||||
from v1.auth.gateway import SupabaseAuthGateway
|
||||
from v1.auth.schemas import (
|
||||
@@ -28,6 +29,7 @@ class TestSupabaseAuthGateway:
|
||||
"v1.auth.gateway.supabase_service.get_admin_client",
|
||||
lambda: mock_admin_client,
|
||||
)
|
||||
monkeypatch.setattr(config.runtime, "environment", "test")
|
||||
return SupabaseAuthGateway(), mock_client, mock_admin_client
|
||||
|
||||
@pytest.mark.asyncio
|
||||
|
||||
@@ -4,8 +4,8 @@ from uuid import uuid4
|
||||
import pytest
|
||||
|
||||
from models.automation_jobs import AutomationJobStatus, ScheduleType
|
||||
from schemas.agent.skill_config import SkillName
|
||||
from schemas.domain.automation import (
|
||||
AgentTool,
|
||||
AutomationJobConfig,
|
||||
ContextSource,
|
||||
ContextWindowMode,
|
||||
@@ -23,7 +23,7 @@ from v1.automation_jobs.schemas import (
|
||||
def _make_config() -> AutomationJobConfig:
|
||||
return AutomationJobConfig(
|
||||
input_template="Hello",
|
||||
enabled_tools=[AgentTool.MEMORY_WRITE],
|
||||
enabled_skills=[SkillName.MEMORY],
|
||||
context=MessageContextConfig(
|
||||
source=ContextSource.LATEST_CHAT,
|
||||
window_mode=ContextWindowMode.DAY,
|
||||
@@ -119,7 +119,7 @@ async def test_update_merges_config_and_recomputes_next_run() -> None:
|
||||
existing_job.timezone = "UTC"
|
||||
existing_job.config = {
|
||||
"input_template": "Old",
|
||||
"enabled_tools": ["memory.write"],
|
||||
"enabled_skills": ["memory"],
|
||||
"context": {
|
||||
"source": "latest_chat",
|
||||
"window_mode": "day",
|
||||
@@ -136,7 +136,7 @@ async def test_update_merges_config_and_recomputes_next_run() -> None:
|
||||
|
||||
data = AutomationJobUpdateRequest(
|
||||
config=AutomationJobConfig(
|
||||
enabled_tools=[AgentTool.MEMORY_WRITE, AgentTool.MEMORY_FORGET],
|
||||
enabled_skills=[SkillName.MEMORY],
|
||||
schedule=ScheduleConfig(
|
||||
type=ScheduleType.WEEKLY,
|
||||
run_at=ScheduleRunAt(hour=10, minute=30),
|
||||
@@ -150,8 +150,8 @@ async def test_update_merges_config_and_recomputes_next_run() -> None:
|
||||
update_values = repository.update_by_id.call_args[0][1]
|
||||
assert "config" in update_values
|
||||
assert "next_run_at" in update_values
|
||||
enabled_tools = update_values["config"]["enabled_tools"]
|
||||
assert isinstance(enabled_tools[0], str)
|
||||
enabled_skills = update_values["config"]["enabled_skills"]
|
||||
assert isinstance(enabled_skills[0], str)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
|
||||
@@ -5,7 +5,7 @@ from uuid import uuid4
|
||||
import pytest
|
||||
from pydantic import ValidationError
|
||||
|
||||
from core.agentscope.tools.tool_config import AgentTool
|
||||
from schemas.agent.skill_config import SkillName
|
||||
from schemas.domain.automation import AutomationJobConfig
|
||||
from v1.automation_jobs.schemas import (
|
||||
AutomationJobCreateRequest,
|
||||
@@ -22,7 +22,7 @@ def _mock_orm_job() -> MagicMock:
|
||||
mock_orm_job.title = "Test Job"
|
||||
mock_orm_job.config = {
|
||||
"input_template": "Hello",
|
||||
"enabled_tools": ["memory.write", "memory.forget"],
|
||||
"enabled_skills": ["memory"],
|
||||
"context": {
|
||||
"source": "latest_chat",
|
||||
"window_mode": "day",
|
||||
@@ -74,7 +74,7 @@ def test_create_request_valid_timezone() -> None:
|
||||
"timezone": "Asia/Shanghai",
|
||||
"config": {
|
||||
"input_template": "Hello",
|
||||
"enabled_tools": ["memory.write"],
|
||||
"enabled_skills": ["memory"],
|
||||
"context": {
|
||||
"source": "latest_chat",
|
||||
"window_mode": "day",
|
||||
@@ -102,7 +102,7 @@ def test_update_timezone_validation() -> None:
|
||||
|
||||
def test_config_patch_still_allows_partial_payload() -> None:
|
||||
patch = AutomationJobConfig.model_validate(
|
||||
{"enabled_tools": [AgentTool.MEMORY_WRITE]}
|
||||
{"enabled_skills": [SkillName.MEMORY]}
|
||||
)
|
||||
assert patch.input_template is None
|
||||
assert patch.enabled_tools == [AgentTool.MEMORY_WRITE]
|
||||
assert patch.enabled_skills == [SkillName.MEMORY]
|
||||
|
||||
@@ -4,9 +4,18 @@ from uuid import UUID, uuid4
|
||||
|
||||
import pytest
|
||||
from sqlalchemy.exc import SQLAlchemyError
|
||||
from core.http.errors import ApiProblemError
|
||||
|
||||
from core.http.errors import ApiProblemError
|
||||
from models.automation_jobs import AutomationJobStatus, ScheduleType
|
||||
from schemas.agent.skill_config import SkillName
|
||||
from schemas.domain.automation import (
|
||||
AutomationJobConfig,
|
||||
ContextSource,
|
||||
ContextWindowMode,
|
||||
MessageContextConfig,
|
||||
ScheduleConfig,
|
||||
ScheduleRunAt,
|
||||
)
|
||||
from v1.automation_jobs.service import (
|
||||
AutomationJobLimitExceeded,
|
||||
AutomationJobNotFound,
|
||||
@@ -17,21 +26,12 @@ from v1.automation_jobs.schemas import (
|
||||
AutomationJobCreateRequest,
|
||||
AutomationJobUpdateRequest,
|
||||
)
|
||||
from schemas.domain.automation import (
|
||||
AgentTool,
|
||||
AutomationJobConfig,
|
||||
ContextSource,
|
||||
ContextWindowMode,
|
||||
MessageContextConfig,
|
||||
ScheduleConfig,
|
||||
ScheduleRunAt,
|
||||
)
|
||||
|
||||
|
||||
def _make_config() -> AutomationJobConfig:
|
||||
return AutomationJobConfig(
|
||||
input_template="Hello",
|
||||
enabled_tools=[AgentTool.MEMORY_WRITE],
|
||||
enabled_skills=[SkillName.MEMORY],
|
||||
context=MessageContextConfig(
|
||||
source=ContextSource.LATEST_CHAT,
|
||||
window_mode=ContextWindowMode.DAY,
|
||||
@@ -65,7 +65,7 @@ def _make_job(
|
||||
job.status = AutomationJobStatus.ACTIVE
|
||||
job.config = {
|
||||
"input_template": "Hello",
|
||||
"enabled_tools": ["memory.write"],
|
||||
"enabled_skills": ["memory"],
|
||||
"context": {
|
||||
"source": "latest_chat",
|
||||
"window_mode": "day",
|
||||
|
||||
@@ -3,7 +3,7 @@ from unittest.mock import AsyncMock, MagicMock
|
||||
from uuid import UUID, uuid4
|
||||
|
||||
import pytest
|
||||
from fastapi import HTTPException
|
||||
from core.http.errors import ApiProblemError
|
||||
|
||||
from core.auth.models import CurrentUser
|
||||
from models.inbox_messages import InboxMessage, InboxMessageStatus
|
||||
@@ -132,7 +132,7 @@ async def test_accept_subscription_not_found(
|
||||
inbox_repository=FakeInboxRepo(None),
|
||||
)
|
||||
|
||||
with pytest.raises(HTTPException) as exc_info:
|
||||
with pytest.raises(ApiProblemError) as exc_info:
|
||||
await service.accept_subscription(item_id)
|
||||
|
||||
assert exc_info.value.status_code == 404
|
||||
@@ -177,7 +177,7 @@ async def test_reject_subscription_not_found(
|
||||
inbox_repository=FakeInboxRepo(None),
|
||||
)
|
||||
|
||||
with pytest.raises(HTTPException) as exc_info:
|
||||
with pytest.raises(ApiProblemError) as exc_info:
|
||||
await service.reject_subscription(item_id)
|
||||
|
||||
assert exc_info.value.status_code == 404
|
||||
|
||||
Reference in New Issue
Block a user