hyperagents 1.2.3rc5.post3__py3-none-any.whl

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.
Files changed (361) hide show
  1. agents/__init__.py +31 -0
  2. agents/background_monitor.py +453 -0
  3. agents/learning.py +460 -0
  4. agents/planner.py +421 -0
  5. agents/transcriber.py +341 -0
  6. civilization/__init__.py +51 -0
  7. civilization/api/__init__.py +7 -0
  8. civilization/api/civilization_api.py +361 -0
  9. civilization/graph/__init__.py +19 -0
  10. civilization/graph/knowledge_linker.py +348 -0
  11. civilization/graph/org_graph.py +348 -0
  12. civilization/graph/process_graph.py +386 -0
  13. civilization/ingestion/__init__.py +20 -0
  14. civilization/ingestion/chunker.py +273 -0
  15. civilization/ingestion/document_ingestor.py +358 -0
  16. civilization/ingestion/embedder.py +262 -0
  17. civilization/ingestion/extractor.py +224 -0
  18. civilization/interview/__init__.py +19 -0
  19. civilization/interview/gap_detector.py +380 -0
  20. civilization/interview/interviewer.py +323 -0
  21. civilization/interview/questioner.py +363 -0
  22. civilization/retrieval/__init__.py +17 -0
  23. civilization/retrieval/civilization_rag.py +316 -0
  24. civilization/retrieval/context_injector.py +342 -0
  25. civilization/retrieval/relevance_ranker.py +287 -0
  26. civilization/schema.py +299 -0
  27. civilization/store.py +177 -0
  28. civilization/sync/__init__.py +16 -0
  29. civilization/sync/confluence_sync.py +317 -0
  30. civilization/sync/gdrive_sync.py +262 -0
  31. civilization/sync/notion_sync.py +237 -0
  32. civilization/versioning/__init__.py +19 -0
  33. civilization/versioning/diff_engine.py +351 -0
  34. civilization/versioning/staleness_detector.py +303 -0
  35. civilization/versioning/version_manager.py +293 -0
  36. cli/__init__.py +1 -0
  37. cli/doctor.py +334 -0
  38. cli/hyperclaw.py +1357 -0
  39. cli/onboarding.py +469 -0
  40. core/__init__.py +1 -0
  41. core/connectors/__init__.py +119 -0
  42. core/connectors/infrastructure.py +772 -0
  43. core/connectors/messaging.py +586 -0
  44. core/connectors/productivity.py +764 -0
  45. core/connectors/smarthome.py +528 -0
  46. core/connectors/social.py +730 -0
  47. core/hyperrouter/__init__.py +23 -0
  48. core/hyperrouter/bandit.py +218 -0
  49. core/hyperrouter/fast_loop.py +68 -0
  50. core/hyperrouter/slow_loop.py +143 -0
  51. core/hyperstate/__init__.py +24 -0
  52. core/hyperstate/certifier.py +154 -0
  53. core/hyperstate/schema.py +160 -0
  54. core/hyperstate/state_manager.py +103 -0
  55. core/hyperstate/store.py +163 -0
  56. core/providers/__init__.py +121 -0
  57. core/providers/embeddings.py +389 -0
  58. core/providers/image.py +534 -0
  59. core/providers/llm.py +1935 -0
  60. core/providers/search.py +608 -0
  61. core/providers/voice.py +474 -0
  62. hyperagents-1.2.3rc5.post3.dist-info/METADATA +469 -0
  63. hyperagents-1.2.3rc5.post3.dist-info/RECORD +361 -0
  64. hyperagents-1.2.3rc5.post3.dist-info/WHEEL +5 -0
  65. hyperagents-1.2.3rc5.post3.dist-info/entry_points.txt +6 -0
  66. hyperagents-1.2.3rc5.post3.dist-info/licenses/LICENSE +21 -0
  67. hyperagents-1.2.3rc5.post3.dist-info/top_level.txt +13 -0
  68. hyperclaw/__init__.py +67 -0
  69. hyperclaw/__main__.py +220 -0
  70. hyperclaw/agent.py +775 -0
  71. hyperclaw/agent_coordinator.py +577 -0
  72. hyperclaw/anthropic_params.py +225 -0
  73. hyperclaw/api_utils.py +85 -0
  74. hyperclaw/approval_telegram.py +209 -0
  75. hyperclaw/approval_terminal.py +80 -0
  76. hyperclaw/attachments.py +177 -0
  77. hyperclaw/branding.py +55 -0
  78. hyperclaw/brief/__init__.py +11 -0
  79. hyperclaw/brief/cli.py +204 -0
  80. hyperclaw/brief/collect.py +146 -0
  81. hyperclaw/brief/compose.py +333 -0
  82. hyperclaw/brief/diff.py +110 -0
  83. hyperclaw/brief/errors.py +99 -0
  84. hyperclaw/brief/google.py +269 -0
  85. hyperclaw/brief/google_auth.py +294 -0
  86. hyperclaw/brief/links.py +124 -0
  87. hyperclaw/brief/service.py +424 -0
  88. hyperclaw/brief/store.py +205 -0
  89. hyperclaw/brief/tasks.py +200 -0
  90. hyperclaw/chat_scope.py +115 -0
  91. hyperclaw/cli_tui.py +497 -0
  92. hyperclaw/client_guard.py +717 -0
  93. hyperclaw/connection_check.py +113 -0
  94. hyperclaw/cost_guard.py +1728 -0
  95. hyperclaw/default_config/agents.yaml +534 -0
  96. hyperclaw/default_config/hyperclaw.yaml +27 -0
  97. hyperclaw/default_config/models.yaml +161 -0
  98. hyperclaw/deployment.py +39 -0
  99. hyperclaw/digest/__init__.py +9 -0
  100. hyperclaw/digest/cli.py +117 -0
  101. hyperclaw/digest/errors.py +100 -0
  102. hyperclaw/digest/gitlab.py +187 -0
  103. hyperclaw/digest/http.py +113 -0
  104. hyperclaw/digest/redact.py +32 -0
  105. hyperclaw/digest/service.py +271 -0
  106. hyperclaw/digest/slack.py +223 -0
  107. hyperclaw/digest/state.py +124 -0
  108. hyperclaw/digest/summarize.py +198 -0
  109. hyperclaw/digest/tokens.py +143 -0
  110. hyperclaw/distribution.py +46 -0
  111. hyperclaw/documents.py +351 -0
  112. hyperclaw/feeds.py +339 -0
  113. hyperclaw/google_workspace/__init__.py +5 -0
  114. hyperclaw/google_workspace/api.py +151 -0
  115. hyperclaw/google_workspace/auth.py +397 -0
  116. hyperclaw/google_workspace/calendar.py +157 -0
  117. hyperclaw/google_workspace/cli.py +72 -0
  118. hyperclaw/google_workspace/drive.py +236 -0
  119. hyperclaw/google_workspace/errors.py +58 -0
  120. hyperclaw/google_workspace/gmail.py +295 -0
  121. hyperclaw/google_workspace/meet.py +122 -0
  122. hyperclaw/google_workspace/tools.py +319 -0
  123. hyperclaw/hyperspeed.py +57 -0
  124. hyperclaw/integrations_layer.py +1427 -0
  125. hyperclaw/live_state.py +188 -0
  126. hyperclaw/managed_config.py +245 -0
  127. hyperclaw/media_hub.py +179 -0
  128. hyperclaw/memory_manager.py +584 -0
  129. hyperclaw/model_catalog.py +551 -0
  130. hyperclaw/model_policy.py +211 -0
  131. hyperclaw/model_router.py +595 -0
  132. hyperclaw/model_selector.py +72 -0
  133. hyperclaw/naming.py +284 -0
  134. hyperclaw/notices.py +65 -0
  135. hyperclaw/oai_stream.py +106 -0
  136. hyperclaw/orchestrator.py +591 -0
  137. hyperclaw/outbox.py +82 -0
  138. hyperclaw/persona.py +367 -0
  139. hyperclaw/prometheus.py +219 -0
  140. hyperclaw/prompt_assembly.py +157 -0
  141. hyperclaw/provider_setup.py +146 -0
  142. hyperclaw/providers.py +340 -0
  143. hyperclaw/qualification.py +341 -0
  144. hyperclaw/qualification_keys/hyper-d292fd127cef5a3e.pub +1 -0
  145. hyperclaw/qualification_records/anthropic--claude-sonnet-5-5.json +14 -0
  146. hyperclaw/reasoning_display.py +35 -0
  147. hyperclaw/scheduler.py +454 -0
  148. hyperclaw/server.py +847 -0
  149. hyperclaw/setup.py +627 -0
  150. hyperclaw/skills.py +717 -0
  151. hyperclaw/solomon.py +442 -0
  152. hyperclaw/swarm.py +533 -0
  153. hyperclaw/taint.py +832 -0
  154. hyperclaw/team/__init__.py +28 -0
  155. hyperclaw/team/dispatcher.py +431 -0
  156. hyperclaw/team/genesis.py +106 -0
  157. hyperclaw/team/lead.py +110 -0
  158. hyperclaw/team/model.py +135 -0
  159. hyperclaw/team/morning.py +238 -0
  160. hyperclaw/team/repos.py +66 -0
  161. hyperclaw/team/roster.py +264 -0
  162. hyperclaw/team/specialist.py +235 -0
  163. hyperclaw/team/status.py +163 -0
  164. hyperclaw/team/watchers.py +112 -0
  165. hyperclaw/teamsetup/__init__.py +17 -0
  166. hyperclaw/teamsetup/checks.py +208 -0
  167. hyperclaw/teamsetup/item.py +391 -0
  168. hyperclaw/teamsetup/oauth_loopback.py +84 -0
  169. hyperclaw/teamsetup/onboard.py +550 -0
  170. hyperclaw/teamsetup/profile.py +82 -0
  171. hyperclaw/teamsetup/secrets.py +313 -0
  172. hyperclaw/teamsetup/selfcheck.py +295 -0
  173. hyperclaw/teamsetup/service.py +495 -0
  174. hyperclaw/teamsetup/slack_oauth.py +442 -0
  175. hyperclaw/teamsetup/tty.py +62 -0
  176. hyperclaw/telegram_bot.py +1106 -0
  177. hyperclaw/telegram_intake.py +126 -0
  178. hyperclaw/telegram_pairing.py +360 -0
  179. hyperclaw/terminal_provider.py +323 -0
  180. hyperclaw/tools/__init__.py +1 -0
  181. hyperclaw/tools/approval.py +949 -0
  182. hyperclaw/tools/args.py +212 -0
  183. hyperclaw/tools/catalog.py +244 -0
  184. hyperclaw/tools/classify.py +266 -0
  185. hyperclaw/tools/effects.py +724 -0
  186. hyperclaw/tools/egress.py +210 -0
  187. hyperclaw/tools/envelope.py +188 -0
  188. hyperclaw/tools/guard.py +367 -0
  189. hyperclaw/tools/ledger.py +339 -0
  190. hyperclaw/tools/receipts.py +70 -0
  191. hyperclaw/tools/registry.py +318 -0
  192. hyperclaw/tools/service_guard.py +151 -0
  193. hyperclaw/tools/surfaces.py +54 -0
  194. hyperclaw/tools/workspace.py +87 -0
  195. hyperclaw/trading/__init__.py +77 -0
  196. hyperclaw/truth_guard.py +791 -0
  197. hyperclaw/tts.py +124 -0
  198. hyperclaw/tui.py +4255 -0
  199. hyperclaw/tui_bridge.py +938 -0
  200. hyperclaw/unattended.py +60 -0
  201. hyperclaw/updates/__init__.py +54 -0
  202. hyperclaw/updates/check.py +131 -0
  203. hyperclaw/updates/index.py +178 -0
  204. hyperclaw/updates/install.py +110 -0
  205. hyperclaw/updates/managed.py +161 -0
  206. hyperclaw/updates/manifest.py +138 -0
  207. hyperclaw/updates/notices.py +86 -0
  208. hyperclaw/updates/paths.py +53 -0
  209. hyperclaw/updates/release.py +146 -0
  210. hyperclaw/updates/upgrade.py +253 -0
  211. hyperclaw/updates/versioning.py +104 -0
  212. hyperclaw/userconfig.py +119 -0
  213. hyperclaw/xai.py +83 -0
  214. integrations/__init__.py +31 -0
  215. integrations/automation/__init__.py +1 -0
  216. integrations/automation/make.py +87 -0
  217. integrations/automation/zapier.py +87 -0
  218. integrations/base.py +476 -0
  219. integrations/communication/__init__.py +1 -0
  220. integrations/communication/twilio.py +279 -0
  221. integrations/communication/zoom.py +290 -0
  222. integrations/data/__init__.py +1 -0
  223. integrations/data/google_analytics.py +135 -0
  224. integrations/data/postgres.py +182 -0
  225. integrations/data/supabase_connector.py +202 -0
  226. integrations/data/weather_feeds.py +450 -0
  227. integrations/developer/__init__.py +9 -0
  228. integrations/developer/github.py +406 -0
  229. integrations/developer/gitlab.py +298 -0
  230. integrations/enterprise/__init__.py +13 -0
  231. integrations/enterprise/confluence.py +266 -0
  232. integrations/enterprise/hubspot.py +293 -0
  233. integrations/enterprise/jira.py +314 -0
  234. integrations/enterprise/salesforce.py +286 -0
  235. integrations/finance/__init__.py +1 -0
  236. integrations/finance/polymarket_client.py +247 -0
  237. integrations/finance/quickbooks.py +281 -0
  238. integrations/finance/stripe.py +261 -0
  239. integrations/gateway.py +238 -0
  240. integrations/messaging/__init__.py +32 -0
  241. integrations/messaging/discord.py +159 -0
  242. integrations/messaging/email.py +401 -0
  243. integrations/messaging/imessage.py +443 -0
  244. integrations/messaging/signal.py +175 -0
  245. integrations/messaging/slack.py +217 -0
  246. integrations/messaging/sms.py +175 -0
  247. integrations/messaging/teams.py +208 -0
  248. integrations/messaging/telegram.py +260 -0
  249. integrations/messaging/whatsapp.py +203 -0
  250. integrations/productivity/__init__.py +17 -0
  251. integrations/productivity/airtable.py +217 -0
  252. integrations/productivity/asana.py +248 -0
  253. integrations/productivity/google/__init__.py +19 -0
  254. integrations/productivity/google/base.py +89 -0
  255. integrations/productivity/google/calendar.py +272 -0
  256. integrations/productivity/google/docs.py +202 -0
  257. integrations/productivity/google/drive.py +268 -0
  258. integrations/productivity/google/gmail.py +279 -0
  259. integrations/productivity/google/meet.py +218 -0
  260. integrations/productivity/google/sheets.py +222 -0
  261. integrations/productivity/google/tasks.py +199 -0
  262. integrations/productivity/linear.py +279 -0
  263. integrations/productivity/microsoft/__init__.py +13 -0
  264. integrations/productivity/microsoft/base.py +93 -0
  265. integrations/productivity/microsoft/calendar.py +207 -0
  266. integrations/productivity/microsoft/onedrive.py +222 -0
  267. integrations/productivity/microsoft/outlook.py +241 -0
  268. integrations/productivity/microsoft/sharepoint.py +211 -0
  269. integrations/productivity/notion.py +243 -0
  270. integrations/productivity/todoist.py +250 -0
  271. integrations/productivity/trello.py +244 -0
  272. integrations/storage/__init__.py +1 -0
  273. integrations/storage/box.py +279 -0
  274. integrations/storage/dropbox.py +215 -0
  275. integrations/telegram_inbound.py +179 -0
  276. integrations/telegram_poller.py +151 -0
  277. memory/__init__.py +1 -0
  278. memory/causal_graph.py +125 -0
  279. memory/impact_tracker.py +88 -0
  280. memory/vector_memory.py +436 -0
  281. models/__init__.py +12 -0
  282. models/chatjimmy_client.py +210 -0
  283. models/claude_client.py +235 -0
  284. models/claude_code_subagent.py +192 -0
  285. models/openai_client.py +120 -0
  286. models/router.py +162 -0
  287. recursive/discovery_loop.py +118 -0
  288. recursive/optimization_loop.py +135 -0
  289. recursive/validation_loop.py +38 -0
  290. security/__init__.py +1 -0
  291. security/audit_logger.py +106 -0
  292. security/filesystem_guard.py +100 -0
  293. security/hypershield.py +99 -0
  294. security/network_guard.py +97 -0
  295. security/policy_engine.py +198 -0
  296. swarm/__init__.py +1 -0
  297. swarm/agents/__init__.py +0 -0
  298. swarm/agents/base.py +230 -0
  299. swarm/agents/business/__init__.py +0 -0
  300. swarm/agents/business/counsel.py +31 -0
  301. swarm/agents/business/herald.py +32 -0
  302. swarm/agents/business/ledger.py +26 -0
  303. swarm/agents/business/nexus.py +30 -0
  304. swarm/agents/business/ops.py +30 -0
  305. swarm/agents/business/pipeline.py +28 -0
  306. swarm/agents/business/revenue.py +30 -0
  307. swarm/agents/business/sovereign.py +31 -0
  308. swarm/agents/business/strategos.py +27 -0
  309. swarm/agents/business/talent.py +27 -0
  310. swarm/agents/business/venture.py +32 -0
  311. swarm/agents/comms/__init__.py +1 -0
  312. swarm/agents/comms/cipher.py +32 -0
  313. swarm/agents/comms/echo_comms.py +31 -0
  314. swarm/agents/comms/envoy.py +31 -0
  315. swarm/agents/comms/herald_scheduler.py +33 -0
  316. swarm/agents/comms/pulse.py +29 -0
  317. swarm/agents/creative/__init__.py +0 -0
  318. swarm/agents/creative/author.py +27 -0
  319. swarm/agents/creative/lens.py +25 -0
  320. swarm/agents/executive/__init__.py +1 -0
  321. swarm/agents/intelligence/__init__.py +1 -0
  322. swarm/agents/intelligence/arbiter.py +30 -0
  323. swarm/agents/intelligence/sentinel.py +30 -0
  324. swarm/agents/media/__init__.py +0 -0
  325. swarm/agents/personal/__init__.py +0 -0
  326. swarm/agents/personal/atlas.py +27 -0
  327. swarm/agents/personal/hearth.py +26 -0
  328. swarm/agents/personal/midas.py +27 -0
  329. swarm/agents/personal/navigator.py +27 -0
  330. swarm/agents/personal/nourish.py +31 -0
  331. swarm/agents/personal/vitals.py +33 -0
  332. swarm/agents/recursive/__init__.py +0 -0
  333. swarm/agents/recursive/alchemist.py +73 -0
  334. swarm/agents/recursive/calibrator.py +45 -0
  335. swarm/agents/recursive/scout.py +97 -0
  336. swarm/agents/scientific/__init__.py +0 -0
  337. swarm/agents/scientific/cosmos.py +36 -0
  338. swarm/agents/scientific/gaia.py +26 -0
  339. swarm/agents/scientific/medicus.py +33 -0
  340. swarm/agents/scientific/oracle_agent.py +39 -0
  341. swarm/agents/scientific/scribe.py +39 -0
  342. swarm/agents/talent/__init__.py +0 -0
  343. swarm/agents/talent/deal.py +28 -0
  344. swarm/agents/talent/roster.py +29 -0
  345. swarm/agents/talent/scout.py +29 -0
  346. swarm/agents/talent/stage.py +27 -0
  347. swarm/agents/tech/__init__.py +0 -0
  348. swarm/agents/tech/aegis.py +28 -0
  349. swarm/agents/tech/bridge.py +28 -0
  350. swarm/agents/tech/forge.py +28 -0
  351. swarm/agents/trading/__init__.py +1 -0
  352. swarm/agents/trading/global_prediction_engine.py +611 -0
  353. swarm/agents/trading/polymarket_trader.py +165 -0
  354. swarm/agents/trading/prediction_strategist.py +218 -0
  355. swarm/autogen_bridge.py +68 -0
  356. swarm/bid_protocol.py +196 -0
  357. swarm/dispatcher.py +527 -0
  358. swarm/nexus.py +181 -0
  359. swarm/registry.py +112 -0
  360. telegram_listener.py +403 -0
  361. ui/tui/app.py +214 -0
