largestack 1.0.0__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 (312) hide show
  1. largestack/__init__.py +128 -0
  2. largestack/_a2a/__init__.py +548 -0
  3. largestack/_a2a/multimodal.py +273 -0
  4. largestack/_a2a/v03.py +442 -0
  5. largestack/_cli/cli_v09.py +393 -0
  6. largestack/_cli/cli_v120.py +328 -0
  7. largestack/_cli/cli_v130_compliance.py +504 -0
  8. largestack/_cli/commands.py +117 -0
  9. largestack/_cli/dev_server.py +346 -0
  10. largestack/_cli/main.py +1016 -0
  11. largestack/_cli/scaffold.py +1265 -0
  12. largestack/_compliance/dpdp_breach.py +472 -0
  13. largestack/_core/__init__.py +0 -0
  14. largestack/_core/a2a_server.py +73 -0
  15. largestack/_core/a2a_v1.py +252 -0
  16. largestack/_core/ag_ui.py +190 -0
  17. largestack/_core/agent_roles.py +241 -0
  18. largestack/_core/agui_v1.py +292 -0
  19. largestack/_core/browser_tool.py +94 -0
  20. largestack/_core/budget.py +254 -0
  21. largestack/_core/builtin_tools/__init__.py +11 -0
  22. largestack/_core/builtin_tools/_url_validator.py +95 -0
  23. largestack/_core/builtin_tools/browser.py +56 -0
  24. largestack/_core/builtin_tools/calc.py +128 -0
  25. largestack/_core/builtin_tools/code.py +183 -0
  26. largestack/_core/builtin_tools/db.py +147 -0
  27. largestack/_core/builtin_tools/files.py +35 -0
  28. largestack/_core/builtin_tools/http_tool.py +81 -0
  29. largestack/_core/builtin_tools/shell.py +122 -0
  30. largestack/_core/builtin_tools/time_tool.py +9 -0
  31. largestack/_core/builtin_tools/voice.py +43 -0
  32. largestack/_core/builtin_tools/web.py +103 -0
  33. largestack/_core/circuit_breaker.py +74 -0
  34. largestack/_core/citation_sandbox.py +341 -0
  35. largestack/_core/code_agent.py +147 -0
  36. largestack/_core/code_agent_v11.py +256 -0
  37. largestack/_core/composio_adapter.py +60 -0
  38. largestack/_core/config.py +102 -0
  39. largestack/_core/context.py +49 -0
  40. largestack/_core/cost.py +66 -0
  41. largestack/_core/database.py +334 -0
  42. largestack/_core/e2b_sandbox.py +175 -0
  43. largestack/_core/engine.py +565 -0
  44. largestack/_core/events.py +33 -0
  45. largestack/_core/feature_flags.py +42 -0
  46. largestack/_core/gateway.py +342 -0
  47. largestack/_core/health.py +95 -0
  48. largestack/_core/hitl.py +74 -0
  49. largestack/_core/license.py +244 -0
  50. largestack/_core/litellm_router.py +143 -0
  51. largestack/_core/loop_guard.py +62 -0
  52. largestack/_core/mcp_client.py +138 -0
  53. largestack/_core/mcp_server.py +99 -0
  54. largestack/_core/mcp_streamable.py +289 -0
  55. largestack/_core/multiagent.py +468 -0
  56. largestack/_core/optimizer.py +109 -0
  57. largestack/_core/parsers.py +288 -0
  58. largestack/_core/plugin_host.py +58 -0
  59. largestack/_core/prompt_templates.py +261 -0
  60. largestack/_core/providers/__init__.py +28 -0
  61. largestack/_core/providers/ai21_prov.py +17 -0
  62. largestack/_core/providers/anthropic_prov.py +96 -0
  63. largestack/_core/providers/anyscale_prov.py +17 -0
  64. largestack/_core/providers/azure_prov.py +30 -0
  65. largestack/_core/providers/base.py +25 -0
  66. largestack/_core/providers/bedrock_prov.py +104 -0
  67. largestack/_core/providers/cerebras_prov.py +19 -0
  68. largestack/_core/providers/cloudflare_prov.py +23 -0
  69. largestack/_core/providers/cohere_prov.py +75 -0
  70. largestack/_core/providers/databricks_prov.py +20 -0
  71. largestack/_core/providers/deepseek_prov.py +13 -0
  72. largestack/_core/providers/fireworks_prov.py +22 -0
  73. largestack/_core/providers/google_prov.py +104 -0
  74. largestack/_core/providers/groq_prov.py +29 -0
  75. largestack/_core/providers/lepton_prov.py +20 -0
  76. largestack/_core/providers/litellm_prov.py +227 -0
  77. largestack/_core/providers/mistral_prov.py +8 -0
  78. largestack/_core/providers/nvidia_prov.py +22 -0
  79. largestack/_core/providers/ollama_prov.py +76 -0
  80. largestack/_core/providers/openai_prov.py +144 -0
  81. largestack/_core/providers/openrouter_prov.py +30 -0
  82. largestack/_core/providers/perplexity_prov.py +25 -0
  83. largestack/_core/providers/replicate_prov.py +19 -0
  84. largestack/_core/providers/sambanova_prov.py +19 -0
  85. largestack/_core/providers/together_prov.py +30 -0
  86. largestack/_core/providers/voyage_prov.py +29 -0
  87. largestack/_core/providers/xai_prov.py +24 -0
  88. largestack/_core/reasoning.py +358 -0
  89. largestack/_core/registry.py +52 -0
  90. largestack/_core/resilience.py +306 -0
  91. largestack/_core/semantic_cache.py +90 -0
  92. largestack/_core/session.py +255 -0
  93. largestack/_core/smart_router.py +81 -0
  94. largestack/_core/steering.py +31 -0
  95. largestack/_core/streaming.py +34 -0
  96. largestack/_core/structured.py +139 -0
  97. largestack/_core/structured_output.py +190 -0
  98. largestack/_core/tools.py +335 -0
  99. largestack/_core/typed_agent.py +282 -0
  100. largestack/_core/versioning.py +76 -0
  101. largestack/_core/vision.py +38 -0
  102. largestack/_core/voice_agent.py +57 -0
  103. largestack/_core/ws_stream.py +62 -0
  104. largestack/_core/yaml_agent.py +133 -0
  105. largestack/_core/yaml_schema.py +275 -0
  106. largestack/_dashboard/README.md +64 -0
  107. largestack/_dashboard/__init__.py +0 -0
  108. largestack/_dashboard/api.py +185 -0
  109. largestack/_dashboard/app.py +576 -0
  110. largestack/_dashboard/auth.py +60 -0
  111. largestack/_dashboard/frontend.jsx +446 -0
  112. largestack/_dashboard/rate_limit.py +250 -0
  113. largestack/_dashboard/spa/App.jsx +446 -0
  114. largestack/_dashboard/spa/README.md +59 -0
  115. largestack/_dashboard/spa/index.html +17 -0
  116. largestack/_dashboard/spa/main.jsx +5 -0
  117. largestack/_distributed/__init__.py +3 -0
  118. largestack/_distributed/event_sourcing.py +343 -0
  119. largestack/_distributed/outbox.py +248 -0
  120. largestack/_distributed/saga.py +270 -0
  121. largestack/_enterprise/__init__.py +7 -0
  122. largestack/_enterprise/audit.py +217 -0
  123. largestack/_enterprise/billing.py +198 -0
  124. largestack/_enterprise/canary.py +223 -0
  125. largestack/_enterprise/payment.py +347 -0
  126. largestack/_enterprise/rbac.py +430 -0
  127. largestack/_enterprise/session_store.py +281 -0
  128. largestack/_enterprise/sso.py +362 -0
  129. largestack/_enterprise/tenant.py +139 -0
  130. largestack/_enterprise/white_label.py +27 -0
  131. largestack/_eval/__init__.py +0 -0
  132. largestack/_eval/alerts.py +350 -0
  133. largestack/_eval/extensions_v130.py +229 -0
  134. largestack/_eval/pr_diff.py +324 -0
  135. largestack/_eval/runner.py +343 -0
  136. largestack/_evals/__init__.py +1 -0
  137. largestack/_evals/adapters.py +80 -0
  138. largestack/_evals/runner.py +55 -0
  139. largestack/_guard/__init__.py +11 -0
  140. largestack/_guard/agent_identity.py +73 -0
  141. largestack/_guard/config.py +83 -0
  142. largestack/_guard/hallucination.py +238 -0
  143. largestack/_guard/injection.py +308 -0
  144. largestack/_guard/inter_agent_auth.py +53 -0
  145. largestack/_guard/kill_switch.py +42 -0
  146. largestack/_guard/memory_integrity.py +67 -0
  147. largestack/_guard/nli_hallucination.py +98 -0
  148. largestack/_guard/pii.py +182 -0
  149. largestack/_guard/pii_ml.py +102 -0
  150. largestack/_guard/pipeline.py +89 -0
  151. largestack/_guard/policy.py +163 -0
  152. largestack/_guard/prompt_guard.py +66 -0
  153. largestack/_guard/provider_policy.py +52 -0
  154. largestack/_guard/redis_kill_switch.py +55 -0
  155. largestack/_guard/tool_access.py +86 -0
  156. largestack/_guard/tool_policy.py +126 -0
  157. largestack/_guard/topic.py +172 -0
  158. largestack/_guard/toxicity.py +186 -0
  159. largestack/_indic/__init__.py +341 -0
  160. largestack/_integrations/__init__.py +121 -0
  161. largestack/_integrations/cohere_embed.py +128 -0
  162. largestack/_integrations/embeddings_v09.py +456 -0
  163. largestack/_integrations/github.py +178 -0
  164. largestack/_integrations/hf_embed.py +143 -0
  165. largestack/_integrations/indian_toolkits.py +795 -0
  166. largestack/_integrations/jina_embed.py +137 -0
  167. largestack/_integrations/jira.py +129 -0
  168. largestack/_integrations/langchain_compat.py +227 -0
  169. largestack/_integrations/langfuse_adapter.py +347 -0
  170. largestack/_integrations/linear.py +137 -0
  171. largestack/_integrations/litellm_bridge.py +300 -0
  172. largestack/_integrations/mcp_adapter.py +116 -0
  173. largestack/_integrations/notion.py +144 -0
  174. largestack/_integrations/openai_embeddings.py +76 -0
  175. largestack/_integrations/openapi_toolkit.py +334 -0
  176. largestack/_integrations/pandas_toolkit.py +158 -0
  177. largestack/_integrations/phoenix_adapter.py +318 -0
  178. largestack/_integrations/postgres.py +96 -0
  179. largestack/_integrations/razorpay_toolkit.py +365 -0
  180. largestack/_integrations/registry.py +176 -0
  181. largestack/_integrations/sheets.py +178 -0
  182. largestack/_integrations/slack.py +90 -0
  183. largestack/_integrations/sql_toolkit.py +219 -0
  184. largestack/_integrations/stripe_toolkit.py +277 -0
  185. largestack/_integrations/toolkits_v09.py +466 -0
  186. largestack/_integrations/voyage_embed.py +135 -0
  187. largestack/_loaders/__init__.py +970 -0
  188. largestack/_loaders/llamaparse.py +246 -0
  189. largestack/_loaders/loaders_v09.py +696 -0
  190. largestack/_loaders/office.py +234 -0
  191. largestack/_loaders/semantic_chunking.py +251 -0
  192. largestack/_memory/__init__.py +8 -0
  193. largestack/_memory/buffer.py +117 -0
  194. largestack/_memory/compression.py +77 -0
  195. largestack/_memory/episodic.py +50 -0
  196. largestack/_memory/external_adapters.py +104 -0
  197. largestack/_memory/graph.py +289 -0
  198. largestack/_memory/long_term.py +853 -0
  199. largestack/_memory/observational.py +90 -0
  200. largestack/_memory/postgres_store.py +355 -0
  201. largestack/_memory/procedural.py +152 -0
  202. largestack/_memory/semantic.py +151 -0
  203. largestack/_memory/shared.py +38 -0
  204. largestack/_memory/tools.py +365 -0
  205. largestack/_memory/vector_store.py +423 -0
  206. largestack/_observability/__init__.py +0 -0
  207. largestack/_observability/otel.py +218 -0
  208. largestack/_observe/__init__.py +5 -0
  209. largestack/_observe/anomaly.py +65 -0
  210. largestack/_observe/auto_trace.py +55 -0
  211. largestack/_observe/cost_dashboard.py +34 -0
  212. largestack/_observe/event_replay.py +57 -0
  213. largestack/_observe/gen_ai_instrumentor.py +87 -0
  214. largestack/_observe/log_redaction.py +83 -0
  215. largestack/_observe/metrics.py +107 -0
  216. largestack/_observe/otel_export.py +235 -0
  217. largestack/_observe/otel_helpers.py +181 -0
  218. largestack/_observe/sqlite_exporter.py +260 -0
  219. largestack/_observe/tracer.py +71 -0
  220. largestack/_observe/traces_db.py +130 -0
  221. largestack/_orchestrate/__init__.py +10 -0
  222. largestack/_orchestrate/dag.py +137 -0
  223. largestack/_orchestrate/debate.py +136 -0
  224. largestack/_orchestrate/flows.py +45 -0
  225. largestack/_orchestrate/map_reduce.py +80 -0
  226. largestack/_orchestrate/parallel.py +156 -0
  227. largestack/_orchestrate/router.py +95 -0
  228. largestack/_orchestrate/sequential.py +145 -0
  229. largestack/_orchestrate/state_machine.py +67 -0
  230. largestack/_orchestrate/supervisor.py +54 -0
  231. largestack/_orchestrate/swarm.py +127 -0
  232. largestack/_rag/__init__.py +8 -0
  233. largestack/_rag/chunker.py +100 -0
  234. largestack/_rag/crag.py +39 -0
  235. largestack/_rag/embedder.py +297 -0
  236. largestack/_rag/eval.py +251 -0
  237. largestack/_rag/graph_rag.py +73 -0
  238. largestack/_rag/pipeline.py +59 -0
  239. largestack/_rag/query_engines.py +228 -0
  240. largestack/_rag/reranker.py +263 -0
  241. largestack/_rag/retriever.py +141 -0
  242. largestack/_rag/summary_index.py +251 -0
  243. largestack/_rag/vector_store.py +148 -0
  244. largestack/_ratelimit/__init__.py +363 -0
  245. largestack/_rerankers/__init__.py +435 -0
  246. largestack/_retrievers/__init__.py +817 -0
  247. largestack/_security/__init__.py +6 -0
  248. largestack/_security/code_sandbox.py +190 -0
  249. largestack/_security/e2b_bridge.py +391 -0
  250. largestack/_security/encryption.py +210 -0
  251. largestack/_security/mtls.py +378 -0
  252. largestack/_security/network.py +234 -0
  253. largestack/_security/permissions.py +226 -0
  254. largestack/_security/sandbox.py +41 -0
  255. largestack/_security/sbom.py +194 -0
  256. largestack/_security/vault.py +299 -0
  257. largestack/_state/__init__.py +2 -0
  258. largestack/_state/checkpoint.py +38 -0
  259. largestack/_state/durable.py +73 -0
  260. largestack/_state/postgres_checkpointer.py +215 -0
  261. largestack/_studio/__init__.py +1081 -0
  262. largestack/_studio/compare.py +409 -0
  263. largestack/_studio/pyodide_eval.py +354 -0
  264. largestack/_templates/__init__.py +1 -0
  265. largestack/_templates/code_generator.py +39 -0
  266. largestack/_templates/content_factory.py +26 -0
  267. largestack/_templates/customer_support.py +33 -0
  268. largestack/_templates/data_pipeline.py +32 -0
  269. largestack/_templates/research_pipeline.py +45 -0
  270. largestack/_test/__init__.py +6 -0
  271. largestack/_test/assertions.py +56 -0
  272. largestack/_test/benchmark.py +237 -0
  273. largestack/_test/ci_gates.py +49 -0
  274. largestack/_test/eval_metrics.py +81 -0
  275. largestack/_test/llm_judge.py +151 -0
  276. largestack/_test/recorder.py +30 -0
  277. largestack/_test/regression.py +81 -0
  278. largestack/_test/replayer.py +34 -0
  279. largestack/_test/synthetic.py +66 -0
  280. largestack/_vectorstores/__init__.py +2000 -0
  281. largestack/_workflow/__init__.py +15 -0
  282. largestack/_workflow/checkpoint.py +240 -0
  283. largestack/_workflow/graph.py +316 -0
  284. largestack/_workflow/interrupt.py +233 -0
  285. largestack/_workflow/sub_graph.py +246 -0
  286. largestack/agent.py +425 -0
  287. largestack/autonomous_builder.py +676 -0
  288. largestack/decorators.py +444 -0
  289. largestack/errors.py +92 -0
  290. largestack/guardrails.py +22 -0
  291. largestack/memory.py +23 -0
  292. largestack/migrations/__init__.py +14 -0
  293. largestack/migrations/config_v1_to_v1_1.py +49 -0
  294. largestack/migrations/memory_v1_to_v1_1.py +46 -0
  295. largestack/migrations/project.py +40 -0
  296. largestack/migrations/trace_db_v1_to_v1_1.py +53 -0
  297. largestack/observability.py +165 -0
  298. largestack/orchestrator.py +369 -0
  299. largestack/provider_matrix.py +81 -0
  300. largestack/py.typed +0 -0
  301. largestack/rag.py +11 -0
  302. largestack/serve.py +345 -0
  303. largestack/team.py +135 -0
  304. largestack/testing.py +354 -0
  305. largestack/types.py +80 -0
  306. largestack/workflow.py +190 -0
  307. largestack-1.0.0.dist-info/METADATA +436 -0
  308. largestack-1.0.0.dist-info/RECORD +312 -0
  309. largestack-1.0.0.dist-info/WHEEL +5 -0
  310. largestack-1.0.0.dist-info/entry_points.txt +2 -0
  311. largestack-1.0.0.dist-info/licenses/LICENSE +17 -0
  312. largestack-1.0.0.dist-info/top_level.txt +1 -0
