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:
2026-09-09 06:54:56 +00:00
parent 5725d635b2
commit 6be1601ccd
3 changed files with 17 additions and 2 deletions
+1 -1
View File
@@ -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…" },
+8 -1
View File
@@ -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,
) )
+8
View File
@@ -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"