@@ -0,0 +1,401 @@
1
+ """EmailConnector — Email integration via SMTP or API (Gmail/Outlook)."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import logging
6
+ import smtplib
7
+ from email.mime.multipart import MIMEMultipart
8
+ from email.mime.text import MIMEText
9
+ from email.mime.base import MIMEBase
10
+ from email import encoders
11
+ from typing import AsyncIterator
12
+ import asyncio
13
+
14
+ import httpx
15
+
16
+ from integrations.base import (
17
+ BaseConnector,
18
+ ConnectorCapability,
19
+ ConnectorInfo,
20
+ InboundMessage,
21
+ OutboundMessage,
22
+ )
23
+
24
+ logger = logging.getLogger("hyperclaw.integrations.email")
25
+
26
+
27
+ class EmailConnector(BaseConnector):
28
+ """
29
+ Email connector supporting SMTP, Gmail API, and Outlook API.
30
+
31
+ Required config:
32
+ - provider: "smtp", "gmail", or "outlook"
33
+
34
+ For SMTP:
35
+ - smtp_host, smtp_port, username, password
36
+
37
+ For Gmail/Outlook:
38
+ - access_token (OAuth2)
39
+ """
40
+
41
+ def __init__(self, config: dict) -> None:
42
+ self.config = config
43
+ self.provider = config.get("provider", "smtp")
44
+ self.smtp_host = config.get("smtp_host", "")
45
+ self.smtp_port = config.get("smtp_port", 587)
46
+ self.username = config.get("username", "")
47
+ self.password = config.get("password", "")
48
+ self.access_token = config.get("access_token", "")
49
+
50
+ if config.get("enabled", False):
51
+ if self.provider == "smtp":
52
+ if not self.smtp_host or not self.username or not self.password:
53
+ raise ValueError(
54
+ "EmailConnector (SMTP) requires smtp_host, username, password"
55
+ )
56
+ elif self.provider in ("gmail", "outlook"):
57
+ if not self.access_token:
58
+ raise ValueError(
59
+ f"EmailConnector ({self.provider}) requires access_token"
60
+ )
61
+
62
+ self._client: httpx.AsyncClient | None = None
63
+
64
+ @property
65
+ def info(self) -> ConnectorInfo:
66
+ return ConnectorInfo(
67
+ connector_id="email",
68
+ platform="email",
69
+ category="messaging",
70
+ capabilities=frozenset(
71
+ [
72
+ ConnectorCapability.SEND_MESSAGE,
73
+ ConnectorCapability.RECEIVE_MESSAGE,
74
+ ConnectorCapability.FILE_UPLOAD,
75
+ ConnectorCapability.FILE_DOWNLOAD,
76
+ ConnectorCapability.SEARCH,
77
+ ConnectorCapability.LIST_DATA,
78
+ ]
79
+ ),
80
+ auth_type="oauth2" if self.provider != "smtp" else "api_key",
81
+ rate_limit_per_minute=100,
82
+ supports_threads=True,
83
+ supports_attachments=True,
84
+ supports_reactions=False,
85
+ )
86
+
87
+ async def _get_client(self) -> httpx.AsyncClient:
88
+ if self._client is None:
89
+ headers = {}
90
+ if self.access_token:
91
+ headers["Authorization"] = f"Bearer {self.access_token}"
92
+ self._client = httpx.AsyncClient(timeout=30.0, headers=headers)
93
+ return self._client
94
+
95
+ async def health(self) -> bool:
96
+ """Check if email service is accessible."""
97
+ try:
98
+ if self.provider == "smtp":
99
+ # Test SMTP connection
100
+ loop = asyncio.get_event_loop()
101
+ return await loop.run_in_executor(None, self._test_smtp)
102
+ elif self.provider == "gmail":
103
+ client = await self._get_client()
104
+ response = await client.get(
105
+ "https://gmail.googleapis.com/gmail/v1/users/me/profile"
106
+ )
107
+ return response.status_code == 200
108
+ elif self.provider == "outlook":
109
+ client = await self._get_client()
110
+ response = await client.get(
111
+ "https://graph.microsoft.com/v1.0/me"
112
+ )
113
+ return response.status_code == 200
114
+ except Exception as e:
115
+ logger.error(f"Email health check failed: {e}")
116
+ return False
117
+
118
+ return False
119
+
120
+ def _test_smtp(self) -> bool:
121
+ """Test SMTP connection."""
122
+ try:
123
+ server = smtplib.SMTP(self.smtp_host, self.smtp_port)
124
+ server.starttls()
125
+ server.login(self.username, self.password)
126
+ server.quit()
127
+ return True
128
+ except Exception:
129
+ return False
130
+
131
+ async def _send_impl(self, message: OutboundMessage) -> str:
132
+ """Send an email."""
133
+ if self.provider == "smtp":
134
+ return await self._send_smtp(message)
135
+ elif self.provider == "gmail":
136
+ return await self._send_gmail(message)
137
+ elif self.provider == "outlook":
138
+ return await self._send_outlook(message)
139
+
140
+ raise ValueError(f"Unknown provider: {self.provider}")
141
+
142
+ async def _send_smtp(self, message: OutboundMessage) -> str:
143
+ """Send via SMTP."""
144
+ loop = asyncio.get_event_loop()
145
+
146
+ def _send():
147
+ msg = MIMEMultipart()
148
+ msg["From"] = self.username
149
+ msg["To"] = message.recipient_id
150
+ msg["Subject"] = message.attachments[0].get("subject", "No Subject") if message.attachments else "No Subject"
151
+
152
+ msg.attach(MIMEText(message.content, "plain"))
153
+
154
+ server = smtplib.SMTP(self.smtp_host, self.smtp_port)
155
+ server.starttls()
156
+ server.login(self.username, self.password)
157
+ server.sendmail(self.username, message.recipient_id, msg.as_string())
158
+ server.quit()
159
+
160
+ return "smtp_sent"
161
+
162
+ return await loop.run_in_executor(None, _send)
163
+
164
+ async def _send_gmail(self, message: OutboundMessage) -> str:
165
+ """Send via Gmail API."""
166
+ import base64
167
+
168
+ client = await self._get_client()
169
+
170
+ # Build raw email
171
+ email_content = f"To: {message.recipient_id}\r\nSubject: Message\r\n\r\n{message.content}"
172
+ raw = base64.urlsafe_b64encode(email_content.encode()).decode()
173
+
174
+ response = await client.post(
175
+ "https://gmail.googleapis.com/gmail/v1/users/me/messages/send",
176
+ json={"raw": raw},
177
+ )
178
+
179
+ if response.status_code not in (200, 201):
180
+ raise Exception(f"Gmail send failed: {response.text}")
181
+
182
+ data = response.json()
183
+ return data.get("id", "")
184
+
185
+ async def _send_outlook(self, message: OutboundMessage) -> str:
186
+ """Send via Outlook/Graph API."""
187
+ client = await self._get_client()
188
+
189
+ payload = {
190
+ "message": {
191
+ "subject": "Message",
192
+ "body": {
193
+ "contentType": "Text",
194
+ "content": message.content,
195
+ },
196
+ "toRecipients": [
197
+ {"emailAddress": {"address": message.recipient_id}}
198
+ ],
199
+ }
200
+ }
201
+
202
+ response = await client.post(
203
+ "https://graph.microsoft.com/v1.0/me/sendMail",
204
+ json=payload,
205
+ )
206
+
207
+ if response.status_code not in (200, 202):
208
+ raise Exception(f"Outlook send failed: {response.text}")
209
+
210
+ return "outlook_sent"
211
+
212
+ async def _receive_impl(self) -> AsyncIterator[InboundMessage]:
213
+ """Receive emails (via API providers)."""
214
+ if self.provider == "gmail":
215
+ async for msg in self._receive_gmail():
216
+ yield msg
217
+ elif self.provider == "outlook":
218
+ async for msg in self._receive_outlook():
219
+ yield msg
220
+ else:
221
+ # SMTP receive would require IMAP
222
+ return
223
+
224
+ async def _receive_gmail(self) -> AsyncIterator[InboundMessage]:
225
+ """Receive via Gmail API."""
226
+ client = await self._get_client()
227
+
228
+ response = await client.get(
229
+ "https://gmail.googleapis.com/gmail/v1/users/me/messages",
230
+ params={"maxResults": 20},
231
+ )
232
+
233
+ if response.status_code != 200:
234
+ return
235
+
236
+ data = response.json()
237
+
238
+ for msg_ref in data.get("messages", []):
239
+ msg_response = await client.get(
240
+ f"https://gmail.googleapis.com/gmail/v1/users/me/messages/{msg_ref['id']}"
241
+ )
242
+
243
+ if msg_response.status_code != 200:
244
+ continue
245
+
246
+ msg = msg_response.json()
247
+ headers = {h["name"]: h["value"] for h in msg.get("payload", {}).get("headers", [])}
248
+
249
+ yield InboundMessage(
250
+ message_id=msg.get("id", ""),
251
+ platform="email",
252
+ sender_id=headers.get("From", ""),
253
+ sender_name=headers.get("From", ""),
254
+ content=msg.get("snippet", ""),
255
+ thread_id=msg.get("threadId"),
256
+ raw=msg,
257
+ )
258
+
259
+ async def _receive_outlook(self) -> AsyncIterator[InboundMessage]:
260
+ """Receive via Outlook/Graph API."""
261
+ client = await self._get_client()
262
+
263
+ response = await client.get(
264
+ "https://graph.microsoft.com/v1.0/me/messages",
265
+ params={"$top": 20},
266
+ )
267
+
268
+ if response.status_code != 200:
269
+ return
270
+
271
+ data = response.json()
272
+
273
+ for msg in data.get("value", []):
274
+ sender = msg.get("from", {}).get("emailAddress", {})
275
+
276
+ yield InboundMessage(
277
+ message_id=msg.get("id", ""),
278
+ platform="email",
279
+ sender_id=sender.get("address", ""),
280
+ sender_name=sender.get("name", sender.get("address", "")),
281
+ content=msg.get("bodyPreview", ""),
282
+ thread_id=msg.get("conversationId"),
283
+ raw=msg,
284
+ )
285
+
286
+ async def list_inbox(
287
+ self,
288
+ max_results: int = 20,
289
+ query: str = "",
290
+ ) -> list[dict]:
291
+ """List inbox messages."""
292
+ if self.provider == "gmail":
293
+ client = await self._get_client()
294
+ params = {"maxResults": max_results}
295
+ if query:
296
+ params["q"] = query
297
+
298
+ response = await client.get(
299
+ "https://gmail.googleapis.com/gmail/v1/users/me/messages",
300
+ params=params,
301
+ )
302
+
303
+ if response.status_code != 200:
304
+ raise Exception(f"Gmail list failed: {response.text}")
305
+
306
+ return response.json().get("messages", [])
307
+
308
+ elif self.provider == "outlook":
309
+ client = await self._get_client()
310
+ params = {"$top": max_results}
311
+ if query:
312
+ params["$search"] = f'"{query}"'
313
+
314
+ response = await client.get(
315
+ "https://graph.microsoft.com/v1.0/me/messages",
316
+ params=params,
317
+ )
318
+
319
+ if response.status_code != 200:
320
+ raise Exception(f"Outlook list failed: {response.text}")
321
+
322
+ return response.json().get("value", [])
323
+
324
+ return []
325
+
326
+ async def search_emails(self, query: str) -> list[dict]:
327
+ """Search emails."""
328
+ return await self.list_inbox(max_results=50, query=query)
329
+
330
+ async def get_thread(self, thread_id: str) -> dict:
331
+ """Get an email thread."""
332
+ client = await self._get_client()
333
+
334
+ if self.provider == "gmail":
335
+ response = await client.get(
336
+ f"https://gmail.googleapis.com/gmail/v1/users/me/threads/{thread_id}"
337
+ )
338
+ elif self.provider == "outlook":
339
+ response = await client.get(
340
+ f"https://graph.microsoft.com/v1.0/me/messages",
341
+ params={"$filter": f"conversationId eq '{thread_id}'"},
342
+ )
343
+ else:
344
+ return {}
345
+
346
+ if response.status_code != 200:
347
+ raise Exception(f"Get thread failed: {response.text}")
348
+
349
+ return response.json()
350
+
351
+ async def create_draft(
352
+ self,
353
+ to: str,
354
+ subject: str,
355
+ body: str,
356
+ ) -> str:
357
+ """Create an email draft."""
358
+ client = await self._get_client()
359
+
360
+ if self.provider == "gmail":
361
+ import base64
362
+
363
+ email_content = f"To: {to}\r\nSubject: {subject}\r\n\r\n{body}"
364
+ raw = base64.urlsafe_b64encode(email_content.encode()).decode()
365
+
366
+ response = await client.post(
367
+ "https://gmail.googleapis.com/gmail/v1/users/me/drafts",
368
+ json={"message": {"raw": raw}},
369
+ )
370
+
371
+ elif self.provider == "outlook":
372
+ response = await client.post(
373
+ "https://graph.microsoft.com/v1.0/me/messages",
374
+ json={
375
+ "subject": subject,
376
+ "body": {"contentType": "Text", "content": body},
377
+ "toRecipients": [{"emailAddress": {"address": to}}],
378
+ },
379
+ )
380
+ else:
381
+ raise ValueError("Draft creation requires Gmail or Outlook provider")
382
+
383
+ if response.status_code not in (200, 201):
384
+ raise Exception(f"Create draft failed: {response.text}")
385
+
386
+ return response.json().get("id", "")
387
+
388
+ async def _search_impl(self, query: str, **kwargs) -> list[dict]:
389
+ """Search implementation."""
390
+ return await self.search_emails(query)
391
+
392
+ async def _list_impl(
393
+ self, resource_type: str, filters: dict, **kwargs
394
+ ) -> list[dict]:
395
+ """List implementation."""
396
+ if resource_type == "inbox":
397
+ return await self.list_inbox(
398
+ max_results=filters.get("max_results", 20),
399
+ query=filters.get("query", ""),
400
+ )
401
+ return []