largestack/__init__.py ADDED
@@ -0,0 +1,128 @@
1
+ """Largestack AI — Universal Multi-Agent AI Framework.
2
+
3
+ from largestack import Agent, Team, Workflow, tool
4
+
5
+ @tool
6
+ async def search(query: str) -> str:
7
+ return f"Results: {query}"
8
+
9
+ agent = Agent(name="r", tools=[search], llm="deepseek/deepseek-chat")
10
+ result = await agent.run("Analyze trends")
11
+
12
+ # Structured output
13
+ result = await agent.run("Analyze", response_model=MySchema)
14
+
15
+ # Multi-agent with error recovery
16
+ team = Team(agents=[a1, a2, a3], on_error="skip", retries_per_agent=2)
17
+ """
18
+ __version__ = "1.0.0"
19
+
20
+ # v0.3.7: auto-install logging redaction filter in production. Strips
21
+ # API keys / Bearer tokens / JWTs from log records before they're emitted.
22
+ # Disable via LARGESTACK_DISABLE_LOG_REDACTION=1 (not recommended).
23
+ import os as _os
24
+ if _os.environ.get("LARGESTACK_DISABLE_LOG_REDACTION", "").lower() not in ("1", "true", "yes"):
25
+ try:
26
+ from largestack._observe.log_redaction import install_redaction_filter
27
+ install_redaction_filter()
28
+ except Exception:
29
+ pass # Never let log-filter install break package import
30
+ del _os
31
+
32
+ _BENCHMARK_SUBPROCESS = __import__("os").environ.get("LARGESTACK_BENCHMARK_SUBPROCESS", "").lower() in ("1", "true", "yes")
33
+ if _BENCHMARK_SUBPROCESS:
34
+ # Keep benchmark subprocesses lightweight and independent of optional ML imports.
35
+ from largestack.agent import Agent
36
+ from largestack.testing import TestModel
37
+ __all__ = ["Agent", "TestModel"]
38
+ else:
39
+ from largestack.agent import Agent
40
+ from largestack.team import Team
41
+ from largestack.workflow import Workflow
42
+ from largestack.orchestrator import Orchestrator, OrchestratorResult
43
+ from largestack._core.tools import tool
44
+ from largestack._core.streaming import StreamHandler
45
+ from largestack._core.context import AgentContext
46
+ from largestack._core.session import SessionStore
47
+ from largestack._core.hitl import HumanInTheLoop
48
+ from largestack._core.registry import AgentRegistry
49
+ from largestack._guard.tool_access import ToolAccessPolicy
50
+ from largestack._guard.agent_identity import AgentIdentityManager
51
+ from largestack._guard.memory_integrity import MemoryIntegrityChecker
52
+ from largestack._guard.inter_agent_auth import InterAgentAuth
53
+ from largestack._core.ag_ui import AGUIServer
54
+
55
+ from largestack._core.steering import (
56
+ steer_before_tool, steer_after_model,
57
+ proceed, guide, interrupt, accept, discard,
58
+ )
59
+
60
+ from largestack._guard.pipeline import GuardrailPipeline as Guardrails
61
+ from largestack.guardrails import create_guardrails
62
+
63
+ from largestack._memory.buffer import ConversationMemory
64
+ from largestack._memory.episodic import EpisodicMemory
65
+ from largestack._memory.observational import ObservationalMemory
66
+ from largestack._memory.procedural import ProceduralMemory
67
+ from largestack._memory.semantic import SemanticMemory
68
+ from largestack._memory.graph import GraphMemory
69
+ from largestack._memory.shared import SharedMemorySpace
70
+ from largestack.memory import create_memory
71
+ from largestack.rag import create_rag
72
+ from largestack.observability import Monitor, FeedbackRecord
73
+ from largestack.provider_matrix import provider_support_matrix, get_provider_capabilities, tool_capable_providers
74
+ from largestack.autonomous_builder import (
75
+ AutonomousProjectBuilder,
76
+ BuilderBudget,
77
+ BuildReport,
78
+ GeneratedFile,
79
+ NoOpMemory,
80
+ PatchSet,
81
+ ProjectBuildPlan,
82
+ ProjectSpec,
83
+ RepairAttempt,
84
+ ValidationResult,
85
+ )
86
+
87
+ # Decorator API (PydanticAI-style) — v0.1.1.3
88
+ from largestack.decorators import (
89
+ Agent as TypedAgent,
90
+ RunContext,
91
+ ModelRetry,
92
+ AgentRunResult,
93
+ ToolDefinition,
94
+ )
95
+
96
+ # Testing utilities
97
+ from largestack.testing import (
98
+ TestModel, FunctionModel, capture_run_messages,
99
+ disable_model_requests, enable_model_requests, block_model_requests,
100
+ ALLOW_MODEL_REQUESTS,
101
+ )
102
+
103
+ from largestack.types import AgentResult, ToolCall, ToolResult, LLMResponse, CostEstimate
104
+ from largestack.errors import (
105
+ LargestackError, BudgetExceededError, LoopDetectedError,
106
+ ProviderError, GuardrailBlockedError, KillSwitchActivatedError,
107
+ ToolExecutionError, ToolPermissionError, ModelRequestsBlockedError,
108
+ )
109
+
110
+ __all__ = [
111
+ "Agent", "Team", "Workflow", "Orchestrator", "OrchestratorResult", "tool", "StreamHandler",
112
+ "TypedAgent", "RunContext", "ModelRetry", "AgentRunResult", "ToolDefinition",
113
+ "TestModel", "FunctionModel", "capture_run_messages",
114
+ "disable_model_requests", "enable_model_requests", "block_model_requests",
115
+ "ALLOW_MODEL_REQUESTS",
116
+ "AgentContext", "SessionStore", "HumanInTheLoop", "AgentRegistry", "AGUIServer", "ToolAccessPolicy", "AgentIdentityManager", "MemoryIntegrityChecker", "InterAgentAuth",
117
+ "steer_before_tool", "steer_after_model", "proceed", "guide", "interrupt", "accept", "discard",
118
+ "Guardrails", "create_guardrails",
119
+ "ConversationMemory", "EpisodicMemory", "ObservationalMemory",
120
+ "ProceduralMemory", "SemanticMemory", "GraphMemory", "SharedMemorySpace",
121
+ "create_memory", "create_rag", "Monitor", "FeedbackRecord", "provider_support_matrix", "get_provider_capabilities", "tool_capable_providers",
122
+ "AutonomousProjectBuilder", "BuilderBudget", "BuildReport", "GeneratedFile", "NoOpMemory",
123
+ "PatchSet", "ProjectBuildPlan", "ProjectSpec", "RepairAttempt", "ValidationResult",
124
+ "AgentResult", "ToolCall", "ToolResult", "LLMResponse", "CostEstimate",
125
+ "LargestackError", "BudgetExceededError", "LoopDetectedError",
126
+ "ProviderError", "GuardrailBlockedError", "KillSwitchActivatedError",
127
+ "ToolExecutionError", "ToolPermissionError", "ModelRequestsBlockedError",
128
+ ]
@@ -0,0 +1,548 @@
1
+ """A2A (Agent2Agent) Protocol adapter (v0.12.0).
2
+
3
+ Closes the Google ADK / cross-framework interop gap. A2A v1.0 was
4
+ donated to the Linux Foundation and is in production at 150+ orgs
5
+ including SAP, ServiceNow, Salesforce, Workday.
6
+
7
+ A2A complements MCP:
8
+ - MCP — connects agents to **tools and data**
9
+ - A2A — connects agents to **other agents**
10
+
11
+ This module implements:
12
+
13
+ 1. ``AgentCard`` — the discovery manifest. Lists what an agent can do.
14
+ 2. ``A2AServer`` — exposes a LARGESTACK agent at an HTTP endpoint conforming
15
+ to the A2A Task interface.
16
+ 3. ``A2AClient`` — invokes a remote A2A agent via its AgentCard.
17
+ 4. ``A2ATask`` — task lifecycle types (submitted → working → completed/failed).
18
+
19
+ Spec reference: https://a2a-protocol.org/
20
+
21
+ This is a **lightweight reference implementation** of the protocol.
22
+ For full v0.3+ features (gRPC, security card signing) production users
23
+ can install the official SDK and use LARGESTACK agents as task handlers.
24
+
25
+ Zero external deps for the core types + client. Server uses ``aiohttp``
26
+ if available; falls back to a stdlib-only test server.
27
+ """
28
+ from __future__ import annotations
29
+ import asyncio
30
+ import json
31
+ import logging
32
+ import time
33
+ import uuid
34
+ from dataclasses import asdict, dataclass, field
35
+ from typing import Any, Awaitable, Callable, Literal
36
+
37
+ log = logging.getLogger("largestack.a2a")
38
+
39
+
40
+ # -------------------- Domain types --------------------
41
+
42
+ TaskState = Literal[
43
+ "submitted", "working", "input-required", "completed",
44
+ "failed", "canceled",
45
+ ]
46
+
47
+
48
+
49
+ def _require_http_url(url: str) -> str:
50
+ """Allow only absolute HTTP/HTTPS URLs before network requests."""
51
+ from urllib.parse import urlparse
52
+
53
+ parsed = urlparse(url)
54
+ if parsed.scheme not in {"http", "https"} or not parsed.netloc:
55
+ raise ValueError("URL must be absolute and use http or https")
56
+ return url
57
+
58
+
59
+ @dataclass
60
+ class AgentSkill:
61
+ """A single capability advertised by an agent."""
62
+ id: str
63
+ name: str
64
+ description: str = ""
65
+ tags: list[str] = field(default_factory=list)
66
+ examples: list[str] = field(default_factory=list)
67
+
68
+
69
+ @dataclass
70
+ class AgentCapabilities:
71
+ """What protocol features the agent supports."""
72
+ streaming: bool = False
73
+ push_notifications: bool = False
74
+ state_transition_history: bool = True
75
+
76
+
77
+ @dataclass
78
+ class AgentCard:
79
+ """A2A agent discovery manifest. Served at ``/.well-known/agent.json``.
80
+
81
+ Spec: https://a2a-protocol.org/latest/specification/agent-card/
82
+ """
83
+ name: str
84
+ description: str
85
+ url: str # base URL where the agent is hosted
86
+ version: str = "1.0.0"
87
+ protocol_version: str = "0.3.0"
88
+ capabilities: AgentCapabilities = field(default_factory=AgentCapabilities)
89
+ skills: list[AgentSkill] = field(default_factory=list)
90
+ default_input_modes: list[str] = field(
91
+ default_factory=lambda: ["text/plain"],
92
+ )
93
+ default_output_modes: list[str] = field(
94
+ default_factory=lambda: ["text/plain"],
95
+ )
96
+ # Provider info (org publishing this agent)
97
+ provider_name: str = ""
98
+ provider_url: str = ""
99
+ # Authentication required to invoke (none / api-key / oauth2)
100
+ authentication: dict[str, Any] = field(default_factory=dict)
101
+
102
+ def to_dict(self) -> dict[str, Any]:
103
+ return asdict(self)
104
+
105
+ @classmethod
106
+ def from_dict(cls, d: dict[str, Any]) -> "AgentCard":
107
+ # Tolerant: drop unknown keys
108
+ valid = {f.name for f in cls.__dataclass_fields__.values()}
109
+ clean = {k: v for k, v in d.items() if k in valid}
110
+
111
+ # Re-hydrate nested types
112
+ if "capabilities" in clean and isinstance(clean["capabilities"], dict):
113
+ cap_valid = {
114
+ f.name for f in AgentCapabilities.__dataclass_fields__.values()
115
+ }
116
+ clean["capabilities"] = AgentCapabilities(**{
117
+ k: v for k, v in clean["capabilities"].items()
118
+ if k in cap_valid
119
+ })
120
+ if "skills" in clean and isinstance(clean["skills"], list):
121
+ skill_valid = {
122
+ f.name for f in AgentSkill.__dataclass_fields__.values()
123
+ }
124
+ clean["skills"] = [
125
+ AgentSkill(**{k: v for k, v in s.items() if k in skill_valid})
126
+ if isinstance(s, dict) else s
127
+ for s in clean["skills"]
128
+ ]
129
+ return cls(**clean)
130
+
131
+
132
+ @dataclass
133
+ class A2AMessage:
134
+ """A single message in a task conversation."""
135
+ role: Literal["user", "agent"]
136
+ parts: list[dict[str, Any]] = field(default_factory=list)
137
+ timestamp: float = field(default_factory=lambda: time.time())
138
+
139
+ @classmethod
140
+ def text(cls, role: Literal["user", "agent"], text: str) -> "A2AMessage":
141
+ return cls(role=role, parts=[{"type": "text", "text": text}])
142
+
143
+ def get_text(self) -> str:
144
+ """Concatenate all text parts."""
145
+ return "\n".join(
146
+ p.get("text", "") for p in self.parts
147
+ if p.get("type") == "text"
148
+ )
149
+
150
+
151
+ @dataclass
152
+ class A2ATask:
153
+ """A2A task lifecycle object."""
154
+ id: str
155
+ state: TaskState = "submitted"
156
+ messages: list[A2AMessage] = field(default_factory=list)
157
+ artifacts: list[dict[str, Any]] = field(default_factory=list)
158
+ created_at: float = field(default_factory=lambda: time.time())
159
+ updated_at: float = field(default_factory=lambda: time.time())
160
+ error: str = ""
161
+ metadata: dict[str, Any] = field(default_factory=dict)
162
+
163
+ def add_message(self, msg: A2AMessage) -> None:
164
+ self.messages.append(msg)
165
+ self.updated_at = time.time()
166
+
167
+ def add_artifact(self, artifact: dict[str, Any]) -> None:
168
+ self.artifacts.append(artifact)
169
+ self.updated_at = time.time()
170
+
171
+ def transition(self, state: TaskState, error: str = "") -> None:
172
+ self.state = state
173
+ self.updated_at = time.time()
174
+ if error:
175
+ self.error = error
176
+
177
+ def to_dict(self) -> dict[str, Any]:
178
+ return {
179
+ "id": self.id,
180
+ "state": self.state,
181
+ "messages": [
182
+ {
183
+ "role": m.role, "parts": m.parts,
184
+ "timestamp": m.timestamp,
185
+ }
186
+ for m in self.messages
187
+ ],
188
+ "artifacts": self.artifacts,
189
+ "created_at": self.created_at,
190
+ "updated_at": self.updated_at,
191
+ "error": self.error,
192
+ "metadata": self.metadata,
193
+ }
194
+
195
+
196
+ # -------------------- Server --------------------
197
+
198
+ # Type signature for the agent handler callable that the server wraps
199
+ AgentHandler = Callable[[str, A2ATask], Awaitable[str]]
200
+
201
+
202
+ class A2AServer:
203
+ """Exposes a LARGESTACK agent as an A2A-compliant HTTP endpoint.
204
+
205
+ Provides:
206
+ - ``GET /.well-known/agent.json`` → AgentCard
207
+ - ``POST /tasks/send`` → submit a new task (sync)
208
+ - ``GET /tasks/{id}`` → query task status
209
+ - ``POST /tasks/{id}/cancel`` → cancel a task
210
+
211
+ Args:
212
+ card: the ``AgentCard`` describing this agent
213
+ handler: async function ``(input_text, task) -> output_text``
214
+ task_ttl_seconds: how long completed tasks are retained (default 1hr)
215
+ """
216
+
217
+ def __init__(
218
+ self,
219
+ *,
220
+ card: AgentCard,
221
+ handler: AgentHandler,
222
+ task_ttl_seconds: float = 3600.0,
223
+ ):
224
+ self.card = card
225
+ self.handler = handler
226
+ self.task_ttl_seconds = task_ttl_seconds
227
+ self._tasks: dict[str, A2ATask] = {}
228
+ self._lock = asyncio.Lock()
229
+
230
+ async def submit_task(
231
+ self, input_text: str,
232
+ *,
233
+ task_id: str | None = None,
234
+ metadata: dict[str, Any] | None = None,
235
+ ) -> A2ATask:
236
+ """Submit a new task. Returns the completed (or failed) task."""
237
+ task = A2ATask(
238
+ id=task_id or str(uuid.uuid4()),
239
+ metadata=metadata or {},
240
+ )
241
+ task.add_message(A2AMessage.text("user", input_text))
242
+
243
+ async with self._lock:
244
+ self._tasks[task.id] = task
245
+
246
+ task.transition("working")
247
+ try:
248
+ output = await self.handler(input_text, task)
249
+ task.add_message(A2AMessage.text("agent", str(output)))
250
+ task.transition("completed")
251
+ except asyncio.CancelledError:
252
+ task.transition("canceled")
253
+ raise
254
+ except Exception as e:
255
+ task.transition("failed", error=str(e))
256
+ log.exception(f"task {task.id} failed")
257
+
258
+ return task
259
+
260
+ async def get_task(self, task_id: str) -> A2ATask | None:
261
+ async with self._lock:
262
+ return self._tasks.get(task_id)
263
+
264
+ async def cancel_task(self, task_id: str) -> bool:
265
+ async with self._lock:
266
+ task = self._tasks.get(task_id)
267
+ if not task:
268
+ return False
269
+ if task.state in ("completed", "failed", "canceled"):
270
+ return False
271
+ task.transition("canceled")
272
+ return True
273
+
274
+ async def purge_expired_tasks(self) -> int:
275
+ """Remove tasks older than ``task_ttl_seconds``."""
276
+ async with self._lock:
277
+ now = time.time()
278
+ to_delete = [
279
+ tid for tid, t in self._tasks.items()
280
+ if t.state in ("completed", "failed", "canceled")
281
+ and (now - t.updated_at) > self.task_ttl_seconds
282
+ ]
283
+ for tid in to_delete:
284
+ del self._tasks[tid]
285
+ return len(to_delete)
286
+
287
+ # -------------------- HTTP request handlers --------------------
288
+
289
+ async def handle_request(
290
+ self, method: str, path: str,
291
+ body: dict[str, Any] | None = None,
292
+ ) -> tuple[int, dict[str, Any]]:
293
+ """Generic request dispatcher. Returns (status_code, body_dict).
294
+
295
+ Implementers can wire this into aiohttp / FastAPI / starlette.
296
+ """
297
+ # Discovery
298
+ if method == "GET" and path == "/.well-known/agent.json":
299
+ return 200, self.card.to_dict()
300
+
301
+ # Submit task
302
+ if method == "POST" and path == "/tasks/send":
303
+ body = body or {}
304
+ input_text = body.get("input", "")
305
+ if not input_text:
306
+ # Fall back to first user message text
307
+ msg = body.get("message", {})
308
+ if isinstance(msg, dict):
309
+ parts = msg.get("parts", [])
310
+ input_text = "\n".join(
311
+ p.get("text", "") for p in parts
312
+ if isinstance(p, dict) and p.get("type") == "text"
313
+ )
314
+ if not input_text:
315
+ return 400, {"error": "input is required"}
316
+ task = await self.submit_task(
317
+ input_text,
318
+ task_id=body.get("id"),
319
+ metadata=body.get("metadata") or {},
320
+ )
321
+ return 200, task.to_dict()
322
+
323
+ # Query task
324
+ if method == "GET" and path.startswith("/tasks/"):
325
+ task_id = path[len("/tasks/"):].split("/")[0]
326
+ task = await self.get_task(task_id)
327
+ if not task:
328
+ return 404, {"error": f"task {task_id} not found"}
329
+ return 200, task.to_dict()
330
+
331
+ # Cancel task
332
+ if method == "POST" and path.startswith("/tasks/") \
333
+ and path.endswith("/cancel"):
334
+ task_id = path[len("/tasks/"):-len("/cancel")]
335
+ ok = await self.cancel_task(task_id)
336
+ if not ok:
337
+ return 400, {"error": "cannot cancel task"}
338
+ return 200, {"id": task_id, "state": "canceled"}
339
+
340
+ return 404, {"error": "unknown endpoint"}
341
+
342
+
343
+ # -------------------- Client --------------------
344
+
345
+ class A2AClient:
346
+ """Client for invoking a remote A2A agent.
347
+
348
+ Uses ``aiohttp`` if available, falls back to ``urllib`` (sync).
349
+
350
+ Args:
351
+ base_url: base URL of the remote agent (e.g. ``https://agent.example.com``)
352
+ api_key: optional bearer token
353
+ timeout: per-request timeout in seconds
354
+ """
355
+
356
+ def __init__(
357
+ self,
358
+ *,
359
+ base_url: str,
360
+ api_key: str = "",
361
+ timeout: float = 30.0,
362
+ ):
363
+ self.base_url = base_url.rstrip("/")
364
+ self.api_key = api_key
365
+ self.timeout = timeout
366
+
367
+ @property
368
+ def _headers(self) -> dict[str, str]:
369
+ h = {"Content-Type": "application/json"}
370
+ if self.api_key:
371
+ h["Authorization"] = f"Bearer {self.api_key}"
372
+ return h
373
+
374
+ async def _post_json(
375
+ self, path: str, body: dict[str, Any],
376
+ ) -> tuple[int, dict[str, Any]]:
377
+ url = self.base_url + path
378
+ try:
379
+ import aiohttp
380
+ except ImportError:
381
+ return await self._post_json_urllib(url, body)
382
+
383
+ async with aiohttp.ClientSession(headers=self._headers) as s:
384
+ async with s.post(
385
+ url, json=body, timeout=aiohttp.ClientTimeout(
386
+ total=self.timeout,
387
+ ),
388
+ ) as resp:
389
+ return resp.status, await resp.json()
390
+
391
+ async def _get_json(
392
+ self, path: str,
393
+ ) -> tuple[int, dict[str, Any]]:
394
+ url = self.base_url + path
395
+ try:
396
+ import aiohttp
397
+ except ImportError:
398
+ return await self._get_json_urllib(url)
399
+
400
+ async with aiohttp.ClientSession(headers=self._headers) as s:
401
+ async with s.get(
402
+ url, timeout=aiohttp.ClientTimeout(total=self.timeout),
403
+ ) as resp:
404
+ return resp.status, await resp.json()
405
+
406
+ async def _post_json_urllib(
407
+ self, url: str, body: dict[str, Any],
408
+ ) -> tuple[int, dict[str, Any]]:
409
+ """stdlib fallback. Synchronous, run in thread."""
410
+ import urllib.error
411
+ import urllib.request
412
+
413
+ def _do():
414
+ req = urllib.request.Request(
415
+ url,
416
+ data=json.dumps(body).encode(),
417
+ headers=self._headers,
418
+ method="POST",
419
+ )
420
+ try:
421
+ with urllib.request.urlopen(req, timeout=self.timeout) as r: # nosec B310
422
+ return r.status, json.loads(r.read().decode())
423
+ except urllib.error.HTTPError as e:
424
+ try:
425
+ return e.code, json.loads(e.read().decode())
426
+ except Exception:
427
+ return e.code, {"error": str(e)}
428
+
429
+ return await asyncio.to_thread(_do)
430
+
431
+ async def _get_json_urllib(
432
+ self, url: str,
433
+ ) -> tuple[int, dict[str, Any]]:
434
+ import urllib.error
435
+ import urllib.request
436
+
437
+ def _do():
438
+ req = urllib.request.Request(
439
+ url, headers=self._headers, method="GET",
440
+ )
441
+ try:
442
+ with urllib.request.urlopen(req, timeout=self.timeout) as r: # nosec B310
443
+ return r.status, json.loads(r.read().decode())
444
+ except urllib.error.HTTPError as e:
445
+ try:
446
+ return e.code, json.loads(e.read().decode())
447
+ except Exception:
448
+ return e.code, {"error": str(e)}
449
+
450
+ return await asyncio.to_thread(_do)
451
+
452
+ # -------------------- Public API --------------------
453
+
454
+ async def discover(self) -> AgentCard:
455
+ """Fetch the agent's AgentCard."""
456
+ status, body = await self._get_json("/.well-known/agent.json")
457
+ if status != 200:
458
+ raise RuntimeError(
459
+ f"discover failed: HTTP {status} - {body.get('error', '')}"
460
+ )
461
+ return AgentCard.from_dict(body)
462
+
463
+ async def send_task(
464
+ self, input_text: str, *,
465
+ task_id: str | None = None,
466
+ metadata: dict[str, Any] | None = None,
467
+ ) -> A2ATask:
468
+ """Submit a task and wait for completion."""
469
+ body: dict[str, Any] = {"input": input_text}
470
+ if task_id:
471
+ body["id"] = task_id
472
+ if metadata:
473
+ body["metadata"] = metadata
474
+ status, resp = await self._post_json("/tasks/send", body)
475
+ if status != 200:
476
+ raise RuntimeError(
477
+ f"send_task failed: HTTP {status} - {resp.get('error', '')}"
478
+ )
479
+ return _task_from_dict(resp)
480
+
481
+ async def get_task(self, task_id: str) -> A2ATask | None:
482
+ status, resp = await self._get_json(f"/tasks/{task_id}")
483
+ if status == 404:
484
+ return None
485
+ if status != 200:
486
+ raise RuntimeError(
487
+ f"get_task failed: HTTP {status} - {resp.get('error', '')}"
488
+ )
489
+ return _task_from_dict(resp)
490
+
491
+ async def cancel_task(self, task_id: str) -> bool:
492
+ status, _ = await self._post_json(f"/tasks/{task_id}/cancel", {})
493
+ return status == 200
494
+
495
+
496
+ def _task_from_dict(d: dict[str, Any]) -> A2ATask:
497
+ """Hydrate an A2ATask from its serialized form."""
498
+ msgs = []
499
+ for m in d.get("messages", []):
500
+ if isinstance(m, dict):
501
+ msgs.append(A2AMessage(
502
+ role=m.get("role", "user"),
503
+ parts=m.get("parts", []),
504
+ timestamp=m.get("timestamp", time.time()),
505
+ ))
506
+ return A2ATask(
507
+ id=d.get("id", ""),
508
+ state=d.get("state", "submitted"),
509
+ messages=msgs,
510
+ artifacts=d.get("artifacts", []),
511
+ created_at=d.get("created_at", time.time()),
512
+ updated_at=d.get("updated_at", time.time()),
513
+ error=d.get("error", ""),
514
+ metadata=d.get("metadata", {}),
515
+ )
516
+
517
+
518
+ # -------------------- LARGESTACK-side helpers --------------------
519
+
520
+ def expose_largestack_agent(
521
+ largestack_agent,
522
+ *,
523
+ name: str,
524
+ description: str,
525
+ url: str,
526
+ skills: list[AgentSkill] | None = None,
527
+ provider_name: str = "RivaiLabs",
528
+ provider_url: str = "https://rivailabs.com",
529
+ ) -> A2AServer:
530
+ """Convenience: wrap a LARGESTACK Agent as an A2A server.
531
+
532
+ The agent must have an async ``.run(input)`` method that returns an
533
+ object with a ``.content`` attribute (LARGESTACK Agent contract).
534
+ """
535
+ card = AgentCard(
536
+ name=name,
537
+ description=description,
538
+ url=url,
539
+ skills=skills or [],
540
+ provider_name=provider_name,
541
+ provider_url=provider_url,
542
+ )
543
+
544
+ async def handler(input_text: str, task: A2ATask) -> str:
545
+ resp = await largestack_agent.run(input_text)
546
+ return getattr(resp, "content", str(resp))
547
+
548
+ return A2AServer(card=card, handler=handler)