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,474 @@
1
+ """
2
+ Voice Provider Abstraction Layer.
3
+ Unified interface for STT (Speech-to-Text) and TTS (Text-to-Speech) providers.
4
+ """
5
+ from __future__ import annotations
6
+
7
+ import os
8
+ import logging
9
+ from abc import ABC, abstractmethod
10
+ from dataclasses import dataclass
11
+ from pathlib import Path
12
+ from typing import AsyncIterator
13
+
14
+ import httpx
15
+
16
+ logger = logging.getLogger(__name__)
17
+
18
+
19
+ @dataclass
20
+ class TranscriptionResult:
21
+ """Speech-to-text result."""
22
+ text: str
23
+ language: str | None = None
24
+ duration: float | None = None
25
+ words: list[dict] | None = None
26
+ raw: dict | None = None
27
+
28
+
29
+ @dataclass
30
+ class SpeechResult:
31
+ """Text-to-speech result."""
32
+ audio: bytes
33
+ format: str = "mp3"
34
+ duration: float | None = None
35
+
36
+
37
+ class VoiceProvider(ABC):
38
+ """Base class for voice providers."""
39
+ name: str = "base"
40
+
41
+
42
+ class STTProvider(VoiceProvider):
43
+ """Abstract base for Speech-to-Text providers."""
44
+
45
+ @abstractmethod
46
+ async def transcribe(
47
+ self,
48
+ audio: bytes | Path,
49
+ language: str | None = None,
50
+ **kwargs,
51
+ ) -> TranscriptionResult:
52
+ """Transcribe audio to text."""
53
+ pass
54
+
55
+
56
+ class TTSProvider(VoiceProvider):
57
+ """Abstract base for Text-to-Speech providers."""
58
+
59
+ @abstractmethod
60
+ async def synthesize(
61
+ self,
62
+ text: str,
63
+ voice: str | None = None,
64
+ **kwargs,
65
+ ) -> SpeechResult:
66
+ """Synthesize text to speech."""
67
+ pass
68
+
69
+ async def stream(
70
+ self,
71
+ text: str,
72
+ voice: str | None = None,
73
+ **kwargs,
74
+ ) -> AsyncIterator[bytes]:
75
+ """Stream synthesized audio. Default: yield full audio."""
76
+ result = await self.synthesize(text, voice, **kwargs)
77
+ yield result.audio
78
+
79
+
80
+ # ═══════════════════════════════════════════════════════════════════════════════
81
+ # OPENAI WHISPER (STT)
82
+ # ═══════════════════════════════════════════════════════════════════════════════
83
+
84
+ class WhisperProvider(STTProvider):
85
+ """OpenAI Whisper speech-to-text."""
86
+
87
+ name = "whisper"
88
+
89
+ MODELS = ["whisper-1"]
90
+
91
+ def __init__(self, api_key: str | None = None):
92
+ self.api_key = api_key or os.environ.get("OPENAI_API_KEY")
93
+ self.base_url = "https://api.openai.com/v1"
94
+
95
+ async def transcribe(
96
+ self,
97
+ audio: bytes | Path,
98
+ language: str | None = None,
99
+ model: str = "whisper-1",
100
+ **kwargs,
101
+ ) -> TranscriptionResult:
102
+ if isinstance(audio, Path):
103
+ audio = audio.read_bytes()
104
+
105
+ async with httpx.AsyncClient() as client:
106
+ files = {"file": ("audio.mp3", audio, "audio/mpeg")}
107
+ data = {"model": model}
108
+ if language:
109
+ data["language"] = language
110
+
111
+ response = await client.post(
112
+ f"{self.base_url}/audio/transcriptions",
113
+ headers={"Authorization": f"Bearer {self.api_key}"},
114
+ files=files,
115
+ data=data,
116
+ timeout=120.0,
117
+ )
118
+ response.raise_for_status()
119
+ result = response.json()
120
+
121
+ return TranscriptionResult(
122
+ text=result["text"],
123
+ language=result.get("language"),
124
+ duration=result.get("duration"),
125
+ raw=result,
126
+ )
127
+
128
+
129
+ # ═══════════════════════════════════════════════════════════════════════════════
130
+ # DEEPGRAM (STT)
131
+ # ═══════════════════════════════════════════════════════════════════════════════
132
+
133
+ class DeepgramProvider(STTProvider):
134
+ """Deepgram speech-to-text."""
135
+
136
+ name = "deepgram"
137
+
138
+ def __init__(self, api_key: str | None = None):
139
+ self.api_key = api_key or os.environ.get("DEEPGRAM_API_KEY")
140
+ self.base_url = "https://api.deepgram.com/v1"
141
+
142
+ async def transcribe(
143
+ self,
144
+ audio: bytes | Path,
145
+ language: str | None = None,
146
+ model: str = "nova-2",
147
+ **kwargs,
148
+ ) -> TranscriptionResult:
149
+ if isinstance(audio, Path):
150
+ audio = audio.read_bytes()
151
+
152
+ params = {"model": model, "smart_format": "true"}
153
+ if language:
154
+ params["language"] = language
155
+
156
+ async with httpx.AsyncClient() as client:
157
+ response = await client.post(
158
+ f"{self.base_url}/listen",
159
+ headers={
160
+ "Authorization": f"Token {self.api_key}",
161
+ "Content-Type": "audio/mpeg",
162
+ },
163
+ params=params,
164
+ content=audio,
165
+ timeout=120.0,
166
+ )
167
+ response.raise_for_status()
168
+ result = response.json()
169
+
170
+ transcript = result["results"]["channels"][0]["alternatives"][0]
171
+
172
+ return TranscriptionResult(
173
+ text=transcript["transcript"],
174
+ language=result["results"].get("detected_language"),
175
+ duration=result["metadata"].get("duration"),
176
+ words=transcript.get("words"),
177
+ raw=result,
178
+ )
179
+
180
+
181
+ # ═══════════════════════════════════════════════════════════════════════════════
182
+ # ASSEMBLYAI (STT)
183
+ # ═══════════════════════════════════════════════════════════════════════════════
184
+
185
+ class AssemblyAIProvider(STTProvider):
186
+ """AssemblyAI speech-to-text."""
187
+
188
+ name = "assemblyai"
189
+
190
+ def __init__(self, api_key: str | None = None):
191
+ self.api_key = api_key or os.environ.get("ASSEMBLYAI_API_KEY")
192
+ self.base_url = "https://api.assemblyai.com/v2"
193
+
194
+ async def transcribe(
195
+ self,
196
+ audio: bytes | Path,
197
+ language: str | None = None,
198
+ **kwargs,
199
+ ) -> TranscriptionResult:
200
+ if isinstance(audio, Path):
201
+ audio = audio.read_bytes()
202
+
203
+ async with httpx.AsyncClient() as client:
204
+ # Upload audio
205
+ upload_response = await client.post(
206
+ f"{self.base_url}/upload",
207
+ headers={"Authorization": self.api_key},
208
+ content=audio,
209
+ timeout=120.0,
210
+ )
211
+ upload_response.raise_for_status()
212
+ audio_url = upload_response.json()["upload_url"]
213
+
214
+ # Start transcription
215
+ transcript_request = {"audio_url": audio_url}
216
+ if language:
217
+ transcript_request["language_code"] = language
218
+
219
+ transcript_response = await client.post(
220
+ f"{self.base_url}/transcript",
221
+ headers={"Authorization": self.api_key},
222
+ json=transcript_request,
223
+ timeout=30.0,
224
+ )
225
+ transcript_response.raise_for_status()
226
+ transcript_id = transcript_response.json()["id"]
227
+
228
+ # Poll for completion
229
+ import asyncio
230
+ while True:
231
+ status_response = await client.get(
232
+ f"{self.base_url}/transcript/{transcript_id}",
233
+ headers={"Authorization": self.api_key},
234
+ timeout=30.0,
235
+ )
236
+ status_response.raise_for_status()
237
+ result = status_response.json()
238
+
239
+ if result["status"] == "completed":
240
+ return TranscriptionResult(
241
+ text=result["text"],
242
+ language=result.get("language_code"),
243
+ duration=result.get("audio_duration"),
244
+ words=result.get("words"),
245
+ raw=result,
246
+ )
247
+ elif result["status"] == "error":
248
+ raise RuntimeError(f"Transcription failed: {result.get('error')}")
249
+
250
+ await asyncio.sleep(1)
251
+
252
+
253
+ # ═══════════════════════════════════════════════════════════════════════════════
254
+ # ELEVENLABS (TTS)
255
+ # ═══════════════════════════════════════════════════════════════════════════════
256
+
257
+ class ElevenLabsProvider(TTSProvider):
258
+ """ElevenLabs text-to-speech."""
259
+
260
+ name = "elevenlabs"
261
+
262
+ VOICES = {
263
+ "rachel": "21m00Tcm4TlvDq8ikWAM",
264
+ "domi": "AZnzlk1XvdvUeBnXmlld",
265
+ "bella": "EXAVITQu4vr4xnSDxMaL",
266
+ "antoni": "ErXwobaYiN019PkySvjV",
267
+ "elli": "MF3mGyEYCl7XYWbV9V6O",
268
+ "josh": "TxGEqnHWrfWFTfGW9XjX",
269
+ "arnold": "VR6AewLTigWG4xSOukaG",
270
+ "adam": "pNInz6obpgDQGcFmaJgB",
271
+ "sam": "yoZ06aMxZJJ28mfd3POQ",
272
+ "george": "JBFqnCBsd6RMkjVDRZzb",
273
+ }
274
+
275
+ def __init__(self, api_key: str | None = None):
276
+ self.api_key = api_key or os.environ.get("ELEVENLABS_API_KEY")
277
+ self.base_url = "https://api.elevenlabs.io/v1"
278
+
279
+ async def synthesize(
280
+ self,
281
+ text: str,
282
+ voice: str | None = None,
283
+ model: str = "eleven_turbo_v2_5",
284
+ **kwargs,
285
+ ) -> SpeechResult:
286
+ voice_id = self.VOICES.get(voice, voice) if voice else self.VOICES["george"]
287
+
288
+ async with httpx.AsyncClient() as client:
289
+ response = await client.post(
290
+ f"{self.base_url}/text-to-speech/{voice_id}",
291
+ headers={
292
+ "xi-api-key": self.api_key,
293
+ "Content-Type": "application/json",
294
+ },
295
+ json={
296
+ "text": text,
297
+ "model_id": model,
298
+ "voice_settings": {
299
+ "stability": 0.35,
300
+ "similarity_boost": 0.75,
301
+ "style": 0.65,
302
+ "use_speaker_boost": True,
303
+ },
304
+ },
305
+ timeout=60.0,
306
+ )
307
+ response.raise_for_status()
308
+
309
+ return SpeechResult(audio=response.content, format="mp3")
310
+
311
+ async def stream(
312
+ self,
313
+ text: str,
314
+ voice: str | None = None,
315
+ model: str = "eleven_turbo_v2_5",
316
+ **kwargs,
317
+ ) -> AsyncIterator[bytes]:
318
+ voice_id = self.VOICES.get(voice, voice) if voice else self.VOICES["george"]
319
+
320
+ async with httpx.AsyncClient() as client:
321
+ async with client.stream(
322
+ "POST",
323
+ f"{self.base_url}/text-to-speech/{voice_id}/stream",
324
+ headers={
325
+ "xi-api-key": self.api_key,
326
+ "Content-Type": "application/json",
327
+ },
328
+ json={
329
+ "text": text,
330
+ "model_id": model,
331
+ "voice_settings": {
332
+ "stability": 0.35,
333
+ "similarity_boost": 0.75,
334
+ "style": 0.65,
335
+ "use_speaker_boost": True,
336
+ },
337
+ },
338
+ timeout=60.0,
339
+ ) as response:
340
+ async for chunk in response.aiter_bytes():
341
+ yield chunk
342
+
343
+
344
+ # ═══════════════════════════════════════════════════════════════════════════════
345
+ # GOOGLE TTS
346
+ # ═══════════════════════════════════════════════════════════════════════════════
347
+
348
+ class GoogleTTSProvider(TTSProvider):
349
+ """Google Cloud Text-to-Speech."""
350
+
351
+ name = "google-tts"
352
+
353
+ VOICES = {
354
+ "en-US-Standard-A": {"languageCode": "en-US", "name": "en-US-Standard-A"},
355
+ "en-US-Standard-B": {"languageCode": "en-US", "name": "en-US-Standard-B"},
356
+ "en-US-Wavenet-A": {"languageCode": "en-US", "name": "en-US-Wavenet-A"},
357
+ "en-US-Neural2-A": {"languageCode": "en-US", "name": "en-US-Neural2-A"},
358
+ "en-GB-Standard-A": {"languageCode": "en-GB", "name": "en-GB-Standard-A"},
359
+ "en-GB-Wavenet-A": {"languageCode": "en-GB", "name": "en-GB-Wavenet-A"},
360
+ }
361
+
362
+ def __init__(self, api_key: str | None = None):
363
+ self.api_key = api_key or os.environ.get("GOOGLE_API_KEY")
364
+ self.base_url = "https://texttospeech.googleapis.com/v1"
365
+
366
+ async def synthesize(
367
+ self,
368
+ text: str,
369
+ voice: str | None = None,
370
+ **kwargs,
371
+ ) -> SpeechResult:
372
+ voice_config = self.VOICES.get(voice, self.VOICES["en-US-Wavenet-A"])
373
+
374
+ async with httpx.AsyncClient() as client:
375
+ response = await client.post(
376
+ f"{self.base_url}/text:synthesize",
377
+ params={"key": self.api_key},
378
+ json={
379
+ "input": {"text": text},
380
+ "voice": voice_config,
381
+ "audioConfig": {"audioEncoding": "MP3"},
382
+ },
383
+ timeout=60.0,
384
+ )
385
+ response.raise_for_status()
386
+ result = response.json()
387
+
388
+ import base64
389
+ audio = base64.b64decode(result["audioContent"])
390
+
391
+ return SpeechResult(audio=audio, format="mp3")
392
+
393
+
394
+ # ═══════════════════════════════════════════════════════════════════════════════
395
+ # OPENAI TTS
396
+ # ═══════════════════════════════════════════════════════════════════════════════
397
+
398
+ class OpenAITTSProvider(TTSProvider):
399
+ """OpenAI Text-to-Speech."""
400
+
401
+ name = "openai-tts"
402
+
403
+ VOICES = ["alloy", "echo", "fable", "onyx", "nova", "shimmer"]
404
+
405
+ def __init__(self, api_key: str | None = None):
406
+ self.api_key = api_key or os.environ.get("OPENAI_API_KEY")
407
+ self.base_url = "https://api.openai.com/v1"
408
+
409
+ async def synthesize(
410
+ self,
411
+ text: str,
412
+ voice: str | None = None,
413
+ model: str = "tts-1",
414
+ **kwargs,
415
+ ) -> SpeechResult:
416
+ voice = voice if voice in self.VOICES else "alloy"
417
+
418
+ async with httpx.AsyncClient() as client:
419
+ response = await client.post(
420
+ f"{self.base_url}/audio/speech",
421
+ headers={"Authorization": f"Bearer {self.api_key}"},
422
+ json={
423
+ "model": model,
424
+ "input": text,
425
+ "voice": voice,
426
+ "response_format": "mp3",
427
+ },
428
+ timeout=60.0,
429
+ )
430
+ response.raise_for_status()
431
+
432
+ return SpeechResult(audio=response.content, format="mp3")
433
+
434
+
435
+ # ═══════════════════════════════════════════════════════════════════════════════
436
+ # PROVIDER FACTORY
437
+ # ═══════════════════════════════════════════════════════════════════════════════
438
+
439
+ STT_PROVIDERS: dict[str, type[STTProvider]] = {
440
+ "whisper": WhisperProvider,
441
+ "openai": WhisperProvider,
442
+ "deepgram": DeepgramProvider,
443
+ "assemblyai": AssemblyAIProvider,
444
+ }
445
+
446
+ TTS_PROVIDERS: dict[str, type[TTSProvider]] = {
447
+ "elevenlabs": ElevenLabsProvider,
448
+ "google": GoogleTTSProvider,
449
+ "openai": OpenAITTSProvider,
450
+ }
451
+
452
+
453
+ def get_stt_provider(name: str | None = None, **kwargs) -> STTProvider:
454
+ """Get a speech-to-text provider by name."""
455
+ name = name or os.environ.get("HYPERCLAW_STT_PROVIDER", "whisper")
456
+ name = name.lower()
457
+
458
+ if name not in STT_PROVIDERS:
459
+ available = ", ".join(sorted(STT_PROVIDERS.keys()))
460
+ raise ValueError(f"Unknown STT provider: {name}. Available: {available}")
461
+
462
+ return STT_PROVIDERS[name](**kwargs)
463
+
464
+
465
+ def get_tts_provider(name: str | None = None, **kwargs) -> TTSProvider:
466
+ """Get a text-to-speech provider by name."""
467
+ name = name or os.environ.get("HYPERCLAW_TTS_PROVIDER", "elevenlabs")
468
+ name = name.lower()
469
+
470
+ if name not in TTS_PROVIDERS:
471
+ available = ", ".join(sorted(TTS_PROVIDERS.keys()))
472
+ raise ValueError(f"Unknown TTS provider: {name}. Available: {available}")
473
+
474
+ return TTS_PROVIDERS[name](**kwargs)