feat(pheby): accept inbound chat attachments

This commit is contained in:
2026-09-23 09:01:28 +00:00
parent 18c4b34d08
commit e8c60a1001
7 changed files with 606 additions and 10 deletions
+37 -3
View File
@@ -1,7 +1,7 @@
# Pheby Protocol v1 — Specification
JSON messages over WebSocket, plus one authenticated HTTPS endpoint for
attachment downloads. Every message (both directions) carries a `"type"`.
JSON messages over WebSocket, plus authenticated HTTPS endpoints for
attachment uploads and downloads. Every message (both directions) carries a `"type"`.
Client→server requests MAY carry a `"request_id"` (any client-chosen string);
the direct reply echoes it. Server→client events are broadcast to all
authenticated connections (single-user app — typically one client).
@@ -145,6 +145,15 @@ message (the agent sees the whole transcript).
→ { "type": "chat.send", "conversation_id": "a1b2…", "text": "What's the weather?", "request_id": "r6" }
```
`attachment_ids` is an optional list of up to 10 distinct inbound upload IDs
from `POST /attachments` in the **same conversation**. The adapter rejects
unknown, already-sent, or cross-conversation IDs. Upload first, then include
all IDs in the single `chat.send`; a rejected active run leaves them retryable.
The `text` field is still required and nonblank (for file-only sends, provide
a short caption). A successful send anchors the attachments to the user turn
and broadcasts `attachment.added`. Small text files are included in agent
context; other files remain available to the agent as local media paths.
### Server → Client run lifecycle
```json
@@ -316,7 +325,31 @@ metadata so it can be restored after the gateway restarts.
---
## Attachments (agent → client deliverables)
## Attachments (both directions)
### Client → agent uploads
```
POST /attachments?conversation_id=a1b2…&filename=notes.md
Authorization: Bearer <PHEBY_SECRET>
Content-Type: text/markdown
# Raw file bytes, not multipart or JSON
```
Returns `201 {"attachment": {"attachment_id": "<32 hex chars>", ...}}`.
The descriptor contains `direction: "inbound"`, `message_id: null`, and the
same fields as agent deliverables below. An upload is **not** broadcast or
listed in conversation history until `chat.send` successfully claims it;
an unused upload expires with normal retention. Limit: 64 MiB per file.
Missing credentials → 401; bad conversation ID or empty body → 400;
oversize body → 413. The file picker may select up to 10 files per message.
The server keeps only registered copies in adapter-owned storage and never
trusts a client-provided path. Markdown and other small `text/*` files
(up to 100 KiB) are inlined into the agent's turn; the conversation history
returns just the user's original message text.
### Agent → client deliverables
When the agent produces a file (image, document, audio, video… via Hermes's
normal `MEDIA:` deliverable pipeline), the server copies it into
@@ -332,6 +365,7 @@ adapter-managed storage and broadcasts:
"size": 48213,
"kind": "image" | "voice" | "video" | "audio" | "document",
"inline_image": false,
"direction": "outbound",
"conversation_id": "a1b2…",
"message_id": "draft-8c1f…" | null,
"created_at": "2026-09-02T18:30:00+00:00",