Anchor generated attachments to assistant messages
Deliverables now inherit the active assistant draft message ID so clients can render them beside the reply that produced them.
This commit is contained in:
+1
-1
@@ -309,7 +309,7 @@ adapter-managed storage and broadcasts:
|
|||||||
"kind": "image" | "voice" | "video" | "audio" | "document",
|
"kind": "image" | "voice" | "video" | "audio" | "document",
|
||||||
"inline_image": false,
|
"inline_image": false,
|
||||||
"conversation_id": "a1b2…",
|
"conversation_id": "a1b2…",
|
||||||
"message_id": null,
|
"message_id": "draft-8c1f…" | null,
|
||||||
"created_at": "2026-09-02T18:30:00+00:00",
|
"created_at": "2026-09-02T18:30:00+00:00",
|
||||||
"expires_at": "2026-09-09T18:30:00+00:00", // null when retention=0
|
"expires_at": "2026-09-09T18:30:00+00:00", // null when retention=0
|
||||||
"download_path": "/attachments/e5f6…" },
|
"download_path": "/attachments/e5f6…" },
|
||||||
|
|||||||
@@ -379,11 +379,18 @@ class PhebyAdapter(BasePlatformAdapter):
|
|||||||
return SendResult(success=True, message_id=clarify_id)
|
return SendResult(success=True, message_id=clarify_id)
|
||||||
|
|
||||||
# ── deliverables (attachments) ───────────────────────────────────────
|
# ── deliverables (attachments) ───────────────────────────────────────
|
||||||
|
def _active_assistant_message_id(self, conversation_id: str) -> Optional[str]:
|
||||||
|
"""Return the live assistant draft that a deliverable belongs beneath."""
|
||||||
|
draft = self._drafts.get(conversation_id)
|
||||||
|
message_id = draft.get("message_id") if isinstance(draft, dict) else None
|
||||||
|
return str(message_id) if message_id else None
|
||||||
|
|
||||||
async def _register_and_broadcast(
|
async def _register_and_broadcast(
|
||||||
self,
|
self,
|
||||||
file_path: str,
|
file_path: str,
|
||||||
conversation_id: str,
|
conversation_id: str,
|
||||||
*,
|
*,
|
||||||
|
message_id: Optional[str] = None,
|
||||||
kind_hint: Optional[str] = None,
|
kind_hint: Optional[str] = None,
|
||||||
filename: Optional[str] = None,
|
filename: Optional[str] = None,
|
||||||
) -> Optional[Dict[str, Any]]:
|
) -> Optional[Dict[str, Any]]:
|
||||||
@@ -392,7 +399,7 @@ class PhebyAdapter(BasePlatformAdapter):
|
|||||||
desc = await self._server.store.register_file(
|
desc = await self._server.store.register_file(
|
||||||
file_path,
|
file_path,
|
||||||
conversation_id=conversation_id,
|
conversation_id=conversation_id,
|
||||||
message_id=None,
|
message_id=message_id or self._active_assistant_message_id(conversation_id),
|
||||||
filename=filename,
|
filename=filename,
|
||||||
kind_hint=kind_hint,
|
kind_hint=kind_hint,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ if str(_PLUGIN_DIR) not in sys.path:
|
|||||||
|
|
||||||
from pheby import protocol as proto # noqa: E402
|
from pheby import protocol as proto # noqa: E402
|
||||||
from pheby.attachments import AttachmentStore, constant_time_equals # noqa: E402
|
from pheby.attachments import AttachmentStore, constant_time_equals # noqa: E402
|
||||||
|
from pheby.adapter import PhebyAdapter # noqa: E402
|
||||||
from pheby.config import load_config # noqa: E402
|
from pheby.config import load_config # noqa: E402
|
||||||
from pheby.conversations import ConversationRouter # noqa: E402
|
from pheby.conversations import ConversationRouter # noqa: E402
|
||||||
from pheby.server import PhebyServer # noqa: E402
|
from pheby.server import PhebyServer # noqa: E402
|
||||||
@@ -344,6 +345,13 @@ class TestConversations:
|
|||||||
# Attachments
|
# Attachments
|
||||||
# ═══════════════════════════════════════════════════════════════════════════
|
# ═══════════════════════════════════════════════════════════════════════════
|
||||||
class TestAttachments:
|
class TestAttachments:
|
||||||
|
def test_adapter_anchors_deliverable_to_active_assistant_draft(self):
|
||||||
|
adapter = object.__new__(PhebyAdapter)
|
||||||
|
adapter._drafts = {"conv": {"message_id": "draft-run-1", "text": ""}}
|
||||||
|
|
||||||
|
assert adapter._active_assistant_message_id("conv") == "draft-run-1"
|
||||||
|
assert adapter._active_assistant_message_id("unknown") is None
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_register_describe_download_path(self, tmp_path):
|
async def test_register_describe_download_path(self, tmp_path):
|
||||||
src = Path(tmp_path) / "report.pdf"
|
src = Path(tmp_path) / "report.pdf"
|
||||||
|
|||||||
Reference in New Issue
Block a user