oneshot-python 0.10.0__tar.gz → 0.10.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: oneshot-python
3
- Version: 0.10.0
3
+ Version: 0.10.1
4
4
  Summary: Core Python SDK for the OneShot API — HTTP client with x402 payment handling
5
5
  License-Expression: MIT
6
6
  Requires-Python: >=3.10
@@ -44,6 +44,35 @@ _POLL_INTERVAL = 2.0 # seconds
44
44
  _MAX_POLL_RETRIES = 3
45
45
 
46
46
 
47
+ def _build_email_payload(
48
+ to: str,
49
+ subject: str,
50
+ body: str,
51
+ from_domain: Optional[str],
52
+ from_mailbox: Optional[str],
53
+ from_name: Optional[str],
54
+ extra: dict[str, Any],
55
+ ) -> dict[str, Any]:
56
+ """Build the /v1/tools/email/send body in the API's field convention.
57
+
58
+ The API expects ``from_address`` + ``to_address`` (see
59
+ EmailSendRequestSchema). Construct from_address from the mailbox/domain
60
+ and forward the optional display name.
61
+ """
62
+ mailbox = from_mailbox or "agent"
63
+ domain = from_domain or "oneshotagent.com"
64
+ payload: dict[str, Any] = {
65
+ "from_address": f"{mailbox}@{domain}",
66
+ "to_address": to,
67
+ "subject": subject,
68
+ "body": body,
69
+ **extra,
70
+ }
71
+ if from_name:
72
+ payload["from_name"] = from_name
73
+ return payload
74
+
75
+
47
76
  class OneShotClient:
48
77
  """Synchronous + async HTTP client for the OneShot API.
49
78
 
@@ -485,19 +514,39 @@ class OneShotClient:
485
514
  """Read a web page and extract content as markdown. Async."""
486
515
  return await self.acall_tool("/v1/tools/web-read", {"url": url, **kwargs})
487
516
 
488
- def email(self, to: str, subject: str, body: str, *, from_domain: Optional[str] = None, **kwargs: Any) -> Any:
517
+ def email(
518
+ self,
519
+ to: str,
520
+ subject: str,
521
+ body: str,
522
+ *,
523
+ from_domain: Optional[str] = None,
524
+ from_mailbox: Optional[str] = None,
525
+ from_name: Optional[str] = None,
526
+ **kwargs: Any,
527
+ ) -> Any:
489
528
  """Send an email. Blocking."""
490
- payload: dict[str, Any] = {"to": to, "subject": subject, "body": body, **kwargs}
491
- if from_domain:
492
- payload["from_domain"] = from_domain
493
- return self.call_tool("/v1/tools/email/send", payload)
529
+ return self.call_tool(
530
+ "/v1/tools/email/send",
531
+ _build_email_payload(to, subject, body, from_domain, from_mailbox, from_name, kwargs),
532
+ )
494
533
 
495
- async def aemail(self, to: str, subject: str, body: str, *, from_domain: Optional[str] = None, **kwargs: Any) -> Any:
534
+ async def aemail(
535
+ self,
536
+ to: str,
537
+ subject: str,
538
+ body: str,
539
+ *,
540
+ from_domain: Optional[str] = None,
541
+ from_mailbox: Optional[str] = None,
542
+ from_name: Optional[str] = None,
543
+ **kwargs: Any,
544
+ ) -> Any:
496
545
  """Send an email. Async."""
497
- payload: dict[str, Any] = {"to": to, "subject": subject, "body": body, **kwargs}
498
- if from_domain:
499
- payload["from_domain"] = from_domain
500
- return await self.acall_tool("/v1/tools/email/send", payload)
546
+ return await self.acall_tool(
547
+ "/v1/tools/email/send",
548
+ _build_email_payload(to, subject, body, from_domain, from_mailbox, from_name, kwargs),
549
+ )
501
550
 
502
551
  def voice(self, objective: str, target_number: str, **kwargs: Any) -> Any:
503
552
  """Make an AI voice call. Blocking."""
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "oneshot-python"
3
- version = "0.10.0"
3
+ version = "0.10.1"
4
4
  description = "Core Python SDK for the OneShot API — HTTP client with x402 payment handling"
5
5
  readme = {text = "Core Python SDK for the OneShot API", content-type = "text/plain"}
6
6
  license = "MIT"
@@ -0,0 +1,74 @@
1
+ """Tests for the email payload construction in OneShotClient.
2
+
3
+ Verifies that email()/aemail() send the API's field convention
4
+ (from_address + to_address), build from_address from from_mailbox/from_domain,
5
+ and forward the optional from_name display name.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from unittest.mock import AsyncMock, MagicMock
11
+
12
+ import pytest
13
+
14
+ from oneshot.client import OneShotClient, _build_email_payload
15
+
16
+ TEST_PRIVATE_KEY = "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80"
17
+
18
+
19
+ # ── _build_email_payload (pure) ───────────────────────────────────────────
20
+
21
+ def test_payload_defaults_to_agent_mailbox():
22
+ p = _build_email_payload("r@x.com", "S", "B", None, None, None, {})
23
+ assert p == {
24
+ "from_address": "agent@oneshotagent.com",
25
+ "to_address": "r@x.com",
26
+ "subject": "S",
27
+ "body": "B",
28
+ }
29
+
30
+
31
+ def test_payload_uses_mailbox_domain_and_name():
32
+ p = _build_email_payload("r@x.com", "S", "B", "acme.com", "jane", "Jane Doe", {})
33
+ assert p["from_address"] == "jane@acme.com"
34
+ assert p["from_name"] == "Jane Doe"
35
+
36
+
37
+ def test_payload_omits_empty_from_name():
38
+ p = _build_email_payload("r@x.com", "S", "B", None, None, "", {})
39
+ assert "from_name" not in p
40
+
41
+
42
+ def test_payload_passes_extra_kwargs():
43
+ p = _build_email_payload("r@x.com", "S", "B", None, None, None, {"wait": True})
44
+ assert p["wait"] is True
45
+
46
+
47
+ # ── email() delegation ────────────────────────────────────────────────────
48
+
49
+ def _client() -> OneShotClient:
50
+ c = OneShotClient(TEST_PRIVATE_KEY)
51
+ c.call_tool = MagicMock(return_value={"request_id": "r1"}) # type: ignore[method-assign]
52
+ return c
53
+
54
+
55
+ def test_email_sends_correct_contract():
56
+ c = _client()
57
+ c.email("r@x.com", "Hi", "Body", from_mailbox="jane", from_domain="acme.com", from_name="Jane Doe")
58
+ endpoint, payload = c.call_tool.call_args[0]
59
+ assert endpoint == "/v1/tools/email/send"
60
+ assert payload["from_address"] == "jane@acme.com"
61
+ assert payload["to_address"] == "r@x.com"
62
+ assert payload["from_name"] == "Jane Doe"
63
+
64
+
65
+ @pytest.mark.asyncio
66
+ async def test_aemail_sends_correct_contract():
67
+ c = OneShotClient(TEST_PRIVATE_KEY)
68
+ c.acall_tool = AsyncMock(return_value={"request_id": "r2"}) # type: ignore[method-assign]
69
+ await c.aemail("r@x.com", "Hi", "Body")
70
+ endpoint, payload = c.acall_tool.call_args[0]
71
+ assert endpoint == "/v1/tools/email/send"
72
+ assert payload["from_address"] == "agent@oneshotagent.com"
73
+ assert payload["to_address"] == "r@x.com"
74
+ assert "from_name" not in payload
File without changes