Skip to content

Commit c2b4553

Browse files
George-iamgeobon
andauthored
feat: add inbox list, thread, and reply client methods (#4)
Implement owner-scoped inbox retrieval and thread reply helpers in the Python SDK with tests and quickstart updates aligned to Track C parity. Made-with: Cursor Co-authored-by: George-iam <georgeb@gmail.com>
1 parent 4458477 commit c2b4553

3 files changed

Lines changed: 121 additions & 0 deletions

File tree

README.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,15 @@ with AxmeClient(config) as client:
2929
idempotency_key="create-intent-001",
3030
)
3131
print(result)
32+
inbox = client.list_inbox(owner_agent="agent://example/receiver")
33+
print(inbox)
34+
replied = client.reply_inbox_thread(
35+
"11111111-1111-4111-8111-111111111111",
36+
message="Acknowledged",
37+
owner_agent="agent://example/receiver",
38+
idempotency_key="reply-001",
39+
)
40+
print(replied)
3241
```
3342

3443
## Development

axme_sdk/client.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,3 +65,45 @@ def create_intent(
6565
if response.status_code >= 400:
6666
raise AxmeHttpError(response.status_code, response.text)
6767
return response.json()
68+
69+
def list_inbox(self, *, owner_agent: str | None = None) -> dict[str, Any]:
70+
params: dict[str, str] | None = None
71+
if owner_agent is not None:
72+
params = {"owner_agent": owner_agent}
73+
response = self._http.get("/v1/inbox", params=params)
74+
if response.status_code >= 400:
75+
raise AxmeHttpError(response.status_code, response.text)
76+
return response.json()
77+
78+
def get_inbox_thread(self, thread_id: str, *, owner_agent: str | None = None) -> dict[str, Any]:
79+
params: dict[str, str] | None = None
80+
if owner_agent is not None:
81+
params = {"owner_agent": owner_agent}
82+
response = self._http.get(f"/v1/inbox/{thread_id}", params=params)
83+
if response.status_code >= 400:
84+
raise AxmeHttpError(response.status_code, response.text)
85+
return response.json()
86+
87+
def reply_inbox_thread(
88+
self,
89+
thread_id: str,
90+
*,
91+
message: str,
92+
owner_agent: str | None = None,
93+
idempotency_key: str | None = None,
94+
) -> dict[str, Any]:
95+
params: dict[str, str] | None = None
96+
if owner_agent is not None:
97+
params = {"owner_agent": owner_agent}
98+
headers: dict[str, str] | None = None
99+
if idempotency_key is not None:
100+
headers = {"Idempotency-Key": idempotency_key}
101+
response = self._http.post(
102+
f"/v1/inbox/{thread_id}/reply",
103+
params=params,
104+
json={"message": message},
105+
headers=headers,
106+
)
107+
if response.status_code >= 400:
108+
raise AxmeHttpError(response.status_code, response.text)
109+
return response.json()

tests/test_client.py

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,28 @@ def _client(handler, api_key: str = "token") -> AxmeClient:
2626
return AxmeClient(cfg, http_client=http_client)
2727

2828

29+
def _thread_payload() -> dict[str, object]:
30+
return {
31+
"thread_id": "11111111-1111-4111-8111-111111111111",
32+
"intent_id": "22222222-2222-4222-8222-222222222222",
33+
"status": "active",
34+
"owner_agent": "agent://owner",
35+
"from_agent": "agent://from",
36+
"to_agent": "agent://to",
37+
"created_at": "2026-02-28T00:00:00Z",
38+
"updated_at": "2026-02-28T00:00:01Z",
39+
"timeline": [
40+
{
41+
"event_id": "33333333-3333-4333-8333-333333333333",
42+
"event_type": "message.sent",
43+
"actor": "gateway",
44+
"at": "2026-02-28T00:00:01Z",
45+
"details": {"message": "hello"},
46+
}
47+
],
48+
}
49+
50+
2951
def test_health_success() -> None:
3052
def handler(request: httpx.Request) -> httpx.Response:
3153
assert request.method == "GET"
@@ -94,3 +116,51 @@ def test_create_intent_raises_for_mismatched_correlation_id() -> None:
94116
},
95117
correlation_id="11111111-1111-1111-1111-111111111111",
96118
)
119+
120+
121+
def test_list_inbox_success() -> None:
122+
thread = _thread_payload()
123+
124+
def handler(request: httpx.Request) -> httpx.Response:
125+
assert request.method == "GET"
126+
assert request.url.path == "/v1/inbox"
127+
assert request.url.params.get("owner_agent") == "agent://owner"
128+
return httpx.Response(200, json={"ok": True, "threads": [thread]})
129+
130+
client = _client(handler)
131+
assert client.list_inbox(owner_agent="agent://owner") == {"ok": True, "threads": [thread]}
132+
133+
134+
def test_get_inbox_thread_success() -> None:
135+
thread = _thread_payload()
136+
thread_id = str(thread["thread_id"])
137+
138+
def handler(request: httpx.Request) -> httpx.Response:
139+
assert request.method == "GET"
140+
assert request.url.path == f"/v1/inbox/{thread_id}"
141+
assert request.url.params.get("owner_agent") == "agent://owner"
142+
return httpx.Response(200, json={"ok": True, "thread": thread})
143+
144+
client = _client(handler)
145+
assert client.get_inbox_thread(thread_id, owner_agent="agent://owner") == {"ok": True, "thread": thread}
146+
147+
148+
def test_reply_inbox_thread_success() -> None:
149+
thread = _thread_payload()
150+
thread_id = str(thread["thread_id"])
151+
152+
def handler(request: httpx.Request) -> httpx.Response:
153+
assert request.method == "POST"
154+
assert request.url.path == f"/v1/inbox/{thread_id}/reply"
155+
assert request.url.params.get("owner_agent") == "agent://owner"
156+
assert request.headers["idempotency-key"] == "reply-1"
157+
assert request.read() == b'{"message":"ack"}'
158+
return httpx.Response(200, json={"ok": True, "thread": thread})
159+
160+
client = _client(handler)
161+
assert client.reply_inbox_thread(
162+
thread_id,
163+
message="ack",
164+
owner_agent="agent://owner",
165+
idempotency_key="reply-1",
166+
) == {"ok": True, "thread": thread}

0 commit comments

Comments
 (0)